From b48726f8704534e7ee5d545874dc4bdf775b4eb6 Mon Sep 17 00:00:00 2001 From: dslovinsky Date: Thu, 8 Jan 2026 15:51:01 -0800 Subject: [PATCH 01/27] feat: migrate content-indexer to docs repo --- package.json | 18 +- pnpm-lock.yaml | 1580 ++++++++++++++++- scripts/generate-content-index.ts | 44 + src/content-indexer/README.md | 342 ++++ src/content-indexer/__tests__/index.test.ts | 171 ++ .../collectors/__tests__/algolia.test.ts | 177 ++ .../__tests__/navigation-trees.test.ts | 136 ++ .../collectors/__tests__/path-index.test.ts | 144 ++ .../__tests__/processing-context.test.ts | 165 ++ src/content-indexer/collectors/algolia.ts | 82 + .../collectors/navigation-trees.ts | 58 + src/content-indexer/collectors/path-index.ts | 49 + .../collectors/processing-context.ts | 77 + src/content-indexer/constants/changelog.ts | 10 + src/content-indexer/constants/http.ts | 12 + src/content-indexer/constants/links.ts | 2 + src/content-indexer/constants/metadata.ts | 56 + .../core/__tests__/batch-fetcher.test.ts | 203 +++ .../core/__tests__/build-all-outputs.test.ts | 273 +++ .../core/__tests__/content-cache.test.ts | 169 ++ .../core/__tests__/path-builder.test.ts | 93 + .../core/__tests__/scanner.test.ts | 265 +++ src/content-indexer/core/batch-fetcher.ts | 104 ++ src/content-indexer/core/build-all-outputs.ts | 85 + src/content-indexer/core/content-cache.ts | 83 + src/content-indexer/core/path-builder.ts | 58 + src/content-indexer/core/scanner.ts | 85 + src/content-indexer/index.ts | 103 ++ src/content-indexer/types/algolia.ts | 13 + src/content-indexer/types/breadcrumb.ts | 8 + src/content-indexer/types/changelog.ts | 28 + src/content-indexer/types/docsYaml.ts | 91 + src/content-indexer/types/navigation.ts | 37 + src/content-indexer/types/openRpc.ts | 12 + src/content-indexer/types/page.ts | 57 + src/content-indexer/types/pathIndex.ts | 29 + src/content-indexer/types/specs.ts | 14 + .../uploaders/__tests__/algolia.test.ts | 97 + .../uploaders/__tests__/redis.test.ts | 129 ++ src/content-indexer/uploaders/algolia.ts | 99 ++ src/content-indexer/uploaders/redis.ts | 65 + .../__tests__/navigation-helpers.test.ts | 33 + .../utils/__tests__/normalization.test.ts | 122 ++ .../utils/__tests__/openapi.test.ts | 275 +++ .../utils/__tests__/openrpc.test.ts | 71 + .../utils/__tests__/truncate-record.test.ts | 121 ++ src/content-indexer/utils/apiSpecs.ts | 136 ++ src/content-indexer/utils/fetchWithRetries.ts | 35 + src/content-indexer/utils/filesystem.ts | 127 ++ src/content-indexer/utils/github.ts | 202 +++ .../utils/navigation-helpers.ts | 14 + src/content-indexer/utils/normalization.ts | 22 + src/content-indexer/utils/openapi.ts | 149 ++ src/content-indexer/utils/openrpc.ts | 13 + src/content-indexer/utils/redis.ts | 32 + src/content-indexer/utils/test-factories.ts | 46 + src/content-indexer/utils/truncate-record.ts | 70 + .../visitors/__tests__/index.test.ts | 120 ++ .../__tests__/visit-api-reference.test.ts | 260 +++ .../visitors/__tests__/visit-link.test.ts | 76 + .../visitors/__tests__/visit-page.test.ts | 253 +++ .../visitors/__tests__/visit-section.test.ts | 337 ++++ src/content-indexer/visitors/index.ts | 76 + .../__tests__/process-openapi.test.ts | 329 ++++ .../__tests__/process-openrpc.test.ts | 306 ++++ .../visitors/processors/process-openapi.ts | 212 +++ .../visitors/processors/process-openrpc.ts | 109 ++ .../visitors/visit-api-reference.ts | 71 + src/content-indexer/visitors/visit-link.ts | 25 + src/content-indexer/visitors/visit-page.ts | 79 + src/content-indexer/visitors/visit-section.ts | 141 ++ tsconfig.json | 6 +- 72 files changed, 9154 insertions(+), 37 deletions(-) create mode 100644 scripts/generate-content-index.ts create mode 100644 src/content-indexer/README.md create mode 100644 src/content-indexer/__tests__/index.test.ts create mode 100644 src/content-indexer/collectors/__tests__/algolia.test.ts create mode 100644 src/content-indexer/collectors/__tests__/navigation-trees.test.ts create mode 100644 src/content-indexer/collectors/__tests__/path-index.test.ts create mode 100644 src/content-indexer/collectors/__tests__/processing-context.test.ts create mode 100644 src/content-indexer/collectors/algolia.ts create mode 100644 src/content-indexer/collectors/navigation-trees.ts create mode 100644 src/content-indexer/collectors/path-index.ts create mode 100644 src/content-indexer/collectors/processing-context.ts create mode 100644 src/content-indexer/constants/changelog.ts create mode 100644 src/content-indexer/constants/http.ts create mode 100644 src/content-indexer/constants/links.ts create mode 100644 src/content-indexer/constants/metadata.ts create mode 100644 src/content-indexer/core/__tests__/batch-fetcher.test.ts create mode 100644 src/content-indexer/core/__tests__/build-all-outputs.test.ts create mode 100644 src/content-indexer/core/__tests__/content-cache.test.ts create mode 100644 src/content-indexer/core/__tests__/path-builder.test.ts create mode 100644 src/content-indexer/core/__tests__/scanner.test.ts create mode 100644 src/content-indexer/core/batch-fetcher.ts create mode 100644 src/content-indexer/core/build-all-outputs.ts create mode 100644 src/content-indexer/core/content-cache.ts create mode 100644 src/content-indexer/core/path-builder.ts create mode 100644 src/content-indexer/core/scanner.ts create mode 100644 src/content-indexer/index.ts create mode 100644 src/content-indexer/types/algolia.ts create mode 100644 src/content-indexer/types/breadcrumb.ts create mode 100644 src/content-indexer/types/changelog.ts create mode 100644 src/content-indexer/types/docsYaml.ts create mode 100644 src/content-indexer/types/navigation.ts create mode 100644 src/content-indexer/types/openRpc.ts create mode 100644 src/content-indexer/types/page.ts create mode 100644 src/content-indexer/types/pathIndex.ts create mode 100644 src/content-indexer/types/specs.ts create mode 100644 src/content-indexer/uploaders/__tests__/algolia.test.ts create mode 100644 src/content-indexer/uploaders/__tests__/redis.test.ts create mode 100644 src/content-indexer/uploaders/algolia.ts create mode 100644 src/content-indexer/uploaders/redis.ts create mode 100644 src/content-indexer/utils/__tests__/navigation-helpers.test.ts create mode 100644 src/content-indexer/utils/__tests__/normalization.test.ts create mode 100644 src/content-indexer/utils/__tests__/openapi.test.ts create mode 100644 src/content-indexer/utils/__tests__/openrpc.test.ts create mode 100644 src/content-indexer/utils/__tests__/truncate-record.test.ts create mode 100644 src/content-indexer/utils/apiSpecs.ts create mode 100644 src/content-indexer/utils/fetchWithRetries.ts create mode 100644 src/content-indexer/utils/filesystem.ts create mode 100644 src/content-indexer/utils/github.ts create mode 100644 src/content-indexer/utils/navigation-helpers.ts create mode 100644 src/content-indexer/utils/normalization.ts create mode 100644 src/content-indexer/utils/openapi.ts create mode 100644 src/content-indexer/utils/openrpc.ts create mode 100644 src/content-indexer/utils/redis.ts create mode 100644 src/content-indexer/utils/test-factories.ts create mode 100644 src/content-indexer/utils/truncate-record.ts create mode 100644 src/content-indexer/visitors/__tests__/index.test.ts create mode 100644 src/content-indexer/visitors/__tests__/visit-api-reference.test.ts create mode 100644 src/content-indexer/visitors/__tests__/visit-link.test.ts create mode 100644 src/content-indexer/visitors/__tests__/visit-page.test.ts create mode 100644 src/content-indexer/visitors/__tests__/visit-section.test.ts create mode 100644 src/content-indexer/visitors/index.ts create mode 100644 src/content-indexer/visitors/processors/__tests__/process-openapi.test.ts create mode 100644 src/content-indexer/visitors/processors/__tests__/process-openrpc.test.ts create mode 100644 src/content-indexer/visitors/processors/process-openapi.ts create mode 100644 src/content-indexer/visitors/processors/process-openrpc.ts create mode 100644 src/content-indexer/visitors/visit-api-reference.ts create mode 100644 src/content-indexer/visitors/visit-link.ts create mode 100644 src/content-indexer/visitors/visit-page.ts create mode 100644 src/content-indexer/visitors/visit-section.ts diff --git a/package.json b/package.json index 7424d57ef..d43b26928 100644 --- a/package.json +++ b/package.json @@ -34,13 +34,24 @@ "lint:prettier:fix": "pnpm run lint:prettier --write", "lint:broken-links": "lychee .", "add-evm-chain": "ts-node ./scripts/add-evm-chain.ts", - "prepare": "husky" + "prepare": "husky", + "index:main": "tsx scripts/generate-content-index.ts --indexer main --branch main", + "index:main:preview": "tsx scripts/generate-content-index.ts --indexer main --mode preview --branch $(git rev-parse --abbrev-ref HEAD)", + "index:sdk": "tsx scripts/generate-content-index.ts --indexer sdk --branch main", + "index:changelog": "tsx scripts/generate-content-index.ts --indexer changelog --branch main", + "index:watch": "tsx scripts/watch-and-index.ts" }, "dependencies": { "@apidevtools/json-schema-ref-parser": "^12.0.2", "@open-rpc/schema-utils-js": "^2.1.2", + "@upstash/redis": "^1.35.7", + "algoliasearch": "^5.46.1", "fern-api": "3.13.0", + "gray-matter": "^4.0.3", + "js-yaml": "^4.1.1", "json-schema-merge-allof": "^0.8.1", + "lodash-es": "^4.17.21", + "octokit": "^5.0.5", "ts-node": "^10.9.2" }, "devDependencies": { @@ -50,9 +61,11 @@ "@trivago/prettier-plugin-sort-imports": "^5.2.2", "@types/js-yaml": "^4.0.9", "@types/json-schema-merge-allof": "^0.6.5", + "@types/lodash-es": "^4.17.12", "@types/node": "^22.14.1", "@types/react": "^19.1.12", "@typescript-eslint/parser": "^8.31.0", + "chokidar": "^4.0.3", "eslint": "^9.25.1", "eslint-config-prettier": "^10.1.2", "eslint-plugin-mdx": "^3.4.1", @@ -61,12 +74,15 @@ "jiti": "^2.4.2", "lint-staged": "^15.5.1", "onchange": "^7.1.0", + "openapi-types": "^12.1.3", "prettier": "3.4.2", + "vitest": "^4.0.14", "remark-frontmatter": "^5.0.0", "remark-mdx": "^3.1.0", "remark-preset-lint-consistent": "^6.0.1", "remark-preset-lint-markdown-style-guide": "^6.0.1", "remark-preset-lint-recommended": "^7.0.1", + "tsx": "^4.20.6", "typescript": "^5.8.3", "typescript-eslint": "^8.31.0" } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index e06d33c6f..28380aecc 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -14,12 +14,30 @@ importers: '@open-rpc/schema-utils-js': specifier: ^2.1.2 version: 2.1.2 + '@upstash/redis': + specifier: ^1.35.7 + version: 1.36.1 + algoliasearch: + specifier: ^5.46.1 + version: 5.46.2 fern-api: specifier: 3.13.0 version: 3.13.0 + gray-matter: + specifier: ^4.0.3 + version: 4.0.3 + js-yaml: + specifier: ^4.1.1 + version: 4.1.1 json-schema-merge-allof: specifier: ^0.8.1 version: 0.8.1 + lodash-es: + specifier: ^4.17.21 + version: 4.17.22 + octokit: + specifier: ^5.0.5 + version: 5.0.5 ts-node: specifier: ^10.9.2 version: 10.9.2(@types/node@22.14.1)(typescript@5.8.3) @@ -42,6 +60,9 @@ importers: '@types/json-schema-merge-allof': specifier: ^0.6.5 version: 0.6.5 + '@types/lodash-es': + specifier: ^4.17.12 + version: 4.17.12 '@types/node': specifier: ^22.14.1 version: 22.14.1 @@ -51,6 +72,9 @@ importers: '@typescript-eslint/parser': specifier: ^8.31.0 version: 8.31.0(eslint@9.25.1(jiti@2.4.2))(typescript@5.8.3) + chokidar: + specifier: ^4.0.3 + version: 4.0.3 eslint: specifier: ^9.25.1 version: 9.25.1(jiti@2.4.2) @@ -75,6 +99,9 @@ importers: onchange: specifier: ^7.1.0 version: 7.1.0 + openapi-types: + specifier: ^12.1.3 + version: 12.1.3 prettier: specifier: 3.4.2 version: 3.4.2 @@ -93,15 +120,77 @@ importers: remark-preset-lint-recommended: specifier: ^7.0.1 version: 7.0.1 + tsx: + specifier: ^4.20.6 + version: 4.21.0 typescript: specifier: ^5.8.3 version: 5.8.3 typescript-eslint: specifier: ^8.31.0 version: 8.31.0(eslint@9.25.1(jiti@2.4.2))(typescript@5.8.3) + vitest: + specifier: ^4.0.14 + version: 4.0.16(@opentelemetry/api@1.9.0)(@types/node@22.14.1)(jiti@2.4.2)(tsx@4.21.0)(yaml@2.7.1) packages: + '@algolia/abtesting@1.12.2': + resolution: {integrity: sha512-oWknd6wpfNrmRcH0vzed3UPX0i17o4kYLM5OMITyMVM2xLgaRbIafoxL0e8mcrNNb0iORCJA0evnNDKRYth5WQ==} + engines: {node: '>= 14.0.0'} + + '@algolia/client-abtesting@5.46.2': + resolution: {integrity: sha512-oRSUHbylGIuxrlzdPA8FPJuwrLLRavOhAmFGgdAvMcX47XsyM+IOGa9tc7/K5SPvBqn4nhppOCEz7BrzOPWc4A==} + engines: {node: '>= 14.0.0'} + + '@algolia/client-analytics@5.46.2': + resolution: {integrity: sha512-EPBN2Oruw0maWOF4OgGPfioTvd+gmiNwx0HmD9IgmlS+l75DatcBkKOPNJN+0z3wBQWUO5oq602ATxIfmTQ8bA==} + engines: {node: '>= 14.0.0'} + + '@algolia/client-common@5.46.2': + resolution: {integrity: sha512-Hj8gswSJNKZ0oyd0wWissqyasm+wTz1oIsv5ZmLarzOZAp3vFEda8bpDQ8PUhO+DfkbiLyVnAxsPe4cGzWtqkg==} + engines: {node: '>= 14.0.0'} + + '@algolia/client-insights@5.46.2': + resolution: {integrity: sha512-6dBZko2jt8FmQcHCbmNLB0kCV079Mx/DJcySTL3wirgDBUH7xhY1pOuUTLMiGkqM5D8moVZTvTdRKZUJRkrwBA==} + engines: {node: '>= 14.0.0'} + + '@algolia/client-personalization@5.46.2': + resolution: {integrity: sha512-1waE2Uqh/PHNeDXGn/PM/WrmYOBiUGSVxAWqiJIj73jqPqvfzZgzdakHscIVaDl6Cp+j5dwjsZ5LCgaUr6DtmA==} + engines: {node: '>= 14.0.0'} + + '@algolia/client-query-suggestions@5.46.2': + resolution: {integrity: sha512-EgOzTZkyDcNL6DV0V/24+oBJ+hKo0wNgyrOX/mePBM9bc9huHxIY2352sXmoZ648JXXY2x//V1kropF/Spx83w==} + engines: {node: '>= 14.0.0'} + + '@algolia/client-search@5.46.2': + resolution: {integrity: sha512-ZsOJqu4HOG5BlvIFnMU0YKjQ9ZI6r3C31dg2jk5kMWPSdhJpYL9xa5hEe7aieE+707dXeMI4ej3diy6mXdZpgA==} + engines: {node: '>= 14.0.0'} + + '@algolia/ingestion@1.46.2': + resolution: {integrity: sha512-1Uw2OslTWiOFDtt83y0bGiErJYy5MizadV0nHnOoHFWMoDqWW0kQoMFI65pXqRSkVvit5zjXSLik2xMiyQJDWQ==} + engines: {node: '>= 14.0.0'} + + '@algolia/monitoring@1.46.2': + resolution: {integrity: sha512-xk9f+DPtNcddWN6E7n1hyNNsATBCHIqAvVGG2EAGHJc4AFYL18uM/kMTiOKXE/LKDPyy1JhIerrh9oYb7RBrgw==} + engines: {node: '>= 14.0.0'} + + '@algolia/recommend@5.46.2': + resolution: {integrity: sha512-NApbTPj9LxGzNw4dYnZmj2BoXiAc8NmbbH6qBNzQgXklGklt/xldTvu+FACN6ltFsTzoNU6j2mWNlHQTKGC5+Q==} + engines: {node: '>= 14.0.0'} + + '@algolia/requester-browser-xhr@5.46.2': + resolution: {integrity: sha512-ekotpCwpSp033DIIrsTpYlGUCF6momkgupRV/FA3m62SreTSZUKjgK6VTNyG7TtYfq9YFm/pnh65bATP/ZWJEg==} + engines: {node: '>= 14.0.0'} + + '@algolia/requester-fetch@5.46.2': + resolution: {integrity: sha512-gKE+ZFi/6y7saTr34wS0SqYFDcjHW4Wminv8PDZEi0/mE99+hSrbKgJWxo2ztb5eqGirQTgIh1AMVacGGWM1iw==} + engines: {node: '>= 14.0.0'} + + '@algolia/requester-node-http@5.46.2': + resolution: {integrity: sha512-ciPihkletp7ttweJ8Zt+GukSVLp2ANJHU+9ttiSxsJZThXc4Y2yJ8HGVWesW5jN1zrsZsezN71KrMx/iZsOYpg==} + engines: {node: '>= 14.0.0'} + '@apidevtools/json-schema-ref-parser@12.0.2': resolution: {integrity: sha512-SoZWqQz4YMKdw4kEMfG5w6QAy+rntjsoAT1FtvZAnVEnCR4uy9YSuDBNoVAFHgzSz0dJbISLLCSrGR2Zd7bcvA==} engines: {node: '>= 16'} @@ -209,6 +298,162 @@ packages: '@emotion/unitless@0.8.1': resolution: {integrity: sha512-KOEGMu6dmJZtpadb476IsZBclKvILjopjUii3V+7MnXIQCYh8W3NgNcgwo21n9LXZX6EDIKvqfjYxXebDwxKmQ==} + '@esbuild/aix-ppc64@0.27.2': + resolution: {integrity: sha512-GZMB+a0mOMZs4MpDbj8RJp4cw+w1WV5NYD6xzgvzUJ5Ek2jerwfO2eADyI6ExDSUED+1X8aMbegahsJi+8mgpw==} + engines: {node: '>=18'} + cpu: [ppc64] + os: [aix] + + '@esbuild/android-arm64@0.27.2': + resolution: {integrity: sha512-pvz8ZZ7ot/RBphf8fv60ljmaoydPU12VuXHImtAs0XhLLw+EXBi2BLe3OYSBslR4rryHvweW5gmkKFwTiFy6KA==} + engines: {node: '>=18'} + cpu: [arm64] + os: [android] + + '@esbuild/android-arm@0.27.2': + resolution: {integrity: sha512-DVNI8jlPa7Ujbr1yjU2PfUSRtAUZPG9I1RwW4F4xFB1Imiu2on0ADiI/c3td+KmDtVKNbi+nffGDQMfcIMkwIA==} + engines: {node: '>=18'} + cpu: [arm] + os: [android] + + '@esbuild/android-x64@0.27.2': + resolution: {integrity: sha512-z8Ank4Byh4TJJOh4wpz8g2vDy75zFL0TlZlkUkEwYXuPSgX8yzep596n6mT7905kA9uHZsf/o2OJZubl2l3M7A==} + engines: {node: '>=18'} + cpu: [x64] + os: [android] + + '@esbuild/darwin-arm64@0.27.2': + resolution: {integrity: sha512-davCD2Zc80nzDVRwXTcQP/28fiJbcOwvdolL0sOiOsbwBa72kegmVU0Wrh1MYrbuCL98Omp5dVhQFWRKR2ZAlg==} + engines: {node: '>=18'} + cpu: [arm64] + os: [darwin] + + '@esbuild/darwin-x64@0.27.2': + resolution: {integrity: sha512-ZxtijOmlQCBWGwbVmwOF/UCzuGIbUkqB1faQRf5akQmxRJ1ujusWsb3CVfk/9iZKr2L5SMU5wPBi1UWbvL+VQA==} + engines: {node: '>=18'} + cpu: [x64] + os: [darwin] + + '@esbuild/freebsd-arm64@0.27.2': + resolution: {integrity: sha512-lS/9CN+rgqQ9czogxlMcBMGd+l8Q3Nj1MFQwBZJyoEKI50XGxwuzznYdwcav6lpOGv5BqaZXqvBSiB/kJ5op+g==} + engines: {node: '>=18'} + cpu: [arm64] + os: [freebsd] + + '@esbuild/freebsd-x64@0.27.2': + resolution: {integrity: sha512-tAfqtNYb4YgPnJlEFu4c212HYjQWSO/w/h/lQaBK7RbwGIkBOuNKQI9tqWzx7Wtp7bTPaGC6MJvWI608P3wXYA==} + engines: {node: '>=18'} + cpu: [x64] + os: [freebsd] + + '@esbuild/linux-arm64@0.27.2': + resolution: {integrity: sha512-hYxN8pr66NsCCiRFkHUAsxylNOcAQaxSSkHMMjcpx0si13t1LHFphxJZUiGwojB1a/Hd5OiPIqDdXONia6bhTw==} + engines: {node: '>=18'} + cpu: [arm64] + os: [linux] + + '@esbuild/linux-arm@0.27.2': + resolution: {integrity: sha512-vWfq4GaIMP9AIe4yj1ZUW18RDhx6EPQKjwe7n8BbIecFtCQG4CfHGaHuh7fdfq+y3LIA2vGS/o9ZBGVxIDi9hw==} + engines: {node: '>=18'} + cpu: [arm] + os: [linux] + + '@esbuild/linux-ia32@0.27.2': + resolution: {integrity: sha512-MJt5BRRSScPDwG2hLelYhAAKh9imjHK5+NE/tvnRLbIqUWa+0E9N4WNMjmp/kXXPHZGqPLxggwVhz7QP8CTR8w==} + engines: {node: '>=18'} + cpu: [ia32] + os: [linux] + + '@esbuild/linux-loong64@0.27.2': + resolution: {integrity: sha512-lugyF1atnAT463aO6KPshVCJK5NgRnU4yb3FUumyVz+cGvZbontBgzeGFO1nF+dPueHD367a2ZXe1NtUkAjOtg==} + engines: {node: '>=18'} + cpu: [loong64] + os: [linux] + + '@esbuild/linux-mips64el@0.27.2': + resolution: {integrity: sha512-nlP2I6ArEBewvJ2gjrrkESEZkB5mIoaTswuqNFRv/WYd+ATtUpe9Y09RnJvgvdag7he0OWgEZWhviS1OTOKixw==} + engines: {node: '>=18'} + cpu: [mips64el] + os: [linux] + + '@esbuild/linux-ppc64@0.27.2': + resolution: {integrity: sha512-C92gnpey7tUQONqg1n6dKVbx3vphKtTHJaNG2Ok9lGwbZil6DrfyecMsp9CrmXGQJmZ7iiVXvvZH6Ml5hL6XdQ==} + engines: {node: '>=18'} + cpu: [ppc64] + os: [linux] + + '@esbuild/linux-riscv64@0.27.2': + resolution: {integrity: sha512-B5BOmojNtUyN8AXlK0QJyvjEZkWwy/FKvakkTDCziX95AowLZKR6aCDhG7LeF7uMCXEJqwa8Bejz5LTPYm8AvA==} + engines: {node: '>=18'} + cpu: [riscv64] + os: [linux] + + '@esbuild/linux-s390x@0.27.2': + resolution: {integrity: sha512-p4bm9+wsPwup5Z8f4EpfN63qNagQ47Ua2znaqGH6bqLlmJ4bx97Y9JdqxgGZ6Y8xVTixUnEkoKSHcpRlDnNr5w==} + engines: {node: '>=18'} + cpu: [s390x] + os: [linux] + + '@esbuild/linux-x64@0.27.2': + resolution: {integrity: sha512-uwp2Tip5aPmH+NRUwTcfLb+W32WXjpFejTIOWZFw/v7/KnpCDKG66u4DLcurQpiYTiYwQ9B7KOeMJvLCu/OvbA==} + engines: {node: '>=18'} + cpu: [x64] + os: [linux] + + '@esbuild/netbsd-arm64@0.27.2': + resolution: {integrity: sha512-Kj6DiBlwXrPsCRDeRvGAUb/LNrBASrfqAIok+xB0LxK8CHqxZ037viF13ugfsIpePH93mX7xfJp97cyDuTZ3cw==} + engines: {node: '>=18'} + cpu: [arm64] + os: [netbsd] + + '@esbuild/netbsd-x64@0.27.2': + resolution: {integrity: sha512-HwGDZ0VLVBY3Y+Nw0JexZy9o/nUAWq9MlV7cahpaXKW6TOzfVno3y3/M8Ga8u8Yr7GldLOov27xiCnqRZf0tCA==} + engines: {node: '>=18'} + cpu: [x64] + os: [netbsd] + + '@esbuild/openbsd-arm64@0.27.2': + resolution: {integrity: sha512-DNIHH2BPQ5551A7oSHD0CKbwIA/Ox7+78/AWkbS5QoRzaqlev2uFayfSxq68EkonB+IKjiuxBFoV8ESJy8bOHA==} + engines: {node: '>=18'} + cpu: [arm64] + os: [openbsd] + + '@esbuild/openbsd-x64@0.27.2': + resolution: {integrity: sha512-/it7w9Nb7+0KFIzjalNJVR5bOzA9Vay+yIPLVHfIQYG/j+j9VTH84aNB8ExGKPU4AzfaEvN9/V4HV+F+vo8OEg==} + engines: {node: '>=18'} + cpu: [x64] + os: [openbsd] + + '@esbuild/openharmony-arm64@0.27.2': + resolution: {integrity: sha512-LRBbCmiU51IXfeXk59csuX/aSaToeG7w48nMwA6049Y4J4+VbWALAuXcs+qcD04rHDuSCSRKdmY63sruDS5qag==} + engines: {node: '>=18'} + cpu: [arm64] + os: [openharmony] + + '@esbuild/sunos-x64@0.27.2': + resolution: {integrity: sha512-kMtx1yqJHTmqaqHPAzKCAkDaKsffmXkPHThSfRwZGyuqyIeBvf08KSsYXl+abf5HDAPMJIPnbBfXvP2ZC2TfHg==} + engines: {node: '>=18'} + cpu: [x64] + os: [sunos] + + '@esbuild/win32-arm64@0.27.2': + resolution: {integrity: sha512-Yaf78O/B3Kkh+nKABUF++bvJv5Ijoy9AN1ww904rOXZFLWVc5OLOfL56W+C8F9xn5JQZa3UX6m+IktJnIb1Jjg==} + engines: {node: '>=18'} + cpu: [arm64] + os: [win32] + + '@esbuild/win32-ia32@0.27.2': + resolution: {integrity: sha512-Iuws0kxo4yusk7sw70Xa2E2imZU5HoixzxfGCdxwBdhiDgt9vX9VUCBhqcwY7/uh//78A1hMkkROMJq9l27oLQ==} + engines: {node: '>=18'} + cpu: [ia32] + os: [win32] + + '@esbuild/win32-x64@0.27.2': + resolution: {integrity: sha512-sRdU18mcKf7F+YgheI/zGf5alZatMUTKj/jNS6l744f9u3WFu4v7twcUI9vu4mknF4Y9aDlblIie0IM+5xxaqQ==} + engines: {node: '>=18'} + cpu: [x64] + os: [win32] + '@eslint-community/eslint-utils@4.6.1': resolution: {integrity: sha512-KTsJMmobmbrFLe3LDh0PC2FXpcSYJt/MLjlkh/9LEnmKYLSYmT/0EW9JWANjeoemiuZrmogti0tW5Ch+qNUYDw==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} @@ -301,6 +546,9 @@ packages: '@jridgewell/sourcemap-codec@1.5.0': resolution: {integrity: sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==} + '@jridgewell/sourcemap-codec@1.5.5': + resolution: {integrity: sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==} + '@jridgewell/trace-mapping@0.3.25': resolution: {integrity: sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==} @@ -373,6 +621,113 @@ packages: resolution: {integrity: sha512-xhfYPXoV5Dy4UkY0D+v2KkwvnDfiA/8Mt3sWCGI/hM03NsYIH8ZaG6QzS9x7pje5vHZBZJ2v6VRFVTWACnqcmQ==} engines: {node: ^16.14.0 || >=18.0.0} + '@octokit/app@16.1.2': + resolution: {integrity: sha512-8j7sEpUYVj18dxvh0KWj6W/l6uAiVRBl1JBDVRqH1VHKAO/G5eRVl4yEoYACjakWers1DjUkcCHyJNQK47JqyQ==} + engines: {node: '>= 20'} + + '@octokit/auth-app@8.1.2': + resolution: {integrity: sha512-db8VO0PqXxfzI6GdjtgEFHY9tzqUql5xMFXYA12juq8TeTgPAuiiP3zid4h50lwlIP457p5+56PnJOgd2GGBuw==} + engines: {node: '>= 20'} + + '@octokit/auth-oauth-app@9.0.3': + resolution: {integrity: sha512-+yoFQquaF8OxJSxTb7rnytBIC2ZLbLqA/yb71I4ZXT9+Slw4TziV9j/kyGhUFRRTF2+7WlnIWsePZCWHs+OGjg==} + engines: {node: '>= 20'} + + '@octokit/auth-oauth-device@8.0.3': + resolution: {integrity: sha512-zh2W0mKKMh/VWZhSqlaCzY7qFyrgd9oTWmTmHaXnHNeQRCZr/CXy2jCgHo4e4dJVTiuxP5dLa0YM5p5QVhJHbw==} + engines: {node: '>= 20'} + + '@octokit/auth-oauth-user@6.0.2': + resolution: {integrity: sha512-qLoPPc6E6GJoz3XeDG/pnDhJpTkODTGG4kY0/Py154i/I003O9NazkrwJwRuzgCalhzyIeWQ+6MDvkUmKXjg/A==} + engines: {node: '>= 20'} + + '@octokit/auth-token@6.0.0': + resolution: {integrity: sha512-P4YJBPdPSpWTQ1NU4XYdvHvXJJDxM6YwpS0FZHRgP7YFkdVxsWcpWGy/NVqlAA7PcPCnMacXlRm1y2PFZRWL/w==} + engines: {node: '>= 20'} + + '@octokit/auth-unauthenticated@7.0.3': + resolution: {integrity: sha512-8Jb1mtUdmBHL7lGmop9mU9ArMRUTRhg8vp0T1VtZ4yd9vEm3zcLwmjQkhNEduKawOOORie61xhtYIhTDN+ZQ3g==} + engines: {node: '>= 20'} + + '@octokit/core@7.0.6': + resolution: {integrity: sha512-DhGl4xMVFGVIyMwswXeyzdL4uXD5OGILGX5N8Y+f6W7LhC1Ze2poSNrkF/fedpVDHEEZ+PHFW0vL14I+mm8K3Q==} + engines: {node: '>= 20'} + + '@octokit/endpoint@11.0.2': + resolution: {integrity: sha512-4zCpzP1fWc7QlqunZ5bSEjxc6yLAlRTnDwKtgXfcI/FxxGoqedDG8V2+xJ60bV2kODqcGB+nATdtap/XYq2NZQ==} + engines: {node: '>= 20'} + + '@octokit/graphql@9.0.3': + resolution: {integrity: sha512-grAEuupr/C1rALFnXTv6ZQhFuL1D8G5y8CN04RgrO4FIPMrtm+mcZzFG7dcBm+nq+1ppNixu+Jd78aeJOYxlGA==} + engines: {node: '>= 20'} + + '@octokit/oauth-app@8.0.3': + resolution: {integrity: sha512-jnAjvTsPepyUaMu9e69hYBuozEPgYqP4Z3UnpmvoIzHDpf8EXDGvTY1l1jK0RsZ194oRd+k6Hm13oRU8EoDFwg==} + engines: {node: '>= 20'} + + '@octokit/oauth-authorization-url@8.0.0': + resolution: {integrity: sha512-7QoLPRh/ssEA/HuHBHdVdSgF8xNLz/Bc5m9fZkArJE5bb6NmVkDm3anKxXPmN1zh6b5WKZPRr3697xKT/yM3qQ==} + engines: {node: '>= 20'} + + '@octokit/oauth-methods@6.0.2': + resolution: {integrity: sha512-HiNOO3MqLxlt5Da5bZbLV8Zarnphi4y9XehrbaFMkcoJ+FL7sMxH/UlUsCVxpddVu4qvNDrBdaTVE2o4ITK8ng==} + engines: {node: '>= 20'} + + '@octokit/openapi-types@27.0.0': + resolution: {integrity: sha512-whrdktVs1h6gtR+09+QsNk2+FO+49j6ga1c55YZudfEG+oKJVvJLQi3zkOm5JjiUXAagWK2tI2kTGKJ2Ys7MGA==} + + '@octokit/openapi-webhooks-types@12.1.0': + resolution: {integrity: sha512-WiuzhOsiOvb7W3Pvmhf8d2C6qaLHXrWiLBP4nJ/4kydu+wpagV5Fkz9RfQwV2afYzv3PB+3xYgp4mAdNGjDprA==} + + '@octokit/plugin-paginate-graphql@6.0.0': + resolution: {integrity: sha512-crfpnIoFiBtRkvPqOyLOsw12XsveYuY2ieP6uYDosoUegBJpSVxGwut9sxUgFFcll3VTOTqpUf8yGd8x1OmAkQ==} + engines: {node: '>= 20'} + peerDependencies: + '@octokit/core': '>=6' + + '@octokit/plugin-paginate-rest@14.0.0': + resolution: {integrity: sha512-fNVRE7ufJiAA3XUrha2omTA39M6IXIc6GIZLvlbsm8QOQCYvpq/LkMNGyFlB1d8hTDzsAXa3OKtybdMAYsV/fw==} + engines: {node: '>= 20'} + peerDependencies: + '@octokit/core': '>=6' + + '@octokit/plugin-rest-endpoint-methods@17.0.0': + resolution: {integrity: sha512-B5yCyIlOJFPqUUeiD0cnBJwWJO8lkJs5d8+ze9QDP6SvfiXSz1BF+91+0MeI1d2yxgOhU/O+CvtiZ9jSkHhFAw==} + engines: {node: '>= 20'} + peerDependencies: + '@octokit/core': '>=6' + + '@octokit/plugin-retry@8.0.3': + resolution: {integrity: sha512-vKGx1i3MC0za53IzYBSBXcrhmd+daQDzuZfYDd52X5S0M2otf3kVZTVP8bLA3EkU0lTvd1WEC2OlNNa4G+dohA==} + engines: {node: '>= 20'} + peerDependencies: + '@octokit/core': '>=7' + + '@octokit/plugin-throttling@11.0.3': + resolution: {integrity: sha512-34eE0RkFCKycLl2D2kq7W+LovheM/ex3AwZCYN8udpi6bxsyjZidb2McXs69hZhLmJlDqTSP8cH+jSRpiaijBg==} + engines: {node: '>= 20'} + peerDependencies: + '@octokit/core': ^7.0.0 + + '@octokit/request-error@7.1.0': + resolution: {integrity: sha512-KMQIfq5sOPpkQYajXHwnhjCC0slzCNScLHs9JafXc4RAJI+9f+jNDlBNaIMTvazOPLgb4BnlhGJOTbnN0wIjPw==} + engines: {node: '>= 20'} + + '@octokit/request@10.0.7': + resolution: {integrity: sha512-v93h0i1yu4idj8qFPZwjehoJx4j3Ntn+JhXsdJrG9pYaX6j/XRz2RmasMUHtNgQD39nrv/VwTWSqK0RNXR8upA==} + engines: {node: '>= 20'} + + '@octokit/types@16.0.0': + resolution: {integrity: sha512-sKq+9r1Mm4efXW1FCk7hFSeJo4QKreL/tTbR0rz/qx/r1Oa2VV83LTA/H/MuCOX7uCIJmQVRKBcbmWoySjAnSg==} + + '@octokit/webhooks-methods@6.0.0': + resolution: {integrity: sha512-MFlzzoDJVw/GcbfzVC1RLR36QqkTLUf79vLVO3D+xn7r0QgxnFoLZgtrzxiQErAjFUOdH6fas2KeQJ1yr/qaXQ==} + engines: {node: '>= 20'} + + '@octokit/webhooks@14.2.0': + resolution: {integrity: sha512-da6KbdNCV5sr1/txD896V+6W0iamFWrvVl8cHkBSPT+YlvmT3DwXa4jxZnQc+gnuTEqSWbBeoSZYTayXH9wXcw==} + engines: {node: '>= 20'} + '@open-rpc/meta-schema@1.14.9': resolution: {integrity: sha512-2/CbDzOVpcaSnMs28TsRv8MKJwJi0TTYFlQ6q6qobAH26oIuhYgcZooKf4l71emgntU6MMcFQCA0h4mJ4dBCdA==} @@ -524,12 +879,140 @@ packages: resolution: {integrity: sha512-X6VR9bbHXrI01Wh5t6TIHxFCVHcP4Iy42micKLIk/Cg6EmHVbaSDGOD6mxChXtEIrwnY+bqyUbjlXr9+YM7B9A==} engines: {node: '>=18.17.0', npm: '>=9.5.0'} + '@rollup/rollup-android-arm-eabi@4.55.1': + resolution: {integrity: sha512-9R0DM/ykwfGIlNu6+2U09ga0WXeZ9MRC2Ter8jnz8415VbuIykVuc6bhdrbORFZANDmTDvq26mJrEVTl8TdnDg==} + cpu: [arm] + os: [android] + + '@rollup/rollup-android-arm64@4.55.1': + resolution: {integrity: sha512-eFZCb1YUqhTysgW3sj/55du5cG57S7UTNtdMjCW7LwVcj3dTTcowCsC8p7uBdzKsZYa8J7IDE8lhMI+HX1vQvg==} + cpu: [arm64] + os: [android] + + '@rollup/rollup-darwin-arm64@4.55.1': + resolution: {integrity: sha512-p3grE2PHcQm2e8PSGZdzIhCKbMCw/xi9XvMPErPhwO17vxtvCN5FEA2mSLgmKlCjHGMQTP6phuQTYWUnKewwGg==} + cpu: [arm64] + os: [darwin] + + '@rollup/rollup-darwin-x64@4.55.1': + resolution: {integrity: sha512-rDUjG25C9qoTm+e02Esi+aqTKSBYwVTaoS1wxcN47/Luqef57Vgp96xNANwt5npq9GDxsH7kXxNkJVEsWEOEaQ==} + cpu: [x64] + os: [darwin] + + '@rollup/rollup-freebsd-arm64@4.55.1': + resolution: {integrity: sha512-+JiU7Jbp5cdxekIgdte0jfcu5oqw4GCKr6i3PJTlXTCU5H5Fvtkpbs4XJHRmWNXF+hKmn4v7ogI5OQPaupJgOg==} + cpu: [arm64] + os: [freebsd] + + '@rollup/rollup-freebsd-x64@4.55.1': + resolution: {integrity: sha512-V5xC1tOVWtLLmr3YUk2f6EJK4qksksOYiz/TCsFHu/R+woubcLWdC9nZQmwjOAbmExBIVKsm1/wKmEy4z4u4Bw==} + cpu: [x64] + os: [freebsd] + + '@rollup/rollup-linux-arm-gnueabihf@4.55.1': + resolution: {integrity: sha512-Rn3n+FUk2J5VWx+ywrG/HGPTD9jXNbicRtTM11e/uorplArnXZYsVifnPPqNNP5BsO3roI4n8332ukpY/zN7rQ==} + cpu: [arm] + os: [linux] + + '@rollup/rollup-linux-arm-musleabihf@4.55.1': + resolution: {integrity: sha512-grPNWydeKtc1aEdrJDWk4opD7nFtQbMmV7769hiAaYyUKCT1faPRm2av8CX1YJsZ4TLAZcg9gTR1KvEzoLjXkg==} + cpu: [arm] + os: [linux] + + '@rollup/rollup-linux-arm64-gnu@4.55.1': + resolution: {integrity: sha512-a59mwd1k6x8tXKcUxSyISiquLwB5pX+fJW9TkWU46lCqD/GRDe9uDN31jrMmVP3feI3mhAdvcCClhV8V5MhJFQ==} + cpu: [arm64] + os: [linux] + + '@rollup/rollup-linux-arm64-musl@4.55.1': + resolution: {integrity: sha512-puS1MEgWX5GsHSoiAsF0TYrpomdvkaXm0CofIMG5uVkP6IBV+ZO9xhC5YEN49nsgYo1DuuMquF9+7EDBVYu4uA==} + cpu: [arm64] + os: [linux] + + '@rollup/rollup-linux-loong64-gnu@4.55.1': + resolution: {integrity: sha512-r3Wv40in+lTsULSb6nnoudVbARdOwb2u5fpeoOAZjFLznp6tDU8kd+GTHmJoqZ9lt6/Sys33KdIHUaQihFcu7g==} + cpu: [loong64] + os: [linux] + + '@rollup/rollup-linux-loong64-musl@4.55.1': + resolution: {integrity: sha512-MR8c0+UxAlB22Fq4R+aQSPBayvYa3+9DrwG/i1TKQXFYEaoW3B5b/rkSRIypcZDdWjWnpcvxbNaAJDcSbJU3Lw==} + cpu: [loong64] + os: [linux] + + '@rollup/rollup-linux-ppc64-gnu@4.55.1': + resolution: {integrity: sha512-3KhoECe1BRlSYpMTeVrD4sh2Pw2xgt4jzNSZIIPLFEsnQn9gAnZagW9+VqDqAHgm1Xc77LzJOo2LdigS5qZ+gw==} + cpu: [ppc64] + os: [linux] + + '@rollup/rollup-linux-ppc64-musl@4.55.1': + resolution: {integrity: sha512-ziR1OuZx0vdYZZ30vueNZTg73alF59DicYrPViG0NEgDVN8/Jl87zkAPu4u6VjZST2llgEUjaiNl9JM6HH1Vdw==} + cpu: [ppc64] + os: [linux] + + '@rollup/rollup-linux-riscv64-gnu@4.55.1': + resolution: {integrity: sha512-uW0Y12ih2XJRERZ4jAfKamTyIHVMPQnTZcQjme2HMVDAHY4amf5u414OqNYC+x+LzRdRcnIG1YodLrrtA8xsxw==} + cpu: [riscv64] + os: [linux] + + '@rollup/rollup-linux-riscv64-musl@4.55.1': + resolution: {integrity: sha512-u9yZ0jUkOED1BFrqu3BwMQoixvGHGZ+JhJNkNKY/hyoEgOwlqKb62qu+7UjbPSHYjiVy8kKJHvXKv5coH4wDeg==} + cpu: [riscv64] + os: [linux] + + '@rollup/rollup-linux-s390x-gnu@4.55.1': + resolution: {integrity: sha512-/0PenBCmqM4ZUd0190j7J0UsQ/1nsi735iPRakO8iPciE7BQ495Y6msPzaOmvx0/pn+eJVVlZrNrSh4WSYLxNg==} + cpu: [s390x] + os: [linux] + + '@rollup/rollup-linux-x64-gnu@4.55.1': + resolution: {integrity: sha512-a8G4wiQxQG2BAvo+gU6XrReRRqj+pLS2NGXKm8io19goR+K8lw269eTrPkSdDTALwMmJp4th2Uh0D8J9bEV1vg==} + cpu: [x64] + os: [linux] + + '@rollup/rollup-linux-x64-musl@4.55.1': + resolution: {integrity: sha512-bD+zjpFrMpP/hqkfEcnjXWHMw5BIghGisOKPj+2NaNDuVT+8Ds4mPf3XcPHuat1tz89WRL+1wbcxKY3WSbiT7w==} + cpu: [x64] + os: [linux] + + '@rollup/rollup-openbsd-x64@4.55.1': + resolution: {integrity: sha512-eLXw0dOiqE4QmvikfQ6yjgkg/xDM+MdU9YJuP4ySTibXU0oAvnEWXt7UDJmD4UkYialMfOGFPJnIHSe/kdzPxg==} + cpu: [x64] + os: [openbsd] + + '@rollup/rollup-openharmony-arm64@4.55.1': + resolution: {integrity: sha512-xzm44KgEP11te3S2HCSyYf5zIzWmx3n8HDCc7EE59+lTcswEWNpvMLfd9uJvVX8LCg9QWG67Xt75AuHn4vgsXw==} + cpu: [arm64] + os: [openharmony] + + '@rollup/rollup-win32-arm64-msvc@4.55.1': + resolution: {integrity: sha512-yR6Bl3tMC/gBok5cz/Qi0xYnVbIxGx5Fcf/ca0eB6/6JwOY+SRUcJfI0OpeTpPls7f194as62thCt/2BjxYN8g==} + cpu: [arm64] + os: [win32] + + '@rollup/rollup-win32-ia32-msvc@4.55.1': + resolution: {integrity: sha512-3fZBidchE0eY0oFZBnekYCfg+5wAB0mbpCBuofh5mZuzIU/4jIVkbESmd2dOsFNS78b53CYv3OAtwqkZZmU5nA==} + cpu: [ia32] + os: [win32] + + '@rollup/rollup-win32-x64-gnu@4.55.1': + resolution: {integrity: sha512-xGGY5pXj69IxKb4yv/POoocPy/qmEGhimy/FoTpTSVju3FYXUQQMFCaZZXJVidsmGxRioZAwpThl/4zX41gRKg==} + cpu: [x64] + os: [win32] + + '@rollup/rollup-win32-x64-msvc@4.55.1': + resolution: {integrity: sha512-SPEpaL6DX4rmcXtnhdrQYgzQ5W2uW3SCJch88lB2zImhJRhIIK44fkUrgIV/Q8yUNfw5oyZ5vkeQsZLhCb06lw==} + cpu: [x64] + os: [win32] + '@scarf/scarf@1.4.0': resolution: {integrity: sha512-xxeapPiUXdZAE3che6f3xogoJPeZgig6omHEy1rIY5WVsB3H2BHNnZH+gHG6x91SCWyQCzWGsuL2Hh3ClO5/qQ==} '@sinclair/typebox@0.27.8': resolution: {integrity: sha512-+Fj43pSMwJs4KRrH/938Uf+uAELIgVBmQzg/q1YG10djyfA3TnrU8N8XzqCh/okZdszqBQTZf96idMfE5lnwTA==} + '@standard-schema/spec@1.1.0': + resolution: {integrity: sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w==} + '@trivago/prettier-plugin-sort-imports@5.2.2': resolution: {integrity: sha512-fYDQA9e6yTNmA13TLVSA+WMQRc5Bn/c0EUBditUHNfMMxN7M82c38b1kEggVE3pLpZ0FwkwJkUEKMiOi52JXFA==} engines: {node: '>18.12'} @@ -558,18 +1041,30 @@ packages: '@tsconfig/node16@1.0.4': resolution: {integrity: sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA==} + '@types/aws-lambda@8.10.159': + resolution: {integrity: sha512-SAP22WSGNN12OQ8PlCzGzRCZ7QDCwI85dQZbmpz7+mAk+L7j+wI7qnvmdKh+o7A5LaOp6QnOZ2NJphAZQTTHQg==} + + '@types/chai@5.2.3': + resolution: {integrity: sha512-Mw558oeA9fFbv65/y4mHtXDs9bPnFMZAL/jxdPFUpOHHIXX91mcgEHbS5Lahr+pwZFR8A7GQleRWeI6cGFC2UA==} + '@types/concat-stream@2.0.3': resolution: {integrity: sha512-3qe4oQAPNwVNwK4C9c8u+VJqv9kez+2MR4qJpoPFfXtgxxif1QbFusvXzK0/Wra2VX07smostI2VMmJNSpZjuQ==} '@types/debug@4.1.12': resolution: {integrity: sha512-vIChWdVG3LG1SMxEvI/AK+FWJthlrqlTu7fbrlywTkkaONwk/UAGaULXRlf8vkzFBLVm0zkMdCquhL5aOjhXPQ==} + '@types/deep-eql@4.0.2': + resolution: {integrity: sha512-c9h9dVVMigMPc4bwTvC5dxqtqJZwQPePsWjPlpSOnojbor6pGqdk541lfA7AqFQr5pB1BRdq0juY9db81BwyFw==} + '@types/estree-jsx@1.0.5': resolution: {integrity: sha512-52CcUVNFyfb1A2ALocQw/Dd1BQFNmSdkuC3BkZ6iqhdMfQz7JWOFRuJFloOzjk+6WijU56m9oKXFAXc7o3Towg==} '@types/estree@1.0.7': resolution: {integrity: sha512-w28IoSUCJpidD/TGviZwwMJckNESJZXFu7NBZ5YJ4mEUnNraUn9Pm8HSZm/jDF1pDWYKspWE7oVphigUPRakIQ==} + '@types/estree@1.0.8': + resolution: {integrity: sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==} + '@types/hast@3.0.4': resolution: {integrity: sha512-WPs+bbQw5aCj+x6laNGWLH3wviHtoCv/P3+otBhbOhJgG8qtpdAMlTCxLtsTWA7LH1Oh/bFCHsBn0TPS5m30EQ==} @@ -585,6 +1080,12 @@ packages: '@types/json-schema@7.0.15': resolution: {integrity: sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==} + '@types/lodash-es@4.17.12': + resolution: {integrity: sha512-0NgftHUcV4v34VhXm8QBSftKVXtbkBG3ViCjs6+eJ5a6y6Mi/jiFGPc1sC7QK+9BFhWrURE3EOggmWaSxL9OzQ==} + + '@types/lodash@4.17.21': + resolution: {integrity: sha512-FOvQ0YPD5NOfPgMzJihoT+Za5pdkDJWcbpuj1DjaKZIr/gxodQjY/uWEFlTNqW2ugXHUiL8lRQgw63dzKHZdeQ==} + '@types/mdast@4.0.4': resolution: {integrity: sha512-kGaNbPh1k7AFzgpud/gMdvIm5xuECykRR+JnWKQno9TAXVa6WIVCGTPvYGekIDL4uwCZQSYbUxNBSb1aUo79oA==} @@ -659,6 +1160,38 @@ packages: resolution: {integrity: sha512-QcGHmlRHWOl93o64ZUMNewCdwKGU6WItOU52H0djgNmn1EOrhVudrDzXz4OycCRSCPwFCDrE2iIt5vmuUdHxuQ==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + '@upstash/redis@1.36.1': + resolution: {integrity: sha512-N6SjDcgXdOcTAF+7uNoY69o7hCspe9BcA7YjQdxVu5d25avljTwyLaHBW3krWjrP0FfocgMk94qyVtQbeDp39A==} + + '@vitest/expect@4.0.16': + resolution: {integrity: sha512-eshqULT2It7McaJkQGLkPjPjNph+uevROGuIMJdG3V+0BSR2w9u6J9Lwu+E8cK5TETlfou8GRijhafIMhXsimA==} + + '@vitest/mocker@4.0.16': + resolution: {integrity: sha512-yb6k4AZxJTB+q9ycAvsoxGn+j/po0UaPgajllBgt1PzoMAAmJGYFdDk0uCcRcxb3BrME34I6u8gHZTQlkqSZpg==} + peerDependencies: + msw: ^2.4.9 + vite: ^6.0.0 || ^7.0.0-0 + peerDependenciesMeta: + msw: + optional: true + vite: + optional: true + + '@vitest/pretty-format@4.0.16': + resolution: {integrity: sha512-eNCYNsSty9xJKi/UdVD8Ou16alu7AYiS2fCPRs0b1OdhJiV89buAXQLpTbe+X8V9L6qrs9CqyvU7OaAopJYPsA==} + + '@vitest/runner@4.0.16': + resolution: {integrity: sha512-VWEDm5Wv9xEo80ctjORcTQRJ539EGPB3Pb9ApvVRAY1U/WkHXmmYISqU5E79uCwcW7xYUV38gwZD+RV755fu3Q==} + + '@vitest/snapshot@4.0.16': + resolution: {integrity: sha512-sf6NcrYhYBsSYefxnry+DR8n3UV4xWZwWxYbCJUt2YdvtqzSPR7VfGrY0zsv090DAbjFZsi7ZaMi1KnSRyK1XA==} + + '@vitest/spy@4.0.16': + resolution: {integrity: sha512-4jIOWjKP0ZUaEmJm00E0cOBLU+5WE0BpeNr3XN6TEF05ltro6NJqHWxXD0kA8/Zc8Nh23AT8WQxwNG+WeROupw==} + + '@vitest/utils@4.0.16': + resolution: {integrity: sha512-h8z9yYhV3e1LEfaQ3zdypIrnAg/9hguReGZoS7Gl0aBG5xgA410zBqECqmaF/+RkTggRsfnzc1XaAHA6bmUufA==} + abbrev@2.0.0: resolution: {integrity: sha512-6/mh1E2u2YgEsCHdY0Yx5oW+61gZU+1vXaoiHHrpKeuRNNgFvS+/jrwHiQhB5apAf5oB7UB7E19ol2R2LKH8hQ==} engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0} @@ -688,6 +1221,10 @@ packages: ajv@6.12.6: resolution: {integrity: sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==} + algoliasearch@5.46.2: + resolution: {integrity: sha512-qqAXW9QvKf2tTyhpDA4qXv1IfBwD2eduSW6tUEBFIfCeE9gn9HQ9I5+MaKoenRuHrzk5sQoNh1/iof8mY7uD6Q==} + engines: {node: '>= 14.0.0'} + ansi-escapes@7.0.0: resolution: {integrity: sha512-GdYO7a61mR0fOlAsvC9/rIHf7L96sBc6dEWzeOu+KAea5bZyQRPIpojrVoI4AXGJS/ycu/fBTdLrUkA4ODrvjw==} engines: {node: '>=18'} @@ -719,9 +1256,16 @@ packages: arg@4.1.3: resolution: {integrity: sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==} + argparse@1.0.10: + resolution: {integrity: sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==} + argparse@2.0.1: resolution: {integrity: sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==} + assertion-error@2.0.1: + resolution: {integrity: sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==} + engines: {node: '>=12'} + asynckit@0.4.0: resolution: {integrity: sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==} @@ -731,6 +1275,9 @@ packages: balanced-match@1.0.2: resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==} + before-after-hook@4.0.0: + resolution: {integrity: sha512-q6tR3RPqIB1pMiTRMFcZwuG5T8vwp+vUvEG0vuI6B+Rikh5BfPp2fQ82c925FOs+b0lcFQ8CFrL+KbilfZFhOQ==} + better-ajv-errors@1.2.0: resolution: {integrity: sha512-UW+IsFycygIo7bclP9h5ugkNH8EjCSgqyFB/yQ4Hqqa1OEYDtb0uFIkYE0b6+CjkgJYVM5UKI/pJPxjYe9EZlA==} engines: {node: '>= 12.13.0'} @@ -741,6 +1288,9 @@ packages: resolution: {integrity: sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==} engines: {node: '>=8'} + bottleneck@2.19.5: + resolution: {integrity: sha512-VHiNCbI1lKdl44tGrhNfU3lup0Tj/ZBMJB5/2ZbNXRCPuRCO7ed2mgcK4r17y+KB2EfuYuRaVlwNbAeaWGSpbw==} + brace-expansion@1.1.11: resolution: {integrity: sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==} @@ -775,6 +1325,10 @@ packages: ccount@2.0.1: resolution: {integrity: sha512-eyrF0jiFpY+3drT6383f1qhkbGsLSifNAjA61IUjZjmLCWjItY6LB9ft9YhoDgwfmclB2zhu51Lc7+95b8NRAg==} + chai@6.2.2: + resolution: {integrity: sha512-NUPRluOfOiTKBKvWPtSD4PhFvWCqOi0BGStNWs57X9js7XGTprSmFoz5F0tWhR4WPjNeR9jXqdC7/UpSJTnlRg==} + engines: {node: '>=18'} + chalk@4.1.2: resolution: {integrity: sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==} engines: {node: '>=10'} @@ -799,6 +1353,10 @@ packages: resolution: {integrity: sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==} engines: {node: '>= 8.10.0'} + chokidar@4.0.3: + resolution: {integrity: sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==} + engines: {node: '>= 14.16.0'} + ci-info@4.2.0: resolution: {integrity: sha512-cYY9mypksY8NRqgDB1XD1RiJL338v/551niynFTGkZOO2LHuB2OmOYxDIe/ttN9AHwrqdum1360G3ald0W9kCg==} engines: {node: '>=8'} @@ -987,6 +1545,9 @@ packages: resolution: {integrity: sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==} engines: {node: '>= 0.4'} + es-module-lexer@1.7.0: + resolution: {integrity: sha512-jEQoCwk8hyb2AZziIOLhDqpm5+2ww5uIE6lkO/6jcOCusfk6LhMHpXXfBLXTZ7Ydyt0j4VoUQv6uGNYbdW+kBA==} + es-object-atoms@1.1.1: resolution: {integrity: sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==} engines: {node: '>= 0.4'} @@ -998,6 +1559,11 @@ packages: es6-promise@3.3.1: resolution: {integrity: sha512-SOp9Phqvqn7jtEUxPWdWfWoLmyt2VaJ6MpvP9Comy1MceMXqE6bxvaTu4iaxpYYPzhny28Lc+M87/c2cPK6lDg==} + esbuild@0.27.2: + resolution: {integrity: sha512-HyNQImnsOC7X9PMNaCIeAm4ISCQXs5a5YasTXVliKv4uuBo1dKrG0A+uQS8M5eXjVMnLg3WgXaKvprHlFJQffw==} + engines: {node: '>=18'} + hasBin: true + escalade@3.2.0: resolution: {integrity: sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==} engines: {node: '>=6'} @@ -1058,6 +1624,11 @@ packages: resolution: {integrity: sha512-0QYC8b24HWY8zjRnDTL6RiHfDbAWn63qb4LMj1Z4b076A4une81+z03Kg7l7mn/48PUTqoLptSXez8oknU8Clg==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + esprima@4.0.1: + resolution: {integrity: sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==} + engines: {node: '>=4'} + hasBin: true + esquery@1.6.0: resolution: {integrity: sha512-ca9pw9fomFcKPvFLXhBKUK90ZvGibiGOvRJNbjljY7s7uq/5YO4BOzcYtJqExdx99rF6aAcnRxHmcUHcz6sQsg==} engines: {node: '>=0.10'} @@ -1076,6 +1647,9 @@ packages: estree-util-visit@2.0.0: resolution: {integrity: sha512-m5KgiH85xAhhW8Wta0vShLcUvOsh3LLPI2YVwcbio1l7E09NTLL1EyMZFM1OyWowoH0skScNbhOPl4kcBgzTww==} + estree-walker@3.0.3: + resolution: {integrity: sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==} + esutils@2.0.3: resolution: {integrity: sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==} engines: {node: '>=0.10.0'} @@ -1091,9 +1665,20 @@ packages: resolution: {integrity: sha512-VyhnebXciFV2DESc+p6B+y0LjSm0krU4OgJN44qFAhBY0TJ+1V61tYD2+wHusZ6F9n5K+vl8k0sTy7PEfV4qpg==} engines: {node: '>=16.17'} + expect-type@1.3.0: + resolution: {integrity: sha512-knvyeauYhqjOYvQ66MznSMs83wmHrCycNEN6Ao+2AeYEfxUIkuiVxdEa1qlGEPK+We3n0THiDciYSsCcgW/DoA==} + engines: {node: '>=12.0.0'} + + extend-shallow@2.0.1: + resolution: {integrity: sha512-zCnTtlxNoAiDc3gqY2aYAWFx7XWWiasuF2K8Me5WbN8otHKTUKBwjPtNpRs/rbUZm7KxWAaNj7P1a/p52GbVug==} + engines: {node: '>=0.10.0'} + extend@3.0.2: resolution: {integrity: sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==} + fast-content-type-parse@3.0.0: + resolution: {integrity: sha512-ZvLdcY8P+N8mGQJahJV5G4U88CSvT1rP8ApL6uETe88MBXrBHAkZlSEySdUlyztF7ccb+Znos3TFqaepHxdhBg==} + fast-deep-equal@3.1.3: resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==} @@ -1120,6 +1705,15 @@ packages: fault@2.0.1: resolution: {integrity: sha512-WtySTkS4OKev5JtpHXnib4Gxiurzh5NCGvWrFaZ34m6JehfTUhKZvn9njTfw48t6JumVQOmrKqpmGcdwxnhqBQ==} + fdir@6.5.0: + resolution: {integrity: sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==} + engines: {node: '>=12.0.0'} + peerDependencies: + picomatch: ^3 || ^4 + peerDependenciesMeta: + picomatch: + optional: true + fern-api@3.13.0: resolution: {integrity: sha512-TSWJSt6fLvcKzZMDT/t9+FPCGgNHVuaKSWkLMUKoM68SYwGkbOZ67PTBDIz5tSYWKTsUmeFVE+YK9G+xqBMzIA==} hasBin: true @@ -1200,6 +1794,9 @@ packages: resolution: {integrity: sha512-VaUJspBffn/LMCJVoMvSAdmscJyS1auj5Zulnn5UoYcY531UWmdwhRWkcGKnGU93m5HSXP9LP2usOryrBtQowA==} engines: {node: '>=16'} + get-tsconfig@4.13.0: + resolution: {integrity: sha512-1VKTZJCwBrvbd+Wn3AOgQP/2Av+TfTCOlE4AcRJE72W1ksZXbAx8PPBR9RzgTeSPzlPMHrbANMH3LbltH73wxQ==} + glob-parent@5.1.2: resolution: {integrity: sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==} engines: {node: '>= 6'} @@ -1238,6 +1835,10 @@ packages: graphemer@1.4.0: resolution: {integrity: sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==} + gray-matter@4.0.3: + resolution: {integrity: sha512-5v6yZd4JK3eMI3FqqCouswVqwugaA9r4dNZB1wwcmrD02QkV5H0y7XBQW8QwQqEaZY1pM9aqORSORhJRdNK44Q==} + engines: {node: '>=6.0'} + handlebars@4.7.8: resolution: {integrity: sha512-vafaFqs8MZkRrSX7sFVUdo3ap/eNiLnb4IakshzvP56X5Nr1iGKAIqdX6tMlm6HcNRIkr6AxO5jFEoJzzpT8aQ==} engines: {node: '>=0.4.7'} @@ -1333,6 +1934,10 @@ packages: is-empty@1.2.0: resolution: {integrity: sha512-F2FnH/otLNJv0J6wc73A5Xo7oHLNnqplYqZhUu01tD54DIPvxIRSTSLkrUB/M0nHO4vo1O9PDfN4KoTxCzLh/w==} + is-extendable@0.1.1: + resolution: {integrity: sha512-5BMULNob1vgFX6EjQw5izWDxrecWK9AM72rugNr0TFldMOi0fj6Jk+zeKIt0xGj4cEfQIJth4w3OKWOJ4f+AFw==} + engines: {node: '>=0.10.0'} + is-extglob@2.1.1: resolution: {integrity: sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==} engines: {node: '>=0.10.0'} @@ -1419,10 +2024,18 @@ packages: js-tokens@4.0.0: resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==} + js-yaml@3.14.2: + resolution: {integrity: sha512-PMSmkqxr106Xa156c2M265Z+FTrPl+oxd/rgOQy2tijQeK5TxQ43psO1ZCwhVOSdnn+RzkzlRz/eY4BgJBYVpg==} + hasBin: true + js-yaml@4.1.0: resolution: {integrity: sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==} hasBin: true + js-yaml@4.1.1: + resolution: {integrity: sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==} + hasBin: true + jsep@1.4.0: resolution: {integrity: sha512-B7qPcEVE3NVkmSJbaYxvv4cHkVW7DQsZz13pUMrfS8z8Q/BuShN+gcTXrUlPiGqM2/t/EEaI030bpxMqY8gMlw==} engines: {node: '>= 10.16.0'} @@ -1473,6 +2086,10 @@ packages: keyv@4.5.4: resolution: {integrity: sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==} + kind-of@6.0.3: + resolution: {integrity: sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==} + engines: {node: '>=0.10.0'} + kleur@4.1.5: resolution: {integrity: sha512-o+NO+8WrRiQEE4/7nwRJhN1HWpVmJm511pBHUxPLtp0BUISzlBplORYSmTclCnJvQq2tKu/sgl3xVpkc7ZWuQQ==} engines: {node: '>=6'} @@ -1509,6 +2126,9 @@ packages: resolution: {integrity: sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==} engines: {node: '>=10'} + lodash-es@4.17.22: + resolution: {integrity: sha512-XEawp1t0gxSi9x01glktRZ5HDy0HXqrM0x5pXQM98EaI0NxO6jVM7omDOxsuEo5UIASAnm2bRp1Jt/e0a2XU8Q==} + lodash.merge@4.6.2: resolution: {integrity: sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==} @@ -1535,6 +2155,9 @@ packages: lunr@2.3.9: resolution: {integrity: sha512-zTU3DaZaF3Rt9rhN3uBMGQD3dD2/vFQqnvZCDv4dl5iOzq2IZQqTxu90r4E5J+nP70J3ilqVCrbho2eWaeW8Ow==} + magic-string@0.30.21: + resolution: {integrity: sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==} + make-error@1.3.6: resolution: {integrity: sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==} @@ -1834,6 +2457,13 @@ packages: resolution: {integrity: sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==} engines: {node: '>=0.10.0'} + obug@2.1.1: + resolution: {integrity: sha512-uTqF9MuPraAQ+IsnPf366RG4cP9RtUi7MLO1N3KEc+wb0a6yKpeL0lmk2IB1jY5KHPAlTc6T/JRdC/YqxHNwkQ==} + + octokit@5.0.5: + resolution: {integrity: sha512-4+/OFSqOjoyULo7eN7EA97DE0Xydj/PW5aIckxqQIoFjFwqXKuFCvXUJObyJfBF9Khu4RL/jlDRI9FPaMGfPnw==} + engines: {node: '>= 20'} + once@1.4.0: resolution: {integrity: sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==} @@ -1856,6 +2486,9 @@ packages: openapi-sampler@1.6.1: resolution: {integrity: sha512-s1cIatOqrrhSj2tmJ4abFYZQK6l5v+V4toO5q1Pa0DyN8mtyqy2I+Qrj5W9vOELEtybIMQs/TBZGVO/DtTFK8w==} + openapi-types@12.1.3: + resolution: {integrity: sha512-N4YtSYJqghVu4iek2ZUvcN/0aqH1kRDuNqzcycDxhOUpg7GdvLa2F3DgS6yBNhInhv2r/6I0Flkn7CqL8+nIcw==} + optionator@0.9.4: resolution: {integrity: sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==} engines: {node: '>= 0.8.0'} @@ -1908,6 +2541,9 @@ packages: resolution: {integrity: sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==} engines: {node: '>=16 || 14 >=14.18'} + pathe@2.0.3: + resolution: {integrity: sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==} + perfect-scrollbar@1.5.6: resolution: {integrity: sha512-rixgxw3SxyJbCaSpo1n35A/fwI1r2rdwMKOTCg/AcG+xOEyZcE8UHVjpZMFCVImzsFoCZeJTT+M/rdEIQYO2nw==} @@ -1918,6 +2554,10 @@ packages: resolution: {integrity: sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==} engines: {node: '>=8.6'} + picomatch@4.0.3: + resolution: {integrity: sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==} + engines: {node: '>=12'} + pidtree@0.6.0: resolution: {integrity: sha512-eG2dWTVw5bzqGRztnHExczNxt5VGsE6OwTeCG3fdUf9KBsZzO3R5OIIIzWR+iZA0NtZ+RDVdaoE2dK1cn6jH4g==} engines: {node: '>=0.10'} @@ -1938,6 +2578,10 @@ packages: resolution: {integrity: sha512-OCVPnIObs4N29kxTjzLfUryOkvZEq+pf8jTF0lg8E7uETuWHA+v7j3c/xJmiqpX450191LlmZfUKkXxkTry7nA==} engines: {node: ^10 || ^12 || >=14} + postcss@8.5.6: + resolution: {integrity: sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==} + engines: {node: ^10 || ^12 || >=14} + prelude-ls@1.2.1: resolution: {integrity: sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==} engines: {node: '>= 0.8.0'} @@ -2023,6 +2667,10 @@ packages: resolution: {integrity: sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==} engines: {node: '>=8.10.0'} + readdirp@4.1.2: + resolution: {integrity: sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==} + engines: {node: '>= 14.18.0'} + redoc@2.4.0: resolution: {integrity: sha512-rFlfzFVWS9XJ6aYAs/bHnLhHP5FQEhwAHDBVgwb9L2FqDQ8Hu8rQ1G84iwaWXxZfPP9UWn7JdWkxI6MXr2ZDjw==} engines: {node: '>=6.9', npm: '>=3.0.0'} @@ -2225,6 +2873,9 @@ packages: resolution: {integrity: sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==} engines: {node: '>=4'} + resolve-pkg-maps@1.0.0: + resolution: {integrity: sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==} + restore-cursor@5.1.0: resolution: {integrity: sha512-oMA2dcrw6u0YfxJQXm342bFKX/E4sG9rbTzO9ptUcR/e8A33cHuvStiYOwH7fszkZlZ1z/ta9AAoPk2F4qIOHA==} engines: {node: '>=18'} @@ -2240,6 +2891,11 @@ packages: rfdc@1.4.1: resolution: {integrity: sha512-q1b3N5QkRUWUl7iyylaaj3kOpIT0N2i9MqIEQXP73GVsN9cw3fdx8X63cEmWhJGi2PPCF23Ijp7ktmd39rawIA==} + rollup@4.55.1: + resolution: {integrity: sha512-wDv/Ht1BNHB4upNbK74s9usvl7hObDnvVzknxqY/E/O3X6rW1U1rV1aENEfJ54eFZDTNo7zv1f5N4edCluH7+A==} + engines: {node: '>=18.0.0', npm: '>=8.0.0'} + hasBin: true + run-applescript@7.0.0: resolution: {integrity: sha512-9by4Ij99JUr/MCFBUkDKLWK3G9HVXmabKz9U5MlIAIuvuzkiOicRYs8XJLxX+xahD+mLiiCYDqF9dKAgtzKP1A==} engines: {node: '>=18'} @@ -2257,6 +2913,10 @@ packages: scheduler@0.26.0: resolution: {integrity: sha512-NlHwttCI/l5gCPR3D1nNXtWABUmBwvZpEQiD4IXSbIDq8BzLIK/7Ir5gTFSGZDUu37K5cMNp0hFtzO38sC7gWA==} + section-matter@1.0.0: + resolution: {integrity: sha512-vfD3pmTzGpufjScBh50YHKzEu2lxBWhVEHsNGoEXmCmn2hKGfeNLYMzCJpe8cD7gqX7TJluOVpBkAequ6dgMmA==} + engines: {node: '>=4'} + semver@7.7.1: resolution: {integrity: sha512-hlq8tAfn0m/61p4BVRcPzIGr6LKiMwo4VM6dGi6pt4qcRkmNzTcWq6eCEjEh+qXjkMDvPlOFFSGwQjoEa6gyMA==} engines: {node: '>=10'} @@ -2294,6 +2954,9 @@ packages: should@13.2.3: resolution: {integrity: sha512-ggLesLtu2xp+ZxI+ysJTmNjh2U0TsC+rQ/pfED9bUZZ4DKefP27D+7YJVVTvKsmjLpIi9jAa7itwDGkDDmt1GQ==} + siginfo@2.0.0: + resolution: {integrity: sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==} + signal-exit@4.1.0: resolution: {integrity: sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==} engines: {node: '>=14'} @@ -2336,6 +2999,15 @@ packages: spdx-license-ids@3.0.21: resolution: {integrity: sha512-Bvg/8F5XephndSK3JffaRqdT+gyhfqIPwDHpX80tJrF8QQRYMo8sNMeaZ2Dp5+jhwKnUmIOyFFQfHRkjJm5nXg==} + sprintf-js@1.0.3: + resolution: {integrity: sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==} + + stackback@0.0.2: + resolution: {integrity: sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==} + + std-env@3.10.0: + resolution: {integrity: sha512-5GS12FdOZNliM5mAOxFRg7Ir0pWz8MdpYm6AY6VPkGpbA7ZzmbzNcBJQ0GPvvyWgcY7QAhCgf9Uy89I03faLkg==} + stickyfill@1.1.1: resolution: {integrity: sha512-GCp7vHAfpao+Qh/3Flh9DXEJ/qSi0KJwJw6zYlZOtRYXWUIpMM6mC2rIep/dK8RQqwW0KxGJIllmjPIBOGN8AA==} @@ -2373,6 +3045,10 @@ packages: resolution: {integrity: sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==} engines: {node: '>=12'} + strip-bom-string@1.0.0: + resolution: {integrity: sha512-uCC2VHvQRYu+lMh4My/sFNmF2klFymLX1wHJeXnbEJERpV/ZsVuonzerjfrGpIGF7LBVa1O7i9kjiWvJiFck8g==} + engines: {node: '>=0.10.0'} + strip-final-newline@3.0.0: resolution: {integrity: sha512-dOESqjYr96iWYylGObzd39EuNTa5VJxyvVAEm5Jnh7KGo75V43Hk1odPQkNDyXNmUR6k+gEiDVXnjB8HJ3crXw==} engines: {node: '>=12'} @@ -2410,10 +3086,29 @@ packages: resolution: {integrity: sha512-Q/XQKRaJiLiFIBNN+mndW7S/RHxvwzuZS6ZwmRzUBqJBv/5QIKCEwkBC8GBf8EQJKYnaFs0wOZbKTXBPj8L9oQ==} engines: {node: ^14.18.0 || >=16.0.0} + tinybench@2.9.0: + resolution: {integrity: sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==} + + tinyexec@1.0.2: + resolution: {integrity: sha512-W/KYk+NFhkmsYpuHq5JykngiOCnxeVL8v8dFnqxSD8qEEdRfXk1SDM6JzNqcERbcGYj9tMrDQBYV9cjgnunFIg==} + engines: {node: '>=18'} + + tinyglobby@0.2.15: + resolution: {integrity: sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==} + engines: {node: '>=12.0.0'} + + tinyrainbow@3.0.3: + resolution: {integrity: sha512-PSkbLUoxOFRzJYjjxHJt9xro7D+iilgMX/C9lawzVuYiIdcihh9DXmVibBe8lmcFrRi/VzlPjBxbN7rH24q8/Q==} + engines: {node: '>=14.0.0'} + to-regex-range@5.0.1: resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==} engines: {node: '>=8.0'} + toad-cache@3.7.0: + resolution: {integrity: sha512-/m8M+2BJUpoJdgAHoG+baCwBT+tf2VraSfkBgl0Y00qIWt41DJ8R5B8nsEw0I58YwF5IZH6z24/2TobDKnqSWw==} + engines: {node: '>=12'} + tr46@0.0.3: resolution: {integrity: sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==} @@ -2450,6 +3145,11 @@ packages: tslib@2.8.1: resolution: {integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==} + tsx@4.21.0: + resolution: {integrity: sha512-5C1sg4USs1lfG0GFb2RLXsdpXqBSEhAaA/0kPL01wxzpMqLILNxIxIOKiILz+cdg/pLnOUxFYOR5yhHU666wbw==} + engines: {node: '>=18.0.0'} + hasBin: true + type-check@0.4.0: resolution: {integrity: sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==} engines: {node: '>= 0.8.0'} @@ -2478,6 +3178,9 @@ packages: engines: {node: '>=0.8.0'} hasBin: true + uncrypto@0.1.3: + resolution: {integrity: sha512-Ql87qFHB3s/De2ClA9e0gsnS6zXG27SkTiSJwjCc9MebbfapQfuPzumMIUMi38ezPZVNFcHI9sUIepeQfw8J8Q==} + undici-types@6.21.0: resolution: {integrity: sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==} @@ -2518,6 +3221,12 @@ packages: unist-util-visit@5.0.0: resolution: {integrity: sha512-MR04uvD+07cwl/yhVuVWAtw+3GOR/knlL55Nd/wAdblk27GCVt3lqpTivy/tkJcZoNPzTwS1Y+KMojlLDhoTzg==} + universal-github-app-jwt@2.2.2: + resolution: {integrity: sha512-dcmbeSrOdTnsjGjUfAlqNDJrhxXizjAz94ija9Qw8YkZ1uu0d+GoZzyH+Jb9tIIqvGsadUfwg+22k5aDqqwzbw==} + + universal-user-agent@7.0.3: + resolution: {integrity: sha512-TmnEAEAsBJVZM/AADELsK76llnwcf9vMKuPz8JflO1frO8Lchitr0fNaN9d+Ap0BjKtqWqd/J17qeDnXh8CL2A==} + universalify@2.0.1: resolution: {integrity: sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==} engines: {node: '>= 10.0.0'} @@ -2587,6 +3296,80 @@ packages: vfile@6.0.3: resolution: {integrity: sha512-KzIbH/9tXat2u30jf+smMwFCsno4wHVdNmzFyL+T/L3UGqqk6JKfVqOFOZEpZSHADH1k40ab6NUIXZq422ov3Q==} + vite@7.3.1: + resolution: {integrity: sha512-w+N7Hifpc3gRjZ63vYBXA56dvvRlNWRczTdmCBBa+CotUzAPf5b7YMdMR/8CQoeYE5LX3W4wj6RYTgonm1b9DA==} + engines: {node: ^20.19.0 || >=22.12.0} + hasBin: true + peerDependencies: + '@types/node': ^20.19.0 || >=22.12.0 + jiti: '>=1.21.0' + less: ^4.0.0 + lightningcss: ^1.21.0 + sass: ^1.70.0 + sass-embedded: ^1.70.0 + stylus: '>=0.54.8' + sugarss: ^5.0.0 + terser: ^5.16.0 + tsx: ^4.8.1 + yaml: ^2.4.2 + peerDependenciesMeta: + '@types/node': + optional: true + jiti: + optional: true + less: + optional: true + lightningcss: + optional: true + sass: + optional: true + sass-embedded: + optional: true + stylus: + optional: true + sugarss: + optional: true + terser: + optional: true + tsx: + optional: true + yaml: + optional: true + + vitest@4.0.16: + resolution: {integrity: sha512-E4t7DJ9pESL6E3I8nFjPa4xGUd3PmiWDLsDztS2qXSJWfHtbQnwAWylaBvSNY48I3vr8PTqIZlyK8TE3V3CA4Q==} + engines: {node: ^20.0.0 || ^22.0.0 || >=24.0.0} + hasBin: true + peerDependencies: + '@edge-runtime/vm': '*' + '@opentelemetry/api': ^1.9.0 + '@types/node': ^20.0.0 || ^22.0.0 || >=24.0.0 + '@vitest/browser-playwright': 4.0.16 + '@vitest/browser-preview': 4.0.16 + '@vitest/browser-webdriverio': 4.0.16 + '@vitest/ui': 4.0.16 + happy-dom: '*' + jsdom: '*' + peerDependenciesMeta: + '@edge-runtime/vm': + optional: true + '@opentelemetry/api': + optional: true + '@types/node': + optional: true + '@vitest/browser-playwright': + optional: true + '@vitest/browser-preview': + optional: true + '@vitest/browser-webdriverio': + optional: true + '@vitest/ui': + optional: true + happy-dom: + optional: true + jsdom: + optional: true + walk-up-path@3.0.1: resolution: {integrity: sha512-9YlCL/ynK3CTlrSRrDxZvUauLzAswPCrsaCgilqFevUYpeEW0/3ScEjaa3kbW/T0ghhkEr7mv+fpjqn1Y1YuTA==} @@ -2609,6 +3392,11 @@ packages: engines: {node: ^16.13.0 || >=18.0.0} hasBin: true + why-is-node-running@2.3.0: + resolution: {integrity: sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w==} + engines: {node: '>=8'} + hasBin: true + word-wrap@1.2.5: resolution: {integrity: sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==} engines: {node: '>=0.10.0'} @@ -2680,11 +3468,95 @@ packages: snapshots: + '@algolia/abtesting@1.12.2': + dependencies: + '@algolia/client-common': 5.46.2 + '@algolia/requester-browser-xhr': 5.46.2 + '@algolia/requester-fetch': 5.46.2 + '@algolia/requester-node-http': 5.46.2 + + '@algolia/client-abtesting@5.46.2': + dependencies: + '@algolia/client-common': 5.46.2 + '@algolia/requester-browser-xhr': 5.46.2 + '@algolia/requester-fetch': 5.46.2 + '@algolia/requester-node-http': 5.46.2 + + '@algolia/client-analytics@5.46.2': + dependencies: + '@algolia/client-common': 5.46.2 + '@algolia/requester-browser-xhr': 5.46.2 + '@algolia/requester-fetch': 5.46.2 + '@algolia/requester-node-http': 5.46.2 + + '@algolia/client-common@5.46.2': {} + + '@algolia/client-insights@5.46.2': + dependencies: + '@algolia/client-common': 5.46.2 + '@algolia/requester-browser-xhr': 5.46.2 + '@algolia/requester-fetch': 5.46.2 + '@algolia/requester-node-http': 5.46.2 + + '@algolia/client-personalization@5.46.2': + dependencies: + '@algolia/client-common': 5.46.2 + '@algolia/requester-browser-xhr': 5.46.2 + '@algolia/requester-fetch': 5.46.2 + '@algolia/requester-node-http': 5.46.2 + + '@algolia/client-query-suggestions@5.46.2': + dependencies: + '@algolia/client-common': 5.46.2 + '@algolia/requester-browser-xhr': 5.46.2 + '@algolia/requester-fetch': 5.46.2 + '@algolia/requester-node-http': 5.46.2 + + '@algolia/client-search@5.46.2': + dependencies: + '@algolia/client-common': 5.46.2 + '@algolia/requester-browser-xhr': 5.46.2 + '@algolia/requester-fetch': 5.46.2 + '@algolia/requester-node-http': 5.46.2 + + '@algolia/ingestion@1.46.2': + dependencies: + '@algolia/client-common': 5.46.2 + '@algolia/requester-browser-xhr': 5.46.2 + '@algolia/requester-fetch': 5.46.2 + '@algolia/requester-node-http': 5.46.2 + + '@algolia/monitoring@1.46.2': + dependencies: + '@algolia/client-common': 5.46.2 + '@algolia/requester-browser-xhr': 5.46.2 + '@algolia/requester-fetch': 5.46.2 + '@algolia/requester-node-http': 5.46.2 + + '@algolia/recommend@5.46.2': + dependencies: + '@algolia/client-common': 5.46.2 + '@algolia/requester-browser-xhr': 5.46.2 + '@algolia/requester-fetch': 5.46.2 + '@algolia/requester-node-http': 5.46.2 + + '@algolia/requester-browser-xhr@5.46.2': + dependencies: + '@algolia/client-common': 5.46.2 + + '@algolia/requester-fetch@5.46.2': + dependencies: + '@algolia/client-common': 5.46.2 + + '@algolia/requester-node-http@5.46.2': + dependencies: + '@algolia/client-common': 5.46.2 + '@apidevtools/json-schema-ref-parser@12.0.2': dependencies: '@jsdevtools/ono': 7.1.3 '@types/json-schema': 7.0.15 - js-yaml: 4.1.0 + js-yaml: 4.1.1 '@babel/code-frame@7.26.2': dependencies: @@ -2730,59 +3602,137 @@ snapshots: transitivePeerDependencies: - supports-color - '@babel/types@7.27.0': - dependencies: - '@babel/helper-string-parser': 7.25.9 - '@babel/helper-validator-identifier': 7.25.9 + '@babel/types@7.27.0': + dependencies: + '@babel/helper-string-parser': 7.25.9 + '@babel/helper-validator-identifier': 7.25.9 + + '@blakeembrey/deque@1.0.5': {} + + '@blakeembrey/template@1.2.0': {} + + '@boundaryml/baml-darwin-arm64@0.211.2': + optional: true + + '@boundaryml/baml-darwin-x64@0.211.2': + optional: true + + '@boundaryml/baml-linux-arm64-gnu@0.211.2': + optional: true + + '@boundaryml/baml-linux-arm64-musl@0.211.2': + optional: true + + '@boundaryml/baml-linux-x64-gnu@0.211.2': + optional: true + + '@boundaryml/baml-linux-x64-musl@0.211.2': + optional: true + + '@boundaryml/baml-win32-x64-msvc@0.211.2': + optional: true + + '@boundaryml/baml@0.211.2': + dependencies: + '@scarf/scarf': 1.4.0 + optionalDependencies: + '@boundaryml/baml-darwin-arm64': 0.211.2 + '@boundaryml/baml-darwin-x64': 0.211.2 + '@boundaryml/baml-linux-arm64-gnu': 0.211.2 + '@boundaryml/baml-linux-arm64-musl': 0.211.2 + '@boundaryml/baml-linux-x64-gnu': 0.211.2 + '@boundaryml/baml-linux-x64-musl': 0.211.2 + '@boundaryml/baml-win32-x64-msvc': 0.211.2 + + '@cspotcode/source-map-support@0.8.1': + dependencies: + '@jridgewell/trace-mapping': 0.3.9 + + '@emotion/is-prop-valid@1.2.2': + dependencies: + '@emotion/memoize': 0.8.1 + + '@emotion/memoize@0.8.1': {} + + '@emotion/unitless@0.8.1': {} + + '@esbuild/aix-ppc64@0.27.2': + optional: true + + '@esbuild/android-arm64@0.27.2': + optional: true + + '@esbuild/android-arm@0.27.2': + optional: true + + '@esbuild/android-x64@0.27.2': + optional: true + + '@esbuild/darwin-arm64@0.27.2': + optional: true + + '@esbuild/darwin-x64@0.27.2': + optional: true + + '@esbuild/freebsd-arm64@0.27.2': + optional: true + + '@esbuild/freebsd-x64@0.27.2': + optional: true + + '@esbuild/linux-arm64@0.27.2': + optional: true - '@blakeembrey/deque@1.0.5': {} + '@esbuild/linux-arm@0.27.2': + optional: true - '@blakeembrey/template@1.2.0': {} + '@esbuild/linux-ia32@0.27.2': + optional: true - '@boundaryml/baml-darwin-arm64@0.211.2': + '@esbuild/linux-loong64@0.27.2': optional: true - '@boundaryml/baml-darwin-x64@0.211.2': + '@esbuild/linux-mips64el@0.27.2': optional: true - '@boundaryml/baml-linux-arm64-gnu@0.211.2': + '@esbuild/linux-ppc64@0.27.2': optional: true - '@boundaryml/baml-linux-arm64-musl@0.211.2': + '@esbuild/linux-riscv64@0.27.2': optional: true - '@boundaryml/baml-linux-x64-gnu@0.211.2': + '@esbuild/linux-s390x@0.27.2': optional: true - '@boundaryml/baml-linux-x64-musl@0.211.2': + '@esbuild/linux-x64@0.27.2': optional: true - '@boundaryml/baml-win32-x64-msvc@0.211.2': + '@esbuild/netbsd-arm64@0.27.2': optional: true - '@boundaryml/baml@0.211.2': - dependencies: - '@scarf/scarf': 1.4.0 - optionalDependencies: - '@boundaryml/baml-darwin-arm64': 0.211.2 - '@boundaryml/baml-darwin-x64': 0.211.2 - '@boundaryml/baml-linux-arm64-gnu': 0.211.2 - '@boundaryml/baml-linux-arm64-musl': 0.211.2 - '@boundaryml/baml-linux-x64-gnu': 0.211.2 - '@boundaryml/baml-linux-x64-musl': 0.211.2 - '@boundaryml/baml-win32-x64-msvc': 0.211.2 + '@esbuild/netbsd-x64@0.27.2': + optional: true - '@cspotcode/source-map-support@0.8.1': - dependencies: - '@jridgewell/trace-mapping': 0.3.9 + '@esbuild/openbsd-arm64@0.27.2': + optional: true - '@emotion/is-prop-valid@1.2.2': - dependencies: - '@emotion/memoize': 0.8.1 + '@esbuild/openbsd-x64@0.27.2': + optional: true - '@emotion/memoize@0.8.1': {} + '@esbuild/openharmony-arm64@0.27.2': + optional: true - '@emotion/unitless@0.8.1': {} + '@esbuild/sunos-x64@0.27.2': + optional: true + + '@esbuild/win32-arm64@0.27.2': + optional: true + + '@esbuild/win32-ia32@0.27.2': + optional: true + + '@esbuild/win32-x64@0.27.2': + optional: true '@eslint-community/eslint-utils@4.6.1(eslint@9.25.1(jiti@2.4.2))': dependencies: @@ -2813,7 +3763,7 @@ snapshots: globals: 14.0.0 ignore: 5.3.2 import-fresh: 3.3.1 - js-yaml: 4.1.0 + js-yaml: 4.1.1 minimatch: 3.1.2 strip-json-comments: 3.1.1 transitivePeerDependencies: @@ -2872,6 +3822,8 @@ snapshots: '@jridgewell/sourcemap-codec@1.5.0': {} + '@jridgewell/sourcemap-codec@1.5.5': {} + '@jridgewell/trace-mapping@0.3.25': dependencies: '@jridgewell/resolve-uri': 3.1.2 @@ -2977,6 +3929,153 @@ snapshots: dependencies: which: 4.0.0 + '@octokit/app@16.1.2': + dependencies: + '@octokit/auth-app': 8.1.2 + '@octokit/auth-unauthenticated': 7.0.3 + '@octokit/core': 7.0.6 + '@octokit/oauth-app': 8.0.3 + '@octokit/plugin-paginate-rest': 14.0.0(@octokit/core@7.0.6) + '@octokit/types': 16.0.0 + '@octokit/webhooks': 14.2.0 + + '@octokit/auth-app@8.1.2': + dependencies: + '@octokit/auth-oauth-app': 9.0.3 + '@octokit/auth-oauth-user': 6.0.2 + '@octokit/request': 10.0.7 + '@octokit/request-error': 7.1.0 + '@octokit/types': 16.0.0 + toad-cache: 3.7.0 + universal-github-app-jwt: 2.2.2 + universal-user-agent: 7.0.3 + + '@octokit/auth-oauth-app@9.0.3': + dependencies: + '@octokit/auth-oauth-device': 8.0.3 + '@octokit/auth-oauth-user': 6.0.2 + '@octokit/request': 10.0.7 + '@octokit/types': 16.0.0 + universal-user-agent: 7.0.3 + + '@octokit/auth-oauth-device@8.0.3': + dependencies: + '@octokit/oauth-methods': 6.0.2 + '@octokit/request': 10.0.7 + '@octokit/types': 16.0.0 + universal-user-agent: 7.0.3 + + '@octokit/auth-oauth-user@6.0.2': + dependencies: + '@octokit/auth-oauth-device': 8.0.3 + '@octokit/oauth-methods': 6.0.2 + '@octokit/request': 10.0.7 + '@octokit/types': 16.0.0 + universal-user-agent: 7.0.3 + + '@octokit/auth-token@6.0.0': {} + + '@octokit/auth-unauthenticated@7.0.3': + dependencies: + '@octokit/request-error': 7.1.0 + '@octokit/types': 16.0.0 + + '@octokit/core@7.0.6': + dependencies: + '@octokit/auth-token': 6.0.0 + '@octokit/graphql': 9.0.3 + '@octokit/request': 10.0.7 + '@octokit/request-error': 7.1.0 + '@octokit/types': 16.0.0 + before-after-hook: 4.0.0 + universal-user-agent: 7.0.3 + + '@octokit/endpoint@11.0.2': + dependencies: + '@octokit/types': 16.0.0 + universal-user-agent: 7.0.3 + + '@octokit/graphql@9.0.3': + dependencies: + '@octokit/request': 10.0.7 + '@octokit/types': 16.0.0 + universal-user-agent: 7.0.3 + + '@octokit/oauth-app@8.0.3': + dependencies: + '@octokit/auth-oauth-app': 9.0.3 + '@octokit/auth-oauth-user': 6.0.2 + '@octokit/auth-unauthenticated': 7.0.3 + '@octokit/core': 7.0.6 + '@octokit/oauth-authorization-url': 8.0.0 + '@octokit/oauth-methods': 6.0.2 + '@types/aws-lambda': 8.10.159 + universal-user-agent: 7.0.3 + + '@octokit/oauth-authorization-url@8.0.0': {} + + '@octokit/oauth-methods@6.0.2': + dependencies: + '@octokit/oauth-authorization-url': 8.0.0 + '@octokit/request': 10.0.7 + '@octokit/request-error': 7.1.0 + '@octokit/types': 16.0.0 + + '@octokit/openapi-types@27.0.0': {} + + '@octokit/openapi-webhooks-types@12.1.0': {} + + '@octokit/plugin-paginate-graphql@6.0.0(@octokit/core@7.0.6)': + dependencies: + '@octokit/core': 7.0.6 + + '@octokit/plugin-paginate-rest@14.0.0(@octokit/core@7.0.6)': + dependencies: + '@octokit/core': 7.0.6 + '@octokit/types': 16.0.0 + + '@octokit/plugin-rest-endpoint-methods@17.0.0(@octokit/core@7.0.6)': + dependencies: + '@octokit/core': 7.0.6 + '@octokit/types': 16.0.0 + + '@octokit/plugin-retry@8.0.3(@octokit/core@7.0.6)': + dependencies: + '@octokit/core': 7.0.6 + '@octokit/request-error': 7.1.0 + '@octokit/types': 16.0.0 + bottleneck: 2.19.5 + + '@octokit/plugin-throttling@11.0.3(@octokit/core@7.0.6)': + dependencies: + '@octokit/core': 7.0.6 + '@octokit/types': 16.0.0 + bottleneck: 2.19.5 + + '@octokit/request-error@7.1.0': + dependencies: + '@octokit/types': 16.0.0 + + '@octokit/request@10.0.7': + dependencies: + '@octokit/endpoint': 11.0.2 + '@octokit/request-error': 7.1.0 + '@octokit/types': 16.0.0 + fast-content-type-parse: 3.0.0 + universal-user-agent: 7.0.3 + + '@octokit/types@16.0.0': + dependencies: + '@octokit/openapi-types': 27.0.0 + + '@octokit/webhooks-methods@6.0.0': {} + + '@octokit/webhooks@14.2.0': + dependencies: + '@octokit/openapi-webhooks-types': 12.1.0 + '@octokit/request-error': 7.1.0 + '@octokit/webhooks-methods': 6.0.0 + '@open-rpc/meta-schema@1.14.9': {} '@open-rpc/schema-utils-js@2.1.2': @@ -3166,7 +4265,7 @@ snapshots: colorette: 1.4.0 https-proxy-agent: 7.0.6 js-levenshtein: 1.1.6 - js-yaml: 4.1.0 + js-yaml: 4.1.1 minimatch: 5.1.6 pluralize: 8.0.0 yaml-ast-parser: 0.0.43 @@ -3198,10 +4297,87 @@ snapshots: - ajv - supports-color + '@rollup/rollup-android-arm-eabi@4.55.1': + optional: true + + '@rollup/rollup-android-arm64@4.55.1': + optional: true + + '@rollup/rollup-darwin-arm64@4.55.1': + optional: true + + '@rollup/rollup-darwin-x64@4.55.1': + optional: true + + '@rollup/rollup-freebsd-arm64@4.55.1': + optional: true + + '@rollup/rollup-freebsd-x64@4.55.1': + optional: true + + '@rollup/rollup-linux-arm-gnueabihf@4.55.1': + optional: true + + '@rollup/rollup-linux-arm-musleabihf@4.55.1': + optional: true + + '@rollup/rollup-linux-arm64-gnu@4.55.1': + optional: true + + '@rollup/rollup-linux-arm64-musl@4.55.1': + optional: true + + '@rollup/rollup-linux-loong64-gnu@4.55.1': + optional: true + + '@rollup/rollup-linux-loong64-musl@4.55.1': + optional: true + + '@rollup/rollup-linux-ppc64-gnu@4.55.1': + optional: true + + '@rollup/rollup-linux-ppc64-musl@4.55.1': + optional: true + + '@rollup/rollup-linux-riscv64-gnu@4.55.1': + optional: true + + '@rollup/rollup-linux-riscv64-musl@4.55.1': + optional: true + + '@rollup/rollup-linux-s390x-gnu@4.55.1': + optional: true + + '@rollup/rollup-linux-x64-gnu@4.55.1': + optional: true + + '@rollup/rollup-linux-x64-musl@4.55.1': + optional: true + + '@rollup/rollup-openbsd-x64@4.55.1': + optional: true + + '@rollup/rollup-openharmony-arm64@4.55.1': + optional: true + + '@rollup/rollup-win32-arm64-msvc@4.55.1': + optional: true + + '@rollup/rollup-win32-ia32-msvc@4.55.1': + optional: true + + '@rollup/rollup-win32-x64-gnu@4.55.1': + optional: true + + '@rollup/rollup-win32-x64-msvc@4.55.1': + optional: true + '@scarf/scarf@1.4.0': {} '@sinclair/typebox@0.27.8': {} + '@standard-schema/spec@1.1.0': {} + '@trivago/prettier-plugin-sort-imports@5.2.2(prettier@3.4.2)': dependencies: '@babel/generator': 7.27.0 @@ -3222,6 +4398,13 @@ snapshots: '@tsconfig/node16@1.0.4': {} + '@types/aws-lambda@8.10.159': {} + + '@types/chai@5.2.3': + dependencies: + '@types/deep-eql': 4.0.2 + assertion-error: 2.0.1 + '@types/concat-stream@2.0.3': dependencies: '@types/node': 22.14.1 @@ -3230,12 +4413,16 @@ snapshots: dependencies: '@types/ms': 2.1.0 + '@types/deep-eql@4.0.2': {} + '@types/estree-jsx@1.0.5': dependencies: '@types/estree': 1.0.7 '@types/estree@1.0.7': {} + '@types/estree@1.0.8': {} + '@types/hast@3.0.4': dependencies: '@types/unist': 3.0.3 @@ -3250,6 +4437,12 @@ snapshots: '@types/json-schema@7.0.15': {} + '@types/lodash-es@4.17.12': + dependencies: + '@types/lodash': 4.17.21 + + '@types/lodash@4.17.21': {} + '@types/mdast@4.0.4': dependencies: '@types/unist': 3.0.3 @@ -3352,6 +4545,49 @@ snapshots: '@typescript-eslint/types': 8.31.0 eslint-visitor-keys: 4.2.0 + '@upstash/redis@1.36.1': + dependencies: + uncrypto: 0.1.3 + + '@vitest/expect@4.0.16': + dependencies: + '@standard-schema/spec': 1.1.0 + '@types/chai': 5.2.3 + '@vitest/spy': 4.0.16 + '@vitest/utils': 4.0.16 + chai: 6.2.2 + tinyrainbow: 3.0.3 + + '@vitest/mocker@4.0.16(vite@7.3.1(@types/node@22.14.1)(jiti@2.4.2)(tsx@4.21.0)(yaml@2.7.1))': + dependencies: + '@vitest/spy': 4.0.16 + estree-walker: 3.0.3 + magic-string: 0.30.21 + optionalDependencies: + vite: 7.3.1(@types/node@22.14.1)(jiti@2.4.2)(tsx@4.21.0)(yaml@2.7.1) + + '@vitest/pretty-format@4.0.16': + dependencies: + tinyrainbow: 3.0.3 + + '@vitest/runner@4.0.16': + dependencies: + '@vitest/utils': 4.0.16 + pathe: 2.0.3 + + '@vitest/snapshot@4.0.16': + dependencies: + '@vitest/pretty-format': 4.0.16 + magic-string: 0.30.21 + pathe: 2.0.3 + + '@vitest/spy@4.0.16': {} + + '@vitest/utils@4.0.16': + dependencies: + '@vitest/pretty-format': 4.0.16 + tinyrainbow: 3.0.3 + abbrev@2.0.0: {} abort-controller@3.0.0: @@ -3377,6 +4613,23 @@ snapshots: json-schema-traverse: 0.4.1 uri-js: 4.4.1 + algoliasearch@5.46.2: + dependencies: + '@algolia/abtesting': 1.12.2 + '@algolia/client-abtesting': 5.46.2 + '@algolia/client-analytics': 5.46.2 + '@algolia/client-common': 5.46.2 + '@algolia/client-insights': 5.46.2 + '@algolia/client-personalization': 5.46.2 + '@algolia/client-query-suggestions': 5.46.2 + '@algolia/client-search': 5.46.2 + '@algolia/ingestion': 1.46.2 + '@algolia/monitoring': 1.46.2 + '@algolia/recommend': 5.46.2 + '@algolia/requester-browser-xhr': 5.46.2 + '@algolia/requester-fetch': 5.46.2 + '@algolia/requester-node-http': 5.46.2 + ansi-escapes@7.0.0: dependencies: environment: 1.1.0 @@ -3400,14 +4653,22 @@ snapshots: arg@4.1.3: {} + argparse@1.0.10: + dependencies: + sprintf-js: 1.0.3 + argparse@2.0.1: {} + assertion-error@2.0.1: {} + asynckit@0.4.0: {} bail@2.0.2: {} balanced-match@1.0.2: {} + before-after-hook@4.0.0: {} + better-ajv-errors@1.2.0(ajv@6.12.6): dependencies: '@babel/code-frame': 7.26.2 @@ -3419,6 +4680,8 @@ snapshots: binary-extensions@2.3.0: {} + bottleneck@2.19.5: {} + brace-expansion@1.1.11: dependencies: balanced-match: 1.0.2 @@ -3451,6 +4714,8 @@ snapshots: ccount@2.0.1: {} + chai@6.2.2: {} + chalk@4.1.2: dependencies: ansi-styles: 4.3.0 @@ -3478,6 +4743,10 @@ snapshots: optionalDependencies: fsevents: 2.3.3 + chokidar@4.0.3: + dependencies: + readdirp: 4.1.2 + ci-info@4.2.0: {} classnames@2.5.1: {} @@ -3636,6 +4905,8 @@ snapshots: es-errors@1.3.0: {} + es-module-lexer@1.7.0: {} + es-object-atoms@1.1.1: dependencies: es-errors: 1.3.0 @@ -3649,6 +4920,35 @@ snapshots: es6-promise@3.3.1: {} + esbuild@0.27.2: + optionalDependencies: + '@esbuild/aix-ppc64': 0.27.2 + '@esbuild/android-arm': 0.27.2 + '@esbuild/android-arm64': 0.27.2 + '@esbuild/android-x64': 0.27.2 + '@esbuild/darwin-arm64': 0.27.2 + '@esbuild/darwin-x64': 0.27.2 + '@esbuild/freebsd-arm64': 0.27.2 + '@esbuild/freebsd-x64': 0.27.2 + '@esbuild/linux-arm': 0.27.2 + '@esbuild/linux-arm64': 0.27.2 + '@esbuild/linux-ia32': 0.27.2 + '@esbuild/linux-loong64': 0.27.2 + '@esbuild/linux-mips64el': 0.27.2 + '@esbuild/linux-ppc64': 0.27.2 + '@esbuild/linux-riscv64': 0.27.2 + '@esbuild/linux-s390x': 0.27.2 + '@esbuild/linux-x64': 0.27.2 + '@esbuild/netbsd-arm64': 0.27.2 + '@esbuild/netbsd-x64': 0.27.2 + '@esbuild/openbsd-arm64': 0.27.2 + '@esbuild/openbsd-x64': 0.27.2 + '@esbuild/openharmony-arm64': 0.27.2 + '@esbuild/sunos-x64': 0.27.2 + '@esbuild/win32-arm64': 0.27.2 + '@esbuild/win32-ia32': 0.27.2 + '@esbuild/win32-x64': 0.27.2 + escalade@3.2.0: {} escape-string-regexp@4.0.0: {} @@ -3758,6 +5058,8 @@ snapshots: acorn-jsx: 5.3.2(acorn@8.14.1) eslint-visitor-keys: 4.2.0 + esprima@4.0.1: {} + esquery@1.6.0: dependencies: estraverse: 5.3.0 @@ -3775,6 +5077,10 @@ snapshots: '@types/estree-jsx': 1.0.5 '@types/unist': 3.0.3 + estree-walker@3.0.3: + dependencies: + '@types/estree': 1.0.7 + esutils@2.0.3: {} event-target-shim@5.0.1: {} @@ -3793,8 +5099,16 @@ snapshots: signal-exit: 4.1.0 strip-final-newline: 3.0.0 + expect-type@1.3.0: {} + + extend-shallow@2.0.1: + dependencies: + is-extendable: 0.1.1 + extend@3.0.2: {} + fast-content-type-parse@3.0.0: {} + fast-deep-equal@3.1.3: {} fast-glob@3.3.3: @@ -3823,6 +5137,10 @@ snapshots: dependencies: format: 0.2.2 + fdir@6.5.0(picomatch@4.0.3): + optionalDependencies: + picomatch: 4.0.3 + fern-api@3.13.0: dependencies: '@boundaryml/baml': 0.211.2 @@ -3909,6 +5227,10 @@ snapshots: get-stream@8.0.1: {} + get-tsconfig@4.13.0: + dependencies: + resolve-pkg-maps: 1.0.0 + glob-parent@5.1.2: dependencies: is-glob: 4.0.3 @@ -3947,6 +5269,13 @@ snapshots: graphemer@1.4.0: {} + gray-matter@4.0.3: + dependencies: + js-yaml: 3.14.2 + kind-of: 6.0.3 + section-matter: 1.0.0 + strip-bom-string: 1.0.0 + handlebars@4.7.8: dependencies: minimist: 1.2.8 @@ -4026,6 +5355,8 @@ snapshots: is-empty@1.2.0: {} + is-extendable@0.1.1: {} + is-extglob@2.1.1: {} is-fullwidth-code-point@3.0.0: {} @@ -4099,10 +5430,19 @@ snapshots: js-tokens@4.0.0: {} + js-yaml@3.14.2: + dependencies: + argparse: 1.0.10 + esprima: 4.0.1 + js-yaml@4.1.0: dependencies: argparse: 2.0.1 + js-yaml@4.1.1: + dependencies: + argparse: 2.0.1 + jsep@1.4.0: {} jsesc@3.1.0: {} @@ -4149,6 +5489,8 @@ snapshots: dependencies: json-buffer: 3.0.1 + kind-of@6.0.3: {} + kleur@4.1.5: {} leven@3.1.0: {} @@ -4197,6 +5539,8 @@ snapshots: dependencies: p-locate: 5.0.0 + lodash-es@4.17.22: {} + lodash.merge@4.6.2: {} lodash@4.17.21: {} @@ -4221,6 +5565,10 @@ snapshots: lunr@2.3.9: {} + magic-string@0.30.21: + dependencies: + '@jridgewell/sourcemap-codec': 1.5.5 + make-error@1.3.6: {} mark.js@8.11.1: {} @@ -4709,6 +6057,22 @@ snapshots: object-assign@4.1.1: {} + obug@2.1.1: {} + + octokit@5.0.5: + dependencies: + '@octokit/app': 16.1.2 + '@octokit/core': 7.0.6 + '@octokit/oauth-app': 8.0.3 + '@octokit/plugin-paginate-graphql': 6.0.0(@octokit/core@7.0.6) + '@octokit/plugin-paginate-rest': 14.0.0(@octokit/core@7.0.6) + '@octokit/plugin-rest-endpoint-methods': 17.0.0(@octokit/core@7.0.6) + '@octokit/plugin-retry': 8.0.3(@octokit/core@7.0.6) + '@octokit/plugin-throttling': 11.0.3(@octokit/core@7.0.6) + '@octokit/request-error': 7.1.0 + '@octokit/types': 16.0.0 + '@octokit/webhooks': 14.2.0 + once@1.4.0: dependencies: wrappy: 1.0.2 @@ -4744,6 +6108,8 @@ snapshots: fast-xml-parser: 4.5.3 json-pointer: 0.6.2 + openapi-types@12.1.3: {} + optionator@0.9.4: dependencies: deep-is: 0.1.4 @@ -4802,12 +6168,16 @@ snapshots: lru-cache: 10.4.3 minipass: 7.1.2 + pathe@2.0.3: {} + perfect-scrollbar@1.5.6: {} picocolors@1.1.1: {} picomatch@2.3.1: {} + picomatch@4.0.3: {} + pidtree@0.6.0: {} pluralize@8.0.0: {} @@ -4824,6 +6194,12 @@ snapshots: picocolors: 1.1.1 source-map-js: 1.2.1 + postcss@8.5.6: + dependencies: + nanoid: 3.3.11 + picocolors: 1.1.1 + source-map-js: 1.2.1 + prelude-ls@1.2.1: {} prettier@3.4.2: {} @@ -4908,6 +6284,8 @@ snapshots: dependencies: picomatch: 2.3.1 + readdirp@4.1.2: {} + redoc@2.4.0(core-js@3.41.0)(mobx@6.13.7)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)(styled-components@6.1.17(react-dom@19.1.0(react@19.1.0))(react@19.1.0)): dependencies: '@redocly/openapi-core': 1.34.2 @@ -5524,6 +6902,8 @@ snapshots: resolve-from@4.0.0: {} + resolve-pkg-maps@1.0.0: {} + restore-cursor@5.1.0: dependencies: onetime: 7.0.0 @@ -5535,6 +6915,37 @@ snapshots: rfdc@1.4.1: {} + rollup@4.55.1: + dependencies: + '@types/estree': 1.0.8 + optionalDependencies: + '@rollup/rollup-android-arm-eabi': 4.55.1 + '@rollup/rollup-android-arm64': 4.55.1 + '@rollup/rollup-darwin-arm64': 4.55.1 + '@rollup/rollup-darwin-x64': 4.55.1 + '@rollup/rollup-freebsd-arm64': 4.55.1 + '@rollup/rollup-freebsd-x64': 4.55.1 + '@rollup/rollup-linux-arm-gnueabihf': 4.55.1 + '@rollup/rollup-linux-arm-musleabihf': 4.55.1 + '@rollup/rollup-linux-arm64-gnu': 4.55.1 + '@rollup/rollup-linux-arm64-musl': 4.55.1 + '@rollup/rollup-linux-loong64-gnu': 4.55.1 + '@rollup/rollup-linux-loong64-musl': 4.55.1 + '@rollup/rollup-linux-ppc64-gnu': 4.55.1 + '@rollup/rollup-linux-ppc64-musl': 4.55.1 + '@rollup/rollup-linux-riscv64-gnu': 4.55.1 + '@rollup/rollup-linux-riscv64-musl': 4.55.1 + '@rollup/rollup-linux-s390x-gnu': 4.55.1 + '@rollup/rollup-linux-x64-gnu': 4.55.1 + '@rollup/rollup-linux-x64-musl': 4.55.1 + '@rollup/rollup-openbsd-x64': 4.55.1 + '@rollup/rollup-openharmony-arm64': 4.55.1 + '@rollup/rollup-win32-arm64-msvc': 4.55.1 + '@rollup/rollup-win32-ia32-msvc': 4.55.1 + '@rollup/rollup-win32-x64-gnu': 4.55.1 + '@rollup/rollup-win32-x64-msvc': 4.55.1 + fsevents: 2.3.3 + run-applescript@7.0.0: {} run-parallel@1.2.0: @@ -5549,6 +6960,11 @@ snapshots: scheduler@0.26.0: {} + section-matter@1.0.0: + dependencies: + extend-shallow: 2.0.1 + kind-of: 6.0.3 + semver@7.7.1: {} set-cookie-parser@2.7.1: {} @@ -5587,6 +7003,8 @@ snapshots: should-type-adaptors: 1.1.0 should-util: 1.0.1 + siginfo@2.0.0: {} + signal-exit@4.1.0: {} simple-websocket@9.1.0: @@ -5633,6 +7051,12 @@ snapshots: spdx-license-ids@3.0.21: {} + sprintf-js@1.0.3: {} + + stackback@0.0.2: {} + + std-env@3.10.0: {} + stickyfill@1.1.1: {} string-argv@0.3.2: {} @@ -5678,6 +7102,8 @@ snapshots: dependencies: ansi-regex: 6.1.0 + strip-bom-string@1.0.0: {} + strip-final-newline@3.0.0: {} strip-json-comments@3.1.1: {} @@ -5727,10 +7153,23 @@ snapshots: '@pkgr/core': 0.2.4 tslib: 2.8.1 + tinybench@2.9.0: {} + + tinyexec@1.0.2: {} + + tinyglobby@0.2.15: + dependencies: + fdir: 6.5.0(picomatch@4.0.3) + picomatch: 4.0.3 + + tinyrainbow@3.0.3: {} + to-regex-range@5.0.1: dependencies: is-number: 7.0.0 + toad-cache@3.7.0: {} + tr46@0.0.3: {} tree-kill@1.2.2: {} @@ -5763,6 +7202,13 @@ snapshots: tslib@2.8.1: {} + tsx@4.21.0: + dependencies: + esbuild: 0.27.2 + get-tsconfig: 4.13.0 + optionalDependencies: + fsevents: 2.3.3 + type-check@0.4.0: dependencies: prelude-ls: 1.2.1 @@ -5786,6 +7232,8 @@ snapshots: uglify-js@3.19.3: optional: true + uncrypto@0.1.3: {} + undici-types@6.21.0: {} undici@6.21.2: {} @@ -5876,6 +7324,10 @@ snapshots: unist-util-is: 6.0.0 unist-util-visit-parents: 6.0.1 + universal-github-app-jwt@2.2.2: {} + + universal-user-agent@7.0.3: {} + universalify@2.0.1: {} uri-js-replace@1.0.1: {} @@ -5959,6 +7411,59 @@ snapshots: '@types/unist': 3.0.3 vfile-message: 4.0.2 + vite@7.3.1(@types/node@22.14.1)(jiti@2.4.2)(tsx@4.21.0)(yaml@2.7.1): + dependencies: + esbuild: 0.27.2 + fdir: 6.5.0(picomatch@4.0.3) + picomatch: 4.0.3 + postcss: 8.5.6 + rollup: 4.55.1 + tinyglobby: 0.2.15 + optionalDependencies: + '@types/node': 22.14.1 + fsevents: 2.3.3 + jiti: 2.4.2 + tsx: 4.21.0 + yaml: 2.7.1 + + vitest@4.0.16(@opentelemetry/api@1.9.0)(@types/node@22.14.1)(jiti@2.4.2)(tsx@4.21.0)(yaml@2.7.1): + dependencies: + '@vitest/expect': 4.0.16 + '@vitest/mocker': 4.0.16(vite@7.3.1(@types/node@22.14.1)(jiti@2.4.2)(tsx@4.21.0)(yaml@2.7.1)) + '@vitest/pretty-format': 4.0.16 + '@vitest/runner': 4.0.16 + '@vitest/snapshot': 4.0.16 + '@vitest/spy': 4.0.16 + '@vitest/utils': 4.0.16 + es-module-lexer: 1.7.0 + expect-type: 1.3.0 + magic-string: 0.30.21 + obug: 2.1.1 + pathe: 2.0.3 + picomatch: 4.0.3 + std-env: 3.10.0 + tinybench: 2.9.0 + tinyexec: 1.0.2 + tinyglobby: 0.2.15 + tinyrainbow: 3.0.3 + vite: 7.3.1(@types/node@22.14.1)(jiti@2.4.2)(tsx@4.21.0)(yaml@2.7.1) + why-is-node-running: 2.3.0 + optionalDependencies: + '@opentelemetry/api': 1.9.0 + '@types/node': 22.14.1 + transitivePeerDependencies: + - jiti + - less + - lightningcss + - msw + - sass + - sass-embedded + - stylus + - sugarss + - terser + - tsx + - yaml + walk-up-path@3.0.1: {} webidl-conversions@3.0.1: {} @@ -5978,6 +7483,11 @@ snapshots: dependencies: isexe: 3.1.1 + why-is-node-running@2.3.0: + dependencies: + siginfo: 2.0.0 + stackback: 0.0.2 + word-wrap@1.2.5: {} wordwrap@1.0.0: {} diff --git a/scripts/generate-content-index.ts b/scripts/generate-content-index.ts new file mode 100644 index 000000000..1cc37d8e2 --- /dev/null +++ b/scripts/generate-content-index.ts @@ -0,0 +1,44 @@ +#!/usr/bin/env tsx +import path from "path"; + +import type { ContentSource } from "@/content-indexer/core/batch-fetcher"; +import { buildContentIndex } from "@/content-indexer/index"; +import { DOCS_REPO } from "@/content-indexer/utils/github"; + +/** + * Temporary script for testing Phase 2: filesystem support + * This will be expanded in Phase 3 to support multiple indexers and modes + */ +async function main() { + try { + console.log("🚀 Testing content indexer with filesystem support...\n"); + + // For now, test with filesystem source reading from local fern/ directory + const source: ContentSource = { + type: "filesystem", + basePath: path.join(process.cwd(), "fern"), + }; + + // Build the content index + const { pathIndex, navigationTrees, algoliaRecords } = + await buildContentIndex(source, DOCS_REPO); + + console.log("\n✅ Content indexer completed successfully!"); + console.log(` Generated ${Object.keys(pathIndex).length} path entries`); + console.log( + ` Generated ${Object.keys(navigationTrees).length} navigation trees`, + ); + console.log(` Generated ${algoliaRecords.length} Algolia records`); + + // TODO: Phase 3 will add: + // - Mode support (preview/production) + // - Indexer type support (main/sdk/changelog) + // - Branch ID support + // - Upload to Redis and Algolia + } catch (error) { + console.error("❌ Error generating content index:", error); + process.exit(1); + } +} + +void main(); diff --git a/src/content-indexer/README.md b/src/content-indexer/README.md new file mode 100644 index 000000000..4c4b476ac --- /dev/null +++ b/src/content-indexer/README.md @@ -0,0 +1,342 @@ +# Content Indexer + +Content Indexer is a single entry-point content indexing system that builds path +indexes, navigation trees, and Algolia search records from remote docs files. It +represents the core system that powers the Alchemy docs site's static generation +system. It processes `docs.yml` configuration files from multiple Github repos +to generate three key outputs: + +1. **Path Index**: Maps URL paths to content sources (MDX files, API specs) +2. **Navigation Trees**: Hierarchical sidebar navigation for each documentation tab +3. **Algolia Index**: Searchable content records with metadata + +The system is optimized for processing **4000+ pages** using a 3-phase parallel +fetching architecture that minimizes API calls. + +## Architecture + +### 3-Phase Processing + +```mermaid +flowchart TD + Start[buildContentIndex] --> FetchYml[Fetch docs.yml] + FetchYml --> Phase1[Phase 1: SCAN] + + Phase1 --> ScanDocs[scanDocsYml] + ScanDocs --> CollectPaths[Collect all MDX paths] + ScanDocs --> CollectSpecs[Collect all spec names] + + CollectPaths --> Phase2[Phase 2: BATCH FETCH] + CollectSpecs --> Phase2 + + Phase2 --> FetchMDX[Fetch all MDX files in parallel] + Phase2 --> FetchSpecs[Fetch all specs in parallel] + + FetchMDX --> Cache[ContentCache] + FetchSpecs --> Cache + + Cache --> Phase3[Phase 3: PROCESS] + + Phase3 --> ProcessNav[visitNavigationItem with cache] + ProcessNav --> BuildIndex[Build PathIndex] + ProcessNav --> BuildNavTrees[Build Navigation Trees] + ProcessNav --> BuildAlgolia[Build Algolia Records with breadcrumbs] + + BuildIndex --> WritePhase[WRITE PHASE] + BuildNavTrees --> WritePhase + BuildAlgolia --> WritePhase + + WritePhase --> Parallel[Upload in parallel] + Parallel --> Redis[storeToRedis] + Parallel --> Algolia[uploadToAlgolia] +``` + +### Why This Architecture? + +With **4000+ pages**, GitHub API calls are the primary bottleneck. This 3-phase approach: + +1. **Maximizes parallelization**: All fetches happen simultaneously +2. **Eliminates wait time**: No sequential blocking between fetches +3. **Single-pass processing**: Build all outputs together with all data + available + +## Key Concepts + +### Constructed Data + +* **Path Index**: Used by Next.js routing to determine which content to render + for a given URL + * Maps paths like `reference/ethereum-api-quickstart` to MDX files or API + operations + * Stored in Redis for fast lookup at runtime + +* **Navigation Trees**: Used to render sidebar navigation + * Hierarchical structure with sections, pages, and API endpoints + * One tree per tab (e.g., "reference", "guides") + * Stored in Redis + +* **Algolia Index**: Used for site-wide search + * Flat list of searchable pages with content, title, breadcrumbs + * Uploaded to Algolia for full-text search + * Updated atomically to avoid search downtime + +### ContentCache + +The `ContentCache` class provides O(1) lookup for all fetched content: + +* **MDX files**: Stores frontmatter and raw MDX body +* **API specs**: Stores parsed OpenAPI/OpenRPC specifications + +By fetching everything upfront in Phase 2, we eliminate duplicate GitHub API +calls during processing. + +### Relatively Stable ObjectIDs for Algolia + +Algolia requires unique `objectID` for each record. We generate deterministic +hashes (SHA-256, first 16 chars) from content-based identifiers: + +* **All pages (MDX and API methods)**: Hash of last breadcrumb + title (e.g., hash of + `"NFT API Endpoints:getNFTsForCollection"`) + * Based on logical position in navigation hierarchy + page title + * Stable as long as content structure and title don't change + * Changes when page is renamed or moved to different section + * Generates clean IDs like `"a3f2c8e1b9d4f6a7"` + +**Why this approach?** Since we replace the entire index on each run (atomic swap), +absolute stability isn't critical. Content-based IDs are simple and work consistently for all content types. +There is no field that provides absolute stability since everything can change. + +**Why hashes?** Provides compact, opaque identifiers that don't expose internal +structure while maintaining uniqueness. + +## Design Decisions + +### 1. Three-Phase Architecture + +**Why?** With 4000+ pages, GitHub API calls dominate execution time. By fetching +everything upfront in parallel, we significantly reduce total runtime. + +**Alternative considered:** Fetch-as-you-go + +* **Pros:** Simpler code, streaming approach +* **Cons:** Sequential fetches, slower for large repos + +### 2. ContentCache for O(1) Lookups + +**Why?** Processing requires multiple lookups (ex. frontmatter for slugs, content +for Algolia). A Map-based cache makes these instant. + +### 3. Single-Pass Processing + +**Why?** Build all outputs (index, nav, Algolia) in one traversal to avoid +processing each item multiple times. + +**Alternative considered:** Separate passes for each output + +* **Pros:** Simpler logic per pass +* **Cons:** Slower, harder to maintain breadcrumbs + +### 4. Atomic Index Swap for Algolia + +Ordinarily we could upload records individually to prod index and use `objectIDs` +to update records in place. The problem is our IDs are based on file paths, which +can change (files renamed, URLs restructured). When a path changes, we generate a +new objectID, leaving the old record orphaned in the index. Instead of managing +deletes and updates, we maintain separate indices for each content source (docs +vs wallets) and fully rebuild/replace the entire index on each run. This is done +via an atomic index swap. + +**How it works:** + +1. Upload to temporary index +2. Copy settings from production +3. Atomic move (replace production with temp index) + +**Why?** Ensure all records are up-to-date. Zero down-time: prevent users from ever seeing empty +search results during index updates. + +**Alternative considered:** Update records in-place + +* **Pros:** Simpler +* **Cons:** High risk of orphaned records + +### 5. Separate Indices for Docs and Wallets + +Docs and Wallets content is indexed separately because their docs.yml is +maintained separately. That means we need to update one without the other. Since +we cannot update records in place, an atomic index swap of both docs and wallets +content would mean we need to generate both content simultaneously which is +inefficient. Instead, maintain separate indices and have the frontend search +both indices simultaneously. The same also applies to changelog which has a +separate indexer. + +**Why?** Independent update schedules + +**How to search both:** Frontend uses `multipleQueries` API or InstantSearch's +multi-index feature. + +## Directory Structure + +```text +content-indexer/ +├── collectors/ # Output collectors (Phase 3) +│ ├── algolia.ts # Collects Algolia search records +│ ├── navigation-trees.ts # Collects navigation trees by tab +│ ├── path-index.ts # Collects path index entries +│ └── processing-context.ts # Unified context encapsulating all collectors +├── core/ # Core processing logic +│ ├── scanner.ts # Phase 1: Scan docs.yml for paths/specs +│ ├── batch-fetcher.ts # Phase 2: Parallel fetch all content +│ ├── content-cache.ts # Phase 2: In-memory cache for fetched content +│ ├── build-all-outputs.ts # Phase 3: Main orchestrator +│ └── path-builder.ts # Hierarchical URL path builder +├── visitors/ # Visitor pattern for processing (Phase 3) +│ ├── index.ts # Main dispatcher (visitNavigationItem) +│ ├── visit-page.ts # Processes MDX pages +│ ├── visit-section.ts # Processes sections (with recursion) +│ ├── visit-link.ts # Processes external links +│ ├── visit-api-reference.ts # Orchestrates API spec processing +│ └── processors/ +│ ├── process-openapi.ts # Processes OpenAPI specifications +│ └── process-openrpc.ts # Processes OpenRPC specifications +├── uploaders/ # Upload to external services +│ ├── algolia.ts # Uploads to Algolia with atomic swap +│ └── redis.ts # Stores to Redis (path index & nav trees) +├── utils/ # Utility functions +│ ├── openapi.ts # OpenAPI-specific utilities +│ ├── openrpc.ts # OpenRPC-specific utilities +│ ├── navigation-helpers.ts # Navigation construction helpers +│ ├── truncate-record.ts # Truncates Algolia records to size limit +│ └── normalization.ts # Path normalization utilities +└── index.ts # Main entry point (buildContentIndex) +``` + +## Data Flow + +### Phase 1: Scan + +First, fetch the `docs.yml` file from GitHub. Then scan it: + +```typescript +scanDocsYml(docsYml) → { mdxPaths: Set, specNames: Set } +``` + +* Recursively walks `docs.yml` navigation structure +* Collects all unique MDX file paths from `page` and `section` items +* Collects all unique API spec names from `api` items +* Uses Sets to avoid duplicates +* **No additional I/O** - just traversing the YAML structure + +### Phase 2: Batch Fetch + +```typescript +batchFetchContent(scanResult, repoConfig) → ContentCache +``` + +* Converts Sets to arrays and maps over them +* Fetches all MDX files in parallel with `Promise.all` +* Fetches all API specs in parallel with `Promise.all` +* Parses frontmatter from MDX files using `gray-matter` +* Stores everything in `ContentCache` for O(1) lookup +* **Maximum parallelization** - all I/O happens simultaneously + +### Phase 3: Process + +```typescript +buildAllOutputs(docsYml, repo, cache) + → { pathIndex, navigationTrees, algoliaRecords } +``` + +* Uses visitor pattern to walk `docs.yml` navigation structure +* `visitNavigationItem` dispatcher routes to type-specific visitors: + * `visitPage` for MDX pages + * `visitSection` for sections (recursive) + * `visitApiReference` for API specs (delegates to OpenAPI/OpenRPC processors) + * `visitLink` for external links +* For each item, looks up content in cache (O(1) lookup) +* `ProcessingContext` encapsulates three collectors: + * `PathIndexCollector` for path index entries + * `NavigationTreesCollector` for navigation tree items + * `AlgoliaCollector` for search records +* Passes `navigationAncestors` through recursion for breadcrumbs +* Returns all three outputs simultaneously + +### Write Phase + +```typescript +Promise.all([ + storeToRedis(index, navigationTrees), + uploadToAlgolia(algoliaRecords), +]); +``` + +* Writes to Redis and Algolia in parallel +* Algolia uses atomic swap (temp index → production) + +## Usage + +### Running the Indexer + +```bash +# Index main docs +pnpm generate:content-index + +# Index wallet docs +pnpm generate:wallet-content-index +``` + +### Environment Variables + +Required for Redis and Algolia upload: + +```bash +# Redis (required for path index and navigation trees) +KV_REST_API_READ_ONLY_TOKEN=your_token +KV_REST_API_TOKEN=your_token +KV_REST_API_URL=your_url +KV_URL=your_url + +# Algolia (required for search index) +ALGOLIA_APP_ID=your_app_id +ALGOLIA_ADMIN_API_KEY=your_admin_key +ALGOLIA_INDEX_NAME=alchemy_docs +ALGOLIA_WALLET_INDEX_NAME=alchemy_docs_wallets + +# GitHub (optional - increases API rate limits) +GITHUB_TOKEN=your_personal_access_token +``` + +The indexer will skip uploads for any service with missing credentials. + +## Troubleshooting + +### "No cached spec found for api-name" + +**Cause:** Spec name in docs.yml doesn't match filename in metadata.json + +**Fix:** Check `API_NAME_TO_FILENAME` mapping in `lib/utils/apiSpecs.ts` + +### "Failed to fetch MDX file" + +**Cause:** File path in docs.yml doesn't exist in GitHub repo + +**Fix:** + +1. Verify file exists in GitHub +2. Check `repoConfig.stripPathPrefix` and `repoConfig.docsPrefix` are correct +3. Ensure path in docs.yml matches actual file path + +### Slow indexing performance + +**Possible causes:** + +1. Check network connection to GitHub API +2. Verify GitHub API rate limits aren't being hit +3. Check if any individual file/spec is timing out + +### Algolia records missing breadcrumbs + +**Cause:** Navigation ancestors not being passed through correctly + +**Fix:** Verify `visitNavigationItem` and visitor functions are receiving and +forwarding `navigationAncestors` array correctly through the visitor chain diff --git a/src/content-indexer/__tests__/index.test.ts b/src/content-indexer/__tests__/index.test.ts new file mode 100644 index 000000000..008cc3968 --- /dev/null +++ b/src/content-indexer/__tests__/index.test.ts @@ -0,0 +1,171 @@ +import { afterEach, beforeEach, describe, expect, test, vi } from "vitest"; + +import { batchFetchContent } from "@/content-indexer/core/batch-fetcher"; +import { buildAllOutputs } from "@/content-indexer/core/build-all-outputs"; +import { ContentCache } from "@/content-indexer/core/content-cache"; +import { scanDocsYml } from "@/content-indexer/core/scanner"; +import type { AlgoliaRecord } from "@/content-indexer/types/algolia"; +import { fetchFileFromGitHub } from "@/content-indexer/utils/github"; +import { repoConfigFactory } from "@/content-indexer/utils/test-factories"; + +import { buildContentIndex } from "../index"; + +// Mock dependencies +vi.mock("@/content-indexer/utils/github", async () => { + const actual = await vi.importActual("@/content-indexer/utils/github"); + return { + ...actual, + fetchFileFromGitHub: vi.fn(), + }; +}); + +vi.mock("@/content-indexer/core/scanner", () => ({ + scanDocsYml: vi.fn(), +})); + +vi.mock("@/content-indexer/core/batch-fetcher", () => ({ + batchFetchContent: vi.fn(), +})); + +vi.mock("@/content-indexer/core/build-all-outputs", () => ({ + buildAllOutputs: vi.fn(), +})); + +describe("buildContentIndex", () => { + let consoleInfoSpy: ReturnType; + + beforeEach(() => { + consoleInfoSpy = vi.spyOn(console, "info").mockImplementation(() => {}); + }); + + afterEach(() => { + consoleInfoSpy.mockRestore(); + vi.clearAllMocks(); + }); + + test("should orchestrate all 3 phases successfully", async () => { + const repoConfig = repoConfigFactory({ docsPrefix: "docs" }); + const docsYmlContent = ` +navigation: + - tab: guides + layout: + - page: quickstart.mdx +`; + + // Mock Phase 0: Fetch docs.yml + vi.mocked(fetchFileFromGitHub).mockResolvedValue(docsYmlContent); + + // Mock Phase 1: Scan + const mockScanResult = { + mdxPaths: new Set(["quickstart.mdx"]), + specNames: new Set(["ethereum-api"]), + }; + vi.mocked(scanDocsYml).mockReturnValue(mockScanResult); + + // Mock Phase 2: Batch fetch + const mockCache = new ContentCache(); + vi.mocked(batchFetchContent).mockResolvedValue(mockCache); + + // Mock Phase 3: Process + const mockResult = { + pathIndex: { + "guides/quickstart": { + type: "mdx" as const, + source: "docs-yml" as const, + filePath: "quickstart.mdx", + tab: "guides", + }, + }, + navigationTrees: { + guides: [], + }, + algoliaRecords: [], + }; + vi.mocked(buildAllOutputs).mockReturnValue(mockResult); + + const result = await buildContentIndex(repoConfig); + + // Verify all phases were called + expect(fetchFileFromGitHub).toHaveBeenCalledWith( + "docs/docs.yml", + repoConfig, + ); + expect(scanDocsYml).toHaveBeenCalled(); + expect(batchFetchContent).toHaveBeenCalledWith(mockScanResult, repoConfig); + expect(buildAllOutputs).toHaveBeenCalledWith( + expect.any(Object), + mockCache, + repoConfig, + ); + + // Verify result + expect(result).toEqual(mockResult); + + // Verify console logs + expect(consoleInfoSpy).toHaveBeenCalledWith( + expect.stringContaining("Building content index"), + ); + expect(consoleInfoSpy).toHaveBeenCalledWith( + expect.stringContaining("Phase 1"), + ); + expect(consoleInfoSpy).toHaveBeenCalledWith( + expect.stringContaining("Phase 2"), + ); + expect(consoleInfoSpy).toHaveBeenCalledWith( + expect.stringContaining("Phase 3"), + ); + }); + + test("should throw error if docs.yml fetch fails", async () => { + const repoConfig = repoConfigFactory({ docsPrefix: "docs" }); + + vi.mocked(fetchFileFromGitHub).mockResolvedValue(null); + + await expect(buildContentIndex(repoConfig)).rejects.toThrow( + "Failed to fetch docs/docs.yml", + ); + }); + + test("should log statistics about generated content", async () => { + const repoConfig = repoConfigFactory({ docsPrefix: "docs" }); + + vi.mocked(fetchFileFromGitHub).mockResolvedValue("navigation: []"); + vi.mocked(scanDocsYml).mockReturnValue({ + mdxPaths: new Set(), + specNames: new Set(), + }); + vi.mocked(batchFetchContent).mockResolvedValue(new ContentCache()); + vi.mocked(buildAllOutputs).mockReturnValue({ + pathIndex: { + "guides/quickstart": { + type: "mdx", + source: "docs-yml", + filePath: "quickstart.mdx", + tab: "guides", + }, + "reference/api": { + type: "openapi", + source: "docs-yml", + operationId: "getBalance", + specUrl: "https://example.com/spec.json", + tab: "reference", + }, + }, + navigationTrees: {}, + algoliaRecords: [{ objectID: "1" }] as AlgoliaRecord[], + }); + + await buildContentIndex(repoConfig); + + // Verify statistics are logged + expect(consoleInfoSpy).toHaveBeenCalledWith( + expect.stringContaining("Generated 2 routes, 1 Algolia records"), + ); + expect(consoleInfoSpy).toHaveBeenCalledWith( + expect.stringContaining("Sources:"), + ); + expect(consoleInfoSpy).toHaveBeenCalledWith( + expect.stringContaining("Types:"), + ); + }); +}); diff --git a/src/content-indexer/collectors/__tests__/algolia.test.ts b/src/content-indexer/collectors/__tests__/algolia.test.ts new file mode 100644 index 000000000..e67e11af4 --- /dev/null +++ b/src/content-indexer/collectors/__tests__/algolia.test.ts @@ -0,0 +1,177 @@ +import { describe, expect, test } from "vitest"; + +import { AlgoliaCollector } from "../algolia"; + +describe("AlgoliaCollector", () => { + test("should initialize with empty records", () => { + const collector = new AlgoliaCollector(); + expect(collector.getRecords()).toEqual([]); + }); + + test("should add Guide record without httpMethod", () => { + const collector = new AlgoliaCollector(); + collector.addRecord({ + pageType: "Guide", + path: "guides/quickstart", + title: "Quickstart Guide", + content: "This is a quickstart guide content", + breadcrumbs: [ + { title: "Guides", path: "/guides", type: "section", children: [] }, + { + title: "Getting Started", + path: "/guides/getting-started", + type: "section", + children: [], + }, + ], + }); + + const records = collector.getRecords(); + expect(records).toHaveLength(1); + expect(records[0].pageType).toBe("Guide"); + expect(records[0].title).toBe("Quickstart Guide"); + expect(records[0].breadcrumbs).toEqual(["Guides", "Getting Started"]); + expect(records[0].httpMethod).toBeUndefined(); + }); + + test("should add API Method record with httpMethod", () => { + const collector = new AlgoliaCollector(); + collector.addRecord({ + pageType: "API Method", + path: "reference/eth-getbalance", + title: "eth_getBalance", + content: "Get the balance of an address", + httpMethod: "POST", + breadcrumbs: [ + { + title: "NFT API", + path: "/reference/nft-api", + type: "api-section", + children: [], + }, + { title: "NFT API Endpoints", type: "section", children: [] }, + ], + }); + + const records = collector.getRecords(); + expect(records).toHaveLength(1); + expect(records[0].pageType).toBe("API Method"); + expect(records[0].httpMethod).toBe("POST"); + expect(records[0].breadcrumbs).toEqual(["NFT API", "NFT API Endpoints"]); + }); + + test("should generate stable objectID from last breadcrumb + title", () => { + const collector = new AlgoliaCollector(); + collector.addRecord({ + pageType: "API Method", + path: "reference/eth-getbalance", + title: "eth_getBalance", + content: "Description", + httpMethod: "POST", + breadcrumbs: [ + { title: "NFT API", type: "section", children: [] }, + { title: "NFT API Endpoints", type: "section", children: [] }, + ], + }); + + const records = collector.getRecords(); + expect(records[0].objectID).toBeDefined(); + expect(records[0].objectID).toHaveLength(16); // SHA-256 hash first 16 chars + expect(typeof records[0].objectID).toBe("string"); + }); + + test("should filter out link breadcrumbs", () => { + const collector = new AlgoliaCollector(); + collector.addRecord({ + pageType: "Guide", + path: "guides/quickstart", + title: "Quickstart", + content: "Content", + breadcrumbs: [ + { title: "Guides", path: "/guides", type: "section", children: [] }, + { title: "External Link", href: "https://example.com", type: "link" }, + { + title: "Getting Started", + path: "/guides/getting-started", + type: "section", + children: [], + }, + ], + }); + + const records = collector.getRecords(); + expect(records[0].breadcrumbs).toEqual(["Guides", "Getting Started"]); + expect(records[0].breadcrumbs).not.toContain("External Link"); + }); + + test("should handle multiple records", () => { + const collector = new AlgoliaCollector(); + collector.addRecord({ + pageType: "Guide", + path: "guides/quickstart", + title: "Quickstart", + content: "Content 1", + breadcrumbs: [], + }); + collector.addRecord({ + pageType: "API Method", + path: "reference/method", + title: "method1", + content: "Content 2", + httpMethod: "GET", + breadcrumbs: [], + }); + + const records = collector.getRecords(); + expect(records).toHaveLength(2); + }); + + test("should handle empty breadcrumbs", () => { + const collector = new AlgoliaCollector(); + collector.addRecord({ + pageType: "Guide", + path: "guides/quickstart", + title: "Quickstart", + content: "Content", + breadcrumbs: [], + }); + + const records = collector.getRecords(); + expect(records[0].breadcrumbs).toEqual([]); + // ObjectID should still be generated (using "unknown" + title) + expect(records[0].objectID).toBeDefined(); + }); + + test("should generate consistent objectID for same last breadcrumb + title", () => { + const collector1 = new AlgoliaCollector(); + collector1.addRecord({ + pageType: "API Method", + path: "reference/path1", + title: "eth_getBalance", + content: "Content 1", + httpMethod: "POST", + breadcrumbs: [ + { title: "API", type: "section", children: [] }, + { title: "Ethereum Endpoints", type: "section", children: [] }, + ], + }); + + const collector2 = new AlgoliaCollector(); + collector2.addRecord({ + pageType: "API Method", + path: "reference/different-path", + title: "eth_getBalance", + content: "Content 2", + httpMethod: "GET", + breadcrumbs: [ + { title: "API", type: "section", children: [] }, + { title: "Ethereum Endpoints", type: "section", children: [] }, + ], + }); + + const records1 = collector1.getRecords(); + const records2 = collector2.getRecords(); + // Same last breadcrumb + title should generate same objectID + expect(records1[0].objectID).toBe(records2[0].objectID); + }); +}); diff --git a/src/content-indexer/collectors/__tests__/navigation-trees.test.ts b/src/content-indexer/collectors/__tests__/navigation-trees.test.ts new file mode 100644 index 000000000..005b0c87c --- /dev/null +++ b/src/content-indexer/collectors/__tests__/navigation-trees.test.ts @@ -0,0 +1,136 @@ +import { describe, expect, test } from "vitest"; + +import { NavigationTreesCollector } from "../navigation-trees"; + +describe("NavigationTreesCollector", () => { + test("should initialize with empty trees", () => { + const collector = new NavigationTreesCollector(); + expect(collector.getTrees()).toEqual({}); + }); + + test("should add item to new tab", () => { + const collector = new NavigationTreesCollector(); + collector.addItem("guides", { + title: "Quickstart", + path: "/guides/quickstart", + type: "page", + }); + + const trees = collector.getTrees(); + expect(trees.guides).toHaveLength(1); + expect(trees.guides[0]).toEqual({ + title: "Quickstart", + path: "/guides/quickstart", + type: "page", + }); + }); + + test("should add multiple items to same tab", () => { + const collector = new NavigationTreesCollector(); + collector.addItem("guides", { + title: "Quickstart", + path: "/guides/quickstart", + type: "page", + }); + collector.addItem("guides", { + title: "Advanced", + path: "/guides/advanced", + type: "page", + }); + + const trees = collector.getTrees(); + expect(trees.guides).toHaveLength(2); + }); + + test("should handle items with children", () => { + const collector = new NavigationTreesCollector(); + collector.addItem("reference", { + title: "NFT API", + type: "section", + children: [ + { + title: "getNFTs", + path: "/reference/getnfts", + method: "POST", + type: "endpoint", + }, + ], + }); + + const trees = collector.getTrees(); + const firstItem = trees.reference[0]; + expect(firstItem.type).toBe("section"); + if (firstItem.type === "section" || firstItem.type === "api-section") { + expect(firstItem.children).toHaveLength(1); + } + }); + + test("should handle multiple tabs", () => { + const collector = new NavigationTreesCollector(); + collector.addItem("guides", { + title: "Quickstart", + path: "/guides/quickstart", + type: "page", + }); + collector.addItem("reference", { + title: "API Reference", + path: "/reference", + type: "page", + }); + + const trees = collector.getTrees(); + expect(Object.keys(trees)).toHaveLength(2); + expect(trees.guides).toBeDefined(); + expect(trees.reference).toBeDefined(); + }); + + test("should return correct stats", () => { + const collector = new NavigationTreesCollector(); + collector.addItem("guides", { + title: "Page1", + path: "/guides/page1", + type: "page", + }); + collector.addItem("guides", { + title: "Page2", + path: "/guides/page2", + type: "page", + }); + collector.addItem("reference", { + title: "Page3", + path: "/reference/page3", + type: "page", + }); + + const stats = collector.getStats(); + expect(stats.tabCount).toBe(2); + expect(stats.itemCounts.guides).toBe(2); + expect(stats.itemCounts.reference).toBe(1); + }); + + test("should count nested items in stats", () => { + const collector = new NavigationTreesCollector(); + collector.addItem("reference", { + title: "API Section", + type: "section", + children: [ + { + title: "Method1", + path: "/reference/method1", + method: "POST", + type: "endpoint", + }, + { + title: "Method2", + path: "/reference/method2", + method: "GET", + type: "endpoint", + }, + ], + }); + + const stats = collector.getStats(); + expect(stats.tabCount).toBe(1); + expect(stats.itemCounts.reference).toBe(3); // 1 section + 2 children + }); +}); diff --git a/src/content-indexer/collectors/__tests__/path-index.test.ts b/src/content-indexer/collectors/__tests__/path-index.test.ts new file mode 100644 index 000000000..8ba8d55d2 --- /dev/null +++ b/src/content-indexer/collectors/__tests__/path-index.test.ts @@ -0,0 +1,144 @@ +import { describe, expect, test } from "vitest"; + +import { PathIndexCollector } from "../path-index"; + +describe("PathIndexCollector", () => { + test("should initialize with empty index", () => { + const collector = new PathIndexCollector(); + expect(collector.getIndex()).toEqual({}); + }); + + test("should add MDX entry to index", () => { + const collector = new PathIndexCollector(); + collector.add("guides/quickstart", { + type: "mdx", + filePath: "fern/guides/quickstart.mdx", + source: "docs-yml", + tab: "guides", + }); + + const index = collector.getIndex(); + expect(index["guides/quickstart"]).toEqual({ + type: "mdx", + filePath: "fern/guides/quickstart.mdx", + source: "docs-yml", + tab: "guides", + }); + }); + + test("should add OpenAPI entry to index", () => { + const collector = new PathIndexCollector(); + collector.add("reference/eth-getbalance", { + type: "openapi", + specUrl: "https://example.com/spec.json", + operationId: "eth_getBalance", + source: "docs-yml", + tab: "reference", + }); + + const index = collector.getIndex(); + expect(index["reference/eth-getbalance"]).toEqual({ + type: "openapi", + specUrl: "https://example.com/spec.json", + operationId: "eth_getBalance", + source: "docs-yml", + tab: "reference", + }); + }); + + test("should add OpenRPC entry to index", () => { + const collector = new PathIndexCollector(); + collector.add("reference/getAsset", { + type: "openrpc", + specUrl: "https://example.com/spec.json", + methodName: "getAsset", + source: "docs-yml", + tab: "reference", + }); + + const index = collector.getIndex(); + expect(index["reference/getAsset"]).toEqual({ + type: "openrpc", + specUrl: "https://example.com/spec.json", + methodName: "getAsset", + source: "docs-yml", + tab: "reference", + }); + }); + + test("should handle multiple entries", () => { + const collector = new PathIndexCollector(); + collector.add("guides/quickstart", { + type: "mdx", + filePath: "fern/guides/quickstart.mdx", + source: "docs-yml", + tab: "guides", + }); + collector.add("guides/advanced", { + type: "mdx", + filePath: "fern/guides/advanced.mdx", + source: "frontmatter", + tab: "guides", + }); + + const index = collector.getIndex(); + expect(Object.keys(index)).toHaveLength(2); + expect(index["guides/quickstart"]).toBeDefined(); + expect(index["guides/advanced"]).toBeDefined(); + }); + + test("should overwrite existing path", () => { + const collector = new PathIndexCollector(); + collector.add("guides/quickstart", { + type: "mdx", + filePath: "fern/guides/quickstart.mdx", + source: "docs-yml", + tab: "guides", + }); + collector.add("guides/quickstart", { + type: "mdx", + filePath: "fern/guides/quickstart-v2.mdx", + source: "frontmatter", + tab: "guides", + }); + + const index = collector.getIndex(); + const entry = index["guides/quickstart"]; + if (entry && "filePath" in entry) { + expect(entry.filePath).toBe("fern/guides/quickstart-v2.mdx"); + if ("source" in entry) { + expect(entry.source).toBe("frontmatter"); + } + } + }); + + test("should return correct stats", () => { + const collector = new PathIndexCollector(); + collector.add("path1", { + type: "mdx", + filePath: "file1.mdx", + source: "docs-yml", + tab: "tab1", + }); + collector.add("path2", { + type: "openapi", + specUrl: "spec.json", + operationId: "op1", + source: "docs-yml", + tab: "tab1", + }); + collector.add("path3", { + type: "openrpc", + specUrl: "spec.json", + methodName: "method1", + source: "docs-yml", + tab: "tab1", + }); + + const stats = collector.getStats(); + expect(stats.total).toBe(3); + expect(stats.byType.mdx).toBe(1); + expect(stats.byType.openapi).toBe(1); + expect(stats.byType.openrpc).toBe(1); + }); +}); diff --git a/src/content-indexer/collectors/__tests__/processing-context.test.ts b/src/content-indexer/collectors/__tests__/processing-context.test.ts new file mode 100644 index 000000000..99b0d0cc3 --- /dev/null +++ b/src/content-indexer/collectors/__tests__/processing-context.test.ts @@ -0,0 +1,165 @@ +import { describe, expect, test } from "vitest"; + +import type { NavItem } from "@/content-indexer/types/navigation"; + +import { ProcessingContext } from "../processing-context"; + +describe("ProcessingContext", () => { + test("should initialize with empty state", () => { + const context = new ProcessingContext(); + const results = context.getResults(); + + expect(results.pathIndex).toEqual({}); + expect(results.navigationTrees).toEqual({}); + expect(results.algoliaRecords).toEqual([]); + }); + + test("should add path index entry", () => { + const context = new ProcessingContext(); + context.addPathIndexEntry("guides/quickstart", { + type: "mdx", + filePath: "fern/guides/quickstart.mdx", + source: "docs-yml", + tab: "guides", + }); + + const results = context.getResults(); + expect(results.pathIndex["guides/quickstart"]).toBeDefined(); + expect(results.pathIndex["guides/quickstart"].type).toBe("mdx"); + }); + + test("should add navigation item", () => { + const context = new ProcessingContext(); + context.addNavigationItem("guides", { + title: "Quickstart", + path: "/guides/quickstart", + type: "page", + }); + + const results = context.getResults(); + expect(results.navigationTrees.guides).toHaveLength(1); + expect(results.navigationTrees.guides[0].title).toBe("Quickstart"); + }); + + test("should add Guide Algolia record", () => { + const context = new ProcessingContext(); + const breadcrumbs: NavItem[] = [ + { title: "Guides", path: "/guides", type: "section", children: [] }, + ]; + + context.addAlgoliaRecord({ + pageType: "Guide", + path: "guides/quickstart", + title: "Quickstart", + content: "Quick start guide content", + breadcrumbs, + }); + + const results = context.getResults(); + expect(results.algoliaRecords).toHaveLength(1); + expect(results.algoliaRecords[0].pageType).toBe("Guide"); + expect(results.algoliaRecords[0].httpMethod).toBeUndefined(); + }); + + test("should add API Method Algolia record with httpMethod", () => { + const context = new ProcessingContext(); + const breadcrumbs: NavItem[] = [ + { title: "API", path: "/api", type: "section", children: [] }, + ]; + + context.addAlgoliaRecord({ + pageType: "API Method", + path: "reference/eth-getbalance", + title: "eth_getBalance", + content: "Get balance", + httpMethod: "POST", + breadcrumbs, + }); + + const results = context.getResults(); + expect(results.algoliaRecords).toHaveLength(1); + expect(results.algoliaRecords[0].pageType).toBe("API Method"); + expect(results.algoliaRecords[0].httpMethod).toBe("POST"); + }); + + test("should accumulate multiple outputs simultaneously", () => { + const context = new ProcessingContext(); + + // Add path index + context.addPathIndexEntry("guides/quickstart", { + type: "mdx", + filePath: "fern/guides/quickstart.mdx", + source: "docs-yml", + tab: "guides", + }); + + // Add navigation + context.addNavigationItem("guides", { + title: "Quickstart", + path: "/guides/quickstart", + type: "page", + }); + + // Add Algolia record + context.addAlgoliaRecord({ + pageType: "Guide", + path: "guides/quickstart", + title: "Quickstart", + content: "Content", + breadcrumbs: [], + }); + + const results = context.getResults(); + expect(Object.keys(results.pathIndex)).toHaveLength(1); + expect(results.navigationTrees.guides).toHaveLength(1); + expect(results.algoliaRecords).toHaveLength(1); + }); + + test("should return correct stats", () => { + const context = new ProcessingContext(); + + context.addPathIndexEntry("path1", { + type: "mdx", + filePath: "file1.mdx", + source: "docs-yml", + tab: "guides", + }); + context.addNavigationItem("guides", { + title: "Page1", + path: "/guides/page1", + type: "page", + }); + context.addAlgoliaRecord({ + pageType: "Guide", + path: "guides/page1", + title: "Page1", + content: "Content", + breadcrumbs: [], + }); + + const stats = context.getStats(); + expect(stats.pathIndex.total).toBe(1); + expect(stats.navigationTrees.tabCount).toBe(1); + expect(stats.algoliaRecords.count).toBe(1); + }); + + test("should handle multiple tabs in navigation", () => { + const context = new ProcessingContext(); + + context.addNavigationItem("guides", { + title: "Guide1", + path: "/guides/guide1", + type: "page", + }); + context.addNavigationItem("reference", { + title: "Ref1", + path: "/reference/ref1", + type: "page", + }); + + const results = context.getResults(); + expect(Object.keys(results.navigationTrees)).toHaveLength(2); + expect(results.navigationTrees.guides).toBeDefined(); + expect(results.navigationTrees.reference).toBeDefined(); + }); +}); diff --git a/src/content-indexer/collectors/algolia.ts b/src/content-indexer/collectors/algolia.ts new file mode 100644 index 000000000..82f658c40 --- /dev/null +++ b/src/content-indexer/collectors/algolia.ts @@ -0,0 +1,82 @@ +import { createHash } from "crypto"; + +import type { AlgoliaRecord } from "@/content-indexer/types/algolia"; +import type { NavItem } from "@/content-indexer/types/navigation"; + +/** + * Extracts breadcrumb titles from NavItems for Algolia. + * Returns only the titles in hierarchical order. + */ +const extractBreadcrumbTitles = (navItems: NavItem[]): string[] => { + return navItems + .filter((item) => item.type !== "link") // Skip links + .map((item) => item.title); +}; + +type AddRecordBaseParams = { + path: string; + title: string; + content: string; + breadcrumbs: NavItem[]; +}; + +type AddGuideRecordParams = AddRecordBaseParams & { + pageType: "Guide"; + httpMethod?: never; // Not allowed for Guide +}; + +type AddApiMethodRecordParams = AddRecordBaseParams & { + pageType: "API Method"; + httpMethod: string; // Required for API Method +}; +export type AddRecordParams = AddGuideRecordParams | AddApiMethodRecordParams; + +/** + * Collector for Algolia search records during content processing. + * Records are built with all required data including breadcrumbs. + */ +export class AlgoliaCollector { + private records: AlgoliaRecord[] = []; + + /** + * Add a search record for either MDX pages or API methods. + * + * ObjectID strategy: + * Uses hash of last breadcrumb + title for relatively stable, content-based identification. + * If we change the title or the last breadcrumb, the objectID will change, + * but this shouldn't matter as long as we continue to replace the entire index on each run. + */ + addRecord(params: AddRecordParams): void { + const breadcrumbTitles = extractBreadcrumbTitles(params.breadcrumbs); + + // Generate stable objectID from last breadcrumb (most specific section) + title + const lastBreadcrumb = breadcrumbTitles.at(-1) || "unknown"; + const stableId = `${lastBreadcrumb}:${params.title}`; + const objectID = this.generateHash(stableId); + + this.records.push({ + objectID, + path: params.path, + pageType: params.pageType, + title: params.title, + content: params.content, + breadcrumbs: breadcrumbTitles, + ...(params.httpMethod && { httpMethod: params.httpMethod }), + }); + } + + /** + * Get all built records. + */ + getRecords(): AlgoliaRecord[] { + return this.records; + } + + /** + * Generate a stable hash-based objectID from a source string. + * Returns first 16 characters of SHA-256 hash for a clean ID format. + */ + private generateHash(source: string): string { + return createHash("sha256").update(source).digest("hex").substring(0, 16); + } +} diff --git a/src/content-indexer/collectors/navigation-trees.ts b/src/content-indexer/collectors/navigation-trees.ts new file mode 100644 index 000000000..1a962345f --- /dev/null +++ b/src/content-indexer/collectors/navigation-trees.ts @@ -0,0 +1,58 @@ +import type { + NavItem, + NavigationTreesByTab, +} from "@/content-indexer/types/navigation"; + +/** + * Collector for accumulating navigation trees during content processing. + * Organizes navigation items by tab. + */ +export class NavigationTreesCollector { + private trees: NavigationTreesByTab = {}; + + /** + * Add a navigation item to a specific tab's tree. + */ + addItem(tab: string, item: NavItem): void { + if (!this.trees[tab]) { + this.trees[tab] = []; + } + this.trees[tab].push(item); + } + + /** + * Get the complete navigation trees. + */ + getTrees(): NavigationTreesByTab { + return this.trees; + } + + /** + * Get statistics about the navigation trees. + */ + getStats(): { tabCount: number; itemCounts: Record } { + return { + tabCount: Object.keys(this.trees).length, + itemCounts: Object.entries(this.trees).reduce( + (acc, [tab, items]) => { + acc[tab] = this.countItems(items); + return acc; + }, + {} as Record, + ), + }; + } + + /** + * Recursively count navigation items including nested children. + */ + private countItems(items: NavItem[]): number { + return items.reduce((sum, item) => { + const childCount = + item.type === "section" || item.type === "api-section" + ? this.countItems(item.children) + : 0; + return sum + 1 + childCount; + }, 0); + } +} diff --git a/src/content-indexer/collectors/path-index.ts b/src/content-indexer/collectors/path-index.ts new file mode 100644 index 000000000..65f2ea35f --- /dev/null +++ b/src/content-indexer/collectors/path-index.ts @@ -0,0 +1,49 @@ +import type { + PathIndex, + PathIndexEntry, +} from "@/content-indexer/types/pathIndex"; + +/** + * Collector for accumulating path index entries during content processing. + * Provides validation to prevent duplicate paths. + */ +export class PathIndexCollector { + private index: PathIndex = {}; + + /** + * Add a path index entry for URL routing. + * Warns if the path already exists to catch configuration errors. + */ + add(path: string, entry: PathIndexEntry): void { + if (this.index[path]) { + console.warn( + `⚠️ Duplicate path detected: ${path} (overwriting previous entry)`, + ); + } + this.index[path] = entry; + } + + /** + * Get the complete path index. + */ + getIndex(): PathIndex { + return this.index; + } + + /** + * Get statistics about the index. + */ + getStats(): { total: number; byType: Record } { + const entries = Object.values(this.index); + return { + total: entries.length, + byType: entries.reduce( + (acc, entry) => { + acc[entry.type] = (acc[entry.type] || 0) + 1; + return acc; + }, + {} as Record, + ), + }; + } +} diff --git a/src/content-indexer/collectors/processing-context.ts b/src/content-indexer/collectors/processing-context.ts new file mode 100644 index 000000000..48abe6310 --- /dev/null +++ b/src/content-indexer/collectors/processing-context.ts @@ -0,0 +1,77 @@ +import type { AlgoliaRecord } from "@/content-indexer/types/algolia"; +import type { + NavItem, + NavigationTreesByTab, +} from "@/content-indexer/types/navigation"; +import type { + PathIndex, + PathIndexEntry, +} from "@/content-indexer/types/pathIndex"; + +import { AlgoliaCollector, type AddRecordParams } from "./algolia"; +import { NavigationTreesCollector } from "./navigation-trees"; +import { PathIndexCollector } from "./path-index"; + +/** + * Result of building all outputs from content processing. + */ +export interface BuildAllOutputsResult { + pathIndex: PathIndex; + navigationTrees: NavigationTreesByTab; + algoliaRecords: AlgoliaRecord[]; +} + +/** + * Encapsulates the three output collectors for Phase 3 processing. + * Provides a unified interface for accumulating results while traversing docs.yml. + */ +export class ProcessingContext { + constructor( + private pathIndexCollector = new PathIndexCollector(), + private navigationTreesCollector = new NavigationTreesCollector(), + private algoliaCollector = new AlgoliaCollector(), + ) {} + + /** + * Add an entry to the path index for URL routing. + */ + addPathIndexEntry(path: string, entry: PathIndexEntry): void { + this.pathIndexCollector.add(path, entry); + } + + /** + * Add a navigation item to a specific tab's tree. + */ + addNavigationItem(tab: string, item: NavItem): void { + this.navigationTreesCollector.addItem(tab, item); + } + + /** + * Add a record to the Algolia index. + */ + addAlgoliaRecord(params: AddRecordParams): void { + this.algoliaCollector.addRecord(params); + } + + /** + * Get all accumulated results. + */ + getResults(): BuildAllOutputsResult { + return { + pathIndex: this.pathIndexCollector.getIndex(), + navigationTrees: this.navigationTreesCollector.getTrees(), + algoliaRecords: this.algoliaCollector.getRecords(), + }; + } + + /** + * Get statistics about accumulated data. + */ + getStats() { + return { + pathIndex: this.pathIndexCollector.getStats(), + navigationTrees: this.navigationTreesCollector.getStats(), + algoliaRecords: { count: this.algoliaCollector.getRecords().length }, + }; + } +} diff --git a/src/content-indexer/constants/changelog.ts b/src/content-indexer/constants/changelog.ts new file mode 100644 index 000000000..4c76ec4ca --- /dev/null +++ b/src/content-indexer/constants/changelog.ts @@ -0,0 +1,10 @@ +/** + * Number of changelog entries to show per page + */ +export const ENTRIES_PER_PAGE = 10; + +/** + * Sticky position for date badges + * Calculated as: header height (~80px) + badge height (~40px) + */ +export const DATE_BADGE_STICKY_TOP = 120; diff --git a/src/content-indexer/constants/http.ts b/src/content-indexer/constants/http.ts new file mode 100644 index 000000000..625173bd0 --- /dev/null +++ b/src/content-indexer/constants/http.ts @@ -0,0 +1,12 @@ +export const HTTP_METHODS = [ + "get", + "post", + "put", + "patch", + "delete", + "options", + "head", + "trace", +] as const; + +export type HttpMethod = Uppercase<(typeof HTTP_METHODS)[number]>; diff --git a/src/content-indexer/constants/links.ts b/src/content-indexer/constants/links.ts new file mode 100644 index 000000000..979e0751e --- /dev/null +++ b/src/content-indexer/constants/links.ts @@ -0,0 +1,2 @@ +export const BASE_PATH = "/docs"; +export const DOCS_BASE_URL = "https://www.alchemy.com/docs"; diff --git a/src/content-indexer/constants/metadata.ts b/src/content-indexer/constants/metadata.ts new file mode 100644 index 000000000..d24ea6709 --- /dev/null +++ b/src/content-indexer/constants/metadata.ts @@ -0,0 +1,56 @@ +import { DOCS_BASE_URL } from "./links"; + +// TODO: This file is copied from docs-site but not used in the content indexer +// It's kept for potential future use but Next.js types are stubbed +type Metadata = { + title?: { template?: string; default?: string } | string; + description?: string; + robots?: { index?: boolean; follow?: boolean }; + alternates?: { canonical?: string }; + openGraph?: Record; + twitter?: Record; +}; + +export const DEFAULT_OG_IMAGE = { + url: "https://alchemyapi-res.cloudinary.com/image/upload/v1753213834/docs/docs-og-image.png", + width: 1200, + height: 630, +}; + +export const DEFAULT_OPEN_GRAPH: Metadata["openGraph"] = { + title: "Alchemy Documentation - Build anything onchain", + description: + "Learn how to use Node APIs, Data APIs, Webhooks, Smart Wallets and Rollups to create powerful onchain experiences.", + siteName: "Alchemy Documentation", + url: DOCS_BASE_URL, + locale: "en_US", + type: "website", + images: [DEFAULT_OG_IMAGE], +}; + +const DEFAULT_TWITTER: Metadata["twitter"] = { + card: "summary_large_image", + title: "Alchemy Documentation - Build anything onchain", + description: + "Learn how to use Node APIs, Data APIs, Webhooks, Smart Wallets and Rollups to create powerful onchain experiences.", + site: "@alchemy", + images: [DEFAULT_OG_IMAGE.url], +}; + +export const DEFAULT_METADATA: Metadata = { + title: { + template: "%s | Alchemy Docs", + default: "Alchemy Documentation - Build anything onchain", + }, + description: + "Learn how to use Node APIs, Data APIs, Webhooks, Smart Wallets and Rollups to create powerful onchain experiences.", + robots: { + index: true, + follow: true, + }, + alternates: { + canonical: DOCS_BASE_URL, + }, + openGraph: DEFAULT_OPEN_GRAPH, + twitter: DEFAULT_TWITTER, +}; diff --git a/src/content-indexer/core/__tests__/batch-fetcher.test.ts b/src/content-indexer/core/__tests__/batch-fetcher.test.ts new file mode 100644 index 000000000..8c8346291 --- /dev/null +++ b/src/content-indexer/core/__tests__/batch-fetcher.test.ts @@ -0,0 +1,203 @@ +import { afterEach, beforeEach, describe, expect, test, vi } from "vitest"; + +import type { OpenApiSpec } from "@/content-indexer/types/specs"; +import { fetchApiSpec } from "@/content-indexer/utils/apiSpecs"; +import { fetchFileFromGitHub } from "@/content-indexer/utils/github"; +import { + openApiSpecFactory, + repoConfigFactory, +} from "@/content-indexer/utils/test-factories"; + +import { batchFetchContent } from "../batch-fetcher"; + +// Mock dependencies +vi.mock("@/content-indexer/utils/github", async () => { + const actual = await vi.importActual("@/content-indexer/utils/github"); + return { + ...actual, + fetchFileFromGitHub: vi.fn(), + }; +}); + +vi.mock("@/content-indexer/utils/apiSpecs", () => ({ + fetchApiSpec: vi.fn(), +})); + +describe("batchFetchContent", () => { + let consoleInfoSpy: ReturnType; + let consoleWarnSpy: ReturnType; + + beforeEach(() => { + consoleInfoSpy = vi.spyOn(console, "info").mockImplementation(() => {}); + consoleWarnSpy = vi.spyOn(console, "warn").mockImplementation(() => {}); + }); + + afterEach(() => { + consoleInfoSpy.mockRestore(); + consoleWarnSpy.mockRestore(); + vi.clearAllMocks(); + }); + + test("should fetch MDX files and populate cache", async () => { + const repoConfig = repoConfigFactory({ docsPrefix: "docs" }); + const scanResult = { + mdxPaths: new Set(["quickstart.mdx", "guides/intro.mdx"]), + specNames: new Set(), + }; + + const mdxContent = `--- +title: Test Page +--- +# Content`; + + vi.mocked(fetchFileFromGitHub).mockResolvedValue(mdxContent); + + const cache = await batchFetchContent(scanResult, repoConfig); + + // Verify fetches were made + expect(fetchFileFromGitHub).toHaveBeenCalledTimes(2); + expect(fetchFileFromGitHub).toHaveBeenCalledWith( + "docs/quickstart.mdx", + repoConfig, + ); + expect(fetchFileFromGitHub).toHaveBeenCalledWith( + "docs/guides/intro.mdx", + repoConfig, + ); + + // Verify cache was populated + const stats = cache.getStats(); + expect(stats.mdxCount).toBe(2); + expect(stats.specCount).toBe(0); + + // Verify cache entries + const entry = cache.getMdxContent("quickstart.mdx"); + expect(entry).toBeDefined(); + expect(entry?.frontmatter.title).toBe("Test Page"); + expect(entry?.content).toContain("# Content"); + }); + + test("should fetch API specs and populate cache", async () => { + const repoConfig = repoConfigFactory({ docsPrefix: "docs" }); + const scanResult = { + mdxPaths: new Set(), + specNames: new Set(["ethereum-api", "solana-api"]), + }; + + const mockSpec = { + specType: "openapi" as const, + spec: openApiSpecFactory({ + openapi: "3.0.0", + info: { title: "Test", version: "1.0" }, + }), + specUrl: "https://example.com/spec.json", + }; + + vi.mocked(fetchApiSpec).mockResolvedValue(mockSpec); + + const cache = await batchFetchContent(scanResult, repoConfig); + + // Verify fetches were made + expect(fetchApiSpec).toHaveBeenCalledTimes(2); + expect(fetchApiSpec).toHaveBeenCalledWith("ethereum-api"); + expect(fetchApiSpec).toHaveBeenCalledWith("solana-api"); + + // Verify cache was populated + const stats = cache.getStats(); + expect(stats.mdxCount).toBe(0); + expect(stats.specCount).toBe(2); + }); + + test("should handle stripPathPrefix configuration", async () => { + const repoConfig = repoConfigFactory({ + docsPrefix: "docs", + stripPathPrefix: "fern/", + }); + const scanResult = { + mdxPaths: new Set(["fern/guides/intro.mdx"]), + specNames: new Set(), + }; + + vi.mocked(fetchFileFromGitHub).mockResolvedValue("---\n---\nContent"); + + await batchFetchContent(scanResult, repoConfig); + + // Verify path was transformed (strip "fern/" prefix) + expect(fetchFileFromGitHub).toHaveBeenCalledWith( + "docs/guides/intro.mdx", + repoConfig, + ); + }); + + test("should handle fetch failures gracefully", async () => { + const repoConfig = repoConfigFactory({ docsPrefix: "docs" }); + const scanResult = { + mdxPaths: new Set(["missing.mdx"]), + specNames: new Set(["missing-api"]), + }; + + vi.mocked(fetchFileFromGitHub).mockRejectedValue( + new Error("File not found"), + ); + vi.mocked(fetchApiSpec).mockRejectedValue(new Error("Spec not found")); + + const cache = await batchFetchContent(scanResult, repoConfig); + + // Verify warnings were logged + expect(consoleWarnSpy).toHaveBeenCalledWith( + expect.stringContaining("Failed to fetch MDX file"), + expect.any(Error), + ); + expect(consoleWarnSpy).toHaveBeenCalledWith( + expect.stringContaining("Failed to fetch spec"), + expect.any(Error), + ); + + // Verify cache is empty + const stats = cache.getStats(); + expect(stats.mdxCount).toBe(0); + expect(stats.specCount).toBe(0); + }); + + test("should handle null responses from fetchers", async () => { + const repoConfig = repoConfigFactory({ docsPrefix: "docs" }); + const scanResult = { + mdxPaths: new Set(["missing.mdx"]), + specNames: new Set(["missing-api"]), + }; + + vi.mocked(fetchFileFromGitHub).mockResolvedValue(null); + vi.mocked(fetchApiSpec).mockResolvedValue(undefined); + + const cache = await batchFetchContent(scanResult, repoConfig); + + // Cache should be empty + const stats = cache.getStats(); + expect(stats.mdxCount).toBe(0); + expect(stats.specCount).toBe(0); + }); + + test("should log progress information", async () => { + const repoConfig = repoConfigFactory({ docsPrefix: "docs" }); + const scanResult = { + mdxPaths: new Set(["test.mdx"]), + specNames: new Set(["test-api"]), + }; + + vi.mocked(fetchFileFromGitHub).mockResolvedValue("---\n---\nContent"); + vi.mocked(fetchApiSpec).mockResolvedValue({ + specType: "openapi", + spec: {} as OpenApiSpec, + specUrl: "url", + }); + + await batchFetchContent(scanResult, repoConfig); + + expect(consoleInfoSpy).toHaveBeenCalledWith( + expect.stringContaining("Fetching 1 MDX files and 1 specs"), + ); + expect(consoleInfoSpy).toHaveBeenCalledWith( + expect.stringContaining("Fetched 1/1 MDX files and 1/1 specs"), + ); + }); +}); diff --git a/src/content-indexer/core/__tests__/build-all-outputs.test.ts b/src/content-indexer/core/__tests__/build-all-outputs.test.ts new file mode 100644 index 000000000..0e4337a61 --- /dev/null +++ b/src/content-indexer/core/__tests__/build-all-outputs.test.ts @@ -0,0 +1,273 @@ +import { beforeEach, describe, expect, test, vi } from "vitest"; + +import type { DocsYml } from "@/content-indexer/types/docsYaml"; +import { repoConfigFactory } from "@/content-indexer/utils/test-factories"; +import { visitNavigationItem } from "@/content-indexer/visitors"; + +import { buildAllOutputs } from "../build-all-outputs"; +import { ContentCache } from "../content-cache"; + +// Mock the visitor +vi.mock("@/content-indexer/visitors", () => ({ + visitNavigationItem: vi.fn(), +})); + +describe("buildAllOutputs", () => { + beforeEach(() => { + vi.clearAllMocks(); + }); + + test("should process navigation items and return results", () => { + const docsYml: DocsYml = { + navigation: [ + { + tab: "guides", + layout: [ + { + page: "quickstart.mdx", + path: "quickstart.mdx", + }, + ], + }, + ], + }; + + const cache = new ContentCache(); + const repoConfig = repoConfigFactory(); + + vi.mocked(visitNavigationItem).mockReturnValue({ + indexEntries: { + "guides/quickstart": { + type: "mdx", + source: "docs-yml", + filePath: "quickstart.mdx", + tab: "guides", + }, + }, + navItem: { + type: "page", + title: "Quickstart", + path: "/guides/quickstart", + }, + }); + + const result = buildAllOutputs(docsYml, cache, repoConfig); + + // Verify visitor was called + expect(visitNavigationItem).toHaveBeenCalled(); + + // Verify results + expect(result.pathIndex).toHaveProperty("guides/quickstart"); + expect(result.navigationTrees).toHaveProperty("guides"); + expect(result.navigationTrees.guides).toHaveLength(1); + }); + + test("should handle multiple tabs", () => { + const docsYml: DocsYml = { + navigation: [ + { + tab: "guides", + layout: [{ page: "intro.mdx", path: "intro.mdx" }], + }, + { + tab: "reference", + layout: [{ api: "API", "api-name": "ethereum-api" }], + }, + ], + }; + + const cache = new ContentCache(); + + vi.mocked(visitNavigationItem) + .mockReturnValueOnce({ + indexEntries: { + "guides/intro": { + type: "mdx", + source: "docs-yml", + filePath: "intro.mdx", + tab: "guides", + }, + }, + navItem: { type: "page", title: "Intro", path: "/guides/intro" }, + }) + .mockReturnValueOnce({ + indexEntries: { + "reference/api": { + type: "openapi", + source: "docs-yml", + operationId: "getBalance", + specUrl: "url", + tab: "reference", + }, + }, + navItem: { + type: "api-section", + title: "API", + children: [], + }, + }); + + const result = buildAllOutputs(docsYml, cache); + + // Verify both tabs were processed + expect(visitNavigationItem).toHaveBeenCalledTimes(2); + expect(result.navigationTrees).toHaveProperty("guides"); + expect(result.navigationTrees).toHaveProperty("reference"); + }); + + test("should skip items without tab or layout", () => { + const docsYml = { + navigation: [ + { + // Missing tab and layout + }, + { + tab: "guides", + // Missing layout + }, + { + tab: "valid", + layout: [{ page: "test.mdx", path: "test.mdx" }], + }, + ], + } as DocsYml; + + const cache = new ContentCache(); + + vi.mocked(visitNavigationItem).mockReturnValue({ + indexEntries: {}, + navItem: { type: "page", title: "Test", path: "/test" }, + }); + + buildAllOutputs(docsYml, cache); + + // Only the valid item should be processed + expect(visitNavigationItem).toHaveBeenCalledTimes(1); + }); + + test("should apply tab configuration", () => { + const docsYml: DocsYml = { + navigation: [ + { + tab: "api-reference", + layout: [{ page: "intro.mdx", path: "intro.mdx" }], + }, + ], + tabs: { + "api-reference": { + "display-name": "API Reference", + slug: "reference", + "skip-slug": false, + }, + }, + }; + + const cache = new ContentCache(); + + vi.mocked(visitNavigationItem).mockReturnValue({ + indexEntries: {}, + }); + + buildAllOutputs(docsYml, cache); + + // Verify visitor was called with correct path builder + expect(visitNavigationItem).toHaveBeenCalledWith( + expect.objectContaining({ + tab: "api-reference", + }), + ); + }); + + test("should handle skip-slug configuration", () => { + const docsYml: DocsYml = { + navigation: [ + { + tab: "home", + layout: [{ page: "index.mdx", path: "index.mdx" }], + }, + ], + tabs: { + home: { + "display-name": "Home", + slug: "home", + "skip-slug": true, + }, + }, + }; + + const cache = new ContentCache(); + + vi.mocked(visitNavigationItem).mockReturnValue({ + indexEntries: {}, + }); + + buildAllOutputs(docsYml, cache); + + expect(visitNavigationItem).toHaveBeenCalled(); + }); + + test("should handle array navigation items", () => { + const docsYml: DocsYml = { + navigation: [ + { + tab: "guides", + layout: [{ page: "test.mdx", path: "test.mdx" }], + }, + ], + }; + + const cache = new ContentCache(); + + // Return array of nav items + vi.mocked(visitNavigationItem).mockReturnValue({ + indexEntries: {}, + navItem: [ + { type: "page", title: "Page 1", path: "/page1" }, + { type: "page", title: "Page 2", path: "/page2" }, + ], + }); + + const result = buildAllOutputs(docsYml, cache); + + // Both items should be added + expect(result.navigationTrees.guides).toHaveLength(2); + }); + + test("should collect Algolia records from context", () => { + const docsYml: DocsYml = { + navigation: [ + { + tab: "guides", + layout: [{ page: "test.mdx", path: "test.mdx" }], + }, + ], + }; + + const cache = new ContentCache(); + cache.setMdxContent("test.mdx", { + frontmatter: { title: "Test" }, + content: "Content", + }); + + // Mock visitor to use ProcessingContext + vi.mocked(visitNavigationItem).mockImplementation(({ context }) => { + context?.addAlgoliaRecord({ + title: "Test", + content: "Content", + path: "/guides/test", + breadcrumbs: [], + pageType: "Guide", + }); + + return { + indexEntries: {}, + navItem: { type: "page", title: "Test", path: "/guides/test" }, + }; + }); + + const result = buildAllOutputs(docsYml, cache); + + expect(result.algoliaRecords).toHaveLength(1); + expect(result.algoliaRecords[0].title).toBe("Test"); + }); +}); diff --git a/src/content-indexer/core/__tests__/content-cache.test.ts b/src/content-indexer/core/__tests__/content-cache.test.ts new file mode 100644 index 000000000..ff8183aa1 --- /dev/null +++ b/src/content-indexer/core/__tests__/content-cache.test.ts @@ -0,0 +1,169 @@ +import { describe, expect, test } from "vitest"; + +import { + openApiSpecFactory, + openRpcSpecFactory, +} from "@/content-indexer/utils/test-factories"; + +import { ContentCache } from "../content-cache"; + +describe("ContentCache", () => { + test("should initialize with empty caches", () => { + const cache = new ContentCache(); + const stats = cache.getStats(); + + expect(stats.mdxCount).toBe(0); + expect(stats.specCount).toBe(0); + }); + + test("should store and retrieve MDX content", () => { + const cache = new ContentCache(); + const mdxEntry = { + frontmatter: { title: "Quickstart", slug: "guides/quickstart" }, + content: "# Quickstart\n\nContent here", + }; + + cache.setMdxContent("fern/guides/quickstart.mdx", mdxEntry); + const retrieved = cache.getMdxContent("fern/guides/quickstart.mdx"); + + expect(retrieved).toEqual(mdxEntry); + expect(retrieved?.frontmatter.title).toBe("Quickstart"); + }); + + test("should return undefined for non-existent MDX", () => { + const cache = new ContentCache(); + const retrieved = cache.getMdxContent("non-existent.mdx"); + + expect(retrieved).toBeUndefined(); + }); + + test("should store and retrieve OpenAPI spec", () => { + const cache = new ContentCache(); + const spec = openApiSpecFactory(); + const specEntry = { + specType: "openapi" as const, + spec: spec, + specUrl: "https://example.com/spec.json", + }; + + cache.setSpec("ethereum-api", specEntry); + const retrieved = cache.getSpec("ethereum-api"); + + expect(retrieved).toEqual(specEntry); + expect(retrieved?.specType).toBe("openapi"); + }); + + test("should store and retrieve OpenRPC spec", () => { + const cache = new ContentCache(); + const spec = openRpcSpecFactory(); + const specEntry = { + specType: "openrpc" as const, + spec: spec, + specUrl: "https://example.com/rpc-spec.json", + }; + + cache.setSpec("solana-das-api", specEntry); + const retrieved = cache.getSpec("solana-das-api"); + + expect(retrieved).toEqual(specEntry); + expect(retrieved?.specType).toBe("openrpc"); + }); + + test("should return undefined for non-existent spec", () => { + const cache = new ContentCache(); + const retrieved = cache.getSpec("non-existent-api"); + + expect(retrieved).toBeUndefined(); + }); + + test("should handle multiple MDX entries", () => { + const cache = new ContentCache(); + + cache.setMdxContent("file1.mdx", { + frontmatter: { title: "Page 1" }, + content: "Content 1", + }); + cache.setMdxContent("file2.mdx", { + frontmatter: { title: "Page 2" }, + content: "Content 2", + }); + + expect(cache.getMdxContent("file1.mdx")?.frontmatter.title).toBe("Page 1"); + expect(cache.getMdxContent("file2.mdx")?.frontmatter.title).toBe("Page 2"); + }); + + test("should handle multiple spec entries", () => { + const cache = new ContentCache(); + + cache.setSpec("api1", { + specType: "openapi", + spec: openApiSpecFactory(), + specUrl: "url1", + }); + cache.setSpec("api2", { + specType: "openrpc", + spec: openRpcSpecFactory(), + specUrl: "url2", + }); + + expect(cache.getSpec("api1")?.specType).toBe("openapi"); + expect(cache.getSpec("api2")?.specType).toBe("openrpc"); + }); + + test("should return correct stats with mixed content", () => { + const cache = new ContentCache(); + + cache.setMdxContent("file1.mdx", { + frontmatter: {}, + content: "Content 1", + }); + cache.setMdxContent("file2.mdx", { + frontmatter: {}, + content: "Content 2", + }); + cache.setSpec("api1", { + specType: "openapi", + spec: openApiSpecFactory(), + specUrl: "url1", + }); + + const stats = cache.getStats(); + expect(stats.mdxCount).toBe(2); + expect(stats.specCount).toBe(1); + }); + + test("should overwrite existing MDX entry", () => { + const cache = new ContentCache(); + + cache.setMdxContent("file.mdx", { + frontmatter: { title: "Old Title" }, + content: "Old content", + }); + cache.setMdxContent("file.mdx", { + frontmatter: { title: "New Title" }, + content: "New content", + }); + + const retrieved = cache.getMdxContent("file.mdx"); + expect(retrieved?.frontmatter.title).toBe("New Title"); + }); + + test("should overwrite existing spec entry", () => { + const cache = new ContentCache(); + + cache.setSpec("api", { + specType: "openapi", + spec: openApiSpecFactory({ info: { title: "API", version: "1.0.0" } }), + specUrl: "url1", + }); + cache.setSpec("api", { + specType: "openrpc", + spec: openRpcSpecFactory({ info: { title: "API", version: "2.0.0" } }), + specUrl: "url2", + }); + + const retrieved = cache.getSpec("api"); + expect(retrieved?.specType).toBe("openrpc"); + expect(retrieved?.specUrl).toBe("url2"); + }); +}); diff --git a/src/content-indexer/core/__tests__/path-builder.test.ts b/src/content-indexer/core/__tests__/path-builder.test.ts new file mode 100644 index 000000000..b9d527da6 --- /dev/null +++ b/src/content-indexer/core/__tests__/path-builder.test.ts @@ -0,0 +1,93 @@ +import { describe, expect, test } from "vitest"; + +import { PathBuilder } from "../path-builder"; + +describe("PathBuilder", () => { + test("should initialize with empty parts", () => { + const builder = PathBuilder.init(); + expect(builder.get()).toBe(""); + }); + + test("should initialize with base path", () => { + const builder = PathBuilder.init("guides/getting-started"); + expect(builder.get()).toBe("guides/getting-started"); + }); + + test("should build path with single urlSlug", () => { + const builder = PathBuilder.init(); + const newBuilder = builder.apply({ urlSlug: "guides" }); + expect(newBuilder.get()).toBe("guides"); + }); + + test("should chain multiple urlSlug applications", () => { + const builder = PathBuilder.init(); + const newBuilder = builder + .apply({ urlSlug: "guides" }) + .apply({ urlSlug: "quickstart" }); + expect(newBuilder.get()).toBe("guides/quickstart"); + }); + + test("should handle fullSlug override", () => { + const builder = PathBuilder.init(); + const newBuilder = builder + .apply({ urlSlug: "guides" }) + .apply({ fullSlug: ["reference", "ethereum", "getbalance"] }); + expect(newBuilder.get()).toBe("reference/ethereum/getbalance"); + }); + + test("should build immutable path - original unchanged", () => { + const builder1 = PathBuilder.init(); + const builder2 = builder1.apply({ urlSlug: "guides" }); + const builder3 = builder2.apply({ urlSlug: "quickstart" }); + + expect(builder1.get()).toBe(""); + expect(builder2.get()).toBe("guides"); + expect(builder3.get()).toBe("guides/quickstart"); + }); + + test("should handle empty fullSlug array", () => { + const builder = PathBuilder.init(); + const newBuilder = builder.apply({ fullSlug: [] }); + expect(newBuilder.get()).toBe(""); + }); + + test("should handle fullSlug with single element", () => { + const builder = PathBuilder.init(); + const newBuilder = builder.apply({ fullSlug: ["guides"] }); + expect(newBuilder.get()).toBe("guides"); + }); + + test("should prefer fullSlug over urlSlug when both provided", () => { + const builder = PathBuilder.init(); + const newBuilder = builder.apply({ + urlSlug: "ignored", + fullSlug: ["reference", "ethereum"], + }); + expect(newBuilder.get()).toBe("reference/ethereum"); + }); + + test("should handle skipUrlSlug flag", () => { + const builder = PathBuilder.init(); + const step1 = builder.apply({ urlSlug: "guides" }); + const step2 = step1.apply({ skipUrlSlug: true }); + expect(step2.get()).toBe("guides"); + }); + + test("should handle complex path building scenario", () => { + const builder = PathBuilder.init(); + const step1 = builder.apply({ urlSlug: "reference" }); + const step2 = step1.apply({ urlSlug: "nft-api" }); + const step3 = step2.apply({ urlSlug: "getnfts" }); + + expect(step3.get()).toBe("reference/nft-api/getnfts"); + }); + + test("should handle path replacement mid-chain", () => { + const builder = PathBuilder.init(); + const step1 = builder.apply({ urlSlug: "guides" }); + const step2 = step1.apply({ urlSlug: "advanced" }); + const step3 = step2.apply({ fullSlug: ["reference", "api"] }); + + expect(step3.get()).toBe("reference/api"); + }); +}); diff --git a/src/content-indexer/core/__tests__/scanner.test.ts b/src/content-indexer/core/__tests__/scanner.test.ts new file mode 100644 index 000000000..ac78ff8c7 --- /dev/null +++ b/src/content-indexer/core/__tests__/scanner.test.ts @@ -0,0 +1,265 @@ +import { describe, expect, test } from "vitest"; + +import type { DocsYml } from "@/content-indexer/types/docsYaml"; + +import { scanDocsYml } from "../scanner"; + +describe("scanner", () => { + test("should throw error if navigation is missing", () => { + const docsYml = {} as DocsYml; + + expect(() => scanDocsYml(docsYml)).toThrow( + "Can't find navigation section in docs.yml", + ); + }); + + test("should scan pages from navigation", () => { + const docsYml: DocsYml = { + navigation: [ + { + tab: "guides", + layout: [ + { + page: "Quickstart", + path: "fern/guides/quickstart.mdx", + }, + { + page: "Advanced", + path: "fern/guides/advanced.mdx", + }, + ], + }, + ], + }; + + const result = scanDocsYml(docsYml); + expect(result.mdxPaths).toContain("fern/guides/quickstart.mdx"); + expect(result.mdxPaths).toContain("fern/guides/advanced.mdx"); + expect(result.mdxPaths.size).toBe(2); + }); + + test("should scan API specs from navigation", () => { + const docsYml: DocsYml = { + navigation: [ + { + tab: "reference", + layout: [ + { + api: "Ethereum API", + "api-name": "ethereum-api", + }, + { + api: "NFT API", + "api-name": "nft-api", + }, + ], + }, + ], + }; + + const result = scanDocsYml(docsYml); + expect(result.specNames).toContain("ethereum-api"); + expect(result.specNames).toContain("nft-api"); + expect(result.specNames.size).toBe(2); + }); + + test("should handle sections with nested pages", () => { + const docsYml: DocsYml = { + navigation: [ + { + tab: "guides", + layout: [ + { + section: "Getting Started", + contents: [ + { + page: "Quickstart", + path: "fern/guides/quickstart.mdx", + }, + { + page: "Installation", + path: "fern/guides/installation.mdx", + }, + ], + }, + ], + }, + ], + }; + + const result = scanDocsYml(docsYml); + expect(result.mdxPaths).toContain("fern/guides/quickstart.mdx"); + expect(result.mdxPaths).toContain("fern/guides/installation.mdx"); + expect(result.mdxPaths.size).toBe(2); + }); + + test("should handle section with overview page", () => { + const docsYml: DocsYml = { + navigation: [ + { + tab: "guides", + layout: [ + { + section: "Getting Started", + path: "fern/guides/overview.mdx", + contents: [ + { + page: "Quickstart", + path: "fern/guides/quickstart.mdx", + }, + ], + }, + ], + }, + ], + }; + + const result = scanDocsYml(docsYml); + expect(result.mdxPaths).toContain("fern/guides/overview.mdx"); + expect(result.mdxPaths).toContain("fern/guides/quickstart.mdx"); + expect(result.mdxPaths.size).toBe(2); + }); + + test("should skip links in navigation", () => { + const docsYml: DocsYml = { + navigation: [ + { + tab: "guides", + layout: [ + { + page: "Quickstart", + path: "fern/guides/quickstart.mdx", + }, + { + link: "External Link", + href: "https://example.com", + }, + ], + }, + ], + }; + + const result = scanDocsYml(docsYml); + expect(result.mdxPaths.size).toBe(1); + expect(result.mdxPaths).toContain("fern/guides/quickstart.mdx"); + }); + + test("should skip changelog in navigation", () => { + const docsYml: DocsYml = { + navigation: [ + { + tab: "guides", + layout: [ + { + page: "Quickstart", + path: "fern/guides/quickstart.mdx", + }, + { + changelog: "CHANGELOG.md", + }, + ], + }, + ], + }; + + const result = scanDocsYml(docsYml); + expect(result.mdxPaths.size).toBe(1); + }); + + test("should handle deeply nested sections", () => { + const docsYml: DocsYml = { + navigation: [ + { + tab: "guides", + layout: [ + { + section: "Level 1", + contents: [ + { + section: "Level 2", + contents: [ + { + page: "Deep Page", + path: "fern/guides/deep.mdx", + }, + ], + }, + ], + }, + ], + }, + ], + }; + + const result = scanDocsYml(docsYml); + expect(result.mdxPaths).toContain("fern/guides/deep.mdx"); + }); + + test("should deduplicate paths using Set", () => { + const docsYml: DocsYml = { + navigation: [ + { + tab: "tab1", + layout: [ + { + page: "Page", + path: "fern/guides/page.mdx", + }, + ], + }, + { + tab: "tab2", + layout: [ + { + page: "Page", + path: "fern/guides/page.mdx", // Duplicate + }, + ], + }, + ], + }; + + const result = scanDocsYml(docsYml); + expect(result.mdxPaths.size).toBe(1); + }); + + test("should handle mixed content types", () => { + const docsYml: DocsYml = { + navigation: [ + { + tab: "all", + layout: [ + { + page: "Guide", + path: "fern/guides/guide.mdx", + }, + { + api: "API", + "api-name": "my-api", + }, + { + section: "Section", + contents: [ + { + page: "Nested", + path: "fern/guides/nested.mdx", + }, + ], + }, + { + link: "Link", + href: "https://example.com", + }, + ], + }, + ], + }; + + const result = scanDocsYml(docsYml); + expect(result.mdxPaths.size).toBe(2); + expect(result.specNames.size).toBe(1); + expect(result.mdxPaths).toContain("fern/guides/guide.mdx"); + expect(result.mdxPaths).toContain("fern/guides/nested.mdx"); + expect(result.specNames).toContain("my-api"); + }); +}); diff --git a/src/content-indexer/core/batch-fetcher.ts b/src/content-indexer/core/batch-fetcher.ts new file mode 100644 index 000000000..7feb905d2 --- /dev/null +++ b/src/content-indexer/core/batch-fetcher.ts @@ -0,0 +1,104 @@ +import matter from "gray-matter"; +import path from "path"; + +import { fetchApiSpec } from "@/content-indexer/utils/apiSpecs"; +import { readLocalMdxFile } from "@/content-indexer/utils/filesystem"; +import { + fetchFileFromGitHub, + type RepoConfig, +} from "@/content-indexer/utils/github"; + +import { ContentCache } from "./content-cache"; +import type { ScanResult } from "./scanner"; + +/** + * Content source configuration - either filesystem or GitHub API + */ +export type ContentSource = + | { type: "filesystem"; basePath: string } + | { type: "github"; repoConfig: RepoConfig }; + +/** + * Fetches all MDX files and API specs in parallel and populates the cache. + * This is the core optimization: all I/O happens upfront in parallel. + * + * Supports two modes: + * - filesystem: Reads from local filesystem (for preview mode) + * - github: Fetches from GitHub API (for production/SDK indexer) + * + * @param scanResult - Result from scanDocsYml containing all paths and spec names + * @param source - Content source (filesystem or GitHub) + * @returns Populated ContentCache ready for processing + */ +export const batchFetchContent = async ( + scanResult: ScanResult, + source: ContentSource, +): Promise => { + const cache = new ContentCache(); + + const sourceType = source.type; + console.info( + ` ${sourceType === "filesystem" ? "Reading" : "Fetching"} ${scanResult.mdxPaths.size} MDX files and ${scanResult.specNames.size} specs...`, + ); + + // Fetch/read all MDX files in parallel + const mdxPromises = Array.from(scanResult.mdxPaths).map(async (mdxPath) => { + try { + if (source.type === "filesystem") { + // Read from local filesystem + const fullPath = path.join(source.basePath, mdxPath); + const result = await readLocalMdxFile(fullPath); + + if (result) { + cache.setMdxContent(mdxPath, { + frontmatter: result.frontmatter, + content: result.content, + }); + } + } else { + // Fetch from GitHub API + const actualPath = mdxPath.replace( + source.repoConfig.stripPathPrefix || "", + "", + ); + const fullPath = `${source.repoConfig.docsPrefix}/${actualPath}`; + + const content = await fetchFileFromGitHub(fullPath, source.repoConfig); + if (content) { + const { data, content: body } = matter(content); + cache.setMdxContent(mdxPath, { + frontmatter: data, + content: body, + }); + } + } + } catch (error) { + console.warn( + ` ⚠️ Failed to ${source.type === "filesystem" ? "read" : "fetch"} MDX file: ${mdxPath}`, + error, + ); + } + }); + + // Fetch all API specs in parallel (always from remote) + const specPromises = Array.from(scanResult.specNames).map(async (apiName) => { + try { + const result = await fetchApiSpec(apiName); + if (result) { + cache.setSpec(apiName, result); + } + } catch (error) { + console.warn(` ⚠️ Failed to fetch spec: ${apiName}`, error); + } + }); + + // Wait for all fetches to complete + await Promise.all([...mdxPromises, ...specPromises]); + + const stats = cache.getStats(); + console.info( + ` ✓ ${sourceType === "filesystem" ? "Read" : "Fetched"} ${stats.mdxCount}/${scanResult.mdxPaths.size} MDX files and ${stats.specCount}/${scanResult.specNames.size} specs`, + ); + + return cache; +}; diff --git a/src/content-indexer/core/build-all-outputs.ts b/src/content-indexer/core/build-all-outputs.ts new file mode 100644 index 000000000..f9242df41 --- /dev/null +++ b/src/content-indexer/core/build-all-outputs.ts @@ -0,0 +1,85 @@ +import { kebabCase } from "lodash-es"; + +import { + ProcessingContext, + type BuildAllOutputsResult, +} from "@/content-indexer/collectors/processing-context"; +import type { ContentCache } from "@/content-indexer/core/content-cache"; +import type { DocsYml } from "@/content-indexer/types/docsYaml"; +import { DOCS_REPO, type RepoConfig } from "@/content-indexer/utils/github"; +import { visitNavigationItem } from "@/content-indexer/visitors"; + +import { PathBuilder } from "./path-builder"; + +/** + * Phase 3 of the content indexing pipeline. + * Builds path index, navigation trees, and Algolia records in a single traversal. + * + * Uses visitor pattern to process each navigation item type and accumulates + * results in ProcessingContext. Maintains navigation hierarchy and breadcrumb + * context during traversal. + */ +export const buildAllOutputs = ( + docsYml: DocsYml, + contentCache: ContentCache, + repo: RepoConfig = DOCS_REPO, +): BuildAllOutputsResult => { + const context = new ProcessingContext(); + + // Process each tab in docs.yml + docsYml.navigation.forEach((navItem) => { + // Skip navigation items without a tab or layout + if (!navItem.tab || !navItem.layout) { + return; + } + + // Tab identifier for the index entries + const tab = kebabCase(navItem.tab); + + // Build base path for this tab + let basePathBuilder = PathBuilder.init(); + + // Apply tab slug to path (use slug from tab config if available) + const tabConfig = docsYml.tabs?.[navItem.tab]; + if (tabConfig) { + const tabSlugForPath = tabConfig.slug ?? tab; + const skipTabSlug = tabConfig["skip-slug"] ?? false; + + basePathBuilder = basePathBuilder.apply({ + urlSlug: tabSlugForPath, + skipUrlSlug: skipTabSlug, + }); + } + + // Visit all layout items using visitor pattern + const results = navItem.layout.map((layoutItem) => + visitNavigationItem({ + item: layoutItem, + parentPath: basePathBuilder, + tab, + repo, + contentCache, + context, + navigationAncestors: [], // Empty ancestors at top level + }), + ); + + // Add results to context + results.forEach((result) => { + // Add path index entries + Object.entries(result.indexEntries).forEach(([path, entry]) => { + context.addPathIndexEntry(path, entry); + }); + + // Add navigation items + if (result.navItem) { + const items = Array.isArray(result.navItem) + ? result.navItem + : [result.navItem]; + items.forEach((item) => context.addNavigationItem(tab, item)); + } + }); + }); + + return context.getResults(); +}; diff --git a/src/content-indexer/core/content-cache.ts b/src/content-indexer/core/content-cache.ts new file mode 100644 index 000000000..3861ea1b6 --- /dev/null +++ b/src/content-indexer/core/content-cache.ts @@ -0,0 +1,83 @@ +import type { OpenApiSpec, OpenRpcSpec } from "@/content-indexer/types/specs"; + +/** + * Cached MDX content with parsed frontmatter and body. + */ +export interface MdxCacheEntry { + frontmatter: { + slug?: string; + title?: string; + sidebarTitle?: string; + [key: string]: unknown; + }; + content: string; // Raw MDX body (without frontmatter) +} + +/** + * Cached API spec with type information. + */ +export interface SpecCacheEntry { + specType: "openrpc" | "openapi"; + spec: OpenRpcSpec | OpenApiSpec; + specUrl: string; +} + +/** + * Cache statistics returned by getStats(). + */ +export interface CacheStats { + mdxCount: number; + specCount: number; +} + +/** + * In-memory cache for all fetched content. + * Provides O(1) lookup for MDX files and API specs. + */ +export class ContentCache { + private mdxCache: Map; + private specCache: Map; + + constructor() { + this.mdxCache = new Map(); + this.specCache = new Map(); + } + + /** + * Store MDX content by normalized file path. + */ + setMdxContent(filePath: string, entry: MdxCacheEntry): void { + this.mdxCache.set(filePath, entry); + } + + /** + * Retrieve MDX content by file path. + */ + getMdxContent(filePath: string): MdxCacheEntry | undefined { + return this.mdxCache.get(filePath); + } + + /** + * Store API spec by api-name. + */ + setSpec(apiName: string, entry: SpecCacheEntry): void { + this.specCache.set(apiName, entry); + } + + /** + * Retrieve API spec by api-name. + */ + getSpec(apiName: string): SpecCacheEntry | undefined { + return this.specCache.get(apiName); + } + + /** + * Get cache statistics for debugging. + */ + getStats(): CacheStats { + return { + mdxCount: this.mdxCache.size, + specCount: this.specCache.size, + }; + } +} diff --git a/src/content-indexer/core/path-builder.ts b/src/content-indexer/core/path-builder.ts new file mode 100644 index 000000000..a9fa30de6 --- /dev/null +++ b/src/content-indexer/core/path-builder.ts @@ -0,0 +1,58 @@ +/** + * PathBuilder mimics Fern's slug generation logic to build full URL paths. + * Maintains an array of path segments and provides methods to build paths hierarchically. + * @note Fern incorrectly refers to full paths as "slugs" in their terminology + * @see https://buildwithfern.com/learn/docs/seo/configuring-slugs + */ +export class PathBuilder { + private segments: string[]; + + private constructor(segments: string[]) { + this.segments = segments; + } + + /** + * Creates a new PathBuilder instance with optional base path segments. + */ + static init(basePath: string = ""): PathBuilder { + const segments = basePath ? basePath.split("/").filter(Boolean) : []; + return new PathBuilder(segments); + } + + /** + * Applies slug generation rules to create a new PathBuilder. + * Supports three modes: + * - fullSlug: Completely replaces the path (used for frontmatter overrides) + * - skipUrlSlug: Returns unchanged path (used for skip-slug sections) + * - urlSlug: Appends to existing path (default behavior) + */ + apply(options: { + fullSlug?: string[]; + urlSlug?: string; + skipUrlSlug?: boolean; + }): PathBuilder { + // If fullSlug is provided (from frontmatter), it completely overrides the path + if (options.fullSlug) { + return new PathBuilder(options.fullSlug.filter(Boolean)); + } + + // If skipUrlSlug is true, don't add anything to the path + if (options.skipUrlSlug) { + return new PathBuilder([...this.segments]); + } + + // Otherwise, add the urlSlug to the path + if (options.urlSlug) { + return new PathBuilder([...this.segments, options.urlSlug]); + } + + return new PathBuilder([...this.segments]); + } + + /** + * Returns the full path as a string by joining all segments with "/". + */ + get(): string { + return this.segments.filter(Boolean).join("/"); + } +} diff --git a/src/content-indexer/core/scanner.ts b/src/content-indexer/core/scanner.ts new file mode 100644 index 000000000..33970de1e --- /dev/null +++ b/src/content-indexer/core/scanner.ts @@ -0,0 +1,85 @@ +import { + isApiConfig, + isChangelogConfig, + isLinkConfig, + isPageConfig, + isSectionConfig, + type DocsYml, + type NavigationItem, +} from "@/content-indexer/types/docsYaml"; + +/** + * Result of scanning docs.yml for all file paths and API specs. + */ +export interface ScanResult { + mdxPaths: Set; // All unique MDX file paths + specNames: Set; // All unique api-name values +} + +/** + * Recursively scans a navigation item to collect MDX paths and spec names. + */ +const scanNavigationItem = (item: NavigationItem, result: ScanResult): void => { + // Skip changelog items + if (isChangelogConfig(item)) { + return; + } + + // Skip external links + if (isLinkConfig(item)) { + return; + } + + // Collect API spec names + if (isApiConfig(item)) { + result.specNames.add(item["api-name"]); + return; + } + + // Collect page paths + if (isPageConfig(item)) { + result.mdxPaths.add(item.path); + return; + } + + // Collect section overview paths and recurse into contents + if (isSectionConfig(item)) { + if (item.path) { + result.mdxPaths.add(item.path); + } + + // Recursively scan all child items + item.contents.forEach((childItem) => { + scanNavigationItem(childItem, result); + }); + } +}; + +/** + * Scans the entire docs.yml to collect all MDX file paths and API spec names. + * This enables batch fetching all content in parallel. + * + * @param docsYml - The parsed docs.yml configuration + * @returns Object with Sets of unique MDX paths and spec names + */ +export const scanDocsYml = (docsYml: DocsYml): ScanResult => { + if (!docsYml.navigation) { + throw new Error("Can't find navigation section in docs.yml"); + } + + const result: ScanResult = { + mdxPaths: new Set(), + specNames: new Set(), + }; + + // Scan all navigation items across all tabs + docsYml.navigation.forEach((navItem) => { + if (navItem.layout) { + navItem.layout.forEach((layoutItem) => { + scanNavigationItem(layoutItem, result); + }); + } + }); + + return result; +}; diff --git a/src/content-indexer/index.ts b/src/content-indexer/index.ts new file mode 100644 index 000000000..1be17b77a --- /dev/null +++ b/src/content-indexer/index.ts @@ -0,0 +1,103 @@ +import yaml from "js-yaml"; + +import type { BuildAllOutputsResult } from "@/content-indexer/collectors/processing-context"; +import { + batchFetchContent, + type ContentSource, +} from "@/content-indexer/core/batch-fetcher"; +import { buildAllOutputs } from "@/content-indexer/core/build-all-outputs"; +import { scanDocsYml } from "@/content-indexer/core/scanner"; +import type { DocsYml } from "@/content-indexer/types/docsYaml"; +import { readLocalDocsYml } from "@/content-indexer/utils/filesystem"; +import { + fetchFileFromGitHub, + type RepoConfig, +} from "@/content-indexer/utils/github"; + +/** + * Main content indexer function using 3-phase architecture. + * Phase 1: Scan docs.yml for all file paths and spec names + * Phase 2: Batch fetch/read all content in parallel + * Phase 3: Process with cached content to build index, nav trees, and Algolia records + * + * @param source - Content source (filesystem or GitHub) + * @param repoConfig - Repository configuration (needed for path-building even in filesystem mode) + */ +export const buildContentIndex = async ( + source: ContentSource, + repoConfig: RepoConfig, +): Promise => { + const sourceName = + source.type === "filesystem" ? "local filesystem" : repoConfig.repo; + console.info( + `🔍 Building content index from ${sourceName}. This may take a few minutes...`, + ); + + // Read/fetch and parse docs.yml + let docsYml: DocsYml; + + if (source.type === "filesystem") { + const result = await readLocalDocsYml(source.basePath); + if (!result) { + throw new Error(`Failed to read docs.yml from ${source.basePath}`); + } + docsYml = result; + } else { + const docsYmlPath = `${source.repoConfig.docsPrefix}/docs.yml`; + const docsYmlContent = await fetchFileFromGitHub( + docsYmlPath, + source.repoConfig, + ); + if (!docsYmlContent) { + throw new Error( + `Failed to fetch ${docsYmlPath} from ${source.repoConfig.repo}`, + ); + } + docsYml = yaml.load(docsYmlContent) as DocsYml; + } + + // PHASE 1: SCAN + console.info("📋 Phase 1: Scanning docs.yml for all paths and specs..."); + const scanResult = scanDocsYml(docsYml); + console.info( + ` Found ${scanResult.mdxPaths.size} MDX files, ${scanResult.specNames.size} specs`, + ); + + // PHASE 2: BATCH FETCH/READ + console.info( + `📥 Phase 2: ${source.type === "filesystem" ? "Reading" : "Fetching"} all content in parallel...`, + ); + const contentCache = await batchFetchContent(scanResult, source); + + // PHASE 3: PROCESS + console.info("⚙️ Phase 3: Processing with cached content..."); + const { pathIndex, navigationTrees, algoliaRecords } = buildAllOutputs( + docsYml, + contentCache, + repoConfig, + ); + + console.info( + `📊 Generated ${Object.keys(pathIndex).length} routes, ${algoliaRecords.length} Algolia records`, + ); + + // Count sources and types for debugging + const sources = Object.values(pathIndex).reduce( + (acc, entry) => { + acc[entry.source] = (acc[entry.source] || 0) + 1; + return acc; + }, + {} as Record, + ); + const types = Object.values(pathIndex).reduce( + (acc, entry) => { + acc[entry.type] = (acc[entry.type] || 0) + 1; + return acc; + }, + {} as Record, + ); + console.info(` Sources: ${JSON.stringify(sources)}`); + console.info(` Types: ${JSON.stringify(types)}`); + + return { pathIndex, navigationTrees, algoliaRecords }; +}; diff --git a/src/content-indexer/types/algolia.ts b/src/content-indexer/types/algolia.ts new file mode 100644 index 000000000..98c34260e --- /dev/null +++ b/src/content-indexer/types/algolia.ts @@ -0,0 +1,13 @@ +/** + * Algolia search record structure. + * Each record represents a searchable page (Guide or API method). + */ +export interface AlgoliaRecord { + objectID: string; // Hash-based unique identifier - REQUIRED by Algolia + path: string; // Full pathname without leading slash (e.g., "reference/ethereum/eth-getbalance"). + pageType: "API Method" | "Guide" | "Changelog"; + title: string; + breadcrumbs: string[]; // Navigation ancestry titles for context (e.g., ["NFT API", "NFT API Endpoints"]) + httpMethod?: string; // For API methods: "GET" | "POST" | etc. + content: string; // MDX content or endpoint description +} diff --git a/src/content-indexer/types/breadcrumb.ts b/src/content-indexer/types/breadcrumb.ts new file mode 100644 index 000000000..da84b74a9 --- /dev/null +++ b/src/content-indexer/types/breadcrumb.ts @@ -0,0 +1,8 @@ +/** + * Represents a single item in the breadcrumb trail + */ +export interface BreadcrumbItem { + title: string; + path: string; + type: "tab" | "section" | "page" | "endpoint"; +} diff --git a/src/content-indexer/types/changelog.ts b/src/content-indexer/types/changelog.ts new file mode 100644 index 000000000..6c8c77483 --- /dev/null +++ b/src/content-indexer/types/changelog.ts @@ -0,0 +1,28 @@ +// TODO: This file is copied from docs-site but not fully used in Phase 2 +// MDXRemoteSerializeResult type is stubbed until changelog indexer is implemented in Phase 3 +type MDXRemoteSerializeResult = unknown; + +export interface ChangelogIndexEntry { + date: string; // YYYY-MM-DD format + fileName: string; // e.g., "2025-11-20.md" +} + +/** + * Maps changelog routes (e.g., "2025/11/20") to their metadata. Sorted by date (newest first). + */ +export type ChangelogIndex = Record; + +export interface SerializedChangelogEntry { + date: string; + serializedContent: MDXRemoteSerializeResult; +} + +export interface ChangelogApiSuccess { + entries: SerializedChangelogEntry[]; + hasMore: boolean; + total: number; +} + +export interface ChangelogApiError { + error: string; +} diff --git a/src/content-indexer/types/docsYaml.ts b/src/content-indexer/types/docsYaml.ts new file mode 100644 index 000000000..e3fa0f1ae --- /dev/null +++ b/src/content-indexer/types/docsYaml.ts @@ -0,0 +1,91 @@ +// Types for parsing Fern's docs.yml structure + +export interface PageConfig { + page: string; + path: string; + slug?: string; + hidden?: boolean; + noindex?: boolean; +} + +export interface SectionConfig { + section: string; + slug?: string; + "skip-slug"?: boolean; + hidden?: boolean; + contents: NavigationItem[]; + path?: string; // Optional overview page +} + +export interface LinkConfig { + link: string; + href: string; +} + +export interface ApiConfig { + api: string; + "api-name": string; + slug?: string; + "skip-slug"?: boolean; + hidden?: boolean; + flattened?: boolean; + paginated?: boolean; +} + +export interface ChangelogConfig { + changelog: string; + slug?: string; +} + +export type NavigationItem = + | PageConfig + | SectionConfig + | LinkConfig + | ApiConfig + | ChangelogConfig; + +export interface TabConfig { + "display-name": string; + slug?: string; + "skip-slug"?: boolean; +} + +export interface DocsYml { + tabs?: Record; + navigation: Array<{ + tab: string; + layout: NavigationItem[]; + }>; +} + +// ============================================================================ +// Type Guards +// ============================================================================ + +export const isPageConfig = (item: NavigationItem): item is PageConfig => { + return item && typeof item === "object" && "page" in item && "path" in item; +}; + +export const isSectionConfig = ( + item: NavigationItem, +): item is SectionConfig => { + return ( + item && typeof item === "object" && "section" in item && "contents" in item + ); +}; + +export const isLinkConfig = (item: NavigationItem): item is LinkConfig => { + return item && typeof item === "object" && "link" in item && "href" in item; +}; + +export const isApiConfig = (item: NavigationItem): item is ApiConfig => { + return ( + item && typeof item === "object" && "api" in item && "api-name" in item + ); +}; + +export const isChangelogConfig = ( + item: NavigationItem, +): item is ChangelogConfig => { + return item && typeof item === "object" && "changelog" in item; +}; diff --git a/src/content-indexer/types/navigation.ts b/src/content-indexer/types/navigation.ts new file mode 100644 index 000000000..715487492 --- /dev/null +++ b/src/content-indexer/types/navigation.ts @@ -0,0 +1,37 @@ +// Types for navigation tree structure stored in Redis + +interface BaseNavItem { + title: string; +} + +interface PageNavItem extends BaseNavItem { + type: "page"; + path: string; +} + +interface EndpointNavItem extends BaseNavItem { + type: "endpoint"; + path: string; + method: string; +} + +interface SectionNavItem extends BaseNavItem { + type: "section" | "api-section"; + path?: string; + children: NavItem[]; +} + +interface LinkNavItem extends BaseNavItem { + type: "link"; + href: string; +} + +export type NavItem = + | PageNavItem + | EndpointNavItem + | SectionNavItem + | LinkNavItem; + +export type NavigationTree = NavItem[]; + +export type NavigationTreesByTab = Record; diff --git a/src/content-indexer/types/openRpc.ts b/src/content-indexer/types/openRpc.ts new file mode 100644 index 000000000..41a8d3f79 --- /dev/null +++ b/src/content-indexer/types/openRpc.ts @@ -0,0 +1,12 @@ +export type AuthParamLocation = "path" | "header" | "query"; + +export interface AuthParameter { + name: string; + in: AuthParamLocation; + schema: { + type: string; + default?: string; + description?: string; + }; + required: boolean; +} diff --git a/src/content-indexer/types/page.ts b/src/content-indexer/types/page.ts new file mode 100644 index 000000000..b794cbdd9 --- /dev/null +++ b/src/content-indexer/types/page.ts @@ -0,0 +1,57 @@ +/** + * Frontmatter for a documentation page + * @see https://buildwithfern.com/learn/docs/configuration/page-level-settings + */ +export interface DocPageFrontmatter { + title?: string; + "sidebar-title"?: string; + subtitle?: string; + slug?: string; + description?: string; + "edit-this-page-url"?: string; + image?: string; + + "hide-toc"?: boolean; + "max-toc-depth"?: number; + + "hide-nav-links"?: boolean; + "hide-feedback"?: boolean; + + logo?: { + light: string; + dark: string; + }; + layout?: "guide" | "overview" | "reference" | "page" | "custom"; + headline?: string; + "canonical-url"?: string; + keywords?: string; + + "og:site_name"?: string; + "og:title"?: string; + "og:description"?: string; + "og:url"?: string; + "og:image"?: string; + "og:image:width"?: number; + "og:image:height"?: number; + "og:locale"?: string; + "og:logo"?: string; + + // SEO - Twitter properties + "twitter:title"?: string; + "twitter:description"?: string; + "twitter:handle"?: string; + "twitter:image"?: string; + "twitter:site"?: string; + "twitter:url"?: string; + "twitter:card"?: "summary" | "summary_large_image" | "app" | "player"; + + noindex?: boolean; + nofollow?: boolean; + tags?: string[]; +} + +export interface DocPage { + slug: string[]; + frontmatter: DocPageFrontmatter; + content: string; +} diff --git a/src/content-indexer/types/pathIndex.ts b/src/content-indexer/types/pathIndex.ts new file mode 100644 index 000000000..b41eeb87f --- /dev/null +++ b/src/content-indexer/types/pathIndex.ts @@ -0,0 +1,29 @@ +export interface MdxPathIndexEntry { + type: "mdx"; + filePath: string; + source: "frontmatter" | "docs-yml" | "runtime-discovery"; + tab: string; +} + +export interface OpenRpcPathIndexEntry { + type: "openrpc"; + specUrl: string; + methodName: string; + source: "docs-yml"; + tab: string; +} + +export interface OpenApiPathIndexEntry { + type: "openapi"; + specUrl: string; + operationId: string; + source: "docs-yml"; + tab: string; +} + +export type PathIndexEntry = + | MdxPathIndexEntry + | OpenRpcPathIndexEntry + | OpenApiPathIndexEntry; + +export type PathIndex = Record; diff --git a/src/content-indexer/types/specs.ts b/src/content-indexer/types/specs.ts new file mode 100644 index 000000000..ea0764fdd --- /dev/null +++ b/src/content-indexer/types/specs.ts @@ -0,0 +1,14 @@ +import type { OpenrpcDocument, ReferenceObject } from "@open-rpc/meta-schema"; +import type { OpenAPIV3 } from "openapi-types"; + +// Remove all $ref references since we only work with dereferenced specs +type NoRefs = T extends ReferenceObject | { $ref: string } + ? never + : T extends object + ? { [K in keyof T]: NoRefs } + : T; + +export type OpenRpcSpec = NoRefs; +export type OpenApiSpec = NoRefs; + +export type SpecType = "openrpc" | "openapi"; diff --git a/src/content-indexer/uploaders/__tests__/algolia.test.ts b/src/content-indexer/uploaders/__tests__/algolia.test.ts new file mode 100644 index 000000000..651b790ba --- /dev/null +++ b/src/content-indexer/uploaders/__tests__/algolia.test.ts @@ -0,0 +1,97 @@ +import { describe, expect, test, vi } from "vitest"; + +import type { AlgoliaRecord } from "@/content-indexer/types/algolia"; + +import { uploadToAlgolia } from "../algolia"; + +describe("uploadToAlgolia", () => { + test("should skip if no ALGOLIA_APP_ID", async () => { + const originalAppId = process.env.ALGOLIA_APP_ID; + const originalKey = process.env.ALGOLIA_ADMIN_API_KEY; + + delete process.env.ALGOLIA_APP_ID; + process.env.ALGOLIA_ADMIN_API_KEY = "test-key"; + + const consoleSpy = vi.spyOn(console, "warn").mockImplementation(() => {}); + const records: AlgoliaRecord[] = []; + + await uploadToAlgolia(records, { isWalletMode: false }); + + expect(consoleSpy).toHaveBeenCalledWith( + expect.stringContaining("Algolia credentials not found"), + ); + + consoleSpy.mockRestore(); + process.env.ALGOLIA_APP_ID = originalAppId; + process.env.ALGOLIA_ADMIN_API_KEY = originalKey; + }); + + test("should skip if no ALGOLIA_ADMIN_API_KEY", async () => { + const originalAppId = process.env.ALGOLIA_APP_ID; + const originalKey = process.env.ALGOLIA_ADMIN_API_KEY; + + process.env.ALGOLIA_APP_ID = "test-app-id"; + delete process.env.ALGOLIA_ADMIN_API_KEY; + + const consoleSpy = vi.spyOn(console, "warn").mockImplementation(() => {}); + const records: AlgoliaRecord[] = []; + + await uploadToAlgolia(records, { isWalletMode: false }); + + expect(consoleSpy).toHaveBeenCalledWith( + expect.stringContaining("Algolia credentials not found"), + ); + + consoleSpy.mockRestore(); + process.env.ALGOLIA_APP_ID = originalAppId; + process.env.ALGOLIA_ADMIN_API_KEY = originalKey; + }); + + test("should skip if index name not configured for docs mode", async () => { + const originalAppId = process.env.ALGOLIA_APP_ID; + const originalKey = process.env.ALGOLIA_ADMIN_API_KEY; + const originalIndexName = process.env.ALGOLIA_INDEX_NAME; + + process.env.ALGOLIA_APP_ID = "test-app-id"; + process.env.ALGOLIA_ADMIN_API_KEY = "test-key"; + delete process.env.ALGOLIA_INDEX_NAME; + + const consoleSpy = vi.spyOn(console, "warn").mockImplementation(() => {}); + const records: AlgoliaRecord[] = []; + + await uploadToAlgolia(records, { isWalletMode: false }); + + expect(consoleSpy).toHaveBeenCalledWith( + expect.stringContaining("index name not configured"), + ); + + consoleSpy.mockRestore(); + process.env.ALGOLIA_APP_ID = originalAppId; + process.env.ALGOLIA_ADMIN_API_KEY = originalKey; + process.env.ALGOLIA_INDEX_NAME = originalIndexName; + }); + + test("should skip if wallet index name not configured", async () => { + const originalAppId = process.env.ALGOLIA_APP_ID; + const originalKey = process.env.ALGOLIA_ADMIN_API_KEY; + const originalWalletIndexName = process.env.ALGOLIA_WALLET_INDEX_NAME; + + process.env.ALGOLIA_APP_ID = "test-app-id"; + process.env.ALGOLIA_ADMIN_API_KEY = "test-key"; + delete process.env.ALGOLIA_WALLET_INDEX_NAME; + + const consoleSpy = vi.spyOn(console, "warn").mockImplementation(() => {}); + const records: AlgoliaRecord[] = []; + + await uploadToAlgolia(records, { isWalletMode: true }); + + expect(consoleSpy).toHaveBeenCalledWith( + expect.stringContaining("index name not configured"), + ); + + consoleSpy.mockRestore(); + process.env.ALGOLIA_APP_ID = originalAppId; + process.env.ALGOLIA_ADMIN_API_KEY = originalKey; + process.env.ALGOLIA_WALLET_INDEX_NAME = originalWalletIndexName; + }); +}); diff --git a/src/content-indexer/uploaders/__tests__/redis.test.ts b/src/content-indexer/uploaders/__tests__/redis.test.ts new file mode 100644 index 000000000..87cd904fc --- /dev/null +++ b/src/content-indexer/uploaders/__tests__/redis.test.ts @@ -0,0 +1,129 @@ +import { beforeEach, describe, expect, test, vi } from "vitest"; + +import type { NavigationTreesByTab } from "@/content-indexer/types/navigation"; +import type { PathIndex } from "@/content-indexer/types/pathIndex"; + +import { storeToRedis } from "../redis"; + +// Mock Redis +const mockSet = vi.fn().mockResolvedValue("OK"); +const mockGet = vi.fn(); + +vi.mock("@/content-indexer/utils/redis", () => ({ + getRedis: vi.fn(() => ({ + set: mockSet, + get: mockGet, + })), +})); + +describe("storeToRedis", () => { + beforeEach(() => { + mockSet.mockClear(); + }); + + test("should store path index to Redis", async () => { + const pathIndex: PathIndex = { + "guides/quickstart": { + type: "mdx", + filePath: "fern/guides/quickstart.mdx", + source: "docs-yml", + tab: "guides", + }, + }; + + await storeToRedis(pathIndex, {}, { isWalletMode: false }); + + expect(mockSet).toHaveBeenCalledWith( + "main/path-index.json", + JSON.stringify(pathIndex, null, 2), + ); + }); + + test("should store navigation trees to Redis", async () => { + const navigationTrees: NavigationTreesByTab = { + guides: [ + { + title: "Quickstart", + path: "/guides/quickstart", + type: "page", + }, + ], + }; + + await storeToRedis({}, navigationTrees, { isWalletMode: false }); + + expect(mockSet).toHaveBeenCalledWith( + "main/nav-guides.json", + JSON.stringify(navigationTrees.guides, null, 2), + ); + }); + + test("should store multiple navigation trees", async () => { + const navigationTrees: NavigationTreesByTab = { + guides: [ + { + title: "Guide1", + path: "/guides/guide1", + type: "page", + }, + ], + reference: [ + { + title: "Ref1", + path: "/reference/ref1", + type: "page", + }, + ], + }; + + await storeToRedis({}, navigationTrees, { isWalletMode: false }); + + expect(mockSet).toHaveBeenCalledWith( + "main/nav-guides.json", + JSON.stringify(navigationTrees.guides, null, 2), + ); + expect(mockSet).toHaveBeenCalledWith( + "main/nav-reference.json", + JSON.stringify(navigationTrees.reference, null, 2), + ); + }); + + test("should use wallet prefix if isWalletMode is true", async () => { + const pathIndex: PathIndex = { + "wallets/metamask": { + type: "mdx", + filePath: "wallets/metamask.mdx", + source: "docs-yml", + tab: "wallets", + }, + }; + + await storeToRedis(pathIndex, {}, { isWalletMode: true }); + + expect(mockSet).toHaveBeenCalledWith( + "main/wallet-path-index.json", + JSON.stringify(pathIndex, null, 2), + ); + }); + + test("should store all data in parallel", async () => { + const pathIndex: PathIndex = { + "guides/quickstart": { + type: "mdx", + filePath: "file.mdx", + source: "docs-yml", + tab: "guides", + }, + }; + + const navigationTrees: NavigationTreesByTab = { + guides: [{ title: "Guide", path: "/guides", type: "page" }], + reference: [{ title: "Ref", path: "/reference", type: "page" }], + }; + + await storeToRedis(pathIndex, navigationTrees, { isWalletMode: false }); + + // Should have called set 3 times (1 pathIndex + 2 nav trees) + expect(mockSet).toHaveBeenCalledTimes(3); + }); +}); diff --git a/src/content-indexer/uploaders/algolia.ts b/src/content-indexer/uploaders/algolia.ts new file mode 100644 index 000000000..67eb0d7d3 --- /dev/null +++ b/src/content-indexer/uploaders/algolia.ts @@ -0,0 +1,99 @@ +import { algoliasearch } from "algoliasearch"; + +import type { AlgoliaRecord } from "@/content-indexer/types/algolia"; +import { truncateRecord } from "@/content-indexer/utils/truncate-record"; + +/** + * Uploads Algolia records using atomic index swap for zero-downtime updates. + * + * Process: + * 1. Upload all records to a temporary index + * 2. Copy settings/synonyms from production index (if exists) + * 3. Atomically swap temp index to production + * + * This ensures users never see empty search results during updates. + */ +export const uploadToAlgolia = async ( + records: AlgoliaRecord[], + options: { isWalletMode: boolean }, +): Promise => { + const appId = process.env.ALGOLIA_APP_ID; + const adminKey = process.env.ALGOLIA_ADMIN_API_KEY; + + if (!appId || !adminKey) { + console.warn("⚠️ Algolia credentials not found. Skipping Algolia upload."); + console.warn( + " Set ALGOLIA_APP_ID and ALGOLIA_ADMIN_API_KEY to enable search indexing.", + ); + return; + } + + const targetIndexName = options.isWalletMode + ? process.env.ALGOLIA_WALLET_INDEX_NAME + : process.env.ALGOLIA_INDEX_NAME; + + if (!targetIndexName) { + console.warn( + "⚠️ Algolia index name not configured. Skipping Algolia upload.", + ); + console.warn( + ` Set ${options.isWalletMode ? "ALGOLIA_WALLET_INDEX_NAME" : "ALGOLIA_INDEX_NAME"} environment variable.`, + ); + return; + } + + const client = algoliasearch(appId, adminKey); + + const tempIndexName = `${targetIndexName}_temp`; + + console.info( + `📤 Uploading ${records.length} records to Algolia (${targetIndexName})...`, + ); + + // Truncate records to fit Algolia's 100KB limit (measures entire JSON payload) + const truncatedRecords = records.map(truncateRecord); + + try { + // 1. Upload all records to temporary index + await client.saveObjects({ + indexName: tempIndexName, + // Algolia SDK expects index signature, but we want to be more precise about the type + objects: truncatedRecords as unknown as Array>, + }); + + console.info(` ✓ Uploaded ${records.length} records to ${tempIndexName}`); + + // 2. Copy settings/synonyms from production index (if it exists) + try { + await client.operationIndex({ + indexName: targetIndexName, + operationIndexParams: { + operation: "copy", + destination: tempIndexName, + scope: ["settings", "synonyms", "rules"], + }, + }); + console.info(" ✓ Copied settings from production index"); + } catch (_error) { + // Production index might not exist on first run - this is fine + console.info( + " ℹ️ No existing production index found (might be first run)", + ); + } + + // 3. Atomic swap: move temp index to production + console.info(` 🔄 Swapping ${tempIndexName} → ${targetIndexName}...`); + await client.operationIndex({ + indexName: tempIndexName, + operationIndexParams: { + operation: "move", + destination: targetIndexName, + }, + }); + + console.info(`✅ Successfully updated Algolia index: ${targetIndexName}`); + } catch (error) { + console.error("❌ Failed to upload to Algolia:", error); + throw error; + } +}; diff --git a/src/content-indexer/uploaders/redis.ts b/src/content-indexer/uploaders/redis.ts new file mode 100644 index 000000000..eaaa0bbcb --- /dev/null +++ b/src/content-indexer/uploaders/redis.ts @@ -0,0 +1,65 @@ +import type { + NavigationTree, + NavigationTreesByTab, +} from "@/content-indexer/types/navigation"; +import type { PathIndex } from "@/content-indexer/types/pathIndex"; +import { getRedis } from "@/content-indexer/utils/redis"; + +// Helper to count nav items recursively +const countItems = (items: NavigationTree): number => { + return items.reduce((sum, item) => { + const childCount = + item.type === "section" || item.type === "api-section" + ? countItems(item.children) + : 0; + return sum + 1 + childCount; + }, 0); +}; + +/** + * Stores the path index and navigation trees to Redis. + * Returns promises for all storage operations. + */ +export const storeToRedis = async ( + pathIndex: PathIndex, + navigationTrees: NavigationTreesByTab, + options: { + isWalletMode: boolean; + }, +): Promise => { + const redis = getRedis(); + + // Determine Redis keys based on mode + const pathIndexKey = options.isWalletMode + ? "main/wallet-path-index.json" + : "main/path-index.json"; + + // Store path index + const pathIndexPromise = redis + .set(pathIndexKey, JSON.stringify(pathIndex, null, 2)) + .then(() => { + console.info(`✅ Path index saved to Redis (${pathIndexKey})`); + }); + + // Filter navigation trees based on mode: + // - Wallet mode: only write nav-wallets + // - Default mode: skip nav-wallets (to avoid overwriting wallet repo data) + const navigationTreesPromises = Object.entries(navigationTrees) + .filter(([tab]) => { + if (options.isWalletMode) { + return tab === "wallets"; + } + // Default mode: skip wallets tab to avoid overwriting wallets index + return tab !== "wallets"; + }) + .map(async ([tab, navTree]) => { + const redisKey = `main/nav-${tab}.json`; + const itemCount = countItems(navTree); + await redis.set(redisKey, JSON.stringify(navTree, null, 2)); + console.info( + `✅ Navigation for '${tab}' saved to Redis (${itemCount} items)`, + ); + }); + + await Promise.all([pathIndexPromise, ...navigationTreesPromises]); +}; diff --git a/src/content-indexer/utils/__tests__/navigation-helpers.test.ts b/src/content-indexer/utils/__tests__/navigation-helpers.test.ts new file mode 100644 index 000000000..7223c19e4 --- /dev/null +++ b/src/content-indexer/utils/__tests__/navigation-helpers.test.ts @@ -0,0 +1,33 @@ +import { describe, expect, test } from "vitest"; + +import { createBreadcrumbNavItem } from "../navigation-helpers"; + +describe("navigation-helpers", () => { + describe("createBreadcrumbNavItem", () => { + test("should create api-section breadcrumb", () => { + const result = createBreadcrumbNavItem("NFT API", "api-section"); + + expect(result).toEqual({ + title: "NFT API", + type: "api-section", + children: [], + }); + }); + + test("should always have empty children array", () => { + const result = createBreadcrumbNavItem("Ethereum API", "api-section"); + + if (result.type === "section" || result.type === "api-section") { + expect(result.children).toEqual([]); + expect(result.children).toHaveLength(0); + } + }); + + test("should preserve title exactly", () => { + const title = "Complex API Name with Spaces & Special Chars!"; + const result = createBreadcrumbNavItem(title, "api-section"); + + expect(result.title).toBe(title); + }); + }); +}); diff --git a/src/content-indexer/utils/__tests__/normalization.test.ts b/src/content-indexer/utils/__tests__/normalization.test.ts new file mode 100644 index 000000000..929b9b6fa --- /dev/null +++ b/src/content-indexer/utils/__tests__/normalization.test.ts @@ -0,0 +1,122 @@ +import { describe, expect, test } from "vitest"; + +import { + normalizeFilePath, + normalizeSlug, +} from "@/content-indexer/utils/normalization"; +import { repoConfigFactory } from "@/content-indexer/utils/test-factories"; + +describe("normalization utils", () => { + describe("normalizeSlug", () => { + test("should return undefined for undefined input", () => { + expect(normalizeSlug(undefined)).toBeUndefined(); + }); + + test("should return undefined for empty string", () => { + expect(normalizeSlug("")).toBeUndefined(); + }); + + test("should remove docs/ prefix", () => { + expect(normalizeSlug("docs/guides/quickstart")).toBe("guides/quickstart"); + }); + + test("should handle slug without docs/ prefix", () => { + expect(normalizeSlug("guides/quickstart")).toBe("guides/quickstart"); + }); + + test("should only remove leading docs/ prefix", () => { + expect(normalizeSlug("docs/reference/docs/api")).toBe( + "reference/docs/api", + ); + }); + + test("should handle docs/ as entire slug", () => { + expect(normalizeSlug("docs/")).toBe(""); + }); + + test("should preserve internal slashes", () => { + expect(normalizeSlug("docs/guides/getting-started/quickstart")).toBe( + "guides/getting-started/quickstart", + ); + }); + }); + + describe("normalizeFilePath", () => { + test("should remove stripPathPrefix", () => { + const result = normalizeFilePath( + "fern/guides/quickstart.mdx", + repoConfigFactory({ + stripPathPrefix: "fern/", + docsPrefix: "docs/fern/", + }), + ); + expect(result).toBe("guides/quickstart.mdx"); + }); + + test("should handle path without matching prefix", () => { + const result = normalizeFilePath( + "guides/quickstart.mdx", + repoConfigFactory({ + stripPathPrefix: "fern/", + docsPrefix: "docs/fern/", + }), + ); + expect(result).toBe("guides/quickstart.mdx"); + }); + + test("should handle empty stripPathPrefix", () => { + const result = normalizeFilePath( + "guides/quickstart.mdx", + repoConfigFactory({ + stripPathPrefix: "", + docsPrefix: "docs/", + }), + ); + expect(result).toBe("guides/quickstart.mdx"); + }); + + test("should handle undefined stripPathPrefix", () => { + const result = normalizeFilePath( + "guides/quickstart.mdx", + repoConfigFactory({ + docsPrefix: "docs/", + }), + ); + expect(result).toBe("guides/quickstart.mdx"); + }); + + test("should handle complex path", () => { + const result = normalizeFilePath( + "fern/docs/reference/ethereum/methods/eth_getBalance.mdx", + repoConfigFactory({ + stripPathPrefix: "fern/docs/", + docsPrefix: "docs/fern/docs/", + }), + ); + expect(result).toBe("reference/ethereum/methods/eth_getBalance.mdx"); + }); + + test("should handle path with no prefix", () => { + const result = normalizeFilePath( + "api/spec.json", + repoConfigFactory({ + stripPathPrefix: "", + docsPrefix: "", + }), + ); + expect(result).toBe("api/spec.json"); + }); + + test("should strip multiple occurrences of prefix", () => { + const result = normalizeFilePath( + "fern/fern/guides/quickstart.mdx", + repoConfigFactory({ + stripPathPrefix: "fern/", + docsPrefix: "docs/", + }), + ); + // replace() only replaces first occurrence by default + expect(result).toBe("fern/guides/quickstart.mdx"); + }); + }); +}); diff --git a/src/content-indexer/utils/__tests__/openapi.test.ts b/src/content-indexer/utils/__tests__/openapi.test.ts new file mode 100644 index 000000000..12c20d128 --- /dev/null +++ b/src/content-indexer/utils/__tests__/openapi.test.ts @@ -0,0 +1,275 @@ +import { describe, expect, test } from "vitest"; + +import { PathBuilder } from "@/content-indexer/core/path-builder"; + +import { + buildOperationPath, + extractOpenApiOperations, + getOperationDescription, + getOperationTitle, +} from "../openapi"; + +describe("openapi utils", () => { + describe("extractOpenApiOperations", () => { + test("should extract operations from paths", () => { + const paths = { + "/users": { + get: { + operationId: "getUsers", + tags: ["users"], + }, + post: { + operationId: "createUser", + tags: ["users"], + }, + }, + }; + + const operations = extractOpenApiOperations(paths); + expect(operations).toHaveLength(2); + expect(operations[0].operationId).toBe("getUsers"); + expect(operations[0].method).toBe("GET"); + expect(operations[1].operationId).toBe("createUser"); + expect(operations[1].method).toBe("POST"); + }); + + test("should handle operation without explicit operationId", () => { + const paths = { + "/users": { + get: { + summary: "Get all users", + }, + }, + }; + + const operations = extractOpenApiOperations(paths); + expect(operations).toHaveLength(1); + expect(operations[0].operationId).toBe("Get all users"); + }); + + test("should generate operationId from method and path if missing", () => { + const paths = { + "/users/{id}": { + get: {}, + }, + }; + + const operations = extractOpenApiOperations(paths); + expect(operations).toHaveLength(1); + expect(operations[0].operationId).toBe("get_/users/{id}"); + }); + + test("should extract first tag from tags array", () => { + const paths = { + "/users": { + get: { + operationId: "getUsers", + tags: ["users", "admin", "v2"], + }, + }, + }; + + const operations = extractOpenApiOperations(paths); + expect(operations[0].tag).toBe("users"); + }); + + test("should handle operation without tags", () => { + const paths = { + "/users": { + get: { + operationId: "getUsers", + }, + }, + }; + + const operations = extractOpenApiOperations(paths); + expect(operations[0].tag).toBeUndefined(); + }); + + test("should skip non-HTTP method properties", () => { + const paths = { + "/users": { + get: { + operationId: "getUsers", + }, + parameters: [], // Not an HTTP method + description: "Users endpoint", // Not an HTTP method + }, + }; + + const operations = extractOpenApiOperations(paths); + expect(operations).toHaveLength(1); + }); + + test("should handle multiple paths", () => { + const paths = { + "/users": { + get: { operationId: "getUsers" }, + }, + "/posts": { + get: { operationId: "getPosts" }, + post: { operationId: "createPost" }, + }, + }; + + const operations = extractOpenApiOperations(paths); + expect(operations).toHaveLength(3); + }); + + test("should handle empty paths object", () => { + const operations = extractOpenApiOperations({}); + expect(operations).toEqual([]); + }); + }); + + describe("getOperationTitle", () => { + test("should return summary if available", () => { + const spec = { + paths: { + "/users": { + get: { + operationId: "getUsers", + summary: "Get All Users", + }, + }, + }, + }; + + const title = getOperationTitle(spec, "getUsers", "/users"); + expect(title).toBe("Get All Users"); + }); + + test("should fallback to operationId if no summary", () => { + const spec = { + paths: { + "/users": { + get: { + operationId: "getUsers", + }, + }, + }, + }; + + const title = getOperationTitle(spec, "getUsers", "/users"); + expect(title).toBe("getUsers"); + }); + + test("should return operationId if path not found", () => { + const spec = { + paths: {}, + }; + + const title = getOperationTitle(spec, "getUsers", "/users"); + expect(title).toBe("getUsers"); + }); + + test("should return operationId if operation not found in path", () => { + const spec = { + paths: { + "/users": { + post: { + operationId: "createUser", + summary: "Create User", + }, + }, + }, + }; + + const title = getOperationTitle(spec, "getUsers", "/users"); + expect(title).toBe("getUsers"); + }); + }); + + describe("getOperationDescription", () => { + test("should return description if available", () => { + const spec = { + paths: { + "/users": { + get: { + description: "Retrieves all users from the system", + }, + }, + }, + }; + + const description = getOperationDescription(spec, "/users", "get"); + expect(description).toBe("Retrieves all users from the system"); + }); + + test("should return empty string if path not found", () => { + const spec = { + paths: {}, + }; + + const description = getOperationDescription(spec, "/users", "get"); + expect(description).toBe(""); + }); + + test("should return empty string if method not found", () => { + const spec = { + paths: { + "/users": { + post: { + description: "Create user", + }, + }, + }, + }; + + const description = getOperationDescription(spec, "/users", "get"); + expect(description).toBe(""); + }); + + test("should return empty string if description not present", () => { + const spec = { + paths: { + "/users": { + get: { + operationId: "getUsers", + }, + }, + }, + }; + + const description = getOperationDescription(spec, "/users", "get"); + expect(description).toBe(""); + }); + }); + + describe("buildOperationPath", () => { + test("should build path without tag", () => { + const builder = PathBuilder.init("reference/api"); + const path = buildOperationPath(builder, "getUsers", undefined); + + expect(path).toBe("reference/api/get-users"); + }); + + test("should build path with tag", () => { + const builder = PathBuilder.init("reference/api"); + const path = buildOperationPath(builder, "getUsers", "users"); + + expect(path).toBe("reference/api/users/get-users"); + }); + + test("should kebab-case operationId", () => { + const builder = PathBuilder.init("reference"); + const path = buildOperationPath(builder, "getUsersById", undefined); + + expect(path).toBe("reference/get-users-by-id"); + }); + + test("should kebab-case tag", () => { + const builder = PathBuilder.init("reference"); + const path = buildOperationPath(builder, "getUsers", "User Management"); + + expect(path).toBe("reference/user-management/get-users"); + }); + + test("should handle empty base path", () => { + const builder = PathBuilder.init(); + const path = buildOperationPath(builder, "getUsers", "users"); + + expect(path).toBe("users/get-users"); + }); + }); +}); diff --git a/src/content-indexer/utils/__tests__/openrpc.test.ts b/src/content-indexer/utils/__tests__/openrpc.test.ts new file mode 100644 index 000000000..b23943ee7 --- /dev/null +++ b/src/content-indexer/utils/__tests__/openrpc.test.ts @@ -0,0 +1,71 @@ +import { describe, expect, test } from "vitest"; + +import type { OpenRpcSpec } from "@/content-indexer/types/specs"; + +import { isValidOpenRpcSpec } from "../openrpc"; + +describe("openrpc utils", () => { + describe("isValidOpenRpcSpec", () => { + test("should return true for valid OpenRPC spec", () => { + const spec: OpenRpcSpec = { + openrpc: "1.0.0", + info: { title: "API", version: "1.0.0" }, + methods: [ + { + name: "getAsset", + params: [], + result: { name: "result", schema: {} }, + }, + ], + }; + + expect(isValidOpenRpcSpec(spec)).toBe(true); + }); + + test("should return false for spec without methods", () => { + const spec = { + openrpc: "1.0.0", + info: { title: "API", version: "1.0.0" }, + }; + + expect(isValidOpenRpcSpec(spec)).toBe(false); + }); + + test("should return true for spec with empty methods array", () => { + const spec = { + openrpc: "1.0.0", + info: { title: "API", version: "1.0.0" }, + methods: [], + }; + + // Type guard only checks structure, not content validity + expect(isValidOpenRpcSpec(spec)).toBe(true); + }); + + test("should return false for spec with non-array methods", () => { + const spec = { + openrpc: "1.0.0", + info: { title: "API", version: "1.0.0" }, + methods: "invalid", + }; + + expect(isValidOpenRpcSpec(spec)).toBe(false); + }); + + test("should return false for undefined spec", () => { + expect(isValidOpenRpcSpec(undefined as unknown as OpenRpcSpec)).toBe( + false, + ); + }); + + test("should return false for null spec", () => { + expect(isValidOpenRpcSpec(null as unknown as OpenRpcSpec)).toBe(false); + }); + + test("should return false for non-object spec", () => { + expect(isValidOpenRpcSpec("string" as unknown as OpenRpcSpec)).toBe( + false, + ); + }); + }); +}); diff --git a/src/content-indexer/utils/__tests__/truncate-record.test.ts b/src/content-indexer/utils/__tests__/truncate-record.test.ts new file mode 100644 index 000000000..6367ee229 --- /dev/null +++ b/src/content-indexer/utils/__tests__/truncate-record.test.ts @@ -0,0 +1,121 @@ +import { describe, expect, test, vi } from "vitest"; + +import type { AlgoliaRecord } from "@/content-indexer/types/algolia"; + +import { truncateRecord } from "../truncate-record"; + +describe("truncateRecord", () => { + test("should return record unchanged if under size limit", () => { + const record: AlgoliaRecord = { + objectID: "abc123", + path: "guides/quickstart", + pageType: "Guide", + title: "Quickstart", + content: "Short content", + breadcrumbs: ["Guides"], + }; + + const result = truncateRecord(record); + expect(result).toEqual(record); + expect(result.content).toBe("Short content"); + }); + + test("should truncate content for oversized record", () => { + // Create a record with content exceeding 100KB + const largeContent = "x".repeat(150_000); + const record: AlgoliaRecord = { + objectID: "abc123", + path: "guides/large", + pageType: "Guide", + title: "Large Page", + content: largeContent, + breadcrumbs: ["Guides"], + }; + + const result = truncateRecord(record); + expect(result.content).not.toBe(largeContent); + expect(result.content.length).toBeLessThan(largeContent.length); + expect(result.content).toMatch(/\.\.\.$/); // Ends with "..." + expect( + Buffer.byteLength(JSON.stringify(result), "utf8"), + ).toBeLessThanOrEqual(100_000); + }); + + test("should preserve all fields except content", () => { + const largeContent = "x".repeat(150_000); + const record: AlgoliaRecord = { + objectID: "abc123", + path: "reference/method", + pageType: "API Method", + title: "eth_getBalance", + content: largeContent, + breadcrumbs: ["API", "Ethereum"], + httpMethod: "POST", + }; + + const result = truncateRecord(record); + expect(result.objectID).toBe(record.objectID); + expect(result.path).toBe(record.path); + expect(result.pageType).toBe(record.pageType); + expect(result.title).toBe(record.title); + expect(result.breadcrumbs).toEqual(record.breadcrumbs); + expect(result.httpMethod).toBe(record.httpMethod); + }); + + test("should throw error if overhead is too large", () => { + const record: AlgoliaRecord = { + objectID: "abc123", + path: "guides/test", + pageType: "Guide", + title: "Test", + content: "Content", + breadcrumbs: Array(50_000).fill("B"), // Many breadcrumbs = huge overhead + }; + + expect(() => truncateRecord(record)).toThrow( + /Record overhead .* is too large/, + ); + }); + + test("should log warning for oversized record", () => { + const warnSpy = vi.spyOn(console, "warn").mockImplementation(() => {}); + + const largeContent = "x".repeat(150_000); + const record: AlgoliaRecord = { + objectID: "abc123", + path: "guides/large", + pageType: "Guide", + title: "Large Page", + content: largeContent, + breadcrumbs: [], + }; + + truncateRecord(record); + + expect(warnSpy).toHaveBeenCalled(); + expect(warnSpy.mock.calls[0][0]).toContain("exceeds"); + expect(warnSpy.mock.calls[0][0]).toContain("Large Page"); + + warnSpy.mockRestore(); + }); + + test("should handle record at exactly the size limit", () => { + // Create content to make record exactly 100KB + const targetSize = 100_000; + const overhead = 200; // Approximate overhead + const content = "x".repeat(targetSize - overhead); + + const record: AlgoliaRecord = { + objectID: "abc", + path: "path", + pageType: "Guide", + title: "Title", + content, + breadcrumbs: [], + }; + + const result = truncateRecord(record); + const resultSize = Buffer.byteLength(JSON.stringify(result), "utf8"); + expect(resultSize).toBeLessThanOrEqual(100_000); + }); +}); diff --git a/src/content-indexer/utils/apiSpecs.ts b/src/content-indexer/utils/apiSpecs.ts new file mode 100644 index 000000000..1c6568155 --- /dev/null +++ b/src/content-indexer/utils/apiSpecs.ts @@ -0,0 +1,136 @@ +// Utilities for fetching and processing OpenRPC/OpenAPI specifications +import type { + OpenApiSpec, + OpenRpcSpec, + SpecType, +} from "@/content-indexer/types/specs"; + +import { fetchWithRetries } from "./fetchWithRetries"; + +interface MetadataJson { + files: string[]; +} + +const METADATA_URL = "https://dev-docs.alchemy.com/metadata.json"; + +// Map api-name values that don't match their filename in metadata.json +const API_NAME_TO_FILENAME: Record = { + avalanche: "avax", + arbitrum: "arb", + "polygon-zkevm": "polygonzkevm", +}; + +let cachedMetadata: MetadataJson | null = null; + +/** + * Fetches the metadata.json file which contains all available spec URLs. + */ +const getMetadata = async (): Promise => { + if (cachedMetadata) { + return cachedMetadata; + } + + const response = await fetchWithRetries(METADATA_URL); + + if (!response.ok) { + console.warn(`Failed to fetch metadata.json`); + return undefined; + } + + try { + const metadata = (await response.json()) as MetadataJson; + cachedMetadata = metadata; + return metadata; + } catch (error) { + console.warn(`Error parsing metadata.json:`, error); + return undefined; + } +}; + +/** + * Determines the spec type from the URL path. + * - /chains/ → openrpc + * - /alchemy/json-rpc/ → openrpc + * - /alchemy/rest/ → openapi + */ +const getSpecTypeFromUrl = (url: string): SpecType => { + if (url.includes("/rest/")) { + return "openapi"; + } + return "openrpc"; +}; + +/** + * Finds the spec URL and type for a given API name from metadata.json. + * The api-name should match the filename (e.g., "eth" → "eth.json"). + */ +export const getSpecInfo = async ( + apiName: string, +): Promise<{ specUrl: string; specType: SpecType } | undefined> => { + const metadata = await getMetadata(); + + if (!metadata) { + console.warn(`Could not fetch metadata.json`); + return undefined; + } + + // Map api-name to filename if there's an exception + const filename = API_NAME_TO_FILENAME[apiName] ?? apiName; + + // Look for a file that matches the filename + const specUrl = metadata.files.find((file) => + file.endsWith(`/${filename}.json`), + ); + + if (!specUrl) { + console.warn( + `Could not find spec for api-name: ${apiName} (filename: ${filename})`, + ); + return undefined; + } + + const specType = getSpecTypeFromUrl(specUrl); + + return { specUrl, specType }; +}; + +/** + * Fetches spec info and the spec itself for a given API name. + */ +export const fetchApiSpec = async ( + apiName: string, +): Promise< + | { specType: "openrpc"; spec: OpenRpcSpec; specUrl: string } + | { specType: "openapi"; spec: OpenApiSpec; specUrl: string } + | undefined +> => { + const specInfo = await getSpecInfo(apiName); + + if (!specInfo) { + console.warn(`Could not determine spec info for api: ${apiName}`); + return undefined; + } + + const { specUrl, specType } = specInfo; + + // Fetch the spec directly + const response = await fetchWithRetries(specUrl); + + if (!response.ok) { + return undefined; + } + + try { + const spec = await response.json(); + + // Return with proper typing based on specType + if (specType === "openrpc") { + return { specType: "openrpc", spec: spec as OpenRpcSpec, specUrl }; + } else { + return { specType: "openapi", spec: spec as OpenApiSpec, specUrl }; + } + } catch (error) { + console.warn(`Error parsing spec JSON for ${apiName}:`, error); + return undefined; + } +}; diff --git a/src/content-indexer/utils/fetchWithRetries.ts b/src/content-indexer/utils/fetchWithRetries.ts new file mode 100644 index 000000000..d47f46e54 --- /dev/null +++ b/src/content-indexer/utils/fetchWithRetries.ts @@ -0,0 +1,35 @@ +type FetchOptions = Parameters[1]; + +const delay = (ms: number) => new Promise((res) => setTimeout(res, ms)); + +export const fetchWithRetries = async ( + url: string, + options: FetchOptions = {}, + retries = 3, +): Promise => { + let errorMessage = ""; + let response = new Response(null, { status: 500 }); + + for (let attempt = 0; attempt < retries; attempt++) { + try { + response = await fetch(url, options); + + if (response.status === 404) { + // no need to retry 404s + return response; + } + + if (!response.ok) { + errorMessage = `HTTP error! Status: ${response.statusText}`; + throw new Error(errorMessage); + } + + return response; + } catch (error) { + console.error(error); + await delay(Math.pow(2, attempt) * 300); // Exponential backoff + } + } + + return response; +}; diff --git a/src/content-indexer/utils/filesystem.ts b/src/content-indexer/utils/filesystem.ts new file mode 100644 index 000000000..fabf3e092 --- /dev/null +++ b/src/content-indexer/utils/filesystem.ts @@ -0,0 +1,127 @@ +import { promises as fs } from "fs"; +import matter from "gray-matter"; +import yaml from "js-yaml"; +import path from "path"; + +import type { DocsYml } from "@/content-indexer/types/docsYaml"; + +/** + * Reads a file from the local filesystem + */ +export const readLocalFile = async ( + filePath: string, +): Promise => { + try { + const content = await fs.readFile(filePath, "utf-8"); + return content; + } catch (error) { + console.warn(`Failed to read file: ${filePath}`, error); + return null; + } +}; + +/** + * Reads and parses a local docs.yml file + */ +export const readLocalDocsYml = async ( + baseDir: string, +): Promise => { + try { + const docsYmlPath = path.join(baseDir, "docs.yml"); + const content = await readLocalFile(docsYmlPath); + + if (!content) { + throw new Error(`Failed to read docs.yml from ${docsYmlPath}`); + } + + const docsYml = yaml.load(content) as DocsYml; + return docsYml; + } catch (error) { + console.error(`Error reading/parsing docs.yml from ${baseDir}:`, error); + return null; + } +}; + +/** + * Reads a local MDX file and parses its frontmatter + */ +export const readLocalMdxFile = async ( + filePath: string, +): Promise<{ + frontmatter: Record; + content: string; +} | null> => { + try { + const fileContent = await readLocalFile(filePath); + + if (!fileContent) { + return null; + } + + const { data, content } = matter(fileContent); + + return { + frontmatter: data, + content, + }; + } catch (error) { + console.warn(`Failed to parse MDX file: ${filePath}`, error); + return null; + } +}; + +/** + * Checks if a file exists + */ +export const fileExists = async (filePath: string): Promise => { + try { + await fs.access(filePath); + return true; + } catch { + return false; + } +}; + +/** + * Reads all files in a directory (non-recursive) + */ +export const readDirectory = async (dirPath: string): Promise => { + try { + const entries = await fs.readdir(dirPath, { withFileTypes: true }); + return entries + .filter((entry) => entry.isFile()) + .map((entry) => path.join(dirPath, entry.name)); + } catch (error) { + console.error(`Error reading directory ${dirPath}:`, error); + return []; + } +}; + +/** + * Recursively reads all files in a directory with a specific extension + */ +export const readDirectoryRecursive = async ( + dirPath: string, + extension?: string, +): Promise => { + const files: string[] = []; + + try { + const entries = await fs.readdir(dirPath, { withFileTypes: true }); + + for (const entry of entries) { + const fullPath = path.join(dirPath, entry.name); + + if (entry.isDirectory()) { + const subFiles = await readDirectoryRecursive(fullPath, extension); + files.push(...subFiles); + } else if (!extension || fullPath.endsWith(extension)) { + files.push(fullPath); + } + } + } catch (error) { + console.error(`Error reading directory recursively ${dirPath}:`, error); + } + + return files; +}; diff --git a/src/content-indexer/utils/github.ts b/src/content-indexer/utils/github.ts new file mode 100644 index 000000000..962743967 --- /dev/null +++ b/src/content-indexer/utils/github.ts @@ -0,0 +1,202 @@ +import { Octokit, RequestError } from "octokit"; + +type GetContentResponse = Awaited< + ReturnType["rest"]["repos"]["getContent"]> +>; +type GetContentData = GetContentResponse["data"]; + +type ContentDirectoryItem = Extract>[number]; + +// ============================================================================ +// Repository Configuration +// ============================================================================ + +export interface RepoConfig { + owner: string; + repo: string; + branch: string; + docsPrefix: string; // "fern" or "docs" - folder where MDX files are stored + stripPathPrefix?: string; // Optional prefix to strip from paths in docs.yml (e.g., "wallets/") +} + +export const DOCS_REPO: RepoConfig = { + owner: "alchemyplatform", + repo: "docs", + branch: "main", + docsPrefix: "fern", +}; + +export const WALLET_REPO: RepoConfig = { + owner: "alchemyplatform", + repo: "aa-sdk", + branch: "main", + docsPrefix: "docs", + stripPathPrefix: "wallets/", // aa-sdk docs.yml uses "wallets/pages/..." but actual files are at "docs/pages/..." +}; + +const octokit = new Octokit({ + auth: process.env.GH_TOKEN, +}); + +function isRequestError(error: unknown): error is RequestError { + return error instanceof RequestError; +} + +/** + * Fetch a single file's text content from GitHub + */ +export async function fetchFileFromGitHub( + filePath: string, + repoConfig: RepoConfig = DOCS_REPO, +): Promise { + try { + const { data } = await octokit.rest.repos.getContent({ + owner: repoConfig.owner, + repo: repoConfig.repo, + path: filePath, + ref: repoConfig.branch, + mediaType: { + format: "raw", + }, + }); + + // When using raw format, data is a string + return data as unknown as string; + } catch (error: unknown) { + if (isRequestError(error) && error.status === 404) { + return null; // File doesn't exist + } + console.error(`Error fetching ${filePath} from ${repoConfig.repo}:`, error); + return null; + } +} + +/** + * Fetch directory contents from GitHub (non-recursive) + */ +export async function fetchGitHubDirectory( + dirPath: string, + repoConfig: RepoConfig = DOCS_REPO, +): Promise { + try { + const { data } = await octokit.rest.repos.getContent({ + owner: repoConfig.owner, + repo: repoConfig.repo, + path: dirPath, + ref: repoConfig.branch, + }); + + // GitHub API returns array for directories, object for files + if (!Array.isArray(data)) { + throw new Error(`Expected directory but got file: ${dirPath}`); + } + + return data; + } catch (error) { + console.error( + `Error fetching directory ${dirPath} from ${repoConfig.repo}:`, + error, + ); + throw error; + } +} + +/** + * Fetch multiple files in parallel with rate limiting + */ +export async function fetchMultipleFiles( + filePaths: string[], + concurrency: number = 10, + repoConfig: RepoConfig = DOCS_REPO, +): Promise> { + const results = new Map(); + + // Process in batches to respect rate limits + // Octokit has built-in rate limiting, but we still batch for efficiency + for (let i = 0; i < filePaths.length; i += concurrency) { + const batch = filePaths.slice(i, i + concurrency); + + const batchResults = await Promise.all( + batch.map(async (path) => { + const content = await fetchFileFromGitHub(path, repoConfig); + return { path, content }; + }), + ); + + for (const { path, content } of batchResults) { + if (content) { + results.set(path, content); + } + } + + // Small delay between batches to be conservative with rate limits + if (i + concurrency < filePaths.length) { + await new Promise((resolve) => setTimeout(resolve, 100)); + } + } + + return results; +} + +/** + * Check if a file exists on GitHub + */ +export async function fileExistsOnGitHub( + filePath: string, + repoConfig: RepoConfig = DOCS_REPO, +): Promise { + try { + await octokit.rest.repos.getContent({ + owner: repoConfig.owner, + repo: repoConfig.repo, + path: filePath, + ref: repoConfig.branch, + }); + return true; + } catch (error: unknown) { + if (isRequestError(error) && error.status === 404) { + return false; + } + // For other errors, log but return false to be safe + console.error(`Error checking if ${filePath} exists:`, error); + return false; + } +} + +/** + * Get file metadata without downloading content + */ +export async function getGitHubFileMetadata( + filePath: string, + repoConfig: RepoConfig = DOCS_REPO, +): Promise<{ sha: string; size: number } | null> { + try { + const { data } = await octokit.rest.repos.getContent({ + owner: repoConfig.owner, + repo: repoConfig.repo, + path: filePath, + ref: repoConfig.branch, + }); + + // Ensure we got a file, not a directory + if (Array.isArray(data)) { + return null; + } + + // Type guard for file object + if ("sha" in data && "size" in data) { + return { + sha: data.sha, + size: data.size, + }; + } + + return null; + } catch (error: unknown) { + if (isRequestError(error) && error.status === 404) { + return null; + } + console.error(`Error fetching metadata for ${filePath}:`, error); + return null; + } +} diff --git a/src/content-indexer/utils/navigation-helpers.ts b/src/content-indexer/utils/navigation-helpers.ts new file mode 100644 index 000000000..967cbf740 --- /dev/null +++ b/src/content-indexer/utils/navigation-helpers.ts @@ -0,0 +1,14 @@ +import type { NavItem } from "@/content-indexer/types/navigation"; + +/** + * Creates breadcrumb-safe navigation item (without populated children). + * Prevents circular references in breadcrumb trails by creating a shallow copy. + */ +export const createBreadcrumbNavItem = ( + title: string, + type: "api-section", +): NavItem => ({ + title, + type, + children: [], +}); diff --git a/src/content-indexer/utils/normalization.ts b/src/content-indexer/utils/normalization.ts new file mode 100644 index 000000000..2da5beb27 --- /dev/null +++ b/src/content-indexer/utils/normalization.ts @@ -0,0 +1,22 @@ +import type { RepoConfig } from "@/content-indexer/utils/github"; + +/** + * Normalizes a frontmatter slug by removing the "docs/" prefix. + * This prefix is used in some legacy frontmatter from the main docs repo + * but should be stripped when generating URL paths. + */ +export const normalizeSlug = (slug: string | undefined): string | undefined => { + if (!slug) return undefined; + return slug.replace(/^docs\//, ""); +}; + +/** + * Normalizes a file path by stripping the repo's configured prefix. + * This ensures the stored filePath can be used directly with the repo's docsPrefix. + */ +export const normalizeFilePath = ( + filePath: string, + repo: RepoConfig, +): string => { + return filePath.replace(repo.stripPathPrefix || "", ""); +}; diff --git a/src/content-indexer/utils/openapi.ts b/src/content-indexer/utils/openapi.ts new file mode 100644 index 000000000..0a8813c93 --- /dev/null +++ b/src/content-indexer/utils/openapi.ts @@ -0,0 +1,149 @@ +import { kebabCase } from "lodash-es"; +import type { OpenAPIV3 } from "openapi-types"; + +import { HTTP_METHODS } from "@/content-indexer/constants/http"; +import type { PathBuilder } from "@/content-indexer/core/path-builder"; + +export interface ExtractedOperation { + operationId: string; + path: string; + method: string; + tag?: string; +} + +/** + * Extracts operationId from an OpenAPI operation object. + * Falls back to summary or generates from method + path if operationId is missing. + */ +const getOperationId = ( + operation: Record, + method: string, + path: string, +): string => { + if ("operationId" in operation && operation.operationId) { + return operation.operationId as string; + } + + if ("summary" in operation && operation.summary) { + return (operation.summary as string).replace(/^\//, ""); + } + + return `${method}_${path}`; +}; + +/** + * Extracts the title for an OpenAPI operation. + * Prefers the summary field, falls back to operationId. + */ +export const getOperationTitle = ( + spec: { paths: Record }, + operationId: string, + operationPath: string, +): string => { + const pathItem = spec.paths[operationPath]; + if (!pathItem || typeof pathItem !== "object") { + return operationId; + } + + // Find the operation with matching operationId + const operation = Object.values(pathItem).find( + (op: unknown) => + typeof op === "object" && + op !== null && + "operationId" in op && + (op as OpenAPIV3.OperationObject).operationId === operationId, + ) as OpenAPIV3.OperationObject | undefined; + + return operation?.summary || operationId; +}; + +/** + * Extracts all operations from an OpenAPI paths object. + * + * Iterates through all paths and HTTP methods, extracting metadata for each operation including: + * - operationId (with fallback logic via getOperationId) + * - path (the URL path from the spec) + * - method (the HTTP method, normalized to uppercase) + * - tag (the first tag from the operation's tags array, used for grouping) + */ +export const extractOpenApiOperations = ( + paths: Record, +): ExtractedOperation[] => { + return Object.entries(paths).flatMap(([path, pathItem]) => { + if (!pathItem || typeof pathItem !== "object") return []; + + return Object.entries(pathItem) + .filter( + ([method, operation]) => + (HTTP_METHODS as readonly string[]).includes(method) && + operation && + typeof operation === "object", + ) + .map(([method, operation]) => { + const op = operation as Record; + // Extract the first tag (Fern uses tags[0] for organization) + const tags = Array.isArray(op.tags) ? op.tags : []; + const tag = tags[0] as string | undefined; + + return { + operationId: getOperationId(op, method, path), + path, + method: method.toUpperCase(), + tag, + }; + }); + }); +}; + +/** + * Builds the final URL path for an OpenAPI operation. + * + * Constructs the path by: + * 1. Optionally adding a tag slug (for grouping operations by tag) + * 2. Adding the operation slug (kebab-cased operationId) + */ +export const buildOperationPath = ( + apiPathBuilder: PathBuilder, + operationId: string, + tag?: string, +): string => { + let pathBuilder = apiPathBuilder; + + // Add tag slug to path if operation has a tag + if (tag) { + const tagSlug = kebabCase(tag); + pathBuilder = apiPathBuilder.apply({ urlSlug: tagSlug }); + } + + // Add operation slug to path + const operationSlug = kebabCase(operationId); + pathBuilder = pathBuilder.apply({ urlSlug: operationSlug }); + + return pathBuilder.get(); +}; + +/** + * Extracts the description from an OpenAPI operation object. + * Falls back to summary if description is not available. + */ +export const getOperationDescription = ( + spec: { paths: Record }, + path: string, + method: string, +): string => { + const pathItem = spec.paths[path]; + if (!pathItem || typeof pathItem !== "object") { + return ""; + } + + const operation = (pathItem as Record)[method]; + if (!operation || typeof operation !== "object") { + return ""; + } + + const operationObj = operation as Record; + const description = operationObj.description as string | undefined; + const summary = operationObj.summary as string | undefined; + + return description || summary || ""; +}; diff --git a/src/content-indexer/utils/openrpc.ts b/src/content-indexer/utils/openrpc.ts new file mode 100644 index 000000000..79c315a0a --- /dev/null +++ b/src/content-indexer/utils/openrpc.ts @@ -0,0 +1,13 @@ +import type { OpenRpcSpec } from "@/content-indexer/types/specs"; + +/** + * Type guard to check if a spec is a valid OpenRPC spec with methods array. + */ +export const isValidOpenRpcSpec = (spec: unknown): spec is OpenRpcSpec => { + return ( + typeof spec === "object" && + spec !== null && + "methods" in spec && + Array.isArray((spec as { methods: unknown }).methods) + ); +}; diff --git a/src/content-indexer/utils/redis.ts b/src/content-indexer/utils/redis.ts new file mode 100644 index 000000000..529d35e3d --- /dev/null +++ b/src/content-indexer/utils/redis.ts @@ -0,0 +1,32 @@ +import { Redis } from "@upstash/redis"; + +/** + * Creates and returns an Upstash Redis client instance. + */ +const getRedisClient = (): Redis => { + const url = process.env.KV_REST_API_URL; + const token = process.env.KV_REST_API_TOKEN; + + if (!url || !token) { + throw new Error( + "Missing required environment variables: KV_REST_API_URL and/or KV_REST_API_TOKEN", + ); + } + + return new Redis({ + url, + token, + }); +}; + +/** + * Singleton instance of Redis client to reuse across requests + */ +let redisClient: Redis | undefined; + +export const getRedis = (): Redis => { + if (!redisClient) { + redisClient = getRedisClient(); + } + return redisClient; +}; diff --git a/src/content-indexer/utils/test-factories.ts b/src/content-indexer/utils/test-factories.ts new file mode 100644 index 000000000..18d0b1f93 --- /dev/null +++ b/src/content-indexer/utils/test-factories.ts @@ -0,0 +1,46 @@ +import type { OpenApiSpec, OpenRpcSpec } from "@/content-indexer/types/specs"; +import type { RepoConfig } from "@/content-indexer/utils/github"; + +/** + * Factory for creating OpenAPI spec with minimal required fields for testing + */ +export const openApiSpecFactory = ( + overrides: Partial = {}, +): OpenApiSpec => ({ + openapi: "3.0.0", + info: { + title: "Test API", + version: "1.0.0", + }, + paths: {}, + ...overrides, +}); + +/** + * Factory for creating OpenRPC spec with minimal required fields for testing + */ +export const openRpcSpecFactory = ( + overrides: Partial = {}, +): OpenRpcSpec => ({ + openrpc: "1.0.0", + info: { + title: "Test API", + version: "1.0.0", + }, + methods: [], + ...overrides, +}); + +/** + * Factory for creating RepoConfig with minimal required fields for testing + */ +export const repoConfigFactory = ( + overrides: Partial = {}, +): RepoConfig => ({ + owner: "test-owner", + repo: "test-repo", + branch: "main", + docsPrefix: "docs/", + stripPathPrefix: "", + ...overrides, +}); diff --git a/src/content-indexer/utils/truncate-record.ts b/src/content-indexer/utils/truncate-record.ts new file mode 100644 index 000000000..57cbd0df9 --- /dev/null +++ b/src/content-indexer/utils/truncate-record.ts @@ -0,0 +1,70 @@ +import type { AlgoliaRecord } from "@/content-indexer/types/algolia"; + +const MAX_RECORD_BYTES = 100_000; // Algolia imposes a 100KB limit on each record +const BUFFER_BYTES = 1_000; +const MAX_ITERATIONS = 5; + +/** + * Truncate record content to ensure entire JSON payload fits within Algolia limit. + */ +export const truncateRecord = (record: AlgoliaRecord): AlgoliaRecord => { + const fullRecordJson = JSON.stringify(record); + const recordBytes = Buffer.byteLength(fullRecordJson, "utf8"); + + if (recordBytes <= MAX_RECORD_BYTES) { + return record; // Record is fine as-is - should be the case for over 99% of records + } + + // Calculate overhead (everything except content field) + const recordWithoutContent = { ...record, content: "" }; + const overheadBytes = Buffer.byteLength( + JSON.stringify(recordWithoutContent), + "utf8", + ); + + console.warn( + `⚠️ Record "${record.title}" (${record.path}) exceeds ${MAX_RECORD_BYTES} bytes\n`, + ` Total: ${recordBytes} bytes\n`, + ` Content: ${Buffer.byteLength(record.content, "utf8")} bytes\n`, + ` Overhead (all non-content data): ${overheadBytes} bytes`, + ); + + if (overheadBytes > MAX_RECORD_BYTES - 1000) { + throw new Error( + `Record overhead (${overheadBytes} bytes) is too large! Something is wrong with the record data.`, + ); + } + + // Iteratively truncate content while measuring full JSON record size + // This accounts for JSON escaping overhead (quotes, backslashes, etc.) + let truncatedContent = record.content; + let truncatedRecord: AlgoliaRecord = { ...record, content: truncatedContent }; + let currentBytes = recordBytes; + let iterations = 0; + + while (currentBytes > MAX_RECORD_BYTES && iterations < MAX_ITERATIONS) { + // Calculate reduction ratio to reach target size + const reductionRatio = (MAX_RECORD_BYTES - BUFFER_BYTES) / currentBytes; + + // Use code point-aware truncation to avoid splitting multi-byte UTF-8 characters (emoji, etc.) + const codePoints = Array.from(truncatedContent); + const targetCodePoints = Math.floor(codePoints.length * reductionRatio); + truncatedContent = codePoints.slice(0, targetCodePoints).join("") + "..."; + + truncatedRecord = { ...record, content: truncatedContent }; + currentBytes = Buffer.byteLength(JSON.stringify(truncatedRecord), "utf8"); + iterations++; + } + + if (currentBytes > MAX_RECORD_BYTES) { + throw new Error( + `Failed to truncate record after ${MAX_ITERATIONS} iterations. Final size: ${currentBytes} bytes`, + ); + } + + console.warn( + ` ✓ Truncated to ${currentBytes} bytes (${truncatedContent.length} chars) in ${iterations} iteration${iterations === 1 ? "" : "s"}\n`, + ); + + return truncatedRecord; +}; diff --git a/src/content-indexer/visitors/__tests__/index.test.ts b/src/content-indexer/visitors/__tests__/index.test.ts new file mode 100644 index 000000000..c2fd18e68 --- /dev/null +++ b/src/content-indexer/visitors/__tests__/index.test.ts @@ -0,0 +1,120 @@ +import { describe, expect, test } from "vitest"; + +import { ProcessingContext } from "@/content-indexer/collectors/processing-context"; +import { ContentCache } from "@/content-indexer/core/content-cache"; +import { PathBuilder } from "@/content-indexer/core/path-builder"; +import { DOCS_REPO } from "@/content-indexer/utils/github"; +import { openApiSpecFactory } from "@/content-indexer/utils/test-factories"; + +import { visitNavigationItem } from "../index"; + +describe("visitNavigationItem dispatcher", () => { + test("should route page config to visitPage", () => { + const context = new ProcessingContext(); + const result = visitNavigationItem({ + item: { + page: "Quickstart", + path: "fern/guides/quickstart.mdx", + }, + parentPath: PathBuilder.init("guides"), + tab: "guides", + repo: DOCS_REPO, + contentCache: new ContentCache(), + context, + navigationAncestors: [], + }); + + expect(result.navItem).toBeDefined(); + expect(result.navItem).toHaveProperty("type", "page"); + }); + + test("should route link config to visitLink", () => { + const context = new ProcessingContext(); + const result = visitNavigationItem({ + item: { + link: "External", + href: "https://example.com", + }, + parentPath: PathBuilder.init(), + tab: "guides", + repo: DOCS_REPO, + contentCache: new ContentCache(), + context, + navigationAncestors: [], + }); + + expect(result.navItem).toBeDefined(); + expect(result.navItem).toHaveProperty("type", "link"); + }); + + test("should route section config to visitSection", () => { + const context = new ProcessingContext(); + const result = visitNavigationItem({ + item: { + section: "Getting Started", + contents: [ + { + page: "Quickstart", + path: "fern/guides/quickstart.mdx", + }, + ], + }, + parentPath: PathBuilder.init("guides"), + tab: "guides", + repo: DOCS_REPO, + contentCache: new ContentCache(), + context, + navigationAncestors: [], + }); + + expect(result.navItem).toBeDefined(); + expect(result.navItem).toHaveProperty("type", "section"); + }); + + test("should skip changelog config", () => { + const context = new ProcessingContext(); + const result = visitNavigationItem({ + item: { + changelog: "CHANGELOG.md", + }, + parentPath: PathBuilder.init(), + tab: "guides", + repo: DOCS_REPO, + contentCache: new ContentCache(), + context, + navigationAncestors: [], + }); + + expect(result.indexEntries).toEqual({}); + expect(result.navItem).toBeUndefined(); + }); + + test("should handle API config routing", () => { + const context = new ProcessingContext(); + const cache = new ContentCache(); + + // Add a mock spec to cache + cache.setSpec("ethereum-api", { + specType: "openapi", + spec: openApiSpecFactory(), + specUrl: "https://example.com/spec.json", + }); + + const result = visitNavigationItem({ + item: { + api: "Ethereum API", + "api-name": "ethereum-api", + }, + parentPath: PathBuilder.init("reference"), + tab: "reference", + repo: DOCS_REPO, + contentCache: cache, + context, + navigationAncestors: [], + }); + + // Should return a result (even if empty due to no operations in spec) + expect(result).toBeDefined(); + expect(result.indexEntries).toBeDefined(); + }); +}); diff --git a/src/content-indexer/visitors/__tests__/visit-api-reference.test.ts b/src/content-indexer/visitors/__tests__/visit-api-reference.test.ts new file mode 100644 index 000000000..a6c820108 --- /dev/null +++ b/src/content-indexer/visitors/__tests__/visit-api-reference.test.ts @@ -0,0 +1,260 @@ +import { describe, expect, test } from "vitest"; + +import { ProcessingContext } from "@/content-indexer/collectors/processing-context"; +import { ContentCache } from "@/content-indexer/core/content-cache"; +import { PathBuilder } from "@/content-indexer/core/path-builder"; +import { DOCS_REPO } from "@/content-indexer/utils/github"; +import { + openApiSpecFactory, + openRpcSpecFactory, +} from "@/content-indexer/utils/test-factories"; + +import { visitApiReference } from "../visit-api-reference"; + +describe("visitApiReference", () => { + test("should return empty result if spec not in cache", () => { + const context = new ProcessingContext(); + const cache = new ContentCache(); + + const result = visitApiReference({ + item: { + api: "Ethereum API", + "api-name": "ethereum-api", + }, + parentPath: PathBuilder.init("reference"), + tab: "reference", + repo: DOCS_REPO, + contentCache: cache, + context, + navigationAncestors: [], + }); + + expect(result.indexEntries).toEqual({}); + expect(result.navItem).toBeUndefined(); + }); + + test("should process OpenAPI spec", () => { + const context = new ProcessingContext(); + const cache = new ContentCache(); + + cache.setSpec("ethereum-api", { + specType: "openapi", + spec: openApiSpecFactory({ + paths: { + "/balance": { + get: { + operationId: "getBalance", + summary: "Get Balance", + description: "Get the balance of an address", + responses: { "200": { description: "Success" } }, + }, + }, + }, + }), + specUrl: "https://example.com/spec.json", + }); + + const result = visitApiReference({ + item: { + api: "Ethereum API", + "api-name": "ethereum-api", + }, + parentPath: PathBuilder.init("reference"), + tab: "reference", + repo: DOCS_REPO, + contentCache: cache, + context, + navigationAncestors: [], + }); + + expect(Object.keys(result.indexEntries).length).toBeGreaterThan(0); + expect(result.navItem).toBeDefined(); + }); + + test("should process OpenRPC spec", () => { + const context = new ProcessingContext(); + const cache = new ContentCache(); + + cache.setSpec("solana-api", { + specType: "openrpc", + spec: openRpcSpecFactory({ + info: { title: "Solana API", version: "1.0.0" }, + methods: [ + { + name: "getAsset", + description: "Get asset information", + params: [], + result: { name: "result", schema: {} }, + }, + ], + }), + specUrl: "https://example.com/rpc-spec.json", + }); + + const result = visitApiReference({ + item: { + api: "Solana API", + "api-name": "solana-api", + }, + parentPath: PathBuilder.init("reference"), + tab: "reference", + repo: DOCS_REPO, + contentCache: cache, + context, + navigationAncestors: [], + }); + + expect(Object.keys(result.indexEntries).length).toBeGreaterThan(0); + expect(result.navItem).toBeDefined(); + }); + + test("should use custom slug for API", () => { + const context = new ProcessingContext(); + const cache = new ContentCache(); + + cache.setSpec("ethereum-api", { + specType: "openapi", + spec: openApiSpecFactory({ + paths: { + "/balance": { + get: { + operationId: "getBalance", + responses: { "200": { description: "Success" } }, + }, + }, + }, + }), + specUrl: "https://example.com/spec.json", + }); + + const result = visitApiReference({ + item: { + api: "Ethereum API", + "api-name": "ethereum-api", + slug: "eth-api", + }, + parentPath: PathBuilder.init("reference"), + tab: "reference", + repo: DOCS_REPO, + contentCache: cache, + context, + navigationAncestors: [], + }); + + const firstPath = Object.keys(result.indexEntries)[0]; + expect(firstPath).toContain("eth-api"); + }); + + test("should skip slug if skip-slug is true", () => { + const context = new ProcessingContext(); + const cache = new ContentCache(); + + cache.setSpec("ethereum-api", { + specType: "openapi", + spec: openApiSpecFactory({ + paths: { + "/balance": { + get: { + operationId: "getBalance", + responses: { "200": { description: "Success" } }, + }, + }, + }, + }), + specUrl: "https://example.com/spec.json", + }); + + const result = visitApiReference({ + item: { + api: "Ethereum API", + "api-name": "ethereum-api", + "skip-slug": true, + }, + parentPath: PathBuilder.init("reference"), + tab: "reference", + repo: DOCS_REPO, + contentCache: cache, + context, + navigationAncestors: [], + }); + + const firstPath = Object.keys(result.indexEntries)[0]; + // Should not include "ethereum-api" segment + expect(firstPath).toBe("reference/get-balance"); + }); + + test("should return no nav for hidden API", () => { + const context = new ProcessingContext(); + const cache = new ContentCache(); + + cache.setSpec("ethereum-api", { + specType: "openapi", + spec: openApiSpecFactory({ + paths: { + "/balance": { + get: { + operationId: "getBalance", + responses: { "200": { description: "Success" } }, + }, + }, + }, + }), + specUrl: "https://example.com/spec.json", + }); + + const result = visitApiReference({ + item: { + api: "Ethereum API", + "api-name": "ethereum-api", + hidden: true, + }, + parentPath: PathBuilder.init("reference"), + tab: "reference", + repo: DOCS_REPO, + contentCache: cache, + context, + navigationAncestors: [], + }); + + expect(result.navItem).toBeUndefined(); + expect(Object.keys(result.indexEntries).length).toBeGreaterThan(0); // Index still created + }); + + test("should flatten API structure if flattened is true", () => { + const context = new ProcessingContext(); + const cache = new ContentCache(); + + cache.setSpec("ethereum-api", { + specType: "openapi", + spec: openApiSpecFactory({ + paths: { + "/balance": { + get: { + operationId: "getBalance", + tags: ["ethereum"], + responses: { "200": { description: "Success" } }, + }, + }, + }, + }), + specUrl: "https://example.com/spec.json", + }); + + const result = visitApiReference({ + item: { + api: "Ethereum API", + "api-name": "ethereum-api", + flattened: true, + }, + parentPath: PathBuilder.init("reference"), + tab: "reference", + repo: DOCS_REPO, + contentCache: cache, + context, + navigationAncestors: [], + }); + + // Should return array instead of wrapped in API section + expect(Array.isArray(result.navItem)).toBe(true); + }); +}); diff --git a/src/content-indexer/visitors/__tests__/visit-link.test.ts b/src/content-indexer/visitors/__tests__/visit-link.test.ts new file mode 100644 index 000000000..2a6b5222c --- /dev/null +++ b/src/content-indexer/visitors/__tests__/visit-link.test.ts @@ -0,0 +1,76 @@ +import { describe, expect, test } from "vitest"; + +import { ProcessingContext } from "@/content-indexer/collectors/processing-context"; +import { ContentCache } from "@/content-indexer/core/content-cache"; +import { PathBuilder } from "@/content-indexer/core/path-builder"; +import { DOCS_REPO } from "@/content-indexer/utils/github"; + +import { visitLink } from "../visit-link"; + +describe("visitLink", () => { + test("should create link nav item", () => { + const context = new ProcessingContext(); + const result = visitLink({ + item: { + link: "External Resource", + href: "https://example.com/docs", + }, + parentPath: PathBuilder.init("guides"), + tab: "guides", + repo: DOCS_REPO, + contentCache: new ContentCache(), + context, + navigationAncestors: [], + }); + + expect(result.indexEntries).toEqual({}); + expect(result.navItem).toEqual({ + title: "External Resource", + href: "https://example.com/docs", + type: "link", + }); + }); + + test("should not add path index entries for links", () => { + const context = new ProcessingContext(); + const result = visitLink({ + item: { + link: "GitHub", + href: "https://github.com", + }, + parentPath: PathBuilder.init(), + tab: "reference", + repo: DOCS_REPO, + contentCache: new ContentCache(), + context, + navigationAncestors: [], + }); + + expect(result.indexEntries).toEqual({}); + expect(Object.keys(result.indexEntries)).toHaveLength(0); + }); + + test("should preserve exact link title and href", () => { + const context = new ProcessingContext(); + const result = visitLink({ + item: { + link: "API Reference (External)", + href: "https://api.example.com/v2/docs#section", + }, + parentPath: PathBuilder.init(), + tab: "reference", + repo: DOCS_REPO, + contentCache: new ContentCache(), + context, + navigationAncestors: [], + }); + + expect(result.navItem).toBeDefined(); + if (result.navItem && "href" in result.navItem) { + expect(result.navItem.title).toBe("API Reference (External)"); + expect(result.navItem.href).toBe( + "https://api.example.com/v2/docs#section", + ); + } + }); +}); diff --git a/src/content-indexer/visitors/__tests__/visit-page.test.ts b/src/content-indexer/visitors/__tests__/visit-page.test.ts new file mode 100644 index 000000000..acc6d3158 --- /dev/null +++ b/src/content-indexer/visitors/__tests__/visit-page.test.ts @@ -0,0 +1,253 @@ +import { describe, expect, test } from "vitest"; + +import { ProcessingContext } from "@/content-indexer/collectors/processing-context"; +import { ContentCache } from "@/content-indexer/core/content-cache"; +import { PathBuilder } from "@/content-indexer/core/path-builder"; +import { DOCS_REPO } from "@/content-indexer/utils/github"; + +import { visitPage } from "../visit-page"; + +describe("visitPage", () => { + test("should create path index entry for page", () => { + const context = new ProcessingContext(); + const cache = new ContentCache(); + + const result = visitPage({ + item: { + page: "Quickstart", + path: "fern/guides/quickstart.mdx", + }, + parentPath: PathBuilder.init("guides"), + tab: "guides", + repo: DOCS_REPO, + contentCache: cache, + context, + navigationAncestors: [], + }); + + expect(result.indexEntries["guides/quickstart"]).toEqual({ + type: "mdx", + filePath: "fern/guides/quickstart.mdx", + source: "docs-yml", + tab: "guides", + }); + }); + + test("should create nav item for visible page", () => { + const context = new ProcessingContext(); + const cache = new ContentCache(); + + const result = visitPage({ + item: { + page: "Quickstart", + path: "fern/guides/quickstart.mdx", + }, + parentPath: PathBuilder.init("guides"), + tab: "guides", + repo: DOCS_REPO, + contentCache: cache, + context, + navigationAncestors: [], + }); + + expect(result.navItem).toEqual({ + title: "Quickstart", + path: "/guides/quickstart", + type: "page", + }); + }); + + test("should skip nav item for hidden page", () => { + const context = new ProcessingContext(); + const cache = new ContentCache(); + + const result = visitPage({ + item: { + page: "Hidden Page", + path: "fern/guides/hidden.mdx", + hidden: true, + }, + parentPath: PathBuilder.init("guides"), + tab: "guides", + repo: DOCS_REPO, + contentCache: cache, + context, + navigationAncestors: [], + }); + + expect(result.navItem).toBeUndefined(); + expect(result.indexEntries["guides/hidden-page"]).toBeDefined(); // Index still created + }); + + test("should use custom slug if provided", () => { + const context = new ProcessingContext(); + const cache = new ContentCache(); + + const result = visitPage({ + item: { + page: "Quickstart", + path: "fern/guides/quickstart.mdx", + slug: "custom-slug", + }, + parentPath: PathBuilder.init("guides"), + tab: "guides", + repo: DOCS_REPO, + contentCache: cache, + context, + navigationAncestors: [], + }); + + expect(result.indexEntries["guides/custom-slug"]).toBeDefined(); + expect(result.navItem).toBeDefined(); + if ( + result.navItem && + !Array.isArray(result.navItem) && + "path" in result.navItem + ) { + expect(result.navItem.path).toBe("/guides/custom-slug"); + } + }); + + test("should use frontmatter slug if available", () => { + const context = new ProcessingContext(); + const cache = new ContentCache(); + + cache.setMdxContent("fern/guides/quickstart.mdx", { + frontmatter: { + slug: "docs/custom/frontmatter/path", + title: "Custom Title", + }, + content: "Content", + }); + + const result = visitPage({ + item: { + page: "Quickstart", + path: "fern/guides/quickstart.mdx", + }, + parentPath: PathBuilder.init("guides"), + tab: "guides", + repo: DOCS_REPO, + contentCache: cache, + context, + navigationAncestors: [], + }); + + expect(result.indexEntries["custom/frontmatter/path"]).toBeDefined(); + expect(result.indexEntries["custom/frontmatter/path"].source).toBe( + "frontmatter", + ); + }); + + test("should add Algolia record if content cached and not hidden", () => { + const context = new ProcessingContext(); + const cache = new ContentCache(); + + cache.setMdxContent("fern/guides/quickstart.mdx", { + frontmatter: { + title: "Quick Start Guide", + }, + content: "This is the content of the quickstart guide", + }); + + visitPage({ + item: { + page: "Quickstart", + path: "fern/guides/quickstart.mdx", + }, + parentPath: PathBuilder.init("guides"), + tab: "guides", + repo: DOCS_REPO, + contentCache: cache, + context, + navigationAncestors: [ + { + title: "Guides", + path: "/guides", + type: "section", + children: [], + }, + ], + }); + + const results = context.getResults(); + expect(results.algoliaRecords).toHaveLength(1); + expect(results.algoliaRecords[0].title).toBe("Quick Start Guide"); + expect(results.algoliaRecords[0].pageType).toBe("Guide"); + expect(results.algoliaRecords[0].breadcrumbs).toEqual(["Guides"]); + }); + + test("should fallback to page name for Algolia title if no frontmatter title", () => { + const context = new ProcessingContext(); + const cache = new ContentCache(); + + cache.setMdxContent("fern/guides/quickstart.mdx", { + frontmatter: {}, + content: "Content without title", + }); + + visitPage({ + item: { + page: "Quickstart Page Name", + path: "fern/guides/quickstart.mdx", + }, + parentPath: PathBuilder.init("guides"), + tab: "guides", + repo: DOCS_REPO, + contentCache: cache, + context, + navigationAncestors: [], + }); + + const results = context.getResults(); + expect(results.algoliaRecords[0].title).toBe("Quickstart Page Name"); + }); + + test("should not add Algolia record if content not cached", () => { + const context = new ProcessingContext(); + const cache = new ContentCache(); + + visitPage({ + item: { + page: "Quickstart", + path: "fern/guides/quickstart.mdx", + }, + parentPath: PathBuilder.init("guides"), + tab: "guides", + repo: DOCS_REPO, + contentCache: cache, + context, + navigationAncestors: [], + }); + + const results = context.getResults(); + expect(results.algoliaRecords).toHaveLength(0); + }); + + test("should not add Algolia record if page is hidden", () => { + const context = new ProcessingContext(); + const cache = new ContentCache(); + + cache.setMdxContent("fern/guides/hidden.mdx", { + frontmatter: { title: "Hidden Page" }, + content: "Secret content", + }); + + visitPage({ + item: { + page: "Hidden", + path: "fern/guides/hidden.mdx", + hidden: true, + }, + parentPath: PathBuilder.init("guides"), + tab: "guides", + repo: DOCS_REPO, + contentCache: cache, + context, + navigationAncestors: [], + }); + + const results = context.getResults(); + expect(results.algoliaRecords).toHaveLength(0); + }); +}); diff --git a/src/content-indexer/visitors/__tests__/visit-section.test.ts b/src/content-indexer/visitors/__tests__/visit-section.test.ts new file mode 100644 index 000000000..db3ec3b1a --- /dev/null +++ b/src/content-indexer/visitors/__tests__/visit-section.test.ts @@ -0,0 +1,337 @@ +import { describe, expect, test } from "vitest"; + +import { ProcessingContext } from "@/content-indexer/collectors/processing-context"; +import { ContentCache } from "@/content-indexer/core/content-cache"; +import { PathBuilder } from "@/content-indexer/core/path-builder"; +import { DOCS_REPO } from "@/content-indexer/utils/github"; + +import { visitNavigationItem } from "../index"; +import { visitSection } from "../visit-section"; + +describe("visitSection", () => { + test("should create section nav item with children", () => { + const context = new ProcessingContext(); + const cache = new ContentCache(); + + const result = visitSection( + { + item: { + section: "Getting Started", + contents: [ + { + page: "Quickstart", + path: "fern/guides/quickstart.mdx", + }, + ], + }, + parentPath: PathBuilder.init("guides"), + tab: "guides", + repo: DOCS_REPO, + contentCache: cache, + context, + navigationAncestors: [], + }, + visitNavigationItem, + ); + + expect(result.navItem).toBeDefined(); + expect(result.navItem).toHaveProperty("type", "section"); + expect(result.navItem).toHaveProperty("title", "Getting Started"); + if ( + result.navItem && + !Array.isArray(result.navItem) && + (result.navItem.type === "section" || + result.navItem.type === "api-section") + ) { + expect(result.navItem.children).toHaveLength(1); + } + }); + + test("should process all child items", () => { + const context = new ProcessingContext(); + const cache = new ContentCache(); + + const result = visitSection( + { + item: { + section: "API Reference", + contents: [ + { + page: "Overview", + path: "fern/reference/overview.mdx", + }, + { + page: "Authentication", + path: "fern/reference/auth.mdx", + }, + ], + }, + parentPath: PathBuilder.init("reference"), + tab: "reference", + repo: DOCS_REPO, + contentCache: cache, + context, + navigationAncestors: [], + }, + visitNavigationItem, + ); + + if ( + result.navItem && + !Array.isArray(result.navItem) && + (result.navItem.type === "section" || + result.navItem.type === "api-section") + ) { + expect(result.navItem.children).toHaveLength(2); + } + expect(Object.keys(result.indexEntries)).toHaveLength(2); + }); + + test("should handle section with overview page", () => { + const context = new ProcessingContext(); + const cache = new ContentCache(); + + cache.setMdxContent("fern/guides/overview.mdx", { + frontmatter: { title: "Overview" }, + content: "Overview content", + }); + + const result = visitSection( + { + item: { + section: "Getting Started", + path: "fern/guides/overview.mdx", + contents: [ + { + page: "Quickstart", + path: "fern/guides/quickstart.mdx", + }, + ], + }, + parentPath: PathBuilder.init("guides"), + tab: "guides", + repo: DOCS_REPO, + contentCache: cache, + context, + navigationAncestors: [], + }, + visitNavigationItem, + ); + + // Should have overview path index entry + expect(result.indexEntries["guides/getting-started"]).toBeDefined(); + expect(result.navItem).toHaveProperty("path"); + }); + + test("should use custom slug if provided", () => { + const context = new ProcessingContext(); + const cache = new ContentCache(); + + const result = visitSection( + { + item: { + section: "Getting Started", + slug: "custom-section", + contents: [ + { + page: "Page", + path: "fern/guides/page.mdx", + }, + ], + }, + parentPath: PathBuilder.init("guides"), + tab: "guides", + repo: DOCS_REPO, + contentCache: cache, + context, + navigationAncestors: [], + }, + visitNavigationItem, + ); + + expect(result.indexEntries["guides/custom-section/page"]).toBeDefined(); + }); + + test("should skip slug if skip-slug is true", () => { + const context = new ProcessingContext(); + const cache = new ContentCache(); + + const result = visitSection( + { + item: { + section: "Getting Started", + "skip-slug": true, + contents: [ + { + page: "Page", + path: "fern/guides/page.mdx", + }, + ], + }, + parentPath: PathBuilder.init("guides"), + tab: "guides", + repo: DOCS_REPO, + contentCache: cache, + context, + navigationAncestors: [], + }, + visitNavigationItem, + ); + + // Page should be directly under guides, not guides/getting-started + expect(result.indexEntries["guides/page"]).toBeDefined(); + }); + + test("should handle hidden section", () => { + const context = new ProcessingContext(); + const cache = new ContentCache(); + + const result = visitSection( + { + item: { + section: "Hidden Section", + hidden: true, + contents: [ + { + page: "Page", + path: "fern/guides/page.mdx", + }, + ], + }, + parentPath: PathBuilder.init("guides"), + tab: "guides", + repo: DOCS_REPO, + contentCache: cache, + context, + navigationAncestors: [], + }, + visitNavigationItem, + ); + + expect(result.navItem).toBeUndefined(); + // Index entries should still be created + expect(Object.keys(result.indexEntries).length).toBeGreaterThan(0); + }); + + test("should recursively process nested sections", () => { + const context = new ProcessingContext(); + const cache = new ContentCache(); + + const result = visitSection( + { + item: { + section: "Level 1", + contents: [ + { + section: "Level 2", + contents: [ + { + page: "Deep Page", + path: "fern/guides/deep.mdx", + }, + ], + }, + ], + }, + parentPath: PathBuilder.init("guides"), + tab: "guides", + repo: DOCS_REPO, + contentCache: cache, + context, + navigationAncestors: [], + }, + visitNavigationItem, + ); + + // Should have deeply nested path + expect( + result.indexEntries["guides/level-1/level-2/deep-page"], + ).toBeDefined(); + }); + + test("should add section to breadcrumbs for children", () => { + const context = new ProcessingContext(); + const cache = new ContentCache(); + + cache.setMdxContent("fern/guides/quickstart.mdx", { + frontmatter: { title: "Quickstart" }, + content: "Content", + }); + + visitSection( + { + item: { + section: "Getting Started", + contents: [ + { + page: "Quickstart", + path: "fern/guides/quickstart.mdx", + }, + ], + }, + parentPath: PathBuilder.init("guides"), + tab: "guides", + repo: DOCS_REPO, + contentCache: cache, + context, + navigationAncestors: [], + }, + visitNavigationItem, + ); + + const results = context.getResults(); + // Child page should have "Getting Started" in breadcrumbs + expect(results.algoliaRecords[0].breadcrumbs).toContain("Getting Started"); + }); + + test("should handle section with mix of pages and subsections", () => { + const context = new ProcessingContext(); + const cache = new ContentCache(); + + const result = visitSection( + { + item: { + section: "Documentation", + contents: [ + { + page: "Overview", + path: "fern/docs/overview.mdx", + }, + { + section: "API Reference", + contents: [ + { + page: "Authentication", + path: "fern/docs/auth.mdx", + }, + ], + }, + { + link: "External", + href: "https://example.com", + }, + ], + }, + parentPath: PathBuilder.init("docs"), + tab: "guides", + repo: DOCS_REPO, + contentCache: cache, + context, + navigationAncestors: [], + }, + visitNavigationItem, + ); + + if ( + result.navItem && + !Array.isArray(result.navItem) && + (result.navItem.type === "section" || + result.navItem.type === "api-section") + ) { + expect(result.navItem.children).toHaveLength(3); // page + section + link + expect(result.navItem.children[0].type).toBe("page"); + expect(result.navItem.children[1].type).toBe("section"); + expect(result.navItem.children[2].type).toBe("link"); + } + }); +}); diff --git a/src/content-indexer/visitors/index.ts b/src/content-indexer/visitors/index.ts new file mode 100644 index 000000000..7b4ef5de7 --- /dev/null +++ b/src/content-indexer/visitors/index.ts @@ -0,0 +1,76 @@ +import type { ProcessingContext } from "@/content-indexer/collectors/processing-context"; +import type { ContentCache } from "@/content-indexer/core/content-cache"; +import type { PathBuilder } from "@/content-indexer/core/path-builder"; +import { + isApiConfig, + isChangelogConfig, + isLinkConfig, + isPageConfig, + isSectionConfig, + type NavigationItem, +} from "@/content-indexer/types/docsYaml"; +import type { NavItem } from "@/content-indexer/types/navigation"; +import type { PathIndex } from "@/content-indexer/types/pathIndex"; +import type { RepoConfig } from "@/content-indexer/utils/github"; + +import { visitApiReference } from "./visit-api-reference"; +import { visitLink } from "./visit-link"; +import { visitPage } from "./visit-page"; +import { visitSection } from "./visit-section"; + +export interface VisitorConfigBase { + parentPath: PathBuilder; + tab: string; + repo: RepoConfig; + contentCache: ContentCache; + context: ProcessingContext; + navigationAncestors: NavItem[]; +} + +export interface VisitorConfig extends VisitorConfigBase { + item: NavigationItem; +} + +export interface VisitorResult { + indexEntries: PathIndex; + navItem?: NavItem | NavItem[]; +} + +/** + * Dispatcher that routes navigation items to the appropriate visitor. + * + * Uses type guards to determine item type and delegates to specialized visitors: + * - Pages → visitPage + * - Sections → visitSection (recursive) + * - API references → visitApiReference + * - Links → visitLink + * - Changelog → skip (no processing needed) + */ +export const visitNavigationItem = (config: VisitorConfig): VisitorResult => { + const { item } = config; + + // Skip changelog items + if (isChangelogConfig(item)) { + return { indexEntries: {}, navItem: undefined }; + } + + // Delegate to appropriate visitor based on item type + if (isLinkConfig(item)) { + return visitLink({ ...config, item }); + } + + if (isPageConfig(item)) { + return visitPage({ ...config, item }); + } + + if (isSectionConfig(item)) { + return visitSection({ ...config, item }, visitNavigationItem); + } + + if (isApiConfig(item)) { + return visitApiReference({ ...config, item }); + } + + // Unknown item type - skip + return { indexEntries: {}, navItem: undefined }; +}; diff --git a/src/content-indexer/visitors/processors/__tests__/process-openapi.test.ts b/src/content-indexer/visitors/processors/__tests__/process-openapi.test.ts new file mode 100644 index 000000000..b00d3c6ae --- /dev/null +++ b/src/content-indexer/visitors/processors/__tests__/process-openapi.test.ts @@ -0,0 +1,329 @@ +import { describe, expect, test } from "vitest"; + +import { ProcessingContext } from "@/content-indexer/collectors/processing-context"; +import { ContentCache } from "@/content-indexer/core/content-cache"; +import { PathBuilder } from "@/content-indexer/core/path-builder"; +import { DOCS_REPO } from "@/content-indexer/utils/github"; +import { openApiSpecFactory } from "@/content-indexer/utils/test-factories"; + +import { processOpenApiSpec } from "../process-openapi"; + +describe("processOpenApiSpec", () => { + test("should process operations and create index entries", () => { + const context = new ProcessingContext(); + + const result = processOpenApiSpec({ + spec: openApiSpecFactory({ + paths: { + "/balance": { + get: { + operationId: "getBalance", + summary: "Get Balance", + description: "Get the balance of an address", + responses: { "200": { description: "Success" } }, + }, + }, + "/transfer": { + post: { + operationId: "transfer", + summary: "Transfer", + responses: { "200": { description: "Success" } }, + }, + }, + }, + }), + specUrl: "https://example.com/spec.json", + visitorConfig: { + item: { api: "Ethereum API", "api-name": "ethereum-api" }, + parentPath: PathBuilder.init(), + tab: "reference", + repo: DOCS_REPO, + contentCache: new ContentCache(), + context, + navigationAncestors: [], + }, + apiPathBuilder: PathBuilder.init("reference/ethereum"), + apiTitle: "Ethereum API", + isHidden: false, + isFlattened: false, + }); + + expect(Object.keys(result.indexEntries)).toHaveLength(2); + expect(result.indexEntries["reference/ethereum/get-balance"]).toBeDefined(); + expect(result.indexEntries["reference/ethereum/transfer"]).toBeDefined(); + }); + + test("should group operations by tag", () => { + const context = new ProcessingContext(); + + const result = processOpenApiSpec({ + spec: openApiSpecFactory({ + paths: { + "/users": { + get: { + operationId: "getUsers", + tags: ["users"], + responses: { "200": { description: "Success" } }, + }, + }, + "/posts": { + get: { + operationId: "getPosts", + tags: ["posts"], + responses: { "200": { description: "Success" } }, + }, + }, + }, + }), + specUrl: "https://example.com/spec.json", + visitorConfig: { + item: { api: "API", "api-name": "api" }, + parentPath: PathBuilder.init(), + tab: "reference", + repo: DOCS_REPO, + contentCache: new ContentCache(), + context, + navigationAncestors: [], + }, + apiPathBuilder: PathBuilder.init("reference/api"), + apiTitle: "API", + isHidden: false, + isFlattened: false, + }); + + if ( + result.navItem && + !Array.isArray(result.navItem) && + (result.navItem.type === "section" || + result.navItem.type === "api-section") + ) { + expect(result.navItem.children).toHaveLength(2); // 2 tag sections + const firstChild = result.navItem.children[0]; + expect(firstChild.type).toBe("section"); + if (firstChild.type === "section" || firstChild.type === "api-section") { + expect(firstChild.title).toMatch(/users|posts/); + } + } + }); + + test("should use summary as operation title", () => { + const context = new ProcessingContext(); + + processOpenApiSpec({ + spec: openApiSpecFactory({ + paths: { + "/balance": { + get: { + operationId: "getBalance", + summary: "Get Account Balance", + responses: { "200": { description: "Success" } }, + }, + }, + }, + }), + specUrl: "https://example.com/spec.json", + visitorConfig: { + item: { api: "API", "api-name": "api" }, + parentPath: PathBuilder.init(), + tab: "reference", + repo: DOCS_REPO, + contentCache: new ContentCache(), + context, + navigationAncestors: [], + }, + apiPathBuilder: PathBuilder.init("reference/api"), + apiTitle: "API", + isHidden: false, + isFlattened: false, + }); + + const results = context.getResults(); + expect(results.algoliaRecords[0].title).toBe("Get Account Balance"); + }); + + test("should fallback to operationId for title if no summary", () => { + const context = new ProcessingContext(); + + processOpenApiSpec({ + spec: openApiSpecFactory({ + paths: { + "/balance": { + get: { + operationId: "getBalance", + responses: { "200": { description: "Success" } }, + }, + }, + }, + }), + specUrl: "https://example.com/spec.json", + visitorConfig: { + item: { api: "API", "api-name": "api" }, + parentPath: PathBuilder.init(), + tab: "reference", + repo: DOCS_REPO, + contentCache: new ContentCache(), + context, + navigationAncestors: [], + }, + apiPathBuilder: PathBuilder.init("reference/api"), + apiTitle: "API", + isHidden: false, + isFlattened: false, + }); + + const results = context.getResults(); + expect(results.algoliaRecords[0].title).toBe("getBalance"); + }); + + test("should include tag in path if tag exists", () => { + const context = new ProcessingContext(); + + const result = processOpenApiSpec({ + spec: openApiSpecFactory({ + paths: { + "/users": { + get: { + operationId: "getUsers", + tags: ["users"], + responses: { "200": { description: "Success" } }, + }, + }, + }, + }), + specUrl: "https://example.com/spec.json", + visitorConfig: { + item: { api: "API", "api-name": "api" }, + parentPath: PathBuilder.init(), + tab: "reference", + repo: DOCS_REPO, + contentCache: new ContentCache(), + context, + navigationAncestors: [], + }, + apiPathBuilder: PathBuilder.init("reference/api"), + apiTitle: "API", + isHidden: false, + isFlattened: false, + }); + + expect(result.indexEntries["reference/api/users/get-users"]).toBeDefined(); + }); + + test("should add Algolia records with breadcrumbs", () => { + const context = new ProcessingContext(); + + processOpenApiSpec({ + spec: openApiSpecFactory({ + paths: { + "/balance": { + get: { + operationId: "getBalance", + description: "Get balance description", + responses: { "200": { description: "Success" } }, + }, + }, + }, + }), + specUrl: "https://example.com/spec.json", + visitorConfig: { + item: { api: "Ethereum API", "api-name": "ethereum-api" }, + parentPath: PathBuilder.init(), + tab: "reference", + repo: DOCS_REPO, + contentCache: new ContentCache(), + context, + navigationAncestors: [ + { + title: "Reference", + path: "/reference", + type: "section", + children: [], + }, + ], + }, + apiPathBuilder: PathBuilder.init("reference/ethereum"), + apiTitle: "Ethereum API", + isHidden: false, + isFlattened: false, + }); + + const results = context.getResults(); + expect(results.algoliaRecords[0].breadcrumbs).toContain("Reference"); + expect(results.algoliaRecords[0].breadcrumbs).toContain("Ethereum API"); + }); + + test("should not add Algolia records if hidden", () => { + const context = new ProcessingContext(); + + processOpenApiSpec({ + spec: openApiSpecFactory({ + paths: { + "/balance": { + get: { + operationId: "getBalance", + responses: { "200": { description: "Success" } }, + }, + }, + }, + }), + specUrl: "https://example.com/spec.json", + visitorConfig: { + item: { api: "API", "api-name": "api" }, + parentPath: PathBuilder.init(), + tab: "reference", + repo: DOCS_REPO, + contentCache: new ContentCache(), + context, + navigationAncestors: [], + }, + apiPathBuilder: PathBuilder.init("reference/api"), + apiTitle: "API", + isHidden: true, + isFlattened: false, + }); + + const results = context.getResults(); + expect(results.algoliaRecords).toHaveLength(0); + }); + + test("should handle operations without tags", () => { + const context = new ProcessingContext(); + + const result = processOpenApiSpec({ + spec: openApiSpecFactory({ + paths: { + "/balance": { + get: { + operationId: "getBalance", + responses: { "200": { description: "Success" } }, + }, + }, + }, + }), + specUrl: "https://example.com/spec.json", + visitorConfig: { + item: { api: "API", "api-name": "api" }, + parentPath: PathBuilder.init(), + tab: "reference", + repo: DOCS_REPO, + contentCache: new ContentCache(), + context, + navigationAncestors: [], + }, + apiPathBuilder: PathBuilder.init("reference/api"), + apiTitle: "API", + isHidden: false, + isFlattened: false, + }); + + // Operations without tags should be added directly without tag wrapper + if ( + result.navItem && + !Array.isArray(result.navItem) && + (result.navItem.type === "section" || + result.navItem.type === "api-section") + ) { + expect(result.navItem.children[0].type).toBe("endpoint"); + } + }); +}); diff --git a/src/content-indexer/visitors/processors/__tests__/process-openrpc.test.ts b/src/content-indexer/visitors/processors/__tests__/process-openrpc.test.ts new file mode 100644 index 000000000..5cae6ce49 --- /dev/null +++ b/src/content-indexer/visitors/processors/__tests__/process-openrpc.test.ts @@ -0,0 +1,306 @@ +import { describe, expect, test, vi } from "vitest"; + +import { ProcessingContext } from "@/content-indexer/collectors/processing-context"; +import { ContentCache } from "@/content-indexer/core/content-cache"; +import { PathBuilder } from "@/content-indexer/core/path-builder"; +import type { OpenRpcSpec } from "@/content-indexer/types/specs"; +import { DOCS_REPO } from "@/content-indexer/utils/github"; +import { openRpcSpecFactory } from "@/content-indexer/utils/test-factories"; + +import { processOpenRpcSpec } from "../process-openrpc"; + +describe("processOpenRpcSpec", () => { + test("should return empty result for invalid spec", () => { + const context = new ProcessingContext(); + const consoleSpy = vi.spyOn(console, "error").mockImplementation(() => {}); + + const result = processOpenRpcSpec({ + spec: {} as OpenRpcSpec, // Invalid spec without methods array + specUrl: "https://example.com/spec.json", + visitorConfig: { + item: { api: "API", "api-name": "api" }, + parentPath: PathBuilder.init(), + tab: "reference", + repo: DOCS_REPO, + contentCache: new ContentCache(), + context, + navigationAncestors: [], + }, + apiPathBuilder: PathBuilder.init("reference/api"), + apiTitle: "Test API", + isHidden: false, + isFlattened: false, + }); + + expect(result.indexEntries).toEqual({}); + expect(consoleSpy).toHaveBeenCalledWith( + expect.stringContaining("Invalid OpenRPC spec"), + ); + + consoleSpy.mockRestore(); + }); + + test("should process methods and create index entries", () => { + const context = new ProcessingContext(); + + const result = processOpenRpcSpec({ + spec: openRpcSpecFactory({ + methods: [ + { + name: "getAsset", + description: "Get asset information", + params: [], + result: { name: "result", schema: {} }, + }, + { + name: "getAccountInfo", + summary: "Get account info", + params: [], + result: { name: "result", schema: {} }, + }, + ], + }), + specUrl: "https://example.com/rpc-spec.json", + visitorConfig: { + item: { api: "Solana API", "api-name": "solana-api" }, + parentPath: PathBuilder.init(), + tab: "reference", + repo: DOCS_REPO, + contentCache: new ContentCache(), + context, + navigationAncestors: [], + }, + apiPathBuilder: PathBuilder.init("reference/solana"), + apiTitle: "Solana API", + isHidden: false, + isFlattened: false, + }); + + expect(Object.keys(result.indexEntries)).toHaveLength(2); + const entry = result.indexEntries["reference/solana/get-asset"]; + expect(entry).toBeDefined(); + expect(entry.type).toBe("openrpc"); + if (entry.type === "openrpc") { + expect(entry.methodName).toBe("getAsset"); + } + }); + + test("should create navigation with API section wrapper", () => { + const context = new ProcessingContext(); + + const result = processOpenRpcSpec({ + spec: openRpcSpecFactory({ + methods: [ + { + name: "getAsset", + params: [], + result: { name: "result", schema: {} }, + }, + ], + }), + specUrl: "https://example.com/spec.json", + visitorConfig: { + item: { api: "Solana API", "api-name": "solana-api" }, + parentPath: PathBuilder.init(), + tab: "reference", + repo: DOCS_REPO, + contentCache: new ContentCache(), + context, + navigationAncestors: [], + }, + apiPathBuilder: PathBuilder.init("reference/solana"), + apiTitle: "Solana API", + isHidden: false, + isFlattened: false, + }); + + expect(result.navItem).toBeDefined(); + expect(result.navItem).toHaveProperty("type", "api-section"); + expect(result.navItem).toHaveProperty("title", "Solana API"); + if ( + result.navItem && + !Array.isArray(result.navItem) && + (result.navItem.type === "section" || + result.navItem.type === "api-section") + ) { + expect(result.navItem.children).toHaveLength(1); + } + }); + + test("should flatten navigation if flattened is true", () => { + const context = new ProcessingContext(); + + const result = processOpenRpcSpec({ + spec: openRpcSpecFactory({ + methods: [ + { + name: "getAsset", + params: [], + result: { name: "result", schema: {} }, + }, + ], + }), + specUrl: "https://example.com/spec.json", + visitorConfig: { + item: { api: "Solana API", "api-name": "solana-api" }, + parentPath: PathBuilder.init(), + tab: "reference", + repo: DOCS_REPO, + contentCache: new ContentCache(), + context, + navigationAncestors: [], + }, + apiPathBuilder: PathBuilder.init("reference/solana"), + apiTitle: "Solana API", + isHidden: false, + isFlattened: true, + }); + + expect(Array.isArray(result.navItem)).toBe(true); + }); + + test("should not create nav if hidden", () => { + const context = new ProcessingContext(); + + const result = processOpenRpcSpec({ + spec: openRpcSpecFactory({ + methods: [ + { + name: "getAsset", + params: [], + result: { name: "result", schema: {} }, + }, + ], + }), + specUrl: "https://example.com/spec.json", + visitorConfig: { + item: { api: "Solana API", "api-name": "solana-api" }, + parentPath: PathBuilder.init(), + tab: "reference", + repo: DOCS_REPO, + contentCache: new ContentCache(), + context, + navigationAncestors: [], + }, + apiPathBuilder: PathBuilder.init("reference/solana"), + apiTitle: "Solana API", + isHidden: true, + isFlattened: false, + }); + + expect(result.navItem).toBeUndefined(); + expect(Object.keys(result.indexEntries).length).toBeGreaterThan(0); // Index still created + }); + + test("should add Algolia records for methods", () => { + const context = new ProcessingContext(); + + processOpenRpcSpec({ + spec: openRpcSpecFactory({ + methods: [ + { + name: "getAsset", + description: "Get asset information", + params: [], + result: { name: "result", schema: {} }, + }, + ], + }), + specUrl: "https://example.com/spec.json", + visitorConfig: { + item: { api: "Solana API", "api-name": "solana-api" }, + parentPath: PathBuilder.init(), + tab: "reference", + repo: DOCS_REPO, + contentCache: new ContentCache(), + context, + navigationAncestors: [ + { + title: "Reference", + path: "/reference", + type: "section", + children: [], + }, + ], + }, + apiPathBuilder: PathBuilder.init("reference/solana"), + apiTitle: "Solana API", + isHidden: false, + isFlattened: false, + }); + + const results = context.getResults(); + expect(results.algoliaRecords).toHaveLength(1); + expect(results.algoliaRecords[0].title).toBe("getAsset"); + expect(results.algoliaRecords[0].pageType).toBe("API Method"); + expect(results.algoliaRecords[0].httpMethod).toBe("POST"); + }); + + test("should use method name as title", () => { + const context = new ProcessingContext(); + + processOpenRpcSpec({ + spec: openRpcSpecFactory({ + methods: [ + { + name: "customMethodName", + params: [], + result: { name: "result", schema: {} }, + }, + ], + }), + specUrl: "https://example.com/spec.json", + visitorConfig: { + item: { api: "API", "api-name": "api" }, + parentPath: PathBuilder.init(), + tab: "reference", + repo: DOCS_REPO, + contentCache: new ContentCache(), + context, + navigationAncestors: [], + }, + apiPathBuilder: PathBuilder.init("reference/api"), + apiTitle: "Test API", + isHidden: false, + isFlattened: false, + }); + + const results = context.getResults(); + expect(results.algoliaRecords[0].title).toBe("customMethodName"); + }); + + test("should use description over summary for Algolia content", () => { + const context = new ProcessingContext(); + + processOpenRpcSpec({ + spec: openRpcSpecFactory({ + methods: [ + { + name: "getAsset", + description: "Full description", + summary: "Short summary", + params: [], + result: { name: "result", schema: {} }, + }, + ], + }), + specUrl: "https://example.com/spec.json", + visitorConfig: { + item: { api: "API", "api-name": "api" }, + parentPath: PathBuilder.init(), + tab: "reference", + repo: DOCS_REPO, + contentCache: new ContentCache(), + context, + navigationAncestors: [], + }, + apiPathBuilder: PathBuilder.init("reference/api"), + apiTitle: "Test API", + isHidden: false, + isFlattened: false, + }); + + const results = context.getResults(); + expect(results.algoliaRecords[0].content).toBe("Full description"); + }); +}); diff --git a/src/content-indexer/visitors/processors/process-openapi.ts b/src/content-indexer/visitors/processors/process-openapi.ts new file mode 100644 index 000000000..3bfd467a2 --- /dev/null +++ b/src/content-indexer/visitors/processors/process-openapi.ts @@ -0,0 +1,212 @@ +import type { ProcessingContext } from "@/content-indexer/collectors/processing-context"; +import type { PathBuilder } from "@/content-indexer/core/path-builder"; +import type { NavItem } from "@/content-indexer/types/navigation"; +import type { PathIndex } from "@/content-indexer/types/pathIndex"; +import type { OpenApiSpec } from "@/content-indexer/types/specs"; +import { createBreadcrumbNavItem } from "@/content-indexer/utils/navigation-helpers"; +import { + buildOperationPath, + extractOpenApiOperations, + getOperationDescription, + getOperationTitle, + type ExtractedOperation, +} from "@/content-indexer/utils/openapi"; +import type { VisitorConfig, VisitorResult } from "@/content-indexer/visitors"; + +/** + * Configuration for processing an OpenAPI specification + */ +export interface ProcessOpenApiConfig { + spec: OpenApiSpec; + specUrl: string; + visitorConfig: VisitorConfig; + apiPathBuilder: PathBuilder; + apiTitle: string; + isHidden: boolean; + isFlattened: boolean; +} + +interface BuildOpenApiIndexEntriesConfig { + operations: ExtractedOperation[]; + apiPathBuilder: PathBuilder; + specUrl: string; + tab: string; +} + +interface BuildOpenApiNavigationConfig { + operations: ExtractedOperation[]; + apiPathBuilder: PathBuilder; + spec: OpenApiSpec; + context: ProcessingContext; + navigationAncestors: NavItem[]; + apiSectionBreadcrumb: NavItem | undefined; + isHidden: boolean; +} + +/** + * Builds path index entries for OpenAPI operations. + */ +const buildOpenApiIndexEntries = ({ + operations, + apiPathBuilder, + specUrl, + tab, +}: BuildOpenApiIndexEntriesConfig): PathIndex => { + const indexEntries: PathIndex = {}; + + operations.forEach((operation) => { + const finalPath = buildOperationPath( + apiPathBuilder, + operation.operationId, + operation.tag, + ); + + indexEntries[finalPath] = { + type: "openapi", + specUrl, + operationId: operation.operationId, + source: "docs-yml", + tab, + }; + }); + + return indexEntries; +}; + +/** + * Builds navigation items for OpenAPI operations, grouped by tag. + */ +const buildOpenApiNavigation = ({ + operations, + apiPathBuilder, + spec, + context, + navigationAncestors, + apiSectionBreadcrumb, + isHidden, +}: BuildOpenApiNavigationConfig): NavItem[] => { + // Group operations by tag + const operationsByTag = new Map(); + operations.forEach((operation) => { + const existing = operationsByTag.get(operation.tag) || []; + existing.push(operation); + operationsByTag.set(operation.tag, existing); + }); + + const tagSections: NavItem[] = []; + + for (const [tag, tagOperations] of operationsByTag.entries()) { + const endpointNavItems: NavItem[] = tagOperations.map((operation) => { + const finalPath = buildOperationPath( + apiPathBuilder, + operation.operationId, + operation.tag, + ); + + const title = getOperationTitle( + spec, + operation.operationId, + operation.path, + ); + + // Build Algolia record if not hidden + if (!isHidden) { + const description = getOperationDescription( + spec, + operation.path, + operation.method.toLowerCase(), + ); + + const breadcrumbs = apiSectionBreadcrumb + ? [...navigationAncestors, apiSectionBreadcrumb] + : navigationAncestors; + + context.addAlgoliaRecord({ + pageType: "API Method", + path: finalPath, + title, + content: description, + httpMethod: operation.method, + breadcrumbs, + }); + } + + return { + title, + path: `/${finalPath}`, + method: operation.method, + type: "endpoint" as const, + }; + }); + + // Wrap in tag section if tag exists + if (tag) { + tagSections.push({ + title: tag, + type: "section", + children: endpointNavItems, + }); + } else { + tagSections.push(...endpointNavItems); + } + } + + return tagSections; +}; + +/** + * Processes an OpenAPI specification. + * Extracts operations, builds path index, navigation, and Algolia records. + */ +export const processOpenApiSpec = ({ + spec, + specUrl, + visitorConfig, + apiPathBuilder, + apiTitle, + isHidden, + isFlattened, +}: ProcessOpenApiConfig): VisitorResult => { + const { tab, context, navigationAncestors } = visitorConfig; + + // Extract operations and build index entries + const operations = extractOpenApiOperations(spec.paths); + const indexEntries = buildOpenApiIndexEntries({ + operations, + apiPathBuilder, + specUrl, + tab, + }); + + // Return early if hidden (index only, no navigation) + if (isHidden) { + return { indexEntries, navItem: undefined }; + } + + // Create breadcrumb for Algolia + const apiSectionBreadcrumb = isFlattened + ? undefined + : createBreadcrumbNavItem(apiTitle, "api-section"); + + // Build navigation items + const tagSections = buildOpenApiNavigation({ + operations, + apiPathBuilder, + spec, + context, + navigationAncestors, + apiSectionBreadcrumb, + isHidden, + }); + + // Return flattened or wrapped navigation + const navItem: NavItem | NavItem[] = isFlattened + ? tagSections + : { + title: apiTitle, + type: "api-section", + children: tagSections, + }; + + return { indexEntries, navItem }; +}; diff --git a/src/content-indexer/visitors/processors/process-openrpc.ts b/src/content-indexer/visitors/processors/process-openrpc.ts new file mode 100644 index 000000000..3893a08fb --- /dev/null +++ b/src/content-indexer/visitors/processors/process-openrpc.ts @@ -0,0 +1,109 @@ +import { kebabCase } from "lodash-es"; + +import type { PathBuilder } from "@/content-indexer/core/path-builder"; +import type { NavItem } from "@/content-indexer/types/navigation"; +import type { PathIndex } from "@/content-indexer/types/pathIndex"; +import type { OpenRpcSpec } from "@/content-indexer/types/specs"; +import { createBreadcrumbNavItem } from "@/content-indexer/utils/navigation-helpers"; +import { isValidOpenRpcSpec } from "@/content-indexer/utils/openrpc"; +import type { VisitorConfig, VisitorResult } from "@/content-indexer/visitors"; + +/** + * Configuration for processing an OpenRPC specification + */ +export interface ProcessOpenRpcConfig { + spec: OpenRpcSpec; + specUrl: string; + visitorConfig: VisitorConfig; + apiPathBuilder: PathBuilder; + apiTitle: string; + isHidden: boolean; + isFlattened: boolean; +} + +/** + * Processes an OpenRPC specification. + * Validates spec, builds path index, navigation, and Algolia records. + */ +export const processOpenRpcSpec = ({ + spec, + specUrl, + visitorConfig, + apiPathBuilder, + apiTitle, + isHidden, + isFlattened, +}: ProcessOpenRpcConfig): VisitorResult => { + const { tab, context, navigationAncestors } = visitorConfig; + + if (!isValidOpenRpcSpec(spec)) { + console.error(` ⚠️ Invalid OpenRPC spec for ${apiTitle}`); + return { indexEntries: {} }; + } + + const indexEntries: PathIndex = {}; + const endpointNavItems: NavItem[] = []; + + // Create breadcrumb for Algolia + const apiSectionBreadcrumb = + !isHidden && !isFlattened + ? createBreadcrumbNavItem(apiTitle, "api-section") + : undefined; + + // Process each RPC method + spec.methods.forEach((method) => { + const slug = kebabCase(method.name); + const pathBuilder = apiPathBuilder.apply({ urlSlug: slug }); + const finalPath = pathBuilder.get(); + + // Add to path index + indexEntries[finalPath] = { + type: "openrpc", + specUrl, + methodName: method.name, + source: "docs-yml", + tab, + }; + + // Build Algolia record if not hidden + if (!isHidden) { + const description = method.description || method.summary || ""; + const breadcrumbs = apiSectionBreadcrumb + ? [...navigationAncestors, apiSectionBreadcrumb] + : navigationAncestors; + + context.addAlgoliaRecord({ + pageType: "API Method", + path: finalPath, + title: method.name, + content: description, + httpMethod: "POST", + breadcrumbs, + }); + } + + // Add navigation item + endpointNavItems.push({ + title: method.name, + path: `/${finalPath}`, + method: "POST", + type: "endpoint", + }); + }); + + // Return early if hidden + if (isHidden) { + return { indexEntries, navItem: undefined }; + } + + // Return flattened or wrapped navigation + const navItem: NavItem | NavItem[] = isFlattened + ? endpointNavItems + : { + title: apiTitle, + type: "api-section", + children: endpointNavItems, + }; + + return { indexEntries, navItem }; +}; diff --git a/src/content-indexer/visitors/visit-api-reference.ts b/src/content-indexer/visitors/visit-api-reference.ts new file mode 100644 index 000000000..4844a2dc0 --- /dev/null +++ b/src/content-indexer/visitors/visit-api-reference.ts @@ -0,0 +1,71 @@ +import { kebabCase } from "lodash-es"; + +import type { ApiConfig } from "@/content-indexer/types/docsYaml"; +import type { OpenApiSpec, OpenRpcSpec } from "@/content-indexer/types/specs"; + +import type { VisitorConfigBase, VisitorResult } from "."; +import { processOpenApiSpec } from "./processors/process-openapi"; +import { processOpenRpcSpec } from "./processors/process-openrpc"; + +export interface ApiVisitorConfig extends VisitorConfigBase { + item: ApiConfig; +} + +/** + * Visits an API reference item from docs.yml. + * + * Handles both OpenAPI and OpenRPC specifications by delegating to + * spec-specific processors. Extracts config, loads cached spec, + * and routes to the appropriate processor. + */ +export const visitApiReference = (config: ApiVisitorConfig): VisitorResult => { + const { item: apiConfig, parentPath, contentCache } = config; + + // Extract configuration + const apiName = apiConfig["api-name"]; + const apiUrlSlug = apiConfig.slug ?? kebabCase(apiConfig.api); + const skipSlug = apiConfig["skip-slug"] ?? false; + const isHidden = apiConfig.hidden ?? false; + const isFlattened = apiConfig.flattened ?? false; + + // Build path for this API + const apiPathBuilder = skipSlug + ? parentPath + : parentPath.apply({ urlSlug: apiUrlSlug }); + + // Retrieve cached spec + const cached = contentCache.getSpec(apiName); + if (!cached) { + console.warn( + ` ⚠️ No cached spec found for api-name: ${apiName} (skipping)`, + ); + return { indexEntries: {} }; + } + + const { specType, spec, specUrl } = cached; + + // Delegate to spec-specific processor + switch (specType) { + case "openapi": + return processOpenApiSpec({ + spec: spec as OpenApiSpec, + specUrl, + visitorConfig: config, + apiPathBuilder, + apiTitle: apiConfig.api, + isHidden, + isFlattened, + }); + + case "openrpc": + return processOpenRpcSpec({ + spec: spec as OpenRpcSpec, + specUrl, + visitorConfig: config, + apiPathBuilder, + apiTitle: apiConfig.api, + isHidden, + isFlattened, + }); + } +}; diff --git a/src/content-indexer/visitors/visit-link.ts b/src/content-indexer/visitors/visit-link.ts new file mode 100644 index 000000000..be0f30ae0 --- /dev/null +++ b/src/content-indexer/visitors/visit-link.ts @@ -0,0 +1,25 @@ +import type { LinkConfig } from "@/content-indexer/types/docsYaml"; + +import type { VisitorConfigBase, VisitorResult } from "."; + +export interface LinkVisitorConfig extends VisitorConfigBase { + item: LinkConfig; +} + +/** + * Visits a link item from docs.yml. + * + * Links are external URLs - they only appear in navigation, not in path index. + */ +export const visitLink = (config: LinkVisitorConfig): VisitorResult => { + const { item: linkItem } = config; + + return { + indexEntries: {}, + navItem: { + title: linkItem.link, + href: linkItem.href, + type: "link", + }, + }; +}; diff --git a/src/content-indexer/visitors/visit-page.ts b/src/content-indexer/visitors/visit-page.ts new file mode 100644 index 000000000..10f0eb2ea --- /dev/null +++ b/src/content-indexer/visitors/visit-page.ts @@ -0,0 +1,79 @@ +import { kebabCase } from "lodash-es"; + +import type { PageConfig } from "@/content-indexer/types/docsYaml"; +import type { NavItem } from "@/content-indexer/types/navigation"; +import { + normalizeFilePath, + normalizeSlug, +} from "@/content-indexer/utils/normalization"; + +import type { VisitorConfigBase, VisitorResult } from "."; + +export interface PageVisitorConfig extends VisitorConfigBase { + item: PageConfig; +} + +/** + * Visits a page item from docs.yml. + * + * Builds: + * - Path index entry for URL routing + * - Navigation item for sidebar (unless hidden) + * - Algolia record for search (unless hidden) + */ +export const visitPage = ({ + item: pageItem, + parentPath, + tab, + repo, + contentCache, + context, + navigationAncestors, +}: PageVisitorConfig): VisitorResult => { + // Look up cached MDX content + const cached = contentCache.getMdxContent(pageItem.path); + const frontmatterSlug = normalizeSlug(cached?.frontmatter.slug); + const urlSlug = pageItem.slug ?? kebabCase(pageItem.page); + + const pagePathBuilder = parentPath.apply({ + fullSlug: frontmatterSlug?.split("/"), + urlSlug, + }); + + const finalPath = pagePathBuilder.get(); + + // Build index entry + const indexEntries = { + [finalPath]: { + type: "mdx" as const, + filePath: normalizeFilePath(pageItem.path, repo), + source: frontmatterSlug + ? ("frontmatter" as const) + : ("docs-yml" as const), + tab, + }, + }; + + // Build nav item (skip if hidden) + const navItem: NavItem | undefined = pageItem.hidden + ? undefined + : { + title: pageItem.page, + path: `/${finalPath}`, + type: "page", + }; + + // Build Algolia record (if content available and not hidden) + if (cached && navItem) { + const title = cached.frontmatter.title || pageItem.page; + context.addAlgoliaRecord({ + pageType: "Guide", + path: finalPath, + title, + content: cached.content, + breadcrumbs: navigationAncestors, // Excludes current page + }); + } + + return { indexEntries, navItem }; +}; diff --git a/src/content-indexer/visitors/visit-section.ts b/src/content-indexer/visitors/visit-section.ts new file mode 100644 index 000000000..7dbac658a --- /dev/null +++ b/src/content-indexer/visitors/visit-section.ts @@ -0,0 +1,141 @@ +import { kebabCase } from "lodash-es"; + +import type { SectionConfig } from "@/content-indexer/types/docsYaml"; +import type { NavItem } from "@/content-indexer/types/navigation"; +import type { PathIndex } from "@/content-indexer/types/pathIndex"; +import { + normalizeFilePath, + normalizeSlug, +} from "@/content-indexer/utils/normalization"; + +import type { VisitorConfig, VisitorConfigBase, VisitorResult } from "."; + +export interface SectionVisitorConfig extends VisitorConfigBase { + item: SectionConfig; +} + +/** + * Visits a section item from docs.yml. + * + * Builds: + * - Path index entry for overview page (if exists) + * - Hierarchical navigation with children + * - Algolia record for overview page (if exists) + * - Recursively processes all child items + */ +export const visitSection = ( + config: SectionVisitorConfig, + visitNavigationItem: (config: VisitorConfig) => VisitorResult, +): VisitorResult => { + const { + item: sectionItem, + parentPath, + tab, + repo, + contentCache, + context, + navigationAncestors, + } = config; + const sectionUrlSlug = sectionItem.slug ?? kebabCase(sectionItem.section); + const skipSlug = sectionItem["skip-slug"] ?? false; + + let sectionFullSlug: string[] | undefined; + let sectionPath: string | undefined; + const indexEntries: PathIndex = {}; + + // If there's an overview page, look up cached content + if (sectionItem.path) { + const cached = contentCache.getMdxContent(sectionItem.path); + const normalizedSlug = normalizeSlug(cached?.frontmatter.slug as string); + sectionFullSlug = normalizedSlug?.split("/"); + + const sectionPathBuilder = parentPath.apply({ + fullSlug: sectionFullSlug, + urlSlug: sectionUrlSlug, + skipUrlSlug: skipSlug, + }); + + const finalPath = sectionPathBuilder.get(); + sectionPath = `/${finalPath}`; + + // Add overview page to index + indexEntries[finalPath] = { + type: "mdx", + filePath: normalizeFilePath(sectionItem.path, repo), + source: normalizedSlug ? "frontmatter" : "docs-yml", + tab, + }; + + // Build Algolia record for section overview page (if content available) + if (cached) { + const title = (cached.frontmatter.title as string) || sectionItem.section; + context.addAlgoliaRecord({ + pageType: "Guide", + path: finalPath, + title, + content: cached.content, + breadcrumbs: navigationAncestors, // Excludes current section + }); + } + } + + // Create path builder for children + const childPathBuilder = parentPath.apply({ + fullSlug: sectionFullSlug, + urlSlug: sectionUrlSlug, + skipUrlSlug: skipSlug, + }); + + // Build section nav item first (for navigation tree) + const sectionNavItem: NavItem = { + title: sectionItem.section, + path: sectionPath, + type: "section", + children: [], // Will be populated below + }; + + // Create breadcrumb (simple copy, no path computation needed) + // If section has no overview page, path will be undefined - this is OK + const sectionBreadcrumb: NavItem = { + title: sectionItem.section, + path: sectionPath, // undefined if no overview page + type: "section", + children: [], + }; + + // Update ancestors to include current section (using breadcrumb copy) + const childAncestors = sectionItem.hidden + ? navigationAncestors // Don't include hidden sections in breadcrumbs + : [...navigationAncestors, sectionBreadcrumb]; + + // Process all children with correct breadcrumbs + const childResults = sectionItem.contents.map((childItem) => + visitNavigationItem({ + ...config, + item: childItem, + parentPath: childPathBuilder, + navigationAncestors: childAncestors, + }), + ); + + // Merge child index entries + childResults.forEach((result) => { + Object.assign(indexEntries, result.indexEntries); + }); + + // Build children nav items (flatten arrays from API refs) + const children: NavItem[] = childResults + .map((result) => result.navItem) + .flat() + .filter((child): child is NavItem => child !== undefined); + + // Only include section in nav if it has children and is not hidden + if (children.length === 0 || sectionItem.hidden) { + return { indexEntries, navItem: undefined }; + } + + // Update section nav item with children + sectionNavItem.children = children; + + return { indexEntries, navItem: sectionNavItem }; +}; diff --git a/tsconfig.json b/tsconfig.json index c70f63164..14d21c6c1 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -7,7 +7,11 @@ "jsx": "react-jsx", "jsxImportSource": "react", "strict": true, - "skipLibCheck": true + "skipLibCheck": true, + "baseUrl": ".", + "paths": { + "@/*": ["src/*"] + } }, "include": ["**/*.ts", "**/*.tsx", "**/*.mts"] } From 7f20c5fb76d5eaa1a44c8266f527c41b26b369b3 Mon Sep 17 00:00:00 2001 From: dslovinsky Date: Thu, 8 Jan 2026 16:56:25 -0800 Subject: [PATCH 02/27] feat: build out separate functionality for content-indexer params --- .env.example | 32 +++ .gitignore | 1 + package.json | 8 +- scripts/generate-content-index.ts | 44 ---- src/content-indexer/__tests__/index.test.ts | 114 +++------ .../core/__tests__/batch-fetcher.test.ts | 30 ++- src/content-indexer/index.ts | 239 +++++++++++------- src/content-indexer/indexers/changelog.ts | 37 +++ src/content-indexer/indexers/main.ts | 72 ++++++ src/content-indexer/indexers/sdk.ts | 99 ++++++++ .../uploaders/__tests__/algolia.test.ts | 16 +- .../uploaders/__tests__/redis.test.ts | 37 ++- src/content-indexer/uploaders/algolia.ts | 36 ++- src/content-indexer/uploaders/redis.ts | 94 ++++--- src/content-indexer/utils/filesystem.ts | 56 ---- 15 files changed, 579 insertions(+), 336 deletions(-) create mode 100644 .env.example delete mode 100644 scripts/generate-content-index.ts create mode 100644 src/content-indexer/indexers/changelog.ts create mode 100644 src/content-indexer/indexers/main.ts create mode 100644 src/content-indexer/indexers/sdk.ts diff --git a/.env.example b/.env.example new file mode 100644 index 000000000..21a105b2e --- /dev/null +++ b/.env.example @@ -0,0 +1,32 @@ +# Content Indexer Environment Variables +# Copy this file to .env.local and fill in your actual values + +# ============================================================================ +# GitHub API (Required for SDK indexer and production mode) +# ============================================================================ +# Get a token at: https://github.com/settings/tokens +# Permissions needed: public_repo (read access to public repositories) +GH_TOKEN=your_github_token_here + +# ============================================================================ +# Redis / Upstash KV (Required for all indexers) +# ============================================================================ +# Used to store path indices and navigation trees +# Get these from your Upstash dashboard: https://console.upstash.com/ +KV_REST_API_URL=your_kv_url_here +KV_REST_API_TOKEN=your_kv_token_here +KV_REST_API_READ_ONLY_TOKEN=your_readonly_token_here +KV_URL=your_kv_url_here + +# ============================================================================ +# Algolia (Required for all indexers) +# ============================================================================ +# Used for search indexing +# Get these from your Algolia dashboard: https://www.algolia.com/ +ALGOLIA_APP_ID=your_app_id_here +ALGOLIA_ADMIN_API_KEY=your_admin_api_key_here + +# Index names for different content types +ALGOLIA_INDEX_NAME=alchemy_docs +ALGOLIA_WALLET_INDEX_NAME=alchemy_docs_wallets +ALGOLIA_CHANGELOG_INDEX_NAME=alchemy_docs_changelog diff --git a/.gitignore b/.gitignore index c2c2cc1cf..6492c543d 100644 --- a/.gitignore +++ b/.gitignore @@ -13,6 +13,7 @@ node_modules/ # dotenv environment variables file .env* +!.env.example # misc .DS_Store diff --git a/package.json b/package.json index d43b26928..d36d1bfb5 100644 --- a/package.json +++ b/package.json @@ -35,10 +35,10 @@ "lint:broken-links": "lychee .", "add-evm-chain": "ts-node ./scripts/add-evm-chain.ts", "prepare": "husky", - "index:main": "tsx scripts/generate-content-index.ts --indexer main --branch main", - "index:main:preview": "tsx scripts/generate-content-index.ts --indexer main --mode preview --branch $(git rev-parse --abbrev-ref HEAD)", - "index:sdk": "tsx scripts/generate-content-index.ts --indexer sdk --branch main", - "index:changelog": "tsx scripts/generate-content-index.ts --indexer changelog --branch main", + "index:main": "tsx src/content-indexer/index.ts --indexer main --branch main", + "index:main:preview": "tsx src/content-indexer/index.ts --indexer main --mode preview --branch $(git rev-parse --abbrev-ref HEAD)", + "index:sdk": "tsx src/content-indexer/index.ts --indexer sdk --branch main", + "index:changelog": "tsx src/content-indexer/index.ts --indexer changelog --branch main", "index:watch": "tsx scripts/watch-and-index.ts" }, "dependencies": { diff --git a/scripts/generate-content-index.ts b/scripts/generate-content-index.ts deleted file mode 100644 index 1cc37d8e2..000000000 --- a/scripts/generate-content-index.ts +++ /dev/null @@ -1,44 +0,0 @@ -#!/usr/bin/env tsx -import path from "path"; - -import type { ContentSource } from "@/content-indexer/core/batch-fetcher"; -import { buildContentIndex } from "@/content-indexer/index"; -import { DOCS_REPO } from "@/content-indexer/utils/github"; - -/** - * Temporary script for testing Phase 2: filesystem support - * This will be expanded in Phase 3 to support multiple indexers and modes - */ -async function main() { - try { - console.log("🚀 Testing content indexer with filesystem support...\n"); - - // For now, test with filesystem source reading from local fern/ directory - const source: ContentSource = { - type: "filesystem", - basePath: path.join(process.cwd(), "fern"), - }; - - // Build the content index - const { pathIndex, navigationTrees, algoliaRecords } = - await buildContentIndex(source, DOCS_REPO); - - console.log("\n✅ Content indexer completed successfully!"); - console.log(` Generated ${Object.keys(pathIndex).length} path entries`); - console.log( - ` Generated ${Object.keys(navigationTrees).length} navigation trees`, - ); - console.log(` Generated ${algoliaRecords.length} Algolia records`); - - // TODO: Phase 3 will add: - // - Mode support (preview/production) - // - Indexer type support (main/sdk/changelog) - // - Branch ID support - // - Upload to Redis and Algolia - } catch (error) { - console.error("❌ Error generating content index:", error); - process.exit(1); - } -} - -void main(); diff --git a/src/content-indexer/__tests__/index.test.ts b/src/content-indexer/__tests__/index.test.ts index 008cc3968..00427fa6d 100644 --- a/src/content-indexer/__tests__/index.test.ts +++ b/src/content-indexer/__tests__/index.test.ts @@ -4,12 +4,10 @@ import { batchFetchContent } from "@/content-indexer/core/batch-fetcher"; import { buildAllOutputs } from "@/content-indexer/core/build-all-outputs"; import { ContentCache } from "@/content-indexer/core/content-cache"; import { scanDocsYml } from "@/content-indexer/core/scanner"; -import type { AlgoliaRecord } from "@/content-indexer/types/algolia"; +import { buildMainContentIndex } from "@/content-indexer/indexers/main"; import { fetchFileFromGitHub } from "@/content-indexer/utils/github"; import { repoConfigFactory } from "@/content-indexer/utils/test-factories"; -import { buildContentIndex } from "../index"; - // Mock dependencies vi.mock("@/content-indexer/utils/github", async () => { const actual = await vi.importActual("@/content-indexer/utils/github"); @@ -31,7 +29,7 @@ vi.mock("@/content-indexer/core/build-all-outputs", () => ({ buildAllOutputs: vi.fn(), })); -describe("buildContentIndex", () => { +describe("buildMainContentIndex", () => { let consoleInfoSpy: ReturnType; beforeEach(() => { @@ -43,19 +41,7 @@ describe("buildContentIndex", () => { vi.clearAllMocks(); }); - test("should orchestrate all 3 phases successfully", async () => { - const repoConfig = repoConfigFactory({ docsPrefix: "docs" }); - const docsYmlContent = ` -navigation: - - tab: guides - layout: - - page: quickstart.mdx -`; - - // Mock Phase 0: Fetch docs.yml - vi.mocked(fetchFileFromGitHub).mockResolvedValue(docsYmlContent); - - // Mock Phase 1: Scan + test("should orchestrate all 3 phases successfully in preview mode", async () => { const mockScanResult = { mdxPaths: new Set(["quickstart.mdx"]), specNames: new Set(["ethereum-api"]), @@ -83,15 +69,21 @@ navigation: }; vi.mocked(buildAllOutputs).mockReturnValue(mockResult); - const result = await buildContentIndex(repoConfig); + const repoConfig = repoConfigFactory({ docsPrefix: "docs" }); - // Verify all phases were called - expect(fetchFileFromGitHub).toHaveBeenCalledWith( - "docs/docs.yml", + const result = await buildMainContentIndex({ + mode: "preview", + localBasePath: "/test/fern", + branchId: "test-branch", repoConfig, - ); + }); + + // Verify all phases were called expect(scanDocsYml).toHaveBeenCalled(); - expect(batchFetchContent).toHaveBeenCalledWith(mockScanResult, repoConfig); + expect(batchFetchContent).toHaveBeenCalledWith(mockScanResult, { + type: "filesystem", + basePath: "/test/fern", + }); expect(buildAllOutputs).toHaveBeenCalledWith( expect.any(Object), mockCache, @@ -100,72 +92,40 @@ navigation: // Verify result expect(result).toEqual(mockResult); - - // Verify console logs - expect(consoleInfoSpy).toHaveBeenCalledWith( - expect.stringContaining("Building content index"), - ); - expect(consoleInfoSpy).toHaveBeenCalledWith( - expect.stringContaining("Phase 1"), - ); - expect(consoleInfoSpy).toHaveBeenCalledWith( - expect.stringContaining("Phase 2"), - ); - expect(consoleInfoSpy).toHaveBeenCalledWith( - expect.stringContaining("Phase 3"), - ); }); - test("should throw error if docs.yml fetch fails", async () => { - const repoConfig = repoConfigFactory({ docsPrefix: "docs" }); - - vi.mocked(fetchFileFromGitHub).mockResolvedValue(null); - - await expect(buildContentIndex(repoConfig)).rejects.toThrow( - "Failed to fetch docs/docs.yml", - ); - }); - - test("should log statistics about generated content", async () => { + test("should use GitHub API in production mode", async () => { const repoConfig = repoConfigFactory({ docsPrefix: "docs" }); + const docsYmlContent = ` +navigation: + - tab: guides + layout: + - page: quickstart.mdx +`; - vi.mocked(fetchFileFromGitHub).mockResolvedValue("navigation: []"); + vi.mocked(fetchFileFromGitHub).mockResolvedValue(docsYmlContent); vi.mocked(scanDocsYml).mockReturnValue({ - mdxPaths: new Set(), + mdxPaths: new Set(["quickstart.mdx"]), specNames: new Set(), }); vi.mocked(batchFetchContent).mockResolvedValue(new ContentCache()); vi.mocked(buildAllOutputs).mockReturnValue({ - pathIndex: { - "guides/quickstart": { - type: "mdx", - source: "docs-yml", - filePath: "quickstart.mdx", - tab: "guides", - }, - "reference/api": { - type: "openapi", - source: "docs-yml", - operationId: "getBalance", - specUrl: "https://example.com/spec.json", - tab: "reference", - }, - }, + pathIndex: {}, navigationTrees: {}, - algoliaRecords: [{ objectID: "1" }] as AlgoliaRecord[], + algoliaRecords: [], }); - await buildContentIndex(repoConfig); + await buildMainContentIndex({ + mode: "production", + localBasePath: "/test/fern", + branchId: "main", + repoConfig, + }); - // Verify statistics are logged - expect(consoleInfoSpy).toHaveBeenCalledWith( - expect.stringContaining("Generated 2 routes, 1 Algolia records"), - ); - expect(consoleInfoSpy).toHaveBeenCalledWith( - expect.stringContaining("Sources:"), - ); - expect(consoleInfoSpy).toHaveBeenCalledWith( - expect.stringContaining("Types:"), - ); + // Verify local filesystem was still used (both modes use local in main indexer) + expect(batchFetchContent).toHaveBeenCalledWith(expect.any(Object), { + type: "filesystem", + basePath: "/test/fern", + }); }); }); diff --git a/src/content-indexer/core/__tests__/batch-fetcher.test.ts b/src/content-indexer/core/__tests__/batch-fetcher.test.ts index 8c8346291..e1654ffc7 100644 --- a/src/content-indexer/core/__tests__/batch-fetcher.test.ts +++ b/src/content-indexer/core/__tests__/batch-fetcher.test.ts @@ -52,7 +52,10 @@ title: Test Page vi.mocked(fetchFileFromGitHub).mockResolvedValue(mdxContent); - const cache = await batchFetchContent(scanResult, repoConfig); + const cache = await batchFetchContent(scanResult, { + type: "github", + repoConfig, + }); // Verify fetches were made expect(fetchFileFromGitHub).toHaveBeenCalledTimes(2); @@ -95,7 +98,10 @@ title: Test Page vi.mocked(fetchApiSpec).mockResolvedValue(mockSpec); - const cache = await batchFetchContent(scanResult, repoConfig); + const cache = await batchFetchContent(scanResult, { + type: "github", + repoConfig, + }); // Verify fetches were made expect(fetchApiSpec).toHaveBeenCalledTimes(2); @@ -120,7 +126,10 @@ title: Test Page vi.mocked(fetchFileFromGitHub).mockResolvedValue("---\n---\nContent"); - await batchFetchContent(scanResult, repoConfig); + await batchFetchContent(scanResult, { + type: "github", + repoConfig, + }); // Verify path was transformed (strip "fern/" prefix) expect(fetchFileFromGitHub).toHaveBeenCalledWith( @@ -141,7 +150,10 @@ title: Test Page ); vi.mocked(fetchApiSpec).mockRejectedValue(new Error("Spec not found")); - const cache = await batchFetchContent(scanResult, repoConfig); + const cache = await batchFetchContent(scanResult, { + type: "github", + repoConfig, + }); // Verify warnings were logged expect(consoleWarnSpy).toHaveBeenCalledWith( @@ -169,7 +181,10 @@ title: Test Page vi.mocked(fetchFileFromGitHub).mockResolvedValue(null); vi.mocked(fetchApiSpec).mockResolvedValue(undefined); - const cache = await batchFetchContent(scanResult, repoConfig); + const cache = await batchFetchContent(scanResult, { + type: "github", + repoConfig, + }); // Cache should be empty const stats = cache.getStats(); @@ -191,7 +206,10 @@ title: Test Page specUrl: "url", }); - await batchFetchContent(scanResult, repoConfig); + await batchFetchContent(scanResult, { + type: "github", + repoConfig, + }); expect(consoleInfoSpy).toHaveBeenCalledWith( expect.stringContaining("Fetching 1 MDX files and 1 specs"), diff --git a/src/content-indexer/index.ts b/src/content-indexer/index.ts index 1be17b77a..72fb985e3 100644 --- a/src/content-indexer/index.ts +++ b/src/content-indexer/index.ts @@ -1,103 +1,170 @@ -import yaml from "js-yaml"; - -import type { BuildAllOutputsResult } from "@/content-indexer/collectors/processing-context"; -import { - batchFetchContent, - type ContentSource, -} from "@/content-indexer/core/batch-fetcher"; -import { buildAllOutputs } from "@/content-indexer/core/build-all-outputs"; -import { scanDocsYml } from "@/content-indexer/core/scanner"; -import type { DocsYml } from "@/content-indexer/types/docsYaml"; -import { readLocalDocsYml } from "@/content-indexer/utils/filesystem"; -import { - fetchFileFromGitHub, - type RepoConfig, -} from "@/content-indexer/utils/github"; - -/** - * Main content indexer function using 3-phase architecture. - * Phase 1: Scan docs.yml for all file paths and spec names - * Phase 2: Batch fetch/read all content in parallel - * Phase 3: Process with cached content to build index, nav trees, and Algolia records - * - * @param source - Content source (filesystem or GitHub) - * @param repoConfig - Repository configuration (needed for path-building even in filesystem mode) - */ -export const buildContentIndex = async ( - source: ContentSource, - repoConfig: RepoConfig, -): Promise => { - const sourceName = - source.type === "filesystem" ? "local filesystem" : repoConfig.repo; - console.info( - `🔍 Building content index from ${sourceName}. This may take a few minutes...`, - ); +#!/usr/bin/env tsx +import path from "path"; - // Read/fetch and parse docs.yml - let docsYml: DocsYml; +import { buildChangelogIndex } from "@/content-indexer/indexers/changelog"; +import { buildMainContentIndex } from "@/content-indexer/indexers/main"; +import { buildSDKContentIndex } from "@/content-indexer/indexers/sdk"; +import { uploadToAlgolia } from "@/content-indexer/uploaders/algolia"; +import { storeToRedis } from "@/content-indexer/uploaders/redis"; +import { DOCS_REPO, WALLET_REPO } from "@/content-indexer/utils/github"; - if (source.type === "filesystem") { - const result = await readLocalDocsYml(source.basePath); - if (!result) { - throw new Error(`Failed to read docs.yml from ${source.basePath}`); - } - docsYml = result; - } else { - const docsYmlPath = `${source.repoConfig.docsPrefix}/docs.yml`; - const docsYmlContent = await fetchFileFromGitHub( - docsYmlPath, - source.repoConfig, +// ============================================================================ +// CLI Argument Parsing +// ============================================================================ + +const parseArgs = () => { + const args = process.argv.slice(2); + + const indexer = + args.find((arg) => arg.startsWith("--indexer="))?.split("=")[1] || "main"; + const mode = + args.find((arg) => arg.startsWith("--mode="))?.split("=")[1] || + "production"; + const branch = + args.find((arg) => arg.startsWith("--branch="))?.split("=")[1] || "main"; + + // Validate arguments + if (!["main", "sdk", "changelog"].includes(indexer)) { + throw new Error( + `Invalid indexer: ${indexer}. Must be 'main', 'sdk', or 'changelog'`, ); - if (!docsYmlContent) { - throw new Error( - `Failed to fetch ${docsYmlPath} from ${source.repoConfig.repo}`, - ); - } - docsYml = yaml.load(docsYmlContent) as DocsYml; } - // PHASE 1: SCAN - console.info("📋 Phase 1: Scanning docs.yml for all paths and specs..."); - const scanResult = scanDocsYml(docsYml); + if (!["preview", "production"].includes(mode)) { + throw new Error(`Invalid mode: ${mode}. Must be 'preview' or 'production'`); + } + + return { + indexer: indexer as "main" | "sdk" | "changelog", + mode: mode as "preview" | "production", + branchId: branch, + }; +}; + +// ============================================================================ +// Main Indexer +// ============================================================================ + +const runMainIndexer = async ( + mode: "preview" | "production", + branchId: string, +) => { console.info( - ` Found ${scanResult.mdxPaths.size} MDX files, ${scanResult.specNames.size} specs`, + `\n🔍 Running MAIN indexer (${mode} mode, branch: ${branchId})\n`, ); - // PHASE 2: BATCH FETCH/READ + const { pathIndex, navigationTrees, algoliaRecords } = + await buildMainContentIndex({ + mode, + localBasePath: path.join(process.cwd(), "fern"), + branchId, + repoConfig: DOCS_REPO, + }); + + console.info("\n📤 Uploading to Redis and Algolia..."); + + await Promise.all([ + storeToRedis(pathIndex, navigationTrees, { + branchId, + pathIndexSuffix: "main", + }), + uploadToAlgolia(algoliaRecords, { indexerType: "main" }), + ]); + console.info( - `📥 Phase 2: ${source.type === "filesystem" ? "Reading" : "Fetching"} all content in parallel...`, - ); - const contentCache = await batchFetchContent(scanResult, source); - - // PHASE 3: PROCESS - console.info("⚙️ Phase 3: Processing with cached content..."); - const { pathIndex, navigationTrees, algoliaRecords } = buildAllOutputs( - docsYml, - contentCache, - repoConfig, + `\n✅ Main indexer completed! (${Object.keys(pathIndex).length} routes, ${algoliaRecords.length} records)`, ); +}; + +// ============================================================================ +// SDK Indexer +// ============================================================================ + +const runSDKIndexer = async (branchId: string) => { + console.info(`\n🔍 Running SDK indexer (branch: ${branchId})\n`); + + const { pathIndex, walletsNavTree, algoliaRecords } = + await buildSDKContentIndex({ + sdkRepoConfig: WALLET_REPO, + branchId, + }); + + console.info("\n📤 Uploading to Redis and Algolia..."); + + await Promise.all([ + storeToRedis( + pathIndex, + { wallets: walletsNavTree }, + { + branchId, + pathIndexSuffix: "sdk-refs", + mergeSDKIntoWallets: true, + }, + ), + uploadToAlgolia(algoliaRecords, { indexerType: "sdk" }), + ]); console.info( - `📊 Generated ${Object.keys(pathIndex).length} routes, ${algoliaRecords.length} Algolia records`, + `\n✅ SDK indexer completed! (${Object.keys(pathIndex).length} routes, ${algoliaRecords.length} records)`, ); +}; - // Count sources and types for debugging - const sources = Object.values(pathIndex).reduce( - (acc, entry) => { - acc[entry.source] = (acc[entry.source] || 0) + 1; - return acc; - }, - {} as Record, - ); - const types = Object.values(pathIndex).reduce( - (acc, entry) => { - acc[entry.type] = (acc[entry.type] || 0) + 1; - return acc; - }, - {} as Record, +// ============================================================================ +// Changelog Indexer +// ============================================================================ + +const runChangelogIndexer = async (branchId: string) => { + console.info(`\n🔍 Running CHANGELOG indexer (branch: ${branchId})\n`); + + const { pathIndex, algoliaRecords } = await buildChangelogIndex({ + localBasePath: path.join(process.cwd(), "fern/changelog"), + branchId, + }); + + console.info("\n📤 Uploading to Redis and Algolia..."); + + await Promise.all([ + storeToRedis(pathIndex, undefined, { + branchId, + pathIndexSuffix: "changelog", + }), + uploadToAlgolia(algoliaRecords, { indexerType: "changelog" }), + ]); + + console.info( + `\n✅ Changelog indexer completed! (${Object.keys(pathIndex).length} routes, ${algoliaRecords.length} records)`, ); - console.info(` Sources: ${JSON.stringify(sources)}`); - console.info(` Types: ${JSON.stringify(types)}`); +}; + +// ============================================================================ +// Main Entry Point +// ============================================================================ + +const main = async () => { + try { + const { indexer, mode, branchId } = parseArgs(); - return { pathIndex, navigationTrees, algoliaRecords }; + console.info("🚀 Content Indexer"); + console.info("=================="); + console.info(` Indexer: ${indexer}`); + console.info(` Mode: ${mode}`); + console.info(` Branch: ${branchId}`); + + switch (indexer) { + case "main": + await runMainIndexer(mode, branchId); + break; + case "sdk": + await runSDKIndexer(branchId); + break; + case "changelog": + await runChangelogIndexer(branchId); + break; + } + } catch (error) { + console.error("\n❌ Error:", error); + process.exit(1); + } }; + +void main(); diff --git a/src/content-indexer/indexers/changelog.ts b/src/content-indexer/indexers/changelog.ts new file mode 100644 index 000000000..a08d480c8 --- /dev/null +++ b/src/content-indexer/indexers/changelog.ts @@ -0,0 +1,37 @@ +import type { AlgoliaRecord } from "@/content-indexer/types/algolia"; +import type { PathIndex } from "@/content-indexer/types/pathIndex"; + +export interface ChangelogIndexerConfig { + localBasePath: string; // Path to fern/changelog/ directory + branchId: string; +} + +/** + * Changelog indexer for changelog entries. + * Simpler than main indexer - no nav trees, just path index and Algolia. + * + * Updates: + * - {branch}/path-index:changelog + * - alchemy_docs_changelog Algolia index + */ +export const buildChangelogIndex = async ( + config: ChangelogIndexerConfig, +): Promise<{ + pathIndex: PathIndex; + algoliaRecords: AlgoliaRecord[]; +}> => { + console.info(`🔍 Building changelog index (branch: ${config.branchId})...`); + + // TODO: Implement changelog indexing + // For now, return empty results + // Will be implemented when we add changelog support + + console.warn( + "⚠️ Changelog indexing not yet implemented - returning empty results", + ); + + return { + pathIndex: {}, + algoliaRecords: [], + }; +}; diff --git a/src/content-indexer/indexers/main.ts b/src/content-indexer/indexers/main.ts new file mode 100644 index 000000000..65fc97fda --- /dev/null +++ b/src/content-indexer/indexers/main.ts @@ -0,0 +1,72 @@ +import type { BuildAllOutputsResult } from "@/content-indexer/collectors/processing-context"; +import { + batchFetchContent, + type ContentSource, +} from "@/content-indexer/core/batch-fetcher"; +import { buildAllOutputs } from "@/content-indexer/core/build-all-outputs"; +import { scanDocsYml } from "@/content-indexer/core/scanner"; +import { readLocalDocsYml } from "@/content-indexer/utils/filesystem"; +import type { RepoConfig } from "@/content-indexer/utils/github"; + +export interface MainIndexerConfig { + mode: "preview" | "production"; + localBasePath: string; // Path to fern/ directory + branchId: string; // For branch-scoped Redis keys (e.g., "main" or "branch-abc123") + repoConfig: RepoConfig; // Needed for path-building +} + +/** + * Main content indexer for docs repo. + * Processes docs.yml and all manual content (main docs + manual wallet pages). + * + * - Preview mode: 100% local filesystem (zero GitHub API calls) + * - Production mode: Same as preview (local only) + * + * Updates: + * - {branch}/path-index:main + * - {branch}/nav-tree:* (all tabs including wallets with manual sections) + * - alchemy_docs Algolia index + */ +export const buildMainContentIndex = async ( + config: MainIndexerConfig, +): Promise => { + const source: ContentSource = { + type: "filesystem", + basePath: config.localBasePath, + }; + + console.info( + `🔍 Building main content index (${config.mode} mode, branch: ${config.branchId})...`, + ); + + // Read and parse local docs.yml + const docsYml = await readLocalDocsYml(config.localBasePath); + if (!docsYml) { + throw new Error(`Failed to read docs.yml from ${config.localBasePath}`); + } + + // PHASE 1: SCAN + console.info("📋 Phase 1: Scanning docs.yml for all paths and specs..."); + const scanResult = scanDocsYml(docsYml); + console.info( + ` Found ${scanResult.mdxPaths.size} MDX files, ${scanResult.specNames.size} specs`, + ); + + // PHASE 2: BATCH READ (all from filesystem) + console.info("📥 Phase 2: Reading all content from filesystem..."); + const contentCache = await batchFetchContent(scanResult, source); + + // PHASE 3: PROCESS + console.info("⚙️ Phase 3: Processing with cached content..."); + const { pathIndex, navigationTrees, algoliaRecords } = buildAllOutputs( + docsYml, + contentCache, + config.repoConfig, + ); + + console.info( + `📊 Generated ${Object.keys(pathIndex).length} routes, ${algoliaRecords.length} Algolia records`, + ); + + return { pathIndex, navigationTrees, algoliaRecords }; +}; diff --git a/src/content-indexer/indexers/sdk.ts b/src/content-indexer/indexers/sdk.ts new file mode 100644 index 000000000..1d5070d92 --- /dev/null +++ b/src/content-indexer/indexers/sdk.ts @@ -0,0 +1,99 @@ +import yaml from "js-yaml"; + +import { + batchFetchContent, + type ContentSource, +} from "@/content-indexer/core/batch-fetcher"; +import { buildAllOutputs } from "@/content-indexer/core/build-all-outputs"; +import { scanDocsYml } from "@/content-indexer/core/scanner"; +import type { AlgoliaRecord } from "@/content-indexer/types/algolia"; +import type { DocsYml } from "@/content-indexer/types/docsYaml"; +import type { NavItem } from "@/content-indexer/types/navigation"; +import type { PathIndex } from "@/content-indexer/types/pathIndex"; +import { + fetchFileFromGitHub, + type RepoConfig, +} from "@/content-indexer/utils/github"; + +export interface SDKIndexerConfig { + sdkRepoConfig: RepoConfig; // aa-sdk repo config + branchId: string; // Usually "main" for production +} + +/** + * SDK references indexer for aa-sdk repo. + * Fetches SDK refs from aa-sdk via GitHub API and merges into wallets nav tree. + * + * Uses read-modify-write pattern: + * 1. Read existing {branch}/nav-tree:wallets from Redis + * 2. Extract and keep manual content sections + * 3. Fetch and process SDK refs from aa-sdk + * 4. Generate new SDK Reference section + * 5. Merge manual sections + SDK section + * 6. Write back complete tree + * + * Updates: + * - {branch}/path-index:sdk-refs + * - {branch}/nav-tree:wallets (read-modify-write) + * - alchemy_docs_wallets Algolia index + */ +export const buildSDKContentIndex = async ( + config: SDKIndexerConfig, +): Promise<{ + pathIndex: PathIndex; + walletsNavTree: NavItem[]; + algoliaRecords: AlgoliaRecord[]; +}> => { + const source: ContentSource = { + type: "github", + repoConfig: config.sdkRepoConfig, + }; + + console.info(`🔍 Building SDK content index (branch: ${config.branchId})...`); + + // Fetch and parse aa-sdk docs.yml + const docsYmlPath = `${config.sdkRepoConfig.docsPrefix}/docs.yml`; + const docsYmlContent = await fetchFileFromGitHub( + docsYmlPath, + config.sdkRepoConfig, + ); + if (!docsYmlContent) { + throw new Error( + `Failed to fetch ${docsYmlPath} from ${config.sdkRepoConfig.repo}`, + ); + } + const docsYml = yaml.load(docsYmlContent) as DocsYml; + + // PHASE 1: SCAN + console.info("📋 Phase 1: Scanning aa-sdk docs.yml for SDK refs..."); + const scanResult = scanDocsYml(docsYml); + console.info( + ` Found ${scanResult.mdxPaths.size} MDX files (SDK refs), ${scanResult.specNames.size} specs`, + ); + + // PHASE 2: BATCH FETCH (from GitHub) + console.info("📥 Phase 2: Fetching SDK refs from GitHub..."); + const contentCache = await batchFetchContent(scanResult, source); + + // PHASE 3: PROCESS + console.info("⚙️ Phase 3: Processing SDK refs..."); + const { pathIndex, navigationTrees, algoliaRecords } = buildAllOutputs( + docsYml, + contentCache, + config.sdkRepoConfig, + ); + + // TODO: Implement read-modify-write for wallets nav tree + // For now, return the SDK nav tree as-is + // In Phase 3 completion, we'll add Redis read/merge logic + + console.info( + `📊 Generated ${Object.keys(pathIndex).length} SDK ref routes, ${algoliaRecords.length} Algolia records`, + ); + + return { + pathIndex, + walletsNavTree: navigationTrees.wallets || [], + algoliaRecords, + }; +}; diff --git a/src/content-indexer/uploaders/__tests__/algolia.test.ts b/src/content-indexer/uploaders/__tests__/algolia.test.ts index 651b790ba..02c386022 100644 --- a/src/content-indexer/uploaders/__tests__/algolia.test.ts +++ b/src/content-indexer/uploaders/__tests__/algolia.test.ts @@ -15,7 +15,7 @@ describe("uploadToAlgolia", () => { const consoleSpy = vi.spyOn(console, "warn").mockImplementation(() => {}); const records: AlgoliaRecord[] = []; - await uploadToAlgolia(records, { isWalletMode: false }); + await uploadToAlgolia(records, { indexerType: "main" }); expect(consoleSpy).toHaveBeenCalledWith( expect.stringContaining("Algolia credentials not found"), @@ -36,7 +36,7 @@ describe("uploadToAlgolia", () => { const consoleSpy = vi.spyOn(console, "warn").mockImplementation(() => {}); const records: AlgoliaRecord[] = []; - await uploadToAlgolia(records, { isWalletMode: false }); + await uploadToAlgolia(records, { indexerType: "main" }); expect(consoleSpy).toHaveBeenCalledWith( expect.stringContaining("Algolia credentials not found"), @@ -59,11 +59,9 @@ describe("uploadToAlgolia", () => { const consoleSpy = vi.spyOn(console, "warn").mockImplementation(() => {}); const records: AlgoliaRecord[] = []; - await uploadToAlgolia(records, { isWalletMode: false }); + await uploadToAlgolia(records, { indexerType: "main" }); - expect(consoleSpy).toHaveBeenCalledWith( - expect.stringContaining("index name not configured"), - ); + expect(consoleSpy).toHaveBeenCalledWith(expect.stringContaining("not set")); consoleSpy.mockRestore(); process.env.ALGOLIA_APP_ID = originalAppId; @@ -83,11 +81,9 @@ describe("uploadToAlgolia", () => { const consoleSpy = vi.spyOn(console, "warn").mockImplementation(() => {}); const records: AlgoliaRecord[] = []; - await uploadToAlgolia(records, { isWalletMode: true }); + await uploadToAlgolia(records, { indexerType: "sdk" }); - expect(consoleSpy).toHaveBeenCalledWith( - expect.stringContaining("index name not configured"), - ); + expect(consoleSpy).toHaveBeenCalledWith(expect.stringContaining("not set")); consoleSpy.mockRestore(); process.env.ALGOLIA_APP_ID = originalAppId; diff --git a/src/content-indexer/uploaders/__tests__/redis.test.ts b/src/content-indexer/uploaders/__tests__/redis.test.ts index 87cd904fc..0bd01a3c0 100644 --- a/src/content-indexer/uploaders/__tests__/redis.test.ts +++ b/src/content-indexer/uploaders/__tests__/redis.test.ts @@ -31,10 +31,13 @@ describe("storeToRedis", () => { }, }; - await storeToRedis(pathIndex, {}, { isWalletMode: false }); + await storeToRedis(pathIndex, undefined, { + branchId: "main", + pathIndexSuffix: "main", + }); expect(mockSet).toHaveBeenCalledWith( - "main/path-index.json", + "main/path-index:main", JSON.stringify(pathIndex, null, 2), ); }); @@ -50,10 +53,13 @@ describe("storeToRedis", () => { ], }; - await storeToRedis({}, navigationTrees, { isWalletMode: false }); + await storeToRedis({}, navigationTrees, { + branchId: "main", + pathIndexSuffix: "main", + }); expect(mockSet).toHaveBeenCalledWith( - "main/nav-guides.json", + "main/nav-tree:guides", JSON.stringify(navigationTrees.guides, null, 2), ); }); @@ -76,19 +82,22 @@ describe("storeToRedis", () => { ], }; - await storeToRedis({}, navigationTrees, { isWalletMode: false }); + await storeToRedis({}, navigationTrees, { + branchId: "main", + pathIndexSuffix: "main", + }); expect(mockSet).toHaveBeenCalledWith( - "main/nav-guides.json", + "main/nav-tree:guides", JSON.stringify(navigationTrees.guides, null, 2), ); expect(mockSet).toHaveBeenCalledWith( - "main/nav-reference.json", + "main/nav-tree:reference", JSON.stringify(navigationTrees.reference, null, 2), ); }); - test("should use wallet prefix if isWalletMode is true", async () => { + test("should use sdk-refs suffix for SDK indexer", async () => { const pathIndex: PathIndex = { "wallets/metamask": { type: "mdx", @@ -98,10 +107,13 @@ describe("storeToRedis", () => { }, }; - await storeToRedis(pathIndex, {}, { isWalletMode: true }); + await storeToRedis(pathIndex, undefined, { + branchId: "main", + pathIndexSuffix: "sdk-refs", + }); expect(mockSet).toHaveBeenCalledWith( - "main/wallet-path-index.json", + "main/path-index:sdk-refs", JSON.stringify(pathIndex, null, 2), ); }); @@ -121,7 +133,10 @@ describe("storeToRedis", () => { reference: [{ title: "Ref", path: "/reference", type: "page" }], }; - await storeToRedis(pathIndex, navigationTrees, { isWalletMode: false }); + await storeToRedis(pathIndex, navigationTrees, { + branchId: "main", + pathIndexSuffix: "main", + }); // Should have called set 3 times (1 pathIndex + 2 nav trees) expect(mockSet).toHaveBeenCalledTimes(3); diff --git a/src/content-indexer/uploaders/algolia.ts b/src/content-indexer/uploaders/algolia.ts index 67eb0d7d3..4479a2b9a 100644 --- a/src/content-indexer/uploaders/algolia.ts +++ b/src/content-indexer/uploaders/algolia.ts @@ -4,7 +4,7 @@ import type { AlgoliaRecord } from "@/content-indexer/types/algolia"; import { truncateRecord } from "@/content-indexer/utils/truncate-record"; /** - * Uploads Algolia records using atomic index swap for zero-downtime updates. + * Uploads records to Algolia using atomic index swap for zero-downtime updates. * * Process: * 1. Upload all records to a temporary index @@ -12,10 +12,16 @@ import { truncateRecord } from "@/content-indexer/utils/truncate-record"; * 3. Atomically swap temp index to production * * This ensures users never see empty search results during updates. + * + * @param records - Algolia records to upload + * @param options - Configuration options + * @param options.indexerType - Type of indexer ("main", "sdk", or "changelog") */ export const uploadToAlgolia = async ( records: AlgoliaRecord[], - options: { isWalletMode: boolean }, + options: { + indexerType: "main" | "sdk" | "changelog"; + }, ): Promise => { const appId = process.env.ALGOLIA_APP_ID; const adminKey = process.env.ALGOLIA_ADMIN_API_KEY; @@ -28,36 +34,41 @@ export const uploadToAlgolia = async ( return; } - const targetIndexName = options.isWalletMode - ? process.env.ALGOLIA_WALLET_INDEX_NAME - : process.env.ALGOLIA_INDEX_NAME; + // Determine target index name based on indexer type + const indexEnvMap = { + main: process.env.ALGOLIA_INDEX_NAME, + sdk: process.env.ALGOLIA_WALLET_INDEX_NAME, + changelog: process.env.ALGOLIA_CHANGELOG_INDEX_NAME, + }; + + const targetIndexName = indexEnvMap[options.indexerType]; if (!targetIndexName) { + const envVarMap = { + main: "ALGOLIA_INDEX_NAME", + sdk: "ALGOLIA_WALLET_INDEX_NAME", + changelog: "ALGOLIA_CHANGELOG_INDEX_NAME", + }; console.warn( - "⚠️ Algolia index name not configured. Skipping Algolia upload.", - ); - console.warn( - ` Set ${options.isWalletMode ? "ALGOLIA_WALLET_INDEX_NAME" : "ALGOLIA_INDEX_NAME"} environment variable.`, + `⚠️ ${envVarMap[options.indexerType]} not set. Skipping Algolia upload.`, ); return; } const client = algoliasearch(appId, adminKey); - const tempIndexName = `${targetIndexName}_temp`; console.info( `📤 Uploading ${records.length} records to Algolia (${targetIndexName})...`, ); - // Truncate records to fit Algolia's 100KB limit (measures entire JSON payload) + // Truncate records to fit Algolia's 100KB limit const truncatedRecords = records.map(truncateRecord); try { // 1. Upload all records to temporary index await client.saveObjects({ indexName: tempIndexName, - // Algolia SDK expects index signature, but we want to be more precise about the type objects: truncatedRecords as unknown as Array>, }); @@ -75,7 +86,6 @@ export const uploadToAlgolia = async ( }); console.info(" ✓ Copied settings from production index"); } catch (_error) { - // Production index might not exist on first run - this is fine console.info( " ℹ️ No existing production index found (might be first run)", ); diff --git a/src/content-indexer/uploaders/redis.ts b/src/content-indexer/uploaders/redis.ts index eaaa0bbcb..dc59927c7 100644 --- a/src/content-indexer/uploaders/redis.ts +++ b/src/content-indexer/uploaders/redis.ts @@ -17,49 +17,85 @@ const countItems = (items: NavigationTree): number => { }; /** - * Stores the path index and navigation trees to Redis. - * Returns promises for all storage operations. + * Stores path index and navigation trees to Redis with branch scoping. + * + * @param pathIndex - The path index to store + * @param navigationTrees - Navigation trees (optional for SDK/changelog indexers) + * @param options - Configuration options + * @param options.branchId - Branch identifier for Redis keys (e.g., "main", "branch-abc123") + * @param options.pathIndexSuffix - Suffix for path index key (e.g., "main", "sdk-refs", "changelog") + * @param options.mergeSDKIntoWallets - If true, merge nav trees into existing wallets tree (for SDK indexer) */ export const storeToRedis = async ( pathIndex: PathIndex, - navigationTrees: NavigationTreesByTab, + navigationTrees: NavigationTreesByTab | undefined, options: { - isWalletMode: boolean; + branchId: string; + pathIndexSuffix: "main" | "sdk-refs" | "changelog"; + mergeSDKIntoWallets?: boolean; }, ): Promise => { const redis = getRedis(); - // Determine Redis keys based on mode - const pathIndexKey = options.isWalletMode - ? "main/wallet-path-index.json" - : "main/path-index.json"; - - // Store path index + // Store path index with branch scope + const pathIndexKey = `${options.branchId}/path-index:${options.pathIndexSuffix}`; const pathIndexPromise = redis .set(pathIndexKey, JSON.stringify(pathIndex, null, 2)) .then(() => { - console.info(`✅ Path index saved to Redis (${pathIndexKey})`); + console.info( + `✅ Path index saved to Redis (${Object.keys(pathIndex).length} routes) -> ${pathIndexKey}`, + ); }); - // Filter navigation trees based on mode: - // - Wallet mode: only write nav-wallets - // - Default mode: skip nav-wallets (to avoid overwriting wallet repo data) - const navigationTreesPromises = Object.entries(navigationTrees) - .filter(([tab]) => { - if (options.isWalletMode) { - return tab === "wallets"; - } - // Default mode: skip wallets tab to avoid overwriting wallets index - return tab !== "wallets"; - }) - .map(async ([tab, navTree]) => { - const redisKey = `main/nav-${tab}.json`; - const itemCount = countItems(navTree); - await redis.set(redisKey, JSON.stringify(navTree, null, 2)); - console.info( - `✅ Navigation for '${tab}' saved to Redis (${itemCount} items)`, + // Handle navigation trees + let navTreePromises: Promise[] = []; + + if (options.mergeSDKIntoWallets && navigationTrees?.wallets) { + // SDK indexer: merge SDK section into existing wallets nav tree + const navTreeKey = `${options.branchId}/nav-tree:wallets`; + const existingTreeJson = await redis.get(navTreeKey); + let existingTree: NavigationTree = []; + + if (existingTreeJson) { + existingTree = JSON.parse(existingTreeJson as string) as NavigationTree; + console.info(`📖 Read existing wallets nav tree from Redis`); + } else { + console.warn( + `⚠️ No existing wallets nav tree found at ${navTreeKey}, creating new one`, ); + } + + // Filter out existing SDK Reference section + const manualSections = existingTree.filter((item) => { + if (item.type === "section" || item.type === "api-section") { + return !item.title.toLowerCase().includes("sdk reference"); + } + return true; }); - await Promise.all([pathIndexPromise, ...navigationTreesPromises]); + // Merge manual + SDK sections + const mergedTree = [...manualSections, ...navigationTrees.wallets]; + + navTreePromises = [ + redis.set(navTreeKey, JSON.stringify(mergedTree, null, 2)).then(() => { + console.info( + `✅ Updated wallets nav tree with SDK refs (${countItems(mergedTree)} total items) -> ${navTreeKey}`, + ); + }), + ]; + } else if (navigationTrees) { + // Main indexer: store all navigation trees normally + navTreePromises = Object.entries(navigationTrees).map( + async ([tab, navTree]) => { + const redisKey = `${options.branchId}/nav-tree:${tab}`; + const itemCount = countItems(navTree); + await redis.set(redisKey, JSON.stringify(navTree, null, 2)); + console.info( + `✅ Navigation tree for '${tab}' saved to Redis (${itemCount} items) -> ${redisKey}`, + ); + }, + ); + } + + await Promise.all([pathIndexPromise, ...navTreePromises]); }; diff --git a/src/content-indexer/utils/filesystem.ts b/src/content-indexer/utils/filesystem.ts index fabf3e092..c1f089452 100644 --- a/src/content-indexer/utils/filesystem.ts +++ b/src/content-indexer/utils/filesystem.ts @@ -69,59 +69,3 @@ export const readLocalMdxFile = async ( return null; } }; - -/** - * Checks if a file exists - */ -export const fileExists = async (filePath: string): Promise => { - try { - await fs.access(filePath); - return true; - } catch { - return false; - } -}; - -/** - * Reads all files in a directory (non-recursive) - */ -export const readDirectory = async (dirPath: string): Promise => { - try { - const entries = await fs.readdir(dirPath, { withFileTypes: true }); - return entries - .filter((entry) => entry.isFile()) - .map((entry) => path.join(dirPath, entry.name)); - } catch (error) { - console.error(`Error reading directory ${dirPath}:`, error); - return []; - } -}; - -/** - * Recursively reads all files in a directory with a specific extension - */ -export const readDirectoryRecursive = async ( - dirPath: string, - extension?: string, -): Promise => { - const files: string[] = []; - - try { - const entries = await fs.readdir(dirPath, { withFileTypes: true }); - - for (const entry of entries) { - const fullPath = path.join(dirPath, entry.name); - - if (entry.isDirectory()) { - const subFiles = await readDirectoryRecursive(fullPath, extension); - files.push(...subFiles); - } else if (!extension || fullPath.endsWith(extension)) { - files.push(fullPath); - } - } - } catch (error) { - console.error(`Error reading directory recursively ${dirPath}:`, error); - } - - return files; -}; From 5548457985657300a7e9897bb9413b406daf94c7 Mon Sep 17 00:00:00 2001 From: dslovinsky Date: Thu, 8 Jan 2026 18:13:48 -0800 Subject: [PATCH 03/27] chore: switch to type: module --- .env.example | 7 +- fern/components/CodeConsole/codeData.ts | 258 +++++++++--------- fern/components/CodeConsole/index.tsx | 8 +- fern/components/Footer.tsx | 20 +- package.json | 1 + prettier.config.js => prettier.config.cjs | 0 scripts/add-evm-chain.ts | 2 +- scripts/fix-esm-imports.sh | 23 ++ scripts/generate-rpc.ts | 4 +- scripts/validate-rpc.ts | 4 +- src/content-indexer/README.md | 5 +- src/content-indexer/__tests__/index.test.ts | 14 +- .../collectors/__tests__/algolia.test.ts | 2 +- .../__tests__/navigation-trees.test.ts | 2 +- .../collectors/__tests__/path-index.test.ts | 2 +- .../__tests__/processing-context.test.ts | 4 +- src/content-indexer/collectors/algolia.ts | 4 +- .../collectors/navigation-trees.ts | 2 +- src/content-indexer/collectors/path-index.ts | 2 +- .../collectors/processing-context.ts | 12 +- src/content-indexer/constants/metadata.ts | 2 +- .../core/__tests__/batch-fetcher.test.ts | 10 +- .../core/__tests__/build-all-outputs.test.ts | 10 +- .../core/__tests__/content-cache.test.ts | 4 +- .../core/__tests__/path-builder.test.ts | 2 +- .../core/__tests__/scanner.test.ts | 4 +- src/content-indexer/core/batch-fetcher.ts | 10 +- src/content-indexer/core/build-all-outputs.ts | 12 +- src/content-indexer/core/content-cache.ts | 5 +- src/content-indexer/core/scanner.ts | 2 +- src/content-indexer/index.ts | 27 +- src/content-indexer/indexers/changelog.ts | 4 +- src/content-indexer/indexers/main.ts | 12 +- src/content-indexer/indexers/sdk.ts | 16 +- .../uploaders/__tests__/algolia.test.ts | 36 +-- .../uploaders/__tests__/redis.test.ts | 6 +- src/content-indexer/uploaders/algolia.ts | 56 ++-- src/content-indexer/uploaders/redis.ts | 6 +- .../__tests__/navigation-helpers.test.ts | 2 +- .../utils/__tests__/normalization.test.ts | 4 +- .../utils/__tests__/openapi.test.ts | 4 +- .../utils/__tests__/openrpc.test.ts | 4 +- .../utils/__tests__/truncate-record.test.ts | 4 +- src/content-indexer/utils/apiSpecs.ts | 4 +- src/content-indexer/utils/filesystem.ts | 2 +- .../utils/navigation-helpers.ts | 2 +- src/content-indexer/utils/normalization.ts | 2 +- src/content-indexer/utils/openapi.ts | 4 +- src/content-indexer/utils/openrpc.ts | 2 +- src/content-indexer/utils/test-factories.ts | 7 +- src/content-indexer/utils/truncate-record.ts | 2 +- .../visitors/__tests__/index.test.ts | 12 +- .../__tests__/visit-api-reference.test.ts | 12 +- .../visitors/__tests__/visit-link.test.ts | 10 +- .../visitors/__tests__/visit-page.test.ts | 10 +- .../visitors/__tests__/visit-section.test.ts | 12 +- src/content-indexer/visitors/index.ts | 22 +- .../__tests__/process-openapi.test.ts | 12 +- .../__tests__/process-openrpc.test.ts | 14 +- .../visitors/processors/process-openapi.ts | 19 +- .../visitors/processors/process-openrpc.ts | 17 +- .../visitors/visit-api-reference.ts | 11 +- src/content-indexer/visitors/visit-link.ts | 2 +- src/content-indexer/visitors/visit-page.ts | 6 +- src/content-indexer/visitors/visit-section.ts | 8 +- src/utils/generateRpcSpecs.ts | 6 +- src/utils/generationHelpers.ts | 2 +- src/utils/validateRpcSpec.ts | 2 +- 68 files changed, 446 insertions(+), 374 deletions(-) rename prettier.config.js => prettier.config.cjs (100%) create mode 100755 scripts/fix-esm-imports.sh diff --git a/.env.example b/.env.example index 21a105b2e..f733d83c8 100644 --- a/.env.example +++ b/.env.example @@ -26,7 +26,6 @@ KV_URL=your_kv_url_here ALGOLIA_APP_ID=your_app_id_here ALGOLIA_ADMIN_API_KEY=your_admin_api_key_here -# Index names for different content types -ALGOLIA_INDEX_NAME=alchemy_docs -ALGOLIA_WALLET_INDEX_NAME=alchemy_docs_wallets -ALGOLIA_CHANGELOG_INDEX_NAME=alchemy_docs_changelog +# Base name for Algolia indices (branch and type will be auto-appended) +# Examples: main_alchemy_docs, main_alchemy_docs_sdk, abc_alchemy_docs +ALGOLIA_INDEX_NAME_BASE=alchemy_docs diff --git a/fern/components/CodeConsole/codeData.ts b/fern/components/CodeConsole/codeData.ts index 52bd43b35..279d287e3 100644 --- a/fern/components/CodeConsole/codeData.ts +++ b/fern/components/CodeConsole/codeData.ts @@ -1,132 +1,132 @@ -import alchemyGetAssetTransfersEthereumRequest from "./code-samples/alchemy_getAssetTransfers/ethereum-request"; -import alchemyGetAssetTransfersEthereumResponse from "./code-samples/alchemy_getAssetTransfers/ethereum-response"; -import alchemyGetTokenBalancesEthereumRequest from "./code-samples/alchemy_getTokenBalances/ethereum-request"; -import alchemyGetTokenBalancesEthereumResponse from "./code-samples/alchemy_getTokenBalances/ethereum-response"; -import ethBlockNumberArbitrumRequest from "./code-samples/eth_blockNumber/arbitrum-request"; -import ethBlockNumberArbitrumResponse from "./code-samples/eth_blockNumber/arbitrum-response"; -import ethBlockNumberBaseRequest from "./code-samples/eth_blockNumber/base-request"; -import ethBlockNumberBaseResponse from "./code-samples/eth_blockNumber/base-response"; -import ethBlockNumberEthereumRequest from "./code-samples/eth_blockNumber/ethereum-request"; -import ethBlockNumberEthereumResponse from "./code-samples/eth_blockNumber/ethereum-response"; -import ethBlockNumberOptimismRequest from "./code-samples/eth_blockNumber/optimism-request"; -import ethBlockNumberOptimismResponse from "./code-samples/eth_blockNumber/optimism-response"; -import ethBlockNumberPolygonRequest from "./code-samples/eth_blockNumber/polygon-request"; -import ethBlockNumberPolygonResponse from "./code-samples/eth_blockNumber/polygon-response"; -import ethChainIdArbitrumRequest from "./code-samples/eth_chainId/arbitrum-request"; -import ethChainIdArbitrumResponse from "./code-samples/eth_chainId/arbitrum-response"; -import ethChainIdBaseRequest from "./code-samples/eth_chainId/base-request"; -import ethChainIdBaseResponse from "./code-samples/eth_chainId/base-response"; -import ethChainIdEthereumRequest from "./code-samples/eth_chainId/ethereum-request"; -import ethChainIdEthereumResponse from "./code-samples/eth_chainId/ethereum-response"; -import ethChainIdOptimismRequest from "./code-samples/eth_chainId/optimism-request"; -import ethChainIdOptimismResponse from "./code-samples/eth_chainId/optimism-response"; -import ethChainIdPolygonRequest from "./code-samples/eth_chainId/polygon-request"; -import ethChainIdPolygonResponse from "./code-samples/eth_chainId/polygon-response"; -import ethEstimateGasArbitrumRequest from "./code-samples/eth_estimateGas/arbitrum-request"; -import ethEstimateGasArbitrumResponse from "./code-samples/eth_estimateGas/arbitrum-response"; -import ethEstimateGasBaseRequest from "./code-samples/eth_estimateGas/base-request"; -import ethEstimateGasBaseResponse from "./code-samples/eth_estimateGas/base-response"; -import ethEstimateGasEthereumRequest from "./code-samples/eth_estimateGas/ethereum-request"; -import ethEstimateGasEthereumResponse from "./code-samples/eth_estimateGas/ethereum-response"; -import ethEstimateGasOptimismRequest from "./code-samples/eth_estimateGas/optimism-request"; -import ethEstimateGasOptimismResponse from "./code-samples/eth_estimateGas/optimism-response"; -import ethEstimateGasPolygonRequest from "./code-samples/eth_estimateGas/polygon-request"; -import ethEstimateGasPolygonResponse from "./code-samples/eth_estimateGas/polygon-response"; -import ethGasPriceArbitrumRequest from "./code-samples/eth_gasPrice/arbitrum-request"; -import ethGasPriceArbitrumResponse from "./code-samples/eth_gasPrice/arbitrum-response"; -import ethGasPriceBaseRequest from "./code-samples/eth_gasPrice/base-request"; -import ethGasPriceBaseResponse from "./code-samples/eth_gasPrice/base-response"; -import ethGasPriceEthereumRequest from "./code-samples/eth_gasPrice/ethereum-request"; -import ethGasPriceEthereumResponse from "./code-samples/eth_gasPrice/ethereum-response"; -import ethGasPriceOptimismRequest from "./code-samples/eth_gasPrice/optimism-request"; -import ethGasPriceOptimismResponse from "./code-samples/eth_gasPrice/optimism-response"; -import ethGasPricePolygonRequest from "./code-samples/eth_gasPrice/polygon-request"; -import ethGasPricePolygonResponse from "./code-samples/eth_gasPrice/polygon-response"; -import ethGetBalanceArbitrumRequest from "./code-samples/eth_getBalance/arbitrum-request"; -import ethGetBalanceArbitrumResponse from "./code-samples/eth_getBalance/arbitrum-response"; -import ethGetBalanceBaseRequest from "./code-samples/eth_getBalance/base-request"; -import ethGetBalanceBaseResponse from "./code-samples/eth_getBalance/base-response"; -import ethGetBalanceEthereumRequest from "./code-samples/eth_getBalance/ethereum-request"; -import ethGetBalanceEthereumResponse from "./code-samples/eth_getBalance/ethereum-response"; -import ethGetBalanceOptimismRequest from "./code-samples/eth_getBalance/optimism-request"; -import ethGetBalanceOptimismResponse from "./code-samples/eth_getBalance/optimism-response"; -import ethGetBalancePolygonRequest from "./code-samples/eth_getBalance/polygon-request"; -import ethGetBalancePolygonResponse from "./code-samples/eth_getBalance/polygon-response"; -import ethGetBlockByNumberArbitrumRequest from "./code-samples/eth_getBlockByNumber/arbitrum-request"; -import ethGetBlockByNumberArbitrumResponse from "./code-samples/eth_getBlockByNumber/arbitrum-response"; -import ethGetBlockByNumberBaseRequest from "./code-samples/eth_getBlockByNumber/base-request"; -import ethGetBlockByNumberBaseResponse from "./code-samples/eth_getBlockByNumber/base-response"; -import ethGetBlockByNumberEthereumRequest from "./code-samples/eth_getBlockByNumber/ethereum-request"; -import ethGetBlockByNumberEthereumResponse from "./code-samples/eth_getBlockByNumber/ethereum-response"; -import ethGetBlockByNumberOptimismRequest from "./code-samples/eth_getBlockByNumber/optimism-request"; -import ethGetBlockByNumberOptimismResponse from "./code-samples/eth_getBlockByNumber/optimism-response"; -import ethGetBlockByNumberPolygonRequest from "./code-samples/eth_getBlockByNumber/polygon-request"; -import ethGetBlockByNumberPolygonResponse from "./code-samples/eth_getBlockByNumber/polygon-response"; -import ethGetBlockReceiptsArbitrumRequest from "./code-samples/eth_getBlockReceipts/arbitrum-request"; -import ethGetBlockReceiptsArbitrumResponse from "./code-samples/eth_getBlockReceipts/arbitrum-response"; -import ethGetBlockReceiptsBaseRequest from "./code-samples/eth_getBlockReceipts/base-request"; -import ethGetBlockReceiptsBaseResponse from "./code-samples/eth_getBlockReceipts/base-response"; -import ethGetBlockReceiptsEthereumRequest from "./code-samples/eth_getBlockReceipts/ethereum-request"; -import ethGetBlockReceiptsEthereumResponse from "./code-samples/eth_getBlockReceipts/ethereum-response"; -import ethGetBlockReceiptsOptimismRequest from "./code-samples/eth_getBlockReceipts/optimism-request"; -import ethGetBlockReceiptsOptimismResponse from "./code-samples/eth_getBlockReceipts/optimism-response"; -import ethGetBlockReceiptsPolygonRequest from "./code-samples/eth_getBlockReceipts/polygon-request"; -import ethGetBlockReceiptsPolygonResponse from "./code-samples/eth_getBlockReceipts/polygon-response"; -import ethGetLogsArbitrumRequest from "./code-samples/eth_getLogs/arbitrum-request"; -import ethGetLogsArbitrumResponse from "./code-samples/eth_getLogs/arbitrum-response"; -import ethGetLogsBaseRequest from "./code-samples/eth_getLogs/base-request"; -import ethGetLogsBaseResponse from "./code-samples/eth_getLogs/base-response"; -import ethGetLogsEthereumRequest from "./code-samples/eth_getLogs/ethereum-request"; -import ethGetLogsEthereumResponse from "./code-samples/eth_getLogs/ethereum-response"; -import ethGetLogsOptimismRequest from "./code-samples/eth_getLogs/optimism-request"; -import ethGetLogsOptimismResponse from "./code-samples/eth_getLogs/optimism-response"; -import ethGetLogsPolygonRequest from "./code-samples/eth_getLogs/polygon-request"; -import ethGetLogsPolygonResponse from "./code-samples/eth_getLogs/polygon-response"; -import ethGetTransactionByHashArbitrumRequest from "./code-samples/eth_getTransactionByHash/arbitrum-request"; -import ethGetTransactionByHashArbitrumResponse from "./code-samples/eth_getTransactionByHash/arbitrum-response"; -import ethGetTransactionByHashBaseRequest from "./code-samples/eth_getTransactionByHash/base-request"; -import ethGetTransactionByHashBaseResponse from "./code-samples/eth_getTransactionByHash/base-response"; -import ethGetTransactionByHashEthereumRequest from "./code-samples/eth_getTransactionByHash/ethereum-request"; -import ethGetTransactionByHashEthereumResponse from "./code-samples/eth_getTransactionByHash/ethereum-response"; -import ethGetTransactionByHashOptimismRequest from "./code-samples/eth_getTransactionByHash/optimism-request"; -import ethGetTransactionByHashOptimismResponse from "./code-samples/eth_getTransactionByHash/optimism-response"; -import ethGetTransactionByHashPolygonRequest from "./code-samples/eth_getTransactionByHash/polygon-request"; -import ethGetTransactionByHashPolygonResponse from "./code-samples/eth_getTransactionByHash/polygon-response"; -import ethGetTransactionCountArbitrumRequest from "./code-samples/eth_getTransactionCount/arbitrum-request"; -import ethGetTransactionCountArbitrumResponse from "./code-samples/eth_getTransactionCount/arbitrum-response"; -import ethGetTransactionCountBaseRequest from "./code-samples/eth_getTransactionCount/base-request"; -import ethGetTransactionCountBaseResponse from "./code-samples/eth_getTransactionCount/base-response"; -import ethGetTransactionCountEthereumRequest from "./code-samples/eth_getTransactionCount/ethereum-request"; -import ethGetTransactionCountEthereumResponse from "./code-samples/eth_getTransactionCount/ethereum-response"; -import ethGetTransactionCountOptimismRequest from "./code-samples/eth_getTransactionCount/optimism-request"; -import ethGetTransactionCountOptimismResponse from "./code-samples/eth_getTransactionCount/optimism-response"; -import ethGetTransactionCountPolygonRequest from "./code-samples/eth_getTransactionCount/polygon-request"; -import ethGetTransactionCountPolygonResponse from "./code-samples/eth_getTransactionCount/polygon-response"; -import ethGetTransactionReceiptArbitrumRequest from "./code-samples/eth_getTransactionReceipt/arbitrum-request"; -import ethGetTransactionReceiptArbitrumResponse from "./code-samples/eth_getTransactionReceipt/arbitrum-response"; -import ethGetTransactionReceiptBaseRequest from "./code-samples/eth_getTransactionReceipt/base-request"; -import ethGetTransactionReceiptBaseResponse from "./code-samples/eth_getTransactionReceipt/base-response"; -import ethGetTransactionReceiptEthereumRequest from "./code-samples/eth_getTransactionReceipt/ethereum-request"; -import ethGetTransactionReceiptEthereumResponse from "./code-samples/eth_getTransactionReceipt/ethereum-response"; -import ethGetTransactionReceiptOptimismRequest from "./code-samples/eth_getTransactionReceipt/optimism-request"; -import ethGetTransactionReceiptOptimismResponse from "./code-samples/eth_getTransactionReceipt/optimism-response"; -import ethGetTransactionReceiptPolygonRequest from "./code-samples/eth_getTransactionReceipt/polygon-request"; -import ethGetTransactionReceiptPolygonResponse from "./code-samples/eth_getTransactionReceipt/polygon-response"; -import getAccountInfoSolanaRequest from "./code-samples/getAccountInfo/solana-request"; -import getAccountInfoSolanaResponse from "./code-samples/getAccountInfo/solana-response"; -import getBalanceSolanaRequest from "./code-samples/getBalance/solana-request"; -import getBalanceSolanaResponse from "./code-samples/getBalance/solana-response"; -import getLatestBlockhashSolanaRequest from "./code-samples/getLatestBlockhash/solana-request"; -import getLatestBlockhashSolanaResponse from "./code-samples/getLatestBlockhash/solana-response"; -import getSignaturesForAddressSolanaRequest from "./code-samples/getSignaturesForAddress/solana-request"; -import getSignaturesForAddressSolanaResponse from "./code-samples/getSignaturesForAddress/solana-response"; -import getTokenAccountBalanceSolanaRequest from "./code-samples/getTokenAccountBalance/solana-request"; -import getTokenAccountBalanceSolanaResponse from "./code-samples/getTokenAccountBalance/solana-response"; -import getTokenAccountsByOwnerSolanaRequest from "./code-samples/getTokenAccountsByOwner/solana-request"; -import getTokenAccountsByOwnerSolanaResponse from "./code-samples/getTokenAccountsByOwner/solana-response"; -import getTransactionSolanaRequest from "./code-samples/getTransaction/solana-request"; -import getTransactionSolanaResponse from "./code-samples/getTransaction/solana-response"; -import type { Option } from "./CodeblockSelect"; +import alchemyGetAssetTransfersEthereumRequest from "./code-samples/alchemy_getAssetTransfers/ethereum-request.js"; +import alchemyGetAssetTransfersEthereumResponse from "./code-samples/alchemy_getAssetTransfers/ethereum-response.js"; +import alchemyGetTokenBalancesEthereumRequest from "./code-samples/alchemy_getTokenBalances/ethereum-request.js"; +import alchemyGetTokenBalancesEthereumResponse from "./code-samples/alchemy_getTokenBalances/ethereum-response.js"; +import ethBlockNumberArbitrumRequest from "./code-samples/eth_blockNumber/arbitrum-request.js"; +import ethBlockNumberArbitrumResponse from "./code-samples/eth_blockNumber/arbitrum-response.js"; +import ethBlockNumberBaseRequest from "./code-samples/eth_blockNumber/base-request.js"; +import ethBlockNumberBaseResponse from "./code-samples/eth_blockNumber/base-response.js"; +import ethBlockNumberEthereumRequest from "./code-samples/eth_blockNumber/ethereum-request.js"; +import ethBlockNumberEthereumResponse from "./code-samples/eth_blockNumber/ethereum-response.js"; +import ethBlockNumberOptimismRequest from "./code-samples/eth_blockNumber/optimism-request.js"; +import ethBlockNumberOptimismResponse from "./code-samples/eth_blockNumber/optimism-response.js"; +import ethBlockNumberPolygonRequest from "./code-samples/eth_blockNumber/polygon-request.js"; +import ethBlockNumberPolygonResponse from "./code-samples/eth_blockNumber/polygon-response.js"; +import ethChainIdArbitrumRequest from "./code-samples/eth_chainId/arbitrum-request.js"; +import ethChainIdArbitrumResponse from "./code-samples/eth_chainId/arbitrum-response.js"; +import ethChainIdBaseRequest from "./code-samples/eth_chainId/base-request.js"; +import ethChainIdBaseResponse from "./code-samples/eth_chainId/base-response.js"; +import ethChainIdEthereumRequest from "./code-samples/eth_chainId/ethereum-request.js"; +import ethChainIdEthereumResponse from "./code-samples/eth_chainId/ethereum-response.js"; +import ethChainIdOptimismRequest from "./code-samples/eth_chainId/optimism-request.js"; +import ethChainIdOptimismResponse from "./code-samples/eth_chainId/optimism-response.js"; +import ethChainIdPolygonRequest from "./code-samples/eth_chainId/polygon-request.js"; +import ethChainIdPolygonResponse from "./code-samples/eth_chainId/polygon-response.js"; +import ethEstimateGasArbitrumRequest from "./code-samples/eth_estimateGas/arbitrum-request.js"; +import ethEstimateGasArbitrumResponse from "./code-samples/eth_estimateGas/arbitrum-response.js"; +import ethEstimateGasBaseRequest from "./code-samples/eth_estimateGas/base-request.js"; +import ethEstimateGasBaseResponse from "./code-samples/eth_estimateGas/base-response.js"; +import ethEstimateGasEthereumRequest from "./code-samples/eth_estimateGas/ethereum-request.js"; +import ethEstimateGasEthereumResponse from "./code-samples/eth_estimateGas/ethereum-response.js"; +import ethEstimateGasOptimismRequest from "./code-samples/eth_estimateGas/optimism-request.js"; +import ethEstimateGasOptimismResponse from "./code-samples/eth_estimateGas/optimism-response.js"; +import ethEstimateGasPolygonRequest from "./code-samples/eth_estimateGas/polygon-request.js"; +import ethEstimateGasPolygonResponse from "./code-samples/eth_estimateGas/polygon-response.js"; +import ethGasPriceArbitrumRequest from "./code-samples/eth_gasPrice/arbitrum-request.js"; +import ethGasPriceArbitrumResponse from "./code-samples/eth_gasPrice/arbitrum-response.js"; +import ethGasPriceBaseRequest from "./code-samples/eth_gasPrice/base-request.js"; +import ethGasPriceBaseResponse from "./code-samples/eth_gasPrice/base-response.js"; +import ethGasPriceEthereumRequest from "./code-samples/eth_gasPrice/ethereum-request.js"; +import ethGasPriceEthereumResponse from "./code-samples/eth_gasPrice/ethereum-response.js"; +import ethGasPriceOptimismRequest from "./code-samples/eth_gasPrice/optimism-request.js"; +import ethGasPriceOptimismResponse from "./code-samples/eth_gasPrice/optimism-response.js"; +import ethGasPricePolygonRequest from "./code-samples/eth_gasPrice/polygon-request.js"; +import ethGasPricePolygonResponse from "./code-samples/eth_gasPrice/polygon-response.js"; +import ethGetBalanceArbitrumRequest from "./code-samples/eth_getBalance/arbitrum-request.js"; +import ethGetBalanceArbitrumResponse from "./code-samples/eth_getBalance/arbitrum-response.js"; +import ethGetBalanceBaseRequest from "./code-samples/eth_getBalance/base-request.js"; +import ethGetBalanceBaseResponse from "./code-samples/eth_getBalance/base-response.js"; +import ethGetBalanceEthereumRequest from "./code-samples/eth_getBalance/ethereum-request.js"; +import ethGetBalanceEthereumResponse from "./code-samples/eth_getBalance/ethereum-response.js"; +import ethGetBalanceOptimismRequest from "./code-samples/eth_getBalance/optimism-request.js"; +import ethGetBalanceOptimismResponse from "./code-samples/eth_getBalance/optimism-response.js"; +import ethGetBalancePolygonRequest from "./code-samples/eth_getBalance/polygon-request.js"; +import ethGetBalancePolygonResponse from "./code-samples/eth_getBalance/polygon-response.js"; +import ethGetBlockByNumberArbitrumRequest from "./code-samples/eth_getBlockByNumber/arbitrum-request.js"; +import ethGetBlockByNumberArbitrumResponse from "./code-samples/eth_getBlockByNumber/arbitrum-response.js"; +import ethGetBlockByNumberBaseRequest from "./code-samples/eth_getBlockByNumber/base-request.js"; +import ethGetBlockByNumberBaseResponse from "./code-samples/eth_getBlockByNumber/base-response.js"; +import ethGetBlockByNumberEthereumRequest from "./code-samples/eth_getBlockByNumber/ethereum-request.js"; +import ethGetBlockByNumberEthereumResponse from "./code-samples/eth_getBlockByNumber/ethereum-response.js"; +import ethGetBlockByNumberOptimismRequest from "./code-samples/eth_getBlockByNumber/optimism-request.js"; +import ethGetBlockByNumberOptimismResponse from "./code-samples/eth_getBlockByNumber/optimism-response.js"; +import ethGetBlockByNumberPolygonRequest from "./code-samples/eth_getBlockByNumber/polygon-request.js"; +import ethGetBlockByNumberPolygonResponse from "./code-samples/eth_getBlockByNumber/polygon-response.js"; +import ethGetBlockReceiptsArbitrumRequest from "./code-samples/eth_getBlockReceipts/arbitrum-request.js"; +import ethGetBlockReceiptsArbitrumResponse from "./code-samples/eth_getBlockReceipts/arbitrum-response.js"; +import ethGetBlockReceiptsBaseRequest from "./code-samples/eth_getBlockReceipts/base-request.js"; +import ethGetBlockReceiptsBaseResponse from "./code-samples/eth_getBlockReceipts/base-response.js"; +import ethGetBlockReceiptsEthereumRequest from "./code-samples/eth_getBlockReceipts/ethereum-request.js"; +import ethGetBlockReceiptsEthereumResponse from "./code-samples/eth_getBlockReceipts/ethereum-response.js"; +import ethGetBlockReceiptsOptimismRequest from "./code-samples/eth_getBlockReceipts/optimism-request.js"; +import ethGetBlockReceiptsOptimismResponse from "./code-samples/eth_getBlockReceipts/optimism-response.js"; +import ethGetBlockReceiptsPolygonRequest from "./code-samples/eth_getBlockReceipts/polygon-request.js"; +import ethGetBlockReceiptsPolygonResponse from "./code-samples/eth_getBlockReceipts/polygon-response.js"; +import ethGetLogsArbitrumRequest from "./code-samples/eth_getLogs/arbitrum-request.js"; +import ethGetLogsArbitrumResponse from "./code-samples/eth_getLogs/arbitrum-response.js"; +import ethGetLogsBaseRequest from "./code-samples/eth_getLogs/base-request.js"; +import ethGetLogsBaseResponse from "./code-samples/eth_getLogs/base-response.js"; +import ethGetLogsEthereumRequest from "./code-samples/eth_getLogs/ethereum-request.js"; +import ethGetLogsEthereumResponse from "./code-samples/eth_getLogs/ethereum-response.js"; +import ethGetLogsOptimismRequest from "./code-samples/eth_getLogs/optimism-request.js"; +import ethGetLogsOptimismResponse from "./code-samples/eth_getLogs/optimism-response.js"; +import ethGetLogsPolygonRequest from "./code-samples/eth_getLogs/polygon-request.js"; +import ethGetLogsPolygonResponse from "./code-samples/eth_getLogs/polygon-response.js"; +import ethGetTransactionByHashArbitrumRequest from "./code-samples/eth_getTransactionByHash/arbitrum-request.js"; +import ethGetTransactionByHashArbitrumResponse from "./code-samples/eth_getTransactionByHash/arbitrum-response.js"; +import ethGetTransactionByHashBaseRequest from "./code-samples/eth_getTransactionByHash/base-request.js"; +import ethGetTransactionByHashBaseResponse from "./code-samples/eth_getTransactionByHash/base-response.js"; +import ethGetTransactionByHashEthereumRequest from "./code-samples/eth_getTransactionByHash/ethereum-request.js"; +import ethGetTransactionByHashEthereumResponse from "./code-samples/eth_getTransactionByHash/ethereum-response.js"; +import ethGetTransactionByHashOptimismRequest from "./code-samples/eth_getTransactionByHash/optimism-request.js"; +import ethGetTransactionByHashOptimismResponse from "./code-samples/eth_getTransactionByHash/optimism-response.js"; +import ethGetTransactionByHashPolygonRequest from "./code-samples/eth_getTransactionByHash/polygon-request.js"; +import ethGetTransactionByHashPolygonResponse from "./code-samples/eth_getTransactionByHash/polygon-response.js"; +import ethGetTransactionCountArbitrumRequest from "./code-samples/eth_getTransactionCount/arbitrum-request.js"; +import ethGetTransactionCountArbitrumResponse from "./code-samples/eth_getTransactionCount/arbitrum-response.js"; +import ethGetTransactionCountBaseRequest from "./code-samples/eth_getTransactionCount/base-request.js"; +import ethGetTransactionCountBaseResponse from "./code-samples/eth_getTransactionCount/base-response.js"; +import ethGetTransactionCountEthereumRequest from "./code-samples/eth_getTransactionCount/ethereum-request.js"; +import ethGetTransactionCountEthereumResponse from "./code-samples/eth_getTransactionCount/ethereum-response.js"; +import ethGetTransactionCountOptimismRequest from "./code-samples/eth_getTransactionCount/optimism-request.js"; +import ethGetTransactionCountOptimismResponse from "./code-samples/eth_getTransactionCount/optimism-response.js"; +import ethGetTransactionCountPolygonRequest from "./code-samples/eth_getTransactionCount/polygon-request.js"; +import ethGetTransactionCountPolygonResponse from "./code-samples/eth_getTransactionCount/polygon-response.js"; +import ethGetTransactionReceiptArbitrumRequest from "./code-samples/eth_getTransactionReceipt/arbitrum-request.js"; +import ethGetTransactionReceiptArbitrumResponse from "./code-samples/eth_getTransactionReceipt/arbitrum-response.js"; +import ethGetTransactionReceiptBaseRequest from "./code-samples/eth_getTransactionReceipt/base-request.js"; +import ethGetTransactionReceiptBaseResponse from "./code-samples/eth_getTransactionReceipt/base-response.js"; +import ethGetTransactionReceiptEthereumRequest from "./code-samples/eth_getTransactionReceipt/ethereum-request.js"; +import ethGetTransactionReceiptEthereumResponse from "./code-samples/eth_getTransactionReceipt/ethereum-response.js"; +import ethGetTransactionReceiptOptimismRequest from "./code-samples/eth_getTransactionReceipt/optimism-request.js"; +import ethGetTransactionReceiptOptimismResponse from "./code-samples/eth_getTransactionReceipt/optimism-response.js"; +import ethGetTransactionReceiptPolygonRequest from "./code-samples/eth_getTransactionReceipt/polygon-request.js"; +import ethGetTransactionReceiptPolygonResponse from "./code-samples/eth_getTransactionReceipt/polygon-response.js"; +import getAccountInfoSolanaRequest from "./code-samples/getAccountInfo/solana-request.js"; +import getAccountInfoSolanaResponse from "./code-samples/getAccountInfo/solana-response.js"; +import getBalanceSolanaRequest from "./code-samples/getBalance/solana-request.js"; +import getBalanceSolanaResponse from "./code-samples/getBalance/solana-response.js"; +import getLatestBlockhashSolanaRequest from "./code-samples/getLatestBlockhash/solana-request.js"; +import getLatestBlockhashSolanaResponse from "./code-samples/getLatestBlockhash/solana-response.js"; +import getSignaturesForAddressSolanaRequest from "./code-samples/getSignaturesForAddress/solana-request.js"; +import getSignaturesForAddressSolanaResponse from "./code-samples/getSignaturesForAddress/solana-response.js"; +import getTokenAccountBalanceSolanaRequest from "./code-samples/getTokenAccountBalance/solana-request.js"; +import getTokenAccountBalanceSolanaResponse from "./code-samples/getTokenAccountBalance/solana-response.js"; +import getTokenAccountsByOwnerSolanaRequest from "./code-samples/getTokenAccountsByOwner/solana-request.js"; +import getTokenAccountsByOwnerSolanaResponse from "./code-samples/getTokenAccountsByOwner/solana-response.js"; +import getTransactionSolanaRequest from "./code-samples/getTransaction/solana-request.js"; +import getTransactionSolanaResponse from "./code-samples/getTransaction/solana-response.js"; +import type { Option } from "./CodeblockSelect.js"; export const CODE_SAMPLES = { arbitrum: { diff --git a/fern/components/CodeConsole/index.tsx b/fern/components/CodeConsole/index.tsx index 47608ace8..7ee402f5a 100644 --- a/fern/components/CodeConsole/index.tsx +++ b/fern/components/CodeConsole/index.tsx @@ -1,4 +1,4 @@ -import { CodeblockSelect } from "./CodeblockSelect"; +import { CodeblockSelect } from "./CodeblockSelect.js"; import { CHAIN_OPTIONS, CODE_SAMPLES, @@ -6,9 +6,9 @@ import { type Method, getDefaultMethodForChain, getMethodOptionsForChain, -} from "./codeData"; -import { highlightCode } from "./highlightCode"; -import useTheme from "./useTheme"; +} from "./codeData.js"; +import { highlightCode } from "./highlightCode.js"; +import useTheme from "./useTheme.js"; export const CodeConsole = () => { const { isDark } = useTheme(); diff --git a/fern/components/Footer.tsx b/fern/components/Footer.tsx index bf7da1c57..0cfd827f3 100644 --- a/fern/components/Footer.tsx +++ b/fern/components/Footer.tsx @@ -1,13 +1,13 @@ -import BuiltByFern from "./BuiltWithFern"; -import AlchemyLogo from "./icons/AlchemyLogo"; -import AlchemyUniversityIcon from "./icons/AlchemyUniversityIcon"; -import DiscordIcon from "./icons/DiscordIcon"; -import EmailIcon from "./icons/EmailIcon"; -import NewsletterIcon from "./icons/NewsletterIcon"; -import RobotIcon from "./icons/RobotIcon"; -import StatusIcon from "./icons/StatusIcon"; -import SupportHubIcon from "./icons/SupportHubIcon"; -import XIcon from "./icons/XIcon"; +import BuiltByFern from "./BuiltWithFern.js"; +import AlchemyLogo from "./icons/AlchemyLogo.js"; +import AlchemyUniversityIcon from "./icons/AlchemyUniversityIcon.js"; +import DiscordIcon from "./icons/DiscordIcon.js"; +import EmailIcon from "./icons/EmailIcon.js"; +import NewsletterIcon from "./icons/NewsletterIcon.js"; +import RobotIcon from "./icons/RobotIcon.js"; +import StatusIcon from "./icons/StatusIcon.js"; +import SupportHubIcon from "./icons/SupportHubIcon.js"; +import XIcon from "./icons/XIcon.js"; /** * CONFIG diff --git a/package.json b/package.json index d36d1bfb5..97afee2e1 100644 --- a/package.json +++ b/package.json @@ -4,6 +4,7 @@ "description": "API Specs and content files for Alchemy's documentation", "packageManager": "pnpm@10.9.0", "license": "CC-BY-4.0", + "type": "module", "engines": { "node": "22.x" }, diff --git a/prettier.config.js b/prettier.config.cjs similarity index 100% rename from prettier.config.js rename to prettier.config.cjs diff --git a/scripts/add-evm-chain.ts b/scripts/add-evm-chain.ts index e80a87ee7..413dba172 100644 --- a/scripts/add-evm-chain.ts +++ b/scripts/add-evm-chain.ts @@ -11,7 +11,7 @@ import { validateChainName, validateUrl, writeChainFiles, -} from "../src/utils/addEvmChainHelpers"; +} from "../src/utils/addEvmChainHelpers.js"; const rl = readline.createInterface({ input: process.stdin, diff --git a/scripts/fix-esm-imports.sh b/scripts/fix-esm-imports.sh new file mode 100755 index 000000000..22f791903 --- /dev/null +++ b/scripts/fix-esm-imports.sh @@ -0,0 +1,23 @@ +#!/bin/bash + +# Fix all @/ and relative imports to add .js extensions for ESM compatibility +# This converts: +# from "@/foo" -> from "@/foo.js" +# from "./foo" -> from "./foo.js" +# from "../foo" -> from "../foo.js" + +# Process src/, fern/, and scripts/ directories +for dir in src fern scripts; do + if [ -d "$dir" ]; then + find "$dir" -type f \( -name "*.ts" -o -name "*.tsx" \) -exec sed -i '' \ + -e 's|from "\(@/[^"]*\)"|from "\1.js"|g' \ + -e 's|from '"'"'\(@/[^'"'"']*\)'"'"'|from '"'"'\1.js'"'"'|g' \ + -e 's|from "\(\./[^"]*\)"|from "\1.js"|g' \ + -e 's|from '"'"'\(\./[^'"'"']*\)'"'"'|from '"'"'\1.js'"'"'|g' \ + -e 's|from "\(\.\./[^"]*\)"|from "\1.js"|g' \ + -e 's|from '"'"'\(\.\./[^'"'"']*\)'"'"'|from '"'"'\1.js'"'"'|g' \ + {} \; + fi +done + +echo "✅ Fixed all imports to include .js extensions" diff --git a/scripts/generate-rpc.ts b/scripts/generate-rpc.ts index 440eef045..d1d6c639a 100644 --- a/scripts/generate-rpc.ts +++ b/scripts/generate-rpc.ts @@ -1,10 +1,10 @@ import { mkdirSync, readdirSync } from "fs"; -import { generateOpenRpcSpec } from "../src/utils/generateRpcSpecs"; +import { generateOpenRpcSpec } from "../src/utils/generateRpcSpecs.js"; import { type DerefErrorGroup, handleDerefErrors, -} from "../src/utils/generationHelpers"; +} from "../src/utils/generationHelpers.js"; const isHiddenDir = (file: string) => !file.startsWith("_") && !file.startsWith("."); diff --git a/scripts/validate-rpc.ts b/scripts/validate-rpc.ts index a035fdaac..afb43a95b 100644 --- a/scripts/validate-rpc.ts +++ b/scripts/validate-rpc.ts @@ -1,7 +1,7 @@ import { existsSync, readFileSync } from "fs"; -import { findFilesOfType } from "../src/utils/findFilesOfType"; -import { validateRpcSpec } from "../src/utils/validateRpcSpec"; +import { findFilesOfType } from "../src/utils/findFilesOfType.js"; +import { validateRpcSpec } from "../src/utils/validateRpcSpec.js"; const validateOpenRpcSpecs = async (directory: string) => { if (!directory) { diff --git a/src/content-indexer/README.md b/src/content-indexer/README.md index 4c4b476ac..45466f2ca 100644 --- a/src/content-indexer/README.md +++ b/src/content-indexer/README.md @@ -299,8 +299,9 @@ KV_URL=your_url # Algolia (required for search index) ALGOLIA_APP_ID=your_app_id ALGOLIA_ADMIN_API_KEY=your_admin_key -ALGOLIA_INDEX_NAME=alchemy_docs -ALGOLIA_WALLET_INDEX_NAME=alchemy_docs_wallets +# Base name for indices (branch/type will be auto-appended) +# Examples: main_alchemy_docs, main_alchemy_docs_sdk, abc_alchemy_docs +ALGOLIA_INDEX_NAME_BASE=alchemy_docs # GitHub (optional - increases API rate limits) GITHUB_TOKEN=your_personal_access_token diff --git a/src/content-indexer/__tests__/index.test.ts b/src/content-indexer/__tests__/index.test.ts index 00427fa6d..b41a45c1f 100644 --- a/src/content-indexer/__tests__/index.test.ts +++ b/src/content-indexer/__tests__/index.test.ts @@ -1,12 +1,12 @@ import { afterEach, beforeEach, describe, expect, test, vi } from "vitest"; -import { batchFetchContent } from "@/content-indexer/core/batch-fetcher"; -import { buildAllOutputs } from "@/content-indexer/core/build-all-outputs"; -import { ContentCache } from "@/content-indexer/core/content-cache"; -import { scanDocsYml } from "@/content-indexer/core/scanner"; -import { buildMainContentIndex } from "@/content-indexer/indexers/main"; -import { fetchFileFromGitHub } from "@/content-indexer/utils/github"; -import { repoConfigFactory } from "@/content-indexer/utils/test-factories"; +import { batchFetchContent } from "@/content-indexer/core/batch-fetcher.js"; +import { buildAllOutputs } from "@/content-indexer/core/build-all-outputs.js"; +import { ContentCache } from "@/content-indexer/core/content-cache.js"; +import { scanDocsYml } from "@/content-indexer/core/scanner.js"; +import { buildMainContentIndex } from "@/content-indexer/indexers/main.js"; +import { fetchFileFromGitHub } from "@/content-indexer/utils/github.js"; +import { repoConfigFactory } from "@/content-indexer/utils/test-factories.js"; // Mock dependencies vi.mock("@/content-indexer/utils/github", async () => { diff --git a/src/content-indexer/collectors/__tests__/algolia.test.ts b/src/content-indexer/collectors/__tests__/algolia.test.ts index e67e11af4..820c97ef1 100644 --- a/src/content-indexer/collectors/__tests__/algolia.test.ts +++ b/src/content-indexer/collectors/__tests__/algolia.test.ts @@ -1,6 +1,6 @@ import { describe, expect, test } from "vitest"; -import { AlgoliaCollector } from "../algolia"; +import { AlgoliaCollector } from "../algolia.js"; describe("AlgoliaCollector", () => { test("should initialize with empty records", () => { diff --git a/src/content-indexer/collectors/__tests__/navigation-trees.test.ts b/src/content-indexer/collectors/__tests__/navigation-trees.test.ts index 005b0c87c..6a212cc58 100644 --- a/src/content-indexer/collectors/__tests__/navigation-trees.test.ts +++ b/src/content-indexer/collectors/__tests__/navigation-trees.test.ts @@ -1,6 +1,6 @@ import { describe, expect, test } from "vitest"; -import { NavigationTreesCollector } from "../navigation-trees"; +import { NavigationTreesCollector } from "../navigation-trees.js"; describe("NavigationTreesCollector", () => { test("should initialize with empty trees", () => { diff --git a/src/content-indexer/collectors/__tests__/path-index.test.ts b/src/content-indexer/collectors/__tests__/path-index.test.ts index 8ba8d55d2..5c5a6ad77 100644 --- a/src/content-indexer/collectors/__tests__/path-index.test.ts +++ b/src/content-indexer/collectors/__tests__/path-index.test.ts @@ -1,6 +1,6 @@ import { describe, expect, test } from "vitest"; -import { PathIndexCollector } from "../path-index"; +import { PathIndexCollector } from "../path-index.js"; describe("PathIndexCollector", () => { test("should initialize with empty index", () => { diff --git a/src/content-indexer/collectors/__tests__/processing-context.test.ts b/src/content-indexer/collectors/__tests__/processing-context.test.ts index 99b0d0cc3..050bf28b9 100644 --- a/src/content-indexer/collectors/__tests__/processing-context.test.ts +++ b/src/content-indexer/collectors/__tests__/processing-context.test.ts @@ -1,8 +1,8 @@ import { describe, expect, test } from "vitest"; -import type { NavItem } from "@/content-indexer/types/navigation"; +import type { NavItem } from "@/content-indexer/types/navigation.js"; -import { ProcessingContext } from "../processing-context"; +import { ProcessingContext } from "../processing-context.js"; describe("ProcessingContext", () => { test("should initialize with empty state", () => { diff --git a/src/content-indexer/collectors/algolia.ts b/src/content-indexer/collectors/algolia.ts index 82f658c40..a8d6c2729 100644 --- a/src/content-indexer/collectors/algolia.ts +++ b/src/content-indexer/collectors/algolia.ts @@ -1,7 +1,7 @@ import { createHash } from "crypto"; -import type { AlgoliaRecord } from "@/content-indexer/types/algolia"; -import type { NavItem } from "@/content-indexer/types/navigation"; +import type { AlgoliaRecord } from "@/content-indexer/types/algolia.js"; +import type { NavItem } from "@/content-indexer/types/navigation.js"; /** * Extracts breadcrumb titles from NavItems for Algolia. diff --git a/src/content-indexer/collectors/navigation-trees.ts b/src/content-indexer/collectors/navigation-trees.ts index 1a962345f..03820a22d 100644 --- a/src/content-indexer/collectors/navigation-trees.ts +++ b/src/content-indexer/collectors/navigation-trees.ts @@ -1,7 +1,7 @@ import type { NavItem, NavigationTreesByTab, -} from "@/content-indexer/types/navigation"; +} from "@/content-indexer/types/navigation.js"; /** * Collector for accumulating navigation trees during content processing. diff --git a/src/content-indexer/collectors/path-index.ts b/src/content-indexer/collectors/path-index.ts index 65f2ea35f..010657743 100644 --- a/src/content-indexer/collectors/path-index.ts +++ b/src/content-indexer/collectors/path-index.ts @@ -1,7 +1,7 @@ import type { PathIndex, PathIndexEntry, -} from "@/content-indexer/types/pathIndex"; +} from "@/content-indexer/types/pathIndex.js"; /** * Collector for accumulating path index entries during content processing. diff --git a/src/content-indexer/collectors/processing-context.ts b/src/content-indexer/collectors/processing-context.ts index 48abe6310..9392b2b88 100644 --- a/src/content-indexer/collectors/processing-context.ts +++ b/src/content-indexer/collectors/processing-context.ts @@ -1,16 +1,16 @@ -import type { AlgoliaRecord } from "@/content-indexer/types/algolia"; +import type { AlgoliaRecord } from "@/content-indexer/types/algolia.js"; import type { NavItem, NavigationTreesByTab, -} from "@/content-indexer/types/navigation"; +} from "@/content-indexer/types/navigation.js"; import type { PathIndex, PathIndexEntry, -} from "@/content-indexer/types/pathIndex"; +} from "@/content-indexer/types/pathIndex.js"; -import { AlgoliaCollector, type AddRecordParams } from "./algolia"; -import { NavigationTreesCollector } from "./navigation-trees"; -import { PathIndexCollector } from "./path-index"; +import { AlgoliaCollector, type AddRecordParams } from "./algolia.js"; +import { NavigationTreesCollector } from "./navigation-trees.js"; +import { PathIndexCollector } from "./path-index.js"; /** * Result of building all outputs from content processing. diff --git a/src/content-indexer/constants/metadata.ts b/src/content-indexer/constants/metadata.ts index d24ea6709..2889d80bb 100644 --- a/src/content-indexer/constants/metadata.ts +++ b/src/content-indexer/constants/metadata.ts @@ -1,4 +1,4 @@ -import { DOCS_BASE_URL } from "./links"; +import { DOCS_BASE_URL } from "./links.js"; // TODO: This file is copied from docs-site but not used in the content indexer // It's kept for potential future use but Next.js types are stubbed diff --git a/src/content-indexer/core/__tests__/batch-fetcher.test.ts b/src/content-indexer/core/__tests__/batch-fetcher.test.ts index e1654ffc7..3a8f4f95a 100644 --- a/src/content-indexer/core/__tests__/batch-fetcher.test.ts +++ b/src/content-indexer/core/__tests__/batch-fetcher.test.ts @@ -1,14 +1,14 @@ import { afterEach, beforeEach, describe, expect, test, vi } from "vitest"; -import type { OpenApiSpec } from "@/content-indexer/types/specs"; -import { fetchApiSpec } from "@/content-indexer/utils/apiSpecs"; -import { fetchFileFromGitHub } from "@/content-indexer/utils/github"; +import type { OpenApiSpec } from "@/content-indexer/types/specs.js"; +import { fetchApiSpec } from "@/content-indexer/utils/apiSpecs.js"; +import { fetchFileFromGitHub } from "@/content-indexer/utils/github.js"; import { openApiSpecFactory, repoConfigFactory, -} from "@/content-indexer/utils/test-factories"; +} from "@/content-indexer/utils/test-factories.js"; -import { batchFetchContent } from "../batch-fetcher"; +import { batchFetchContent } from "../batch-fetcher.js"; // Mock dependencies vi.mock("@/content-indexer/utils/github", async () => { diff --git a/src/content-indexer/core/__tests__/build-all-outputs.test.ts b/src/content-indexer/core/__tests__/build-all-outputs.test.ts index 0e4337a61..6f8f4441b 100644 --- a/src/content-indexer/core/__tests__/build-all-outputs.test.ts +++ b/src/content-indexer/core/__tests__/build-all-outputs.test.ts @@ -1,11 +1,11 @@ import { beforeEach, describe, expect, test, vi } from "vitest"; -import type { DocsYml } from "@/content-indexer/types/docsYaml"; -import { repoConfigFactory } from "@/content-indexer/utils/test-factories"; -import { visitNavigationItem } from "@/content-indexer/visitors"; +import type { DocsYml } from "@/content-indexer/types/docsYaml.js"; +import { repoConfigFactory } from "@/content-indexer/utils/test-factories.js"; +import { visitNavigationItem } from "@/content-indexer/visitors/index.js"; -import { buildAllOutputs } from "../build-all-outputs"; -import { ContentCache } from "../content-cache"; +import { buildAllOutputs } from "../build-all-outputs.js"; +import { ContentCache } from "../content-cache.js"; // Mock the visitor vi.mock("@/content-indexer/visitors", () => ({ diff --git a/src/content-indexer/core/__tests__/content-cache.test.ts b/src/content-indexer/core/__tests__/content-cache.test.ts index ff8183aa1..c563786ee 100644 --- a/src/content-indexer/core/__tests__/content-cache.test.ts +++ b/src/content-indexer/core/__tests__/content-cache.test.ts @@ -3,9 +3,9 @@ import { describe, expect, test } from "vitest"; import { openApiSpecFactory, openRpcSpecFactory, -} from "@/content-indexer/utils/test-factories"; +} from "@/content-indexer/utils/test-factories.js"; -import { ContentCache } from "../content-cache"; +import { ContentCache } from "../content-cache.js"; describe("ContentCache", () => { test("should initialize with empty caches", () => { diff --git a/src/content-indexer/core/__tests__/path-builder.test.ts b/src/content-indexer/core/__tests__/path-builder.test.ts index b9d527da6..84198fbf9 100644 --- a/src/content-indexer/core/__tests__/path-builder.test.ts +++ b/src/content-indexer/core/__tests__/path-builder.test.ts @@ -1,6 +1,6 @@ import { describe, expect, test } from "vitest"; -import { PathBuilder } from "../path-builder"; +import { PathBuilder } from "../path-builder.js"; describe("PathBuilder", () => { test("should initialize with empty parts", () => { diff --git a/src/content-indexer/core/__tests__/scanner.test.ts b/src/content-indexer/core/__tests__/scanner.test.ts index ac78ff8c7..848386696 100644 --- a/src/content-indexer/core/__tests__/scanner.test.ts +++ b/src/content-indexer/core/__tests__/scanner.test.ts @@ -1,8 +1,8 @@ import { describe, expect, test } from "vitest"; -import type { DocsYml } from "@/content-indexer/types/docsYaml"; +import type { DocsYml } from "@/content-indexer/types/docsYaml.js"; -import { scanDocsYml } from "../scanner"; +import { scanDocsYml } from "../scanner.js"; describe("scanner", () => { test("should throw error if navigation is missing", () => { diff --git a/src/content-indexer/core/batch-fetcher.ts b/src/content-indexer/core/batch-fetcher.ts index 7feb905d2..d19891f30 100644 --- a/src/content-indexer/core/batch-fetcher.ts +++ b/src/content-indexer/core/batch-fetcher.ts @@ -1,15 +1,15 @@ import matter from "gray-matter"; import path from "path"; -import { fetchApiSpec } from "@/content-indexer/utils/apiSpecs"; -import { readLocalMdxFile } from "@/content-indexer/utils/filesystem"; +import { fetchApiSpec } from "@/content-indexer/utils/apiSpecs.js"; +import { readLocalMdxFile } from "@/content-indexer/utils/filesystem.js"; import { fetchFileFromGitHub, type RepoConfig, -} from "@/content-indexer/utils/github"; +} from "@/content-indexer/utils/github.js"; -import { ContentCache } from "./content-cache"; -import type { ScanResult } from "./scanner"; +import { ContentCache } from "./content-cache.js"; +import type { ScanResult } from "./scanner.js"; /** * Content source configuration - either filesystem or GitHub API diff --git a/src/content-indexer/core/build-all-outputs.ts b/src/content-indexer/core/build-all-outputs.ts index f9242df41..fa384d3e2 100644 --- a/src/content-indexer/core/build-all-outputs.ts +++ b/src/content-indexer/core/build-all-outputs.ts @@ -3,13 +3,13 @@ import { kebabCase } from "lodash-es"; import { ProcessingContext, type BuildAllOutputsResult, -} from "@/content-indexer/collectors/processing-context"; -import type { ContentCache } from "@/content-indexer/core/content-cache"; -import type { DocsYml } from "@/content-indexer/types/docsYaml"; -import { DOCS_REPO, type RepoConfig } from "@/content-indexer/utils/github"; -import { visitNavigationItem } from "@/content-indexer/visitors"; +} from "@/content-indexer/collectors/processing-context.js"; +import type { ContentCache } from "@/content-indexer/core/content-cache.js"; +import type { DocsYml } from "@/content-indexer/types/docsYaml.js"; +import { DOCS_REPO, type RepoConfig } from "@/content-indexer/utils/github.js"; +import { visitNavigationItem } from "@/content-indexer/visitors/index.js"; -import { PathBuilder } from "./path-builder"; +import { PathBuilder } from "./path-builder.js"; /** * Phase 3 of the content indexing pipeline. diff --git a/src/content-indexer/core/content-cache.ts b/src/content-indexer/core/content-cache.ts index 3861ea1b6..8b5d6fdb2 100644 --- a/src/content-indexer/core/content-cache.ts +++ b/src/content-indexer/core/content-cache.ts @@ -1,4 +1,7 @@ -import type { OpenApiSpec, OpenRpcSpec } from "@/content-indexer/types/specs"; +import type { + OpenApiSpec, + OpenRpcSpec, +} from "@/content-indexer/types/specs.js"; /** * Cached MDX content with parsed frontmatter and body. diff --git a/src/content-indexer/core/scanner.ts b/src/content-indexer/core/scanner.ts index 33970de1e..bdc3558bb 100644 --- a/src/content-indexer/core/scanner.ts +++ b/src/content-indexer/core/scanner.ts @@ -6,7 +6,7 @@ import { isSectionConfig, type DocsYml, type NavigationItem, -} from "@/content-indexer/types/docsYaml"; +} from "@/content-indexer/types/docsYaml.js"; /** * Result of scanning docs.yml for all file paths and API specs. diff --git a/src/content-indexer/index.ts b/src/content-indexer/index.ts index 72fb985e3..9dfcb085d 100644 --- a/src/content-indexer/index.ts +++ b/src/content-indexer/index.ts @@ -1,12 +1,12 @@ #!/usr/bin/env tsx import path from "path"; -import { buildChangelogIndex } from "@/content-indexer/indexers/changelog"; -import { buildMainContentIndex } from "@/content-indexer/indexers/main"; -import { buildSDKContentIndex } from "@/content-indexer/indexers/sdk"; -import { uploadToAlgolia } from "@/content-indexer/uploaders/algolia"; -import { storeToRedis } from "@/content-indexer/uploaders/redis"; -import { DOCS_REPO, WALLET_REPO } from "@/content-indexer/utils/github"; +import { buildChangelogIndex } from "@/content-indexer/indexers/changelog.js"; +import { buildMainContentIndex } from "@/content-indexer/indexers/main.js"; +import { buildSDKContentIndex } from "@/content-indexer/indexers/sdk.js"; +import { uploadToAlgolia } from "@/content-indexer/uploaders/algolia.js"; +import { storeToRedis } from "@/content-indexer/uploaders/redis.js"; +import { DOCS_REPO, WALLET_REPO } from "@/content-indexer/utils/github.js"; // ============================================================================ // CLI Argument Parsing @@ -68,7 +68,10 @@ const runMainIndexer = async ( branchId, pathIndexSuffix: "main", }), - uploadToAlgolia(algoliaRecords, { indexerType: "main" }), + uploadToAlgolia(algoliaRecords, { + indexerType: "main", + branchId, + }), ]); console.info( @@ -101,7 +104,10 @@ const runSDKIndexer = async (branchId: string) => { mergeSDKIntoWallets: true, }, ), - uploadToAlgolia(algoliaRecords, { indexerType: "sdk" }), + uploadToAlgolia(algoliaRecords, { + indexerType: "sdk", + branchId, + }), ]); console.info( @@ -128,7 +134,10 @@ const runChangelogIndexer = async (branchId: string) => { branchId, pathIndexSuffix: "changelog", }), - uploadToAlgolia(algoliaRecords, { indexerType: "changelog" }), + uploadToAlgolia(algoliaRecords, { + indexerType: "changelog", + branchId, + }), ]); console.info( diff --git a/src/content-indexer/indexers/changelog.ts b/src/content-indexer/indexers/changelog.ts index a08d480c8..6f5898131 100644 --- a/src/content-indexer/indexers/changelog.ts +++ b/src/content-indexer/indexers/changelog.ts @@ -1,5 +1,5 @@ -import type { AlgoliaRecord } from "@/content-indexer/types/algolia"; -import type { PathIndex } from "@/content-indexer/types/pathIndex"; +import type { AlgoliaRecord } from "@/content-indexer/types/algolia.js"; +import type { PathIndex } from "@/content-indexer/types/pathIndex.js"; export interface ChangelogIndexerConfig { localBasePath: string; // Path to fern/changelog/ directory diff --git a/src/content-indexer/indexers/main.ts b/src/content-indexer/indexers/main.ts index 65fc97fda..921c62576 100644 --- a/src/content-indexer/indexers/main.ts +++ b/src/content-indexer/indexers/main.ts @@ -1,12 +1,12 @@ -import type { BuildAllOutputsResult } from "@/content-indexer/collectors/processing-context"; +import type { BuildAllOutputsResult } from "@/content-indexer/collectors/processing-context.js"; import { batchFetchContent, type ContentSource, -} from "@/content-indexer/core/batch-fetcher"; -import { buildAllOutputs } from "@/content-indexer/core/build-all-outputs"; -import { scanDocsYml } from "@/content-indexer/core/scanner"; -import { readLocalDocsYml } from "@/content-indexer/utils/filesystem"; -import type { RepoConfig } from "@/content-indexer/utils/github"; +} from "@/content-indexer/core/batch-fetcher.js"; +import { buildAllOutputs } from "@/content-indexer/core/build-all-outputs.js"; +import { scanDocsYml } from "@/content-indexer/core/scanner.js"; +import { readLocalDocsYml } from "@/content-indexer/utils/filesystem.js"; +import type { RepoConfig } from "@/content-indexer/utils/github.js"; export interface MainIndexerConfig { mode: "preview" | "production"; diff --git a/src/content-indexer/indexers/sdk.ts b/src/content-indexer/indexers/sdk.ts index 1d5070d92..44312f4c8 100644 --- a/src/content-indexer/indexers/sdk.ts +++ b/src/content-indexer/indexers/sdk.ts @@ -3,17 +3,17 @@ import yaml from "js-yaml"; import { batchFetchContent, type ContentSource, -} from "@/content-indexer/core/batch-fetcher"; -import { buildAllOutputs } from "@/content-indexer/core/build-all-outputs"; -import { scanDocsYml } from "@/content-indexer/core/scanner"; -import type { AlgoliaRecord } from "@/content-indexer/types/algolia"; -import type { DocsYml } from "@/content-indexer/types/docsYaml"; -import type { NavItem } from "@/content-indexer/types/navigation"; -import type { PathIndex } from "@/content-indexer/types/pathIndex"; +} from "@/content-indexer/core/batch-fetcher.js"; +import { buildAllOutputs } from "@/content-indexer/core/build-all-outputs.js"; +import { scanDocsYml } from "@/content-indexer/core/scanner.js"; +import type { AlgoliaRecord } from "@/content-indexer/types/algolia.js"; +import type { DocsYml } from "@/content-indexer/types/docsYaml.js"; +import type { NavItem } from "@/content-indexer/types/navigation.js"; +import type { PathIndex } from "@/content-indexer/types/pathIndex.js"; import { fetchFileFromGitHub, type RepoConfig, -} from "@/content-indexer/utils/github"; +} from "@/content-indexer/utils/github.js"; export interface SDKIndexerConfig { sdkRepoConfig: RepoConfig; // aa-sdk repo config diff --git a/src/content-indexer/uploaders/__tests__/algolia.test.ts b/src/content-indexer/uploaders/__tests__/algolia.test.ts index 02c386022..88c82f283 100644 --- a/src/content-indexer/uploaders/__tests__/algolia.test.ts +++ b/src/content-indexer/uploaders/__tests__/algolia.test.ts @@ -1,8 +1,8 @@ import { describe, expect, test, vi } from "vitest"; -import type { AlgoliaRecord } from "@/content-indexer/types/algolia"; +import type { AlgoliaRecord } from "@/content-indexer/types/algolia.js"; -import { uploadToAlgolia } from "../algolia"; +import { uploadToAlgolia } from "../algolia.js"; describe("uploadToAlgolia", () => { test("should skip if no ALGOLIA_APP_ID", async () => { @@ -15,7 +15,7 @@ describe("uploadToAlgolia", () => { const consoleSpy = vi.spyOn(console, "warn").mockImplementation(() => {}); const records: AlgoliaRecord[] = []; - await uploadToAlgolia(records, { indexerType: "main" }); + await uploadToAlgolia(records, { indexerType: "main", branchId: "main" }); expect(consoleSpy).toHaveBeenCalledWith( expect.stringContaining("Algolia credentials not found"), @@ -36,7 +36,7 @@ describe("uploadToAlgolia", () => { const consoleSpy = vi.spyOn(console, "warn").mockImplementation(() => {}); const records: AlgoliaRecord[] = []; - await uploadToAlgolia(records, { indexerType: "main" }); + await uploadToAlgolia(records, { indexerType: "main", branchId: "main" }); expect(consoleSpy).toHaveBeenCalledWith( expect.stringContaining("Algolia credentials not found"), @@ -47,47 +47,51 @@ describe("uploadToAlgolia", () => { process.env.ALGOLIA_ADMIN_API_KEY = originalKey; }); - test("should skip if index name not configured for docs mode", async () => { + test("should skip if base index name not configured", async () => { const originalAppId = process.env.ALGOLIA_APP_ID; const originalKey = process.env.ALGOLIA_ADMIN_API_KEY; - const originalIndexName = process.env.ALGOLIA_INDEX_NAME; + const originalBaseName = process.env.ALGOLIA_INDEX_NAME_BASE; process.env.ALGOLIA_APP_ID = "test-app-id"; process.env.ALGOLIA_ADMIN_API_KEY = "test-key"; - delete process.env.ALGOLIA_INDEX_NAME; + delete process.env.ALGOLIA_INDEX_NAME_BASE; const consoleSpy = vi.spyOn(console, "warn").mockImplementation(() => {}); const records: AlgoliaRecord[] = []; - await uploadToAlgolia(records, { indexerType: "main" }); + await uploadToAlgolia(records, { indexerType: "main", branchId: "main" }); - expect(consoleSpy).toHaveBeenCalledWith(expect.stringContaining("not set")); + expect(consoleSpy).toHaveBeenCalledWith( + expect.stringContaining("ALGOLIA_INDEX_NAME_BASE not set"), + ); consoleSpy.mockRestore(); process.env.ALGOLIA_APP_ID = originalAppId; process.env.ALGOLIA_ADMIN_API_KEY = originalKey; - process.env.ALGOLIA_INDEX_NAME = originalIndexName; + process.env.ALGOLIA_INDEX_NAME_BASE = originalBaseName; }); - test("should skip if wallet index name not configured", async () => { + test("should skip if base index name not configured", async () => { const originalAppId = process.env.ALGOLIA_APP_ID; const originalKey = process.env.ALGOLIA_ADMIN_API_KEY; - const originalWalletIndexName = process.env.ALGOLIA_WALLET_INDEX_NAME; + const originalBaseName = process.env.ALGOLIA_INDEX_NAME_BASE; process.env.ALGOLIA_APP_ID = "test-app-id"; process.env.ALGOLIA_ADMIN_API_KEY = "test-key"; - delete process.env.ALGOLIA_WALLET_INDEX_NAME; + delete process.env.ALGOLIA_INDEX_NAME_BASE; const consoleSpy = vi.spyOn(console, "warn").mockImplementation(() => {}); const records: AlgoliaRecord[] = []; - await uploadToAlgolia(records, { indexerType: "sdk" }); + await uploadToAlgolia(records, { indexerType: "main", branchId: "main" }); - expect(consoleSpy).toHaveBeenCalledWith(expect.stringContaining("not set")); + expect(consoleSpy).toHaveBeenCalledWith( + expect.stringContaining("ALGOLIA_INDEX_NAME_BASE not set"), + ); consoleSpy.mockRestore(); process.env.ALGOLIA_APP_ID = originalAppId; process.env.ALGOLIA_ADMIN_API_KEY = originalKey; - process.env.ALGOLIA_WALLET_INDEX_NAME = originalWalletIndexName; + process.env.ALGOLIA_INDEX_NAME_BASE = originalBaseName; }); }); diff --git a/src/content-indexer/uploaders/__tests__/redis.test.ts b/src/content-indexer/uploaders/__tests__/redis.test.ts index 0bd01a3c0..bfddd5f22 100644 --- a/src/content-indexer/uploaders/__tests__/redis.test.ts +++ b/src/content-indexer/uploaders/__tests__/redis.test.ts @@ -1,9 +1,9 @@ import { beforeEach, describe, expect, test, vi } from "vitest"; -import type { NavigationTreesByTab } from "@/content-indexer/types/navigation"; -import type { PathIndex } from "@/content-indexer/types/pathIndex"; +import type { NavigationTreesByTab } from "@/content-indexer/types/navigation.js"; +import type { PathIndex } from "@/content-indexer/types/pathIndex.js"; -import { storeToRedis } from "../redis"; +import { storeToRedis } from "../redis.js"; // Mock Redis const mockSet = vi.fn().mockResolvedValue("OK"); diff --git a/src/content-indexer/uploaders/algolia.ts b/src/content-indexer/uploaders/algolia.ts index 4479a2b9a..b6de96bd6 100644 --- a/src/content-indexer/uploaders/algolia.ts +++ b/src/content-indexer/uploaders/algolia.ts @@ -1,7 +1,32 @@ import { algoliasearch } from "algoliasearch"; -import type { AlgoliaRecord } from "@/content-indexer/types/algolia"; -import { truncateRecord } from "@/content-indexer/utils/truncate-record"; +import type { AlgoliaRecord } from "@/content-indexer/types/algolia.js"; +import { truncateRecord } from "@/content-indexer/utils/truncate-record.js"; + +/** + * Builds an Algolia index name with branch and type scoping. + * Pattern: {branchId}_{baseName}[_{indexerType}] + * + * Examples: + * - main_alchemy_docs (main branch, main content) + * - main_alchemy_docs_sdk (main branch, SDK content) + * - abc_alchemy_docs (branch-abc, main content) + * - abc_alchemy_docs_sdk (branch-abc, SDK content) + */ +const buildIndexName = ( + base: string, + indexerType: "main" | "sdk" | "changelog", + branchId: string, +): string => { + const parts = [branchId, base]; + + // Add type suffix (except for main content) + if (indexerType !== "main") { + parts.push(indexerType); + } + + return parts.join("_"); +}; /** * Uploads records to Algolia using atomic index swap for zero-downtime updates. @@ -16,15 +41,18 @@ import { truncateRecord } from "@/content-indexer/utils/truncate-record"; * @param records - Algolia records to upload * @param options - Configuration options * @param options.indexerType - Type of indexer ("main", "sdk", or "changelog") + * @param options.branchId - Branch identifier for index naming (e.g., "main", "branch-abc") */ export const uploadToAlgolia = async ( records: AlgoliaRecord[], options: { indexerType: "main" | "sdk" | "changelog"; + branchId: string; }, ): Promise => { const appId = process.env.ALGOLIA_APP_ID; const adminKey = process.env.ALGOLIA_ADMIN_API_KEY; + const baseName = process.env.ALGOLIA_INDEX_NAME_BASE; if (!appId || !adminKey) { console.warn("⚠️ Algolia credentials not found. Skipping Algolia upload."); @@ -34,27 +62,19 @@ export const uploadToAlgolia = async ( return; } - // Determine target index name based on indexer type - const indexEnvMap = { - main: process.env.ALGOLIA_INDEX_NAME, - sdk: process.env.ALGOLIA_WALLET_INDEX_NAME, - changelog: process.env.ALGOLIA_CHANGELOG_INDEX_NAME, - }; - - const targetIndexName = indexEnvMap[options.indexerType]; - - if (!targetIndexName) { - const envVarMap = { - main: "ALGOLIA_INDEX_NAME", - sdk: "ALGOLIA_WALLET_INDEX_NAME", - changelog: "ALGOLIA_CHANGELOG_INDEX_NAME", - }; + if (!baseName) { console.warn( - `⚠️ ${envVarMap[options.indexerType]} not set. Skipping Algolia upload.`, + "⚠️ ALGOLIA_INDEX_NAME_BASE not set. Skipping Algolia upload.", ); return; } + const targetIndexName = buildIndexName( + baseName, + options.indexerType, + options.branchId, + ); + const client = algoliasearch(appId, adminKey); const tempIndexName = `${targetIndexName}_temp`; diff --git a/src/content-indexer/uploaders/redis.ts b/src/content-indexer/uploaders/redis.ts index dc59927c7..337af2973 100644 --- a/src/content-indexer/uploaders/redis.ts +++ b/src/content-indexer/uploaders/redis.ts @@ -1,9 +1,9 @@ import type { NavigationTree, NavigationTreesByTab, -} from "@/content-indexer/types/navigation"; -import type { PathIndex } from "@/content-indexer/types/pathIndex"; -import { getRedis } from "@/content-indexer/utils/redis"; +} from "@/content-indexer/types/navigation.js"; +import type { PathIndex } from "@/content-indexer/types/pathIndex.js"; +import { getRedis } from "@/content-indexer/utils/redis.js"; // Helper to count nav items recursively const countItems = (items: NavigationTree): number => { diff --git a/src/content-indexer/utils/__tests__/navigation-helpers.test.ts b/src/content-indexer/utils/__tests__/navigation-helpers.test.ts index 7223c19e4..5ca6b378f 100644 --- a/src/content-indexer/utils/__tests__/navigation-helpers.test.ts +++ b/src/content-indexer/utils/__tests__/navigation-helpers.test.ts @@ -1,6 +1,6 @@ import { describe, expect, test } from "vitest"; -import { createBreadcrumbNavItem } from "../navigation-helpers"; +import { createBreadcrumbNavItem } from "../navigation-helpers.js"; describe("navigation-helpers", () => { describe("createBreadcrumbNavItem", () => { diff --git a/src/content-indexer/utils/__tests__/normalization.test.ts b/src/content-indexer/utils/__tests__/normalization.test.ts index 929b9b6fa..b9f04000f 100644 --- a/src/content-indexer/utils/__tests__/normalization.test.ts +++ b/src/content-indexer/utils/__tests__/normalization.test.ts @@ -3,8 +3,8 @@ import { describe, expect, test } from "vitest"; import { normalizeFilePath, normalizeSlug, -} from "@/content-indexer/utils/normalization"; -import { repoConfigFactory } from "@/content-indexer/utils/test-factories"; +} from "@/content-indexer/utils/normalization.js"; +import { repoConfigFactory } from "@/content-indexer/utils/test-factories.js"; describe("normalization utils", () => { describe("normalizeSlug", () => { diff --git a/src/content-indexer/utils/__tests__/openapi.test.ts b/src/content-indexer/utils/__tests__/openapi.test.ts index 12c20d128..a75ac27c4 100644 --- a/src/content-indexer/utils/__tests__/openapi.test.ts +++ b/src/content-indexer/utils/__tests__/openapi.test.ts @@ -1,13 +1,13 @@ import { describe, expect, test } from "vitest"; -import { PathBuilder } from "@/content-indexer/core/path-builder"; +import { PathBuilder } from "@/content-indexer/core/path-builder.js"; import { buildOperationPath, extractOpenApiOperations, getOperationDescription, getOperationTitle, -} from "../openapi"; +} from "../openapi.js"; describe("openapi utils", () => { describe("extractOpenApiOperations", () => { diff --git a/src/content-indexer/utils/__tests__/openrpc.test.ts b/src/content-indexer/utils/__tests__/openrpc.test.ts index b23943ee7..56003bc48 100644 --- a/src/content-indexer/utils/__tests__/openrpc.test.ts +++ b/src/content-indexer/utils/__tests__/openrpc.test.ts @@ -1,8 +1,8 @@ import { describe, expect, test } from "vitest"; -import type { OpenRpcSpec } from "@/content-indexer/types/specs"; +import type { OpenRpcSpec } from "@/content-indexer/types/specs.js"; -import { isValidOpenRpcSpec } from "../openrpc"; +import { isValidOpenRpcSpec } from "../openrpc.js"; describe("openrpc utils", () => { describe("isValidOpenRpcSpec", () => { diff --git a/src/content-indexer/utils/__tests__/truncate-record.test.ts b/src/content-indexer/utils/__tests__/truncate-record.test.ts index 6367ee229..031bd36b8 100644 --- a/src/content-indexer/utils/__tests__/truncate-record.test.ts +++ b/src/content-indexer/utils/__tests__/truncate-record.test.ts @@ -1,8 +1,8 @@ import { describe, expect, test, vi } from "vitest"; -import type { AlgoliaRecord } from "@/content-indexer/types/algolia"; +import type { AlgoliaRecord } from "@/content-indexer/types/algolia.js"; -import { truncateRecord } from "../truncate-record"; +import { truncateRecord } from "../truncate-record.js"; describe("truncateRecord", () => { test("should return record unchanged if under size limit", () => { diff --git a/src/content-indexer/utils/apiSpecs.ts b/src/content-indexer/utils/apiSpecs.ts index 1c6568155..4f05ce60e 100644 --- a/src/content-indexer/utils/apiSpecs.ts +++ b/src/content-indexer/utils/apiSpecs.ts @@ -3,9 +3,9 @@ import type { OpenApiSpec, OpenRpcSpec, SpecType, -} from "@/content-indexer/types/specs"; +} from "@/content-indexer/types/specs.js"; -import { fetchWithRetries } from "./fetchWithRetries"; +import { fetchWithRetries } from "./fetchWithRetries.js"; interface MetadataJson { files: string[]; diff --git a/src/content-indexer/utils/filesystem.ts b/src/content-indexer/utils/filesystem.ts index c1f089452..6e821d2f8 100644 --- a/src/content-indexer/utils/filesystem.ts +++ b/src/content-indexer/utils/filesystem.ts @@ -3,7 +3,7 @@ import matter from "gray-matter"; import yaml from "js-yaml"; import path from "path"; -import type { DocsYml } from "@/content-indexer/types/docsYaml"; +import type { DocsYml } from "@/content-indexer/types/docsYaml.js"; /** * Reads a file from the local filesystem diff --git a/src/content-indexer/utils/navigation-helpers.ts b/src/content-indexer/utils/navigation-helpers.ts index 967cbf740..f62c9e78e 100644 --- a/src/content-indexer/utils/navigation-helpers.ts +++ b/src/content-indexer/utils/navigation-helpers.ts @@ -1,4 +1,4 @@ -import type { NavItem } from "@/content-indexer/types/navigation"; +import type { NavItem } from "@/content-indexer/types/navigation.js"; /** * Creates breadcrumb-safe navigation item (without populated children). diff --git a/src/content-indexer/utils/normalization.ts b/src/content-indexer/utils/normalization.ts index 2da5beb27..5134ecc07 100644 --- a/src/content-indexer/utils/normalization.ts +++ b/src/content-indexer/utils/normalization.ts @@ -1,4 +1,4 @@ -import type { RepoConfig } from "@/content-indexer/utils/github"; +import type { RepoConfig } from "@/content-indexer/utils/github.js"; /** * Normalizes a frontmatter slug by removing the "docs/" prefix. diff --git a/src/content-indexer/utils/openapi.ts b/src/content-indexer/utils/openapi.ts index 0a8813c93..4ba06524a 100644 --- a/src/content-indexer/utils/openapi.ts +++ b/src/content-indexer/utils/openapi.ts @@ -1,8 +1,8 @@ import { kebabCase } from "lodash-es"; import type { OpenAPIV3 } from "openapi-types"; -import { HTTP_METHODS } from "@/content-indexer/constants/http"; -import type { PathBuilder } from "@/content-indexer/core/path-builder"; +import { HTTP_METHODS } from "@/content-indexer/constants/http.js"; +import type { PathBuilder } from "@/content-indexer/core/path-builder.js"; export interface ExtractedOperation { operationId: string; diff --git a/src/content-indexer/utils/openrpc.ts b/src/content-indexer/utils/openrpc.ts index 79c315a0a..4edd24338 100644 --- a/src/content-indexer/utils/openrpc.ts +++ b/src/content-indexer/utils/openrpc.ts @@ -1,4 +1,4 @@ -import type { OpenRpcSpec } from "@/content-indexer/types/specs"; +import type { OpenRpcSpec } from "@/content-indexer/types/specs.js"; /** * Type guard to check if a spec is a valid OpenRPC spec with methods array. diff --git a/src/content-indexer/utils/test-factories.ts b/src/content-indexer/utils/test-factories.ts index 18d0b1f93..d35891d10 100644 --- a/src/content-indexer/utils/test-factories.ts +++ b/src/content-indexer/utils/test-factories.ts @@ -1,5 +1,8 @@ -import type { OpenApiSpec, OpenRpcSpec } from "@/content-indexer/types/specs"; -import type { RepoConfig } from "@/content-indexer/utils/github"; +import type { + OpenApiSpec, + OpenRpcSpec, +} from "@/content-indexer/types/specs.js"; +import type { RepoConfig } from "@/content-indexer/utils/github.js"; /** * Factory for creating OpenAPI spec with minimal required fields for testing diff --git a/src/content-indexer/utils/truncate-record.ts b/src/content-indexer/utils/truncate-record.ts index 57cbd0df9..569d36187 100644 --- a/src/content-indexer/utils/truncate-record.ts +++ b/src/content-indexer/utils/truncate-record.ts @@ -1,4 +1,4 @@ -import type { AlgoliaRecord } from "@/content-indexer/types/algolia"; +import type { AlgoliaRecord } from "@/content-indexer/types/algolia.js"; const MAX_RECORD_BYTES = 100_000; // Algolia imposes a 100KB limit on each record const BUFFER_BYTES = 1_000; diff --git a/src/content-indexer/visitors/__tests__/index.test.ts b/src/content-indexer/visitors/__tests__/index.test.ts index c2fd18e68..a0d599ca0 100644 --- a/src/content-indexer/visitors/__tests__/index.test.ts +++ b/src/content-indexer/visitors/__tests__/index.test.ts @@ -1,12 +1,12 @@ import { describe, expect, test } from "vitest"; -import { ProcessingContext } from "@/content-indexer/collectors/processing-context"; -import { ContentCache } from "@/content-indexer/core/content-cache"; -import { PathBuilder } from "@/content-indexer/core/path-builder"; -import { DOCS_REPO } from "@/content-indexer/utils/github"; -import { openApiSpecFactory } from "@/content-indexer/utils/test-factories"; +import { ProcessingContext } from "@/content-indexer/collectors/processing-context.js"; +import { ContentCache } from "@/content-indexer/core/content-cache.js"; +import { PathBuilder } from "@/content-indexer/core/path-builder.js"; +import { DOCS_REPO } from "@/content-indexer/utils/github.js"; +import { openApiSpecFactory } from "@/content-indexer/utils/test-factories.js"; -import { visitNavigationItem } from "../index"; +import { visitNavigationItem } from "../index.js"; describe("visitNavigationItem dispatcher", () => { test("should route page config to visitPage", () => { diff --git a/src/content-indexer/visitors/__tests__/visit-api-reference.test.ts b/src/content-indexer/visitors/__tests__/visit-api-reference.test.ts index a6c820108..205e14b32 100644 --- a/src/content-indexer/visitors/__tests__/visit-api-reference.test.ts +++ b/src/content-indexer/visitors/__tests__/visit-api-reference.test.ts @@ -1,15 +1,15 @@ import { describe, expect, test } from "vitest"; -import { ProcessingContext } from "@/content-indexer/collectors/processing-context"; -import { ContentCache } from "@/content-indexer/core/content-cache"; -import { PathBuilder } from "@/content-indexer/core/path-builder"; -import { DOCS_REPO } from "@/content-indexer/utils/github"; +import { ProcessingContext } from "@/content-indexer/collectors/processing-context.js"; +import { ContentCache } from "@/content-indexer/core/content-cache.js"; +import { PathBuilder } from "@/content-indexer/core/path-builder.js"; +import { DOCS_REPO } from "@/content-indexer/utils/github.js"; import { openApiSpecFactory, openRpcSpecFactory, -} from "@/content-indexer/utils/test-factories"; +} from "@/content-indexer/utils/test-factories.js"; -import { visitApiReference } from "../visit-api-reference"; +import { visitApiReference } from "../visit-api-reference.js"; describe("visitApiReference", () => { test("should return empty result if spec not in cache", () => { diff --git a/src/content-indexer/visitors/__tests__/visit-link.test.ts b/src/content-indexer/visitors/__tests__/visit-link.test.ts index 2a6b5222c..25ef44d89 100644 --- a/src/content-indexer/visitors/__tests__/visit-link.test.ts +++ b/src/content-indexer/visitors/__tests__/visit-link.test.ts @@ -1,11 +1,11 @@ import { describe, expect, test } from "vitest"; -import { ProcessingContext } from "@/content-indexer/collectors/processing-context"; -import { ContentCache } from "@/content-indexer/core/content-cache"; -import { PathBuilder } from "@/content-indexer/core/path-builder"; -import { DOCS_REPO } from "@/content-indexer/utils/github"; +import { ProcessingContext } from "@/content-indexer/collectors/processing-context.js"; +import { ContentCache } from "@/content-indexer/core/content-cache.js"; +import { PathBuilder } from "@/content-indexer/core/path-builder.js"; +import { DOCS_REPO } from "@/content-indexer/utils/github.js"; -import { visitLink } from "../visit-link"; +import { visitLink } from "../visit-link.js"; describe("visitLink", () => { test("should create link nav item", () => { diff --git a/src/content-indexer/visitors/__tests__/visit-page.test.ts b/src/content-indexer/visitors/__tests__/visit-page.test.ts index acc6d3158..d0dcd1ae3 100644 --- a/src/content-indexer/visitors/__tests__/visit-page.test.ts +++ b/src/content-indexer/visitors/__tests__/visit-page.test.ts @@ -1,11 +1,11 @@ import { describe, expect, test } from "vitest"; -import { ProcessingContext } from "@/content-indexer/collectors/processing-context"; -import { ContentCache } from "@/content-indexer/core/content-cache"; -import { PathBuilder } from "@/content-indexer/core/path-builder"; -import { DOCS_REPO } from "@/content-indexer/utils/github"; +import { ProcessingContext } from "@/content-indexer/collectors/processing-context.js"; +import { ContentCache } from "@/content-indexer/core/content-cache.js"; +import { PathBuilder } from "@/content-indexer/core/path-builder.js"; +import { DOCS_REPO } from "@/content-indexer/utils/github.js"; -import { visitPage } from "../visit-page"; +import { visitPage } from "../visit-page.js"; describe("visitPage", () => { test("should create path index entry for page", () => { diff --git a/src/content-indexer/visitors/__tests__/visit-section.test.ts b/src/content-indexer/visitors/__tests__/visit-section.test.ts index db3ec3b1a..35f55a377 100644 --- a/src/content-indexer/visitors/__tests__/visit-section.test.ts +++ b/src/content-indexer/visitors/__tests__/visit-section.test.ts @@ -1,12 +1,12 @@ import { describe, expect, test } from "vitest"; -import { ProcessingContext } from "@/content-indexer/collectors/processing-context"; -import { ContentCache } from "@/content-indexer/core/content-cache"; -import { PathBuilder } from "@/content-indexer/core/path-builder"; -import { DOCS_REPO } from "@/content-indexer/utils/github"; +import { ProcessingContext } from "@/content-indexer/collectors/processing-context.js"; +import { ContentCache } from "@/content-indexer/core/content-cache.js"; +import { PathBuilder } from "@/content-indexer/core/path-builder.js"; +import { DOCS_REPO } from "@/content-indexer/utils/github.js"; -import { visitNavigationItem } from "../index"; -import { visitSection } from "../visit-section"; +import { visitNavigationItem } from "../index.js"; +import { visitSection } from "../visit-section.js"; describe("visitSection", () => { test("should create section nav item with children", () => { diff --git a/src/content-indexer/visitors/index.ts b/src/content-indexer/visitors/index.ts index 7b4ef5de7..d61e02697 100644 --- a/src/content-indexer/visitors/index.ts +++ b/src/content-indexer/visitors/index.ts @@ -1,6 +1,6 @@ -import type { ProcessingContext } from "@/content-indexer/collectors/processing-context"; -import type { ContentCache } from "@/content-indexer/core/content-cache"; -import type { PathBuilder } from "@/content-indexer/core/path-builder"; +import type { ProcessingContext } from "@/content-indexer/collectors/processing-context.js"; +import type { ContentCache } from "@/content-indexer/core/content-cache.js"; +import type { PathBuilder } from "@/content-indexer/core/path-builder.js"; import { isApiConfig, isChangelogConfig, @@ -8,15 +8,15 @@ import { isPageConfig, isSectionConfig, type NavigationItem, -} from "@/content-indexer/types/docsYaml"; -import type { NavItem } from "@/content-indexer/types/navigation"; -import type { PathIndex } from "@/content-indexer/types/pathIndex"; -import type { RepoConfig } from "@/content-indexer/utils/github"; +} from "@/content-indexer/types/docsYaml.js"; +import type { NavItem } from "@/content-indexer/types/navigation.js"; +import type { PathIndex } from "@/content-indexer/types/pathIndex.js"; +import type { RepoConfig } from "@/content-indexer/utils/github.js"; -import { visitApiReference } from "./visit-api-reference"; -import { visitLink } from "./visit-link"; -import { visitPage } from "./visit-page"; -import { visitSection } from "./visit-section"; +import { visitApiReference } from "./visit-api-reference.js"; +import { visitLink } from "./visit-link.js"; +import { visitPage } from "./visit-page.js"; +import { visitSection } from "./visit-section.js"; export interface VisitorConfigBase { parentPath: PathBuilder; diff --git a/src/content-indexer/visitors/processors/__tests__/process-openapi.test.ts b/src/content-indexer/visitors/processors/__tests__/process-openapi.test.ts index b00d3c6ae..cbb12831f 100644 --- a/src/content-indexer/visitors/processors/__tests__/process-openapi.test.ts +++ b/src/content-indexer/visitors/processors/__tests__/process-openapi.test.ts @@ -1,12 +1,12 @@ import { describe, expect, test } from "vitest"; -import { ProcessingContext } from "@/content-indexer/collectors/processing-context"; -import { ContentCache } from "@/content-indexer/core/content-cache"; -import { PathBuilder } from "@/content-indexer/core/path-builder"; -import { DOCS_REPO } from "@/content-indexer/utils/github"; -import { openApiSpecFactory } from "@/content-indexer/utils/test-factories"; +import { ProcessingContext } from "@/content-indexer/collectors/processing-context.js"; +import { ContentCache } from "@/content-indexer/core/content-cache.js"; +import { PathBuilder } from "@/content-indexer/core/path-builder.js"; +import { DOCS_REPO } from "@/content-indexer/utils/github.js"; +import { openApiSpecFactory } from "@/content-indexer/utils/test-factories.js"; -import { processOpenApiSpec } from "../process-openapi"; +import { processOpenApiSpec } from "../process-openapi.js"; describe("processOpenApiSpec", () => { test("should process operations and create index entries", () => { diff --git a/src/content-indexer/visitors/processors/__tests__/process-openrpc.test.ts b/src/content-indexer/visitors/processors/__tests__/process-openrpc.test.ts index 5cae6ce49..eaebe8c25 100644 --- a/src/content-indexer/visitors/processors/__tests__/process-openrpc.test.ts +++ b/src/content-indexer/visitors/processors/__tests__/process-openrpc.test.ts @@ -1,13 +1,13 @@ import { describe, expect, test, vi } from "vitest"; -import { ProcessingContext } from "@/content-indexer/collectors/processing-context"; -import { ContentCache } from "@/content-indexer/core/content-cache"; -import { PathBuilder } from "@/content-indexer/core/path-builder"; -import type { OpenRpcSpec } from "@/content-indexer/types/specs"; -import { DOCS_REPO } from "@/content-indexer/utils/github"; -import { openRpcSpecFactory } from "@/content-indexer/utils/test-factories"; +import { ProcessingContext } from "@/content-indexer/collectors/processing-context.js"; +import { ContentCache } from "@/content-indexer/core/content-cache.js"; +import { PathBuilder } from "@/content-indexer/core/path-builder.js"; +import type { OpenRpcSpec } from "@/content-indexer/types/specs.js"; +import { DOCS_REPO } from "@/content-indexer/utils/github.js"; +import { openRpcSpecFactory } from "@/content-indexer/utils/test-factories.js"; -import { processOpenRpcSpec } from "../process-openrpc"; +import { processOpenRpcSpec } from "../process-openrpc.js"; describe("processOpenRpcSpec", () => { test("should return empty result for invalid spec", () => { diff --git a/src/content-indexer/visitors/processors/process-openapi.ts b/src/content-indexer/visitors/processors/process-openapi.ts index 3bfd467a2..479d64ba2 100644 --- a/src/content-indexer/visitors/processors/process-openapi.ts +++ b/src/content-indexer/visitors/processors/process-openapi.ts @@ -1,17 +1,20 @@ -import type { ProcessingContext } from "@/content-indexer/collectors/processing-context"; -import type { PathBuilder } from "@/content-indexer/core/path-builder"; -import type { NavItem } from "@/content-indexer/types/navigation"; -import type { PathIndex } from "@/content-indexer/types/pathIndex"; -import type { OpenApiSpec } from "@/content-indexer/types/specs"; -import { createBreadcrumbNavItem } from "@/content-indexer/utils/navigation-helpers"; +import type { ProcessingContext } from "@/content-indexer/collectors/processing-context.js"; +import type { PathBuilder } from "@/content-indexer/core/path-builder.js"; +import type { NavItem } from "@/content-indexer/types/navigation.js"; +import type { PathIndex } from "@/content-indexer/types/pathIndex.js"; +import type { OpenApiSpec } from "@/content-indexer/types/specs.js"; +import { createBreadcrumbNavItem } from "@/content-indexer/utils/navigation-helpers.js"; import { buildOperationPath, extractOpenApiOperations, getOperationDescription, getOperationTitle, type ExtractedOperation, -} from "@/content-indexer/utils/openapi"; -import type { VisitorConfig, VisitorResult } from "@/content-indexer/visitors"; +} from "@/content-indexer/utils/openapi.js"; +import type { + VisitorConfig, + VisitorResult, +} from "@/content-indexer/visitors/index.js"; /** * Configuration for processing an OpenAPI specification diff --git a/src/content-indexer/visitors/processors/process-openrpc.ts b/src/content-indexer/visitors/processors/process-openrpc.ts index 3893a08fb..01925f4b8 100644 --- a/src/content-indexer/visitors/processors/process-openrpc.ts +++ b/src/content-indexer/visitors/processors/process-openrpc.ts @@ -1,12 +1,15 @@ import { kebabCase } from "lodash-es"; -import type { PathBuilder } from "@/content-indexer/core/path-builder"; -import type { NavItem } from "@/content-indexer/types/navigation"; -import type { PathIndex } from "@/content-indexer/types/pathIndex"; -import type { OpenRpcSpec } from "@/content-indexer/types/specs"; -import { createBreadcrumbNavItem } from "@/content-indexer/utils/navigation-helpers"; -import { isValidOpenRpcSpec } from "@/content-indexer/utils/openrpc"; -import type { VisitorConfig, VisitorResult } from "@/content-indexer/visitors"; +import type { PathBuilder } from "@/content-indexer/core/path-builder.js"; +import type { NavItem } from "@/content-indexer/types/navigation.js"; +import type { PathIndex } from "@/content-indexer/types/pathIndex.js"; +import type { OpenRpcSpec } from "@/content-indexer/types/specs.js"; +import { createBreadcrumbNavItem } from "@/content-indexer/utils/navigation-helpers.js"; +import { isValidOpenRpcSpec } from "@/content-indexer/utils/openrpc.js"; +import type { + VisitorConfig, + VisitorResult, +} from "@/content-indexer/visitors/index.js"; /** * Configuration for processing an OpenRPC specification diff --git a/src/content-indexer/visitors/visit-api-reference.ts b/src/content-indexer/visitors/visit-api-reference.ts index 4844a2dc0..8dc1a5ecd 100644 --- a/src/content-indexer/visitors/visit-api-reference.ts +++ b/src/content-indexer/visitors/visit-api-reference.ts @@ -1,11 +1,14 @@ import { kebabCase } from "lodash-es"; -import type { ApiConfig } from "@/content-indexer/types/docsYaml"; -import type { OpenApiSpec, OpenRpcSpec } from "@/content-indexer/types/specs"; +import type { ApiConfig } from "@/content-indexer/types/docsYaml.js"; +import type { + OpenApiSpec, + OpenRpcSpec, +} from "@/content-indexer/types/specs.js"; import type { VisitorConfigBase, VisitorResult } from "."; -import { processOpenApiSpec } from "./processors/process-openapi"; -import { processOpenRpcSpec } from "./processors/process-openrpc"; +import { processOpenApiSpec } from "./processors/process-openapi.js"; +import { processOpenRpcSpec } from "./processors/process-openrpc.js"; export interface ApiVisitorConfig extends VisitorConfigBase { item: ApiConfig; diff --git a/src/content-indexer/visitors/visit-link.ts b/src/content-indexer/visitors/visit-link.ts index be0f30ae0..a3b4ae9fc 100644 --- a/src/content-indexer/visitors/visit-link.ts +++ b/src/content-indexer/visitors/visit-link.ts @@ -1,4 +1,4 @@ -import type { LinkConfig } from "@/content-indexer/types/docsYaml"; +import type { LinkConfig } from "@/content-indexer/types/docsYaml.js"; import type { VisitorConfigBase, VisitorResult } from "."; diff --git a/src/content-indexer/visitors/visit-page.ts b/src/content-indexer/visitors/visit-page.ts index 10f0eb2ea..13e5a4aa3 100644 --- a/src/content-indexer/visitors/visit-page.ts +++ b/src/content-indexer/visitors/visit-page.ts @@ -1,11 +1,11 @@ import { kebabCase } from "lodash-es"; -import type { PageConfig } from "@/content-indexer/types/docsYaml"; -import type { NavItem } from "@/content-indexer/types/navigation"; +import type { PageConfig } from "@/content-indexer/types/docsYaml.js"; +import type { NavItem } from "@/content-indexer/types/navigation.js"; import { normalizeFilePath, normalizeSlug, -} from "@/content-indexer/utils/normalization"; +} from "@/content-indexer/utils/normalization.js"; import type { VisitorConfigBase, VisitorResult } from "."; diff --git a/src/content-indexer/visitors/visit-section.ts b/src/content-indexer/visitors/visit-section.ts index 7dbac658a..e3b481503 100644 --- a/src/content-indexer/visitors/visit-section.ts +++ b/src/content-indexer/visitors/visit-section.ts @@ -1,12 +1,12 @@ import { kebabCase } from "lodash-es"; -import type { SectionConfig } from "@/content-indexer/types/docsYaml"; -import type { NavItem } from "@/content-indexer/types/navigation"; -import type { PathIndex } from "@/content-indexer/types/pathIndex"; +import type { SectionConfig } from "@/content-indexer/types/docsYaml.js"; +import type { NavItem } from "@/content-indexer/types/navigation.js"; +import type { PathIndex } from "@/content-indexer/types/pathIndex.js"; import { normalizeFilePath, normalizeSlug, -} from "@/content-indexer/utils/normalization"; +} from "@/content-indexer/utils/normalization.js"; import type { VisitorConfig, VisitorConfigBase, VisitorResult } from "."; diff --git a/src/utils/generateRpcSpecs.ts b/src/utils/generateRpcSpecs.ts index edffff2f2..a74b00c03 100644 --- a/src/utils/generateRpcSpecs.ts +++ b/src/utils/generateRpcSpecs.ts @@ -1,8 +1,8 @@ import { dereference } from "@apidevtools/json-schema-ref-parser"; -import type { DerefedOpenRpcDoc } from "../types/openRpc"; -import { formatOpenRpcDoc, writeOpenRpcDoc } from "./generationHelpers"; -import { validateRpcSpec } from "./validateRpcSpec"; +import type { DerefedOpenRpcDoc } from "../types/openRpc.js"; +import { formatOpenRpcDoc, writeOpenRpcDoc } from "./generationHelpers.js"; +import { validateRpcSpec } from "./validateRpcSpec.js"; /** * Generates an OpenRPC specification for the Alchemy JSON-RPC API. diff --git a/src/utils/generationHelpers.ts b/src/utils/generationHelpers.ts index 5e64000fc..a163aed81 100644 --- a/src/utils/generationHelpers.ts +++ b/src/utils/generationHelpers.ts @@ -2,7 +2,7 @@ import type { JSONSchema, OpenrpcDocument } from "@open-rpc/meta-schema"; import { writeFileSync } from "fs"; import mergeAllOf from "json-schema-merge-allof"; -import type { DerefedOpenRpcDoc } from "../types/openRpc"; +import type { DerefedOpenRpcDoc } from "../types/openRpc.js"; /** * Formats an OpenRPC document by removing components, merging allOf schemas, and sorting methods diff --git a/src/utils/validateRpcSpec.ts b/src/utils/validateRpcSpec.ts index dede0e0ad..a8a40b17f 100644 --- a/src/utils/validateRpcSpec.ts +++ b/src/utils/validateRpcSpec.ts @@ -1,6 +1,6 @@ import { validateOpenRPCDocument } from "@open-rpc/schema-utils-js"; -import type { DerefedOpenRpcDoc } from "../types/openRpc"; +import type { DerefedOpenRpcDoc } from "../types/openRpc.js"; interface ValidationError { keyword: string; From 8a1dd65aa85bbed4f1df1b624e56c03891e572e9 Mon Sep 17 00:00:00 2001 From: dslovinsky Date: Thu, 8 Jan 2026 18:17:22 -0800 Subject: [PATCH 04/27] feat: switch to tsx from ts-node --- package.json | 15 +++--- pnpm-lock.yaml | 106 ------------------------------------- scripts/fix-esm-imports.sh | 23 -------- 3 files changed, 7 insertions(+), 137 deletions(-) delete mode 100755 scripts/fix-esm-imports.sh diff --git a/package.json b/package.json index 97afee2e1..fc6a53767 100644 --- a/package.json +++ b/package.json @@ -16,15 +16,15 @@ "fern:preview": "fern generate --docs --preview", "fern:publish": "fern generate --docs", "generate:rest": "./scripts/generate-open-api.sh", - "generate:rpc": "ts-node ./scripts/generate-rpc.ts", + "generate:rpc": "tsx ./scripts/generate-rpc.ts", "generate:rpc:watch": "onchange \"src/openrpc/**/*.{yaml,yml}\" -- pnpm run generate:rpc", "generate:rest:watch": "onchange \"src/openapi/**/*.{yaml,yml}\" -- pnpm run generate:rest", "generate:watch": "pnpm run generate:rpc:watch & pnpm run generate:rest:watch", - "generate:metadata": "ts-node ./scripts/generate-metadata.ts", + "generate:metadata": "tsx ./scripts/generate-metadata.ts", "generate": "pnpm run generate:rest & pnpm run generate:rpc", "clean": "rm -rf fern/api-specs", "validate:rest": "./scripts/generate-open-api.sh --validate-only", - "validate:rpc": "ts-node ./scripts/validate-rpc.ts", + "validate:rpc": "tsx ./scripts/validate-rpc.ts", "validate": "pnpm run validate:rest & pnpm run validate:rpc", "typecheck": "tsc --noEmit", "lint": "pnpm run lint:eslint & pnpm run lint:prettier & pnpm run typecheck", @@ -34,7 +34,7 @@ "lint:prettier": "prettier --cache --check \"{src,scripts}/**/*.{js,jsx,ts,tsx,md,mjs,mts,json,yml,yaml}\"", "lint:prettier:fix": "pnpm run lint:prettier --write", "lint:broken-links": "lychee .", - "add-evm-chain": "ts-node ./scripts/add-evm-chain.ts", + "add-evm-chain": "tsx ./scripts/add-evm-chain.ts", "prepare": "husky", "index:main": "tsx src/content-indexer/index.ts --indexer main --branch main", "index:main:preview": "tsx src/content-indexer/index.ts --indexer main --mode preview --branch $(git rev-parse --abbrev-ref HEAD)", @@ -52,8 +52,7 @@ "js-yaml": "^4.1.1", "json-schema-merge-allof": "^0.8.1", "lodash-es": "^4.17.21", - "octokit": "^5.0.5", - "ts-node": "^10.9.2" + "octokit": "^5.0.5" }, "devDependencies": { "@eslint/js": "^9.25.0", @@ -77,7 +76,6 @@ "onchange": "^7.1.0", "openapi-types": "^12.1.3", "prettier": "3.4.2", - "vitest": "^4.0.14", "remark-frontmatter": "^5.0.0", "remark-mdx": "^3.1.0", "remark-preset-lint-consistent": "^6.0.1", @@ -85,6 +83,7 @@ "remark-preset-lint-recommended": "^7.0.1", "tsx": "^4.20.6", "typescript": "^5.8.3", - "typescript-eslint": "^8.31.0" + "typescript-eslint": "^8.31.0", + "vitest": "^4.0.14" } } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 28380aecc..b0fe2aeda 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -38,9 +38,6 @@ importers: octokit: specifier: ^5.0.5 version: 5.0.5 - ts-node: - specifier: ^10.9.2 - version: 10.9.2(@types/node@22.14.1)(typescript@5.8.3) devDependencies: '@eslint/js': specifier: ^9.25.0 @@ -285,10 +282,6 @@ packages: engines: {node: '>= 10'} hasBin: true - '@cspotcode/source-map-support@0.8.1': - resolution: {integrity: sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==} - engines: {node: '>=12'} - '@emotion/is-prop-valid@1.2.2': resolution: {integrity: sha512-uNsoYd37AFmaCdXlg6EYD1KaPOaRWRByMCYzbKUX4+hhMfrxdVSelShywL4JVaAeM/eHUOSprYBQls+/neX3pw==} @@ -552,9 +545,6 @@ packages: '@jridgewell/trace-mapping@0.3.25': resolution: {integrity: sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==} - '@jridgewell/trace-mapping@0.3.9': - resolution: {integrity: sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==} - '@jsdevtools/ono@7.1.3': resolution: {integrity: sha512-4JQNk+3mVzK3xh2rqd6RB4J46qUR19azEHBneZyTZM+c456qOrbbM/5xcR8huNCCcbVt7+UmizG6GuUvPvKUYg==} @@ -1029,18 +1019,6 @@ packages: svelte: optional: true - '@tsconfig/node10@1.0.11': - resolution: {integrity: sha512-DcRjDCujK/kCk/cUe8Xz8ZSpm8mS3mNNpta+jGCA6USEDfktlNvm1+IuZ9eTcDbNk41BHwpHHeW+N1lKCz4zOw==} - - '@tsconfig/node12@1.0.11': - resolution: {integrity: sha512-cqefuRsh12pWyGsIoBKJA9luFu3mRxCA+ORZvA4ktLSzIuCUtWVxGIuXigEwO5/ywWFMZ2QEGKWvkZG1zDMTag==} - - '@tsconfig/node14@1.0.3': - resolution: {integrity: sha512-ysT8mhdixWK6Hw3i1V2AeRqZ5WfXg1G43mqoYlM2nc6388Fq5jcXyr5mRsqViLx/GJYdoL0bfXD8nmF+Zn/Iow==} - - '@tsconfig/node16@1.0.4': - resolution: {integrity: sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA==} - '@types/aws-lambda@8.10.159': resolution: {integrity: sha512-SAP22WSGNN12OQ8PlCzGzRCZ7QDCwI85dQZbmpz7+mAk+L7j+wI7qnvmdKh+o7A5LaOp6QnOZ2NJphAZQTTHQg==} @@ -1205,10 +1183,6 @@ packages: peerDependencies: acorn: ^6.0.0 || ^7.0.0 || ^8.0.0 - acorn-walk@8.3.4: - resolution: {integrity: sha512-ueEepnujpqee2o5aIYnvHU6C0A42MNdsIDeqy5BydrkuC5R1ZuUFnm27EeFJGoEHJQgn3uleRvmTXaJgfXbt4g==} - engines: {node: '>=0.4.0'} - acorn@8.14.1: resolution: {integrity: sha512-OvQ/2pUDKmgfCg++xsTX1wGxfTaszcHVcTctW4UJB4hibJx2HXxxO5UmVgyjMa+ZDsiaf5wWLXYpRWMmBI0QHg==} engines: {node: '>=0.4.0'} @@ -1427,9 +1401,6 @@ packages: core-js@3.41.0: resolution: {integrity: sha512-SJ4/EHwS36QMJd6h/Rg+GyR4A5xE0FSI3eZ+iBVpfqf1x0eTSg1smWLHrA+2jQThZSh97fmSgFSU8B61nxosxA==} - create-require@1.1.1: - resolution: {integrity: sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==} - cross-spawn@7.0.6: resolution: {integrity: sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==} engines: {node: '>= 8'} @@ -1492,10 +1463,6 @@ packages: resolution: {integrity: sha512-EjePK1srD3P08o2j4f0ExnylqRs5B9tJjcp9t1krH2qRi8CCdsYfwe9JgSLurFBWwq4uOlipzfk5fHNvwFKr8Q==} engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} - diff@4.0.2: - resolution: {integrity: sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==} - engines: {node: '>=0.3.1'} - diff@5.2.0: resolution: {integrity: sha512-uIFDxqpRZGZ6ThOk84hEfqWoHx2devRFvpTZcTHur85vImfaxUbTW9Ryh4CpCuDnToOP1CEtXKIgytHBPVff5A==} engines: {node: '>=0.3.1'} @@ -2158,9 +2125,6 @@ packages: magic-string@0.30.21: resolution: {integrity: sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==} - make-error@1.3.6: - resolution: {integrity: sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==} - mark.js@8.11.1: resolution: {integrity: sha512-1I+1qpDt4idfgLQG+BNWmrqku+7/2bi5nLf4YwF8y8zXvmfiTBY3PV3ZibfrjBueCByROpuBjLLFCajqkgYoLQ==} @@ -3125,20 +3089,6 @@ packages: peerDependencies: typescript: '>=4.8.4' - ts-node@10.9.2: - resolution: {integrity: sha512-f0FFpIdcHgn8zcPSbf1dRevwt047YMnaiJM3u2w2RewrB+fob/zePZcrOyQoLMMO7aBIddLcQIEK5dYjkLnGrQ==} - hasBin: true - peerDependencies: - '@swc/core': '>=1.2.50' - '@swc/wasm': '>=1.2.50' - '@types/node': '*' - typescript: '>=2.7' - peerDependenciesMeta: - '@swc/core': - optional: true - '@swc/wasm': - optional: true - tslib@2.6.2: resolution: {integrity: sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==} @@ -3253,9 +3203,6 @@ packages: engines: {node: '>=8'} hasBin: true - v8-compile-cache-lib@3.0.1: - resolution: {integrity: sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==} - validate-npm-package-license@3.0.4: resolution: {integrity: sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew==} @@ -3455,10 +3402,6 @@ packages: resolution: {integrity: sha512-xBBulfCc8Y6gLFcrPvtqKz9hz8SO0l1Ni8GgDekvBX2ro0HRQImDGnikfc33cgzcYUSncapnNcZDjVFIH3f6KQ==} engines: {node: '>=12'} - yn@3.1.1: - resolution: {integrity: sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==} - engines: {node: '>=6'} - yocto-queue@0.1.0: resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==} engines: {node: '>=10'} @@ -3644,10 +3587,6 @@ snapshots: '@boundaryml/baml-linux-x64-musl': 0.211.2 '@boundaryml/baml-win32-x64-msvc': 0.211.2 - '@cspotcode/source-map-support@0.8.1': - dependencies: - '@jridgewell/trace-mapping': 0.3.9 - '@emotion/is-prop-valid@1.2.2': dependencies: '@emotion/memoize': 0.8.1 @@ -3829,11 +3768,6 @@ snapshots: '@jridgewell/resolve-uri': 3.1.2 '@jridgewell/sourcemap-codec': 1.5.0 - '@jridgewell/trace-mapping@0.3.9': - dependencies: - '@jridgewell/resolve-uri': 3.1.2 - '@jridgewell/sourcemap-codec': 1.5.0 - '@jsdevtools/ono@7.1.3': {} '@jsep-plugin/assignment@1.3.0(jsep@1.4.0)': @@ -4390,14 +4324,6 @@ snapshots: transitivePeerDependencies: - supports-color - '@tsconfig/node10@1.0.11': {} - - '@tsconfig/node12@1.0.11': {} - - '@tsconfig/node14@1.0.3': {} - - '@tsconfig/node16@1.0.4': {} - '@types/aws-lambda@8.10.159': {} '@types/chai@5.2.3': @@ -4598,10 +4524,6 @@ snapshots: dependencies: acorn: 8.14.1 - acorn-walk@8.3.4: - dependencies: - acorn: 8.14.1 - acorn@8.14.1: {} agent-base@7.1.3: {} @@ -4816,8 +4738,6 @@ snapshots: core-js@3.41.0: {} - create-require@1.1.1: {} - cross-spawn@7.0.6: dependencies: path-key: 3.1.1 @@ -4867,8 +4787,6 @@ snapshots: diff-sequences@29.6.3: {} - diff@4.0.2: {} - diff@5.2.0: {} dompurify@3.2.5: @@ -5569,8 +5487,6 @@ snapshots: dependencies: '@jridgewell/sourcemap-codec': 1.5.5 - make-error@1.3.6: {} - mark.js@8.11.1: {} marked@4.3.0: {} @@ -7180,24 +7096,6 @@ snapshots: dependencies: typescript: 5.8.3 - ts-node@10.9.2(@types/node@22.14.1)(typescript@5.8.3): - dependencies: - '@cspotcode/source-map-support': 0.8.1 - '@tsconfig/node10': 1.0.11 - '@tsconfig/node12': 1.0.11 - '@tsconfig/node14': 1.0.3 - '@tsconfig/node16': 1.0.4 - '@types/node': 22.14.1 - acorn: 8.14.1 - acorn-walk: 8.3.4 - arg: 4.1.3 - create-require: 1.1.1 - diff: 4.0.2 - make-error: 1.3.6 - typescript: 5.8.3 - v8-compile-cache-lib: 3.0.1 - yn: 3.1.1 - tslib@2.6.2: {} tslib@2.8.1: {} @@ -7351,8 +7249,6 @@ snapshots: kleur: 4.1.5 sade: 1.8.1 - v8-compile-cache-lib@3.0.1: {} - validate-npm-package-license@3.0.4: dependencies: spdx-correct: 3.2.0 @@ -7534,8 +7430,6 @@ snapshots: y18n: 5.0.8 yargs-parser: 20.2.9 - yn@3.1.1: {} - yocto-queue@0.1.0: {} zwitch@2.0.4: {} diff --git a/scripts/fix-esm-imports.sh b/scripts/fix-esm-imports.sh deleted file mode 100755 index 22f791903..000000000 --- a/scripts/fix-esm-imports.sh +++ /dev/null @@ -1,23 +0,0 @@ -#!/bin/bash - -# Fix all @/ and relative imports to add .js extensions for ESM compatibility -# This converts: -# from "@/foo" -> from "@/foo.js" -# from "./foo" -> from "./foo.js" -# from "../foo" -> from "../foo.js" - -# Process src/, fern/, and scripts/ directories -for dir in src fern scripts; do - if [ -d "$dir" ]; then - find "$dir" -type f \( -name "*.ts" -o -name "*.tsx" \) -exec sed -i '' \ - -e 's|from "\(@/[^"]*\)"|from "\1.js"|g' \ - -e 's|from '"'"'\(@/[^'"'"']*\)'"'"'|from '"'"'\1.js'"'"'|g' \ - -e 's|from "\(\./[^"]*\)"|from "\1.js"|g' \ - -e 's|from '"'"'\(\./[^'"'"']*\)'"'"'|from '"'"'\1.js'"'"'|g' \ - -e 's|from "\(\.\./[^"]*\)"|from "\1.js"|g' \ - -e 's|from '"'"'\(\.\./[^'"'"']*\)'"'"'|from '"'"'\1.js'"'"'|g' \ - {} \; - fi -done - -echo "✅ Fixed all imports to include .js extensions" From 671d213fef1b90eb6bd8957635b808ca5beb0752 Mon Sep 17 00:00:00 2001 From: dslovinsky Date: Thu, 8 Jan 2026 18:40:11 -0800 Subject: [PATCH 05/27] feat: config env vars in content-indexer scripts --- package.json | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/package.json b/package.json index fc6a53767..306114df9 100644 --- a/package.json +++ b/package.json @@ -36,10 +36,10 @@ "lint:broken-links": "lychee .", "add-evm-chain": "tsx ./scripts/add-evm-chain.ts", "prepare": "husky", - "index:main": "tsx src/content-indexer/index.ts --indexer main --branch main", - "index:main:preview": "tsx src/content-indexer/index.ts --indexer main --mode preview --branch $(git rev-parse --abbrev-ref HEAD)", - "index:sdk": "tsx src/content-indexer/index.ts --indexer sdk --branch main", - "index:changelog": "tsx src/content-indexer/index.ts --indexer changelog --branch main", + "index:main": "tsx --env-file=.env src/content-indexer/index.ts --indexer main --branch main", + "index:main:preview": "tsx --env-file=.env src/content-indexer/index.ts --indexer main --mode preview --branch $(git rev-parse --abbrev-ref HEAD)", + "index:sdk": "tsx --env-file=.env src/content-indexer/index.ts --indexer sdk --branch main", + "index:changelog": "tsx --env-file=.env src/content-indexer/index.ts --indexer changelog --branch main", "index:watch": "tsx scripts/watch-and-index.ts" }, "dependencies": { From 1be65efbfacc15ef252b7534f627eb7c56f87814 Mon Sep 17 00:00:00 2001 From: dslovinsky Date: Thu, 8 Jan 2026 19:05:10 -0800 Subject: [PATCH 06/27] fix: content-indexer bugs --- package.json | 8 ++++---- src/content-indexer/uploaders/redis.ts | 8 +++----- 2 files changed, 7 insertions(+), 9 deletions(-) diff --git a/package.json b/package.json index 306114df9..138b2e26b 100644 --- a/package.json +++ b/package.json @@ -36,10 +36,10 @@ "lint:broken-links": "lychee .", "add-evm-chain": "tsx ./scripts/add-evm-chain.ts", "prepare": "husky", - "index:main": "tsx --env-file=.env src/content-indexer/index.ts --indexer main --branch main", - "index:main:preview": "tsx --env-file=.env src/content-indexer/index.ts --indexer main --mode preview --branch $(git rev-parse --abbrev-ref HEAD)", - "index:sdk": "tsx --env-file=.env src/content-indexer/index.ts --indexer sdk --branch main", - "index:changelog": "tsx --env-file=.env src/content-indexer/index.ts --indexer changelog --branch main", + "index:main": "tsx --env-file=.env src/content-indexer/index.ts --indexer=main --branch=main", + "index:main:preview": "tsx --env-file=.env src/content-indexer/index.ts --indexer=main --mode=preview --branch=$(git rev-parse --abbrev-ref HEAD)", + "index:sdk": "tsx --env-file=.env src/content-indexer/index.ts --indexer=sdk --branch=main", + "index:changelog": "tsx --env-file=.env src/content-indexer/index.ts --indexer=changelog --branch=main", "index:watch": "tsx scripts/watch-and-index.ts" }, "dependencies": { diff --git a/src/content-indexer/uploaders/redis.ts b/src/content-indexer/uploaders/redis.ts index 337af2973..36e1f9b8b 100644 --- a/src/content-indexer/uploaders/redis.ts +++ b/src/content-indexer/uploaders/redis.ts @@ -53,11 +53,9 @@ export const storeToRedis = async ( if (options.mergeSDKIntoWallets && navigationTrees?.wallets) { // SDK indexer: merge SDK section into existing wallets nav tree const navTreeKey = `${options.branchId}/nav-tree:wallets`; - const existingTreeJson = await redis.get(navTreeKey); - let existingTree: NavigationTree = []; + const existingTree = await redis.get(navTreeKey); - if (existingTreeJson) { - existingTree = JSON.parse(existingTreeJson as string) as NavigationTree; + if (existingTree) { console.info(`📖 Read existing wallets nav tree from Redis`); } else { console.warn( @@ -66,7 +64,7 @@ export const storeToRedis = async ( } // Filter out existing SDK Reference section - const manualSections = existingTree.filter((item) => { + const manualSections = (existingTree || []).filter((item) => { if (item.type === "section" || item.type === "api-section") { return !item.title.toLowerCase().includes("sdk reference"); } From 2e186f0291e096f8698a9f12e4430b1021ab9e04 Mon Sep 17 00:00:00 2001 From: dslovinsky Date: Thu, 8 Jan 2026 19:40:52 -0800 Subject: [PATCH 07/27] fix: content-indexer bugs re:sdk nav tree merge --- src/content-indexer/index.ts | 7 +- .../uploaders/__tests__/redis.test.ts | 14 +-- src/content-indexer/uploaders/redis.ts | 48 +++++----- src/content-indexer/utils/nav-tree-merge.ts | 93 +++++++++++++++++++ 4 files changed, 124 insertions(+), 38 deletions(-) create mode 100644 src/content-indexer/utils/nav-tree-merge.ts diff --git a/src/content-indexer/index.ts b/src/content-indexer/index.ts index 9dfcb085d..409bb9468 100644 --- a/src/content-indexer/index.ts +++ b/src/content-indexer/index.ts @@ -66,7 +66,7 @@ const runMainIndexer = async ( await Promise.all([ storeToRedis(pathIndex, navigationTrees, { branchId, - pathIndexSuffix: "main", + indexerType: "main", }), uploadToAlgolia(algoliaRecords, { indexerType: "main", @@ -100,8 +100,7 @@ const runSDKIndexer = async (branchId: string) => { { wallets: walletsNavTree }, { branchId, - pathIndexSuffix: "sdk-refs", - mergeSDKIntoWallets: true, + indexerType: "sdk", }, ), uploadToAlgolia(algoliaRecords, { @@ -132,7 +131,7 @@ const runChangelogIndexer = async (branchId: string) => { await Promise.all([ storeToRedis(pathIndex, undefined, { branchId, - pathIndexSuffix: "changelog", + indexerType: "changelog", }), uploadToAlgolia(algoliaRecords, { indexerType: "changelog", diff --git a/src/content-indexer/uploaders/__tests__/redis.test.ts b/src/content-indexer/uploaders/__tests__/redis.test.ts index bfddd5f22..71094afa7 100644 --- a/src/content-indexer/uploaders/__tests__/redis.test.ts +++ b/src/content-indexer/uploaders/__tests__/redis.test.ts @@ -33,7 +33,7 @@ describe("storeToRedis", () => { await storeToRedis(pathIndex, undefined, { branchId: "main", - pathIndexSuffix: "main", + indexerType: "main", }); expect(mockSet).toHaveBeenCalledWith( @@ -55,7 +55,7 @@ describe("storeToRedis", () => { await storeToRedis({}, navigationTrees, { branchId: "main", - pathIndexSuffix: "main", + indexerType: "main", }); expect(mockSet).toHaveBeenCalledWith( @@ -84,7 +84,7 @@ describe("storeToRedis", () => { await storeToRedis({}, navigationTrees, { branchId: "main", - pathIndexSuffix: "main", + indexerType: "main", }); expect(mockSet).toHaveBeenCalledWith( @@ -97,7 +97,7 @@ describe("storeToRedis", () => { ); }); - test("should use sdk-refs suffix for SDK indexer", async () => { + test("should use sdk suffix for SDK indexer", async () => { const pathIndex: PathIndex = { "wallets/metamask": { type: "mdx", @@ -109,11 +109,11 @@ describe("storeToRedis", () => { await storeToRedis(pathIndex, undefined, { branchId: "main", - pathIndexSuffix: "sdk-refs", + indexerType: "sdk", }); expect(mockSet).toHaveBeenCalledWith( - "main/path-index:sdk-refs", + "main/path-index:sdk", JSON.stringify(pathIndex, null, 2), ); }); @@ -135,7 +135,7 @@ describe("storeToRedis", () => { await storeToRedis(pathIndex, navigationTrees, { branchId: "main", - pathIndexSuffix: "main", + indexerType: "main", }); // Should have called set 3 times (1 pathIndex + 2 nav trees) diff --git a/src/content-indexer/uploaders/redis.ts b/src/content-indexer/uploaders/redis.ts index 36e1f9b8b..883cfcc84 100644 --- a/src/content-indexer/uploaders/redis.ts +++ b/src/content-indexer/uploaders/redis.ts @@ -3,6 +3,7 @@ import type { NavigationTreesByTab, } from "@/content-indexer/types/navigation.js"; import type { PathIndex } from "@/content-indexer/types/pathIndex.js"; +import { mergeWalletsNavTree } from "@/content-indexer/utils/nav-tree-merge.js"; import { getRedis } from "@/content-indexer/utils/redis.js"; // Helper to count nav items recursively @@ -23,22 +24,20 @@ const countItems = (items: NavigationTree): number => { * @param navigationTrees - Navigation trees (optional for SDK/changelog indexers) * @param options - Configuration options * @param options.branchId - Branch identifier for Redis keys (e.g., "main", "branch-abc123") - * @param options.pathIndexSuffix - Suffix for path index key (e.g., "main", "sdk-refs", "changelog") - * @param options.mergeSDKIntoWallets - If true, merge nav trees into existing wallets tree (for SDK indexer) + * @param options.indexerType - Type of indexer ("main", "sdk", or "changelog") */ export const storeToRedis = async ( pathIndex: PathIndex, navigationTrees: NavigationTreesByTab | undefined, options: { branchId: string; - pathIndexSuffix: "main" | "sdk-refs" | "changelog"; - mergeSDKIntoWallets?: boolean; + indexerType: "main" | "sdk" | "changelog"; }, ): Promise => { const redis = getRedis(); // Store path index with branch scope - const pathIndexKey = `${options.branchId}/path-index:${options.pathIndexSuffix}`; + const pathIndexKey = `${options.branchId}/path-index:${options.indexerType}`; const pathIndexPromise = redis .set(pathIndexKey, JSON.stringify(pathIndex, null, 2)) .then(() => { @@ -50,29 +49,16 @@ export const storeToRedis = async ( // Handle navigation trees let navTreePromises: Promise[] = []; - if (options.mergeSDKIntoWallets && navigationTrees?.wallets) { + if (options.indexerType === "sdk" && navigationTrees?.wallets) { // SDK indexer: merge SDK section into existing wallets nav tree const navTreeKey = `${options.branchId}/nav-tree:wallets`; const existingTree = await redis.get(navTreeKey); - if (existingTree) { - console.info(`📖 Read existing wallets nav tree from Redis`); - } else { - console.warn( - `⚠️ No existing wallets nav tree found at ${navTreeKey}, creating new one`, - ); - } - - // Filter out existing SDK Reference section - const manualSections = (existingTree || []).filter((item) => { - if (item.type === "section" || item.type === "api-section") { - return !item.title.toLowerCase().includes("sdk reference"); - } - return true; - }); - - // Merge manual + SDK sections - const mergedTree = [...manualSections, ...navigationTrees.wallets]; + const mergedTree = mergeWalletsNavTree( + navigationTrees.wallets, + existingTree, + "sdk", + ); navTreePromises = [ redis.set(navTreeKey, JSON.stringify(mergedTree, null, 2)).then(() => { @@ -82,12 +68,20 @@ export const storeToRedis = async ( }), ]; } else if (navigationTrees) { - // Main indexer: store all navigation trees normally + // Main indexer: store all navigation trees navTreePromises = Object.entries(navigationTrees).map( async ([tab, navTree]) => { const redisKey = `${options.branchId}/nav-tree:${tab}`; - const itemCount = countItems(navTree); - await redis.set(redisKey, JSON.stringify(navTree, null, 2)); + let finalTree = navTree; + + // Main indexer: preserve SDK references in wallets tab + if (tab === "wallets" && options.indexerType === "main") { + const existingTree = await redis.get(redisKey); + finalTree = mergeWalletsNavTree(navTree, existingTree, "main"); + } + + const itemCount = countItems(finalTree); + await redis.set(redisKey, JSON.stringify(finalTree, null, 2)); console.info( `✅ Navigation tree for '${tab}' saved to Redis (${itemCount} items) -> ${redisKey}`, ); diff --git a/src/content-indexer/utils/nav-tree-merge.ts b/src/content-indexer/utils/nav-tree-merge.ts new file mode 100644 index 000000000..b7f5da2a6 --- /dev/null +++ b/src/content-indexer/utils/nav-tree-merge.ts @@ -0,0 +1,93 @@ +import type { + NavItem, + NavigationTree, +} from "@/content-indexer/types/navigation.js"; + +/** + * Checks if a navigation item is an SDK reference section. + * SDK sections are identified by title containing "sdk reference" (case-insensitive) + * WARNING: This is an assumption and will break if the title for this section is changed + */ +const isSDKReferenceSection = (item: NavItem): boolean => { + if (item.type === "section" || item.type === "api-section") { + return item.title.toLowerCase().includes("sdk reference"); + } + return false; +}; + +/** + * Identifies SDK reference sections vs manual sections in a navigation tree. + * SDK sections are identified by title containing "sdk reference" (case-insensitive). + * + * @param tree - The navigation tree to separate + * @returns Object with sdk and manual sections + */ +export const separateSDKAndManualSections = ( + tree: NavigationTree, +): { sdk: NavigationTree; manual: NavigationTree } => { + return tree.reduce<{ sdk: NavigationTree; manual: NavigationTree }>( + (acc, item) => { + if (isSDKReferenceSection(item)) { + acc.sdk.push(item); + } else { + acc.manual.push(item); + } + return acc; + }, + { sdk: [], manual: [] }, + ); +}; + +/** + * Merges navigation trees for the wallets tab, handling SDK references and manual sections. + * SDK References are always inserted second-to-last (before Resources section). + * + * @param newTree - New sections from current indexer run + * @param existingTree - Existing wallets navigation tree from Redis (or null if none) + * @param indexerType - Type of indexer: "sdk" means newTree is SDK refs, "main" means newTree is manual content + * @returns Merged tree with manual sections + SDK sections at second-to-last position + */ +export const mergeWalletsNavTree = ( + newTree: NavigationTree, + existingTree: NavigationTree | null, + indexerType: "main" | "sdk", +): NavigationTree => { + if (!existingTree) { + if (indexerType === "sdk") { + console.warn("⚠️ No existing wallets nav tree found, creating new one"); + } + return newTree; + } + + console.info("📖 Read existing wallets nav tree from Redis"); + + // Separate SDK and manual sections from existing tree + const { sdk: existingSDK, manual: existingManual } = + separateSDKAndManualSections(existingTree); + + // Determine which sections are new and which to preserve + const manualSections = indexerType === "main" ? newTree : existingManual; + const sdkSections = indexerType === "sdk" ? newTree : existingSDK; + + // Log preservation info + if (indexerType === "main" && sdkSections.length > 0) { + console.info( + `📖 Preserved ${sdkSections.length} SDK reference section(s) in wallets nav tree`, + ); + } + + // Handle edge cases + if (sdkSections.length === 0) { + return manualSections; + } + if (manualSections.length === 0) { + return sdkSections; + } + + // Insert SDK sections at second-to-last position (before Resources) + return [ + ...manualSections.slice(0, -1), // All manual sections except last + ...sdkSections, // SDK sections + manualSections[manualSections.length - 1], // Last section (Resources) + ]; +}; From 1c5cfe4d934dbf0ff4c13fe0cc8433288328cae9 Mon Sep 17 00:00:00 2001 From: dslovinsky Date: Thu, 8 Jan 2026 20:35:17 -0800 Subject: [PATCH 08/27] feat: port over changelog functionality to content-indexer --- src/content-indexer/indexers/changelog.ts | 136 ++++++++++++++++++++-- src/content-indexer/types/pathIndex.ts | 2 +- 2 files changed, 129 insertions(+), 9 deletions(-) diff --git a/src/content-indexer/indexers/changelog.ts b/src/content-indexer/indexers/changelog.ts index 6f5898131..8b1c67541 100644 --- a/src/content-indexer/indexers/changelog.ts +++ b/src/content-indexer/indexers/changelog.ts @@ -1,18 +1,67 @@ +import { promises as fs } from "fs"; +import path from "path"; + import type { AlgoliaRecord } from "@/content-indexer/types/algolia.js"; import type { PathIndex } from "@/content-indexer/types/pathIndex.js"; +import { readLocalFile } from "@/content-indexer/utils/filesystem.js"; +import { truncateRecord } from "@/content-indexer/utils/truncate-record.js"; export interface ChangelogIndexerConfig { localBasePath: string; // Path to fern/changelog/ directory branchId: string; } +/** + * Parse a changelog filename (e.g., "2025-11-20.md") into date components + */ +const parseChangelogFilename = ( + filename: string, +): { + date: string; + year: string; + month: string; + day: string; +} | null => { + const match = filename.match(/^(\d{4})-(\d{2})-(\d{2})\.md$/); + if (!match) return null; + + const [, year, month, day] = match; + return { + date: `${year}-${month}-${day}`, + year, + month, + day, + }; +}; + +/** + * Extract text content from markdown for Algolia indexing. + * Strips markdown syntax to get plain text. + */ +const extractTextFromMarkdown = (markdown: string): string => { + return ( + markdown + // Remove headings markdown + .replace(/^#{1,6}\s+/gm, "") + // Remove links but keep text + .replace(/\[([^\]]+)\]\([^)]+\)/g, "$1") + // Remove bold/italic + .replace(/[*_]{1,2}([^*_]+)[*_]{1,2}/g, "$1") + // Remove inline code + .replace(/`([^`]+)`/g, "$1") + // Collapse multiple spaces/newlines + .replace(/\s+/g, " ") + .trim() + ); +}; + /** * Changelog indexer for changelog entries. * Simpler than main indexer - no nav trees, just path index and Algolia. * * Updates: * - {branch}/path-index:changelog - * - alchemy_docs_changelog Algolia index + * - {branch}_alchemy_docs_changelog Algolia index */ export const buildChangelogIndex = async ( config: ChangelogIndexerConfig, @@ -22,16 +71,87 @@ export const buildChangelogIndex = async ( }> => { console.info(`🔍 Building changelog index (branch: ${config.branchId})...`); - // TODO: Implement changelog indexing - // For now, return empty results - // Will be implemented when we add changelog support + // Read all files from changelog directory + const files = await fs.readdir(config.localBasePath); + + // Filter and parse changelog files + const changelogFiles = files + .map((filename) => { + const parsed = parseChangelogFilename(filename); + if (!parsed) { + // Silently skip dotfiles (.gitkeep, .DS_Store, etc.) + if (!filename.startsWith(".")) { + console.warn(` ⚠️ Skipping non-date file: ${filename}`); + } + return null; + } + return { filename, ...parsed }; + }) + .filter((entry): entry is NonNullable => entry !== null); + + // Sort by date descending (newest first) + changelogFiles.sort((a, b) => b.date.localeCompare(a.date)); + + console.info(` 📄 Found ${changelogFiles.length} changelog entries`); + + // Process each changelog file in parallel + const results = await Promise.all( + changelogFiles.map(async ({ filename, date, year, month, day }) => { + const filePath = path.join(config.localBasePath, filename); + const content = await readLocalFile(filePath); + + if (!content) { + console.warn(` ⚠️ Failed to read: ${filename}`); + return null; + } + + // Build route: e.g., "2025/11/20" + const route = `${year}/${Number(month)}/${Number(day)}`; + + // Create path index entry + const pathIndexEntry = { + type: "mdx" as const, + filePath: `fern/changelog/${filename}`, + source: "changelog" as const, + tab: "changelog", + }; + + // Create Algolia record + const plainText = extractTextFromMarkdown(content); + const algoliaRecord = truncateRecord({ + objectID: `changelog-${date}`, + title: `Changelog - ${date}`, + content: plainText, + path: `changelog/${route}`, + pageType: "Changelog" as const, + breadcrumbs: ["Changelog", date], + }); + + return { route, pathIndexEntry, algoliaRecord }; + }), + ); + + // Build final outputs from results in a single pass + const { pathIndex, algoliaRecords } = results.reduce<{ + pathIndex: PathIndex; + algoliaRecords: AlgoliaRecord[]; + }>( + (acc, result) => { + if (result) { + acc.pathIndex[result.route] = result.pathIndexEntry; + acc.algoliaRecords.push(result.algoliaRecord); + } + return acc; + }, + { pathIndex: {}, algoliaRecords: [] }, + ); - console.warn( - "⚠️ Changelog indexing not yet implemented - returning empty results", + console.info( + `\n📊 Changelog index complete: ${Object.keys(pathIndex).length} routes, ${algoliaRecords.length} Algolia records`, ); return { - pathIndex: {}, - algoliaRecords: [], + pathIndex, + algoliaRecords, }; }; diff --git a/src/content-indexer/types/pathIndex.ts b/src/content-indexer/types/pathIndex.ts index b41eeb87f..e02aa4f5d 100644 --- a/src/content-indexer/types/pathIndex.ts +++ b/src/content-indexer/types/pathIndex.ts @@ -1,7 +1,7 @@ export interface MdxPathIndexEntry { type: "mdx"; filePath: string; - source: "frontmatter" | "docs-yml" | "runtime-discovery"; + source: "frontmatter" | "docs-yml" | "runtime-discovery" | "changelog"; tab: string; } From 93f195beef5b11bcb64efb329dd715192109ab7a Mon Sep 17 00:00:00 2001 From: dslovinsky Date: Thu, 8 Jan 2026 20:57:26 -0800 Subject: [PATCH 09/27] feat: remove markdown syntax from algolia content field --- package.json | 4 ++- pnpm-lock.yaml | 16 +++++++++ src/content-indexer/indexers/changelog.ts | 35 ++++---------------- src/content-indexer/utils/truncate-record.ts | 11 ++++-- 4 files changed, 35 insertions(+), 31 deletions(-) diff --git a/package.json b/package.json index 138b2e26b..f63f7c1e7 100644 --- a/package.json +++ b/package.json @@ -52,7 +52,8 @@ "js-yaml": "^4.1.1", "json-schema-merge-allof": "^0.8.1", "lodash-es": "^4.17.21", - "octokit": "^5.0.5" + "octokit": "^5.0.5", + "remove-markdown": "^0.6.2" }, "devDependencies": { "@eslint/js": "^9.25.0", @@ -64,6 +65,7 @@ "@types/lodash-es": "^4.17.12", "@types/node": "^22.14.1", "@types/react": "^19.1.12", + "@types/remove-markdown": "^0.3.4", "@typescript-eslint/parser": "^8.31.0", "chokidar": "^4.0.3", "eslint": "^9.25.1", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index b0fe2aeda..37561c3a7 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -38,6 +38,9 @@ importers: octokit: specifier: ^5.0.5 version: 5.0.5 + remove-markdown: + specifier: ^0.6.2 + version: 0.6.2 devDependencies: '@eslint/js': specifier: ^9.25.0 @@ -66,6 +69,9 @@ importers: '@types/react': specifier: ^19.1.12 version: 19.1.12 + '@types/remove-markdown': + specifier: ^0.3.4 + version: 0.3.4 '@typescript-eslint/parser': specifier: ^8.31.0 version: 8.31.0(eslint@9.25.1(jiti@2.4.2))(typescript@5.8.3) @@ -1076,6 +1082,9 @@ packages: '@types/react@19.1.12': resolution: {integrity: sha512-cMoR+FoAf/Jyq6+Df2/Z41jISvGZZ2eTlnsaJRptmZ76Caldwy1odD4xTr/gNV9VLj0AWgg/nmkevIyUfIIq5w==} + '@types/remove-markdown@0.3.4': + resolution: {integrity: sha512-i753EH/p02bw7bLlpfS/4CV1rdikbGiLabWyVsAvsFid3cA5RNU1frG7JycgY+NSnFwtoGlElvZVceCytecTDA==} + '@types/stylis@4.2.5': resolution: {integrity: sha512-1Xve+NMN7FWjY14vLoY5tL3BVEQ/n42YLwaqJIPYhotZ9uBHt87VceMwWQpzmdEt2TNXIorIFG+YeCUUW7RInw==} @@ -2825,6 +2834,9 @@ packages: remark-stringify@11.0.0: resolution: {integrity: sha512-1OSmLd3awB/t8qdoEOMazZkNsfVTeY4fTsgzcQFdXNq8ToTN4ZGwrMnlda4K6smTFKD+GRV6O48i6Z4iKgPPpw==} + remove-markdown@0.6.2: + resolution: {integrity: sha512-EijDXJZbzpGbQBd852ViUzcqgpMujthM+SAEHiWCMcZonRbZ+xViWKLJA/vrwbDwYdxrs1aFDjpBhcGrZoJRGA==} + require-directory@2.1.1: resolution: {integrity: sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==} engines: {node: '>=0.10.0'} @@ -4383,6 +4395,8 @@ snapshots: dependencies: csstype: 3.1.3 + '@types/remove-markdown@0.3.4': {} + '@types/stylis@4.2.5': {} '@types/supports-color@8.1.3': {} @@ -6812,6 +6826,8 @@ snapshots: mdast-util-to-markdown: 2.1.2 unified: 11.0.5 + remove-markdown@0.6.2: {} + require-directory@2.1.1: {} require-from-string@2.0.2: {} diff --git a/src/content-indexer/indexers/changelog.ts b/src/content-indexer/indexers/changelog.ts index 8b1c67541..4d0b564f4 100644 --- a/src/content-indexer/indexers/changelog.ts +++ b/src/content-indexer/indexers/changelog.ts @@ -34,27 +34,6 @@ const parseChangelogFilename = ( }; }; -/** - * Extract text content from markdown for Algolia indexing. - * Strips markdown syntax to get plain text. - */ -const extractTextFromMarkdown = (markdown: string): string => { - return ( - markdown - // Remove headings markdown - .replace(/^#{1,6}\s+/gm, "") - // Remove links but keep text - .replace(/\[([^\]]+)\]\([^)]+\)/g, "$1") - // Remove bold/italic - .replace(/[*_]{1,2}([^*_]+)[*_]{1,2}/g, "$1") - // Remove inline code - .replace(/`([^`]+)`/g, "$1") - // Collapse multiple spaces/newlines - .replace(/\s+/g, " ") - .trim() - ); -}; - /** * Changelog indexer for changelog entries. * Simpler than main indexer - no nav trees, just path index and Algolia. @@ -94,9 +73,8 @@ export const buildChangelogIndex = async ( console.info(` 📄 Found ${changelogFiles.length} changelog entries`); - // Process each changelog file in parallel - const results = await Promise.all( - changelogFiles.map(async ({ filename, date, year, month, day }) => { + const resultPromises = changelogFiles.map( + async ({ filename, date, year, month, day }) => { const filePath = path.join(config.localBasePath, filename); const content = await readLocalFile(filePath); @@ -117,21 +95,22 @@ export const buildChangelogIndex = async ( }; // Create Algolia record - const plainText = extractTextFromMarkdown(content); const algoliaRecord = truncateRecord({ objectID: `changelog-${date}`, title: `Changelog - ${date}`, - content: plainText, + content, // Raw markdown - truncateRecord will clean it path: `changelog/${route}`, pageType: "Changelog" as const, breadcrumbs: ["Changelog", date], }); return { route, pathIndexEntry, algoliaRecord }; - }), + }, ); - // Build final outputs from results in a single pass + const results = await Promise.all(resultPromises); + + // Build final outputs from results const { pathIndex, algoliaRecords } = results.reduce<{ pathIndex: PathIndex; algoliaRecords: AlgoliaRecord[]; diff --git a/src/content-indexer/utils/truncate-record.ts b/src/content-indexer/utils/truncate-record.ts index 569d36187..35dfaa67b 100644 --- a/src/content-indexer/utils/truncate-record.ts +++ b/src/content-indexer/utils/truncate-record.ts @@ -1,3 +1,5 @@ +import removeMd from "remove-markdown"; + import type { AlgoliaRecord } from "@/content-indexer/types/algolia.js"; const MAX_RECORD_BYTES = 100_000; // Algolia imposes a 100KB limit on each record @@ -6,8 +8,13 @@ const MAX_ITERATIONS = 5; /** * Truncate record content to ensure entire JSON payload fits within Algolia limit. + * Also strips markdown formatting from content for better search experience. */ -export const truncateRecord = (record: AlgoliaRecord): AlgoliaRecord => { +export const truncateRecord = (rawRecord: AlgoliaRecord): AlgoliaRecord => { + // Strip markdown formatting to get clean, searchable text + const cleanedContent = removeMd(rawRecord.content); + const record = { ...rawRecord, content: cleanedContent }; + const fullRecordJson = JSON.stringify(record); const recordBytes = Buffer.byteLength(fullRecordJson, "utf8"); @@ -37,7 +44,7 @@ export const truncateRecord = (record: AlgoliaRecord): AlgoliaRecord => { // Iteratively truncate content while measuring full JSON record size // This accounts for JSON escaping overhead (quotes, backslashes, etc.) - let truncatedContent = record.content; + let truncatedContent = cleanedContent; let truncatedRecord: AlgoliaRecord = { ...record, content: truncatedContent }; let currentBytes = recordBytes; let iterations = 0; From 2101a10283aaaa1f422309c6610f95d62914a201 Mon Sep 17 00:00:00 2001 From: dslovinsky Date: Thu, 8 Jan 2026 21:28:44 -0800 Subject: [PATCH 10/27] refactor: reduce duplicate code --- eslint.config.ts | 1 + src/content-indexer/__tests__/index.test.ts | 16 +- src/content-indexer/index.ts | 146 ++++++------------ src/content-indexer/indexers/changelog.ts | 7 +- src/content-indexer/indexers/main.ts | 89 +++++------ src/content-indexer/indexers/sdk.ts | 99 ------------ src/content-indexer/types/indexer.ts | 12 ++ .../visitors/visit-api-reference.ts | 2 +- src/content-indexer/visitors/visit-link.ts | 2 +- src/content-indexer/visitors/visit-page.ts | 2 +- src/content-indexer/visitors/visit-section.ts | 6 +- 11 files changed, 127 insertions(+), 255 deletions(-) delete mode 100644 src/content-indexer/indexers/sdk.ts create mode 100644 src/content-indexer/types/indexer.ts diff --git a/eslint.config.ts b/eslint.config.ts index 083deb8ad..90c1d53a3 100644 --- a/eslint.config.ts +++ b/eslint.config.ts @@ -71,6 +71,7 @@ const tslintConfigs = tseslint.config({ }, ], "@typescript-eslint/consistent-type-imports": "error", + "@typescript-eslint/no-non-null-assertion": "error", }, }) as ConfigWithExtends[]; diff --git a/src/content-indexer/__tests__/index.test.ts b/src/content-indexer/__tests__/index.test.ts index b41a45c1f..ff01fdff5 100644 --- a/src/content-indexer/__tests__/index.test.ts +++ b/src/content-indexer/__tests__/index.test.ts @@ -4,7 +4,7 @@ import { batchFetchContent } from "@/content-indexer/core/batch-fetcher.js"; import { buildAllOutputs } from "@/content-indexer/core/build-all-outputs.js"; import { ContentCache } from "@/content-indexer/core/content-cache.js"; import { scanDocsYml } from "@/content-indexer/core/scanner.js"; -import { buildMainContentIndex } from "@/content-indexer/indexers/main.js"; +import { buildDocsContentIndex } from "@/content-indexer/indexers/main.js"; import { fetchFileFromGitHub } from "@/content-indexer/utils/github.js"; import { repoConfigFactory } from "@/content-indexer/utils/test-factories.js"; @@ -29,7 +29,7 @@ vi.mock("@/content-indexer/core/build-all-outputs", () => ({ buildAllOutputs: vi.fn(), })); -describe("buildMainContentIndex", () => { +describe("buildDocsContentIndex", () => { let consoleInfoSpy: ReturnType; beforeEach(() => { @@ -71,11 +71,11 @@ describe("buildMainContentIndex", () => { const repoConfig = repoConfigFactory({ docsPrefix: "docs" }); - const result = await buildMainContentIndex({ - mode: "preview", - localBasePath: "/test/fern", + const result = await buildDocsContentIndex({ + source: { type: "filesystem", basePath: "/test/fern" }, branchId: "test-branch", repoConfig, + mode: "preview", }); // Verify all phases were called @@ -115,11 +115,11 @@ navigation: algoliaRecords: [], }); - await buildMainContentIndex({ - mode: "production", - localBasePath: "/test/fern", + await buildDocsContentIndex({ + source: { type: "filesystem", basePath: "/test/fern" }, branchId: "main", repoConfig, + mode: "production", }); // Verify local filesystem was still used (both modes use local in main indexer) diff --git a/src/content-indexer/index.ts b/src/content-indexer/index.ts index 409bb9468..8d2b48d19 100644 --- a/src/content-indexer/index.ts +++ b/src/content-indexer/index.ts @@ -2,8 +2,8 @@ import path from "path"; import { buildChangelogIndex } from "@/content-indexer/indexers/changelog.js"; -import { buildMainContentIndex } from "@/content-indexer/indexers/main.js"; -import { buildSDKContentIndex } from "@/content-indexer/indexers/sdk.js"; +import { buildDocsContentIndex } from "@/content-indexer/indexers/main.js"; +import type { IndexerResult } from "@/content-indexer/types/indexer.js"; import { uploadToAlgolia } from "@/content-indexer/uploaders/algolia.js"; import { storeToRedis } from "@/content-indexer/uploaders/redis.js"; import { DOCS_REPO, WALLET_REPO } from "@/content-indexer/utils/github.js"; @@ -42,105 +42,67 @@ const parseArgs = () => { }; // ============================================================================ -// Main Indexer +// Indexer Runner // ============================================================================ -const runMainIndexer = async ( - mode: "preview" | "production", +const buildIndexResults = async ( + indexerType: "main" | "sdk" | "changelog", branchId: string, -) => { - console.info( - `\n🔍 Running MAIN indexer (${mode} mode, branch: ${branchId})\n`, - ); - - const { pathIndex, navigationTrees, algoliaRecords } = - await buildMainContentIndex({ - mode, - localBasePath: path.join(process.cwd(), "fern"), - branchId, - repoConfig: DOCS_REPO, - }); - - console.info("\n📤 Uploading to Redis and Algolia..."); - - await Promise.all([ - storeToRedis(pathIndex, navigationTrees, { - branchId, - indexerType: "main", - }), - uploadToAlgolia(algoliaRecords, { - indexerType: "main", - branchId, - }), - ]); - - console.info( - `\n✅ Main indexer completed! (${Object.keys(pathIndex).length} routes, ${algoliaRecords.length} records)`, - ); -}; - -// ============================================================================ -// SDK Indexer -// ============================================================================ - -const runSDKIndexer = async (branchId: string) => { - console.info(`\n🔍 Running SDK indexer (branch: ${branchId})\n`); - - const { pathIndex, walletsNavTree, algoliaRecords } = - await buildSDKContentIndex({ - sdkRepoConfig: WALLET_REPO, - branchId, - }); - - console.info("\n📤 Uploading to Redis and Algolia..."); - - await Promise.all([ - storeToRedis( - pathIndex, - { wallets: walletsNavTree }, - { + mode: "preview" | "production" = "production", +): Promise => { + switch (indexerType) { + case "changelog": + return buildChangelogIndex({ + localBasePath: path.join(process.cwd(), "fern/changelog"), branchId, - indexerType: "sdk", - }, - ), - uploadToAlgolia(algoliaRecords, { - indexerType: "sdk", - branchId, - }), - ]); + }); + case "main": + return buildDocsContentIndex({ + source: { + type: "filesystem", + basePath: path.join(process.cwd(), "fern"), + }, + repoConfig: DOCS_REPO, + branchId, + mode, + }); + case "sdk": { + const result = await buildDocsContentIndex({ + source: { type: "github", repoConfig: WALLET_REPO }, + repoConfig: WALLET_REPO, + branchId, + }); + return { + ...result, + navigationTrees: { + wallets: result.navigationTrees?.wallets || [], + }, + }; + } + } +}; +const runIndexer = async ( + indexerType: "main" | "sdk" | "changelog", + branchId: string, + mode?: "preview" | "production", +) => { console.info( - `\n✅ SDK indexer completed! (${Object.keys(pathIndex).length} routes, ${algoliaRecords.length} records)`, + `\n🔍 Running ${indexerType.toUpperCase()} indexer${indexerType === "main" && mode ? ` (${mode} mode)` : ""} (branch: ${branchId})\n`, ); -}; -// ============================================================================ -// Changelog Indexer -// ============================================================================ - -const runChangelogIndexer = async (branchId: string) => { - console.info(`\n🔍 Running CHANGELOG indexer (branch: ${branchId})\n`); - - const { pathIndex, algoliaRecords } = await buildChangelogIndex({ - localBasePath: path.join(process.cwd(), "fern/changelog"), - branchId, - }); + const { pathIndex, algoliaRecords, navigationTrees } = + await buildIndexResults(indexerType, branchId, mode); console.info("\n📤 Uploading to Redis and Algolia..."); await Promise.all([ - storeToRedis(pathIndex, undefined, { - branchId, - indexerType: "changelog", - }), - uploadToAlgolia(algoliaRecords, { - indexerType: "changelog", - branchId, - }), + storeToRedis(pathIndex, navigationTrees, { branchId, indexerType }), + uploadToAlgolia(algoliaRecords, { indexerType, branchId }), ]); console.info( - `\n✅ Changelog indexer completed! (${Object.keys(pathIndex).length} routes, ${algoliaRecords.length} records)`, + `\n✅ ${indexerType.charAt(0).toUpperCase() + indexerType.slice(1)} indexer completed! (${Object.keys(pathIndex).length} routes, ${algoliaRecords.length} records)`, ); }; @@ -158,17 +120,7 @@ const main = async () => { console.info(` Mode: ${mode}`); console.info(` Branch: ${branchId}`); - switch (indexer) { - case "main": - await runMainIndexer(mode, branchId); - break; - case "sdk": - await runSDKIndexer(branchId); - break; - case "changelog": - await runChangelogIndexer(branchId); - break; - } + await runIndexer(indexer, branchId, mode); } catch (error) { console.error("\n❌ Error:", error); process.exit(1); diff --git a/src/content-indexer/indexers/changelog.ts b/src/content-indexer/indexers/changelog.ts index 4d0b564f4..3913b31d5 100644 --- a/src/content-indexer/indexers/changelog.ts +++ b/src/content-indexer/indexers/changelog.ts @@ -2,6 +2,7 @@ import { promises as fs } from "fs"; import path from "path"; import type { AlgoliaRecord } from "@/content-indexer/types/algolia.js"; +import type { IndexerResult } from "@/content-indexer/types/indexer.js"; import type { PathIndex } from "@/content-indexer/types/pathIndex.js"; import { readLocalFile } from "@/content-indexer/utils/filesystem.js"; import { truncateRecord } from "@/content-indexer/utils/truncate-record.js"; @@ -44,10 +45,7 @@ const parseChangelogFilename = ( */ export const buildChangelogIndex = async ( config: ChangelogIndexerConfig, -): Promise<{ - pathIndex: PathIndex; - algoliaRecords: AlgoliaRecord[]; -}> => { +): Promise => { console.info(`🔍 Building changelog index (branch: ${config.branchId})...`); // Read all files from changelog directory @@ -132,5 +130,6 @@ export const buildChangelogIndex = async ( return { pathIndex, algoliaRecords, + navigationTrees: undefined, // changelog has no sidebar nav }; }; diff --git a/src/content-indexer/indexers/main.ts b/src/content-indexer/indexers/main.ts index 921c62576..40f4cde1d 100644 --- a/src/content-indexer/indexers/main.ts +++ b/src/content-indexer/indexers/main.ts @@ -1,72 +1,75 @@ -import type { BuildAllOutputsResult } from "@/content-indexer/collectors/processing-context.js"; +import yaml from "js-yaml"; + import { batchFetchContent, type ContentSource, } from "@/content-indexer/core/batch-fetcher.js"; import { buildAllOutputs } from "@/content-indexer/core/build-all-outputs.js"; import { scanDocsYml } from "@/content-indexer/core/scanner.js"; +import type { DocsYml } from "@/content-indexer/types/docsYaml.js"; +import type { IndexerResult } from "@/content-indexer/types/indexer.js"; import { readLocalDocsYml } from "@/content-indexer/utils/filesystem.js"; -import type { RepoConfig } from "@/content-indexer/utils/github.js"; +import { + fetchFileFromGitHub, + type RepoConfig, +} from "@/content-indexer/utils/github.js"; -export interface MainIndexerConfig { - mode: "preview" | "production"; - localBasePath: string; // Path to fern/ directory - branchId: string; // For branch-scoped Redis keys (e.g., "main" or "branch-abc123") - repoConfig: RepoConfig; // Needed for path-building +export interface DocsIndexerConfig { + source: ContentSource; // filesystem or github + repoConfig: RepoConfig; + branchId: string; + mode?: "preview" | "production"; // Only relevant for logging } /** - * Main content indexer for docs repo. - * Processes docs.yml and all manual content (main docs + manual wallet pages). + * Unified docs content indexer. + * Handles both main docs (local filesystem) and SDK refs (GitHub API). * - * - Preview mode: 100% local filesystem (zero GitHub API calls) - * - Production mode: Same as preview (local only) - * - * Updates: - * - {branch}/path-index:main - * - {branch}/nav-tree:* (all tabs including wallets with manual sections) - * - alchemy_docs Algolia index + * Processes docs.yml through 3 phases: + * 1. SCAN - Parse docs.yml to discover all paths and specs + * 2. BATCH FETCH - Read all content (filesystem or GitHub) + * 3. PROCESS - Build path index, navigation trees, and Algolia records */ -export const buildMainContentIndex = async ( - config: MainIndexerConfig, -): Promise => { - const source: ContentSource = { - type: "filesystem", - basePath: config.localBasePath, - }; +export const buildDocsContentIndex = async ( + config: DocsIndexerConfig, +): Promise => { + console.info(`🔍 Building content index (branch: ${config.branchId})...`); - console.info( - `🔍 Building main content index (${config.mode} mode, branch: ${config.branchId})...`, - ); - - // Read and parse local docs.yml - const docsYml = await readLocalDocsYml(config.localBasePath); - if (!docsYml) { - throw new Error(`Failed to read docs.yml from ${config.localBasePath}`); + // Read docs.yml based on source type + let docsYml: DocsYml; + if (config.source.type === "filesystem") { + const result = await readLocalDocsYml(config.source.basePath); + if (!result) { + throw new Error(`Failed to read docs.yml from ${config.source.basePath}`); + } + docsYml = result; + } else { + const docsYmlPath = `${config.repoConfig.docsPrefix}/docs.yml`; + const content = await fetchFileFromGitHub(docsYmlPath, config.repoConfig); + if (!content) { + throw new Error(`Failed to fetch ${docsYmlPath} from GitHub`); + } + docsYml = yaml.load(content) as DocsYml; } // PHASE 1: SCAN - console.info("📋 Phase 1: Scanning docs.yml for all paths and specs..."); + console.info("📋 Phase 1: Scanning docs.yml..."); const scanResult = scanDocsYml(docsYml); console.info( ` Found ${scanResult.mdxPaths.size} MDX files, ${scanResult.specNames.size} specs`, ); - // PHASE 2: BATCH READ (all from filesystem) - console.info("📥 Phase 2: Reading all content from filesystem..."); - const contentCache = await batchFetchContent(scanResult, source); + // PHASE 2: BATCH FETCH + console.info("📥 Phase 2: Fetching content..."); + const contentCache = await batchFetchContent(scanResult, config.source); // PHASE 3: PROCESS - console.info("⚙️ Phase 3: Processing with cached content..."); - const { pathIndex, navigationTrees, algoliaRecords } = buildAllOutputs( - docsYml, - contentCache, - config.repoConfig, - ); + console.info("⚙️ Phase 3: Processing..."); + const outputs = buildAllOutputs(docsYml, contentCache, config.repoConfig); console.info( - `📊 Generated ${Object.keys(pathIndex).length} routes, ${algoliaRecords.length} Algolia records`, + `📊 Generated ${Object.keys(outputs.pathIndex).length} routes, ${outputs.algoliaRecords.length} Algolia records`, ); - return { pathIndex, navigationTrees, algoliaRecords }; + return outputs; }; diff --git a/src/content-indexer/indexers/sdk.ts b/src/content-indexer/indexers/sdk.ts deleted file mode 100644 index 44312f4c8..000000000 --- a/src/content-indexer/indexers/sdk.ts +++ /dev/null @@ -1,99 +0,0 @@ -import yaml from "js-yaml"; - -import { - batchFetchContent, - type ContentSource, -} from "@/content-indexer/core/batch-fetcher.js"; -import { buildAllOutputs } from "@/content-indexer/core/build-all-outputs.js"; -import { scanDocsYml } from "@/content-indexer/core/scanner.js"; -import type { AlgoliaRecord } from "@/content-indexer/types/algolia.js"; -import type { DocsYml } from "@/content-indexer/types/docsYaml.js"; -import type { NavItem } from "@/content-indexer/types/navigation.js"; -import type { PathIndex } from "@/content-indexer/types/pathIndex.js"; -import { - fetchFileFromGitHub, - type RepoConfig, -} from "@/content-indexer/utils/github.js"; - -export interface SDKIndexerConfig { - sdkRepoConfig: RepoConfig; // aa-sdk repo config - branchId: string; // Usually "main" for production -} - -/** - * SDK references indexer for aa-sdk repo. - * Fetches SDK refs from aa-sdk via GitHub API and merges into wallets nav tree. - * - * Uses read-modify-write pattern: - * 1. Read existing {branch}/nav-tree:wallets from Redis - * 2. Extract and keep manual content sections - * 3. Fetch and process SDK refs from aa-sdk - * 4. Generate new SDK Reference section - * 5. Merge manual sections + SDK section - * 6. Write back complete tree - * - * Updates: - * - {branch}/path-index:sdk-refs - * - {branch}/nav-tree:wallets (read-modify-write) - * - alchemy_docs_wallets Algolia index - */ -export const buildSDKContentIndex = async ( - config: SDKIndexerConfig, -): Promise<{ - pathIndex: PathIndex; - walletsNavTree: NavItem[]; - algoliaRecords: AlgoliaRecord[]; -}> => { - const source: ContentSource = { - type: "github", - repoConfig: config.sdkRepoConfig, - }; - - console.info(`🔍 Building SDK content index (branch: ${config.branchId})...`); - - // Fetch and parse aa-sdk docs.yml - const docsYmlPath = `${config.sdkRepoConfig.docsPrefix}/docs.yml`; - const docsYmlContent = await fetchFileFromGitHub( - docsYmlPath, - config.sdkRepoConfig, - ); - if (!docsYmlContent) { - throw new Error( - `Failed to fetch ${docsYmlPath} from ${config.sdkRepoConfig.repo}`, - ); - } - const docsYml = yaml.load(docsYmlContent) as DocsYml; - - // PHASE 1: SCAN - console.info("📋 Phase 1: Scanning aa-sdk docs.yml for SDK refs..."); - const scanResult = scanDocsYml(docsYml); - console.info( - ` Found ${scanResult.mdxPaths.size} MDX files (SDK refs), ${scanResult.specNames.size} specs`, - ); - - // PHASE 2: BATCH FETCH (from GitHub) - console.info("📥 Phase 2: Fetching SDK refs from GitHub..."); - const contentCache = await batchFetchContent(scanResult, source); - - // PHASE 3: PROCESS - console.info("⚙️ Phase 3: Processing SDK refs..."); - const { pathIndex, navigationTrees, algoliaRecords } = buildAllOutputs( - docsYml, - contentCache, - config.sdkRepoConfig, - ); - - // TODO: Implement read-modify-write for wallets nav tree - // For now, return the SDK nav tree as-is - // In Phase 3 completion, we'll add Redis read/merge logic - - console.info( - `📊 Generated ${Object.keys(pathIndex).length} SDK ref routes, ${algoliaRecords.length} Algolia records`, - ); - - return { - pathIndex, - walletsNavTree: navigationTrees.wallets || [], - algoliaRecords, - }; -}; diff --git a/src/content-indexer/types/indexer.ts b/src/content-indexer/types/indexer.ts new file mode 100644 index 000000000..bfa27ca49 --- /dev/null +++ b/src/content-indexer/types/indexer.ts @@ -0,0 +1,12 @@ +import type { AlgoliaRecord } from "@/content-indexer/types/algolia.js"; +import type { NavigationTreesByTab } from "@/content-indexer/types/navigation.js"; +import type { PathIndex } from "@/content-indexer/types/pathIndex.js"; + +/** + * Standard result structure returned by all indexers. + */ +export interface IndexerResult { + pathIndex: PathIndex; + algoliaRecords: AlgoliaRecord[]; + navigationTrees: NavigationTreesByTab | undefined; +} diff --git a/src/content-indexer/visitors/visit-api-reference.ts b/src/content-indexer/visitors/visit-api-reference.ts index 8dc1a5ecd..7fc27d1d3 100644 --- a/src/content-indexer/visitors/visit-api-reference.ts +++ b/src/content-indexer/visitors/visit-api-reference.ts @@ -6,7 +6,7 @@ import type { OpenRpcSpec, } from "@/content-indexer/types/specs.js"; -import type { VisitorConfigBase, VisitorResult } from "."; +import type { VisitorConfigBase, VisitorResult } from "./index.js"; import { processOpenApiSpec } from "./processors/process-openapi.js"; import { processOpenRpcSpec } from "./processors/process-openrpc.js"; diff --git a/src/content-indexer/visitors/visit-link.ts b/src/content-indexer/visitors/visit-link.ts index a3b4ae9fc..8b478289a 100644 --- a/src/content-indexer/visitors/visit-link.ts +++ b/src/content-indexer/visitors/visit-link.ts @@ -1,6 +1,6 @@ import type { LinkConfig } from "@/content-indexer/types/docsYaml.js"; -import type { VisitorConfigBase, VisitorResult } from "."; +import type { VisitorConfigBase, VisitorResult } from "./index.js"; export interface LinkVisitorConfig extends VisitorConfigBase { item: LinkConfig; diff --git a/src/content-indexer/visitors/visit-page.ts b/src/content-indexer/visitors/visit-page.ts index 13e5a4aa3..de2c1932f 100644 --- a/src/content-indexer/visitors/visit-page.ts +++ b/src/content-indexer/visitors/visit-page.ts @@ -7,7 +7,7 @@ import { normalizeSlug, } from "@/content-indexer/utils/normalization.js"; -import type { VisitorConfigBase, VisitorResult } from "."; +import type { VisitorConfigBase, VisitorResult } from "./index.js"; export interface PageVisitorConfig extends VisitorConfigBase { item: PageConfig; diff --git a/src/content-indexer/visitors/visit-section.ts b/src/content-indexer/visitors/visit-section.ts index e3b481503..da697c33e 100644 --- a/src/content-indexer/visitors/visit-section.ts +++ b/src/content-indexer/visitors/visit-section.ts @@ -8,7 +8,11 @@ import { normalizeSlug, } from "@/content-indexer/utils/normalization.js"; -import type { VisitorConfig, VisitorConfigBase, VisitorResult } from "."; +import type { + VisitorConfig, + VisitorConfigBase, + VisitorResult, +} from "./index.js"; export interface SectionVisitorConfig extends VisitorConfigBase { item: SectionConfig; From 2c31f114b3740ded8ba8ec351dd7bd2163c3f754 Mon Sep 17 00:00:00 2001 From: dslovinsky Date: Thu, 8 Jan 2026 21:36:28 -0800 Subject: [PATCH 11/27] build: configure tests and test coverage --- coverage/base.css | 224 + coverage/block-navigation.js | 84 + coverage/collectors/algolia.ts.html | 331 + coverage/collectors/index.html | 161 + coverage/collectors/navigation-trees.ts.html | 259 + coverage/collectors/path-index.ts.html | 232 + .../collectors/processing-context.ts.html | 316 + coverage/constants/http.ts.html | 121 + coverage/constants/index.html | 116 + coverage/core/batch-fetcher.ts.html | 397 + coverage/core/build-all-outputs.ts.html | 340 + coverage/core/content-cache.ts.html | 343 + coverage/core/index.html | 176 + coverage/core/path-builder.ts.html | 259 + coverage/core/scanner.ts.html | 340 + coverage/coverage-final.json | 8298 +++++++++++++++++ coverage/favicon.png | Bin 0 -> 445 bytes coverage/index.html | 236 + coverage/indexers/index.html | 116 + coverage/indexers/main.ts.html | 310 + coverage/prettify.css | 1 + coverage/prettify.js | 1008 ++ coverage/sort-arrow-sprite.png | Bin 0 -> 138 bytes coverage/sorter.js | 209 + coverage/types/docsYaml.ts.html | 358 + coverage/types/index.html | 116 + coverage/uploaders/algolia.ts.html | 472 + coverage/uploaders/index.html | 131 + coverage/uploaders/redis.ts.html | 364 + coverage/utils/filesystem.ts.html | 298 + coverage/utils/github.ts.html | 691 ++ coverage/utils/index.html | 236 + coverage/utils/nav-tree-merge.ts.html | 364 + coverage/utils/navigation-helpers.ts.html | 127 + coverage/utils/normalization.ts.html | 151 + coverage/utils/openapi.ts.html | 532 ++ coverage/utils/openrpc.ts.html | 124 + coverage/utils/test-factories.ts.html | 232 + coverage/utils/truncate-record.ts.html | 316 + coverage/visitors/index.html | 176 + coverage/visitors/index.ts.html | 313 + coverage/visitors/processors/index.html | 131 + .../processors/process-openapi.ts.html | 730 ++ .../processors/process-openrpc.ts.html | 421 + coverage/visitors/visit-api-reference.ts.html | 307 + coverage/visitors/visit-link.ts.html | 160 + coverage/visitors/visit-page.ts.html | 322 + coverage/visitors/visit-section.ts.html | 520 ++ package.json | 4 + pnpm-lock.yaml | 189 +- src/content-indexer/__tests__/index.test.ts | 34 +- vitest.config.ts | 21 + 52 files changed, 21711 insertions(+), 6 deletions(-) create mode 100644 coverage/base.css create mode 100644 coverage/block-navigation.js create mode 100644 coverage/collectors/algolia.ts.html create mode 100644 coverage/collectors/index.html create mode 100644 coverage/collectors/navigation-trees.ts.html create mode 100644 coverage/collectors/path-index.ts.html create mode 100644 coverage/collectors/processing-context.ts.html create mode 100644 coverage/constants/http.ts.html create mode 100644 coverage/constants/index.html create mode 100644 coverage/core/batch-fetcher.ts.html create mode 100644 coverage/core/build-all-outputs.ts.html create mode 100644 coverage/core/content-cache.ts.html create mode 100644 coverage/core/index.html create mode 100644 coverage/core/path-builder.ts.html create mode 100644 coverage/core/scanner.ts.html create mode 100644 coverage/coverage-final.json create mode 100644 coverage/favicon.png create mode 100644 coverage/index.html create mode 100644 coverage/indexers/index.html create mode 100644 coverage/indexers/main.ts.html create mode 100644 coverage/prettify.css create mode 100644 coverage/prettify.js create mode 100644 coverage/sort-arrow-sprite.png create mode 100644 coverage/sorter.js create mode 100644 coverage/types/docsYaml.ts.html create mode 100644 coverage/types/index.html create mode 100644 coverage/uploaders/algolia.ts.html create mode 100644 coverage/uploaders/index.html create mode 100644 coverage/uploaders/redis.ts.html create mode 100644 coverage/utils/filesystem.ts.html create mode 100644 coverage/utils/github.ts.html create mode 100644 coverage/utils/index.html create mode 100644 coverage/utils/nav-tree-merge.ts.html create mode 100644 coverage/utils/navigation-helpers.ts.html create mode 100644 coverage/utils/normalization.ts.html create mode 100644 coverage/utils/openapi.ts.html create mode 100644 coverage/utils/openrpc.ts.html create mode 100644 coverage/utils/test-factories.ts.html create mode 100644 coverage/utils/truncate-record.ts.html create mode 100644 coverage/visitors/index.html create mode 100644 coverage/visitors/index.ts.html create mode 100644 coverage/visitors/processors/index.html create mode 100644 coverage/visitors/processors/process-openapi.ts.html create mode 100644 coverage/visitors/processors/process-openrpc.ts.html create mode 100644 coverage/visitors/visit-api-reference.ts.html create mode 100644 coverage/visitors/visit-link.ts.html create mode 100644 coverage/visitors/visit-page.ts.html create mode 100644 coverage/visitors/visit-section.ts.html create mode 100644 vitest.config.ts diff --git a/coverage/base.css b/coverage/base.css new file mode 100644 index 000000000..f418035b4 --- /dev/null +++ b/coverage/base.css @@ -0,0 +1,224 @@ +body, html { + margin:0; padding: 0; + height: 100%; +} +body { + font-family: Helvetica Neue, Helvetica, Arial; + font-size: 14px; + color:#333; +} +.small { font-size: 12px; } +*, *:after, *:before { + -webkit-box-sizing:border-box; + -moz-box-sizing:border-box; + box-sizing:border-box; + } +h1 { font-size: 20px; margin: 0;} +h2 { font-size: 14px; } +pre { + font: 12px/1.4 Consolas, "Liberation Mono", Menlo, Courier, monospace; + margin: 0; + padding: 0; + -moz-tab-size: 2; + -o-tab-size: 2; + tab-size: 2; +} +a { color:#0074D9; text-decoration:none; } +a:hover { text-decoration:underline; } +.strong { font-weight: bold; } +.space-top1 { padding: 10px 0 0 0; } +.pad2y { padding: 20px 0; } +.pad1y { padding: 10px 0; } +.pad2x { padding: 0 20px; } +.pad2 { padding: 20px; } +.pad1 { padding: 10px; } +.space-left2 { padding-left:55px; } +.space-right2 { padding-right:20px; } +.center { text-align:center; } +.clearfix { display:block; } +.clearfix:after { + content:''; + display:block; + height:0; + clear:both; + visibility:hidden; + } +.fl { float: left; } +@media only screen and (max-width:640px) { + .col3 { width:100%; max-width:100%; } + .hide-mobile { display:none!important; } +} + +.quiet { + color: #7f7f7f; + color: rgba(0,0,0,0.5); +} +.quiet a { opacity: 0.7; } + +.fraction { + font-family: Consolas, 'Liberation Mono', Menlo, Courier, monospace; + font-size: 10px; + color: #555; + background: #E8E8E8; + padding: 4px 5px; + border-radius: 3px; + vertical-align: middle; +} + +div.path a:link, div.path a:visited { color: #333; } +table.coverage { + border-collapse: collapse; + margin: 10px 0 0 0; + padding: 0; +} + +table.coverage td { + margin: 0; + padding: 0; + vertical-align: top; +} +table.coverage td.line-count { + text-align: right; + padding: 0 5px 0 20px; +} +table.coverage td.line-coverage { + text-align: right; + padding-right: 10px; + min-width:20px; +} + +table.coverage td span.cline-any { + display: inline-block; + padding: 0 5px; + width: 100%; +} +.missing-if-branch { + display: inline-block; + margin-right: 5px; + border-radius: 3px; + position: relative; + padding: 0 4px; + background: #333; + color: yellow; +} + +.skip-if-branch { + display: none; + margin-right: 10px; + position: relative; + padding: 0 4px; + background: #ccc; + color: white; +} +.missing-if-branch .typ, .skip-if-branch .typ { + color: inherit !important; +} +.coverage-summary { + border-collapse: collapse; + width: 100%; +} +.coverage-summary tr { border-bottom: 1px solid #bbb; } +.keyline-all { border: 1px solid #ddd; } +.coverage-summary td, .coverage-summary th { padding: 10px; } +.coverage-summary tbody { border: 1px solid #bbb; } +.coverage-summary td { border-right: 1px solid #bbb; } +.coverage-summary td:last-child { border-right: none; } +.coverage-summary th { + text-align: left; + font-weight: normal; + white-space: nowrap; +} +.coverage-summary th.file { border-right: none !important; } +.coverage-summary th.pct { } +.coverage-summary th.pic, +.coverage-summary th.abs, +.coverage-summary td.pct, +.coverage-summary td.abs { text-align: right; } +.coverage-summary td.file { white-space: nowrap; } +.coverage-summary td.pic { min-width: 120px !important; } +.coverage-summary tfoot td { } + +.coverage-summary .sorter { + height: 10px; + width: 7px; + display: inline-block; + margin-left: 0.5em; + background: url(sort-arrow-sprite.png) no-repeat scroll 0 0 transparent; +} +.coverage-summary .sorted .sorter { + background-position: 0 -20px; +} +.coverage-summary .sorted-desc .sorter { + background-position: 0 -10px; +} +.status-line { height: 10px; } +/* yellow */ +.cbranch-no { background: yellow !important; color: #111; } +/* dark red */ +.red.solid, .status-line.low, .low .cover-fill { background:#C21F39 } +.low .chart { border:1px solid #C21F39 } +.highlighted, +.highlighted .cstat-no, .highlighted .fstat-no, .highlighted .cbranch-no{ + background: #C21F39 !important; +} +/* medium red */ +.cstat-no, .fstat-no, .cbranch-no, .cbranch-no { background:#F6C6CE } +/* light red */ +.low, .cline-no { background:#FCE1E5 } +/* light green */ +.high, .cline-yes { background:rgb(230,245,208) } +/* medium green */ +.cstat-yes { background:rgb(161,215,106) } +/* dark green */ +.status-line.high, .high .cover-fill { background:rgb(77,146,33) } +.high .chart { border:1px solid rgb(77,146,33) } +/* dark yellow (gold) */ +.status-line.medium, .medium .cover-fill { background: #f9cd0b; } +.medium .chart { border:1px solid #f9cd0b; } +/* light yellow */ +.medium { background: #fff4c2; } + +.cstat-skip { background: #ddd; color: #111; } +.fstat-skip { background: #ddd; color: #111 !important; } +.cbranch-skip { background: #ddd !important; color: #111; } + +span.cline-neutral { background: #eaeaea; } + +.coverage-summary td.empty { + opacity: .5; + padding-top: 4px; + padding-bottom: 4px; + line-height: 1; + color: #888; +} + +.cover-fill, .cover-empty { + display:inline-block; + height: 12px; +} +.chart { + line-height: 0; +} +.cover-empty { + background: white; +} +.cover-full { + border-right: none !important; +} +pre.prettyprint { + border: none !important; + padding: 0 !important; + margin: 0 !important; +} +.com { color: #999 !important; } +.ignore-none { color: #999; font-weight: normal; } + +.wrapper { + min-height: 100%; + height: auto !important; + height: 100%; + margin: 0 auto -48px; +} +.footer, .push { + height: 48px; +} diff --git a/coverage/block-navigation.js b/coverage/block-navigation.js new file mode 100644 index 000000000..873182862 --- /dev/null +++ b/coverage/block-navigation.js @@ -0,0 +1,84 @@ +var jumpToCode = (function init() { + // Classes of code we would like to highlight in the file view + var missingCoverageClasses = [".cbranch-no", ".cstat-no", ".fstat-no"]; + + // Elements to highlight in the file listing view + var fileListingElements = ["td.pct.low"]; + + // We don't want to select elements that are direct descendants of another match + var notSelector = ":not(" + missingCoverageClasses.join("):not(") + ") > "; // becomes `:not(a):not(b) > ` + + // Selector that finds elements on the page to which we can jump + var selector = + fileListingElements.join(", ") + + ", " + + notSelector + + missingCoverageClasses.join(", " + notSelector); // becomes `:not(a):not(b) > a, :not(a):not(b) > b` + + // The NodeList of matching elements + var missingCoverageElements = document.querySelectorAll(selector); + + var currentIndex; + + function toggleClass(index) { + missingCoverageElements.item(currentIndex).classList.remove("highlighted"); + missingCoverageElements.item(index).classList.add("highlighted"); + } + + function makeCurrent(index) { + toggleClass(index); + currentIndex = index; + missingCoverageElements.item(index).scrollIntoView({ + behavior: "smooth", + block: "center", + inline: "center", + }); + } + + function goToPrevious() { + var nextIndex = 0; + if (typeof currentIndex !== "number" || currentIndex === 0) { + nextIndex = missingCoverageElements.length - 1; + } else if (missingCoverageElements.length > 1) { + nextIndex = currentIndex - 1; + } + + makeCurrent(nextIndex); + } + + function goToNext() { + var nextIndex = 0; + + if ( + typeof currentIndex === "number" && + currentIndex < missingCoverageElements.length - 1 + ) { + nextIndex = currentIndex + 1; + } + + makeCurrent(nextIndex); + } + + return function jump(event) { + if ( + document.getElementById("fileSearch") === document.activeElement && + document.activeElement != null + ) { + // if we're currently focused on the search input, we don't want to navigate + return; + } + + switch (event.which) { + case 78: // n + case 74: // j + goToNext(); + break; + case 66: // b + case 75: // k + case 80: // p + goToPrevious(); + break; + } + }; +})(); +window.addEventListener("keydown", jumpToCode); diff --git a/coverage/collectors/algolia.ts.html b/coverage/collectors/algolia.ts.html new file mode 100644 index 000000000..7a813f484 --- /dev/null +++ b/coverage/collectors/algolia.ts.html @@ -0,0 +1,331 @@ + + + + + + Code coverage report for collectors/algolia.ts + + + + + + + + + +
+
+

All files / collectors algolia.ts

+
+ +
+ 100% + Statements + 12/12 +
+ + +
+ 100% + Branches + 4/4 +
+ + +
+ 100% + Functions + 7/7 +
+ + +
+ 100% + Lines + 12/12 +
+ + +
+

+ Press n or j to go to the next uncovered block, b, p or k for the previous block. +

+ +
+
+

+
1 +2 +3 +4 +5 +6 +7 +8 +9 +10 +11 +12 +13 +14 +15 +16 +17 +18 +19 +20 +21 +22 +23 +24 +25 +26 +27 +28 +29 +30 +31 +32 +33 +34 +35 +36 +37 +38 +39 +40 +41 +42 +43 +44 +45 +46 +47 +48 +49 +50 +51 +52 +53 +54 +55 +56 +57 +58 +59 +60 +61 +62 +63 +64 +65 +66 +67 +68 +69 +70 +71 +72 +73 +74 +75 +76 +77 +78 +79 +80 +81 +82 +83  +  +  +  +  +  +  +  +  +10x +40x +38x +37x +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +73x +  +  +  +  +  +  +  +  +  +  +40x +  +  +40x +40x +40x +  +40x +  +  +  +  +  +  +  +  +  +  +  +  +  +  +36x +  +  +  +  +  +  +  +40x +  +  + 
import { createHash } from "crypto";
+ 
+import type { AlgoliaRecord } from "@/content-indexer/types/algolia.js";
+import type { NavItem } from "@/content-indexer/types/navigation.js";
+ 
+/**
+ * Extracts breadcrumb titles from NavItems for Algolia.
+ * Returns only the titles in hierarchical order.
+ */
+const extractBreadcrumbTitles = (navItems: NavItem[]): string[] => {
+  return navItems
+    .filter((item) => item.type !== "link") // Skip links
+    .map((item) => item.title);
+};
+ 
+type AddRecordBaseParams = {
+  path: string;
+  title: string;
+  content: string;
+  breadcrumbs: NavItem[];
+};
+ 
+type AddGuideRecordParams = AddRecordBaseParams & {
+  pageType: "Guide";
+  httpMethod?: never; // Not allowed for Guide
+};
+ 
+type AddApiMethodRecordParams = AddRecordBaseParams & {
+  pageType: "API Method";
+  httpMethod: string; // Required for API Method
+};
+export type AddRecordParams = AddGuideRecordParams | AddApiMethodRecordParams;
+ 
+/**
+ * Collector for Algolia search records during content processing.
+ * Records are built with all required data including breadcrumbs.
+ */
+export class AlgoliaCollector {
+  private records: AlgoliaRecord[] = [];
+ 
+  /**
+   * Add a search record for either MDX pages or API methods.
+   *
+   * ObjectID strategy:
+   * Uses hash of last breadcrumb + title for relatively stable, content-based identification.
+   * If we change the title or the last breadcrumb, the objectID will change,
+   * but this shouldn't matter as long as we continue to replace the entire index on each run.
+   */
+  addRecord(params: AddRecordParams): void {
+    const breadcrumbTitles = extractBreadcrumbTitles(params.breadcrumbs);
+ 
+    // Generate stable objectID from last breadcrumb (most specific section) + title
+    const lastBreadcrumb = breadcrumbTitles.at(-1) || "unknown";
+    const stableId = `${lastBreadcrumb}:${params.title}`;
+    const objectID = this.generateHash(stableId);
+ 
+    this.records.push({
+      objectID,
+      path: params.path,
+      pageType: params.pageType,
+      title: params.title,
+      content: params.content,
+      breadcrumbs: breadcrumbTitles,
+      ...(params.httpMethod && { httpMethod: params.httpMethod }),
+    });
+  }
+ 
+  /**
+   * Get all built records.
+   */
+  getRecords(): AlgoliaRecord[] {
+    return this.records;
+  }
+ 
+  /**
+   * Generate a stable hash-based objectID from a source string.
+   * Returns first 16 characters of SHA-256 hash for a clean ID format.
+   */
+  private generateHash(source: string): string {
+    return createHash("sha256").update(source).digest("hex").substring(0, 16);
+  }
+}
+ 
+ +
+
+ + + + + + + + \ No newline at end of file diff --git a/coverage/collectors/index.html b/coverage/collectors/index.html new file mode 100644 index 000000000..c5b5f3b08 --- /dev/null +++ b/coverage/collectors/index.html @@ -0,0 +1,161 @@ + + + + + + Code coverage report for collectors + + + + + + + + + +
+
+

All files collectors

+
+ +
+ 100% + Statements + 40/40 +
+ + +
+ 100% + Branches + 17/17 +
+ + +
+ 100% + Functions + 25/25 +
+ + +
+ 100% + Lines + 40/40 +
+ + +
+

+ Press n or j to go to the next uncovered block, b, p or k for the previous block. +

+ +
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
FileStatementsBranchesFunctionsLines
algolia.ts +
+
100%12/12100%4/4100%7/7100%12/12
navigation-trees.ts +
+
100%11/11100%6/6100%7/7100%11/11
path-index.ts +
+
100%9/9100%4/4100%5/5100%9/9
processing-context.ts +
+
100%8/8100%3/3100%6/6100%8/8
+
+
+
+ + + + + + + + \ No newline at end of file diff --git a/coverage/collectors/navigation-trees.ts.html b/coverage/collectors/navigation-trees.ts.html new file mode 100644 index 000000000..c7f8f2804 --- /dev/null +++ b/coverage/collectors/navigation-trees.ts.html @@ -0,0 +1,259 @@ + + + + + + Code coverage report for collectors/navigation-trees.ts + + + + + + + + + +
+
+

All files / collectors navigation-trees.ts

+
+ +
+ 100% + Statements + 11/11 +
+ + +
+ 100% + Branches + 6/6 +
+ + +
+ 100% + Functions + 7/7 +
+ + +
+ 100% + Lines + 11/11 +
+ + +
+

+ Press n or j to go to the next uncovered block, b, p or k for the previous block. +

+ +
+
+

+
1 +2 +3 +4 +5 +6 +7 +8 +9 +10 +11 +12 +13 +14 +15 +16 +17 +18 +19 +20 +21 +22 +23 +24 +25 +26 +27 +28 +29 +30 +31 +32 +33 +34 +35 +36 +37 +38 +39 +40 +41 +42 +43 +44 +45 +46 +47 +48 +49 +50 +51 +52 +53 +54 +55 +56 +57 +58 +59  +  +  +  +  +  +  +  +  +  +71x +  +  +  +  +  +22x +19x +  +22x +  +  +  +  +  +  +31x +  +  +  +  +  +  +3x +  +  +  +4x +4x +  +  +  +  +  +  +  +  +  +  +5x +  +7x +  +  +7x +  +  +  + 
import type {
+  NavItem,
+  NavigationTreesByTab,
+} from "@/content-indexer/types/navigation.js";
+ 
+/**
+ * Collector for accumulating navigation trees during content processing.
+ * Organizes navigation items by tab.
+ */
+export class NavigationTreesCollector {
+  private trees: NavigationTreesByTab = {};
+ 
+  /**
+   * Add a navigation item to a specific tab's tree.
+   */
+  addItem(tab: string, item: NavItem): void {
+    if (!this.trees[tab]) {
+      this.trees[tab] = [];
+    }
+    this.trees[tab].push(item);
+  }
+ 
+  /**
+   * Get the complete navigation trees.
+   */
+  getTrees(): NavigationTreesByTab {
+    return this.trees;
+  }
+ 
+  /**
+   * Get statistics about the navigation trees.
+   */
+  getStats(): { tabCount: number; itemCounts: Record<string, number> } {
+    return {
+      tabCount: Object.keys(this.trees).length,
+      itemCounts: Object.entries(this.trees).reduce(
+        (acc, [tab, items]) => {
+          acc[tab] = this.countItems(items);
+          return acc;
+        },
+        {} as Record<string, number>,
+      ),
+    };
+  }
+ 
+  /**
+   * Recursively count navigation items including nested children.
+   */
+  private countItems(items: NavItem[]): number {
+    return items.reduce((sum, item) => {
+      const childCount =
+        item.type === "section" || item.type === "api-section"
+          ? this.countItems(item.children)
+          : 0;
+      return sum + 1 + childCount;
+    }, 0);
+  }
+}
+ 
+ +
+
+ + + + + + + + \ No newline at end of file diff --git a/coverage/collectors/path-index.ts.html b/coverage/collectors/path-index.ts.html new file mode 100644 index 000000000..54185d8dc --- /dev/null +++ b/coverage/collectors/path-index.ts.html @@ -0,0 +1,232 @@ + + + + + + Code coverage report for collectors/path-index.ts + + + + + + + + + +
+
+

All files / collectors path-index.ts

+
+ +
+ 100% + Statements + 9/9 +
+ + +
+ 100% + Branches + 4/4 +
+ + +
+ 100% + Functions + 5/5 +
+ + +
+ 100% + Lines + 9/9 +
+ + +
+

+ Press n or j to go to the next uncovered block, b, p or k for the previous block. +

+ +
+
+

+
1 +2 +3 +4 +5 +6 +7 +8 +9 +10 +11 +12 +13 +14 +15 +16 +17 +18 +19 +20 +21 +22 +23 +24 +25 +26 +27 +28 +29 +30 +31 +32 +33 +34 +35 +36 +37 +38 +39 +40 +41 +42 +43 +44 +45 +46 +47 +48 +49 +50  +  +  +  +  +  +  +  +  +  +71x +  +  +  +  +  +  +16x +1x +  +  +  +16x +  +  +  +  +  +  +32x +  +  +  +  +  +  +2x +2x +  +  +  +4x +4x +  +  +  +  +  +  + 
import type {
+  PathIndex,
+  PathIndexEntry,
+} from "@/content-indexer/types/pathIndex.js";
+ 
+/**
+ * Collector for accumulating path index entries during content processing.
+ * Provides validation to prevent duplicate paths.
+ */
+export class PathIndexCollector {
+  private index: PathIndex = {};
+ 
+  /**
+   * Add a path index entry for URL routing.
+   * Warns if the path already exists to catch configuration errors.
+   */
+  add(path: string, entry: PathIndexEntry): void {
+    if (this.index[path]) {
+      console.warn(
+        `⚠️  Duplicate path detected: ${path} (overwriting previous entry)`,
+      );
+    }
+    this.index[path] = entry;
+  }
+ 
+  /**
+   * Get the complete path index.
+   */
+  getIndex(): PathIndex {
+    return this.index;
+  }
+ 
+  /**
+   * Get statistics about the index.
+   */
+  getStats(): { total: number; byType: Record<string, number> } {
+    const entries = Object.values(this.index);
+    return {
+      total: entries.length,
+      byType: entries.reduce(
+        (acc, entry) => {
+          acc[entry.type] = (acc[entry.type] || 0) + 1;
+          return acc;
+        },
+        {} as Record<string, number>,
+      ),
+    };
+  }
+}
+ 
+ +
+
+ + + + + + + + \ No newline at end of file diff --git a/coverage/collectors/processing-context.ts.html b/coverage/collectors/processing-context.ts.html new file mode 100644 index 000000000..05f60643c --- /dev/null +++ b/coverage/collectors/processing-context.ts.html @@ -0,0 +1,316 @@ + + + + + + Code coverage report for collectors/processing-context.ts + + + + + + + + + +
+
+

All files / collectors processing-context.ts

+
+ +
+ 100% + Statements + 8/8 +
+ + +
+ 100% + Branches + 3/3 +
+ + +
+ 100% + Functions + 6/6 +
+ + +
+ 100% + Lines + 8/8 +
+ + +
+

+ Press n or j to go to the next uncovered block, b, p or k for the previous block. +

+ +
+
+

+
1 +2 +3 +4 +5 +6 +7 +8 +9 +10 +11 +12 +13 +14 +15 +16 +17 +18 +19 +20 +21 +22 +23 +24 +25 +26 +27 +28 +29 +30 +31 +32 +33 +34 +35 +36 +37 +38 +39 +40 +41 +42 +43 +44 +45 +46 +47 +48 +49 +50 +51 +52 +53 +54 +55 +56 +57 +58 +59 +60 +61 +62 +63 +64 +65 +66 +67 +68 +69 +70 +71 +72 +73 +74 +75 +76 +77 +78  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +64x +64x +64x +  +  +  +  +  +  +6x +  +  +  +  +  +  +12x +  +  +  +  +  +  +31x +  +  +  +  +  +  +26x +  +  +  +  +  +  +  +  +  +  +1x +  +  +  +  +  +  + 
import type { AlgoliaRecord } from "@/content-indexer/types/algolia.js";
+import type {
+  NavItem,
+  NavigationTreesByTab,
+} from "@/content-indexer/types/navigation.js";
+import type {
+  PathIndex,
+  PathIndexEntry,
+} from "@/content-indexer/types/pathIndex.js";
+ 
+import { AlgoliaCollector, type AddRecordParams } from "./algolia.js";
+import { NavigationTreesCollector } from "./navigation-trees.js";
+import { PathIndexCollector } from "./path-index.js";
+ 
+/**
+ * Result of building all outputs from content processing.
+ */
+export interface BuildAllOutputsResult {
+  pathIndex: PathIndex;
+  navigationTrees: NavigationTreesByTab;
+  algoliaRecords: AlgoliaRecord[];
+}
+ 
+/**
+ * Encapsulates the three output collectors for Phase 3 processing.
+ * Provides a unified interface for accumulating results while traversing docs.yml.
+ */
+export class ProcessingContext {
+  constructor(
+    private pathIndexCollector = new PathIndexCollector(),
+    private navigationTreesCollector = new NavigationTreesCollector(),
+    private algoliaCollector = new AlgoliaCollector(),
+  ) {}
+ 
+  /**
+   * Add an entry to the path index for URL routing.
+   */
+  addPathIndexEntry(path: string, entry: PathIndexEntry): void {
+    this.pathIndexCollector.add(path, entry);
+  }
+ 
+  /**
+   * Add a navigation item to a specific tab's tree.
+   */
+  addNavigationItem(tab: string, item: NavItem): void {
+    this.navigationTreesCollector.addItem(tab, item);
+  }
+ 
+  /**
+   * Add a record to the Algolia index.
+   */
+  addAlgoliaRecord(params: AddRecordParams): void {
+    this.algoliaCollector.addRecord(params);
+  }
+ 
+  /**
+   * Get all accumulated results.
+   */
+  getResults(): BuildAllOutputsResult {
+    return {
+      pathIndex: this.pathIndexCollector.getIndex(),
+      navigationTrees: this.navigationTreesCollector.getTrees(),
+      algoliaRecords: this.algoliaCollector.getRecords(),
+    };
+  }
+ 
+  /**
+   * Get statistics about accumulated data.
+   */
+  getStats() {
+    return {
+      pathIndex: this.pathIndexCollector.getStats(),
+      navigationTrees: this.navigationTreesCollector.getStats(),
+      algoliaRecords: { count: this.algoliaCollector.getRecords().length },
+    };
+  }
+}
+ 
+ +
+
+ + + + + + + + \ No newline at end of file diff --git a/coverage/constants/http.ts.html b/coverage/constants/http.ts.html new file mode 100644 index 000000000..b34c66206 --- /dev/null +++ b/coverage/constants/http.ts.html @@ -0,0 +1,121 @@ + + + + + + Code coverage report for constants/http.ts + + + + + + + + + +
+
+

All files / constants http.ts

+
+ +
+ 100% + Statements + 1/1 +
+ + +
+ 100% + Branches + 0/0 +
+ + +
+ 100% + Functions + 0/0 +
+ + +
+ 100% + Lines + 1/1 +
+ + +
+

+ Press n or j to go to the next uncovered block, b, p or k for the previous block. +

+ +
+
+

+
1 +2 +3 +4 +5 +6 +7 +8 +9 +10 +11 +12 +135x +  +  +  +  +  +  +  +  +  +  +  + 
export const HTTP_METHODS = [
+  "get",
+  "post",
+  "put",
+  "patch",
+  "delete",
+  "options",
+  "head",
+  "trace",
+] as const;
+ 
+export type HttpMethod = Uppercase<(typeof HTTP_METHODS)[number]>;
+ 
+ +
+
+ + + + + + + + \ No newline at end of file diff --git a/coverage/constants/index.html b/coverage/constants/index.html new file mode 100644 index 000000000..1c143b1a5 --- /dev/null +++ b/coverage/constants/index.html @@ -0,0 +1,116 @@ + + + + + + Code coverage report for constants + + + + + + + + + +
+
+

All files constants

+
+ +
+ 100% + Statements + 1/1 +
+ + +
+ 100% + Branches + 0/0 +
+ + +
+ 100% + Functions + 0/0 +
+ + +
+ 100% + Lines + 1/1 +
+ + +
+

+ Press n or j to go to the next uncovered block, b, p or k for the previous block. +

+ +
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
FileStatementsBranchesFunctionsLines
http.ts +
+
100%1/1100%0/0100%0/0100%1/1
+
+
+
+ + + + + + + + \ No newline at end of file diff --git a/coverage/core/batch-fetcher.ts.html b/coverage/core/batch-fetcher.ts.html new file mode 100644 index 000000000..d6502cba5 --- /dev/null +++ b/coverage/core/batch-fetcher.ts.html @@ -0,0 +1,397 @@ + + + + + + Code coverage report for core/batch-fetcher.ts + + + + + + + + + +
+
+

All files / core batch-fetcher.ts

+
+ +
+ 85.71% + Statements + 24/28 +
+ + +
+ 62.5% + Branches + 10/16 +
+ + +
+ 100% + Functions + 3/3 +
+ + +
+ 85.71% + Lines + 24/28 +
+ + +
+

+ Press n or j to go to the next uncovered block, b, p or k for the previous block. +

+ +
+
+

+
1 +2 +3 +4 +5 +6 +7 +8 +9 +10 +11 +12 +13 +14 +15 +16 +17 +18 +19 +20 +21 +22 +23 +24 +25 +26 +27 +28 +29 +30 +31 +32 +33 +34 +35 +36 +37 +38 +39 +40 +41 +42 +43 +44 +45 +46 +47 +48 +49 +50 +51 +52 +53 +54 +55 +56 +57 +58 +59 +60 +61 +62 +63 +64 +65 +66 +67 +68 +69 +70 +71 +72 +73 +74 +75 +76 +77 +78 +79 +80 +81 +82 +83 +84 +85 +86 +87 +88 +89 +90 +91 +92 +93 +94 +95 +96 +97 +98 +99 +100 +101 +102 +103 +104 +105  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +1x +  +  +  +6x +  +6x +6x +  +  +  +  +6x +6x +6x +  +  +  +  +  +  +  +  +  +  +  +  +6x +  +  +  +6x +  +6x +5x +4x +4x +  +  +  +  +  +  +1x +  +  +  +  +  +  +  +6x +5x +5x +4x +3x +  +  +1x +  +  +  +  +6x +  +6x +6x +  +  +  +6x +  + 
import matter from "gray-matter";
+import path from "path";
+ 
+import { fetchApiSpec } from "@/content-indexer/utils/apiSpecs.js";
+import { readLocalMdxFile } from "@/content-indexer/utils/filesystem.js";
+import {
+  fetchFileFromGitHub,
+  type RepoConfig,
+} from "@/content-indexer/utils/github.js";
+ 
+import { ContentCache } from "./content-cache.js";
+import type { ScanResult } from "./scanner.js";
+ 
+/**
+ * Content source configuration - either filesystem or GitHub API
+ */
+export type ContentSource =
+  | { type: "filesystem"; basePath: string }
+  | { type: "github"; repoConfig: RepoConfig };
+ 
+/**
+ * Fetches all MDX files and API specs in parallel and populates the cache.
+ * This is the core optimization: all I/O happens upfront in parallel.
+ *
+ * Supports two modes:
+ * - filesystem: Reads from local filesystem (for preview mode)
+ * - github: Fetches from GitHub API (for production/SDK indexer)
+ *
+ * @param scanResult - Result from scanDocsYml containing all paths and spec names
+ * @param source - Content source (filesystem or GitHub)
+ * @returns Populated ContentCache ready for processing
+ */
+export const batchFetchContent = async (
+  scanResult: ScanResult,
+  source: ContentSource,
+): Promise<ContentCache> => {
+  const cache = new ContentCache();
+ 
+  const sourceType = source.type;
+  console.info(
+    `   ${sourceType === "filesystem" ? "Reading" : "Fetching"} ${scanResult.mdxPaths.size} MDX files and ${scanResult.specNames.size} specs...`,
+  );
+ 
+  // Fetch/read all MDX files in parallel
+  const mdxPromises = Array.from(scanResult.mdxPaths).map(async (mdxPath) => {
+    try {
+      Iif (source.type === "filesystem") {
+        // Read from local filesystem
+        const fullPath = path.join(source.basePath, mdxPath);
+        const result = await readLocalMdxFile(fullPath);
+ 
+        if (result) {
+          cache.setMdxContent(mdxPath, {
+            frontmatter: result.frontmatter,
+            content: result.content,
+          });
+        }
+      } else {
+        // Fetch from GitHub API
+        const actualPath = mdxPath.replace(
+          source.repoConfig.stripPathPrefix || "",
+          "",
+        );
+        const fullPath = `${source.repoConfig.docsPrefix}/${actualPath}`;
+ 
+        const content = await fetchFileFromGitHub(fullPath, source.repoConfig);
+        if (content) {
+          const { data, content: body } = matter(content);
+          cache.setMdxContent(mdxPath, {
+            frontmatter: data,
+            content: body,
+          });
+        }
+      }
+    } catch (error) {
+      console.warn(
+        `   ⚠️  Failed to ${source.type === "filesystem" ? "read" : "fetch"} MDX file: ${mdxPath}`,
+        error,
+      );
+    }
+  });
+ 
+  // Fetch all API specs in parallel (always from remote)
+  const specPromises = Array.from(scanResult.specNames).map(async (apiName) => {
+    try {
+      const result = await fetchApiSpec(apiName);
+      if (result) {
+        cache.setSpec(apiName, result);
+      }
+    } catch (error) {
+      console.warn(`   ⚠️  Failed to fetch spec: ${apiName}`, error);
+    }
+  });
+ 
+  // Wait for all fetches to complete
+  await Promise.all([...mdxPromises, ...specPromises]);
+ 
+  const stats = cache.getStats();
+  console.info(
+    `   ✓ ${sourceType === "filesystem" ? "Read" : "Fetched"} ${stats.mdxCount}/${scanResult.mdxPaths.size} MDX files and ${stats.specCount}/${scanResult.specNames.size} specs`,
+  );
+ 
+  return cache;
+};
+ 
+ +
+
+ + + + + + + + \ No newline at end of file diff --git a/coverage/core/build-all-outputs.ts.html b/coverage/core/build-all-outputs.ts.html new file mode 100644 index 000000000..03878f6ff --- /dev/null +++ b/coverage/core/build-all-outputs.ts.html @@ -0,0 +1,340 @@ + + + + + + Code coverage report for core/build-all-outputs.ts + + + + + + + + + +
+
+

All files / core build-all-outputs.ts

+
+ +
+ 100% + Statements + 22/22 +
+ + +
+ 86.66% + Branches + 13/15 +
+ + +
+ 100% + Functions + 6/6 +
+ + +
+ 100% + Lines + 20/20 +
+ + +
+

+ Press n or j to go to the next uncovered block, b, p or k for the previous block. +

+ +
+
+

+
1 +2 +3 +4 +5 +6 +7 +8 +9 +10 +11 +12 +13 +14 +15 +16 +17 +18 +19 +20 +21 +22 +23 +24 +25 +26 +27 +28 +29 +30 +31 +32 +33 +34 +35 +36 +37 +38 +39 +40 +41 +42 +43 +44 +45 +46 +47 +48 +49 +50 +51 +52 +53 +54 +55 +56 +57 +58 +59 +60 +61 +62 +63 +64 +65 +66 +67 +68 +69 +70 +71 +72 +73 +74 +75 +76 +77 +78 +79 +80 +81 +82 +83 +84 +85 +86  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +1x +  +  +  +  +7x +  +  +7x +  +10x +2x +  +  +  +8x +  +  +8x +  +  +8x +10x +2x +2x +  +2x +  +  +  +  +  +  +8x +  +  +  +  +  +  +  +  +  +  +  +  +8x +  +8x +3x +  +  +  +8x +6x +  +  +7x +  +  +  +  +7x +  + 
import { kebabCase } from "lodash-es";
+ 
+import {
+  ProcessingContext,
+  type BuildAllOutputsResult,
+} from "@/content-indexer/collectors/processing-context.js";
+import type { ContentCache } from "@/content-indexer/core/content-cache.js";
+import type { DocsYml } from "@/content-indexer/types/docsYaml.js";
+import { DOCS_REPO, type RepoConfig } from "@/content-indexer/utils/github.js";
+import { visitNavigationItem } from "@/content-indexer/visitors/index.js";
+ 
+import { PathBuilder } from "./path-builder.js";
+ 
+/**
+ * Phase 3 of the content indexing pipeline.
+ * Builds path index, navigation trees, and Algolia records in a single traversal.
+ *
+ * Uses visitor pattern to process each navigation item type and accumulates
+ * results in ProcessingContext. Maintains navigation hierarchy and breadcrumb
+ * context during traversal.
+ */
+export const buildAllOutputs = (
+  docsYml: DocsYml,
+  contentCache: ContentCache,
+  repo: RepoConfig = DOCS_REPO,
+): BuildAllOutputsResult => {
+  const context = new ProcessingContext();
+ 
+  // Process each tab in docs.yml
+  docsYml.navigation.forEach((navItem) => {
+    // Skip navigation items without a tab or layout
+    if (!navItem.tab || !navItem.layout) {
+      return;
+    }
+ 
+    // Tab identifier for the index entries
+    const tab = kebabCase(navItem.tab);
+ 
+    // Build base path for this tab
+    let basePathBuilder = PathBuilder.init();
+ 
+    // Apply tab slug to path (use slug from tab config if available)
+    const tabConfig = docsYml.tabs?.[navItem.tab];
+    if (tabConfig) {
+      const tabSlugForPath = tabConfig.slug ?? tab;
+      const skipTabSlug = tabConfig["skip-slug"] ?? false;
+ 
+      basePathBuilder = basePathBuilder.apply({
+        urlSlug: tabSlugForPath,
+        skipUrlSlug: skipTabSlug,
+      });
+    }
+ 
+    // Visit all layout items using visitor pattern
+    const results = navItem.layout.map((layoutItem) =>
+      visitNavigationItem({
+        item: layoutItem,
+        parentPath: basePathBuilder,
+        tab,
+        repo,
+        contentCache,
+        context,
+        navigationAncestors: [], // Empty ancestors at top level
+      }),
+    );
+ 
+    // Add results to context
+    results.forEach((result) => {
+      // Add path index entries
+      Object.entries(result.indexEntries).forEach(([path, entry]) => {
+        context.addPathIndexEntry(path, entry);
+      });
+ 
+      // Add navigation items
+      if (result.navItem) {
+        const items = Array.isArray(result.navItem)
+          ? result.navItem
+          : [result.navItem];
+        items.forEach((item) => context.addNavigationItem(tab, item));
+      }
+    });
+  });
+ 
+  return context.getResults();
+};
+ 
+ +
+
+ + + + + + + + \ No newline at end of file diff --git a/coverage/core/content-cache.ts.html b/coverage/core/content-cache.ts.html new file mode 100644 index 000000000..0c9a120b5 --- /dev/null +++ b/coverage/core/content-cache.ts.html @@ -0,0 +1,343 @@ + + + + + + Code coverage report for core/content-cache.ts + + + + + + + + + +
+
+

All files / core content-cache.ts

+
+ +
+ 100% + Statements + 7/7 +
+ + +
+ 100% + Branches + 0/0 +
+ + +
+ 100% + Functions + 6/6 +
+ + +
+ 100% + Lines + 7/7 +
+ + +
+

+ Press n or j to go to the next uncovered block, b, p or k for the previous block. +

+ +
+
+

+
1 +2 +3 +4 +5 +6 +7 +8 +9 +10 +11 +12 +13 +14 +15 +16 +17 +18 +19 +20 +21 +22 +23 +24 +25 +26 +27 +28 +29 +30 +31 +32 +33 +34 +35 +36 +37 +38 +39 +40 +41 +42 +43 +44 +45 +46 +47 +48 +49 +50 +51 +52 +53 +54 +55 +56 +57 +58 +59 +60 +61 +62 +63 +64 +65 +66 +67 +68 +69 +70 +71 +72 +73 +74 +75 +76 +77 +78 +79 +80 +81 +82 +83 +84 +85 +86 +87  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +75x +75x +  +  +  +  +  +  +18x +  +  +  +  +  +  +29x +  +  +  +  +  +  +17x +  +  +  +  +  +  +14x +  +  +  +  +  +  +12x +  +  +  +  +  + 
import type {
+  OpenApiSpec,
+  OpenRpcSpec,
+} from "@/content-indexer/types/specs.js";
+ 
+/**
+ * Cached MDX content with parsed frontmatter and body.
+ */
+export interface MdxCacheEntry {
+  frontmatter: {
+    slug?: string;
+    title?: string;
+    sidebarTitle?: string;
+    [key: string]: unknown;
+  };
+  content: string; // Raw MDX body (without frontmatter)
+}
+ 
+/**
+ * Cached API spec with type information.
+ */
+export interface SpecCacheEntry {
+  specType: "openrpc" | "openapi";
+  spec: OpenRpcSpec | OpenApiSpec;
+  specUrl: string;
+}
+ 
+/**
+ * Cache statistics returned by getStats().
+ */
+export interface CacheStats {
+  mdxCount: number;
+  specCount: number;
+}
+ 
+/**
+ * In-memory cache for all fetched content.
+ * Provides O(1) lookup for MDX files and API specs.
+ */
+export class ContentCache {
+  private mdxCache: Map<string, MdxCacheEntry>;
+  private specCache: Map<string, SpecCacheEntry>;
+ 
+  constructor() {
+    this.mdxCache = new Map();
+    this.specCache = new Map();
+  }
+ 
+  /**
+   * Store MDX content by normalized file path.
+   */
+  setMdxContent(filePath: string, entry: MdxCacheEntry): void {
+    this.mdxCache.set(filePath, entry);
+  }
+ 
+  /**
+   * Retrieve MDX content by file path.
+   */
+  getMdxContent(filePath: string): MdxCacheEntry | undefined {
+    return this.mdxCache.get(filePath);
+  }
+ 
+  /**
+   * Store API spec by api-name.
+   */
+  setSpec(apiName: string, entry: SpecCacheEntry): void {
+    this.specCache.set(apiName, entry);
+  }
+ 
+  /**
+   * Retrieve API spec by api-name.
+   */
+  getSpec(apiName: string): SpecCacheEntry | undefined {
+    return this.specCache.get(apiName);
+  }
+ 
+  /**
+   * Get cache statistics for debugging.
+   */
+  getStats(): CacheStats {
+    return {
+      mdxCount: this.mdxCache.size,
+      specCount: this.specCache.size,
+    };
+  }
+}
+ 
+ +
+
+ + + + + + + + \ No newline at end of file diff --git a/coverage/core/index.html b/coverage/core/index.html new file mode 100644 index 000000000..8751e0fb6 --- /dev/null +++ b/coverage/core/index.html @@ -0,0 +1,176 @@ + + + + + + Code coverage report for core + + + + + + + + + +
+
+

All files core

+
+ +
+ 94.62% + Statements + 88/93 +
+ + +
+ 80.35% + Branches + 45/56 +
+ + +
+ 100% + Functions + 24/24 +
+ + +
+ 94.5% + Lines + 86/91 +
+ + +
+

+ Press n or j to go to the next uncovered block, b, p or k for the previous block. +

+ +
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
FileStatementsBranchesFunctionsLines
batch-fetcher.ts +
+
85.71%24/2862.5%10/16100%3/385.71%24/28
build-all-outputs.ts +
+
100%22/2286.66%13/15100%6/6100%20/20
content-cache.ts +
+
100%7/7100%0/0100%6/6100%7/7
path-builder.ts +
+
90.9%10/1188.88%8/9100%4/490.9%10/11
scanner.ts +
+
100%25/2587.5%14/16100%5/5100%25/25
+
+
+
+ + + + + + + + \ No newline at end of file diff --git a/coverage/core/path-builder.ts.html b/coverage/core/path-builder.ts.html new file mode 100644 index 000000000..0a71caa78 --- /dev/null +++ b/coverage/core/path-builder.ts.html @@ -0,0 +1,259 @@ + + + + + + Code coverage report for core/path-builder.ts + + + + + + + + + +
+
+

All files / core path-builder.ts

+
+ +
+ 90.9% + Statements + 10/11 +
+ + +
+ 88.88% + Branches + 8/9 +
+ + +
+ 100% + Functions + 4/4 +
+ + +
+ 90.9% + Lines + 10/11 +
+ + +
+

+ Press n or j to go to the next uncovered block, b, p or k for the previous block. +

+ +
+
+

+
1 +2 +3 +4 +5 +6 +7 +8 +9 +10 +11 +12 +13 +14 +15 +16 +17 +18 +19 +20 +21 +22 +23 +24 +25 +26 +27 +28 +29 +30 +31 +32 +33 +34 +35 +36 +37 +38 +39 +40 +41 +42 +43 +44 +45 +46 +47 +48 +49 +50 +51 +52 +53 +54 +55 +56 +57 +58 +59  +  +  +  +  +  +  +  +  +  +205x +  +  +  +  +  +  +90x +90x +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +115x +6x +  +  +  +109x +3x +  +  +  +106x +106x +  +  +  +  +  +  +  +  +  +79x +  +  + 
/**
+ * PathBuilder mimics Fern's slug generation logic to build full URL paths.
+ * Maintains an array of path segments and provides methods to build paths hierarchically.
+ * @note Fern incorrectly refers to full paths as "slugs" in their terminology
+ * @see https://buildwithfern.com/learn/docs/seo/configuring-slugs
+ */
+export class PathBuilder {
+  private segments: string[];
+ 
+  private constructor(segments: string[]) {
+    this.segments = segments;
+  }
+ 
+  /**
+   * Creates a new PathBuilder instance with optional base path segments.
+   */
+  static init(basePath: string = ""): PathBuilder {
+    const segments = basePath ? basePath.split("/").filter(Boolean) : [];
+    return new PathBuilder(segments);
+  }
+ 
+  /**
+   * Applies slug generation rules to create a new PathBuilder.
+   * Supports three modes:
+   * - fullSlug: Completely replaces the path (used for frontmatter overrides)
+   * - skipUrlSlug: Returns unchanged path (used for skip-slug sections)
+   * - urlSlug: Appends to existing path (default behavior)
+   */
+  apply(options: {
+    fullSlug?: string[];
+    urlSlug?: string;
+    skipUrlSlug?: boolean;
+  }): PathBuilder {
+    // If fullSlug is provided (from frontmatter), it completely overrides the path
+    if (options.fullSlug) {
+      return new PathBuilder(options.fullSlug.filter(Boolean));
+    }
+ 
+    // If skipUrlSlug is true, don't add anything to the path
+    if (options.skipUrlSlug) {
+      return new PathBuilder([...this.segments]);
+    }
+ 
+    // Otherwise, add the urlSlug to the path
+    Eif (options.urlSlug) {
+      return new PathBuilder([...this.segments, options.urlSlug]);
+    }
+ 
+    return new PathBuilder([...this.segments]);
+  }
+ 
+  /**
+   * Returns the full path as a string by joining all segments with "/".
+   */
+  get(): string {
+    return this.segments.filter(Boolean).join("/");
+  }
+}
+ 
+ +
+
+ + + + + + + + \ No newline at end of file diff --git a/coverage/core/scanner.ts.html b/coverage/core/scanner.ts.html new file mode 100644 index 000000000..375f50b91 --- /dev/null +++ b/coverage/core/scanner.ts.html @@ -0,0 +1,340 @@ + + + + + + Code coverage report for core/scanner.ts + + + + + + + + + +
+
+

All files / core scanner.ts

+
+ +
+ 100% + Statements + 25/25 +
+ + +
+ 87.5% + Branches + 14/16 +
+ + +
+ 100% + Functions + 5/5 +
+ + +
+ 100% + Lines + 25/25 +
+ + +
+

+ Press n or j to go to the next uncovered block, b, p or k for the previous block. +

+ +
+
+

+
1 +2 +3 +4 +5 +6 +7 +8 +9 +10 +11 +12 +13 +14 +15 +16 +17 +18 +19 +20 +21 +22 +23 +24 +25 +26 +27 +28 +29 +30 +31 +32 +33 +34 +35 +36 +37 +38 +39 +40 +41 +42 +43 +44 +45 +46 +47 +48 +49 +50 +51 +52 +53 +54 +55 +56 +57 +58 +59 +60 +61 +62 +63 +64 +65 +66 +67 +68 +69 +70 +71 +72 +73 +74 +75 +76 +77 +78 +79 +80 +81 +82 +83 +84 +85 +86  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +1x +  +23x +1x +  +  +  +22x +2x +  +  +  +20x +3x +3x +  +  +  +17x +12x +12x +  +  +  +5x +5x +1x +  +  +  +5x +6x +  +  +  +  +  +  +  +  +  +  +  +1x +10x +1x +  +  +9x +  +  +  +  +  +9x +10x +10x +17x +  +  +  +  +9x +  + 
import {
+  isApiConfig,
+  isChangelogConfig,
+  isLinkConfig,
+  isPageConfig,
+  isSectionConfig,
+  type DocsYml,
+  type NavigationItem,
+} from "@/content-indexer/types/docsYaml.js";
+ 
+/**
+ * Result of scanning docs.yml for all file paths and API specs.
+ */
+export interface ScanResult {
+  mdxPaths: Set<string>; // All unique MDX file paths
+  specNames: Set<string>; // All unique api-name values
+}
+ 
+/**
+ * Recursively scans a navigation item to collect MDX paths and spec names.
+ */
+const scanNavigationItem = (item: NavigationItem, result: ScanResult): void => {
+  // Skip changelog items
+  if (isChangelogConfig(item)) {
+    return;
+  }
+ 
+  // Skip external links
+  if (isLinkConfig(item)) {
+    return;
+  }
+ 
+  // Collect API spec names
+  if (isApiConfig(item)) {
+    result.specNames.add(item["api-name"]);
+    return;
+  }
+ 
+  // Collect page paths
+  if (isPageConfig(item)) {
+    result.mdxPaths.add(item.path);
+    return;
+  }
+ 
+  // Collect section overview paths and recurse into contents
+  Eif (isSectionConfig(item)) {
+    if (item.path) {
+      result.mdxPaths.add(item.path);
+    }
+ 
+    // Recursively scan all child items
+    item.contents.forEach((childItem) => {
+      scanNavigationItem(childItem, result);
+    });
+  }
+};
+ 
+/**
+ * Scans the entire docs.yml to collect all MDX file paths and API spec names.
+ * This enables batch fetching all content in parallel.
+ *
+ * @param docsYml - The parsed docs.yml configuration
+ * @returns Object with Sets of unique MDX paths and spec names
+ */
+export const scanDocsYml = (docsYml: DocsYml): ScanResult => {
+  if (!docsYml.navigation) {
+    throw new Error("Can't find navigation section in docs.yml");
+  }
+ 
+  const result: ScanResult = {
+    mdxPaths: new Set(),
+    specNames: new Set(),
+  };
+ 
+  // Scan all navigation items across all tabs
+  docsYml.navigation.forEach((navItem) => {
+    Eif (navItem.layout) {
+      navItem.layout.forEach((layoutItem) => {
+        scanNavigationItem(layoutItem, result);
+      });
+    }
+  });
+ 
+  return result;
+};
+ 
+ +
+
+ + + + + + + + \ No newline at end of file diff --git a/coverage/coverage-final.json b/coverage/coverage-final.json new file mode 100644 index 000000000..83f2dbc41 --- /dev/null +++ b/coverage/coverage-final.json @@ -0,0 +1,8298 @@ +{ + "/Users/danielslovinsky/Documents/projects/alchemy-docs/docs/src/content-indexer/collectors/algolia.ts": { + "path": "/Users/danielslovinsky/Documents/projects/alchemy-docs/docs/src/content-indexer/collectors/algolia.ts", + "statementMap": { + "0": { + "start": { "line": 10, "column": 32 }, + "end": { "line": 14, "column": null } + }, + "1": { + "start": { "line": 11, "column": 2 }, + "end": { "line": 13, "column": null } + }, + "2": { + "start": { "line": 12, "column": 22 }, + "end": { "line": 12, "column": 42 } + }, + "3": { + "start": { "line": 13, "column": 19 }, + "end": { "line": 13, "column": 29 } + }, + "4": { + "start": { "line": 39, "column": 2 }, + "end": { "line": 39, "column": null } + }, + "5": { + "start": { "line": 50, "column": 29 }, + "end": { "line": 50, "column": null } + }, + "6": { + "start": { "line": 53, "column": 27 }, + "end": { "line": 53, "column": null } + }, + "7": { + "start": { "line": 54, "column": 21 }, + "end": { "line": 54, "column": null } + }, + "8": { + "start": { "line": 55, "column": 21 }, + "end": { "line": 55, "column": null } + }, + "9": { + "start": { "line": 57, "column": 4 }, + "end": { "line": 65, "column": null } + }, + "10": { + "start": { "line": 72, "column": 4 }, + "end": { "line": 72, "column": null } + }, + "11": { + "start": { "line": 80, "column": 4 }, + "end": { "line": 80, "column": null } + } + }, + "fnMap": { + "0": { + "name": "(anonymous_0)", + "decl": { + "start": { "line": 10, "column": 32 }, + "end": { "line": 10, "column": 33 } + }, + "loc": { + "start": { "line": 10, "column": 67 }, + "end": { "line": 14, "column": null } + }, + "line": 10 + }, + "1": { + "name": "(anonymous_1)", + "decl": { + "start": { "line": 12, "column": 12 }, + "end": { "line": 12, "column": 13 } + }, + "loc": { + "start": { "line": 12, "column": 22 }, + "end": { "line": 12, "column": 42 } + }, + "line": 12 + }, + "2": { + "name": "(anonymous_2)", + "decl": { + "start": { "line": 13, "column": 9 }, + "end": { "line": 13, "column": 10 } + }, + "loc": { + "start": { "line": 13, "column": 19 }, + "end": { "line": 13, "column": 29 } + }, + "line": 13 + }, + "3": { + "name": "(anonymous_3)", + "decl": { + "start": { "line": 38, "column": 7 }, + "end": { "line": 38, "column": 13 } + }, + "loc": { + "start": { "line": 38, "column": 7 }, + "end": { "line": 39, "column": null } + }, + "line": 38 + }, + "4": { + "name": "(anonymous_4)", + "decl": { + "start": { "line": 49, "column": 2 }, + "end": { "line": 49, "column": 12 } + }, + "loc": { + "start": { "line": 49, "column": 43 }, + "end": { "line": 66, "column": null } + }, + "line": 49 + }, + "5": { + "name": "(anonymous_5)", + "decl": { + "start": { "line": 71, "column": 2 }, + "end": { "line": 71, "column": 32 } + }, + "loc": { + "start": { "line": 71, "column": 32 }, + "end": { "line": 73, "column": null } + }, + "line": 71 + }, + "6": { + "name": "(anonymous_6)", + "decl": { + "start": { "line": 79, "column": 10 }, + "end": { "line": 79, "column": 23 } + }, + "loc": { + "start": { "line": 79, "column": 47 }, + "end": { "line": 81, "column": null } + }, + "line": 79 + } + }, + "branchMap": { + "0": { + "loc": { + "start": { "line": 53, "column": 27 }, + "end": { "line": 53, "column": null } + }, + "type": "binary-expr", + "locations": [ + { + "start": { "line": 53, "column": 27 }, + "end": { "line": 53, "column": 54 } + }, + { + "start": { "line": 53, "column": 54 }, + "end": { "line": 53, "column": null } + } + ], + "line": 53 + }, + "1": { + "loc": { + "start": { "line": 64, "column": 10 }, + "end": { "line": 64, "column": null } + }, + "type": "binary-expr", + "locations": [ + { + "start": { "line": 64, "column": 10 }, + "end": { "line": 64, "column": 31 } + }, + { + "start": { "line": 64, "column": 31 }, + "end": { "line": 64, "column": null } + } + ], + "line": 64 + } + }, + "s": { + "0": 10, + "1": 40, + "2": 38, + "3": 37, + "4": 73, + "5": 40, + "6": 40, + "7": 40, + "8": 40, + "9": 40, + "10": 36, + "11": 40 + }, + "f": { "0": 40, "1": 38, "2": 37, "3": 73, "4": 40, "5": 36, "6": 40 }, + "b": { "0": [40, 11], "1": [40, 27] }, + "meta": { + "lastBranch": 2, + "lastFunction": 7, + "lastStatement": 12, + "seen": { + "s:10:32:14:Infinity": 0, + "f:10:32:10:33": 0, + "s:11:2:13:Infinity": 1, + "f:12:12:12:13": 1, + "s:12:22:12:42": 2, + "f:13:9:13:10": 2, + "s:13:19:13:29": 3, + "f:38:7:38:13": 3, + "s:39:2:39:Infinity": 4, + "f:49:2:49:12": 4, + "s:50:29:50:Infinity": 5, + "s:53:27:53:Infinity": 6, + "b:53:27:53:54:53:54:53:Infinity": 0, + "s:54:21:54:Infinity": 7, + "s:55:21:55:Infinity": 8, + "s:57:4:65:Infinity": 9, + "b:64:10:64:31:64:31:64:Infinity": 1, + "f:71:2:71:32": 5, + "s:72:4:72:Infinity": 10, + "f:79:10:79:23": 6, + "s:80:4:80:Infinity": 11 + } + } + }, + "/Users/danielslovinsky/Documents/projects/alchemy-docs/docs/src/content-indexer/collectors/navigation-trees.ts": { + "path": "/Users/danielslovinsky/Documents/projects/alchemy-docs/docs/src/content-indexer/collectors/navigation-trees.ts", + "statementMap": { + "0": { + "start": { "line": 11, "column": 2 }, + "end": { "line": 11, "column": null } + }, + "1": { + "start": { "line": 17, "column": 4 }, + "end": { "line": 19, "column": null } + }, + "2": { + "start": { "line": 18, "column": 6 }, + "end": { "line": 18, "column": null } + }, + "3": { + "start": { "line": 20, "column": 4 }, + "end": { "line": 20, "column": null } + }, + "4": { + "start": { "line": 27, "column": 4 }, + "end": { "line": 27, "column": null } + }, + "5": { + "start": { "line": 34, "column": 4 }, + "end": { "line": 43, "column": null } + }, + "6": { + "start": { "line": 38, "column": 10 }, + "end": { "line": 38, "column": null } + }, + "7": { + "start": { "line": 39, "column": 10 }, + "end": { "line": 39, "column": null } + }, + "8": { + "start": { "line": 50, "column": 4 }, + "end": { "line": 56, "column": null } + }, + "9": { + "start": { "line": 52, "column": 8 }, + "end": { "line": 54, "column": null } + }, + "10": { + "start": { "line": 55, "column": 6 }, + "end": { "line": 55, "column": null } + } + }, + "fnMap": { + "0": { + "name": "(anonymous_0)", + "decl": { + "start": { "line": 10, "column": 7 }, + "end": { "line": 10, "column": 13 } + }, + "loc": { + "start": { "line": 10, "column": 7 }, + "end": { "line": 11, "column": null } + }, + "line": 10 + }, + "1": { + "name": "(anonymous_1)", + "decl": { + "start": { "line": 16, "column": 2 }, + "end": { "line": 16, "column": 10 } + }, + "loc": { + "start": { "line": 16, "column": 44 }, + "end": { "line": 21, "column": null } + }, + "line": 16 + }, + "2": { + "name": "(anonymous_2)", + "decl": { + "start": { "line": 26, "column": 2 }, + "end": { "line": 26, "column": 35 } + }, + "loc": { + "start": { "line": 26, "column": 35 }, + "end": { "line": 28, "column": null } + }, + "line": 26 + }, + "3": { + "name": "(anonymous_3)", + "decl": { + "start": { "line": 33, "column": 2 }, + "end": { "line": 33, "column": 71 } + }, + "loc": { + "start": { "line": 33, "column": 71 }, + "end": { "line": 44, "column": null } + }, + "line": 33 + }, + "4": { + "name": "(anonymous_4)", + "decl": { + "start": { "line": 37, "column": 8 }, + "end": { "line": 37, "column": 9 } + }, + "loc": { + "start": { "line": 37, "column": 31 }, + "end": { "line": 40, "column": null } + }, + "line": 37 + }, + "5": { + "name": "(anonymous_5)", + "decl": { + "start": { "line": 49, "column": 10 }, + "end": { "line": 49, "column": 21 } + }, + "loc": { + "start": { "line": 49, "column": 47 }, + "end": { "line": 57, "column": null } + }, + "line": 49 + }, + "6": { + "name": "(anonymous_6)", + "decl": { + "start": { "line": 50, "column": 24 }, + "end": { "line": 50, "column": 25 } + }, + "loc": { + "start": { "line": 50, "column": 39 }, + "end": { "line": 56, "column": 7 } + }, + "line": 50 + } + }, + "branchMap": { + "0": { + "loc": { + "start": { "line": 17, "column": 4 }, + "end": { "line": 19, "column": null } + }, + "type": "if", + "locations": [ + { + "start": { "line": 17, "column": 4 }, + "end": { "line": 19, "column": null } + }, + { "start": {}, "end": {} } + ], + "line": 17 + }, + "1": { + "loc": { + "start": { "line": 52, "column": 8 }, + "end": { "line": 54, "column": null } + }, + "type": "cond-expr", + "locations": [ + { + "start": { "line": 53, "column": 12 }, + "end": { "line": 53, "column": null } + }, + { + "start": { "line": 54, "column": 12 }, + "end": { "line": 54, "column": null } + } + ], + "line": 52 + }, + "2": { + "loc": { + "start": { "line": 52, "column": 8 }, + "end": { "line": 52, "column": null } + }, + "type": "binary-expr", + "locations": [ + { + "start": { "line": 52, "column": 8 }, + "end": { "line": 52, "column": 35 } + }, + { + "start": { "line": 52, "column": 35 }, + "end": { "line": 52, "column": null } + } + ], + "line": 52 + } + }, + "s": { + "0": 71, + "1": 22, + "2": 19, + "3": 22, + "4": 31, + "5": 3, + "6": 4, + "7": 4, + "8": 5, + "9": 7, + "10": 7 + }, + "f": { "0": 71, "1": 22, "2": 31, "3": 3, "4": 4, "5": 5, "6": 7 }, + "b": { "0": [19, 3], "1": [1, 6], "2": [7, 6] }, + "meta": { + "lastBranch": 3, + "lastFunction": 7, + "lastStatement": 11, + "seen": { + "f:10:7:10:13": 0, + "s:11:2:11:Infinity": 0, + "f:16:2:16:10": 1, + "b:17:4:19:Infinity:undefined:undefined:undefined:undefined": 0, + "s:17:4:19:Infinity": 1, + "s:18:6:18:Infinity": 2, + "s:20:4:20:Infinity": 3, + "f:26:2:26:35": 2, + "s:27:4:27:Infinity": 4, + "f:33:2:33:71": 3, + "s:34:4:43:Infinity": 5, + "f:37:8:37:9": 4, + "s:38:10:38:Infinity": 6, + "s:39:10:39:Infinity": 7, + "f:49:10:49:21": 5, + "s:50:4:56:Infinity": 8, + "f:50:24:50:25": 6, + "s:52:8:54:Infinity": 9, + "b:53:12:53:Infinity:54:12:54:Infinity": 1, + "b:52:8:52:35:52:35:52:Infinity": 2, + "s:55:6:55:Infinity": 10 + } + } + }, + "/Users/danielslovinsky/Documents/projects/alchemy-docs/docs/src/content-indexer/collectors/path-index.ts": { + "path": "/Users/danielslovinsky/Documents/projects/alchemy-docs/docs/src/content-indexer/collectors/path-index.ts", + "statementMap": { + "0": { + "start": { "line": 11, "column": 2 }, + "end": { "line": 11, "column": null } + }, + "1": { + "start": { "line": 18, "column": 4 }, + "end": { "line": 22, "column": null } + }, + "2": { + "start": { "line": 19, "column": 6 }, + "end": { "line": 21, "column": null } + }, + "3": { + "start": { "line": 23, "column": 4 }, + "end": { "line": 23, "column": null } + }, + "4": { + "start": { "line": 30, "column": 4 }, + "end": { "line": 30, "column": null } + }, + "5": { + "start": { "line": 37, "column": 20 }, + "end": { "line": 37, "column": null } + }, + "6": { + "start": { "line": 38, "column": 4 }, + "end": { "line": 47, "column": null } + }, + "7": { + "start": { "line": 42, "column": 10 }, + "end": { "line": 42, "column": null } + }, + "8": { + "start": { "line": 43, "column": 10 }, + "end": { "line": 43, "column": null } + } + }, + "fnMap": { + "0": { + "name": "(anonymous_0)", + "decl": { + "start": { "line": 10, "column": 7 }, + "end": { "line": 10, "column": 13 } + }, + "loc": { + "start": { "line": 10, "column": 7 }, + "end": { "line": 11, "column": null } + }, + "line": 10 + }, + "1": { + "name": "(anonymous_1)", + "decl": { + "start": { "line": 17, "column": 2 }, + "end": { "line": 17, "column": 6 } + }, + "loc": { + "start": { "line": 17, "column": 49 }, + "end": { "line": 24, "column": null } + }, + "line": 17 + }, + "2": { + "name": "(anonymous_2)", + "decl": { + "start": { "line": 29, "column": 2 }, + "end": { "line": 29, "column": 24 } + }, + "loc": { + "start": { "line": 29, "column": 24 }, + "end": { "line": 31, "column": null } + }, + "line": 29 + }, + "3": { + "name": "(anonymous_3)", + "decl": { + "start": { "line": 36, "column": 2 }, + "end": { "line": 36, "column": 64 } + }, + "loc": { + "start": { "line": 36, "column": 64 }, + "end": { "line": 48, "column": null } + }, + "line": 36 + }, + "4": { + "name": "(anonymous_4)", + "decl": { + "start": { "line": 41, "column": 8 }, + "end": { "line": 41, "column": 9 } + }, + "loc": { + "start": { "line": 41, "column": 24 }, + "end": { "line": 44, "column": null } + }, + "line": 41 + } + }, + "branchMap": { + "0": { + "loc": { + "start": { "line": 18, "column": 4 }, + "end": { "line": 22, "column": null } + }, + "type": "if", + "locations": [ + { + "start": { "line": 18, "column": 4 }, + "end": { "line": 22, "column": null } + }, + { "start": {}, "end": {} } + ], + "line": 18 + }, + "1": { + "loc": { + "start": { "line": 42, "column": 29 }, + "end": { "line": 42, "column": 53 } + }, + "type": "binary-expr", + "locations": [ + { + "start": { "line": 42, "column": 29 }, + "end": { "line": 42, "column": 48 } + }, + { + "start": { "line": 42, "column": 48 }, + "end": { "line": 42, "column": 53 } + } + ], + "line": 42 + } + }, + "s": { + "0": 71, + "1": 16, + "2": 1, + "3": 16, + "4": 32, + "5": 2, + "6": 2, + "7": 4, + "8": 4 + }, + "f": { "0": 71, "1": 16, "2": 32, "3": 2, "4": 4 }, + "b": { "0": [1, 15], "1": [4, 4] }, + "meta": { + "lastBranch": 2, + "lastFunction": 5, + "lastStatement": 9, + "seen": { + "f:10:7:10:13": 0, + "s:11:2:11:Infinity": 0, + "f:17:2:17:6": 1, + "b:18:4:22:Infinity:undefined:undefined:undefined:undefined": 0, + "s:18:4:22:Infinity": 1, + "s:19:6:21:Infinity": 2, + "s:23:4:23:Infinity": 3, + "f:29:2:29:24": 2, + "s:30:4:30:Infinity": 4, + "f:36:2:36:64": 3, + "s:37:20:37:Infinity": 5, + "s:38:4:47:Infinity": 6, + "f:41:8:41:9": 4, + "s:42:10:42:Infinity": 7, + "b:42:29:42:48:42:48:42:53": 1, + "s:43:10:43:Infinity": 8 + } + } + }, + "/Users/danielslovinsky/Documents/projects/alchemy-docs/docs/src/content-indexer/collectors/processing-context.ts": { + "path": "/Users/danielslovinsky/Documents/projects/alchemy-docs/docs/src/content-indexer/collectors/processing-context.ts", + "statementMap": { + "0": { + "start": { "line": 30, "column": 12 }, + "end": { "line": 30, "column": 33 } + }, + "1": { + "start": { "line": 31, "column": 12 }, + "end": { "line": 31, "column": 39 } + }, + "2": { + "start": { "line": 32, "column": 12 }, + "end": { "line": 32, "column": 31 } + }, + "3": { + "start": { "line": 39, "column": 4 }, + "end": { "line": 39, "column": null } + }, + "4": { + "start": { "line": 46, "column": 4 }, + "end": { "line": 46, "column": null } + }, + "5": { + "start": { "line": 53, "column": 4 }, + "end": { "line": 53, "column": null } + }, + "6": { + "start": { "line": 60, "column": 4 }, + "end": { "line": 64, "column": null } + }, + "7": { + "start": { "line": 71, "column": 4 }, + "end": { "line": 75, "column": null } + } + }, + "fnMap": { + "0": { + "name": "(anonymous_0)", + "decl": { + "start": { "line": 29, "column": 2 }, + "end": { "line": 29, "column": null } + }, + "loc": { + "start": { "line": 33, "column": 4 }, + "end": { "line": 33, "column": null } + }, + "line": 33 + }, + "1": { + "name": "(anonymous_1)", + "decl": { + "start": { "line": 38, "column": 2 }, + "end": { "line": 38, "column": 20 } + }, + "loc": { + "start": { "line": 38, "column": 63 }, + "end": { "line": 40, "column": null } + }, + "line": 38 + }, + "2": { + "name": "(anonymous_2)", + "decl": { + "start": { "line": 45, "column": 2 }, + "end": { "line": 45, "column": 20 } + }, + "loc": { + "start": { "line": 45, "column": 54 }, + "end": { "line": 47, "column": null } + }, + "line": 45 + }, + "3": { + "name": "(anonymous_3)", + "decl": { + "start": { "line": 52, "column": 2 }, + "end": { "line": 52, "column": 19 } + }, + "loc": { + "start": { "line": 52, "column": 50 }, + "end": { "line": 54, "column": null } + }, + "line": 52 + }, + "4": { + "name": "(anonymous_4)", + "decl": { + "start": { "line": 59, "column": 2 }, + "end": { "line": 59, "column": 38 } + }, + "loc": { + "start": { "line": 59, "column": 38 }, + "end": { "line": 65, "column": null } + }, + "line": 59 + }, + "5": { + "name": "(anonymous_5)", + "decl": { + "start": { "line": 70, "column": 2 }, + "end": { "line": 70, "column": 13 } + }, + "loc": { + "start": { "line": 70, "column": 13 }, + "end": { "line": 76, "column": null } + }, + "line": 70 + } + }, + "branchMap": { + "0": { + "loc": { + "start": { "line": 30, "column": 12 }, + "end": { "line": 30, "column": null } + }, + "type": "default-arg", + "locations": [ + { + "start": { "line": 30, "column": 33 }, + "end": { "line": 30, "column": null } + } + ], + "line": 30 + }, + "1": { + "loc": { + "start": { "line": 31, "column": 12 }, + "end": { "line": 31, "column": null } + }, + "type": "default-arg", + "locations": [ + { + "start": { "line": 31, "column": 39 }, + "end": { "line": 31, "column": null } + } + ], + "line": 31 + }, + "2": { + "loc": { + "start": { "line": 32, "column": 12 }, + "end": { "line": 32, "column": null } + }, + "type": "default-arg", + "locations": [ + { + "start": { "line": 32, "column": 31 }, + "end": { "line": 32, "column": null } + } + ], + "line": 32 + } + }, + "s": { + "0": 64, + "1": 64, + "2": 64, + "3": 6, + "4": 12, + "5": 31, + "6": 26, + "7": 1 + }, + "f": { "0": 64, "1": 6, "2": 12, "3": 31, "4": 26, "5": 1 }, + "b": { "0": [64], "1": [64], "2": [64] }, + "meta": { + "lastBranch": 3, + "lastFunction": 6, + "lastStatement": 8, + "seen": { + "f:29:2:29:Infinity": 0, + "b:30:33:30:Infinity": 0, + "b:31:39:31:Infinity": 1, + "b:32:31:32:Infinity": 2, + "s:30:12:30:33": 0, + "s:31:12:31:39": 1, + "s:32:12:32:31": 2, + "f:38:2:38:20": 1, + "s:39:4:39:Infinity": 3, + "f:45:2:45:20": 2, + "s:46:4:46:Infinity": 4, + "f:52:2:52:19": 3, + "s:53:4:53:Infinity": 5, + "f:59:2:59:38": 4, + "s:60:4:64:Infinity": 6, + "f:70:2:70:13": 5, + "s:71:4:75:Infinity": 7 + } + } + }, + "/Users/danielslovinsky/Documents/projects/alchemy-docs/docs/src/content-indexer/constants/http.ts": { + "path": "/Users/danielslovinsky/Documents/projects/alchemy-docs/docs/src/content-indexer/constants/http.ts", + "statementMap": { + "0": { + "start": { "line": 1, "column": 28 }, + "end": { "line": 10, "column": null } + } + }, + "fnMap": {}, + "branchMap": {}, + "s": { "0": 5 }, + "f": {}, + "b": {}, + "meta": { + "lastBranch": 0, + "lastFunction": 0, + "lastStatement": 1, + "seen": { "s:1:28:10:Infinity": 0 } + } + }, + "/Users/danielslovinsky/Documents/projects/alchemy-docs/docs/src/content-indexer/core/batch-fetcher.ts": { + "path": "/Users/danielslovinsky/Documents/projects/alchemy-docs/docs/src/content-indexer/core/batch-fetcher.ts", + "statementMap": { + "0": { + "start": { "line": 33, "column": 33 }, + "end": { "line": 104, "column": null } + }, + "1": { + "start": { "line": 37, "column": 16 }, + "end": { "line": 37, "column": null } + }, + "2": { + "start": { "line": 39, "column": 21 }, + "end": { "line": 39, "column": null } + }, + "3": { + "start": { "line": 40, "column": 2 }, + "end": { "line": 42, "column": null } + }, + "4": { + "start": { "line": 45, "column": 22 }, + "end": { "line": 81, "column": null } + }, + "5": { + "start": { "line": 46, "column": 4 }, + "end": { "line": 80, "column": null } + }, + "6": { + "start": { "line": 47, "column": 6 }, + "end": { "line": 74, "column": null } + }, + "7": { + "start": { "line": 49, "column": 25 }, + "end": { "line": 49, "column": null } + }, + "8": { + "start": { "line": 50, "column": 23 }, + "end": { "line": 50, "column": null } + }, + "9": { + "start": { "line": 52, "column": 8 }, + "end": { "line": 57, "column": null } + }, + "10": { + "start": { "line": 53, "column": 10 }, + "end": { "line": 56, "column": null } + }, + "11": { + "start": { "line": 60, "column": 27 }, + "end": { "line": 63, "column": null } + }, + "12": { + "start": { "line": 64, "column": 25 }, + "end": { "line": 64, "column": null } + }, + "13": { + "start": { "line": 66, "column": 24 }, + "end": { "line": 66, "column": null } + }, + "14": { + "start": { "line": 67, "column": 8 }, + "end": { "line": 73, "column": null } + }, + "15": { + "start": { "line": 68, "column": 38 }, + "end": { "line": 68, "column": null } + }, + "16": { + "start": { "line": 69, "column": 10 }, + "end": { "line": 72, "column": null } + }, + "17": { + "start": { "line": 76, "column": 6 }, + "end": { "line": 79, "column": null } + }, + "18": { + "start": { "line": 84, "column": 23 }, + "end": { "line": 93, "column": null } + }, + "19": { + "start": { "line": 85, "column": 4 }, + "end": { "line": 92, "column": null } + }, + "20": { + "start": { "line": 86, "column": 21 }, + "end": { "line": 86, "column": null } + }, + "21": { + "start": { "line": 87, "column": 6 }, + "end": { "line": 89, "column": null } + }, + "22": { + "start": { "line": 88, "column": 8 }, + "end": { "line": 88, "column": null } + }, + "23": { + "start": { "line": 91, "column": 6 }, + "end": { "line": 91, "column": null } + }, + "24": { + "start": { "line": 96, "column": 2 }, + "end": { "line": 96, "column": null } + }, + "25": { + "start": { "line": 98, "column": 16 }, + "end": { "line": 98, "column": null } + }, + "26": { + "start": { "line": 99, "column": 2 }, + "end": { "line": 101, "column": null } + }, + "27": { + "start": { "line": 103, "column": 2 }, + "end": { "line": 103, "column": null } + } + }, + "fnMap": { + "0": { + "name": "(anonymous_0)", + "decl": { + "start": { "line": 33, "column": 33 }, + "end": { "line": 33, "column": null } + }, + "loc": { + "start": { "line": 36, "column": 28 }, + "end": { "line": 104, "column": null } + }, + "line": 36 + }, + "1": { + "name": "(anonymous_1)", + "decl": { + "start": { "line": 45, "column": 58 }, + "end": { "line": 45, "column": 65 } + }, + "loc": { + "start": { "line": 45, "column": 77 }, + "end": { "line": 81, "column": 3 } + }, + "line": 45 + }, + "2": { + "name": "(anonymous_2)", + "decl": { + "start": { "line": 84, "column": 60 }, + "end": { "line": 84, "column": 67 } + }, + "loc": { + "start": { "line": 84, "column": 79 }, + "end": { "line": 93, "column": 3 } + }, + "line": 84 + } + }, + "branchMap": { + "0": { + "loc": { + "start": { "line": 41, "column": 10 }, + "end": { "line": 41, "column": 62 } + }, + "type": "cond-expr", + "locations": [ + { + "start": { "line": 41, "column": 40 }, + "end": { "line": 41, "column": 52 } + }, + { + "start": { "line": 41, "column": 52 }, + "end": { "line": 41, "column": 62 } + } + ], + "line": 41 + }, + "1": { + "loc": { + "start": { "line": 47, "column": 6 }, + "end": { "line": 74, "column": null } + }, + "type": "if", + "locations": [ + { + "start": { "line": 47, "column": 6 }, + "end": { "line": 74, "column": null } + }, + { + "start": { "line": 58, "column": 13 }, + "end": { "line": 74, "column": null } + } + ], + "line": 47 + }, + "2": { + "loc": { + "start": { "line": 52, "column": 8 }, + "end": { "line": 57, "column": null } + }, + "type": "if", + "locations": [ + { + "start": { "line": 52, "column": 8 }, + "end": { "line": 57, "column": null } + }, + { "start": {}, "end": {} } + ], + "line": 52 + }, + "3": { + "loc": { + "start": { "line": 61, "column": 10 }, + "end": { "line": 61, "column": null } + }, + "type": "binary-expr", + "locations": [ + { + "start": { "line": 61, "column": 10 }, + "end": { "line": 61, "column": 47 } + }, + { + "start": { "line": 61, "column": 47 }, + "end": { "line": 61, "column": null } + } + ], + "line": 61 + }, + "4": { + "loc": { + "start": { "line": 67, "column": 8 }, + "end": { "line": 73, "column": null } + }, + "type": "if", + "locations": [ + { + "start": { "line": 67, "column": 8 }, + "end": { "line": 73, "column": null } + }, + { "start": {}, "end": {} } + ], + "line": 67 + }, + "5": { + "loc": { + "start": { "line": 77, "column": 28 }, + "end": { "line": 77, "column": 75 } + }, + "type": "cond-expr", + "locations": [ + { + "start": { "line": 77, "column": 59 }, + "end": { "line": 77, "column": 68 } + }, + { + "start": { "line": 77, "column": 68 }, + "end": { "line": 77, "column": 75 } + } + ], + "line": 77 + }, + "6": { + "loc": { + "start": { "line": 87, "column": 6 }, + "end": { "line": 89, "column": null } + }, + "type": "if", + "locations": [ + { + "start": { "line": 87, "column": 6 }, + "end": { "line": 89, "column": null } + }, + { "start": {}, "end": {} } + ], + "line": 87 + }, + "7": { + "loc": { + "start": { "line": 100, "column": 12 }, + "end": { "line": 100, "column": 60 } + }, + "type": "cond-expr", + "locations": [ + { + "start": { "line": 100, "column": 42 }, + "end": { "line": 100, "column": 51 } + }, + { + "start": { "line": 100, "column": 51 }, + "end": { "line": 100, "column": 60 } + } + ], + "line": 100 + } + }, + "s": { + "0": 1, + "1": 6, + "2": 6, + "3": 6, + "4": 6, + "5": 6, + "6": 6, + "7": 0, + "8": 0, + "9": 0, + "10": 0, + "11": 6, + "12": 6, + "13": 6, + "14": 5, + "15": 4, + "16": 4, + "17": 1, + "18": 6, + "19": 5, + "20": 5, + "21": 4, + "22": 3, + "23": 1, + "24": 6, + "25": 6, + "26": 6, + "27": 6 + }, + "f": { "0": 6, "1": 6, "2": 5 }, + "b": { + "0": [0, 6], + "1": [0, 6], + "2": [0, 0], + "3": [6, 5], + "4": [4, 1], + "5": [0, 1], + "6": [3, 1], + "7": [0, 6] + }, + "meta": { + "lastBranch": 8, + "lastFunction": 3, + "lastStatement": 28, + "seen": { + "s:33:33:104:Infinity": 0, + "f:33:33:33:Infinity": 0, + "s:37:16:37:Infinity": 1, + "s:39:21:39:Infinity": 2, + "s:40:2:42:Infinity": 3, + "b:41:40:41:52:41:52:41:62": 0, + "s:45:22:81:Infinity": 4, + "f:45:58:45:65": 1, + "s:46:4:80:Infinity": 5, + "b:47:6:74:Infinity:58:13:74:Infinity": 1, + "s:47:6:74:Infinity": 6, + "s:49:25:49:Infinity": 7, + "s:50:23:50:Infinity": 8, + "b:52:8:57:Infinity:undefined:undefined:undefined:undefined": 2, + "s:52:8:57:Infinity": 9, + "s:53:10:56:Infinity": 10, + "s:60:27:63:Infinity": 11, + "b:61:10:61:47:61:47:61:Infinity": 3, + "s:64:25:64:Infinity": 12, + "s:66:24:66:Infinity": 13, + "b:67:8:73:Infinity:undefined:undefined:undefined:undefined": 4, + "s:67:8:73:Infinity": 14, + "s:68:38:68:Infinity": 15, + "s:69:10:72:Infinity": 16, + "s:76:6:79:Infinity": 17, + "b:77:59:77:68:77:68:77:75": 5, + "s:84:23:93:Infinity": 18, + "f:84:60:84:67": 2, + "s:85:4:92:Infinity": 19, + "s:86:21:86:Infinity": 20, + "b:87:6:89:Infinity:undefined:undefined:undefined:undefined": 6, + "s:87:6:89:Infinity": 21, + "s:88:8:88:Infinity": 22, + "s:91:6:91:Infinity": 23, + "s:96:2:96:Infinity": 24, + "s:98:16:98:Infinity": 25, + "s:99:2:101:Infinity": 26, + "b:100:42:100:51:100:51:100:60": 7, + "s:103:2:103:Infinity": 27 + } + } + }, + "/Users/danielslovinsky/Documents/projects/alchemy-docs/docs/src/content-indexer/core/build-all-outputs.ts": { + "path": "/Users/danielslovinsky/Documents/projects/alchemy-docs/docs/src/content-indexer/core/build-all-outputs.ts", + "statementMap": { + "0": { + "start": { "line": 22, "column": 31 }, + "end": { "line": 85, "column": null } + }, + "1": { + "start": { "line": 27, "column": 18 }, + "end": { "line": 27, "column": null } + }, + "2": { + "start": { "line": 30, "column": 2 }, + "end": { "line": 82, "column": null } + }, + "3": { + "start": { "line": 32, "column": 4 }, + "end": { "line": 34, "column": null } + }, + "4": { + "start": { "line": 33, "column": 6 }, + "end": { "line": 33, "column": null } + }, + "5": { + "start": { "line": 37, "column": 10 }, + "end": { "line": 37, "column": null } + }, + "6": { + "start": { "line": 40, "column": 26 }, + "end": { "line": 40, "column": null } + }, + "7": { + "start": { "line": 43, "column": 22 }, + "end": { "line": 43, "column": null } + }, + "8": { + "start": { "line": 44, "column": 4 }, + "end": { "line": 52, "column": null } + }, + "9": { + "start": { "line": 45, "column": 29 }, + "end": { "line": 45, "column": null } + }, + "10": { + "start": { "line": 46, "column": 26 }, + "end": { "line": 46, "column": null } + }, + "11": { + "start": { "line": 48, "column": 6 }, + "end": { "line": 51, "column": null } + }, + "12": { + "start": { "line": 55, "column": 20 }, + "end": { "line": 65, "column": null } + }, + "13": { + "start": { "line": 55, "column": 40 }, + "end": { "line": 64, "column": null } + }, + "14": { + "start": { "line": 68, "column": 4 }, + "end": { "line": 81, "column": null } + }, + "15": { + "start": { "line": 70, "column": 6 }, + "end": { "line": 72, "column": null } + }, + "16": { + "start": { "line": 71, "column": 8 }, + "end": { "line": 71, "column": null } + }, + "17": { + "start": { "line": 75, "column": 6 }, + "end": { "line": 80, "column": null } + }, + "18": { + "start": { "line": 76, "column": 22 }, + "end": { "line": 78, "column": null } + }, + "19": { + "start": { "line": 79, "column": 8 }, + "end": { "line": 79, "column": null } + }, + "20": { + "start": { "line": 79, "column": 32 }, + "end": { "line": 79, "column": 68 } + }, + "21": { + "start": { "line": 84, "column": 2 }, + "end": { "line": 84, "column": null } + } + }, + "fnMap": { + "0": { + "name": "(anonymous_0)", + "decl": { + "start": { "line": 22, "column": 31 }, + "end": { "line": 22, "column": null } + }, + "loc": { + "start": { "line": 26, "column": 28 }, + "end": { "line": 85, "column": null } + }, + "line": 26 + }, + "1": { + "name": "(anonymous_1)", + "decl": { + "start": { "line": 30, "column": 29 }, + "end": { "line": 30, "column": 30 } + }, + "loc": { + "start": { "line": 30, "column": 42 }, + "end": { "line": 82, "column": 3 } + }, + "line": 30 + }, + "2": { + "name": "(anonymous_2)", + "decl": { + "start": { "line": 55, "column": 39 }, + "end": { "line": 55, "column": 40 } + }, + "loc": { + "start": { "line": 55, "column": 40 }, + "end": { "line": 64, "column": null } + }, + "line": 55 + }, + "3": { + "name": "(anonymous_3)", + "decl": { + "start": { "line": 68, "column": 20 }, + "end": { "line": 68, "column": 21 } + }, + "loc": { + "start": { "line": 68, "column": 32 }, + "end": { "line": 81, "column": 5 } + }, + "line": 68 + }, + "4": { + "name": "(anonymous_4)", + "decl": { + "start": { "line": 70, "column": 50 }, + "end": { "line": 70, "column": 51 } + }, + "loc": { + "start": { "line": 70, "column": 69 }, + "end": { "line": 72, "column": 7 } + }, + "line": 70 + }, + "5": { + "name": "(anonymous_5)", + "decl": { + "start": { "line": 79, "column": 22 }, + "end": { "line": 79, "column": 23 } + }, + "loc": { + "start": { "line": 79, "column": 32 }, + "end": { "line": 79, "column": 68 } + }, + "line": 79 + } + }, + "branchMap": { + "0": { + "loc": { + "start": { "line": 25, "column": 2 }, + "end": { "line": 25, "column": null } + }, + "type": "default-arg", + "locations": [ + { + "start": { "line": 25, "column": 21 }, + "end": { "line": 25, "column": null } + } + ], + "line": 25 + }, + "1": { + "loc": { + "start": { "line": 32, "column": 4 }, + "end": { "line": 34, "column": null } + }, + "type": "if", + "locations": [ + { + "start": { "line": 32, "column": 4 }, + "end": { "line": 34, "column": null } + }, + { "start": {}, "end": {} } + ], + "line": 32 + }, + "2": { + "loc": { + "start": { "line": 32, "column": 8 }, + "end": { "line": 32, "column": 41 } + }, + "type": "binary-expr", + "locations": [ + { + "start": { "line": 32, "column": 8 }, + "end": { "line": 32, "column": 24 } + }, + { + "start": { "line": 32, "column": 24 }, + "end": { "line": 32, "column": 41 } + } + ], + "line": 32 + }, + "3": { + "loc": { + "start": { "line": 44, "column": 4 }, + "end": { "line": 52, "column": null } + }, + "type": "if", + "locations": [ + { + "start": { "line": 44, "column": 4 }, + "end": { "line": 52, "column": null } + }, + { "start": {}, "end": {} } + ], + "line": 44 + }, + "4": { + "loc": { + "start": { "line": 45, "column": 29 }, + "end": { "line": 45, "column": null } + }, + "type": "binary-expr", + "locations": [ + { + "start": { "line": 45, "column": 29 }, + "end": { "line": 45, "column": 47 } + }, + { + "start": { "line": 45, "column": 47 }, + "end": { "line": 45, "column": null } + } + ], + "line": 45 + }, + "5": { + "loc": { + "start": { "line": 46, "column": 26 }, + "end": { "line": 46, "column": null } + }, + "type": "binary-expr", + "locations": [ + { + "start": { "line": 46, "column": 26 }, + "end": { "line": 46, "column": 52 } + }, + { + "start": { "line": 46, "column": 52 }, + "end": { "line": 46, "column": null } + } + ], + "line": 46 + }, + "6": { + "loc": { + "start": { "line": 75, "column": 6 }, + "end": { "line": 80, "column": null } + }, + "type": "if", + "locations": [ + { + "start": { "line": 75, "column": 6 }, + "end": { "line": 80, "column": null } + }, + { "start": {}, "end": {} } + ], + "line": 75 + }, + "7": { + "loc": { + "start": { "line": 76, "column": 22 }, + "end": { "line": 78, "column": null } + }, + "type": "cond-expr", + "locations": [ + { + "start": { "line": 77, "column": 12 }, + "end": { "line": 77, "column": null } + }, + { + "start": { "line": 78, "column": 12 }, + "end": { "line": 78, "column": null } + } + ], + "line": 76 + } + }, + "s": { + "0": 1, + "1": 7, + "2": 7, + "3": 10, + "4": 2, + "5": 8, + "6": 8, + "7": 8, + "8": 10, + "9": 2, + "10": 2, + "11": 2, + "12": 8, + "13": 8, + "14": 8, + "15": 8, + "16": 3, + "17": 8, + "18": 6, + "19": 6, + "20": 7, + "21": 7 + }, + "f": { "0": 7, "1": 10, "2": 8, "3": 8, "4": 3, "5": 7 }, + "b": { + "0": [7], + "1": [2, 8], + "2": [10, 9], + "3": [2, 8], + "4": [2, 0], + "5": [2, 0], + "6": [6, 2], + "7": [1, 5] + }, + "meta": { + "lastBranch": 8, + "lastFunction": 6, + "lastStatement": 22, + "seen": { + "s:22:31:85:Infinity": 0, + "f:22:31:22:Infinity": 0, + "b:25:21:25:Infinity": 0, + "s:27:18:27:Infinity": 1, + "s:30:2:82:Infinity": 2, + "f:30:29:30:30": 1, + "b:32:4:34:Infinity:undefined:undefined:undefined:undefined": 1, + "s:32:4:34:Infinity": 3, + "b:32:8:32:24:32:24:32:41": 2, + "s:33:6:33:Infinity": 4, + "s:37:10:37:Infinity": 5, + "s:40:26:40:Infinity": 6, + "s:43:22:43:Infinity": 7, + "b:44:4:52:Infinity:undefined:undefined:undefined:undefined": 3, + "s:44:4:52:Infinity": 8, + "s:45:29:45:Infinity": 9, + "b:45:29:45:47:45:47:45:Infinity": 4, + "s:46:26:46:Infinity": 10, + "b:46:26:46:52:46:52:46:Infinity": 5, + "s:48:6:51:Infinity": 11, + "s:55:20:65:Infinity": 12, + "f:55:39:55:40": 2, + "s:55:40:64:Infinity": 13, + "s:68:4:81:Infinity": 14, + "f:68:20:68:21": 3, + "s:70:6:72:Infinity": 15, + "f:70:50:70:51": 4, + "s:71:8:71:Infinity": 16, + "b:75:6:80:Infinity:undefined:undefined:undefined:undefined": 6, + "s:75:6:80:Infinity": 17, + "s:76:22:78:Infinity": 18, + "b:77:12:77:Infinity:78:12:78:Infinity": 7, + "s:79:8:79:Infinity": 19, + "f:79:22:79:23": 5, + "s:79:32:79:68": 20, + "s:84:2:84:Infinity": 21 + } + } + }, + "/Users/danielslovinsky/Documents/projects/alchemy-docs/docs/src/content-indexer/core/content-cache.ts": { + "path": "/Users/danielslovinsky/Documents/projects/alchemy-docs/docs/src/content-indexer/core/content-cache.ts", + "statementMap": { + "0": { + "start": { "line": 45, "column": 4 }, + "end": { "line": 45, "column": null } + }, + "1": { + "start": { "line": 46, "column": 4 }, + "end": { "line": 46, "column": null } + }, + "2": { + "start": { "line": 53, "column": 4 }, + "end": { "line": 53, "column": null } + }, + "3": { + "start": { "line": 60, "column": 4 }, + "end": { "line": 60, "column": null } + }, + "4": { + "start": { "line": 67, "column": 4 }, + "end": { "line": 67, "column": null } + }, + "5": { + "start": { "line": 74, "column": 4 }, + "end": { "line": 74, "column": null } + }, + "6": { + "start": { "line": 81, "column": 4 }, + "end": { "line": 84, "column": null } + } + }, + "fnMap": { + "0": { + "name": "(anonymous_0)", + "decl": { + "start": { "line": 44, "column": 2 }, + "end": { "line": 44, "column": 16 } + }, + "loc": { + "start": { "line": 44, "column": 16 }, + "end": { "line": 47, "column": null } + }, + "line": 44 + }, + "1": { + "name": "(anonymous_1)", + "decl": { + "start": { "line": 52, "column": 2 }, + "end": { "line": 52, "column": 16 } + }, + "loc": { + "start": { "line": 52, "column": 62 }, + "end": { "line": 54, "column": null } + }, + "line": 52 + }, + "2": { + "name": "(anonymous_2)", + "decl": { + "start": { "line": 59, "column": 2 }, + "end": { "line": 59, "column": 16 } + }, + "loc": { + "start": { "line": 59, "column": 61 }, + "end": { "line": 61, "column": null } + }, + "line": 59 + }, + "3": { + "name": "(anonymous_3)", + "decl": { + "start": { "line": 66, "column": 2 }, + "end": { "line": 66, "column": 10 } + }, + "loc": { + "start": { "line": 66, "column": 56 }, + "end": { "line": 68, "column": null } + }, + "line": 66 + }, + "4": { + "name": "(anonymous_4)", + "decl": { + "start": { "line": 73, "column": 2 }, + "end": { "line": 73, "column": 10 } + }, + "loc": { + "start": { "line": 73, "column": 55 }, + "end": { "line": 75, "column": null } + }, + "line": 73 + }, + "5": { + "name": "(anonymous_5)", + "decl": { + "start": { "line": 80, "column": 2 }, + "end": { "line": 80, "column": 25 } + }, + "loc": { + "start": { "line": 80, "column": 25 }, + "end": { "line": 85, "column": null } + }, + "line": 80 + } + }, + "branchMap": {}, + "s": { "0": 75, "1": 75, "2": 18, "3": 29, "4": 17, "5": 14, "6": 12 }, + "f": { "0": 75, "1": 18, "2": 29, "3": 17, "4": 14, "5": 12 }, + "b": {}, + "meta": { + "lastBranch": 0, + "lastFunction": 6, + "lastStatement": 7, + "seen": { + "f:44:2:44:16": 0, + "s:45:4:45:Infinity": 0, + "s:46:4:46:Infinity": 1, + "f:52:2:52:16": 1, + "s:53:4:53:Infinity": 2, + "f:59:2:59:16": 2, + "s:60:4:60:Infinity": 3, + "f:66:2:66:10": 3, + "s:67:4:67:Infinity": 4, + "f:73:2:73:10": 4, + "s:74:4:74:Infinity": 5, + "f:80:2:80:25": 5, + "s:81:4:84:Infinity": 6 + } + } + }, + "/Users/danielslovinsky/Documents/projects/alchemy-docs/docs/src/content-indexer/core/path-builder.ts": { + "path": "/Users/danielslovinsky/Documents/projects/alchemy-docs/docs/src/content-indexer/core/path-builder.ts", + "statementMap": { + "0": { + "start": { "line": 11, "column": 4 }, + "end": { "line": 11, "column": null } + }, + "1": { + "start": { "line": 18, "column": 21 }, + "end": { "line": 18, "column": null } + }, + "2": { + "start": { "line": 19, "column": 4 }, + "end": { "line": 19, "column": null } + }, + "3": { + "start": { "line": 35, "column": 4 }, + "end": { "line": 37, "column": null } + }, + "4": { + "start": { "line": 36, "column": 6 }, + "end": { "line": 36, "column": null } + }, + "5": { + "start": { "line": 40, "column": 4 }, + "end": { "line": 42, "column": null } + }, + "6": { + "start": { "line": 41, "column": 6 }, + "end": { "line": 41, "column": null } + }, + "7": { + "start": { "line": 45, "column": 4 }, + "end": { "line": 47, "column": null } + }, + "8": { + "start": { "line": 46, "column": 6 }, + "end": { "line": 46, "column": null } + }, + "9": { + "start": { "line": 49, "column": 4 }, + "end": { "line": 49, "column": null } + }, + "10": { + "start": { "line": 56, "column": 4 }, + "end": { "line": 56, "column": null } + } + }, + "fnMap": { + "0": { + "name": "(anonymous_0)", + "decl": { + "start": { "line": 10, "column": 10 }, + "end": { "line": 10, "column": 22 } + }, + "loc": { + "start": { "line": 10, "column": 42 }, + "end": { "line": 12, "column": null } + }, + "line": 10 + }, + "1": { + "name": "(anonymous_1)", + "decl": { + "start": { "line": 17, "column": 9 }, + "end": { "line": 17, "column": 14 } + }, + "loc": { + "start": { "line": 17, "column": 50 }, + "end": { "line": 20, "column": null } + }, + "line": 17 + }, + "2": { + "name": "(anonymous_2)", + "decl": { + "start": { "line": 29, "column": 2 }, + "end": { "line": 29, "column": 8 } + }, + "loc": { + "start": { "line": 33, "column": 18 }, + "end": { "line": 50, "column": null } + }, + "line": 33 + }, + "3": { + "name": "(anonymous_3)", + "decl": { + "start": { "line": 55, "column": 2 }, + "end": { "line": 55, "column": 16 } + }, + "loc": { + "start": { "line": 55, "column": 16 }, + "end": { "line": 57, "column": null } + }, + "line": 55 + } + }, + "branchMap": { + "0": { + "loc": { + "start": { "line": 17, "column": 14 }, + "end": { "line": 17, "column": 50 } + }, + "type": "default-arg", + "locations": [ + { + "start": { "line": 17, "column": 33 }, + "end": { "line": 17, "column": 50 } + } + ], + "line": 17 + }, + "1": { + "loc": { + "start": { "line": 18, "column": 21 }, + "end": { "line": 18, "column": null } + }, + "type": "cond-expr", + "locations": [ + { + "start": { "line": 18, "column": 32 }, + "end": { "line": 18, "column": 70 } + }, + { + "start": { "line": 18, "column": 70 }, + "end": { "line": 18, "column": null } + } + ], + "line": 18 + }, + "2": { + "loc": { + "start": { "line": 35, "column": 4 }, + "end": { "line": 37, "column": null } + }, + "type": "if", + "locations": [ + { + "start": { "line": 35, "column": 4 }, + "end": { "line": 37, "column": null } + }, + { "start": {}, "end": {} } + ], + "line": 35 + }, + "3": { + "loc": { + "start": { "line": 40, "column": 4 }, + "end": { "line": 42, "column": null } + }, + "type": "if", + "locations": [ + { + "start": { "line": 40, "column": 4 }, + "end": { "line": 42, "column": null } + }, + { "start": {}, "end": {} } + ], + "line": 40 + }, + "4": { + "loc": { + "start": { "line": 45, "column": 4 }, + "end": { "line": 47, "column": null } + }, + "type": "if", + "locations": [ + { + "start": { "line": 45, "column": 4 }, + "end": { "line": 47, "column": null } + }, + { "start": {}, "end": {} } + ], + "line": 45 + } + }, + "s": { + "0": 205, + "1": 90, + "2": 90, + "3": 115, + "4": 6, + "5": 109, + "6": 3, + "7": 106, + "8": 106, + "9": 0, + "10": 79 + }, + "f": { "0": 205, "1": 10, "2": 115, "3": 79 }, + "b": { + "0": [90], + "1": [50, 40], + "2": [6, 109], + "3": [3, 106], + "4": [106, 0] + }, + "meta": { + "lastBranch": 5, + "lastFunction": 4, + "lastStatement": 11, + "seen": { + "f:10:10:10:22": 0, + "s:11:4:11:Infinity": 0, + "f:17:9:17:14": 1, + "b:17:33:17:50": 0, + "s:18:21:18:Infinity": 1, + "b:18:32:18:70:18:70:18:Infinity": 1, + "s:19:4:19:Infinity": 2, + "f:29:2:29:8": 2, + "b:35:4:37:Infinity:undefined:undefined:undefined:undefined": 2, + "s:35:4:37:Infinity": 3, + "s:36:6:36:Infinity": 4, + "b:40:4:42:Infinity:undefined:undefined:undefined:undefined": 3, + "s:40:4:42:Infinity": 5, + "s:41:6:41:Infinity": 6, + "b:45:4:47:Infinity:undefined:undefined:undefined:undefined": 4, + "s:45:4:47:Infinity": 7, + "s:46:6:46:Infinity": 8, + "s:49:4:49:Infinity": 9, + "f:55:2:55:16": 3, + "s:56:4:56:Infinity": 10 + } + } + }, + "/Users/danielslovinsky/Documents/projects/alchemy-docs/docs/src/content-indexer/core/scanner.ts": { + "path": "/Users/danielslovinsky/Documents/projects/alchemy-docs/docs/src/content-indexer/core/scanner.ts", + "statementMap": { + "0": { + "start": { "line": 22, "column": 27 }, + "end": { "line": 56, "column": null } + }, + "1": { + "start": { "line": 24, "column": 2 }, + "end": { "line": 26, "column": null } + }, + "2": { + "start": { "line": 25, "column": 4 }, + "end": { "line": 25, "column": null } + }, + "3": { + "start": { "line": 29, "column": 2 }, + "end": { "line": 31, "column": null } + }, + "4": { + "start": { "line": 30, "column": 4 }, + "end": { "line": 30, "column": null } + }, + "5": { + "start": { "line": 34, "column": 2 }, + "end": { "line": 37, "column": null } + }, + "6": { + "start": { "line": 35, "column": 4 }, + "end": { "line": 35, "column": null } + }, + "7": { + "start": { "line": 36, "column": 4 }, + "end": { "line": 36, "column": null } + }, + "8": { + "start": { "line": 40, "column": 2 }, + "end": { "line": 43, "column": null } + }, + "9": { + "start": { "line": 41, "column": 4 }, + "end": { "line": 41, "column": null } + }, + "10": { + "start": { "line": 42, "column": 4 }, + "end": { "line": 42, "column": null } + }, + "11": { + "start": { "line": 46, "column": 2 }, + "end": { "line": 55, "column": null } + }, + "12": { + "start": { "line": 47, "column": 4 }, + "end": { "line": 49, "column": null } + }, + "13": { + "start": { "line": 48, "column": 6 }, + "end": { "line": 48, "column": null } + }, + "14": { + "start": { "line": 52, "column": 4 }, + "end": { "line": 54, "column": null } + }, + "15": { + "start": { "line": 53, "column": 6 }, + "end": { "line": 53, "column": null } + }, + "16": { + "start": { "line": 65, "column": 27 }, + "end": { "line": 85, "column": null } + }, + "17": { + "start": { "line": 66, "column": 2 }, + "end": { "line": 68, "column": null } + }, + "18": { + "start": { "line": 67, "column": 4 }, + "end": { "line": 67, "column": null } + }, + "19": { + "start": { "line": 70, "column": 29 }, + "end": { "line": 73, "column": null } + }, + "20": { + "start": { "line": 76, "column": 2 }, + "end": { "line": 82, "column": null } + }, + "21": { + "start": { "line": 77, "column": 4 }, + "end": { "line": 81, "column": null } + }, + "22": { + "start": { "line": 78, "column": 6 }, + "end": { "line": 80, "column": null } + }, + "23": { + "start": { "line": 79, "column": 8 }, + "end": { "line": 79, "column": null } + }, + "24": { + "start": { "line": 84, "column": 2 }, + "end": { "line": 84, "column": null } + } + }, + "fnMap": { + "0": { + "name": "(anonymous_0)", + "decl": { + "start": { "line": 22, "column": 27 }, + "end": { "line": 22, "column": 28 } + }, + "loc": { + "start": { "line": 22, "column": 79 }, + "end": { "line": 56, "column": null } + }, + "line": 22 + }, + "1": { + "name": "(anonymous_1)", + "decl": { + "start": { "line": 52, "column": 26 }, + "end": { "line": 52, "column": 27 } + }, + "loc": { + "start": { "line": 52, "column": 41 }, + "end": { "line": 54, "column": 5 } + }, + "line": 52 + }, + "2": { + "name": "(anonymous_2)", + "decl": { + "start": { "line": 65, "column": 27 }, + "end": { "line": 65, "column": 28 } + }, + "loc": { + "start": { "line": 65, "column": 61 }, + "end": { "line": 85, "column": null } + }, + "line": 65 + }, + "3": { + "name": "(anonymous_3)", + "decl": { + "start": { "line": 76, "column": 29 }, + "end": { "line": 76, "column": 30 } + }, + "loc": { + "start": { "line": 76, "column": 42 }, + "end": { "line": 82, "column": 3 } + }, + "line": 76 + }, + "4": { + "name": "(anonymous_4)", + "decl": { + "start": { "line": 78, "column": 29 }, + "end": { "line": 78, "column": 30 } + }, + "loc": { + "start": { "line": 78, "column": 45 }, + "end": { "line": 80, "column": 7 } + }, + "line": 78 + } + }, + "branchMap": { + "0": { + "loc": { + "start": { "line": 24, "column": 2 }, + "end": { "line": 26, "column": null } + }, + "type": "if", + "locations": [ + { + "start": { "line": 24, "column": 2 }, + "end": { "line": 26, "column": null } + }, + { "start": {}, "end": {} } + ], + "line": 24 + }, + "1": { + "loc": { + "start": { "line": 29, "column": 2 }, + "end": { "line": 31, "column": null } + }, + "type": "if", + "locations": [ + { + "start": { "line": 29, "column": 2 }, + "end": { "line": 31, "column": null } + }, + { "start": {}, "end": {} } + ], + "line": 29 + }, + "2": { + "loc": { + "start": { "line": 34, "column": 2 }, + "end": { "line": 37, "column": null } + }, + "type": "if", + "locations": [ + { + "start": { "line": 34, "column": 2 }, + "end": { "line": 37, "column": null } + }, + { "start": {}, "end": {} } + ], + "line": 34 + }, + "3": { + "loc": { + "start": { "line": 40, "column": 2 }, + "end": { "line": 43, "column": null } + }, + "type": "if", + "locations": [ + { + "start": { "line": 40, "column": 2 }, + "end": { "line": 43, "column": null } + }, + { "start": {}, "end": {} } + ], + "line": 40 + }, + "4": { + "loc": { + "start": { "line": 46, "column": 2 }, + "end": { "line": 55, "column": null } + }, + "type": "if", + "locations": [ + { + "start": { "line": 46, "column": 2 }, + "end": { "line": 55, "column": null } + }, + { "start": {}, "end": {} } + ], + "line": 46 + }, + "5": { + "loc": { + "start": { "line": 47, "column": 4 }, + "end": { "line": 49, "column": null } + }, + "type": "if", + "locations": [ + { + "start": { "line": 47, "column": 4 }, + "end": { "line": 49, "column": null } + }, + { "start": {}, "end": {} } + ], + "line": 47 + }, + "6": { + "loc": { + "start": { "line": 66, "column": 2 }, + "end": { "line": 68, "column": null } + }, + "type": "if", + "locations": [ + { + "start": { "line": 66, "column": 2 }, + "end": { "line": 68, "column": null } + }, + { "start": {}, "end": {} } + ], + "line": 66 + }, + "7": { + "loc": { + "start": { "line": 77, "column": 4 }, + "end": { "line": 81, "column": null } + }, + "type": "if", + "locations": [ + { + "start": { "line": 77, "column": 4 }, + "end": { "line": 81, "column": null } + }, + { "start": {}, "end": {} } + ], + "line": 77 + } + }, + "s": { + "0": 1, + "1": 23, + "2": 1, + "3": 22, + "4": 2, + "5": 20, + "6": 3, + "7": 3, + "8": 17, + "9": 12, + "10": 12, + "11": 5, + "12": 5, + "13": 1, + "14": 5, + "15": 6, + "16": 1, + "17": 10, + "18": 1, + "19": 9, + "20": 9, + "21": 10, + "22": 10, + "23": 17, + "24": 9 + }, + "f": { "0": 23, "1": 6, "2": 10, "3": 10, "4": 17 }, + "b": { + "0": [1, 22], + "1": [2, 20], + "2": [3, 17], + "3": [12, 5], + "4": [5, 0], + "5": [1, 4], + "6": [1, 9], + "7": [10, 0] + }, + "meta": { + "lastBranch": 8, + "lastFunction": 5, + "lastStatement": 25, + "seen": { + "s:22:27:56:Infinity": 0, + "f:22:27:22:28": 0, + "b:24:2:26:Infinity:undefined:undefined:undefined:undefined": 0, + "s:24:2:26:Infinity": 1, + "s:25:4:25:Infinity": 2, + "b:29:2:31:Infinity:undefined:undefined:undefined:undefined": 1, + "s:29:2:31:Infinity": 3, + "s:30:4:30:Infinity": 4, + "b:34:2:37:Infinity:undefined:undefined:undefined:undefined": 2, + "s:34:2:37:Infinity": 5, + "s:35:4:35:Infinity": 6, + "s:36:4:36:Infinity": 7, + "b:40:2:43:Infinity:undefined:undefined:undefined:undefined": 3, + "s:40:2:43:Infinity": 8, + "s:41:4:41:Infinity": 9, + "s:42:4:42:Infinity": 10, + "b:46:2:55:Infinity:undefined:undefined:undefined:undefined": 4, + "s:46:2:55:Infinity": 11, + "b:47:4:49:Infinity:undefined:undefined:undefined:undefined": 5, + "s:47:4:49:Infinity": 12, + "s:48:6:48:Infinity": 13, + "s:52:4:54:Infinity": 14, + "f:52:26:52:27": 1, + "s:53:6:53:Infinity": 15, + "s:65:27:85:Infinity": 16, + "f:65:27:65:28": 2, + "b:66:2:68:Infinity:undefined:undefined:undefined:undefined": 6, + "s:66:2:68:Infinity": 17, + "s:67:4:67:Infinity": 18, + "s:70:29:73:Infinity": 19, + "s:76:2:82:Infinity": 20, + "f:76:29:76:30": 3, + "b:77:4:81:Infinity:undefined:undefined:undefined:undefined": 7, + "s:77:4:81:Infinity": 21, + "s:78:6:80:Infinity": 22, + "f:78:29:78:30": 4, + "s:79:8:79:Infinity": 23, + "s:84:2:84:Infinity": 24 + } + } + }, + "/Users/danielslovinsky/Documents/projects/alchemy-docs/docs/src/content-indexer/indexers/main.ts": { + "path": "/Users/danielslovinsky/Documents/projects/alchemy-docs/docs/src/content-indexer/indexers/main.ts", + "statementMap": { + "0": { + "start": { "line": 33, "column": 37 }, + "end": { "line": 75, "column": null } + }, + "1": { + "start": { "line": 36, "column": 2 }, + "end": { "line": 36, "column": null } + }, + "2": { + "start": { "line": 40, "column": 2 }, + "end": { "line": 53, "column": null } + }, + "3": { + "start": { "line": 41, "column": 19 }, + "end": { "line": 41, "column": null } + }, + "4": { + "start": { "line": 42, "column": 4 }, + "end": { "line": 44, "column": null } + }, + "5": { + "start": { "line": 43, "column": 6 }, + "end": { "line": 43, "column": null } + }, + "6": { + "start": { "line": 45, "column": 4 }, + "end": { "line": 45, "column": null } + }, + "7": { + "start": { "line": 47, "column": 24 }, + "end": { "line": 47, "column": null } + }, + "8": { + "start": { "line": 48, "column": 20 }, + "end": { "line": 48, "column": null } + }, + "9": { + "start": { "line": 49, "column": 4 }, + "end": { "line": 51, "column": null } + }, + "10": { + "start": { "line": 50, "column": 6 }, + "end": { "line": 50, "column": null } + }, + "11": { + "start": { "line": 52, "column": 4 }, + "end": { "line": 52, "column": null } + }, + "12": { + "start": { "line": 56, "column": 2 }, + "end": { "line": 56, "column": null } + }, + "13": { + "start": { "line": 57, "column": 8 }, + "end": { "line": 57, "column": null } + }, + "14": { + "start": { "line": 58, "column": 2 }, + "end": { "line": 60, "column": null } + }, + "15": { + "start": { "line": 63, "column": 2 }, + "end": { "line": 63, "column": null } + }, + "16": { + "start": { "line": 64, "column": 23 }, + "end": { "line": 64, "column": null } + }, + "17": { + "start": { "line": 67, "column": 2 }, + "end": { "line": 67, "column": null } + }, + "18": { + "start": { "line": 68, "column": 8 }, + "end": { "line": 68, "column": null } + }, + "19": { + "start": { "line": 70, "column": 2 }, + "end": { "line": 72, "column": null } + }, + "20": { + "start": { "line": 74, "column": 2 }, + "end": { "line": 74, "column": null } + } + }, + "fnMap": { + "0": { + "name": "(anonymous_0)", + "decl": { + "start": { "line": 33, "column": 37 }, + "end": { "line": 33, "column": null } + }, + "loc": { + "start": { "line": 35, "column": 29 }, + "end": { "line": 75, "column": null } + }, + "line": 35 + } + }, + "branchMap": { + "0": { + "loc": { + "start": { "line": 40, "column": 2 }, + "end": { "line": 53, "column": null } + }, + "type": "if", + "locations": [ + { + "start": { "line": 40, "column": 2 }, + "end": { "line": 53, "column": null } + }, + { + "start": { "line": 46, "column": 9 }, + "end": { "line": 53, "column": null } + } + ], + "line": 40 + }, + "1": { + "loc": { + "start": { "line": 42, "column": 4 }, + "end": { "line": 44, "column": null } + }, + "type": "if", + "locations": [ + { + "start": { "line": 42, "column": 4 }, + "end": { "line": 44, "column": null } + }, + { "start": {}, "end": {} } + ], + "line": 42 + }, + "2": { + "loc": { + "start": { "line": 49, "column": 4 }, + "end": { "line": 51, "column": null } + }, + "type": "if", + "locations": [ + { + "start": { "line": 49, "column": 4 }, + "end": { "line": 51, "column": null } + }, + { "start": {}, "end": {} } + ], + "line": 49 + } + }, + "s": { + "0": 1, + "1": 2, + "2": 2, + "3": 1, + "4": 1, + "5": 0, + "6": 1, + "7": 1, + "8": 1, + "9": 1, + "10": 0, + "11": 1, + "12": 2, + "13": 2, + "14": 2, + "15": 2, + "16": 2, + "17": 2, + "18": 2, + "19": 2, + "20": 2 + }, + "f": { "0": 2 }, + "b": { "0": [1, 1], "1": [0, 1], "2": [0, 1] }, + "meta": { + "lastBranch": 3, + "lastFunction": 1, + "lastStatement": 21, + "seen": { + "s:33:37:75:Infinity": 0, + "f:33:37:33:Infinity": 0, + "s:36:2:36:Infinity": 1, + "b:40:2:53:Infinity:46:9:53:Infinity": 0, + "s:40:2:53:Infinity": 2, + "s:41:19:41:Infinity": 3, + "b:42:4:44:Infinity:undefined:undefined:undefined:undefined": 1, + "s:42:4:44:Infinity": 4, + "s:43:6:43:Infinity": 5, + "s:45:4:45:Infinity": 6, + "s:47:24:47:Infinity": 7, + "s:48:20:48:Infinity": 8, + "b:49:4:51:Infinity:undefined:undefined:undefined:undefined": 2, + "s:49:4:51:Infinity": 9, + "s:50:6:50:Infinity": 10, + "s:52:4:52:Infinity": 11, + "s:56:2:56:Infinity": 12, + "s:57:8:57:Infinity": 13, + "s:58:2:60:Infinity": 14, + "s:63:2:63:Infinity": 15, + "s:64:23:64:Infinity": 16, + "s:67:2:67:Infinity": 17, + "s:68:8:68:Infinity": 18, + "s:70:2:72:Infinity": 19, + "s:74:2:74:Infinity": 20 + } + } + }, + "/Users/danielslovinsky/Documents/projects/alchemy-docs/docs/src/content-indexer/types/docsYaml.ts": { + "path": "/Users/danielslovinsky/Documents/projects/alchemy-docs/docs/src/content-indexer/types/docsYaml.ts", + "statementMap": { + "0": { + "start": { "line": 65, "column": 28 }, + "end": { "line": 67, "column": null } + }, + "1": { + "start": { "line": 66, "column": 2 }, + "end": { "line": 66, "column": null } + }, + "2": { + "start": { "line": 69, "column": 31 }, + "end": { "line": 75, "column": null } + }, + "3": { + "start": { "line": 72, "column": 2 }, + "end": { "line": 73, "column": null } + }, + "4": { + "start": { "line": 77, "column": 28 }, + "end": { "line": 79, "column": null } + }, + "5": { + "start": { "line": 78, "column": 2 }, + "end": { "line": 78, "column": null } + }, + "6": { + "start": { "line": 81, "column": 27 }, + "end": { "line": 85, "column": null } + }, + "7": { + "start": { "line": 82, "column": 2 }, + "end": { "line": 83, "column": null } + }, + "8": { + "start": { "line": 87, "column": 33 }, + "end": { "line": 91, "column": null } + }, + "9": { + "start": { "line": 90, "column": 2 }, + "end": { "line": 90, "column": null } + } + }, + "fnMap": { + "0": { + "name": "(anonymous_0)", + "decl": { + "start": { "line": 65, "column": 28 }, + "end": { "line": 65, "column": 29 } + }, + "loc": { + "start": { "line": 65, "column": 74 }, + "end": { "line": 67, "column": null } + }, + "line": 65 + }, + "1": { + "name": "(anonymous_1)", + "decl": { + "start": { "line": 69, "column": 31 }, + "end": { "line": 69, "column": null } + }, + "loc": { + "start": { "line": 71, "column": 28 }, + "end": { "line": 75, "column": null } + }, + "line": 71 + }, + "2": { + "name": "(anonymous_2)", + "decl": { + "start": { "line": 77, "column": 28 }, + "end": { "line": 77, "column": 29 } + }, + "loc": { + "start": { "line": 77, "column": 74 }, + "end": { "line": 79, "column": null } + }, + "line": 77 + }, + "3": { + "name": "(anonymous_3)", + "decl": { + "start": { "line": 81, "column": 27 }, + "end": { "line": 81, "column": 28 } + }, + "loc": { + "start": { "line": 81, "column": 72 }, + "end": { "line": 85, "column": null } + }, + "line": 81 + }, + "4": { + "name": "(anonymous_4)", + "decl": { + "start": { "line": 87, "column": 33 }, + "end": { "line": 87, "column": null } + }, + "loc": { + "start": { "line": 89, "column": 30 }, + "end": { "line": 91, "column": null } + }, + "line": 89 + } + }, + "branchMap": { + "0": { + "loc": { + "start": { "line": 66, "column": 9 }, + "end": { "line": 66, "column": null } + }, + "type": "binary-expr", + "locations": [ + { + "start": { "line": 66, "column": 9 }, + "end": { "line": 66, "column": 17 } + }, + { + "start": { "line": 66, "column": 17 }, + "end": { "line": 66, "column": 45 } + }, + { + "start": { "line": 66, "column": 45 }, + "end": { "line": 66, "column": 63 } + }, + { + "start": { "line": 66, "column": 63 }, + "end": { "line": 66, "column": null } + } + ], + "line": 66 + }, + "1": { + "loc": { + "start": { "line": 73, "column": 4 }, + "end": { "line": 73, "column": null } + }, + "type": "binary-expr", + "locations": [ + { + "start": { "line": 73, "column": 4 }, + "end": { "line": 73, "column": 12 } + }, + { + "start": { "line": 73, "column": 12 }, + "end": { "line": 73, "column": 40 } + }, + { + "start": { "line": 73, "column": 40 }, + "end": { "line": 73, "column": 61 } + }, + { + "start": { "line": 73, "column": 61 }, + "end": { "line": 73, "column": null } + } + ], + "line": 73 + }, + "2": { + "loc": { + "start": { "line": 78, "column": 9 }, + "end": { "line": 78, "column": null } + }, + "type": "binary-expr", + "locations": [ + { + "start": { "line": 78, "column": 9 }, + "end": { "line": 78, "column": 17 } + }, + { + "start": { "line": 78, "column": 17 }, + "end": { "line": 78, "column": 45 } + }, + { + "start": { "line": 78, "column": 45 }, + "end": { "line": 78, "column": 63 } + }, + { + "start": { "line": 78, "column": 63 }, + "end": { "line": 78, "column": null } + } + ], + "line": 78 + }, + "3": { + "loc": { + "start": { "line": 83, "column": 4 }, + "end": { "line": 83, "column": null } + }, + "type": "binary-expr", + "locations": [ + { + "start": { "line": 83, "column": 4 }, + "end": { "line": 83, "column": 12 } + }, + { + "start": { "line": 83, "column": 12 }, + "end": { "line": 83, "column": 40 } + }, + { + "start": { "line": 83, "column": 40 }, + "end": { "line": 83, "column": 57 } + }, + { + "start": { "line": 83, "column": 57 }, + "end": { "line": 83, "column": null } + } + ], + "line": 83 + }, + "4": { + "loc": { + "start": { "line": 90, "column": 9 }, + "end": { "line": 90, "column": null } + }, + "type": "binary-expr", + "locations": [ + { + "start": { "line": 90, "column": 9 }, + "end": { "line": 90, "column": 17 } + }, + { + "start": { "line": 90, "column": 17 }, + "end": { "line": 90, "column": 45 } + }, + { + "start": { "line": 90, "column": 45 }, + "end": { "line": 90, "column": null } + } + ], + "line": 90 + } + }, + "s": { + "0": 3, + "1": 34, + "2": 3, + "3": 9, + "4": 3, + "5": 41, + "6": 3, + "7": 21, + "8": 3, + "9": 43 + }, + "f": { "0": 34, "1": 9, "2": 41, "3": 21, "4": 43 }, + "b": { + "0": [34, 34, 34, 25], + "1": [9, 9, 9, 8], + "2": [41, 41, 41, 4], + "3": [21, 21, 21, 4], + "4": [43, 43, 43] + }, + "meta": { + "lastBranch": 5, + "lastFunction": 5, + "lastStatement": 10, + "seen": { + "s:65:28:67:Infinity": 0, + "f:65:28:65:29": 0, + "s:66:2:66:Infinity": 1, + "b:66:9:66:17:66:17:66:45:66:45:66:63:66:63:66:Infinity": 0, + "s:69:31:75:Infinity": 2, + "f:69:31:69:Infinity": 1, + "s:72:2:73:Infinity": 3, + "b:73:4:73:12:73:12:73:40:73:40:73:61:73:61:73:Infinity": 1, + "s:77:28:79:Infinity": 4, + "f:77:28:77:29": 2, + "s:78:2:78:Infinity": 5, + "b:78:9:78:17:78:17:78:45:78:45:78:63:78:63:78:Infinity": 2, + "s:81:27:85:Infinity": 6, + "f:81:27:81:28": 3, + "s:82:2:83:Infinity": 7, + "b:83:4:83:12:83:12:83:40:83:40:83:57:83:57:83:Infinity": 3, + "s:87:33:91:Infinity": 8, + "f:87:33:87:Infinity": 4, + "s:90:2:90:Infinity": 9, + "b:90:9:90:17:90:17:90:45:90:45:90:Infinity": 4 + } + } + }, + "/Users/danielslovinsky/Documents/projects/alchemy-docs/docs/src/content-indexer/uploaders/algolia.ts": { + "path": "/Users/danielslovinsky/Documents/projects/alchemy-docs/docs/src/content-indexer/uploaders/algolia.ts", + "statementMap": { + "0": { + "start": { "line": 16, "column": 23 }, + "end": { "line": 29, "column": null } + }, + "1": { + "start": { "line": 21, "column": 16 }, + "end": { "line": 21, "column": null } + }, + "2": { + "start": { "line": 24, "column": 2 }, + "end": { "line": 26, "column": null } + }, + "3": { + "start": { "line": 25, "column": 4 }, + "end": { "line": 25, "column": null } + }, + "4": { + "start": { "line": 28, "column": 2 }, + "end": { "line": 28, "column": null } + }, + "5": { + "start": { "line": 46, "column": 31 }, + "end": { "line": 129, "column": null } + }, + "6": { + "start": { "line": 53, "column": 16 }, + "end": { "line": 53, "column": null } + }, + "7": { + "start": { "line": 54, "column": 19 }, + "end": { "line": 54, "column": null } + }, + "8": { + "start": { "line": 55, "column": 19 }, + "end": { "line": 55, "column": null } + }, + "9": { + "start": { "line": 57, "column": 2 }, + "end": { "line": 63, "column": null } + }, + "10": { + "start": { "line": 58, "column": 4 }, + "end": { "line": 58, "column": null } + }, + "11": { + "start": { "line": 59, "column": 4 }, + "end": { "line": 61, "column": null } + }, + "12": { + "start": { "line": 62, "column": 4 }, + "end": { "line": 62, "column": null } + }, + "13": { + "start": { "line": 65, "column": 2 }, + "end": { "line": 70, "column": null } + }, + "14": { + "start": { "line": 66, "column": 4 }, + "end": { "line": 68, "column": null } + }, + "15": { + "start": { "line": 69, "column": 4 }, + "end": { "line": 69, "column": null } + }, + "16": { + "start": { "line": 72, "column": 26 }, + "end": { "line": 76, "column": null } + }, + "17": { + "start": { "line": 78, "column": 8 }, + "end": { "line": 78, "column": null } + }, + "18": { + "start": { "line": 79, "column": 24 }, + "end": { "line": 79, "column": null } + }, + "19": { + "start": { "line": 81, "column": 2 }, + "end": { "line": 83, "column": null } + }, + "20": { + "start": { "line": 86, "column": 27 }, + "end": { "line": 86, "column": null } + }, + "21": { + "start": { "line": 88, "column": 2 }, + "end": { "line": 128, "column": null } + }, + "22": { + "start": { "line": 90, "column": 4 }, + "end": { "line": 93, "column": null } + }, + "23": { + "start": { "line": 95, "column": 4 }, + "end": { "line": 95, "column": null } + }, + "24": { + "start": { "line": 98, "column": 4 }, + "end": { "line": 112, "column": null } + }, + "25": { + "start": { "line": 99, "column": 6 }, + "end": { "line": 106, "column": null } + }, + "26": { + "start": { "line": 107, "column": 6 }, + "end": { "line": 107, "column": null } + }, + "27": { + "start": { "line": 109, "column": 6 }, + "end": { "line": 111, "column": null } + }, + "28": { + "start": { "line": 115, "column": 4 }, + "end": { "line": 115, "column": null } + }, + "29": { + "start": { "line": 116, "column": 4 }, + "end": { "line": 122, "column": null } + }, + "30": { + "start": { "line": 124, "column": 4 }, + "end": { "line": 124, "column": null } + }, + "31": { + "start": { "line": 126, "column": 4 }, + "end": { "line": 126, "column": null } + }, + "32": { + "start": { "line": 127, "column": 4 }, + "end": { "line": 127, "column": null } + } + }, + "fnMap": { + "0": { + "name": "(anonymous_0)", + "decl": { + "start": { "line": 16, "column": 23 }, + "end": { "line": 16, "column": null } + }, + "loc": { + "start": { "line": 20, "column": 13 }, + "end": { "line": 29, "column": null } + }, + "line": 20 + }, + "1": { + "name": "(anonymous_1)", + "decl": { + "start": { "line": 46, "column": 31 }, + "end": { "line": 46, "column": null } + }, + "loc": { + "start": { "line": 52, "column": 20 }, + "end": { "line": 129, "column": null } + }, + "line": 52 + } + }, + "branchMap": { + "0": { + "loc": { + "start": { "line": 24, "column": 2 }, + "end": { "line": 26, "column": null } + }, + "type": "if", + "locations": [ + { + "start": { "line": 24, "column": 2 }, + "end": { "line": 26, "column": null } + }, + { "start": {}, "end": {} } + ], + "line": 24 + }, + "1": { + "loc": { + "start": { "line": 57, "column": 2 }, + "end": { "line": 63, "column": null } + }, + "type": "if", + "locations": [ + { + "start": { "line": 57, "column": 2 }, + "end": { "line": 63, "column": null } + }, + { "start": {}, "end": {} } + ], + "line": 57 + }, + "2": { + "loc": { + "start": { "line": 57, "column": 6 }, + "end": { "line": 57, "column": 27 } + }, + "type": "binary-expr", + "locations": [ + { + "start": { "line": 57, "column": 6 }, + "end": { "line": 57, "column": 16 } + }, + { + "start": { "line": 57, "column": 16 }, + "end": { "line": 57, "column": 27 } + } + ], + "line": 57 + }, + "3": { + "loc": { + "start": { "line": 65, "column": 2 }, + "end": { "line": 70, "column": null } + }, + "type": "if", + "locations": [ + { + "start": { "line": 65, "column": 2 }, + "end": { "line": 70, "column": null } + }, + { "start": {}, "end": {} } + ], + "line": 65 + } + }, + "s": { + "0": 1, + "1": 0, + "2": 0, + "3": 0, + "4": 0, + "5": 1, + "6": 4, + "7": 4, + "8": 4, + "9": 4, + "10": 2, + "11": 2, + "12": 2, + "13": 2, + "14": 2, + "15": 2, + "16": 0, + "17": 0, + "18": 0, + "19": 0, + "20": 0, + "21": 0, + "22": 0, + "23": 0, + "24": 0, + "25": 0, + "26": 0, + "27": 0, + "28": 0, + "29": 0, + "30": 0, + "31": 0, + "32": 0 + }, + "f": { "0": 0, "1": 4 }, + "b": { "0": [0, 0], "1": [2, 2], "2": [4, 3], "3": [2, 0] }, + "meta": { + "lastBranch": 4, + "lastFunction": 2, + "lastStatement": 33, + "seen": { + "s:16:23:29:Infinity": 0, + "f:16:23:16:Infinity": 0, + "s:21:16:21:Infinity": 1, + "b:24:2:26:Infinity:undefined:undefined:undefined:undefined": 0, + "s:24:2:26:Infinity": 2, + "s:25:4:25:Infinity": 3, + "s:28:2:28:Infinity": 4, + "s:46:31:129:Infinity": 5, + "f:46:31:46:Infinity": 1, + "s:53:16:53:Infinity": 6, + "s:54:19:54:Infinity": 7, + "s:55:19:55:Infinity": 8, + "b:57:2:63:Infinity:undefined:undefined:undefined:undefined": 1, + "s:57:2:63:Infinity": 9, + "b:57:6:57:16:57:16:57:27": 2, + "s:58:4:58:Infinity": 10, + "s:59:4:61:Infinity": 11, + "s:62:4:62:Infinity": 12, + "b:65:2:70:Infinity:undefined:undefined:undefined:undefined": 3, + "s:65:2:70:Infinity": 13, + "s:66:4:68:Infinity": 14, + "s:69:4:69:Infinity": 15, + "s:72:26:76:Infinity": 16, + "s:78:8:78:Infinity": 17, + "s:79:24:79:Infinity": 18, + "s:81:2:83:Infinity": 19, + "s:86:27:86:Infinity": 20, + "s:88:2:128:Infinity": 21, + "s:90:4:93:Infinity": 22, + "s:95:4:95:Infinity": 23, + "s:98:4:112:Infinity": 24, + "s:99:6:106:Infinity": 25, + "s:107:6:107:Infinity": 26, + "s:109:6:111:Infinity": 27, + "s:115:4:115:Infinity": 28, + "s:116:4:122:Infinity": 29, + "s:124:4:124:Infinity": 30, + "s:126:4:126:Infinity": 31, + "s:127:4:127:Infinity": 32 + } + } + }, + "/Users/danielslovinsky/Documents/projects/alchemy-docs/docs/src/content-indexer/uploaders/redis.ts": { + "path": "/Users/danielslovinsky/Documents/projects/alchemy-docs/docs/src/content-indexer/uploaders/redis.ts", + "statementMap": { + "0": { + "start": { "line": 10, "column": 19 }, + "end": { "line": 18, "column": null } + }, + "1": { + "start": { "line": 11, "column": 2 }, + "end": { "line": 17, "column": null } + }, + "2": { + "start": { "line": 13, "column": 6 }, + "end": { "line": 15, "column": null } + }, + "3": { + "start": { "line": 16, "column": 4 }, + "end": { "line": 16, "column": null } + }, + "4": { + "start": { "line": 29, "column": 28 }, + "end": { "line": 93, "column": null } + }, + "5": { + "start": { "line": 37, "column": 8 }, + "end": { "line": 37, "column": null } + }, + "6": { + "start": { "line": 40, "column": 23 }, + "end": { "line": 40, "column": null } + }, + "7": { + "start": { "line": 41, "column": 27 }, + "end": { "line": 47, "column": null } + }, + "8": { + "start": { "line": 44, "column": 6 }, + "end": { "line": 46, "column": null } + }, + "9": { + "start": { "line": 50, "column": 41 }, + "end": { "line": 50, "column": null } + }, + "10": { + "start": { "line": 52, "column": 2 }, + "end": { "line": 90, "column": null } + }, + "11": { + "start": { "line": 54, "column": 23 }, + "end": { "line": 54, "column": null } + }, + "12": { + "start": { "line": 55, "column": 25 }, + "end": { "line": 55, "column": null } + }, + "13": { + "start": { "line": 57, "column": 10 }, + "end": { "line": 61, "column": null } + }, + "14": { + "start": { "line": 63, "column": 4 }, + "end": { "line": 69, "column": null } + }, + "15": { + "start": { "line": 65, "column": 8 }, + "end": { "line": 67, "column": null } + }, + "16": { + "start": { "line": 70, "column": 2 }, + "end": { "line": 90, "column": null } + }, + "17": { + "start": { "line": 72, "column": 4 }, + "end": { "line": 89, "column": null } + }, + "18": { + "start": { "line": 74, "column": 25 }, + "end": { "line": 74, "column": null } + }, + "19": { + "start": { "line": 75, "column": 24 }, + "end": { "line": 75, "column": null } + }, + "20": { + "start": { "line": 78, "column": 8 }, + "end": { "line": 81, "column": null } + }, + "21": { + "start": { "line": 79, "column": 31 }, + "end": { "line": 79, "column": null } + }, + "22": { + "start": { "line": 80, "column": 10 }, + "end": { "line": 80, "column": null } + }, + "23": { + "start": { "line": 83, "column": 26 }, + "end": { "line": 83, "column": null } + }, + "24": { + "start": { "line": 84, "column": 8 }, + "end": { "line": 84, "column": null } + }, + "25": { + "start": { "line": 85, "column": 8 }, + "end": { "line": 87, "column": null } + }, + "26": { + "start": { "line": 92, "column": 2 }, + "end": { "line": 92, "column": null } + } + }, + "fnMap": { + "0": { + "name": "(anonymous_0)", + "decl": { + "start": { "line": 10, "column": 19 }, + "end": { "line": 10, "column": 20 } + }, + "loc": { + "start": { "line": 10, "column": 54 }, + "end": { "line": 18, "column": null } + }, + "line": 10 + }, + "1": { + "name": "(anonymous_1)", + "decl": { + "start": { "line": 11, "column": 22 }, + "end": { "line": 11, "column": 23 } + }, + "loc": { + "start": { "line": 11, "column": 37 }, + "end": { "line": 17, "column": 5 } + }, + "line": 11 + }, + "2": { + "name": "(anonymous_2)", + "decl": { + "start": { "line": 29, "column": 28 }, + "end": { "line": 29, "column": null } + }, + "loc": { + "start": { "line": 36, "column": 20 }, + "end": { "line": 93, "column": null } + }, + "line": 36 + }, + "3": { + "name": "(anonymous_3)", + "decl": { + "start": { "line": 43, "column": 10 }, + "end": { "line": 43, "column": 16 } + }, + "loc": { + "start": { "line": 43, "column": 16 }, + "end": { "line": 47, "column": 5 } + }, + "line": 43 + }, + "4": { + "name": "(anonymous_4)", + "decl": { + "start": { "line": 64, "column": 70 }, + "end": { "line": 64, "column": 76 } + }, + "loc": { + "start": { "line": 64, "column": 76 }, + "end": { "line": 68, "column": 7 } + }, + "line": 64 + }, + "5": { + "name": "(anonymous_5)", + "decl": { + "start": { "line": 73, "column": 6 }, + "end": { "line": 73, "column": 13 } + }, + "loc": { + "start": { "line": 73, "column": 32 }, + "end": { "line": 88, "column": null } + }, + "line": 73 + } + }, + "branchMap": { + "0": { + "loc": { + "start": { "line": 13, "column": 6 }, + "end": { "line": 15, "column": null } + }, + "type": "cond-expr", + "locations": [ + { + "start": { "line": 14, "column": 10 }, + "end": { "line": 14, "column": null } + }, + { + "start": { "line": 15, "column": 10 }, + "end": { "line": 15, "column": null } + } + ], + "line": 13 + }, + "1": { + "loc": { + "start": { "line": 13, "column": 6 }, + "end": { "line": 13, "column": null } + }, + "type": "binary-expr", + "locations": [ + { + "start": { "line": 13, "column": 6 }, + "end": { "line": 13, "column": 33 } + }, + { + "start": { "line": 13, "column": 33 }, + "end": { "line": 13, "column": null } + } + ], + "line": 13 + }, + "2": { + "loc": { + "start": { "line": 52, "column": 2 }, + "end": { "line": 90, "column": null } + }, + "type": "if", + "locations": [ + { + "start": { "line": 52, "column": 2 }, + "end": { "line": 90, "column": null } + }, + { + "start": { "line": 70, "column": 2 }, + "end": { "line": 90, "column": null } + } + ], + "line": 52 + }, + "3": { + "loc": { + "start": { "line": 52, "column": 6 }, + "end": { "line": 52, "column": 65 } + }, + "type": "binary-expr", + "locations": [ + { + "start": { "line": 52, "column": 6 }, + "end": { "line": 52, "column": 39 } + }, + { + "start": { "line": 52, "column": 39 }, + "end": { "line": 52, "column": 65 } + } + ], + "line": 52 + }, + "4": { + "loc": { + "start": { "line": 70, "column": 2 }, + "end": { "line": 90, "column": null } + }, + "type": "if", + "locations": [ + { + "start": { "line": 70, "column": 2 }, + "end": { "line": 90, "column": null } + }, + { "start": {}, "end": {} } + ], + "line": 70 + }, + "5": { + "loc": { + "start": { "line": 78, "column": 8 }, + "end": { "line": 81, "column": null } + }, + "type": "if", + "locations": [ + { + "start": { "line": 78, "column": 8 }, + "end": { "line": 81, "column": null } + }, + { "start": {}, "end": {} } + ], + "line": 78 + }, + "6": { + "loc": { + "start": { "line": 78, "column": 12 }, + "end": { "line": 78, "column": 65 } + }, + "type": "binary-expr", + "locations": [ + { + "start": { "line": 78, "column": 12 }, + "end": { "line": 78, "column": 33 } + }, + { + "start": { "line": 78, "column": 33 }, + "end": { "line": 78, "column": 65 } + } + ], + "line": 78 + } + }, + "s": { + "0": 1, + "1": 5, + "2": 5, + "3": 5, + "4": 1, + "5": 5, + "6": 5, + "7": 5, + "8": 5, + "9": 5, + "10": 5, + "11": 0, + "12": 0, + "13": 0, + "14": 0, + "15": 0, + "16": 5, + "17": 3, + "18": 5, + "19": 5, + "20": 5, + "21": 0, + "22": 0, + "23": 5, + "24": 5, + "25": 5, + "26": 5 + }, + "f": { "0": 5, "1": 5, "2": 5, "3": 5, "4": 0, "5": 5 }, + "b": { + "0": [0, 5], + "1": [5, 5], + "2": [0, 5], + "3": [5, 1], + "4": [3, 2], + "5": [0, 5], + "6": [5, 0] + }, + "meta": { + "lastBranch": 7, + "lastFunction": 6, + "lastStatement": 27, + "seen": { + "s:10:19:18:Infinity": 0, + "f:10:19:10:20": 0, + "s:11:2:17:Infinity": 1, + "f:11:22:11:23": 1, + "s:13:6:15:Infinity": 2, + "b:14:10:14:Infinity:15:10:15:Infinity": 0, + "b:13:6:13:33:13:33:13:Infinity": 1, + "s:16:4:16:Infinity": 3, + "s:29:28:93:Infinity": 4, + "f:29:28:29:Infinity": 2, + "s:37:8:37:Infinity": 5, + "s:40:23:40:Infinity": 6, + "s:41:27:47:Infinity": 7, + "f:43:10:43:16": 3, + "s:44:6:46:Infinity": 8, + "s:50:41:50:Infinity": 9, + "b:52:2:90:Infinity:70:2:90:Infinity": 2, + "s:52:2:90:Infinity": 10, + "b:52:6:52:39:52:39:52:65": 3, + "s:54:23:54:Infinity": 11, + "s:55:25:55:Infinity": 12, + "s:57:10:61:Infinity": 13, + "s:63:4:69:Infinity": 14, + "f:64:70:64:76": 4, + "s:65:8:67:Infinity": 15, + "b:70:2:90:Infinity:undefined:undefined:undefined:undefined": 4, + "s:70:2:90:Infinity": 16, + "s:72:4:89:Infinity": 17, + "f:73:6:73:13": 5, + "s:74:25:74:Infinity": 18, + "s:75:24:75:Infinity": 19, + "b:78:8:81:Infinity:undefined:undefined:undefined:undefined": 5, + "s:78:8:81:Infinity": 20, + "b:78:12:78:33:78:33:78:65": 6, + "s:79:31:79:Infinity": 21, + "s:80:10:80:Infinity": 22, + "s:83:26:83:Infinity": 23, + "s:84:8:84:Infinity": 24, + "s:85:8:87:Infinity": 25, + "s:92:2:92:Infinity": 26 + } + } + }, + "/Users/danielslovinsky/Documents/projects/alchemy-docs/docs/src/content-indexer/utils/filesystem.ts": { + "path": "/Users/danielslovinsky/Documents/projects/alchemy-docs/docs/src/content-indexer/utils/filesystem.ts", + "statementMap": { + "0": { + "start": { "line": 11, "column": 29 }, + "end": { "line": 21, "column": null } + }, + "1": { + "start": { "line": 14, "column": 2 }, + "end": { "line": 20, "column": null } + }, + "2": { + "start": { "line": 15, "column": 20 }, + "end": { "line": 15, "column": null } + }, + "3": { + "start": { "line": 16, "column": 4 }, + "end": { "line": 16, "column": null } + }, + "4": { + "start": { "line": 18, "column": 4 }, + "end": { "line": 18, "column": null } + }, + "5": { + "start": { "line": 19, "column": 4 }, + "end": { "line": 19, "column": null } + }, + "6": { + "start": { "line": 26, "column": 32 }, + "end": { "line": 43, "column": null } + }, + "7": { + "start": { "line": 29, "column": 2 }, + "end": { "line": 42, "column": null } + }, + "8": { + "start": { "line": 30, "column": 24 }, + "end": { "line": 30, "column": null } + }, + "9": { + "start": { "line": 31, "column": 20 }, + "end": { "line": 31, "column": null } + }, + "10": { + "start": { "line": 33, "column": 4 }, + "end": { "line": 35, "column": null } + }, + "11": { + "start": { "line": 34, "column": 6 }, + "end": { "line": 34, "column": null } + }, + "12": { + "start": { "line": 37, "column": 20 }, + "end": { "line": 37, "column": null } + }, + "13": { + "start": { "line": 38, "column": 4 }, + "end": { "line": 38, "column": null } + }, + "14": { + "start": { "line": 40, "column": 4 }, + "end": { "line": 40, "column": null } + }, + "15": { + "start": { "line": 41, "column": 4 }, + "end": { "line": 41, "column": null } + }, + "16": { + "start": { "line": 48, "column": 32 }, + "end": { "line": 71, "column": null } + }, + "17": { + "start": { "line": 54, "column": 2 }, + "end": { "line": 70, "column": null } + }, + "18": { + "start": { "line": 55, "column": 24 }, + "end": { "line": 55, "column": null } + }, + "19": { + "start": { "line": 57, "column": 4 }, + "end": { "line": 59, "column": null } + }, + "20": { + "start": { "line": 58, "column": 6 }, + "end": { "line": 58, "column": null } + }, + "21": { + "start": { "line": 61, "column": 26 }, + "end": { "line": 61, "column": null } + }, + "22": { + "start": { "line": 63, "column": 4 }, + "end": { "line": 66, "column": null } + }, + "23": { + "start": { "line": 68, "column": 4 }, + "end": { "line": 68, "column": null } + }, + "24": { + "start": { "line": 69, "column": 4 }, + "end": { "line": 69, "column": null } + } + }, + "fnMap": { + "0": { + "name": "(anonymous_0)", + "decl": { + "start": { "line": 11, "column": 29 }, + "end": { "line": 11, "column": null } + }, + "loc": { + "start": { "line": 13, "column": 29 }, + "end": { "line": 21, "column": null } + }, + "line": 13 + }, + "1": { + "name": "(anonymous_1)", + "decl": { + "start": { "line": 26, "column": 32 }, + "end": { "line": 26, "column": null } + }, + "loc": { + "start": { "line": 28, "column": 30 }, + "end": { "line": 43, "column": null } + }, + "line": 28 + }, + "2": { + "name": "(anonymous_2)", + "decl": { + "start": { "line": 48, "column": 32 }, + "end": { "line": 48, "column": null } + }, + "loc": { + "start": { "line": 53, "column": 13 }, + "end": { "line": 71, "column": null } + }, + "line": 53 + } + }, + "branchMap": { + "0": { + "loc": { + "start": { "line": 33, "column": 4 }, + "end": { "line": 35, "column": null } + }, + "type": "if", + "locations": [ + { + "start": { "line": 33, "column": 4 }, + "end": { "line": 35, "column": null } + }, + { "start": {}, "end": {} } + ], + "line": 33 + }, + "1": { + "loc": { + "start": { "line": 57, "column": 4 }, + "end": { "line": 59, "column": null } + }, + "type": "if", + "locations": [ + { + "start": { "line": 57, "column": 4 }, + "end": { "line": 59, "column": null } + }, + { "start": {}, "end": {} } + ], + "line": 57 + } + }, + "s": { + "0": 2, + "1": 0, + "2": 0, + "3": 0, + "4": 0, + "5": 0, + "6": 2, + "7": 0, + "8": 0, + "9": 0, + "10": 0, + "11": 0, + "12": 0, + "13": 0, + "14": 0, + "15": 0, + "16": 2, + "17": 0, + "18": 0, + "19": 0, + "20": 0, + "21": 0, + "22": 0, + "23": 0, + "24": 0 + }, + "f": { "0": 0, "1": 0, "2": 0 }, + "b": { "0": [0, 0], "1": [0, 0] }, + "meta": { + "lastBranch": 2, + "lastFunction": 3, + "lastStatement": 25, + "seen": { + "s:11:29:21:Infinity": 0, + "f:11:29:11:Infinity": 0, + "s:14:2:20:Infinity": 1, + "s:15:20:15:Infinity": 2, + "s:16:4:16:Infinity": 3, + "s:18:4:18:Infinity": 4, + "s:19:4:19:Infinity": 5, + "s:26:32:43:Infinity": 6, + "f:26:32:26:Infinity": 1, + "s:29:2:42:Infinity": 7, + "s:30:24:30:Infinity": 8, + "s:31:20:31:Infinity": 9, + "b:33:4:35:Infinity:undefined:undefined:undefined:undefined": 0, + "s:33:4:35:Infinity": 10, + "s:34:6:34:Infinity": 11, + "s:37:20:37:Infinity": 12, + "s:38:4:38:Infinity": 13, + "s:40:4:40:Infinity": 14, + "s:41:4:41:Infinity": 15, + "s:48:32:71:Infinity": 16, + "f:48:32:48:Infinity": 2, + "s:54:2:70:Infinity": 17, + "s:55:24:55:Infinity": 18, + "b:57:4:59:Infinity:undefined:undefined:undefined:undefined": 1, + "s:57:4:59:Infinity": 19, + "s:58:6:58:Infinity": 20, + "s:61:26:61:Infinity": 21, + "s:63:4:66:Infinity": 22, + "s:68:4:68:Infinity": 23, + "s:69:4:69:Infinity": 24 + } + } + }, + "/Users/danielslovinsky/Documents/projects/alchemy-docs/docs/src/content-indexer/utils/github.ts": { + "path": "/Users/danielslovinsky/Documents/projects/alchemy-docs/docs/src/content-indexer/utils/github.ts", + "statementMap": { + "0": { + "start": { "line": 22, "column": 37 }, + "end": { "line": 27, "column": null } + }, + "1": { + "start": { "line": 29, "column": 39 }, + "end": { "line": 35, "column": null } + }, + "2": { + "start": { "line": 37, "column": 16 }, + "end": { "line": 39, "column": null } + }, + "3": { + "start": { "line": 42, "column": 2 }, + "end": { "line": 42, "column": null } + }, + "4": { + "start": { "line": 52, "column": 2 }, + "end": { "line": 71, "column": null } + }, + "5": { + "start": { "line": 53, "column": 21 }, + "end": { "line": 61, "column": null } + }, + "6": { + "start": { "line": 64, "column": 4 }, + "end": { "line": 64, "column": null } + }, + "7": { + "start": { "line": 66, "column": 4 }, + "end": { "line": 68, "column": null } + }, + "8": { + "start": { "line": 67, "column": 6 }, + "end": { "line": 67, "column": null } + }, + "9": { + "start": { "line": 69, "column": 4 }, + "end": { "line": 69, "column": null } + }, + "10": { + "start": { "line": 70, "column": 4 }, + "end": { "line": 70, "column": null } + }, + "11": { + "start": { "line": 81, "column": 2 }, + "end": { "line": 101, "column": null } + }, + "12": { + "start": { "line": 82, "column": 21 }, + "end": { "line": 87, "column": null } + }, + "13": { + "start": { "line": 90, "column": 4 }, + "end": { "line": 92, "column": null } + }, + "14": { + "start": { "line": 91, "column": 6 }, + "end": { "line": 91, "column": null } + }, + "15": { + "start": { "line": 94, "column": 4 }, + "end": { "line": 94, "column": null } + }, + "16": { + "start": { "line": 96, "column": 4 }, + "end": { "line": 99, "column": null } + }, + "17": { + "start": { "line": 100, "column": 4 }, + "end": { "line": 100, "column": null } + }, + "18": { + "start": { "line": 112, "column": 18 }, + "end": { "line": 112, "column": null } + }, + "19": { + "start": { "line": 116, "column": 2 }, + "end": { "line": 136, "column": null } + }, + "20": { + "start": { "line": 116, "column": 15 }, + "end": { "line": 116, "column": 18 } + }, + "21": { + "start": { "line": 117, "column": 18 }, + "end": { "line": 117, "column": null } + }, + "22": { + "start": { "line": 119, "column": 25 }, + "end": { "line": 124, "column": null } + }, + "23": { + "start": { "line": 121, "column": 24 }, + "end": { "line": 121, "column": null } + }, + "24": { + "start": { "line": 122, "column": 8 }, + "end": { "line": 122, "column": null } + }, + "25": { + "start": { "line": 126, "column": 4 }, + "end": { "line": 130, "column": null } + }, + "26": { + "start": { "line": 127, "column": 6 }, + "end": { "line": 129, "column": null } + }, + "27": { + "start": { "line": 128, "column": 8 }, + "end": { "line": 128, "column": null } + }, + "28": { + "start": { "line": 133, "column": 4 }, + "end": { "line": 135, "column": null } + }, + "29": { + "start": { "line": 134, "column": 6 }, + "end": { "line": 134, "column": null } + }, + "30": { + "start": { "line": 134, "column": 37 }, + "end": { "line": 134, "column": 61 } + }, + "31": { + "start": { "line": 138, "column": 2 }, + "end": { "line": 138, "column": null } + }, + "32": { + "start": { "line": 148, "column": 2 }, + "end": { "line": 163, "column": null } + }, + "33": { + "start": { "line": 149, "column": 4 }, + "end": { "line": 154, "column": null } + }, + "34": { + "start": { "line": 155, "column": 4 }, + "end": { "line": 155, "column": null } + }, + "35": { + "start": { "line": 157, "column": 4 }, + "end": { "line": 159, "column": null } + }, + "36": { + "start": { "line": 158, "column": 6 }, + "end": { "line": 158, "column": null } + }, + "37": { + "start": { "line": 161, "column": 4 }, + "end": { "line": 161, "column": null } + }, + "38": { + "start": { "line": 162, "column": 4 }, + "end": { "line": 162, "column": null } + }, + "39": { + "start": { "line": 173, "column": 2 }, + "end": { "line": 201, "column": null } + }, + "40": { + "start": { "line": 174, "column": 21 }, + "end": { "line": 179, "column": null } + }, + "41": { + "start": { "line": 182, "column": 4 }, + "end": { "line": 184, "column": null } + }, + "42": { + "start": { "line": 183, "column": 6 }, + "end": { "line": 183, "column": null } + }, + "43": { + "start": { "line": 187, "column": 4 }, + "end": { "line": 192, "column": null } + }, + "44": { + "start": { "line": 188, "column": 6 }, + "end": { "line": 191, "column": null } + }, + "45": { + "start": { "line": 194, "column": 4 }, + "end": { "line": 194, "column": null } + }, + "46": { + "start": { "line": 196, "column": 4 }, + "end": { "line": 198, "column": null } + }, + "47": { + "start": { "line": 197, "column": 6 }, + "end": { "line": 197, "column": null } + }, + "48": { + "start": { "line": 199, "column": 4 }, + "end": { "line": 199, "column": null } + }, + "49": { + "start": { "line": 200, "column": 4 }, + "end": { "line": 200, "column": null } + } + }, + "fnMap": { + "0": { + "name": "isRequestError", + "decl": { + "start": { "line": 41, "column": 9 }, + "end": { "line": 41, "column": 24 } + }, + "loc": { + "start": { "line": 41, "column": 63 }, + "end": { "line": 43, "column": null } + }, + "line": 41 + }, + "1": { + "name": "fetchFileFromGitHub", + "decl": { + "start": { "line": 48, "column": 22 }, + "end": { "line": 48, "column": null } + }, + "loc": { + "start": { "line": 51, "column": 26 }, + "end": { "line": 72, "column": null } + }, + "line": 51 + }, + "2": { + "name": "fetchGitHubDirectory", + "decl": { + "start": { "line": 77, "column": 22 }, + "end": { "line": 77, "column": null } + }, + "loc": { + "start": { "line": 80, "column": 35 }, + "end": { "line": 102, "column": null } + }, + "line": 80 + }, + "3": { + "name": "fetchMultipleFiles", + "decl": { + "start": { "line": 107, "column": 22 }, + "end": { "line": 107, "column": null } + }, + "loc": { + "start": { "line": 111, "column": 32 }, + "end": { "line": 139, "column": null } + }, + "line": 111 + }, + "4": { + "name": "(anonymous_4)", + "decl": { + "start": { "line": 120, "column": 16 }, + "end": { "line": 120, "column": 23 } + }, + "loc": { + "start": { "line": 120, "column": 32 }, + "end": { "line": 123, "column": 7 } + }, + "line": 120 + }, + "5": { + "name": "(anonymous_5)", + "decl": { + "start": { "line": 134, "column": 24 }, + "end": { "line": 134, "column": 25 } + }, + "loc": { + "start": { "line": 134, "column": 37 }, + "end": { "line": 134, "column": 61 } + }, + "line": 134 + }, + "6": { + "name": "fileExistsOnGitHub", + "decl": { + "start": { "line": 144, "column": 22 }, + "end": { "line": 144, "column": null } + }, + "loc": { + "start": { "line": 147, "column": 20 }, + "end": { "line": 164, "column": null } + }, + "line": 147 + }, + "7": { + "name": "getGitHubFileMetadata", + "decl": { + "start": { "line": 169, "column": 22 }, + "end": { "line": 169, "column": null } + }, + "loc": { + "start": { "line": 172, "column": 49 }, + "end": { "line": 202, "column": null } + }, + "line": 172 + } + }, + "branchMap": { + "0": { + "loc": { + "start": { "line": 50, "column": 2 }, + "end": { "line": 50, "column": null } + }, + "type": "default-arg", + "locations": [ + { + "start": { "line": 50, "column": 27 }, + "end": { "line": 50, "column": null } + } + ], + "line": 50 + }, + "1": { + "loc": { + "start": { "line": 66, "column": 4 }, + "end": { "line": 68, "column": null } + }, + "type": "if", + "locations": [ + { + "start": { "line": 66, "column": 4 }, + "end": { "line": 68, "column": null } + }, + { "start": {}, "end": {} } + ], + "line": 66 + }, + "2": { + "loc": { + "start": { "line": 66, "column": 8 }, + "end": { "line": 66, "column": 55 } + }, + "type": "binary-expr", + "locations": [ + { + "start": { "line": 66, "column": 8 }, + "end": { "line": 66, "column": 33 } + }, + { + "start": { "line": 66, "column": 33 }, + "end": { "line": 66, "column": 55 } + } + ], + "line": 66 + }, + "3": { + "loc": { + "start": { "line": 79, "column": 2 }, + "end": { "line": 79, "column": null } + }, + "type": "default-arg", + "locations": [ + { + "start": { "line": 79, "column": 27 }, + "end": { "line": 79, "column": null } + } + ], + "line": 79 + }, + "4": { + "loc": { + "start": { "line": 90, "column": 4 }, + "end": { "line": 92, "column": null } + }, + "type": "if", + "locations": [ + { + "start": { "line": 90, "column": 4 }, + "end": { "line": 92, "column": null } + }, + { "start": {}, "end": {} } + ], + "line": 90 + }, + "5": { + "loc": { + "start": { "line": 109, "column": 2 }, + "end": { "line": 109, "column": null } + }, + "type": "default-arg", + "locations": [ + { + "start": { "line": 109, "column": 24 }, + "end": { "line": 109, "column": null } + } + ], + "line": 109 + }, + "6": { + "loc": { + "start": { "line": 110, "column": 2 }, + "end": { "line": 110, "column": null } + }, + "type": "default-arg", + "locations": [ + { + "start": { "line": 110, "column": 27 }, + "end": { "line": 110, "column": null } + } + ], + "line": 110 + }, + "7": { + "loc": { + "start": { "line": 127, "column": 6 }, + "end": { "line": 129, "column": null } + }, + "type": "if", + "locations": [ + { + "start": { "line": 127, "column": 6 }, + "end": { "line": 129, "column": null } + }, + { "start": {}, "end": {} } + ], + "line": 127 + }, + "8": { + "loc": { + "start": { "line": 133, "column": 4 }, + "end": { "line": 135, "column": null } + }, + "type": "if", + "locations": [ + { + "start": { "line": 133, "column": 4 }, + "end": { "line": 135, "column": null } + }, + { "start": {}, "end": {} } + ], + "line": 133 + }, + "9": { + "loc": { + "start": { "line": 146, "column": 2 }, + "end": { "line": 146, "column": null } + }, + "type": "default-arg", + "locations": [ + { + "start": { "line": 146, "column": 27 }, + "end": { "line": 146, "column": null } + } + ], + "line": 146 + }, + "10": { + "loc": { + "start": { "line": 157, "column": 4 }, + "end": { "line": 159, "column": null } + }, + "type": "if", + "locations": [ + { + "start": { "line": 157, "column": 4 }, + "end": { "line": 159, "column": null } + }, + { "start": {}, "end": {} } + ], + "line": 157 + }, + "11": { + "loc": { + "start": { "line": 157, "column": 8 }, + "end": { "line": 157, "column": 55 } + }, + "type": "binary-expr", + "locations": [ + { + "start": { "line": 157, "column": 8 }, + "end": { "line": 157, "column": 33 } + }, + { + "start": { "line": 157, "column": 33 }, + "end": { "line": 157, "column": 55 } + } + ], + "line": 157 + }, + "12": { + "loc": { + "start": { "line": 171, "column": 2 }, + "end": { "line": 171, "column": null } + }, + "type": "default-arg", + "locations": [ + { + "start": { "line": 171, "column": 27 }, + "end": { "line": 171, "column": null } + } + ], + "line": 171 + }, + "13": { + "loc": { + "start": { "line": 182, "column": 4 }, + "end": { "line": 184, "column": null } + }, + "type": "if", + "locations": [ + { + "start": { "line": 182, "column": 4 }, + "end": { "line": 184, "column": null } + }, + { "start": {}, "end": {} } + ], + "line": 182 + }, + "14": { + "loc": { + "start": { "line": 187, "column": 4 }, + "end": { "line": 192, "column": null } + }, + "type": "if", + "locations": [ + { + "start": { "line": 187, "column": 4 }, + "end": { "line": 192, "column": null } + }, + { "start": {}, "end": {} } + ], + "line": 187 + }, + "15": { + "loc": { + "start": { "line": 187, "column": 8 }, + "end": { "line": 187, "column": 41 } + }, + "type": "binary-expr", + "locations": [ + { + "start": { "line": 187, "column": 8 }, + "end": { "line": 187, "column": 25 } + }, + { + "start": { "line": 187, "column": 25 }, + "end": { "line": 187, "column": 41 } + } + ], + "line": 187 + }, + "16": { + "loc": { + "start": { "line": 196, "column": 4 }, + "end": { "line": 198, "column": null } + }, + "type": "if", + "locations": [ + { + "start": { "line": 196, "column": 4 }, + "end": { "line": 198, "column": null } + }, + { "start": {}, "end": {} } + ], + "line": 196 + }, + "17": { + "loc": { + "start": { "line": 196, "column": 8 }, + "end": { "line": 196, "column": 55 } + }, + "type": "binary-expr", + "locations": [ + { + "start": { "line": 196, "column": 8 }, + "end": { "line": 196, "column": 33 } + }, + { + "start": { "line": 196, "column": 33 }, + "end": { "line": 196, "column": 55 } + } + ], + "line": 196 + } + }, + "s": { + "0": 10, + "1": 10, + "2": 10, + "3": 0, + "4": 0, + "5": 0, + "6": 0, + "7": 0, + "8": 0, + "9": 0, + "10": 0, + "11": 0, + "12": 0, + "13": 0, + "14": 0, + "15": 0, + "16": 0, + "17": 0, + "18": 0, + "19": 0, + "20": 0, + "21": 0, + "22": 0, + "23": 0, + "24": 0, + "25": 0, + "26": 0, + "27": 0, + "28": 0, + "29": 0, + "30": 0, + "31": 0, + "32": 0, + "33": 0, + "34": 0, + "35": 0, + "36": 0, + "37": 0, + "38": 0, + "39": 0, + "40": 0, + "41": 0, + "42": 0, + "43": 0, + "44": 0, + "45": 0, + "46": 0, + "47": 0, + "48": 0, + "49": 0 + }, + "f": { "0": 0, "1": 0, "2": 0, "3": 0, "4": 0, "5": 0, "6": 0, "7": 0 }, + "b": { + "0": [0], + "1": [0, 0], + "2": [0, 0], + "3": [0], + "4": [0, 0], + "5": [0], + "6": [0], + "7": [0, 0], + "8": [0, 0], + "9": [0], + "10": [0, 0], + "11": [0, 0], + "12": [0], + "13": [0, 0], + "14": [0, 0], + "15": [0, 0], + "16": [0, 0], + "17": [0, 0] + }, + "meta": { + "lastBranch": 18, + "lastFunction": 8, + "lastStatement": 50, + "seen": { + "s:22:37:27:Infinity": 0, + "s:29:39:35:Infinity": 1, + "s:37:16:39:Infinity": 2, + "f:41:9:41:24": 0, + "s:42:2:42:Infinity": 3, + "f:48:22:48:Infinity": 1, + "b:50:27:50:Infinity": 0, + "s:52:2:71:Infinity": 4, + "s:53:21:61:Infinity": 5, + "s:64:4:64:Infinity": 6, + "b:66:4:68:Infinity:undefined:undefined:undefined:undefined": 1, + "s:66:4:68:Infinity": 7, + "b:66:8:66:33:66:33:66:55": 2, + "s:67:6:67:Infinity": 8, + "s:69:4:69:Infinity": 9, + "s:70:4:70:Infinity": 10, + "f:77:22:77:Infinity": 2, + "b:79:27:79:Infinity": 3, + "s:81:2:101:Infinity": 11, + "s:82:21:87:Infinity": 12, + "b:90:4:92:Infinity:undefined:undefined:undefined:undefined": 4, + "s:90:4:92:Infinity": 13, + "s:91:6:91:Infinity": 14, + "s:94:4:94:Infinity": 15, + "s:96:4:99:Infinity": 16, + "s:100:4:100:Infinity": 17, + "f:107:22:107:Infinity": 3, + "b:109:24:109:Infinity": 5, + "b:110:27:110:Infinity": 6, + "s:112:18:112:Infinity": 18, + "s:116:2:136:Infinity": 19, + "s:116:15:116:18": 20, + "s:117:18:117:Infinity": 21, + "s:119:25:124:Infinity": 22, + "f:120:16:120:23": 4, + "s:121:24:121:Infinity": 23, + "s:122:8:122:Infinity": 24, + "s:126:4:130:Infinity": 25, + "b:127:6:129:Infinity:undefined:undefined:undefined:undefined": 7, + "s:127:6:129:Infinity": 26, + "s:128:8:128:Infinity": 27, + "b:133:4:135:Infinity:undefined:undefined:undefined:undefined": 8, + "s:133:4:135:Infinity": 28, + "s:134:6:134:Infinity": 29, + "f:134:24:134:25": 5, + "s:134:37:134:61": 30, + "s:138:2:138:Infinity": 31, + "f:144:22:144:Infinity": 6, + "b:146:27:146:Infinity": 9, + "s:148:2:163:Infinity": 32, + "s:149:4:154:Infinity": 33, + "s:155:4:155:Infinity": 34, + "b:157:4:159:Infinity:undefined:undefined:undefined:undefined": 10, + "s:157:4:159:Infinity": 35, + "b:157:8:157:33:157:33:157:55": 11, + "s:158:6:158:Infinity": 36, + "s:161:4:161:Infinity": 37, + "s:162:4:162:Infinity": 38, + "f:169:22:169:Infinity": 7, + "b:171:27:171:Infinity": 12, + "s:173:2:201:Infinity": 39, + "s:174:21:179:Infinity": 40, + "b:182:4:184:Infinity:undefined:undefined:undefined:undefined": 13, + "s:182:4:184:Infinity": 41, + "s:183:6:183:Infinity": 42, + "b:187:4:192:Infinity:undefined:undefined:undefined:undefined": 14, + "s:187:4:192:Infinity": 43, + "b:187:8:187:25:187:25:187:41": 15, + "s:188:6:191:Infinity": 44, + "s:194:4:194:Infinity": 45, + "b:196:4:198:Infinity:undefined:undefined:undefined:undefined": 16, + "s:196:4:198:Infinity": 46, + "b:196:8:196:33:196:33:196:55": 17, + "s:197:6:197:Infinity": 47, + "s:199:4:199:Infinity": 48, + "s:200:4:200:Infinity": 49 + } + } + }, + "/Users/danielslovinsky/Documents/projects/alchemy-docs/docs/src/content-indexer/utils/nav-tree-merge.ts": { + "path": "/Users/danielslovinsky/Documents/projects/alchemy-docs/docs/src/content-indexer/utils/nav-tree-merge.ts", + "statementMap": { + "0": { + "start": { "line": 11, "column": 30 }, + "end": { "line": 16, "column": null } + }, + "1": { + "start": { "line": 12, "column": 2 }, + "end": { "line": 14, "column": null } + }, + "2": { + "start": { "line": 13, "column": 4 }, + "end": { "line": 13, "column": null } + }, + "3": { + "start": { "line": 15, "column": 2 }, + "end": { "line": 15, "column": null } + }, + "4": { + "start": { "line": 25, "column": 44 }, + "end": { "line": 39, "column": null } + }, + "5": { + "start": { "line": 28, "column": 2 }, + "end": { "line": 38, "column": null } + }, + "6": { + "start": { "line": 30, "column": 6 }, + "end": { "line": 34, "column": null } + }, + "7": { + "start": { "line": 31, "column": 8 }, + "end": { "line": 31, "column": null } + }, + "8": { + "start": { "line": 33, "column": 8 }, + "end": { "line": 33, "column": null } + }, + "9": { + "start": { "line": 35, "column": 6 }, + "end": { "line": 35, "column": null } + }, + "10": { + "start": { "line": 50, "column": 35 }, + "end": { "line": 93, "column": null } + }, + "11": { + "start": { "line": 55, "column": 2 }, + "end": { "line": 60, "column": null } + }, + "12": { + "start": { "line": 56, "column": 4 }, + "end": { "line": 58, "column": null } + }, + "13": { + "start": { "line": 57, "column": 6 }, + "end": { "line": 57, "column": null } + }, + "14": { + "start": { "line": 59, "column": 4 }, + "end": { "line": 59, "column": null } + }, + "15": { + "start": { "line": 62, "column": 2 }, + "end": { "line": 62, "column": null } + }, + "16": { + "start": { "line": 66, "column": 4 }, + "end": { "line": 66, "column": null } + }, + "17": { + "start": { "line": 69, "column": 25 }, + "end": { "line": 69, "column": null } + }, + "18": { + "start": { "line": 70, "column": 22 }, + "end": { "line": 70, "column": null } + }, + "19": { + "start": { "line": 73, "column": 2 }, + "end": { "line": 77, "column": null } + }, + "20": { + "start": { "line": 74, "column": 4 }, + "end": { "line": 76, "column": null } + }, + "21": { + "start": { "line": 80, "column": 2 }, + "end": { "line": 82, "column": null } + }, + "22": { + "start": { "line": 81, "column": 4 }, + "end": { "line": 81, "column": null } + }, + "23": { + "start": { "line": 83, "column": 2 }, + "end": { "line": 85, "column": null } + }, + "24": { + "start": { "line": 84, "column": 4 }, + "end": { "line": 84, "column": null } + }, + "25": { + "start": { "line": 88, "column": 2 }, + "end": { "line": 92, "column": null } + } + }, + "fnMap": { + "0": { + "name": "(anonymous_0)", + "decl": { + "start": { "line": 11, "column": 30 }, + "end": { "line": 11, "column": 31 } + }, + "loc": { + "start": { "line": 11, "column": 58 }, + "end": { "line": 16, "column": null } + }, + "line": 11 + }, + "1": { + "name": "(anonymous_1)", + "decl": { + "start": { "line": 25, "column": 44 }, + "end": { "line": 25, "column": null } + }, + "loc": { + "start": { "line": 27, "column": 54 }, + "end": { "line": 39, "column": null } + }, + "line": 27 + }, + "2": { + "name": "(anonymous_2)", + "decl": { + "start": { "line": 29, "column": 4 }, + "end": { "line": 29, "column": 5 } + }, + "loc": { + "start": { "line": 29, "column": 19 }, + "end": { "line": 36, "column": null } + }, + "line": 29 + }, + "3": { + "name": "(anonymous_3)", + "decl": { + "start": { "line": 50, "column": 35 }, + "end": { "line": 50, "column": null } + }, + "loc": { + "start": { "line": 54, "column": 21 }, + "end": { "line": 93, "column": null } + }, + "line": 54 + } + }, + "branchMap": { + "0": { + "loc": { + "start": { "line": 12, "column": 2 }, + "end": { "line": 14, "column": null } + }, + "type": "if", + "locations": [ + { + "start": { "line": 12, "column": 2 }, + "end": { "line": 14, "column": null } + }, + { "start": {}, "end": {} } + ], + "line": 12 + }, + "1": { + "loc": { + "start": { "line": 12, "column": 6 }, + "end": { "line": 12, "column": 62 } + }, + "type": "binary-expr", + "locations": [ + { + "start": { "line": 12, "column": 6 }, + "end": { "line": 12, "column": 33 } + }, + { + "start": { "line": 12, "column": 33 }, + "end": { "line": 12, "column": 62 } + } + ], + "line": 12 + }, + "2": { + "loc": { + "start": { "line": 30, "column": 6 }, + "end": { "line": 34, "column": null } + }, + "type": "if", + "locations": [ + { + "start": { "line": 30, "column": 6 }, + "end": { "line": 34, "column": null } + }, + { + "start": { "line": 32, "column": 13 }, + "end": { "line": 34, "column": null } + } + ], + "line": 30 + }, + "3": { + "loc": { + "start": { "line": 55, "column": 2 }, + "end": { "line": 60, "column": null } + }, + "type": "if", + "locations": [ + { + "start": { "line": 55, "column": 2 }, + "end": { "line": 60, "column": null } + }, + { "start": {}, "end": {} } + ], + "line": 55 + }, + "4": { + "loc": { + "start": { "line": 56, "column": 4 }, + "end": { "line": 58, "column": null } + }, + "type": "if", + "locations": [ + { + "start": { "line": 56, "column": 4 }, + "end": { "line": 58, "column": null } + }, + { "start": {}, "end": {} } + ], + "line": 56 + }, + "5": { + "loc": { + "start": { "line": 69, "column": 25 }, + "end": { "line": 69, "column": null } + }, + "type": "cond-expr", + "locations": [ + { + "start": { "line": 69, "column": 50 }, + "end": { "line": 69, "column": 60 } + }, + { + "start": { "line": 69, "column": 60 }, + "end": { "line": 69, "column": null } + } + ], + "line": 69 + }, + "6": { + "loc": { + "start": { "line": 70, "column": 22 }, + "end": { "line": 70, "column": null } + }, + "type": "cond-expr", + "locations": [ + { + "start": { "line": 70, "column": 46 }, + "end": { "line": 70, "column": 56 } + }, + { + "start": { "line": 70, "column": 56 }, + "end": { "line": 70, "column": null } + } + ], + "line": 70 + }, + "7": { + "loc": { + "start": { "line": 73, "column": 2 }, + "end": { "line": 77, "column": null } + }, + "type": "if", + "locations": [ + { + "start": { "line": 73, "column": 2 }, + "end": { "line": 77, "column": null } + }, + { "start": {}, "end": {} } + ], + "line": 73 + }, + "8": { + "loc": { + "start": { "line": 73, "column": 6 }, + "end": { "line": 73, "column": 56 } + }, + "type": "binary-expr", + "locations": [ + { + "start": { "line": 73, "column": 6 }, + "end": { "line": 73, "column": 32 } + }, + { + "start": { "line": 73, "column": 32 }, + "end": { "line": 73, "column": 56 } + } + ], + "line": 73 + }, + "9": { + "loc": { + "start": { "line": 80, "column": 2 }, + "end": { "line": 82, "column": null } + }, + "type": "if", + "locations": [ + { + "start": { "line": 80, "column": 2 }, + "end": { "line": 82, "column": null } + }, + { "start": {}, "end": {} } + ], + "line": 80 + }, + "10": { + "loc": { + "start": { "line": 83, "column": 2 }, + "end": { "line": 85, "column": null } + }, + "type": "if", + "locations": [ + { + "start": { "line": 83, "column": 2 }, + "end": { "line": 85, "column": null } + }, + { "start": {}, "end": {} } + ], + "line": 83 + } + }, + "s": { + "0": 1, + "1": 0, + "2": 0, + "3": 0, + "4": 1, + "5": 0, + "6": 0, + "7": 0, + "8": 0, + "9": 0, + "10": 1, + "11": 0, + "12": 0, + "13": 0, + "14": 0, + "15": 0, + "16": 0, + "17": 0, + "18": 0, + "19": 0, + "20": 0, + "21": 0, + "22": 0, + "23": 0, + "24": 0, + "25": 0 + }, + "f": { "0": 0, "1": 0, "2": 0, "3": 0 }, + "b": { + "0": [0, 0], + "1": [0, 0], + "2": [0, 0], + "3": [0, 0], + "4": [0, 0], + "5": [0, 0], + "6": [0, 0], + "7": [0, 0], + "8": [0, 0], + "9": [0, 0], + "10": [0, 0] + }, + "meta": { + "lastBranch": 11, + "lastFunction": 4, + "lastStatement": 26, + "seen": { + "s:11:30:16:Infinity": 0, + "f:11:30:11:31": 0, + "b:12:2:14:Infinity:undefined:undefined:undefined:undefined": 0, + "s:12:2:14:Infinity": 1, + "b:12:6:12:33:12:33:12:62": 1, + "s:13:4:13:Infinity": 2, + "s:15:2:15:Infinity": 3, + "s:25:44:39:Infinity": 4, + "f:25:44:25:Infinity": 1, + "s:28:2:38:Infinity": 5, + "f:29:4:29:5": 2, + "b:30:6:34:Infinity:32:13:34:Infinity": 2, + "s:30:6:34:Infinity": 6, + "s:31:8:31:Infinity": 7, + "s:33:8:33:Infinity": 8, + "s:35:6:35:Infinity": 9, + "s:50:35:93:Infinity": 10, + "f:50:35:50:Infinity": 3, + "b:55:2:60:Infinity:undefined:undefined:undefined:undefined": 3, + "s:55:2:60:Infinity": 11, + "b:56:4:58:Infinity:undefined:undefined:undefined:undefined": 4, + "s:56:4:58:Infinity": 12, + "s:57:6:57:Infinity": 13, + "s:59:4:59:Infinity": 14, + "s:62:2:62:Infinity": 15, + "s:66:4:66:Infinity": 16, + "s:69:25:69:Infinity": 17, + "b:69:50:69:60:69:60:69:Infinity": 5, + "s:70:22:70:Infinity": 18, + "b:70:46:70:56:70:56:70:Infinity": 6, + "b:73:2:77:Infinity:undefined:undefined:undefined:undefined": 7, + "s:73:2:77:Infinity": 19, + "b:73:6:73:32:73:32:73:56": 8, + "s:74:4:76:Infinity": 20, + "b:80:2:82:Infinity:undefined:undefined:undefined:undefined": 9, + "s:80:2:82:Infinity": 21, + "s:81:4:81:Infinity": 22, + "b:83:2:85:Infinity:undefined:undefined:undefined:undefined": 10, + "s:83:2:85:Infinity": 23, + "s:84:4:84:Infinity": 24, + "s:88:2:92:Infinity": 25 + } + } + }, + "/Users/danielslovinsky/Documents/projects/alchemy-docs/docs/src/content-indexer/utils/navigation-helpers.ts": { + "path": "/Users/danielslovinsky/Documents/projects/alchemy-docs/docs/src/content-indexer/utils/navigation-helpers.ts", + "statementMap": { + "0": { + "start": { "line": 7, "column": 39 }, + "end": { "line": 14, "column": null } + }, + "1": { + "start": { "line": 10, "column": 15 }, + "end": { "line": 14, "column": null } + } + }, + "fnMap": { + "0": { + "name": "(anonymous_0)", + "decl": { + "start": { "line": 7, "column": 39 }, + "end": { "line": 7, "column": null } + }, + "loc": { + "start": { "line": 10, "column": 15 }, + "end": { "line": 14, "column": null } + }, + "line": 10 + } + }, + "branchMap": {}, + "s": { "0": 6, "1": 20 }, + "f": { "0": 20 }, + "b": {}, + "meta": { + "lastBranch": 0, + "lastFunction": 1, + "lastStatement": 2, + "seen": { + "s:7:39:14:Infinity": 0, + "f:7:39:7:Infinity": 0, + "s:10:15:14:Infinity": 1 + } + } + }, + "/Users/danielslovinsky/Documents/projects/alchemy-docs/docs/src/content-indexer/utils/normalization.ts": { + "path": "/Users/danielslovinsky/Documents/projects/alchemy-docs/docs/src/content-indexer/utils/normalization.ts", + "statementMap": { + "0": { + "start": { "line": 8, "column": 29 }, + "end": { "line": 11, "column": null } + }, + "1": { + "start": { "line": 9, "column": 2 }, + "end": { "line": 9, "column": null } + }, + "2": { + "start": { "line": 9, "column": 13 }, + "end": { "line": 9, "column": null } + }, + "3": { + "start": { "line": 10, "column": 2 }, + "end": { "line": 10, "column": null } + }, + "4": { + "start": { "line": 17, "column": 33 }, + "end": { "line": 22, "column": null } + }, + "5": { + "start": { "line": 21, "column": 2 }, + "end": { "line": 21, "column": null } + } + }, + "fnMap": { + "0": { + "name": "(anonymous_0)", + "decl": { + "start": { "line": 8, "column": 29 }, + "end": { "line": 8, "column": 30 } + }, + "loc": { + "start": { "line": 8, "column": 79 }, + "end": { "line": 11, "column": null } + }, + "line": 8 + }, + "1": { + "name": "(anonymous_1)", + "decl": { + "start": { "line": 17, "column": 33 }, + "end": { "line": 17, "column": null } + }, + "loc": { + "start": { "line": 20, "column": 13 }, + "end": { "line": 22, "column": null } + }, + "line": 20 + } + }, + "branchMap": { + "0": { + "loc": { + "start": { "line": 9, "column": 2 }, + "end": { "line": 9, "column": null } + }, + "type": "if", + "locations": [ + { + "start": { "line": 9, "column": 2 }, + "end": { "line": 9, "column": null } + }, + { "start": {}, "end": {} } + ], + "line": 9 + }, + "1": { + "loc": { + "start": { "line": 21, "column": 26 }, + "end": { "line": 21, "column": 54 } + }, + "type": "binary-expr", + "locations": [ + { + "start": { "line": 21, "column": 26 }, + "end": { "line": 21, "column": 50 } + }, + { + "start": { "line": 21, "column": 50 }, + "end": { "line": 21, "column": 54 } + } + ], + "line": 21 + } + }, + "s": { "0": 4, "1": 30, "2": 24, "3": 6, "4": 4, "5": 30 }, + "f": { "0": 30, "1": 30 }, + "b": { "0": [24, 6], "1": [30, 26] }, + "meta": { + "lastBranch": 2, + "lastFunction": 2, + "lastStatement": 6, + "seen": { + "s:8:29:11:Infinity": 0, + "f:8:29:8:30": 0, + "b:9:2:9:Infinity:undefined:undefined:undefined:undefined": 0, + "s:9:2:9:Infinity": 1, + "s:9:13:9:Infinity": 2, + "s:10:2:10:Infinity": 3, + "s:17:33:22:Infinity": 4, + "f:17:33:17:Infinity": 1, + "s:21:2:21:Infinity": 5, + "b:21:26:21:50:21:50:21:54": 1 + } + } + }, + "/Users/danielslovinsky/Documents/projects/alchemy-docs/docs/src/content-indexer/utils/openapi.ts": { + "path": "/Users/danielslovinsky/Documents/projects/alchemy-docs/docs/src/content-indexer/utils/openapi.ts", + "statementMap": { + "0": { + "start": { "line": 18, "column": 23 }, + "end": { "line": 32, "column": null } + }, + "1": { + "start": { "line": 23, "column": 2 }, + "end": { "line": 25, "column": null } + }, + "2": { + "start": { "line": 24, "column": 4 }, + "end": { "line": 24, "column": null } + }, + "3": { + "start": { "line": 27, "column": 2 }, + "end": { "line": 29, "column": null } + }, + "4": { + "start": { "line": 28, "column": 4 }, + "end": { "line": 28, "column": null } + }, + "5": { + "start": { "line": 31, "column": 2 }, + "end": { "line": 31, "column": null } + }, + "6": { + "start": { "line": 38, "column": 33 }, + "end": { "line": 58, "column": null } + }, + "7": { + "start": { "line": 43, "column": 19 }, + "end": { "line": 43, "column": null } + }, + "8": { + "start": { "line": 44, "column": 2 }, + "end": { "line": 46, "column": null } + }, + "9": { + "start": { "line": 45, "column": 4 }, + "end": { "line": 45, "column": null } + }, + "10": { + "start": { "line": 49, "column": 20 }, + "end": { "line": 55, "column": null } + }, + "11": { + "start": { "line": 51, "column": 6 }, + "end": { "line": 54, "column": null } + }, + "12": { + "start": { "line": 57, "column": 2 }, + "end": { "line": 57, "column": null } + }, + "13": { + "start": { "line": 69, "column": 40 }, + "end": { "line": 96, "column": null } + }, + "14": { + "start": { "line": 72, "column": 2 }, + "end": { "line": 95, "column": null } + }, + "15": { + "start": { "line": 73, "column": 4 }, + "end": { "line": 73, "column": null } + }, + "16": { + "start": { "line": 73, "column": 51 }, + "end": { "line": 73, "column": null } + }, + "17": { + "start": { "line": 75, "column": 4 }, + "end": { "line": 94, "column": null } + }, + "18": { + "start": { "line": 78, "column": 11 }, + "end": { "line": 80, "column": null } + }, + "19": { + "start": { "line": 83, "column": 19 }, + "end": { "line": 83, "column": null } + }, + "20": { + "start": { "line": 85, "column": 21 }, + "end": { "line": 85, "column": null } + }, + "21": { + "start": { "line": 86, "column": 20 }, + "end": { "line": 86, "column": null } + }, + "22": { + "start": { "line": 88, "column": 8 }, + "end": { "line": 93, "column": null } + }, + "23": { + "start": { "line": 105, "column": 34 }, + "end": { "line": 123, "column": null } + }, + "24": { + "start": { "line": 110, "column": 20 }, + "end": { "line": 110, "column": null } + }, + "25": { + "start": { "line": 113, "column": 2 }, + "end": { "line": 116, "column": null } + }, + "26": { + "start": { "line": 114, "column": 10 }, + "end": { "line": 114, "column": null } + }, + "27": { + "start": { "line": 115, "column": 4 }, + "end": { "line": 115, "column": null } + }, + "28": { + "start": { "line": 119, "column": 8 }, + "end": { "line": 119, "column": null } + }, + "29": { + "start": { "line": 120, "column": 2 }, + "end": { "line": 120, "column": null } + }, + "30": { + "start": { "line": 122, "column": 2 }, + "end": { "line": 122, "column": null } + }, + "31": { + "start": { "line": 129, "column": 39 }, + "end": { "line": 149, "column": null } + }, + "32": { + "start": { "line": 134, "column": 19 }, + "end": { "line": 134, "column": null } + }, + "33": { + "start": { "line": 135, "column": 2 }, + "end": { "line": 137, "column": null } + }, + "34": { + "start": { "line": 136, "column": 4 }, + "end": { "line": 136, "column": null } + }, + "35": { + "start": { "line": 139, "column": 21 }, + "end": { "line": 139, "column": null } + }, + "36": { + "start": { "line": 140, "column": 2 }, + "end": { "line": 142, "column": null } + }, + "37": { + "start": { "line": 141, "column": 4 }, + "end": { "line": 141, "column": null } + }, + "38": { + "start": { "line": 144, "column": 23 }, + "end": { "line": 144, "column": null } + }, + "39": { + "start": { "line": 145, "column": 22 }, + "end": { "line": 145, "column": null } + }, + "40": { + "start": { "line": 146, "column": 18 }, + "end": { "line": 146, "column": null } + }, + "41": { + "start": { "line": 148, "column": 2 }, + "end": { "line": 148, "column": null } + } + }, + "fnMap": { + "0": { + "name": "(anonymous_0)", + "decl": { + "start": { "line": 18, "column": 23 }, + "end": { "line": 18, "column": null } + }, + "loc": { + "start": { "line": 22, "column": 13 }, + "end": { "line": 32, "column": null } + }, + "line": 22 + }, + "1": { + "name": "(anonymous_1)", + "decl": { + "start": { "line": 38, "column": 33 }, + "end": { "line": 38, "column": null } + }, + "loc": { + "start": { "line": 42, "column": 13 }, + "end": { "line": 58, "column": null } + }, + "line": 42 + }, + "2": { + "name": "(anonymous_2)", + "decl": { + "start": { "line": 50, "column": 4 }, + "end": { "line": 50, "column": 5 } + }, + "loc": { + "start": { "line": 51, "column": 6 }, + "end": { "line": 54, "column": null } + }, + "line": 51 + }, + "3": { + "name": "(anonymous_3)", + "decl": { + "start": { "line": 69, "column": 40 }, + "end": { "line": 69, "column": null } + }, + "loc": { + "start": { "line": 71, "column": 27 }, + "end": { "line": 96, "column": null } + }, + "line": 71 + }, + "4": { + "name": "(anonymous_4)", + "decl": { + "start": { "line": 72, "column": 39 }, + "end": { "line": 72, "column": 40 } + }, + "loc": { + "start": { "line": 72, "column": 61 }, + "end": { "line": 95, "column": 3 } + }, + "line": 72 + }, + "5": { + "name": "(anonymous_5)", + "decl": { + "start": { "line": 77, "column": 8 }, + "end": { "line": 77, "column": 9 } + }, + "loc": { + "start": { "line": 78, "column": 11 }, + "end": { "line": 80, "column": null } + }, + "line": 78 + }, + "6": { + "name": "(anonymous_6)", + "decl": { + "start": { "line": 82, "column": 11 }, + "end": { "line": 82, "column": 12 } + }, + "loc": { + "start": { "line": 82, "column": 36 }, + "end": { "line": 94, "column": 7 } + }, + "line": 82 + }, + "7": { + "name": "(anonymous_7)", + "decl": { + "start": { "line": 105, "column": 34 }, + "end": { "line": 105, "column": null } + }, + "loc": { + "start": { "line": 109, "column": 13 }, + "end": { "line": 123, "column": null } + }, + "line": 109 + }, + "8": { + "name": "(anonymous_8)", + "decl": { + "start": { "line": 129, "column": 39 }, + "end": { "line": 129, "column": null } + }, + "loc": { + "start": { "line": 133, "column": 13 }, + "end": { "line": 149, "column": null } + }, + "line": 133 + } + }, + "branchMap": { + "0": { + "loc": { + "start": { "line": 23, "column": 2 }, + "end": { "line": 25, "column": null } + }, + "type": "if", + "locations": [ + { + "start": { "line": 23, "column": 2 }, + "end": { "line": 25, "column": null } + }, + { "start": {}, "end": {} } + ], + "line": 23 + }, + "1": { + "loc": { + "start": { "line": 23, "column": 6 }, + "end": { "line": 23, "column": 59 } + }, + "type": "binary-expr", + "locations": [ + { + "start": { "line": 23, "column": 6 }, + "end": { "line": 23, "column": 36 } + }, + { + "start": { "line": 23, "column": 36 }, + "end": { "line": 23, "column": 59 } + } + ], + "line": 23 + }, + "2": { + "loc": { + "start": { "line": 27, "column": 2 }, + "end": { "line": 29, "column": null } + }, + "type": "if", + "locations": [ + { + "start": { "line": 27, "column": 2 }, + "end": { "line": 29, "column": null } + }, + { "start": {}, "end": {} } + ], + "line": 27 + }, + "3": { + "loc": { + "start": { "line": 27, "column": 6 }, + "end": { "line": 27, "column": 51 } + }, + "type": "binary-expr", + "locations": [ + { + "start": { "line": 27, "column": 6 }, + "end": { "line": 27, "column": 32 } + }, + { + "start": { "line": 27, "column": 32 }, + "end": { "line": 27, "column": 51 } + } + ], + "line": 27 + }, + "4": { + "loc": { + "start": { "line": 44, "column": 2 }, + "end": { "line": 46, "column": null } + }, + "type": "if", + "locations": [ + { + "start": { "line": 44, "column": 2 }, + "end": { "line": 46, "column": null } + }, + { "start": {}, "end": {} } + ], + "line": 44 + }, + "5": { + "loc": { + "start": { "line": 44, "column": 6 }, + "end": { "line": 44, "column": 49 } + }, + "type": "binary-expr", + "locations": [ + { + "start": { "line": 44, "column": 6 }, + "end": { "line": 44, "column": 19 } + }, + { + "start": { "line": 44, "column": 19 }, + "end": { "line": 44, "column": 49 } + } + ], + "line": 44 + }, + "6": { + "loc": { + "start": { "line": 51, "column": 6 }, + "end": { "line": 54, "column": null } + }, + "type": "binary-expr", + "locations": [ + { + "start": { "line": 51, "column": 6 }, + "end": { "line": 51, "column": null } + }, + { + "start": { "line": 52, "column": 6 }, + "end": { "line": 52, "column": null } + }, + { + "start": { "line": 53, "column": 6 }, + "end": { "line": 53, "column": null } + }, + { + "start": { "line": 54, "column": 7 }, + "end": { "line": 54, "column": null } + } + ], + "line": 51 + }, + "7": { + "loc": { + "start": { "line": 57, "column": 9 }, + "end": { "line": 57, "column": null } + }, + "type": "binary-expr", + "locations": [ + { + "start": { "line": 57, "column": 9 }, + "end": { "line": 57, "column": 31 } + }, + { + "start": { "line": 57, "column": 31 }, + "end": { "line": 57, "column": null } + } + ], + "line": 57 + }, + "8": { + "loc": { + "start": { "line": 73, "column": 4 }, + "end": { "line": 73, "column": null } + }, + "type": "if", + "locations": [ + { + "start": { "line": 73, "column": 4 }, + "end": { "line": 73, "column": null } + }, + { "start": {}, "end": {} } + ], + "line": 73 + }, + "9": { + "loc": { + "start": { "line": 73, "column": 8 }, + "end": { "line": 73, "column": 51 } + }, + "type": "binary-expr", + "locations": [ + { + "start": { "line": 73, "column": 8 }, + "end": { "line": 73, "column": 21 } + }, + { + "start": { "line": 73, "column": 21 }, + "end": { "line": 73, "column": 51 } + } + ], + "line": 73 + }, + "10": { + "loc": { + "start": { "line": 78, "column": 11 }, + "end": { "line": 80, "column": null } + }, + "type": "binary-expr", + "locations": [ + { + "start": { "line": 78, "column": 11 }, + "end": { "line": 78, "column": null } + }, + { + "start": { "line": 79, "column": 10 }, + "end": { "line": 79, "column": null } + }, + { + "start": { "line": 80, "column": 10 }, + "end": { "line": 80, "column": null } + } + ], + "line": 78 + }, + "11": { + "loc": { + "start": { "line": 85, "column": 21 }, + "end": { "line": 85, "column": null } + }, + "type": "cond-expr", + "locations": [ + { + "start": { "line": 85, "column": 46 }, + "end": { "line": 85, "column": 56 } + }, + { + "start": { "line": 85, "column": 56 }, + "end": { "line": 85, "column": null } + } + ], + "line": 85 + }, + "12": { + "loc": { + "start": { "line": 113, "column": 2 }, + "end": { "line": 116, "column": null } + }, + "type": "if", + "locations": [ + { + "start": { "line": 113, "column": 2 }, + "end": { "line": 116, "column": null } + }, + { "start": {}, "end": {} } + ], + "line": 113 + }, + "13": { + "loc": { + "start": { "line": 135, "column": 2 }, + "end": { "line": 137, "column": null } + }, + "type": "if", + "locations": [ + { + "start": { "line": 135, "column": 2 }, + "end": { "line": 137, "column": null } + }, + { "start": {}, "end": {} } + ], + "line": 135 + }, + "14": { + "loc": { + "start": { "line": 135, "column": 6 }, + "end": { "line": 135, "column": 49 } + }, + "type": "binary-expr", + "locations": [ + { + "start": { "line": 135, "column": 6 }, + "end": { "line": 135, "column": 19 } + }, + { + "start": { "line": 135, "column": 19 }, + "end": { "line": 135, "column": 49 } + } + ], + "line": 135 + }, + "15": { + "loc": { + "start": { "line": 140, "column": 2 }, + "end": { "line": 142, "column": null } + }, + "type": "if", + "locations": [ + { + "start": { "line": 140, "column": 2 }, + "end": { "line": 142, "column": null } + }, + { "start": {}, "end": {} } + ], + "line": 140 + }, + "16": { + "loc": { + "start": { "line": 140, "column": 6 }, + "end": { "line": 140, "column": 51 } + }, + "type": "binary-expr", + "locations": [ + { + "start": { "line": 140, "column": 6 }, + "end": { "line": 140, "column": 20 } + }, + { + "start": { "line": 140, "column": 20 }, + "end": { "line": 140, "column": 51 } + } + ], + "line": 140 + }, + "17": { + "loc": { + "start": { "line": 148, "column": 9 }, + "end": { "line": 148, "column": null } + }, + "type": "binary-expr", + "locations": [ + { + "start": { "line": 148, "column": 9 }, + "end": { "line": 148, "column": 24 } + }, + { + "start": { "line": 148, "column": 24 }, + "end": { "line": 148, "column": 35 } + }, + { + "start": { "line": 148, "column": 35 }, + "end": { "line": 148, "column": null } + } + ], + "line": 148 + } + }, + "s": { + "0": 5, + "1": 25, + "2": 23, + "3": 2, + "4": 1, + "5": 1, + "6": 5, + "7": 17, + "8": 17, + "9": 1, + "10": 16, + "11": 16, + "12": 16, + "13": 5, + "14": 22, + "15": 23, + "16": 0, + "17": 23, + "18": 27, + "19": 25, + "20": 25, + "21": 25, + "22": 25, + "23": 5, + "24": 33, + "25": 33, + "26": 11, + "27": 11, + "28": 33, + "29": 33, + "30": 33, + "31": 5, + "32": 17, + "33": 17, + "34": 1, + "35": 16, + "36": 16, + "37": 1, + "38": 15, + "39": 15, + "40": 15, + "41": 15 + }, + "f": { + "0": 25, + "1": 17, + "2": 16, + "3": 22, + "4": 23, + "5": 27, + "6": 25, + "7": 33, + "8": 17 + }, + "b": { + "0": [23, 2], + "1": [25, 23], + "2": [1, 1], + "3": [2, 1], + "4": [1, 16], + "5": [17, 16], + "6": [16, 16, 16, 16], + "7": [16, 11], + "8": [0, 23], + "9": [23, 23], + "10": [27, 25, 25], + "11": [7, 18], + "12": [11, 22], + "13": [1, 16], + "14": [17, 16], + "15": [1, 15], + "16": [16, 15], + "17": [15, 11, 9] + }, + "meta": { + "lastBranch": 18, + "lastFunction": 9, + "lastStatement": 42, + "seen": { + "s:18:23:32:Infinity": 0, + "f:18:23:18:Infinity": 0, + "b:23:2:25:Infinity:undefined:undefined:undefined:undefined": 0, + "s:23:2:25:Infinity": 1, + "b:23:6:23:36:23:36:23:59": 1, + "s:24:4:24:Infinity": 2, + "b:27:2:29:Infinity:undefined:undefined:undefined:undefined": 2, + "s:27:2:29:Infinity": 3, + "b:27:6:27:32:27:32:27:51": 3, + "s:28:4:28:Infinity": 4, + "s:31:2:31:Infinity": 5, + "s:38:33:58:Infinity": 6, + "f:38:33:38:Infinity": 1, + "s:43:19:43:Infinity": 7, + "b:44:2:46:Infinity:undefined:undefined:undefined:undefined": 4, + "s:44:2:46:Infinity": 8, + "b:44:6:44:19:44:19:44:49": 5, + "s:45:4:45:Infinity": 9, + "s:49:20:55:Infinity": 10, + "f:50:4:50:5": 2, + "s:51:6:54:Infinity": 11, + "b:51:6:51:Infinity:52:6:52:Infinity:53:6:53:Infinity:54:7:54:Infinity": 6, + "s:57:2:57:Infinity": 12, + "b:57:9:57:31:57:31:57:Infinity": 7, + "s:69:40:96:Infinity": 13, + "f:69:40:69:Infinity": 3, + "s:72:2:95:Infinity": 14, + "f:72:39:72:40": 4, + "b:73:4:73:Infinity:undefined:undefined:undefined:undefined": 8, + "s:73:4:73:Infinity": 15, + "b:73:8:73:21:73:21:73:51": 9, + "s:73:51:73:Infinity": 16, + "s:75:4:94:Infinity": 17, + "f:77:8:77:9": 5, + "s:78:11:80:Infinity": 18, + "b:78:11:78:Infinity:79:10:79:Infinity:80:10:80:Infinity": 10, + "f:82:11:82:12": 6, + "s:83:19:83:Infinity": 19, + "s:85:21:85:Infinity": 20, + "b:85:46:85:56:85:56:85:Infinity": 11, + "s:86:20:86:Infinity": 21, + "s:88:8:93:Infinity": 22, + "s:105:34:123:Infinity": 23, + "f:105:34:105:Infinity": 7, + "s:110:20:110:Infinity": 24, + "b:113:2:116:Infinity:undefined:undefined:undefined:undefined": 12, + "s:113:2:116:Infinity": 25, + "s:114:10:114:Infinity": 26, + "s:115:4:115:Infinity": 27, + "s:119:8:119:Infinity": 28, + "s:120:2:120:Infinity": 29, + "s:122:2:122:Infinity": 30, + "s:129:39:149:Infinity": 31, + "f:129:39:129:Infinity": 8, + "s:134:19:134:Infinity": 32, + "b:135:2:137:Infinity:undefined:undefined:undefined:undefined": 13, + "s:135:2:137:Infinity": 33, + "b:135:6:135:19:135:19:135:49": 14, + "s:136:4:136:Infinity": 34, + "s:139:21:139:Infinity": 35, + "b:140:2:142:Infinity:undefined:undefined:undefined:undefined": 15, + "s:140:2:142:Infinity": 36, + "b:140:6:140:20:140:20:140:51": 16, + "s:141:4:141:Infinity": 37, + "s:144:23:144:Infinity": 38, + "s:145:22:145:Infinity": 39, + "s:146:18:146:Infinity": 40, + "s:148:2:148:Infinity": 41, + "b:148:9:148:24:148:24:148:35:148:35:148:Infinity": 17 + } + } + }, + "/Users/danielslovinsky/Documents/projects/alchemy-docs/docs/src/content-indexer/utils/openrpc.ts": { + "path": "/Users/danielslovinsky/Documents/projects/alchemy-docs/docs/src/content-indexer/utils/openrpc.ts", + "statementMap": { + "0": { + "start": { "line": 6, "column": 34 }, + "end": { "line": 13, "column": null } + }, + "1": { + "start": { "line": 7, "column": 2 }, + "end": { "line": 11, "column": null } + } + }, + "fnMap": { + "0": { + "name": "(anonymous_0)", + "decl": { + "start": { "line": 6, "column": 34 }, + "end": { "line": 6, "column": 35 } + }, + "loc": { + "start": { "line": 6, "column": 74 }, + "end": { "line": 13, "column": null } + }, + "line": 6 + } + }, + "branchMap": { + "0": { + "loc": { + "start": { "line": 8, "column": 4 }, + "end": { "line": 11, "column": null } + }, + "type": "binary-expr", + "locations": [ + { + "start": { "line": 8, "column": 4 }, + "end": { "line": 8, "column": null } + }, + { + "start": { "line": 9, "column": 4 }, + "end": { "line": 9, "column": null } + }, + { + "start": { "line": 10, "column": 4 }, + "end": { "line": 10, "column": null } + }, + { + "start": { "line": 11, "column": 4 }, + "end": { "line": 11, "column": null } + } + ], + "line": 8 + } + }, + "s": { "0": 5, "1": 16 }, + "f": { "0": 16 }, + "b": { "0": [16, 14, 13, 11] }, + "meta": { + "lastBranch": 1, + "lastFunction": 1, + "lastStatement": 2, + "seen": { + "s:6:34:13:Infinity": 0, + "f:6:34:6:35": 0, + "s:7:2:11:Infinity": 1, + "b:8:4:8:Infinity:9:4:9:Infinity:10:4:10:Infinity:11:4:11:Infinity": 0 + } + } + }, + "/Users/danielslovinsky/Documents/projects/alchemy-docs/docs/src/content-indexer/utils/test-factories.ts": { + "path": "/Users/danielslovinsky/Documents/projects/alchemy-docs/docs/src/content-indexer/utils/test-factories.ts", + "statementMap": { + "0": { + "start": { "line": 10, "column": 34 }, + "end": { "line": 20, "column": null } + }, + "1": { + "start": { "line": 12, "column": 19 }, + "end": { "line": 20, "column": null } + }, + "2": { + "start": { "line": 25, "column": 34 }, + "end": { "line": 35, "column": null } + }, + "3": { + "start": { "line": 27, "column": 19 }, + "end": { "line": 35, "column": null } + }, + "4": { + "start": { "line": 40, "column": 33 }, + "end": { "line": 49, "column": null } + }, + "5": { + "start": { "line": 42, "column": 18 }, + "end": { "line": 49, "column": null } + } + }, + "fnMap": { + "0": { + "name": "(anonymous_0)", + "decl": { + "start": { "line": 10, "column": 34 }, + "end": { "line": 10, "column": null } + }, + "loc": { + "start": { "line": 12, "column": 19 }, + "end": { "line": 20, "column": null } + }, + "line": 12 + }, + "1": { + "name": "(anonymous_1)", + "decl": { + "start": { "line": 25, "column": 34 }, + "end": { "line": 25, "column": null } + }, + "loc": { + "start": { "line": 27, "column": 19 }, + "end": { "line": 35, "column": null } + }, + "line": 27 + }, + "2": { + "name": "(anonymous_2)", + "decl": { + "start": { "line": 40, "column": 33 }, + "end": { "line": 40, "column": null } + }, + "loc": { + "start": { "line": 42, "column": 18 }, + "end": { "line": 49, "column": null } + }, + "line": 42 + } + }, + "branchMap": { + "0": { + "loc": { + "start": { "line": 11, "column": 2 }, + "end": { "line": 11, "column": null } + }, + "type": "default-arg", + "locations": [ + { + "start": { "line": 11, "column": 36 }, + "end": { "line": 11, "column": null } + } + ], + "line": 11 + }, + "1": { + "loc": { + "start": { "line": 26, "column": 2 }, + "end": { "line": 26, "column": null } + }, + "type": "default-arg", + "locations": [ + { + "start": { "line": 26, "column": 36 }, + "end": { "line": 26, "column": null } + } + ], + "line": 26 + }, + "2": { + "loc": { + "start": { "line": 41, "column": 2 }, + "end": { "line": 41, "column": null } + }, + "type": "default-arg", + "locations": [ + { + "start": { "line": 41, "column": 35 }, + "end": { "line": 41, "column": null } + } + ], + "line": 41 + } + }, + "s": { "0": 9, "1": 19, "2": 9, "3": 11, "4": 9, "5": 16 }, + "f": { "0": 19, "1": 11, "2": 16 }, + "b": { "0": [19], "1": [11], "2": [16] }, + "meta": { + "lastBranch": 3, + "lastFunction": 3, + "lastStatement": 6, + "seen": { + "s:10:34:20:Infinity": 0, + "f:10:34:10:Infinity": 0, + "s:12:19:20:Infinity": 1, + "b:11:36:11:Infinity": 0, + "s:25:34:35:Infinity": 2, + "f:25:34:25:Infinity": 1, + "s:27:19:35:Infinity": 3, + "b:26:36:26:Infinity": 1, + "s:40:33:49:Infinity": 4, + "f:40:33:40:Infinity": 2, + "s:42:18:49:Infinity": 5, + "b:41:35:41:Infinity": 2 + } + } + }, + "/Users/danielslovinsky/Documents/projects/alchemy-docs/docs/src/content-indexer/utils/truncate-record.ts": { + "path": "/Users/danielslovinsky/Documents/projects/alchemy-docs/docs/src/content-indexer/utils/truncate-record.ts", + "statementMap": { + "0": { + "start": { "line": 5, "column": 25 }, + "end": { "line": 5, "column": null } + }, + "1": { + "start": { "line": 6, "column": 21 }, + "end": { "line": 6, "column": null } + }, + "2": { + "start": { "line": 7, "column": 23 }, + "end": { "line": 7, "column": null } + }, + "3": { + "start": { "line": 13, "column": 30 }, + "end": { "line": 77, "column": null } + }, + "4": { + "start": { "line": 15, "column": 8 }, + "end": { "line": 15, "column": null } + }, + "5": { + "start": { "line": 16, "column": 17 }, + "end": { "line": 16, "column": null } + }, + "6": { + "start": { "line": 18, "column": 25 }, + "end": { "line": 18, "column": null } + }, + "7": { + "start": { "line": 19, "column": 22 }, + "end": { "line": 19, "column": null } + }, + "8": { + "start": { "line": 21, "column": 2 }, + "end": { "line": 23, "column": null } + }, + "9": { + "start": { "line": 22, "column": 4 }, + "end": { "line": 22, "column": null } + }, + "10": { + "start": { "line": 26, "column": 31 }, + "end": { "line": 26, "column": null } + }, + "11": { + "start": { "line": 27, "column": 24 }, + "end": { "line": 30, "column": null } + }, + "12": { + "start": { "line": 32, "column": 2 }, + "end": { "line": 37, "column": null } + }, + "13": { + "start": { "line": 39, "column": 2 }, + "end": { "line": 43, "column": null } + }, + "14": { + "start": { "line": 40, "column": 4 }, + "end": { "line": 42, "column": null } + }, + "15": { + "start": { "line": 47, "column": 25 }, + "end": { "line": 47, "column": null } + }, + "16": { + "start": { "line": 48, "column": 39 }, + "end": { "line": 48, "column": null } + }, + "17": { + "start": { "line": 49, "column": 21 }, + "end": { "line": 49, "column": null } + }, + "18": { + "start": { "line": 50, "column": 19 }, + "end": { "line": 50, "column": null } + }, + "19": { + "start": { "line": 52, "column": 2 }, + "end": { "line": 64, "column": null } + }, + "20": { + "start": { "line": 54, "column": 10 }, + "end": { "line": 54, "column": null } + }, + "21": { + "start": { "line": 57, "column": 23 }, + "end": { "line": 57, "column": null } + }, + "22": { + "start": { "line": 58, "column": 29 }, + "end": { "line": 58, "column": null } + }, + "23": { + "start": { "line": 59, "column": 4 }, + "end": { "line": 59, "column": null } + }, + "24": { + "start": { "line": 61, "column": 4 }, + "end": { "line": 61, "column": null } + }, + "25": { + "start": { "line": 62, "column": 4 }, + "end": { "line": 62, "column": null } + }, + "26": { + "start": { "line": 63, "column": 4 }, + "end": { "line": 63, "column": null } + }, + "27": { + "start": { "line": 66, "column": 2 }, + "end": { "line": 70, "column": null } + }, + "28": { + "start": { "line": 67, "column": 4 }, + "end": { "line": 69, "column": null } + }, + "29": { + "start": { "line": 72, "column": 2 }, + "end": { "line": 74, "column": null } + }, + "30": { + "start": { "line": 76, "column": 2 }, + "end": { "line": 76, "column": null } + } + }, + "fnMap": { + "0": { + "name": "(anonymous_0)", + "decl": { + "start": { "line": 13, "column": 30 }, + "end": { "line": 13, "column": 31 } + }, + "loc": { + "start": { "line": 13, "column": 75 }, + "end": { "line": 77, "column": null } + }, + "line": 13 + } + }, + "branchMap": { + "0": { + "loc": { + "start": { "line": 21, "column": 2 }, + "end": { "line": 23, "column": null } + }, + "type": "if", + "locations": [ + { + "start": { "line": 21, "column": 2 }, + "end": { "line": 23, "column": null } + }, + { "start": {}, "end": {} } + ], + "line": 21 + }, + "1": { + "loc": { + "start": { "line": 39, "column": 2 }, + "end": { "line": 43, "column": null } + }, + "type": "if", + "locations": [ + { + "start": { "line": 39, "column": 2 }, + "end": { "line": 43, "column": null } + }, + { "start": {}, "end": {} } + ], + "line": 39 + }, + "2": { + "loc": { + "start": { "line": 52, "column": 9 }, + "end": { "line": 52, "column": 73 } + }, + "type": "binary-expr", + "locations": [ + { + "start": { "line": 52, "column": 9 }, + "end": { "line": 52, "column": 44 } + }, + { + "start": { "line": 52, "column": 44 }, + "end": { "line": 52, "column": 73 } + } + ], + "line": 52 + }, + "3": { + "loc": { + "start": { "line": 66, "column": 2 }, + "end": { "line": 70, "column": null } + }, + "type": "if", + "locations": [ + { + "start": { "line": 66, "column": 2 }, + "end": { "line": 70, "column": null } + }, + { "start": {}, "end": {} } + ], + "line": 66 + }, + "4": { + "loc": { + "start": { "line": 73, "column": 108 }, + "end": { "line": 73, "column": 135 } + }, + "type": "cond-expr", + "locations": [ + { + "start": { "line": 73, "column": 127 }, + "end": { "line": 73, "column": 132 } + }, + { + "start": { "line": 73, "column": 132 }, + "end": { "line": 73, "column": 135 } + } + ], + "line": 73 + } + }, + "s": { + "0": 2, + "1": 2, + "2": 2, + "3": 2, + "4": 6, + "5": 6, + "6": 6, + "7": 6, + "8": 6, + "9": 2, + "10": 4, + "11": 4, + "12": 4, + "13": 4, + "14": 1, + "15": 3, + "16": 3, + "17": 3, + "18": 3, + "19": 3, + "20": 3, + "21": 3, + "22": 3, + "23": 3, + "24": 3, + "25": 3, + "26": 3, + "27": 3, + "28": 0, + "29": 3, + "30": 6 + }, + "f": { "0": 6 }, + "b": { "0": [2, 4], "1": [1, 3], "2": [3, 3], "3": [0, 3], "4": [3, 0] }, + "meta": { + "lastBranch": 5, + "lastFunction": 1, + "lastStatement": 31, + "seen": { + "s:5:25:5:Infinity": 0, + "s:6:21:6:Infinity": 1, + "s:7:23:7:Infinity": 2, + "s:13:30:77:Infinity": 3, + "f:13:30:13:31": 0, + "s:15:8:15:Infinity": 4, + "s:16:17:16:Infinity": 5, + "s:18:25:18:Infinity": 6, + "s:19:22:19:Infinity": 7, + "b:21:2:23:Infinity:undefined:undefined:undefined:undefined": 0, + "s:21:2:23:Infinity": 8, + "s:22:4:22:Infinity": 9, + "s:26:31:26:Infinity": 10, + "s:27:24:30:Infinity": 11, + "s:32:2:37:Infinity": 12, + "b:39:2:43:Infinity:undefined:undefined:undefined:undefined": 1, + "s:39:2:43:Infinity": 13, + "s:40:4:42:Infinity": 14, + "s:47:25:47:Infinity": 15, + "s:48:39:48:Infinity": 16, + "s:49:21:49:Infinity": 17, + "s:50:19:50:Infinity": 18, + "s:52:2:64:Infinity": 19, + "b:52:9:52:44:52:44:52:73": 2, + "s:54:10:54:Infinity": 20, + "s:57:23:57:Infinity": 21, + "s:58:29:58:Infinity": 22, + "s:59:4:59:Infinity": 23, + "s:61:4:61:Infinity": 24, + "s:62:4:62:Infinity": 25, + "s:63:4:63:Infinity": 26, + "b:66:2:70:Infinity:undefined:undefined:undefined:undefined": 3, + "s:66:2:70:Infinity": 27, + "s:67:4:69:Infinity": 28, + "s:72:2:74:Infinity": 29, + "b:73:127:73:132:73:132:73:135": 4, + "s:76:2:76:Infinity": 30 + } + } + }, + "/Users/danielslovinsky/Documents/projects/alchemy-docs/docs/src/content-indexer/visitors/index.ts": { + "path": "/Users/danielslovinsky/Documents/projects/alchemy-docs/docs/src/content-indexer/visitors/index.ts", + "statementMap": { + "0": { + "start": { "line": 49, "column": 35 }, + "end": { "line": 76, "column": null } + }, + "1": { + "start": { "line": 50, "column": 19 }, + "end": { "line": 50, "column": null } + }, + "2": { + "start": { "line": 53, "column": 2 }, + "end": { "line": 55, "column": null } + }, + "3": { + "start": { "line": 54, "column": 4 }, + "end": { "line": 54, "column": null } + }, + "4": { + "start": { "line": 58, "column": 2 }, + "end": { "line": 60, "column": null } + }, + "5": { + "start": { "line": 59, "column": 4 }, + "end": { "line": 59, "column": null } + }, + "6": { + "start": { "line": 62, "column": 2 }, + "end": { "line": 64, "column": null } + }, + "7": { + "start": { "line": 63, "column": 4 }, + "end": { "line": 63, "column": null } + }, + "8": { + "start": { "line": 66, "column": 2 }, + "end": { "line": 68, "column": null } + }, + "9": { + "start": { "line": 67, "column": 4 }, + "end": { "line": 67, "column": null } + }, + "10": { + "start": { "line": 70, "column": 2 }, + "end": { "line": 72, "column": null } + }, + "11": { + "start": { "line": 71, "column": 4 }, + "end": { "line": 71, "column": null } + }, + "12": { + "start": { "line": 75, "column": 2 }, + "end": { "line": 75, "column": null } + } + }, + "fnMap": { + "0": { + "name": "(anonymous_0)", + "decl": { + "start": { "line": 49, "column": 35 }, + "end": { "line": 49, "column": 36 } + }, + "loc": { + "start": { "line": 49, "column": 77 }, + "end": { "line": 76, "column": null } + }, + "line": 49 + } + }, + "branchMap": { + "0": { + "loc": { + "start": { "line": 53, "column": 2 }, + "end": { "line": 55, "column": null } + }, + "type": "if", + "locations": [ + { + "start": { "line": 53, "column": 2 }, + "end": { "line": 55, "column": null } + }, + { "start": {}, "end": {} } + ], + "line": 53 + }, + "1": { + "loc": { + "start": { "line": 58, "column": 2 }, + "end": { "line": 60, "column": null } + }, + "type": "if", + "locations": [ + { + "start": { "line": 58, "column": 2 }, + "end": { "line": 60, "column": null } + }, + { "start": {}, "end": {} } + ], + "line": 58 + }, + "2": { + "loc": { + "start": { "line": 62, "column": 2 }, + "end": { "line": 64, "column": null } + }, + "type": "if", + "locations": [ + { + "start": { "line": 62, "column": 2 }, + "end": { "line": 64, "column": null } + }, + { "start": {}, "end": {} } + ], + "line": 62 + }, + "3": { + "loc": { + "start": { "line": 66, "column": 2 }, + "end": { "line": 68, "column": null } + }, + "type": "if", + "locations": [ + { + "start": { "line": 66, "column": 2 }, + "end": { "line": 68, "column": null } + }, + { "start": {}, "end": {} } + ], + "line": 66 + }, + "4": { + "loc": { + "start": { "line": 70, "column": 2 }, + "end": { "line": 72, "column": null } + }, + "type": "if", + "locations": [ + { + "start": { "line": 70, "column": 2 }, + "end": { "line": 72, "column": null } + }, + { "start": {}, "end": {} } + ], + "line": 70 + } + }, + "s": { + "0": 2, + "1": 20, + "2": 20, + "3": 1, + "4": 19, + "5": 2, + "6": 17, + "7": 13, + "8": 4, + "9": 3, + "10": 1, + "11": 1, + "12": 0 + }, + "f": { "0": 20 }, + "b": { "0": [1, 19], "1": [2, 17], "2": [13, 4], "3": [3, 1], "4": [1, 0] }, + "meta": { + "lastBranch": 5, + "lastFunction": 1, + "lastStatement": 13, + "seen": { + "s:49:35:76:Infinity": 0, + "f:49:35:49:36": 0, + "s:50:19:50:Infinity": 1, + "b:53:2:55:Infinity:undefined:undefined:undefined:undefined": 0, + "s:53:2:55:Infinity": 2, + "s:54:4:54:Infinity": 3, + "b:58:2:60:Infinity:undefined:undefined:undefined:undefined": 1, + "s:58:2:60:Infinity": 4, + "s:59:4:59:Infinity": 5, + "b:62:2:64:Infinity:undefined:undefined:undefined:undefined": 2, + "s:62:2:64:Infinity": 6, + "s:63:4:63:Infinity": 7, + "b:66:2:68:Infinity:undefined:undefined:undefined:undefined": 3, + "s:66:2:68:Infinity": 8, + "s:67:4:67:Infinity": 9, + "b:70:2:72:Infinity:undefined:undefined:undefined:undefined": 4, + "s:70:2:72:Infinity": 10, + "s:71:4:71:Infinity": 11, + "s:75:2:75:Infinity": 12 + } + } + }, + "/Users/danielslovinsky/Documents/projects/alchemy-docs/docs/src/content-indexer/visitors/visit-api-reference.ts": { + "path": "/Users/danielslovinsky/Documents/projects/alchemy-docs/docs/src/content-indexer/visitors/visit-api-reference.ts", + "statementMap": { + "0": { + "start": { "line": 24, "column": 33 }, + "end": { "line": 74, "column": null } + }, + "1": { + "start": { "line": 25, "column": 56 }, + "end": { "line": 25, "column": null } + }, + "2": { + "start": { "line": 28, "column": 18 }, + "end": { "line": 28, "column": null } + }, + "3": { + "start": { "line": 29, "column": 21 }, + "end": { "line": 29, "column": null } + }, + "4": { + "start": { "line": 30, "column": 19 }, + "end": { "line": 30, "column": null } + }, + "5": { + "start": { "line": 31, "column": 19 }, + "end": { "line": 31, "column": null } + }, + "6": { + "start": { "line": 32, "column": 22 }, + "end": { "line": 32, "column": null } + }, + "7": { + "start": { "line": 35, "column": 25 }, + "end": { "line": 37, "column": null } + }, + "8": { + "start": { "line": 40, "column": 17 }, + "end": { "line": 40, "column": null } + }, + "9": { + "start": { "line": 41, "column": 2 }, + "end": { "line": 46, "column": null } + }, + "10": { + "start": { "line": 42, "column": 4 }, + "end": { "line": 44, "column": null } + }, + "11": { + "start": { "line": 45, "column": 4 }, + "end": { "line": 45, "column": null } + }, + "12": { + "start": { "line": 48, "column": 38 }, + "end": { "line": 48, "column": null } + }, + "13": { + "start": { "line": 51, "column": 2 }, + "end": { "line": 73, "column": null } + }, + "14": { + "start": { "line": 53, "column": 6 }, + "end": { "line": 61, "column": null } + }, + "15": { + "start": { "line": 64, "column": 6 }, + "end": { "line": 72, "column": null } + } + }, + "fnMap": { + "0": { + "name": "(anonymous_0)", + "decl": { + "start": { "line": 24, "column": 33 }, + "end": { "line": 24, "column": 34 } + }, + "loc": { + "start": { "line": 24, "column": 78 }, + "end": { "line": 74, "column": null } + }, + "line": 24 + } + }, + "branchMap": { + "0": { + "loc": { + "start": { "line": 29, "column": 21 }, + "end": { "line": 29, "column": null } + }, + "type": "binary-expr", + "locations": [ + { + "start": { "line": 29, "column": 21 }, + "end": { "line": 29, "column": 39 } + }, + { + "start": { "line": 29, "column": 31 }, + "end": { "line": 29, "column": null } + } + ], + "line": 29 + }, + "1": { + "loc": { + "start": { "line": 30, "column": 19 }, + "end": { "line": 30, "column": null } + }, + "type": "binary-expr", + "locations": [ + { + "start": { "line": 30, "column": 19 }, + "end": { "line": 30, "column": 45 } + }, + { + "start": { "line": 30, "column": 45 }, + "end": { "line": 30, "column": null } + } + ], + "line": 30 + }, + "2": { + "loc": { + "start": { "line": 31, "column": 19 }, + "end": { "line": 31, "column": null } + }, + "type": "binary-expr", + "locations": [ + { + "start": { "line": 31, "column": 19 }, + "end": { "line": 31, "column": 39 } + }, + { + "start": { "line": 31, "column": 39 }, + "end": { "line": 31, "column": null } + } + ], + "line": 31 + }, + "3": { + "loc": { + "start": { "line": 32, "column": 22 }, + "end": { "line": 32, "column": null } + }, + "type": "binary-expr", + "locations": [ + { + "start": { "line": 32, "column": 22 }, + "end": { "line": 32, "column": 45 } + }, + { + "start": { "line": 32, "column": 45 }, + "end": { "line": 32, "column": null } + } + ], + "line": 32 + }, + "4": { + "loc": { + "start": { "line": 35, "column": 25 }, + "end": { "line": 37, "column": null } + }, + "type": "cond-expr", + "locations": [ + { + "start": { "line": 36, "column": 6 }, + "end": { "line": 36, "column": null } + }, + { + "start": { "line": 37, "column": 6 }, + "end": { "line": 37, "column": null } + } + ], + "line": 35 + }, + "5": { + "loc": { + "start": { "line": 41, "column": 2 }, + "end": { "line": 46, "column": null } + }, + "type": "if", + "locations": [ + { + "start": { "line": 41, "column": 2 }, + "end": { "line": 46, "column": null } + }, + { "start": {}, "end": {} } + ], + "line": 41 + }, + "6": { + "loc": { + "start": { "line": 51, "column": 2 }, + "end": { "line": 73, "column": null } + }, + "type": "switch", + "locations": [ + { + "start": { "line": 52, "column": 4 }, + "end": { "line": 61, "column": null } + }, + { + "start": { "line": 63, "column": 4 }, + "end": { "line": 72, "column": null } + } + ], + "line": 51 + } + }, + "s": { + "0": 3, + "1": 8, + "2": 8, + "3": 8, + "4": 8, + "5": 8, + "6": 8, + "7": 8, + "8": 8, + "9": 8, + "10": 1, + "11": 1, + "12": 7, + "13": 7, + "14": 6, + "15": 1 + }, + "f": { "0": 8 }, + "b": { + "0": [8, 7], + "1": [8, 7], + "2": [8, 7], + "3": [8, 7], + "4": [1, 7], + "5": [1, 7], + "6": [7, 1] + }, + "meta": { + "lastBranch": 7, + "lastFunction": 1, + "lastStatement": 16, + "seen": { + "s:24:33:74:Infinity": 0, + "f:24:33:24:34": 0, + "s:25:56:25:Infinity": 1, + "s:28:18:28:Infinity": 2, + "s:29:21:29:Infinity": 3, + "b:29:21:29:39:29:31:29:Infinity": 0, + "s:30:19:30:Infinity": 4, + "b:30:19:30:45:30:45:30:Infinity": 1, + "s:31:19:31:Infinity": 5, + "b:31:19:31:39:31:39:31:Infinity": 2, + "s:32:22:32:Infinity": 6, + "b:32:22:32:45:32:45:32:Infinity": 3, + "s:35:25:37:Infinity": 7, + "b:36:6:36:Infinity:37:6:37:Infinity": 4, + "s:40:17:40:Infinity": 8, + "b:41:2:46:Infinity:undefined:undefined:undefined:undefined": 5, + "s:41:2:46:Infinity": 9, + "s:42:4:44:Infinity": 10, + "s:45:4:45:Infinity": 11, + "s:48:38:48:Infinity": 12, + "b:52:4:61:Infinity:63:4:72:Infinity": 6, + "s:51:2:73:Infinity": 13, + "s:53:6:61:Infinity": 14, + "s:64:6:72:Infinity": 15 + } + } + }, + "/Users/danielslovinsky/Documents/projects/alchemy-docs/docs/src/content-indexer/visitors/visit-link.ts": { + "path": "/Users/danielslovinsky/Documents/projects/alchemy-docs/docs/src/content-indexer/visitors/visit-link.ts", + "statementMap": { + "0": { + "start": { "line": 14, "column": 25 }, + "end": { "line": 25, "column": null } + }, + "1": { + "start": { "line": 15, "column": 29 }, + "end": { "line": 15, "column": null } + }, + "2": { + "start": { "line": 17, "column": 2 }, + "end": { "line": 24, "column": null } + } + }, + "fnMap": { + "0": { + "name": "(anonymous_0)", + "decl": { + "start": { "line": 14, "column": 25 }, + "end": { "line": 14, "column": 26 } + }, + "loc": { + "start": { "line": 14, "column": 71 }, + "end": { "line": 25, "column": null } + }, + "line": 14 + } + }, + "branchMap": {}, + "s": { "0": 3, "1": 5, "2": 5 }, + "f": { "0": 5 }, + "b": {}, + "meta": { + "lastBranch": 0, + "lastFunction": 1, + "lastStatement": 3, + "seen": { + "s:14:25:25:Infinity": 0, + "f:14:25:14:26": 0, + "s:15:29:15:Infinity": 1, + "s:17:2:24:Infinity": 2 + } + } + }, + "/Users/danielslovinsky/Documents/projects/alchemy-docs/docs/src/content-indexer/visitors/visit-page.ts": { + "path": "/Users/danielslovinsky/Documents/projects/alchemy-docs/docs/src/content-indexer/visitors/visit-page.ts", + "statementMap": { + "0": { + "start": { "line": 24, "column": 25 }, + "end": { "line": 79, "column": null } + }, + "1": { + "start": { "line": 34, "column": 17 }, + "end": { "line": 34, "column": null } + }, + "2": { + "start": { "line": 35, "column": 8 }, + "end": { "line": 35, "column": null } + }, + "3": { + "start": { "line": 36, "column": 18 }, + "end": { "line": 36, "column": null } + }, + "4": { + "start": { "line": 38, "column": 26 }, + "end": { "line": 41, "column": null } + }, + "5": { + "start": { "line": 43, "column": 20 }, + "end": { "line": 43, "column": null } + }, + "6": { + "start": { "line": 46, "column": 23 }, + "end": { "line": 55, "column": null } + }, + "7": { + "start": { "line": 58, "column": 39 }, + "end": { "line": 64, "column": null } + }, + "8": { + "start": { "line": 67, "column": 2 }, + "end": { "line": 76, "column": null } + }, + "9": { + "start": { "line": 68, "column": 18 }, + "end": { "line": 68, "column": null } + }, + "10": { + "start": { "line": 69, "column": 4 }, + "end": { "line": 75, "column": null } + }, + "11": { + "start": { "line": 78, "column": 2 }, + "end": { "line": 78, "column": null } + } + }, + "fnMap": { + "0": { + "name": "(anonymous_0)", + "decl": { + "start": { "line": 24, "column": 25 }, + "end": { "line": 24, "column": 26 } + }, + "loc": { + "start": { "line": 32, "column": 40 }, + "end": { "line": 79, "column": null } + }, + "line": 32 + } + }, + "branchMap": { + "0": { + "loc": { + "start": { "line": 36, "column": 18 }, + "end": { "line": 36, "column": null } + }, + "type": "binary-expr", + "locations": [ + { + "start": { "line": 36, "column": 18 }, + "end": { "line": 36, "column": 35 } + }, + { + "start": { "line": 36, "column": 27 }, + "end": { "line": 36, "column": null } + } + ], + "line": 36 + }, + "1": { + "loc": { + "start": { "line": 50, "column": 14 }, + "end": { "line": 52, "column": null } + }, + "type": "cond-expr", + "locations": [ + { + "start": { "line": 51, "column": 11 }, + "end": { "line": 51, "column": null } + }, + { + "start": { "line": 52, "column": 11 }, + "end": { "line": 52, "column": null } + } + ], + "line": 50 + }, + "2": { + "loc": { + "start": { "line": 58, "column": 39 }, + "end": { "line": 64, "column": null } + }, + "type": "cond-expr", + "locations": [ + { + "start": { "line": 59, "column": 6 }, + "end": { "line": 59, "column": null } + }, + { + "start": { "line": 60, "column": 6 }, + "end": { "line": 64, "column": null } + } + ], + "line": 58 + }, + "3": { + "loc": { + "start": { "line": 67, "column": 2 }, + "end": { "line": 76, "column": null } + }, + "type": "if", + "locations": [ + { + "start": { "line": 67, "column": 2 }, + "end": { "line": 76, "column": null } + }, + { "start": {}, "end": {} } + ], + "line": 67 + }, + "4": { + "loc": { + "start": { "line": 67, "column": 6 }, + "end": { "line": 67, "column": 25 } + }, + "type": "binary-expr", + "locations": [ + { + "start": { "line": 67, "column": 6 }, + "end": { "line": 67, "column": 16 } + }, + { + "start": { "line": 67, "column": 16 }, + "end": { "line": 67, "column": 25 } + } + ], + "line": 67 + }, + "5": { + "loc": { + "start": { "line": 68, "column": 18 }, + "end": { "line": 68, "column": null } + }, + "type": "binary-expr", + "locations": [ + { + "start": { "line": 68, "column": 18 }, + "end": { "line": 68, "column": 46 } + }, + { + "start": { "line": 68, "column": 46 }, + "end": { "line": 68, "column": null } + } + ], + "line": 68 + } + }, + "s": { + "0": 3, + "1": 22, + "2": 22, + "3": 22, + "4": 22, + "5": 22, + "6": 22, + "7": 22, + "8": 22, + "9": 4, + "10": 4, + "11": 22 + }, + "f": { "0": 22 }, + "b": { + "0": [22, 21], + "1": [1, 21], + "2": [2, 20], + "3": [4, 18], + "4": [22, 5], + "5": [4, 1] + }, + "meta": { + "lastBranch": 6, + "lastFunction": 1, + "lastStatement": 12, + "seen": { + "s:24:25:79:Infinity": 0, + "f:24:25:24:26": 0, + "s:34:17:34:Infinity": 1, + "s:35:8:35:Infinity": 2, + "s:36:18:36:Infinity": 3, + "b:36:18:36:35:36:27:36:Infinity": 0, + "s:38:26:41:Infinity": 4, + "s:43:20:43:Infinity": 5, + "s:46:23:55:Infinity": 6, + "b:51:11:51:Infinity:52:11:52:Infinity": 1, + "s:58:39:64:Infinity": 7, + "b:59:6:59:Infinity:60:6:64:Infinity": 2, + "b:67:2:76:Infinity:undefined:undefined:undefined:undefined": 3, + "s:67:2:76:Infinity": 8, + "b:67:6:67:16:67:16:67:25": 4, + "s:68:18:68:Infinity": 9, + "b:68:18:68:46:68:46:68:Infinity": 5, + "s:69:4:75:Infinity": 10, + "s:78:2:78:Infinity": 11 + } + } + }, + "/Users/danielslovinsky/Documents/projects/alchemy-docs/docs/src/content-indexer/visitors/visit-section.ts": { + "path": "/Users/danielslovinsky/Documents/projects/alchemy-docs/docs/src/content-indexer/visitors/visit-section.ts", + "statementMap": { + "0": { + "start": { "line": 30, "column": 28 }, + "end": { "line": 145, "column": null } + }, + "1": { + "start": { "line": 42, "column": 6 }, + "end": { "line": 42, "column": null } + }, + "2": { + "start": { "line": 43, "column": 25 }, + "end": { "line": 43, "column": null } + }, + "3": { + "start": { "line": 44, "column": 19 }, + "end": { "line": 44, "column": null } + }, + "4": { + "start": { "line": 48, "column": 34 }, + "end": { "line": 48, "column": null } + }, + "5": { + "start": { "line": 51, "column": 2 }, + "end": { "line": 84, "column": null } + }, + "6": { + "start": { "line": 52, "column": 19 }, + "end": { "line": 52, "column": null } + }, + "7": { + "start": { "line": 53, "column": 10 }, + "end": { "line": 53, "column": null } + }, + "8": { + "start": { "line": 54, "column": 4 }, + "end": { "line": 54, "column": null } + }, + "9": { + "start": { "line": 56, "column": 31 }, + "end": { "line": 60, "column": null } + }, + "10": { + "start": { "line": 62, "column": 22 }, + "end": { "line": 62, "column": null } + }, + "11": { + "start": { "line": 63, "column": 4 }, + "end": { "line": 63, "column": null } + }, + "12": { + "start": { "line": 66, "column": 4 }, + "end": { "line": 71, "column": null } + }, + "13": { + "start": { "line": 74, "column": 4 }, + "end": { "line": 83, "column": null } + }, + "14": { + "start": { "line": 75, "column": 21 }, + "end": { "line": 75, "column": null } + }, + "15": { + "start": { "line": 76, "column": 6 }, + "end": { "line": 82, "column": null } + }, + "16": { + "start": { "line": 87, "column": 27 }, + "end": { "line": 91, "column": null } + }, + "17": { + "start": { "line": 94, "column": 34 }, + "end": { "line": 99, "column": null } + }, + "18": { + "start": { "line": 103, "column": 37 }, + "end": { "line": 108, "column": null } + }, + "19": { + "start": { "line": 111, "column": 25 }, + "end": { "line": 113, "column": null } + }, + "20": { + "start": { "line": 116, "column": 23 }, + "end": { "line": 123, "column": null } + }, + "21": { + "start": { "line": 117, "column": 4 }, + "end": { "line": 122, "column": null } + }, + "22": { + "start": { "line": 126, "column": 2 }, + "end": { "line": 128, "column": null } + }, + "23": { + "start": { "line": 127, "column": 4 }, + "end": { "line": 127, "column": null } + }, + "24": { + "start": { "line": 131, "column": 30 }, + "end": { "line": 134, "column": null } + }, + "25": { + "start": { "line": 132, "column": 21 }, + "end": { "line": 132, "column": 35 } + }, + "26": { + "start": { "line": 134, "column": 41 }, + "end": { "line": 134, "column": 60 } + }, + "27": { + "start": { "line": 137, "column": 2 }, + "end": { "line": 139, "column": null } + }, + "28": { + "start": { "line": 138, "column": 4 }, + "end": { "line": 138, "column": null } + }, + "29": { + "start": { "line": 142, "column": 2 }, + "end": { "line": 142, "column": null } + }, + "30": { + "start": { "line": 144, "column": 2 }, + "end": { "line": 144, "column": null } + } + }, + "fnMap": { + "0": { + "name": "(anonymous_0)", + "decl": { + "start": { "line": 30, "column": 28 }, + "end": { "line": 30, "column": null } + }, + "loc": { + "start": { "line": 33, "column": 20 }, + "end": { "line": 145, "column": null } + }, + "line": 33 + }, + "1": { + "name": "(anonymous_1)", + "decl": { + "start": { "line": 116, "column": 48 }, + "end": { "line": 116, "column": 49 } + }, + "loc": { + "start": { "line": 117, "column": 4 }, + "end": { "line": 122, "column": null } + }, + "line": 117 + }, + "2": { + "name": "(anonymous_2)", + "decl": { + "start": { "line": 126, "column": 23 }, + "end": { "line": 126, "column": 24 } + }, + "loc": { + "start": { "line": 126, "column": 35 }, + "end": { "line": 128, "column": 3 } + }, + "line": 126 + }, + "3": { + "name": "(anonymous_3)", + "decl": { + "start": { "line": 132, "column": 9 }, + "end": { "line": 132, "column": 10 } + }, + "loc": { + "start": { "line": 132, "column": 21 }, + "end": { "line": 132, "column": 35 } + }, + "line": 132 + }, + "4": { + "name": "(anonymous_4)", + "decl": { + "start": { "line": 134, "column": 12 }, + "end": { "line": 134, "column": 13 } + }, + "loc": { + "start": { "line": 134, "column": 41 }, + "end": { "line": 134, "column": 60 } + }, + "line": 134 + } + }, + "branchMap": { + "0": { + "loc": { + "start": { "line": 43, "column": 25 }, + "end": { "line": 43, "column": null } + }, + "type": "binary-expr", + "locations": [ + { + "start": { "line": 43, "column": 25 }, + "end": { "line": 43, "column": 45 } + }, + { + "start": { "line": 43, "column": 37 }, + "end": { "line": 43, "column": null } + } + ], + "line": 43 + }, + "1": { + "loc": { + "start": { "line": 44, "column": 19 }, + "end": { "line": 44, "column": null } + }, + "type": "binary-expr", + "locations": [ + { + "start": { "line": 44, "column": 19 }, + "end": { "line": 44, "column": 47 } + }, + { + "start": { "line": 44, "column": 47 }, + "end": { "line": 44, "column": null } + } + ], + "line": 44 + }, + "2": { + "loc": { + "start": { "line": 51, "column": 2 }, + "end": { "line": 84, "column": null } + }, + "type": "if", + "locations": [ + { + "start": { "line": 51, "column": 2 }, + "end": { "line": 84, "column": null } + }, + { "start": {}, "end": {} } + ], + "line": 51 + }, + "3": { + "loc": { + "start": { "line": 69, "column": 14 }, + "end": { "line": 69, "column": null } + }, + "type": "cond-expr", + "locations": [ + { + "start": { "line": 69, "column": 31 }, + "end": { "line": 69, "column": 47 } + }, + { + "start": { "line": 69, "column": 47 }, + "end": { "line": 69, "column": null } + } + ], + "line": 69 + }, + "4": { + "loc": { + "start": { "line": 74, "column": 4 }, + "end": { "line": 83, "column": null } + }, + "type": "if", + "locations": [ + { + "start": { "line": 74, "column": 4 }, + "end": { "line": 83, "column": null } + }, + { "start": {}, "end": {} } + ], + "line": 74 + }, + "5": { + "loc": { + "start": { "line": 75, "column": 21 }, + "end": { "line": 75, "column": null } + }, + "type": "binary-expr", + "locations": [ + { + "start": { "line": 75, "column": 21 }, + "end": { "line": 75, "column": 60 } + }, + { + "start": { "line": 75, "column": 60 }, + "end": { "line": 75, "column": null } + } + ], + "line": 75 + }, + "6": { + "loc": { + "start": { "line": 111, "column": 25 }, + "end": { "line": 113, "column": null } + }, + "type": "cond-expr", + "locations": [ + { + "start": { "line": 112, "column": 6 }, + "end": { "line": 112, "column": null } + }, + { + "start": { "line": 113, "column": 6 }, + "end": { "line": 113, "column": null } + } + ], + "line": 111 + }, + "7": { + "loc": { + "start": { "line": 137, "column": 2 }, + "end": { "line": 139, "column": null } + }, + "type": "if", + "locations": [ + { + "start": { "line": 137, "column": 2 }, + "end": { "line": 139, "column": null } + }, + { "start": {}, "end": {} } + ], + "line": 137 + }, + "8": { + "loc": { + "start": { "line": 137, "column": 6 }, + "end": { "line": 137, "column": 51 } + }, + "type": "binary-expr", + "locations": [ + { + "start": { "line": 137, "column": 6 }, + "end": { "line": 137, "column": 31 } + }, + { + "start": { "line": 137, "column": 31 }, + "end": { "line": 137, "column": 51 } + } + ], + "line": 137 + } + }, + "s": { + "0": 2, + "1": 12, + "2": 12, + "3": 12, + "4": 12, + "5": 12, + "6": 1, + "7": 1, + "8": 1, + "9": 1, + "10": 1, + "11": 1, + "12": 1, + "13": 1, + "14": 1, + "15": 1, + "16": 12, + "17": 12, + "18": 12, + "19": 12, + "20": 12, + "21": 15, + "22": 12, + "23": 15, + "24": 12, + "25": 15, + "26": 15, + "27": 12, + "28": 1, + "29": 11, + "30": 11 + }, + "f": { "0": 12, "1": 15, "2": 15, "3": 15, "4": 15 }, + "b": { + "0": [12, 11], + "1": [12, 11], + "2": [1, 11], + "3": [0, 1], + "4": [1, 0], + "5": [1, 0], + "6": [1, 11], + "7": [1, 11], + "8": [12, 12] + }, + "meta": { + "lastBranch": 9, + "lastFunction": 5, + "lastStatement": 31, + "seen": { + "s:30:28:145:Infinity": 0, + "f:30:28:30:Infinity": 0, + "s:42:6:42:Infinity": 1, + "s:43:25:43:Infinity": 2, + "b:43:25:43:45:43:37:43:Infinity": 0, + "s:44:19:44:Infinity": 3, + "b:44:19:44:47:44:47:44:Infinity": 1, + "s:48:34:48:Infinity": 4, + "b:51:2:84:Infinity:undefined:undefined:undefined:undefined": 2, + "s:51:2:84:Infinity": 5, + "s:52:19:52:Infinity": 6, + "s:53:10:53:Infinity": 7, + "s:54:4:54:Infinity": 8, + "s:56:31:60:Infinity": 9, + "s:62:22:62:Infinity": 10, + "s:63:4:63:Infinity": 11, + "s:66:4:71:Infinity": 12, + "b:69:31:69:47:69:47:69:Infinity": 3, + "b:74:4:83:Infinity:undefined:undefined:undefined:undefined": 4, + "s:74:4:83:Infinity": 13, + "s:75:21:75:Infinity": 14, + "b:75:21:75:60:75:60:75:Infinity": 5, + "s:76:6:82:Infinity": 15, + "s:87:27:91:Infinity": 16, + "s:94:34:99:Infinity": 17, + "s:103:37:108:Infinity": 18, + "s:111:25:113:Infinity": 19, + "b:112:6:112:Infinity:113:6:113:Infinity": 6, + "s:116:23:123:Infinity": 20, + "f:116:48:116:49": 1, + "s:117:4:122:Infinity": 21, + "s:126:2:128:Infinity": 22, + "f:126:23:126:24": 2, + "s:127:4:127:Infinity": 23, + "s:131:30:134:Infinity": 24, + "f:132:9:132:10": 3, + "s:132:21:132:35": 25, + "f:134:12:134:13": 4, + "s:134:41:134:60": 26, + "b:137:2:139:Infinity:undefined:undefined:undefined:undefined": 7, + "s:137:2:139:Infinity": 27, + "b:137:6:137:31:137:31:137:51": 8, + "s:138:4:138:Infinity": 28, + "s:142:2:142:Infinity": 29, + "s:144:2:144:Infinity": 30 + } + } + }, + "/Users/danielslovinsky/Documents/projects/alchemy-docs/docs/src/content-indexer/visitors/processors/process-openapi.ts": { + "path": "/Users/danielslovinsky/Documents/projects/alchemy-docs/docs/src/content-indexer/visitors/processors/process-openapi.ts", + "statementMap": { + "0": { + "start": { "line": 52, "column": 33 }, + "end": { "line": 77, "column": null } + }, + "1": { + "start": { "line": 58, "column": 34 }, + "end": { "line": 58, "column": null } + }, + "2": { + "start": { "line": 60, "column": 2 }, + "end": { "line": 74, "column": null } + }, + "3": { + "start": { "line": 61, "column": 10 }, + "end": { "line": 65, "column": null } + }, + "4": { + "start": { "line": 67, "column": 4 }, + "end": { "line": 73, "column": null } + }, + "5": { + "start": { "line": 76, "column": 2 }, + "end": { "line": 76, "column": null } + }, + "6": { + "start": { "line": 82, "column": 31 }, + "end": { "line": 158, "column": null } + }, + "7": { + "start": { "line": 92, "column": 26 }, + "end": { "line": 92, "column": null } + }, + "8": { + "start": { "line": 93, "column": 2 }, + "end": { "line": 97, "column": null } + }, + "9": { + "start": { "line": 94, "column": 21 }, + "end": { "line": 94, "column": null } + }, + "10": { + "start": { "line": 95, "column": 4 }, + "end": { "line": 95, "column": null } + }, + "11": { + "start": { "line": 96, "column": 4 }, + "end": { "line": 96, "column": null } + }, + "12": { + "start": { "line": 99, "column": 33 }, + "end": { "line": 99, "column": null } + }, + "13": { + "start": { "line": 101, "column": 2 }, + "end": { "line": 155, "column": null } + }, + "14": { + "start": { "line": 102, "column": 40 }, + "end": { "line": 143, "column": null } + }, + "15": { + "start": { "line": 103, "column": 12 }, + "end": { "line": 107, "column": null } + }, + "16": { + "start": { "line": 109, "column": 12 }, + "end": { "line": 113, "column": null } + }, + "17": { + "start": { "line": 116, "column": 6 }, + "end": { "line": 135, "column": null } + }, + "18": { + "start": { "line": 117, "column": 14 }, + "end": { "line": 121, "column": null } + }, + "19": { + "start": { "line": 123, "column": 28 }, + "end": { "line": 125, "column": null } + }, + "20": { + "start": { "line": 127, "column": 8 }, + "end": { "line": 134, "column": null } + }, + "21": { + "start": { "line": 137, "column": 6 }, + "end": { "line": 142, "column": null } + }, + "22": { + "start": { "line": 146, "column": 4 }, + "end": { "line": 154, "column": null } + }, + "23": { + "start": { "line": 147, "column": 6 }, + "end": { "line": 151, "column": null } + }, + "24": { + "start": { "line": 153, "column": 6 }, + "end": { "line": 153, "column": null } + }, + "25": { + "start": { "line": 157, "column": 2 }, + "end": { "line": 157, "column": null } + }, + "26": { + "start": { "line": 164, "column": 34 }, + "end": { "line": 215, "column": null } + }, + "27": { + "start": { "line": 173, "column": 48 }, + "end": { "line": 173, "column": null } + }, + "28": { + "start": { "line": 176, "column": 8 }, + "end": { "line": 176, "column": null } + }, + "29": { + "start": { "line": 177, "column": 23 }, + "end": { "line": 182, "column": null } + }, + "30": { + "start": { "line": 185, "column": 2 }, + "end": { "line": 187, "column": null } + }, + "31": { + "start": { "line": 186, "column": 4 }, + "end": { "line": 186, "column": null } + }, + "32": { + "start": { "line": 190, "column": 31 }, + "end": { "line": 192, "column": null } + }, + "33": { + "start": { "line": 195, "column": 22 }, + "end": { "line": 203, "column": null } + }, + "34": { + "start": { "line": 206, "column": 39 }, + "end": { "line": 212, "column": null } + }, + "35": { + "start": { "line": 214, "column": 2 }, + "end": { "line": 214, "column": null } + } + }, + "fnMap": { + "0": { + "name": "(anonymous_0)", + "decl": { + "start": { "line": 52, "column": 33 }, + "end": { "line": 52, "column": 34 } + }, + "loc": { + "start": { "line": 57, "column": 49 }, + "end": { "line": 77, "column": null } + }, + "line": 57 + }, + "1": { + "name": "(anonymous_1)", + "decl": { + "start": { "line": 60, "column": 21 }, + "end": { "line": 60, "column": 22 } + }, + "loc": { + "start": { "line": 60, "column": 36 }, + "end": { "line": 74, "column": 3 } + }, + "line": 60 + }, + "2": { + "name": "(anonymous_2)", + "decl": { + "start": { "line": 82, "column": 31 }, + "end": { "line": 82, "column": 32 } + }, + "loc": { + "start": { "line": 90, "column": 47 }, + "end": { "line": 158, "column": null } + }, + "line": 90 + }, + "3": { + "name": "(anonymous_3)", + "decl": { + "start": { "line": 93, "column": 21 }, + "end": { "line": 93, "column": 22 } + }, + "loc": { + "start": { "line": 93, "column": 36 }, + "end": { "line": 97, "column": 3 } + }, + "line": 93 + }, + "4": { + "name": "(anonymous_4)", + "decl": { + "start": { "line": 102, "column": 58 }, + "end": { "line": 102, "column": 59 } + }, + "loc": { + "start": { "line": 102, "column": 73 }, + "end": { "line": 143, "column": 5 } + }, + "line": 102 + }, + "5": { + "name": "(anonymous_5)", + "decl": { + "start": { "line": 164, "column": 34 }, + "end": { "line": 164, "column": 35 } + }, + "loc": { + "start": { "line": 172, "column": 43 }, + "end": { "line": 215, "column": null } + }, + "line": 172 + } + }, + "branchMap": { + "0": { + "loc": { + "start": { "line": 94, "column": 21 }, + "end": { "line": 94, "column": null } + }, + "type": "binary-expr", + "locations": [ + { + "start": { "line": 94, "column": 21 }, + "end": { "line": 94, "column": 59 } + }, + { + "start": { "line": 94, "column": 59 }, + "end": { "line": 94, "column": null } + } + ], + "line": 94 + }, + "1": { + "loc": { + "start": { "line": 116, "column": 6 }, + "end": { "line": 135, "column": null } + }, + "type": "if", + "locations": [ + { + "start": { "line": 116, "column": 6 }, + "end": { "line": 135, "column": null } + }, + { "start": {}, "end": {} } + ], + "line": 116 + }, + "2": { + "loc": { + "start": { "line": 123, "column": 28 }, + "end": { "line": 125, "column": null } + }, + "type": "cond-expr", + "locations": [ + { + "start": { "line": 124, "column": 12 }, + "end": { "line": 124, "column": null } + }, + { + "start": { "line": 125, "column": 12 }, + "end": { "line": 125, "column": null } + } + ], + "line": 123 + }, + "3": { + "loc": { + "start": { "line": 146, "column": 4 }, + "end": { "line": 154, "column": null } + }, + "type": "if", + "locations": [ + { + "start": { "line": 146, "column": 4 }, + "end": { "line": 154, "column": null } + }, + { + "start": { "line": 152, "column": 11 }, + "end": { "line": 154, "column": null } + } + ], + "line": 146 + }, + "4": { + "loc": { + "start": { "line": 185, "column": 2 }, + "end": { "line": 187, "column": null } + }, + "type": "if", + "locations": [ + { + "start": { "line": 185, "column": 2 }, + "end": { "line": 187, "column": null } + }, + { "start": {}, "end": {} } + ], + "line": 185 + }, + "5": { + "loc": { + "start": { "line": 190, "column": 31 }, + "end": { "line": 192, "column": null } + }, + "type": "cond-expr", + "locations": [ + { + "start": { "line": 191, "column": 6 }, + "end": { "line": 191, "column": null } + }, + { + "start": { "line": 191, "column": 6 }, + "end": { "line": 192, "column": null } + } + ], + "line": 190 + }, + "6": { + "loc": { + "start": { "line": 206, "column": 39 }, + "end": { "line": 212, "column": null } + }, + "type": "cond-expr", + "locations": [ + { + "start": { "line": 207, "column": 6 }, + "end": { "line": 207, "column": null } + }, + { + "start": { "line": 208, "column": 6 }, + "end": { "line": 212, "column": null } + } + ], + "line": 206 + } + }, + "s": { + "0": 4, + "1": 14, + "2": 14, + "3": 15, + "4": 15, + "5": 14, + "6": 4, + "7": 12, + "8": 12, + "9": 13, + "10": 13, + "11": 13, + "12": 12, + "13": 12, + "14": 12, + "15": 13, + "16": 13, + "17": 13, + "18": 13, + "19": 13, + "20": 13, + "21": 13, + "22": 12, + "23": 4, + "24": 8, + "25": 12, + "26": 4, + "27": 14, + "28": 14, + "29": 14, + "30": 14, + "31": 2, + "32": 12, + "33": 14, + "34": 14, + "35": 14 + }, + "f": { "0": 14, "1": 15, "2": 12, "3": 13, "4": 13, "5": 14 }, + "b": { + "0": [13, 12], + "1": [13, 0], + "2": [12, 1], + "3": [4, 8], + "4": [2, 12], + "5": [1, 11], + "6": [1, 11] + }, + "meta": { + "lastBranch": 7, + "lastFunction": 6, + "lastStatement": 36, + "seen": { + "s:52:33:77:Infinity": 0, + "f:52:33:52:34": 0, + "s:58:34:58:Infinity": 1, + "s:60:2:74:Infinity": 2, + "f:60:21:60:22": 1, + "s:61:10:65:Infinity": 3, + "s:67:4:73:Infinity": 4, + "s:76:2:76:Infinity": 5, + "s:82:31:158:Infinity": 6, + "f:82:31:82:32": 2, + "s:92:26:92:Infinity": 7, + "s:93:2:97:Infinity": 8, + "f:93:21:93:22": 3, + "s:94:21:94:Infinity": 9, + "b:94:21:94:59:94:59:94:Infinity": 0, + "s:95:4:95:Infinity": 10, + "s:96:4:96:Infinity": 11, + "s:99:33:99:Infinity": 12, + "s:101:2:155:Infinity": 13, + "s:102:40:143:Infinity": 14, + "f:102:58:102:59": 4, + "s:103:12:107:Infinity": 15, + "s:109:12:113:Infinity": 16, + "b:116:6:135:Infinity:undefined:undefined:undefined:undefined": 1, + "s:116:6:135:Infinity": 17, + "s:117:14:121:Infinity": 18, + "s:123:28:125:Infinity": 19, + "b:124:12:124:Infinity:125:12:125:Infinity": 2, + "s:127:8:134:Infinity": 20, + "s:137:6:142:Infinity": 21, + "b:146:4:154:Infinity:152:11:154:Infinity": 3, + "s:146:4:154:Infinity": 22, + "s:147:6:151:Infinity": 23, + "s:153:6:153:Infinity": 24, + "s:157:2:157:Infinity": 25, + "s:164:34:215:Infinity": 26, + "f:164:34:164:35": 5, + "s:173:48:173:Infinity": 27, + "s:176:8:176:Infinity": 28, + "s:177:23:182:Infinity": 29, + "b:185:2:187:Infinity:undefined:undefined:undefined:undefined": 4, + "s:185:2:187:Infinity": 30, + "s:186:4:186:Infinity": 31, + "s:190:31:192:Infinity": 32, + "b:191:6:191:Infinity:191:6:192:Infinity": 5, + "s:195:22:203:Infinity": 33, + "s:206:39:212:Infinity": 34, + "b:207:6:207:Infinity:208:6:212:Infinity": 6, + "s:214:2:214:Infinity": 35 + } + } + }, + "/Users/danielslovinsky/Documents/projects/alchemy-docs/docs/src/content-indexer/visitors/processors/process-openrpc.ts": { + "path": "/Users/danielslovinsky/Documents/projects/alchemy-docs/docs/src/content-indexer/visitors/processors/process-openrpc.ts", + "statementMap": { + "0": { + "start": { "line": 31, "column": 34 }, + "end": { "line": 112, "column": null } + }, + "1": { + "start": { "line": 40, "column": 48 }, + "end": { "line": 40, "column": null } + }, + "2": { + "start": { "line": 42, "column": 2 }, + "end": { "line": 45, "column": null } + }, + "3": { + "start": { "line": 43, "column": 4 }, + "end": { "line": 43, "column": null } + }, + "4": { + "start": { "line": 44, "column": 4 }, + "end": { "line": 44, "column": null } + }, + "5": { + "start": { "line": 47, "column": 34 }, + "end": { "line": 47, "column": null } + }, + "6": { + "start": { "line": 48, "column": 38 }, + "end": { "line": 48, "column": null } + }, + "7": { + "start": { "line": 52, "column": 4 }, + "end": { "line": 54, "column": null } + }, + "8": { + "start": { "line": 57, "column": 2 }, + "end": { "line": 95, "column": null } + }, + "9": { + "start": { "line": 58, "column": 10 }, + "end": { "line": 58, "column": null } + }, + "10": { + "start": { "line": 59, "column": 24 }, + "end": { "line": 59, "column": null } + }, + "11": { + "start": { "line": 60, "column": 22 }, + "end": { "line": 60, "column": null } + }, + "12": { + "start": { "line": 63, "column": 4 }, + "end": { "line": 69, "column": null } + }, + "13": { + "start": { "line": 72, "column": 4 }, + "end": { "line": 86, "column": null } + }, + "14": { + "start": { "line": 73, "column": 26 }, + "end": { "line": 73, "column": null } + }, + "15": { + "start": { "line": 74, "column": 26 }, + "end": { "line": 76, "column": null } + }, + "16": { + "start": { "line": 78, "column": 6 }, + "end": { "line": 85, "column": null } + }, + "17": { + "start": { "line": 89, "column": 4 }, + "end": { "line": 94, "column": null } + }, + "18": { + "start": { "line": 98, "column": 2 }, + "end": { "line": 100, "column": null } + }, + "19": { + "start": { "line": 99, "column": 4 }, + "end": { "line": 99, "column": null } + }, + "20": { + "start": { "line": 103, "column": 39 }, + "end": { "line": 109, "column": null } + }, + "21": { + "start": { "line": 111, "column": 2 }, + "end": { "line": 111, "column": null } + } + }, + "fnMap": { + "0": { + "name": "(anonymous_0)", + "decl": { + "start": { "line": 31, "column": 34 }, + "end": { "line": 31, "column": 35 } + }, + "loc": { + "start": { "line": 39, "column": 43 }, + "end": { "line": 112, "column": null } + }, + "line": 39 + }, + "1": { + "name": "(anonymous_1)", + "decl": { + "start": { "line": 57, "column": 23 }, + "end": { "line": 57, "column": 24 } + }, + "loc": { + "start": { "line": 57, "column": 35 }, + "end": { "line": 95, "column": 3 } + }, + "line": 57 + } + }, + "branchMap": { + "0": { + "loc": { + "start": { "line": 42, "column": 2 }, + "end": { "line": 45, "column": null } + }, + "type": "if", + "locations": [ + { + "start": { "line": 42, "column": 2 }, + "end": { "line": 45, "column": null } + }, + { "start": {}, "end": {} } + ], + "line": 42 + }, + "1": { + "loc": { + "start": { "line": 52, "column": 4 }, + "end": { "line": 54, "column": null } + }, + "type": "cond-expr", + "locations": [ + { + "start": { "line": 52, "column": 18 }, + "end": { "line": 53, "column": null } + }, + { + "start": { "line": 54, "column": 8 }, + "end": { "line": 54, "column": null } + } + ], + "line": 52 + }, + "2": { + "loc": { + "start": { "line": 52, "column": 4 }, + "end": { "line": 52, "column": null } + }, + "type": "binary-expr", + "locations": [ + { + "start": { "line": 52, "column": 4 }, + "end": { "line": 52, "column": 17 } + }, + { + "start": { "line": 52, "column": 17 }, + "end": { "line": 52, "column": null } + } + ], + "line": 52 + }, + "3": { + "loc": { + "start": { "line": 72, "column": 4 }, + "end": { "line": 86, "column": null } + }, + "type": "if", + "locations": [ + { + "start": { "line": 72, "column": 4 }, + "end": { "line": 86, "column": null } + }, + { "start": {}, "end": {} } + ], + "line": 72 + }, + "4": { + "loc": { + "start": { "line": 73, "column": 26 }, + "end": { "line": 73, "column": null } + }, + "type": "binary-expr", + "locations": [ + { + "start": { "line": 73, "column": 26 }, + "end": { "line": 73, "column": 48 } + }, + { + "start": { "line": 73, "column": 48 }, + "end": { "line": 73, "column": 66 } + }, + { + "start": { "line": 73, "column": 66 }, + "end": { "line": 73, "column": null } + } + ], + "line": 73 + }, + "5": { + "loc": { + "start": { "line": 74, "column": 26 }, + "end": { "line": 76, "column": null } + }, + "type": "cond-expr", + "locations": [ + { + "start": { "line": 75, "column": 10 }, + "end": { "line": 75, "column": null } + }, + { + "start": { "line": 76, "column": 10 }, + "end": { "line": 76, "column": null } + } + ], + "line": 74 + }, + "6": { + "loc": { + "start": { "line": 98, "column": 2 }, + "end": { "line": 100, "column": null } + }, + "type": "if", + "locations": [ + { + "start": { "line": 98, "column": 2 }, + "end": { "line": 100, "column": null } + }, + { "start": {}, "end": {} } + ], + "line": 98 + }, + "7": { + "loc": { + "start": { "line": 103, "column": 39 }, + "end": { "line": 109, "column": null } + }, + "type": "cond-expr", + "locations": [ + { + "start": { "line": 104, "column": 6 }, + "end": { "line": 104, "column": null } + }, + { + "start": { "line": 105, "column": 6 }, + "end": { "line": 109, "column": null } + } + ], + "line": 103 + } + }, + "s": { + "0": 4, + "1": 9, + "2": 9, + "3": 1, + "4": 1, + "5": 8, + "6": 8, + "7": 8, + "8": 9, + "9": 9, + "10": 9, + "11": 9, + "12": 9, + "13": 9, + "14": 8, + "15": 8, + "16": 8, + "17": 9, + "18": 9, + "19": 1, + "20": 7, + "21": 9 + }, + "f": { "0": 9, "1": 9 }, + "b": { + "0": [1, 8], + "1": [6, 2], + "2": [8, 7], + "3": [8, 1], + "4": [8, 4, 3], + "5": [7, 1], + "6": [1, 8], + "7": [1, 6] + }, + "meta": { + "lastBranch": 8, + "lastFunction": 2, + "lastStatement": 22, + "seen": { + "s:31:34:112:Infinity": 0, + "f:31:34:31:35": 0, + "s:40:48:40:Infinity": 1, + "b:42:2:45:Infinity:undefined:undefined:undefined:undefined": 0, + "s:42:2:45:Infinity": 2, + "s:43:4:43:Infinity": 3, + "s:44:4:44:Infinity": 4, + "s:47:34:47:Infinity": 5, + "s:48:38:48:Infinity": 6, + "s:52:4:54:Infinity": 7, + "b:52:18:53:Infinity:54:8:54:Infinity": 1, + "b:52:4:52:17:52:17:52:Infinity": 2, + "s:57:2:95:Infinity": 8, + "f:57:23:57:24": 1, + "s:58:10:58:Infinity": 9, + "s:59:24:59:Infinity": 10, + "s:60:22:60:Infinity": 11, + "s:63:4:69:Infinity": 12, + "b:72:4:86:Infinity:undefined:undefined:undefined:undefined": 3, + "s:72:4:86:Infinity": 13, + "s:73:26:73:Infinity": 14, + "b:73:26:73:48:73:48:73:66:73:66:73:Infinity": 4, + "s:74:26:76:Infinity": 15, + "b:75:10:75:Infinity:76:10:76:Infinity": 5, + "s:78:6:85:Infinity": 16, + "s:89:4:94:Infinity": 17, + "b:98:2:100:Infinity:undefined:undefined:undefined:undefined": 6, + "s:98:2:100:Infinity": 18, + "s:99:4:99:Infinity": 19, + "s:103:39:109:Infinity": 20, + "b:104:6:104:Infinity:105:6:109:Infinity": 7, + "s:111:2:111:Infinity": 21 + } + } + } +} diff --git a/coverage/favicon.png b/coverage/favicon.png new file mode 100644 index 0000000000000000000000000000000000000000..c1525b811a167671e9de1fa78aab9f5c0b61cef7 GIT binary patch literal 445 zcmV;u0Yd(XP))rP{nL}Ln%S7`m{0DjX9TLF* zFCb$4Oi7vyLOydb!7n&^ItCzb-%BoB`=x@N2jll2Nj`kauio%aw_@fe&*}LqlFT43 z8doAAe))z_%=P%v^@JHp3Hjhj^6*Kr_h|g_Gr?ZAa&y>wxHE99Gk>A)2MplWz2xdG zy8VD2J|Uf#EAw*bo5O*PO_}X2Tob{%bUoO2G~T`@%S6qPyc}VkhV}UifBuRk>%5v( z)x7B{I~z*k<7dv#5tC+m{km(D087J4O%+<<;K|qwefb6@GSX45wCK}Sn*> + + + + Code coverage report for All files + + + + + + + + + +
+
+

All files

+
+ +
+ 76.27% + Statements + 418/548 +
+ + +
+ 73.91% + Branches + 238/322 +
+ + +
+ 84.82% + Functions + 95/112 +
+ + +
+ 76.56% + Lines + 415/542 +
+ + +
+

+ Press n or j to go to the next uncovered block, b, p or k for the previous block. +

+ +
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
FileStatementsBranchesFunctionsLines
collectors +
+
100%40/40100%17/17100%25/25100%40/40
constants +
+
100%1/1100%0/0100%0/0100%1/1
core +
+
94.62%88/9380.35%45/56100%24/2494.5%86/91
indexers +
+
90.47%19/2166.66%4/6100%1/190.47%19/21
types +
+
100%10/10100%19/19100%5/5100%10/10
uploaders +
+
53.33%32/6068.18%15/2275%6/853.33%32/60
utils +
+
50.52%96/19049.57%58/11753.12%17/3251.07%95/186
visitors +
+
98.66%74/7592.59%50/54100%9/998.66%74/75
visitors/processors +
+
100%58/5896.77%30/31100%8/8100%58/58
+
+
+
+ + + + + + + + \ No newline at end of file diff --git a/coverage/indexers/index.html b/coverage/indexers/index.html new file mode 100644 index 000000000..7e5371c46 --- /dev/null +++ b/coverage/indexers/index.html @@ -0,0 +1,116 @@ + + + + + + Code coverage report for indexers + + + + + + + + + +
+
+

All files indexers

+
+ +
+ 90.47% + Statements + 19/21 +
+ + +
+ 66.66% + Branches + 4/6 +
+ + +
+ 100% + Functions + 1/1 +
+ + +
+ 90.47% + Lines + 19/21 +
+ + +
+

+ Press n or j to go to the next uncovered block, b, p or k for the previous block. +

+ +
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
FileStatementsBranchesFunctionsLines
main.ts +
+
90.47%19/2166.66%4/6100%1/190.47%19/21
+
+
+
+ + + + + + + + \ No newline at end of file diff --git a/coverage/indexers/main.ts.html b/coverage/indexers/main.ts.html new file mode 100644 index 000000000..d5c714b91 --- /dev/null +++ b/coverage/indexers/main.ts.html @@ -0,0 +1,310 @@ + + + + + + Code coverage report for indexers/main.ts + + + + + + + + + +
+
+

All files / indexers main.ts

+
+ +
+ 90.47% + Statements + 19/21 +
+ + +
+ 66.66% + Branches + 4/6 +
+ + +
+ 100% + Functions + 1/1 +
+ + +
+ 90.47% + Lines + 19/21 +
+ + +
+

+ Press n or j to go to the next uncovered block, b, p or k for the previous block. +

+ +
+
+

+
1 +2 +3 +4 +5 +6 +7 +8 +9 +10 +11 +12 +13 +14 +15 +16 +17 +18 +19 +20 +21 +22 +23 +24 +25 +26 +27 +28 +29 +30 +31 +32 +33 +34 +35 +36 +37 +38 +39 +40 +41 +42 +43 +44 +45 +46 +47 +48 +49 +50 +51 +52 +53 +54 +55 +56 +57 +58 +59 +60 +61 +62 +63 +64 +65 +66 +67 +68 +69 +70 +71 +72 +73 +74 +75 +76  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +1x +  +  +2x +  +  +  +2x +1x +1x +  +  +1x +  +1x +1x +1x +  +  +1x +  +  +  +2x +2x +2x +  +  +  +  +2x +2x +  +  +2x +2x +  +2x +  +  +  +2x +  + 
import yaml from "js-yaml";
+ 
+import {
+  batchFetchContent,
+  type ContentSource,
+} from "@/content-indexer/core/batch-fetcher.js";
+import { buildAllOutputs } from "@/content-indexer/core/build-all-outputs.js";
+import { scanDocsYml } from "@/content-indexer/core/scanner.js";
+import type { DocsYml } from "@/content-indexer/types/docsYaml.js";
+import type { IndexerResult } from "@/content-indexer/types/indexer.js";
+import { readLocalDocsYml } from "@/content-indexer/utils/filesystem.js";
+import {
+  fetchFileFromGitHub,
+  type RepoConfig,
+} from "@/content-indexer/utils/github.js";
+ 
+export interface DocsIndexerConfig {
+  source: ContentSource; // filesystem or github
+  repoConfig: RepoConfig;
+  branchId: string;
+  mode?: "preview" | "production"; // Only relevant for logging
+}
+ 
+/**
+ * Unified docs content indexer.
+ * Handles both main docs (local filesystem) and SDK refs (GitHub API).
+ *
+ * Processes docs.yml through 3 phases:
+ * 1. SCAN - Parse docs.yml to discover all paths and specs
+ * 2. BATCH FETCH - Read all content (filesystem or GitHub)
+ * 3. PROCESS - Build path index, navigation trees, and Algolia records
+ */
+export const buildDocsContentIndex = async (
+  config: DocsIndexerConfig,
+): Promise<IndexerResult> => {
+  console.info(`🔍 Building content index (branch: ${config.branchId})...`);
+ 
+  // Read docs.yml based on source type
+  let docsYml: DocsYml;
+  if (config.source.type === "filesystem") {
+    const result = await readLocalDocsYml(config.source.basePath);
+    Iif (!result) {
+      throw new Error(`Failed to read docs.yml from ${config.source.basePath}`);
+    }
+    docsYml = result;
+  } else {
+    const docsYmlPath = `${config.repoConfig.docsPrefix}/docs.yml`;
+    const content = await fetchFileFromGitHub(docsYmlPath, config.repoConfig);
+    Iif (!content) {
+      throw new Error(`Failed to fetch ${docsYmlPath} from GitHub`);
+    }
+    docsYml = yaml.load(content) as DocsYml;
+  }
+ 
+  // PHASE 1: SCAN
+  console.info("📋 Phase 1: Scanning docs.yml...");
+  const scanResult = scanDocsYml(docsYml);
+  console.info(
+    `   Found ${scanResult.mdxPaths.size} MDX files, ${scanResult.specNames.size} specs`,
+  );
+ 
+  // PHASE 2: BATCH FETCH
+  console.info("📥 Phase 2: Fetching content...");
+  const contentCache = await batchFetchContent(scanResult, config.source);
+ 
+  // PHASE 3: PROCESS
+  console.info("⚙️  Phase 3: Processing...");
+  const outputs = buildAllOutputs(docsYml, contentCache, config.repoConfig);
+ 
+  console.info(
+    `📊 Generated ${Object.keys(outputs.pathIndex).length} routes, ${outputs.algoliaRecords.length} Algolia records`,
+  );
+ 
+  return outputs;
+};
+ 
+ +
+
+ + + + + + + + \ No newline at end of file diff --git a/coverage/prettify.css b/coverage/prettify.css new file mode 100644 index 000000000..b317a7cda --- /dev/null +++ b/coverage/prettify.css @@ -0,0 +1 @@ +.pln{color:#000}@media screen{.str{color:#080}.kwd{color:#008}.com{color:#800}.typ{color:#606}.lit{color:#066}.pun,.opn,.clo{color:#660}.tag{color:#008}.atn{color:#606}.atv{color:#080}.dec,.var{color:#606}.fun{color:red}}@media print,projection{.str{color:#060}.kwd{color:#006;font-weight:bold}.com{color:#600;font-style:italic}.typ{color:#404;font-weight:bold}.lit{color:#044}.pun,.opn,.clo{color:#440}.tag{color:#006;font-weight:bold}.atn{color:#404}.atv{color:#060}}pre.prettyprint{padding:2px;border:1px solid #888}ol.linenums{margin-top:0;margin-bottom:0}li.L0,li.L1,li.L2,li.L3,li.L5,li.L6,li.L7,li.L8{list-style-type:none}li.L1,li.L3,li.L5,li.L7,li.L9{background:#eee} diff --git a/coverage/prettify.js b/coverage/prettify.js new file mode 100644 index 000000000..87aafe233 --- /dev/null +++ b/coverage/prettify.js @@ -0,0 +1,1008 @@ +/* eslint-disable */ +window.PR_SHOULD_USE_CONTINUATION = true; +(function () { + var h = ["break,continue,do,else,for,if,return,while"]; + var u = [ + h, + "auto,case,char,const,default,double,enum,extern,float,goto,int,long,register,short,signed,sizeof,static,struct,switch,typedef,union,unsigned,void,volatile", + ]; + var p = [ + u, + "catch,class,delete,false,import,new,operator,private,protected,public,this,throw,true,try,typeof", + ]; + var l = [ + p, + "alignof,align_union,asm,axiom,bool,concept,concept_map,const_cast,constexpr,decltype,dynamic_cast,explicit,export,friend,inline,late_check,mutable,namespace,nullptr,reinterpret_cast,static_assert,static_cast,template,typeid,typename,using,virtual,where", + ]; + var x = [ + p, + "abstract,boolean,byte,extends,final,finally,implements,import,instanceof,null,native,package,strictfp,super,synchronized,throws,transient", + ]; + var R = [ + x, + "as,base,by,checked,decimal,delegate,descending,dynamic,event,fixed,foreach,from,group,implicit,in,interface,internal,into,is,lock,object,out,override,orderby,params,partial,readonly,ref,sbyte,sealed,stackalloc,string,select,uint,ulong,unchecked,unsafe,ushort,var", + ]; + var r = + "all,and,by,catch,class,else,extends,false,finally,for,if,in,is,isnt,loop,new,no,not,null,of,off,on,or,return,super,then,true,try,unless,until,when,while,yes"; + var w = [ + p, + "debugger,eval,export,function,get,null,set,undefined,var,with,Infinity,NaN", + ]; + var s = + "caller,delete,die,do,dump,elsif,eval,exit,foreach,for,goto,if,import,last,local,my,next,no,our,print,package,redo,require,sub,undef,unless,until,use,wantarray,while,BEGIN,END"; + var I = [ + h, + "and,as,assert,class,def,del,elif,except,exec,finally,from,global,import,in,is,lambda,nonlocal,not,or,pass,print,raise,try,with,yield,False,True,None", + ]; + var f = [ + h, + "alias,and,begin,case,class,def,defined,elsif,end,ensure,false,in,module,next,nil,not,or,redo,rescue,retry,self,super,then,true,undef,unless,until,when,yield,BEGIN,END", + ]; + var H = [h, "case,done,elif,esac,eval,fi,function,in,local,set,then,until"]; + var A = [l, R, w, s + I, f, H]; + var e = + /^(DIR|FILE|vector|(de|priority_)?queue|list|stack|(const_)?iterator|(multi)?(set|map)|bitset|u?(int|float)\d*)/; + var C = "str"; + var z = "kwd"; + var j = "com"; + var O = "typ"; + var G = "lit"; + var L = "pun"; + var F = "pln"; + var m = "tag"; + var E = "dec"; + var J = "src"; + var P = "atn"; + var n = "atv"; + var N = "nocode"; + var M = + "(?:^^\\.?|[+-]|\\!|\\!=|\\!==|\\#|\\%|\\%=|&|&&|&&=|&=|\\(|\\*|\\*=|\\+=|\\,|\\-=|\\->|\\/|\\/=|:|::|\\;|<|<<|<<=|<=|=|==|===|>|>=|>>|>>=|>>>|>>>=|\\?|\\@|\\[|\\^|\\^=|\\^\\^|\\^\\^=|\\{|\\||\\|=|\\|\\||\\|\\|=|\\~|break|case|continue|delete|do|else|finally|instanceof|return|throw|try|typeof)\\s*"; + function k(Z) { + var ad = 0; + var S = false; + var ac = false; + for (var V = 0, U = Z.length; V < U; ++V) { + var ae = Z[V]; + if (ae.ignoreCase) { + ac = true; + } else { + if ( + /[a-z]/i.test( + ae.source.replace(/\\u[0-9a-f]{4}|\\x[0-9a-f]{2}|\\[^ux]/gi, ""), + ) + ) { + S = true; + ac = false; + break; + } + } + } + var Y = { b: 8, t: 9, n: 10, v: 11, f: 12, r: 13 }; + function ab(ah) { + var ag = ah.charCodeAt(0); + if (ag !== 92) { + return ag; + } + var af = ah.charAt(1); + ag = Y[af]; + if (ag) { + return ag; + } else { + if ("0" <= af && af <= "7") { + return parseInt(ah.substring(1), 8); + } else { + if (af === "u" || af === "x") { + return parseInt(ah.substring(2), 16); + } else { + return ah.charCodeAt(1); + } + } + } + } + function T(af) { + if (af < 32) { + return (af < 16 ? "\\x0" : "\\x") + af.toString(16); + } + var ag = String.fromCharCode(af); + if (ag === "\\" || ag === "-" || ag === "[" || ag === "]") { + ag = "\\" + ag; + } + return ag; + } + function X(am) { + var aq = am + .substring(1, am.length - 1) + .match( + new RegExp( + "\\\\u[0-9A-Fa-f]{4}|\\\\x[0-9A-Fa-f]{2}|\\\\[0-3][0-7]{0,2}|\\\\[0-7]{1,2}|\\\\[\\s\\S]|-|[^-\\\\]", + "g", + ), + ); + var ak = []; + var af = []; + var ao = aq[0] === "^"; + for (var ar = ao ? 1 : 0, aj = aq.length; ar < aj; ++ar) { + var ah = aq[ar]; + if (/\\[bdsw]/i.test(ah)) { + ak.push(ah); + } else { + var ag = ab(ah); + var al; + if (ar + 2 < aj && "-" === aq[ar + 1]) { + al = ab(aq[ar + 2]); + ar += 2; + } else { + al = ag; + } + af.push([ag, al]); + if (!(al < 65 || ag > 122)) { + if (!(al < 65 || ag > 90)) { + af.push([Math.max(65, ag) | 32, Math.min(al, 90) | 32]); + } + if (!(al < 97 || ag > 122)) { + af.push([Math.max(97, ag) & ~32, Math.min(al, 122) & ~32]); + } + } + } + } + af.sort(function (av, au) { + return av[0] - au[0] || au[1] - av[1]; + }); + var ai = []; + var ap = [NaN, NaN]; + for (var ar = 0; ar < af.length; ++ar) { + var at = af[ar]; + if (at[0] <= ap[1] + 1) { + ap[1] = Math.max(ap[1], at[1]); + } else { + ai.push((ap = at)); + } + } + var an = ["["]; + if (ao) { + an.push("^"); + } + an.push.apply(an, ak); + for (var ar = 0; ar < ai.length; ++ar) { + var at = ai[ar]; + an.push(T(at[0])); + if (at[1] > at[0]) { + if (at[1] + 1 > at[0]) { + an.push("-"); + } + an.push(T(at[1])); + } + } + an.push("]"); + return an.join(""); + } + function W(al) { + var aj = al.source.match( + new RegExp( + "(?:\\[(?:[^\\x5C\\x5D]|\\\\[\\s\\S])*\\]|\\\\u[A-Fa-f0-9]{4}|\\\\x[A-Fa-f0-9]{2}|\\\\[0-9]+|\\\\[^ux0-9]|\\(\\?[:!=]|[\\(\\)\\^]|[^\\x5B\\x5C\\(\\)\\^]+)", + "g", + ), + ); + var ah = aj.length; + var an = []; + for (var ak = 0, am = 0; ak < ah; ++ak) { + var ag = aj[ak]; + if (ag === "(") { + ++am; + } else { + if ("\\" === ag.charAt(0)) { + var af = +ag.substring(1); + if (af && af <= am) { + an[af] = -1; + } + } + } + } + for (var ak = 1; ak < an.length; ++ak) { + if (-1 === an[ak]) { + an[ak] = ++ad; + } + } + for (var ak = 0, am = 0; ak < ah; ++ak) { + var ag = aj[ak]; + if (ag === "(") { + ++am; + if (an[am] === undefined) { + aj[ak] = "(?:"; + } + } else { + if ("\\" === ag.charAt(0)) { + var af = +ag.substring(1); + if (af && af <= am) { + aj[ak] = "\\" + an[am]; + } + } + } + } + for (var ak = 0, am = 0; ak < ah; ++ak) { + if ("^" === aj[ak] && "^" !== aj[ak + 1]) { + aj[ak] = ""; + } + } + if (al.ignoreCase && S) { + for (var ak = 0; ak < ah; ++ak) { + var ag = aj[ak]; + var ai = ag.charAt(0); + if (ag.length >= 2 && ai === "[") { + aj[ak] = X(ag); + } else { + if (ai !== "\\") { + aj[ak] = ag.replace(/[a-zA-Z]/g, function (ao) { + var ap = ao.charCodeAt(0); + return "[" + String.fromCharCode(ap & ~32, ap | 32) + "]"; + }); + } + } + } + } + return aj.join(""); + } + var aa = []; + for (var V = 0, U = Z.length; V < U; ++V) { + var ae = Z[V]; + if (ae.global || ae.multiline) { + throw new Error("" + ae); + } + aa.push("(?:" + W(ae) + ")"); + } + return new RegExp(aa.join("|"), ac ? "gi" : "g"); + } + function a(V) { + var U = /(?:^|\s)nocode(?:\s|$)/; + var X = []; + var T = 0; + var Z = []; + var W = 0; + var S; + if (V.currentStyle) { + S = V.currentStyle.whiteSpace; + } else { + if (window.getComputedStyle) { + S = document.defaultView + .getComputedStyle(V, null) + .getPropertyValue("white-space"); + } + } + var Y = S && "pre" === S.substring(0, 3); + function aa(ab) { + switch (ab.nodeType) { + case 1: + if (U.test(ab.className)) { + return; + } + for (var ae = ab.firstChild; ae; ae = ae.nextSibling) { + aa(ae); + } + var ad = ab.nodeName; + if ("BR" === ad || "LI" === ad) { + X[W] = "\n"; + Z[W << 1] = T++; + Z[(W++ << 1) | 1] = ab; + } + break; + case 3: + case 4: + var ac = ab.nodeValue; + if (ac.length) { + if (!Y) { + ac = ac.replace(/[ \t\r\n]+/g, " "); + } else { + ac = ac.replace(/\r\n?/g, "\n"); + } + X[W] = ac; + Z[W << 1] = T; + T += ac.length; + Z[(W++ << 1) | 1] = ab; + } + break; + } + } + aa(V); + return { sourceCode: X.join("").replace(/\n$/, ""), spans: Z }; + } + function B(S, U, W, T) { + if (!U) { + return; + } + var V = { sourceCode: U, basePos: S }; + W(V); + T.push.apply(T, V.decorations); + } + var v = /\S/; + function o(S) { + var V = undefined; + for (var U = S.firstChild; U; U = U.nextSibling) { + var T = U.nodeType; + V = T === 1 ? (V ? S : U) : T === 3 ? (v.test(U.nodeValue) ? S : V) : V; + } + return V === S ? undefined : V; + } + function g(U, T) { + var S = {}; + var V; + (function () { + var ad = U.concat(T); + var ah = []; + var ag = {}; + for (var ab = 0, Z = ad.length; ab < Z; ++ab) { + var Y = ad[ab]; + var ac = Y[3]; + if (ac) { + for (var ae = ac.length; --ae >= 0; ) { + S[ac.charAt(ae)] = Y; + } + } + var af = Y[1]; + var aa = "" + af; + if (!ag.hasOwnProperty(aa)) { + ah.push(af); + ag[aa] = null; + } + } + ah.push(/[\0-\uffff]/); + V = k(ah); + })(); + var X = T.length; + var W = function (ah) { + var Z = ah.sourceCode, + Y = ah.basePos; + var ad = [Y, F]; + var af = 0; + var an = Z.match(V) || []; + var aj = {}; + for (var ae = 0, aq = an.length; ae < aq; ++ae) { + var ag = an[ae]; + var ap = aj[ag]; + var ai = void 0; + var am; + if (typeof ap === "string") { + am = false; + } else { + var aa = S[ag.charAt(0)]; + if (aa) { + ai = ag.match(aa[1]); + ap = aa[0]; + } else { + for (var ao = 0; ao < X; ++ao) { + aa = T[ao]; + ai = ag.match(aa[1]); + if (ai) { + ap = aa[0]; + break; + } + } + if (!ai) { + ap = F; + } + } + am = ap.length >= 5 && "lang-" === ap.substring(0, 5); + if (am && !(ai && typeof ai[1] === "string")) { + am = false; + ap = J; + } + if (!am) { + aj[ag] = ap; + } + } + var ab = af; + af += ag.length; + if (!am) { + ad.push(Y + ab, ap); + } else { + var al = ai[1]; + var ak = ag.indexOf(al); + var ac = ak + al.length; + if (ai[2]) { + ac = ag.length - ai[2].length; + ak = ac - al.length; + } + var ar = ap.substring(5); + B(Y + ab, ag.substring(0, ak), W, ad); + B(Y + ab + ak, al, q(ar, al), ad); + B(Y + ab + ac, ag.substring(ac), W, ad); + } + } + ah.decorations = ad; + }; + return W; + } + function i(T) { + var W = [], + S = []; + if (T.tripleQuotedStrings) { + W.push([ + C, + /^(?:\'\'\'(?:[^\'\\]|\\[\s\S]|\'{1,2}(?=[^\']))*(?:\'\'\'|$)|\"\"\"(?:[^\"\\]|\\[\s\S]|\"{1,2}(?=[^\"]))*(?:\"\"\"|$)|\'(?:[^\\\']|\\[\s\S])*(?:\'|$)|\"(?:[^\\\"]|\\[\s\S])*(?:\"|$))/, + null, + "'\"", + ]); + } else { + if (T.multiLineStrings) { + W.push([ + C, + /^(?:\'(?:[^\\\']|\\[\s\S])*(?:\'|$)|\"(?:[^\\\"]|\\[\s\S])*(?:\"|$)|\`(?:[^\\\`]|\\[\s\S])*(?:\`|$))/, + null, + "'\"`", + ]); + } else { + W.push([ + C, + /^(?:\'(?:[^\\\'\r\n]|\\.)*(?:\'|$)|\"(?:[^\\\"\r\n]|\\.)*(?:\"|$))/, + null, + "\"'", + ]); + } + } + if (T.verbatimStrings) { + S.push([C, /^@\"(?:[^\"]|\"\")*(?:\"|$)/, null]); + } + var Y = T.hashComments; + if (Y) { + if (T.cStyleComments) { + if (Y > 1) { + W.push([j, /^#(?:##(?:[^#]|#(?!##))*(?:###|$)|.*)/, null, "#"]); + } else { + W.push([ + j, + /^#(?:(?:define|elif|else|endif|error|ifdef|include|ifndef|line|pragma|undef|warning)\b|[^\r\n]*)/, + null, + "#", + ]); + } + S.push([ + C, + /^<(?:(?:(?:\.\.\/)*|\/?)(?:[\w-]+(?:\/[\w-]+)+)?[\w-]+\.h|[a-z]\w*)>/, + null, + ]); + } else { + W.push([j, /^#[^\r\n]*/, null, "#"]); + } + } + if (T.cStyleComments) { + S.push([j, /^\/\/[^\r\n]*/, null]); + S.push([j, /^\/\*[\s\S]*?(?:\*\/|$)/, null]); + } + if (T.regexLiterals) { + var X = + "/(?=[^/*])(?:[^/\\x5B\\x5C]|\\x5C[\\s\\S]|\\x5B(?:[^\\x5C\\x5D]|\\x5C[\\s\\S])*(?:\\x5D|$))+/"; + S.push(["lang-regex", new RegExp("^" + M + "(" + X + ")")]); + } + var V = T.types; + if (V) { + S.push([O, V]); + } + var U = ("" + T.keywords).replace(/^ | $/g, ""); + if (U.length) { + S.push([ + z, + new RegExp("^(?:" + U.replace(/[\s,]+/g, "|") + ")\\b"), + null, + ]); + } + W.push([F, /^\s+/, null, " \r\n\t\xA0"]); + S.push( + [G, /^@[a-z_$][a-z_$@0-9]*/i, null], + [O, /^(?:[@_]?[A-Z]+[a-z][A-Za-z_$@0-9]*|\w+_t\b)/, null], + [F, /^[a-z_$][a-z_$@0-9]*/i, null], + [ + G, + new RegExp( + "^(?:0x[a-f0-9]+|(?:\\d(?:_\\d+)*\\d*(?:\\.\\d*)?|\\.\\d\\+)(?:e[+\\-]?\\d+)?)[a-z]*", + "i", + ), + null, + "0123456789", + ], + [F, /^\\[\s\S]?/, null], + [L, /^.[^\s\w\.$@\'\"\`\/\#\\]*/, null], + ); + return g(W, S); + } + var K = i({ + keywords: A, + hashComments: true, + cStyleComments: true, + multiLineStrings: true, + regexLiterals: true, + }); + function Q(V, ag) { + var U = /(?:^|\s)nocode(?:\s|$)/; + var ab = /\r\n?|\n/; + var ac = V.ownerDocument; + var S; + if (V.currentStyle) { + S = V.currentStyle.whiteSpace; + } else { + if (window.getComputedStyle) { + S = ac.defaultView + .getComputedStyle(V, null) + .getPropertyValue("white-space"); + } + } + var Z = S && "pre" === S.substring(0, 3); + var af = ac.createElement("LI"); + while (V.firstChild) { + af.appendChild(V.firstChild); + } + var W = [af]; + function ae(al) { + switch (al.nodeType) { + case 1: + if (U.test(al.className)) { + break; + } + if ("BR" === al.nodeName) { + ad(al); + if (al.parentNode) { + al.parentNode.removeChild(al); + } + } else { + for (var an = al.firstChild; an; an = an.nextSibling) { + ae(an); + } + } + break; + case 3: + case 4: + if (Z) { + var am = al.nodeValue; + var aj = am.match(ab); + if (aj) { + var ai = am.substring(0, aj.index); + al.nodeValue = ai; + var ah = am.substring(aj.index + aj[0].length); + if (ah) { + var ak = al.parentNode; + ak.insertBefore(ac.createTextNode(ah), al.nextSibling); + } + ad(al); + if (!ai) { + al.parentNode.removeChild(al); + } + } + } + break; + } + } + function ad(ak) { + while (!ak.nextSibling) { + ak = ak.parentNode; + if (!ak) { + return; + } + } + function ai(al, ar) { + var aq = ar ? al.cloneNode(false) : al; + var ao = al.parentNode; + if (ao) { + var ap = ai(ao, 1); + var an = al.nextSibling; + ap.appendChild(aq); + for (var am = an; am; am = an) { + an = am.nextSibling; + ap.appendChild(am); + } + } + return aq; + } + var ah = ai(ak.nextSibling, 0); + for (var aj; (aj = ah.parentNode) && aj.nodeType === 1; ) { + ah = aj; + } + W.push(ah); + } + for (var Y = 0; Y < W.length; ++Y) { + ae(W[Y]); + } + if (ag === (ag | 0)) { + W[0].setAttribute("value", ag); + } + var aa = ac.createElement("OL"); + aa.className = "linenums"; + var X = Math.max(0, (ag - 1) | 0) || 0; + for (var Y = 0, T = W.length; Y < T; ++Y) { + af = W[Y]; + af.className = "L" + ((Y + X) % 10); + if (!af.firstChild) { + af.appendChild(ac.createTextNode("\xA0")); + } + aa.appendChild(af); + } + V.appendChild(aa); + } + function D(ac) { + var aj = /\bMSIE\b/.test(navigator.userAgent); + var am = /\n/g; + var al = ac.sourceCode; + var an = al.length; + var V = 0; + var aa = ac.spans; + var T = aa.length; + var ah = 0; + var X = ac.decorations; + var Y = X.length; + var Z = 0; + X[Y] = an; + var ar, aq; + for (aq = ar = 0; aq < Y; ) { + if (X[aq] !== X[aq + 2]) { + X[ar++] = X[aq++]; + X[ar++] = X[aq++]; + } else { + aq += 2; + } + } + Y = ar; + for (aq = ar = 0; aq < Y; ) { + var at = X[aq]; + var ab = X[aq + 1]; + var W = aq + 2; + while (W + 2 <= Y && X[W + 1] === ab) { + W += 2; + } + X[ar++] = at; + X[ar++] = ab; + aq = W; + } + Y = X.length = ar; + var ae = null; + while (ah < T) { + var af = aa[ah]; + var S = aa[ah + 2] || an; + var ag = X[Z]; + var ap = X[Z + 2] || an; + var W = Math.min(S, ap); + var ak = aa[ah + 1]; + var U; + if (ak.nodeType !== 1 && (U = al.substring(V, W))) { + if (aj) { + U = U.replace(am, "\r"); + } + ak.nodeValue = U; + var ai = ak.ownerDocument; + var ao = ai.createElement("SPAN"); + ao.className = X[Z + 1]; + var ad = ak.parentNode; + ad.replaceChild(ao, ak); + ao.appendChild(ak); + if (V < S) { + aa[ah + 1] = ak = ai.createTextNode(al.substring(W, S)); + ad.insertBefore(ak, ao.nextSibling); + } + } + V = W; + if (V >= S) { + ah += 2; + } + if (V >= ap) { + Z += 2; + } + } + } + var t = {}; + function c(U, V) { + for (var S = V.length; --S >= 0; ) { + var T = V[S]; + if (!t.hasOwnProperty(T)) { + t[T] = U; + } else { + if (window.console) { + console.warn("cannot override language handler %s", T); + } + } + } + } + function q(T, S) { + if (!(T && t.hasOwnProperty(T))) { + T = /^\s*]*(?:>|$)/], + [j, /^<\!--[\s\S]*?(?:-\->|$)/], + ["lang-", /^<\?([\s\S]+?)(?:\?>|$)/], + ["lang-", /^<%([\s\S]+?)(?:%>|$)/], + [L, /^(?:<[%?]|[%?]>)/], + ["lang-", /^]*>([\s\S]+?)<\/xmp\b[^>]*>/i], + ["lang-js", /^]*>([\s\S]*?)(<\/script\b[^>]*>)/i], + ["lang-css", /^]*>([\s\S]*?)(<\/style\b[^>]*>)/i], + ["lang-in.tag", /^(<\/?[a-z][^<>]*>)/i], + ], + ), + ["default-markup", "htm", "html", "mxml", "xhtml", "xml", "xsl"], + ); + c( + g( + [ + [F, /^[\s]+/, null, " \t\r\n"], + [n, /^(?:\"[^\"]*\"?|\'[^\']*\'?)/, null, "\"'"], + ], + [ + [m, /^^<\/?[a-z](?:[\w.:-]*\w)?|\/?>$/i], + [P, /^(?!style[\s=]|on)[a-z](?:[\w:-]*\w)?/i], + ["lang-uq.val", /^=\s*([^>\'\"\s]*(?:[^>\'\"\s\/]|\/(?=\s)))/], + [L, /^[=<>\/]+/], + ["lang-js", /^on\w+\s*=\s*\"([^\"]+)\"/i], + ["lang-js", /^on\w+\s*=\s*\'([^\']+)\'/i], + ["lang-js", /^on\w+\s*=\s*([^\"\'>\s]+)/i], + ["lang-css", /^style\s*=\s*\"([^\"]+)\"/i], + ["lang-css", /^style\s*=\s*\'([^\']+)\'/i], + ["lang-css", /^style\s*=\s*([^\"\'>\s]+)/i], + ], + ), + ["in.tag"], + ); + c(g([], [[n, /^[\s\S]+/]]), ["uq.val"]); + c(i({ keywords: l, hashComments: true, cStyleComments: true, types: e }), [ + "c", + "cc", + "cpp", + "cxx", + "cyc", + "m", + ]); + c(i({ keywords: "null,true,false" }), ["json"]); + c( + i({ + keywords: R, + hashComments: true, + cStyleComments: true, + verbatimStrings: true, + types: e, + }), + ["cs"], + ); + c(i({ keywords: x, cStyleComments: true }), ["java"]); + c(i({ keywords: H, hashComments: true, multiLineStrings: true }), [ + "bsh", + "csh", + "sh", + ]); + c( + i({ + keywords: I, + hashComments: true, + multiLineStrings: true, + tripleQuotedStrings: true, + }), + ["cv", "py"], + ); + c( + i({ + keywords: s, + hashComments: true, + multiLineStrings: true, + regexLiterals: true, + }), + ["perl", "pl", "pm"], + ); + c( + i({ + keywords: f, + hashComments: true, + multiLineStrings: true, + regexLiterals: true, + }), + ["rb"], + ); + c(i({ keywords: w, cStyleComments: true, regexLiterals: true }), ["js"]); + c( + i({ + keywords: r, + hashComments: 3, + cStyleComments: true, + multilineStrings: true, + tripleQuotedStrings: true, + regexLiterals: true, + }), + ["coffee"], + ); + c(g([], [[C, /^[\s\S]+/]]), ["regex"]); + function d(V) { + var U = V.langExtension; + try { + var S = a(V.sourceNode); + var T = S.sourceCode; + V.sourceCode = T; + V.spans = S.spans; + V.basePos = 0; + q(U, T)(V); + D(V); + } catch (W) { + if ("console" in window) { + console.log(W && W.stack ? W.stack : W); + } + } + } + function y(W, V, U) { + var S = document.createElement("PRE"); + S.innerHTML = W; + if (U) { + Q(S, U); + } + var T = { langExtension: V, numberLines: U, sourceNode: S }; + d(T); + return S.innerHTML; + } + function b(ad) { + function Y(af) { + return document.getElementsByTagName(af); + } + var ac = [Y("pre"), Y("code"), Y("xmp")]; + var T = []; + for (var aa = 0; aa < ac.length; ++aa) { + for (var Z = 0, V = ac[aa].length; Z < V; ++Z) { + T.push(ac[aa][Z]); + } + } + ac = null; + var W = Date; + if (!W.now) { + W = { + now: function () { + return +new Date(); + }, + }; + } + var X = 0; + var S; + var ab = /\blang(?:uage)?-([\w.]+)(?!\S)/; + var ae = /\bprettyprint\b/; + function U() { + var ag = window.PR_SHOULD_USE_CONTINUATION ? W.now() + 250 : Infinity; + for (; X < T.length && W.now() < ag; X++) { + var aj = T[X]; + var ai = aj.className; + if (ai.indexOf("prettyprint") >= 0) { + var ah = ai.match(ab); + var am; + if (!ah && (am = o(aj)) && "CODE" === am.tagName) { + ah = am.className.match(ab); + } + if (ah) { + ah = ah[1]; + } + var al = false; + for (var ak = aj.parentNode; ak; ak = ak.parentNode) { + if ( + (ak.tagName === "pre" || + ak.tagName === "code" || + ak.tagName === "xmp") && + ak.className && + ak.className.indexOf("prettyprint") >= 0 + ) { + al = true; + break; + } + } + if (!al) { + var af = aj.className.match(/\blinenums\b(?::(\d+))?/); + af = af ? (af[1] && af[1].length ? +af[1] : true) : false; + if (af) { + Q(aj, af); + } + S = { langExtension: ah, sourceNode: aj, numberLines: af }; + d(S); + } + } + } + if (X < T.length) { + setTimeout(U, 250); + } else { + if (ad) { + ad(); + } + } + } + U(); + } + window.prettyPrintOne = y; + window.prettyPrint = b; + window.PR = { + createSimpleLexer: g, + registerLangHandler: c, + sourceDecorator: i, + PR_ATTRIB_NAME: P, + PR_ATTRIB_VALUE: n, + PR_COMMENT: j, + PR_DECLARATION: E, + PR_KEYWORD: z, + PR_LITERAL: G, + PR_NOCODE: N, + PR_PLAIN: F, + PR_PUNCTUATION: L, + PR_SOURCE: J, + PR_STRING: C, + PR_TAG: m, + PR_TYPE: O, + }; +})(); +PR.registerLangHandler( + PR.createSimpleLexer( + [], + [ + [PR.PR_DECLARATION, /^]*(?:>|$)/], + [PR.PR_COMMENT, /^<\!--[\s\S]*?(?:-\->|$)/], + [PR.PR_PUNCTUATION, /^(?:<[%?]|[%?]>)/], + ["lang-", /^<\?([\s\S]+?)(?:\?>|$)/], + ["lang-", /^<%([\s\S]+?)(?:%>|$)/], + ["lang-", /^]*>([\s\S]+?)<\/xmp\b[^>]*>/i], + [ + "lang-handlebars", + /^]*type\s*=\s*['"]?text\/x-handlebars-template['"]?\b[^>]*>([\s\S]*?)(<\/script\b[^>]*>)/i, + ], + ["lang-js", /^]*>([\s\S]*?)(<\/script\b[^>]*>)/i], + ["lang-css", /^]*>([\s\S]*?)(<\/style\b[^>]*>)/i], + ["lang-in.tag", /^(<\/?[a-z][^<>]*>)/i], + [PR.PR_DECLARATION, /^{{[#^>/]?\s*[\w.][^}]*}}/], + [PR.PR_DECLARATION, /^{{&?\s*[\w.][^}]*}}/], + [PR.PR_DECLARATION, /^{{{>?\s*[\w.][^}]*}}}/], + [PR.PR_COMMENT, /^{{![^}]*}}/], + ], + ), + ["handlebars", "hbs"], +); +PR.registerLangHandler( + PR.createSimpleLexer( + [[PR.PR_PLAIN, /^[ \t\r\n\f]+/, null, " \t\r\n\f"]], + [ + [ + PR.PR_STRING, + /^\"(?:[^\n\r\f\\\"]|\\(?:\r\n?|\n|\f)|\\[\s\S])*\"/, + null, + ], + [ + PR.PR_STRING, + /^\'(?:[^\n\r\f\\\']|\\(?:\r\n?|\n|\f)|\\[\s\S])*\'/, + null, + ], + ["lang-css-str", /^url\(([^\)\"\']*)\)/i], + [ + PR.PR_KEYWORD, + /^(?:url|rgb|\!important|@import|@page|@media|@charset|inherit)(?=[^\-\w]|$)/i, + null, + ], + [ + "lang-css-kw", + /^(-?(?:[_a-z]|(?:\\[0-9a-f]+ ?))(?:[_a-z0-9\-]|\\(?:\\[0-9a-f]+ ?))*)\s*:/i, + ], + [PR.PR_COMMENT, /^\/\*[^*]*\*+(?:[^\/*][^*]*\*+)*\//], + [PR.PR_COMMENT, /^(?:)/], + [PR.PR_LITERAL, /^(?:\d+|\d*\.\d+)(?:%|[a-z]+)?/i], + [PR.PR_LITERAL, /^#(?:[0-9a-f]{3}){1,2}/i], + [ + PR.PR_PLAIN, + /^-?(?:[_a-z]|(?:\\[\da-f]+ ?))(?:[_a-z\d\-]|\\(?:\\[\da-f]+ ?))*/i, + ], + [PR.PR_PUNCTUATION, /^[^\s\w\'\"]+/], + ], + ), + ["css"], +); +PR.registerLangHandler( + PR.createSimpleLexer( + [], + [ + [ + PR.PR_KEYWORD, + /^-?(?:[_a-z]|(?:\\[\da-f]+ ?))(?:[_a-z\d\-]|\\(?:\\[\da-f]+ ?))*/i, + ], + ], + ), + ["css-kw"], +); +PR.registerLangHandler( + PR.createSimpleLexer([], [[PR.PR_STRING, /^[^\)\"\']+/]]), + ["css-str"], +); diff --git a/coverage/sort-arrow-sprite.png b/coverage/sort-arrow-sprite.png new file mode 100644 index 0000000000000000000000000000000000000000..6ed68316eb3f65dec9063332d2f69bf3093bbfab GIT binary patch literal 138 zcmeAS@N?(olHy`uVBq!ia0vp^>_9Bd!3HEZxJ@+%Qh}Z>jv*C{$p!i!8j}?a+@3A= zIAGwzjijN=FBi!|L1t?LM;Q;gkwn>2cAy-KV{dn nf0J1DIvEHQu*n~6U}x}qyky7vi4|9XhBJ7&`njxgN@xNA8m%nc literal 0 HcmV?d00001 diff --git a/coverage/sorter.js b/coverage/sorter.js new file mode 100644 index 000000000..dae7b4a41 --- /dev/null +++ b/coverage/sorter.js @@ -0,0 +1,209 @@ +/* eslint-disable */ +var addSorting = (function () { + "use strict"; + var cols, + currentSort = { + index: 0, + desc: false, + }; + + // returns the summary table element + function getTable() { + return document.querySelector(".coverage-summary"); + } + // returns the thead element of the summary table + function getTableHeader() { + return getTable().querySelector("thead tr"); + } + // returns the tbody element of the summary table + function getTableBody() { + return getTable().querySelector("tbody"); + } + // returns the th element for nth column + function getNthColumn(n) { + return getTableHeader().querySelectorAll("th")[n]; + } + + function onFilterInput() { + const searchValue = document.getElementById("fileSearch").value; + const rows = document.getElementsByTagName("tbody")[0].children; + + // Try to create a RegExp from the searchValue. If it fails (invalid regex), + // it will be treated as a plain text search + let searchRegex; + try { + searchRegex = new RegExp(searchValue, "i"); // 'i' for case-insensitive + } catch (error) { + searchRegex = null; + } + + for (let i = 0; i < rows.length; i++) { + const row = rows[i]; + let isMatch = false; + + if (searchRegex) { + // If a valid regex was created, use it for matching + isMatch = searchRegex.test(row.textContent); + } else { + // Otherwise, fall back to the original plain text search + isMatch = row.textContent + .toLowerCase() + .includes(searchValue.toLowerCase()); + } + + row.style.display = isMatch ? "" : "none"; + } + } + + // loads the search box + function addSearchBox() { + var template = document.getElementById("filterTemplate"); + var templateClone = template.content.cloneNode(true); + templateClone.getElementById("fileSearch").oninput = onFilterInput; + template.parentElement.appendChild(templateClone); + } + + // loads all columns + function loadColumns() { + var colNodes = getTableHeader().querySelectorAll("th"), + colNode, + cols = [], + col, + i; + + for (i = 0; i < colNodes.length; i += 1) { + colNode = colNodes[i]; + col = { + key: colNode.getAttribute("data-col"), + sortable: !colNode.getAttribute("data-nosort"), + type: colNode.getAttribute("data-type") || "string", + }; + cols.push(col); + if (col.sortable) { + col.defaultDescSort = col.type === "number"; + colNode.innerHTML = colNode.innerHTML + ''; + } + } + return cols; + } + // attaches a data attribute to every tr element with an object + // of data values keyed by column name + function loadRowData(tableRow) { + var tableCols = tableRow.querySelectorAll("td"), + colNode, + col, + data = {}, + i, + val; + for (i = 0; i < tableCols.length; i += 1) { + colNode = tableCols[i]; + col = cols[i]; + val = colNode.getAttribute("data-value"); + if (col.type === "number") { + val = Number(val); + } + data[col.key] = val; + } + return data; + } + // loads all row data + function loadData() { + var rows = getTableBody().querySelectorAll("tr"), + i; + + for (i = 0; i < rows.length; i += 1) { + rows[i].data = loadRowData(rows[i]); + } + } + // sorts the table using the data for the ith column + function sortByIndex(index, desc) { + var key = cols[index].key, + sorter = function (a, b) { + a = a.data[key]; + b = b.data[key]; + return a < b ? -1 : a > b ? 1 : 0; + }, + finalSorter = sorter, + tableBody = document.querySelector(".coverage-summary tbody"), + rowNodes = tableBody.querySelectorAll("tr"), + rows = [], + i; + + if (desc) { + finalSorter = function (a, b) { + return -1 * sorter(a, b); + }; + } + + for (i = 0; i < rowNodes.length; i += 1) { + rows.push(rowNodes[i]); + tableBody.removeChild(rowNodes[i]); + } + + rows.sort(finalSorter); + + for (i = 0; i < rows.length; i += 1) { + tableBody.appendChild(rows[i]); + } + } + // removes sort indicators for current column being sorted + function removeSortIndicators() { + var col = getNthColumn(currentSort.index), + cls = col.className; + + cls = cls.replace(/ sorted$/, "").replace(/ sorted-desc$/, ""); + col.className = cls; + } + // adds sort indicators for current column being sorted + function addSortIndicators() { + getNthColumn(currentSort.index).className += currentSort.desc + ? " sorted-desc" + : " sorted"; + } + // adds event listeners for all sorter widgets + function enableUI() { + var i, + el, + ithSorter = function ithSorter(i) { + var col = cols[i]; + + return function () { + var desc = col.defaultDescSort; + + if (currentSort.index === i) { + desc = !currentSort.desc; + } + sortByIndex(i, desc); + removeSortIndicators(); + currentSort.index = i; + currentSort.desc = desc; + addSortIndicators(); + }; + }; + for (i = 0; i < cols.length; i += 1) { + if (cols[i].sortable) { + // add the click event handler on the th so users + // dont have to click on those tiny arrows + el = getNthColumn(i).querySelector(".sorter").parentElement; + if (el.addEventListener) { + el.addEventListener("click", ithSorter(i)); + } else { + el.attachEvent("onclick", ithSorter(i)); + } + } + } + } + // adds sorting functionality to the UI + return function () { + if (!getTable()) { + return; + } + cols = loadColumns(); + loadData(); + addSearchBox(); + addSortIndicators(); + enableUI(); + }; +})(); + +window.addEventListener("load", addSorting); diff --git a/coverage/types/docsYaml.ts.html b/coverage/types/docsYaml.ts.html new file mode 100644 index 000000000..06ddac807 --- /dev/null +++ b/coverage/types/docsYaml.ts.html @@ -0,0 +1,358 @@ + + + + + + Code coverage report for types/docsYaml.ts + + + + + + + + + +
+
+

All files / types docsYaml.ts

+
+ +
+ 100% + Statements + 10/10 +
+ + +
+ 100% + Branches + 19/19 +
+ + +
+ 100% + Functions + 5/5 +
+ + +
+ 100% + Lines + 10/10 +
+ + +
+

+ Press n or j to go to the next uncovered block, b, p or k for the previous block. +

+ +
+
+

+
1 +2 +3 +4 +5 +6 +7 +8 +9 +10 +11 +12 +13 +14 +15 +16 +17 +18 +19 +20 +21 +22 +23 +24 +25 +26 +27 +28 +29 +30 +31 +32 +33 +34 +35 +36 +37 +38 +39 +40 +41 +42 +43 +44 +45 +46 +47 +48 +49 +50 +51 +52 +53 +54 +55 +56 +57 +58 +59 +60 +61 +62 +63 +64 +65 +66 +67 +68 +69 +70 +71 +72 +73 +74 +75 +76 +77 +78 +79 +80 +81 +82 +83 +84 +85 +86 +87 +88 +89 +90 +91 +92  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +3x +34x +  +  +3x +  +  +9x +  +  +  +  +3x +41x +  +  +3x +21x +  +  +  +  +3x +  +  +43x +  + 
// Types for parsing Fern's docs.yml structure
+ 
+export interface PageConfig {
+  page: string;
+  path: string;
+  slug?: string;
+  hidden?: boolean;
+  noindex?: boolean;
+}
+ 
+export interface SectionConfig {
+  section: string;
+  slug?: string;
+  "skip-slug"?: boolean;
+  hidden?: boolean;
+  contents: NavigationItem[];
+  path?: string; // Optional overview page
+}
+ 
+export interface LinkConfig {
+  link: string;
+  href: string;
+}
+ 
+export interface ApiConfig {
+  api: string;
+  "api-name": string;
+  slug?: string;
+  "skip-slug"?: boolean;
+  hidden?: boolean;
+  flattened?: boolean;
+  paginated?: boolean;
+}
+ 
+export interface ChangelogConfig {
+  changelog: string;
+  slug?: string;
+}
+ 
+export type NavigationItem =
+  | PageConfig
+  | SectionConfig
+  | LinkConfig
+  | ApiConfig
+  | ChangelogConfig;
+ 
+export interface TabConfig {
+  "display-name": string;
+  slug?: string;
+  "skip-slug"?: boolean;
+}
+ 
+export interface DocsYml {
+  tabs?: Record<string, TabConfig>;
+  navigation: Array<{
+    tab: string;
+    layout: NavigationItem[];
+  }>;
+}
+ 
+// ============================================================================
+// Type Guards
+// ============================================================================
+ 
+export const isPageConfig = (item: NavigationItem): item is PageConfig => {
+  return item && typeof item === "object" && "page" in item && "path" in item;
+};
+ 
+export const isSectionConfig = (
+  item: NavigationItem,
+): item is SectionConfig => {
+  return (
+    item && typeof item === "object" && "section" in item && "contents" in item
+  );
+};
+ 
+export const isLinkConfig = (item: NavigationItem): item is LinkConfig => {
+  return item && typeof item === "object" && "link" in item && "href" in item;
+};
+ 
+export const isApiConfig = (item: NavigationItem): item is ApiConfig => {
+  return (
+    item && typeof item === "object" && "api" in item && "api-name" in item
+  );
+};
+ 
+export const isChangelogConfig = (
+  item: NavigationItem,
+): item is ChangelogConfig => {
+  return item && typeof item === "object" && "changelog" in item;
+};
+ 
+ +
+
+ + + + + + + + \ No newline at end of file diff --git a/coverage/types/index.html b/coverage/types/index.html new file mode 100644 index 000000000..e9aa67bbc --- /dev/null +++ b/coverage/types/index.html @@ -0,0 +1,116 @@ + + + + + + Code coverage report for types + + + + + + + + + +
+
+

All files types

+
+ +
+ 100% + Statements + 10/10 +
+ + +
+ 100% + Branches + 19/19 +
+ + +
+ 100% + Functions + 5/5 +
+ + +
+ 100% + Lines + 10/10 +
+ + +
+

+ Press n or j to go to the next uncovered block, b, p or k for the previous block. +

+ +
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
FileStatementsBranchesFunctionsLines
docsYaml.ts +
+
100%10/10100%19/19100%5/5100%10/10
+
+
+
+ + + + + + + + \ No newline at end of file diff --git a/coverage/uploaders/algolia.ts.html b/coverage/uploaders/algolia.ts.html new file mode 100644 index 000000000..a0dcf5c72 --- /dev/null +++ b/coverage/uploaders/algolia.ts.html @@ -0,0 +1,472 @@ + + + + + + Code coverage report for uploaders/algolia.ts + + + + + + + + + +
+
+

All files / uploaders algolia.ts

+
+ +
+ 36.36% + Statements + 12/33 +
+ + +
+ 62.5% + Branches + 5/8 +
+ + +
+ 50% + Functions + 1/2 +
+ + +
+ 36.36% + Lines + 12/33 +
+ + +
+

+ Press n or j to go to the next uncovered block, b, p or k for the previous block. +

+ +
+
+

+
1 +2 +3 +4 +5 +6 +7 +8 +9 +10 +11 +12 +13 +14 +15 +16 +17 +18 +19 +20 +21 +22 +23 +24 +25 +26 +27 +28 +29 +30 +31 +32 +33 +34 +35 +36 +37 +38 +39 +40 +41 +42 +43 +44 +45 +46 +47 +48 +49 +50 +51 +52 +53 +54 +55 +56 +57 +58 +59 +60 +61 +62 +63 +64 +65 +66 +67 +68 +69 +70 +71 +72 +73 +74 +75 +76 +77 +78 +79 +80 +81 +82 +83 +84 +85 +86 +87 +88 +89 +90 +91 +92 +93 +94 +95 +96 +97 +98 +99 +100 +101 +102 +103 +104 +105 +106 +107 +108 +109 +110 +111 +112 +113 +114 +115 +116 +117 +118 +119 +120 +121 +122 +123 +124 +125 +126 +127 +128 +129 +130  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +1x +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +1x +  +  +  +  +  +  +4x +4x +4x +  +4x +2x +2x +  +  +2x +  +  +2x +2x +  +  +2x +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  + 
import { algoliasearch } from "algoliasearch";
+ 
+import type { AlgoliaRecord } from "@/content-indexer/types/algolia.js";
+import { truncateRecord } from "@/content-indexer/utils/truncate-record.js";
+ 
+/**
+ * Builds an Algolia index name with branch and type scoping.
+ * Pattern: {branchId}_{baseName}[_{indexerType}]
+ *
+ * Examples:
+ * - main_alchemy_docs (main branch, main content)
+ * - main_alchemy_docs_sdk (main branch, SDK content)
+ * - abc_alchemy_docs (branch-abc, main content)
+ * - abc_alchemy_docs_sdk (branch-abc, SDK content)
+ */
+const buildIndexName = (
+  base: string,
+  indexerType: "main" | "sdk" | "changelog",
+  branchId: string,
+): string => {
+  const parts = [branchId, base];
+ 
+  // Add type suffix (except for main content)
+  if (indexerType !== "main") {
+    parts.push(indexerType);
+  }
+ 
+  return parts.join("_");
+};
+ 
+/**
+ * Uploads records to Algolia using atomic index swap for zero-downtime updates.
+ *
+ * Process:
+ * 1. Upload all records to a temporary index
+ * 2. Copy settings/synonyms from production index (if exists)
+ * 3. Atomically swap temp index to production
+ *
+ * This ensures users never see empty search results during updates.
+ *
+ * @param records - Algolia records to upload
+ * @param options - Configuration options
+ * @param options.indexerType - Type of indexer ("main", "sdk", or "changelog")
+ * @param options.branchId - Branch identifier for index naming (e.g., "main", "branch-abc")
+ */
+export const uploadToAlgolia = async (
+  records: AlgoliaRecord[],
+  options: {
+    indexerType: "main" | "sdk" | "changelog";
+    branchId: string;
+  },
+): Promise<void> => {
+  const appId = process.env.ALGOLIA_APP_ID;
+  const adminKey = process.env.ALGOLIA_ADMIN_API_KEY;
+  const baseName = process.env.ALGOLIA_INDEX_NAME_BASE;
+ 
+  if (!appId || !adminKey) {
+    console.warn("⚠️  Algolia credentials not found. Skipping Algolia upload.");
+    console.warn(
+      "   Set ALGOLIA_APP_ID and ALGOLIA_ADMIN_API_KEY to enable search indexing.",
+    );
+    return;
+  }
+ 
+  Eif (!baseName) {
+    console.warn(
+      "⚠️  ALGOLIA_INDEX_NAME_BASE not set. Skipping Algolia upload.",
+    );
+    return;
+  }
+ 
+  const targetIndexName = buildIndexName(
+    baseName,
+    options.indexerType,
+    options.branchId,
+  );
+ 
+  const client = algoliasearch(appId, adminKey);
+  const tempIndexName = `${targetIndexName}_temp`;
+ 
+  console.info(
+    `📤 Uploading ${records.length} records to Algolia (${targetIndexName})...`,
+  );
+ 
+  // Truncate records to fit Algolia's 100KB limit
+  const truncatedRecords = records.map(truncateRecord);
+ 
+  try {
+    // 1. Upload all records to temporary index
+    await client.saveObjects({
+      indexName: tempIndexName,
+      objects: truncatedRecords as unknown as Array<Record<string, unknown>>,
+    });
+ 
+    console.info(`   ✓ Uploaded ${records.length} records to ${tempIndexName}`);
+ 
+    // 2. Copy settings/synonyms from production index (if it exists)
+    try {
+      await client.operationIndex({
+        indexName: targetIndexName,
+        operationIndexParams: {
+          operation: "copy",
+          destination: tempIndexName,
+          scope: ["settings", "synonyms", "rules"],
+        },
+      });
+      console.info("   ✓ Copied settings from production index");
+    } catch (_error) {
+      console.info(
+        "   ℹ️  No existing production index found (might be first run)",
+      );
+    }
+ 
+    // 3. Atomic swap: move temp index to production
+    console.info(`   🔄 Swapping ${tempIndexName} → ${targetIndexName}...`);
+    await client.operationIndex({
+      indexName: tempIndexName,
+      operationIndexParams: {
+        operation: "move",
+        destination: targetIndexName,
+      },
+    });
+ 
+    console.info(`✅ Successfully updated Algolia index: ${targetIndexName}`);
+  } catch (error) {
+    console.error("❌ Failed to upload to Algolia:", error);
+    throw error;
+  }
+};
+ 
+ +
+
+ + + + + + + + \ No newline at end of file diff --git a/coverage/uploaders/index.html b/coverage/uploaders/index.html new file mode 100644 index 000000000..e55596976 --- /dev/null +++ b/coverage/uploaders/index.html @@ -0,0 +1,131 @@ + + + + + + Code coverage report for uploaders + + + + + + + + + +
+
+

All files uploaders

+
+ +
+ 53.33% + Statements + 32/60 +
+ + +
+ 68.18% + Branches + 15/22 +
+ + +
+ 75% + Functions + 6/8 +
+ + +
+ 53.33% + Lines + 32/60 +
+ + +
+

+ Press n or j to go to the next uncovered block, b, p or k for the previous block. +

+ +
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
FileStatementsBranchesFunctionsLines
algolia.ts +
+
36.36%12/3362.5%5/850%1/236.36%12/33
redis.ts +
+
74.07%20/2771.42%10/1483.33%5/674.07%20/27
+
+
+
+ + + + + + + + \ No newline at end of file diff --git a/coverage/uploaders/redis.ts.html b/coverage/uploaders/redis.ts.html new file mode 100644 index 000000000..77398b556 --- /dev/null +++ b/coverage/uploaders/redis.ts.html @@ -0,0 +1,364 @@ + + + + + + Code coverage report for uploaders/redis.ts + + + + + + + + + +
+
+

All files / uploaders redis.ts

+
+ +
+ 74.07% + Statements + 20/27 +
+ + +
+ 71.42% + Branches + 10/14 +
+ + +
+ 83.33% + Functions + 5/6 +
+ + +
+ 74.07% + Lines + 20/27 +
+ + +
+

+ Press n or j to go to the next uncovered block, b, p or k for the previous block. +

+ +
+
+

+
1 +2 +3 +4 +5 +6 +7 +8 +9 +10 +11 +12 +13 +14 +15 +16 +17 +18 +19 +20 +21 +22 +23 +24 +25 +26 +27 +28 +29 +30 +31 +32 +33 +34 +35 +36 +37 +38 +39 +40 +41 +42 +43 +44 +45 +46 +47 +48 +49 +50 +51 +52 +53 +54 +55 +56 +57 +58 +59 +60 +61 +62 +63 +64 +65 +66 +67 +68 +69 +70 +71 +72 +73 +74 +75 +76 +77 +78 +79 +80 +81 +82 +83 +84 +85 +86 +87 +88 +89 +90 +91 +92 +93 +94  +  +  +  +  +  +  +  +  +1x +5x +  +5x +  +  +5x +  +  +  +  +  +  +  +  +  +  +  +  +1x +  +  +  +  +  +  +  +5x +  +  +5x +5x +  +  +5x +  +  +  +  +  +5x +  +5x +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +5x +  +3x +  +5x +5x +  +  +5x +  +  +  +  +5x +5x +5x +  +  +  +  +  +  +5x +  + 
import type {
+  NavigationTree,
+  NavigationTreesByTab,
+} from "@/content-indexer/types/navigation.js";
+import type { PathIndex } from "@/content-indexer/types/pathIndex.js";
+import { mergeWalletsNavTree } from "@/content-indexer/utils/nav-tree-merge.js";
+import { getRedis } from "@/content-indexer/utils/redis.js";
+ 
+// Helper to count nav items recursively
+const countItems = (items: NavigationTree): number => {
+  return items.reduce((sum, item) => {
+    const childCount =
+      item.type === "section" || item.type === "api-section"
+        ? countItems(item.children)
+        : 0;
+    return sum + 1 + childCount;
+  }, 0);
+};
+ 
+/**
+ * Stores path index and navigation trees to Redis with branch scoping.
+ *
+ * @param pathIndex - The path index to store
+ * @param navigationTrees - Navigation trees (optional for SDK/changelog indexers)
+ * @param options - Configuration options
+ * @param options.branchId - Branch identifier for Redis keys (e.g., "main", "branch-abc123")
+ * @param options.indexerType - Type of indexer ("main", "sdk", or "changelog")
+ */
+export const storeToRedis = async (
+  pathIndex: PathIndex,
+  navigationTrees: NavigationTreesByTab | undefined,
+  options: {
+    branchId: string;
+    indexerType: "main" | "sdk" | "changelog";
+  },
+): Promise<void> => {
+  const redis = getRedis();
+ 
+  // Store path index with branch scope
+  const pathIndexKey = `${options.branchId}/path-index:${options.indexerType}`;
+  const pathIndexPromise = redis
+    .set(pathIndexKey, JSON.stringify(pathIndex, null, 2))
+    .then(() => {
+      console.info(
+        `✅ Path index saved to Redis (${Object.keys(pathIndex).length} routes) -> ${pathIndexKey}`,
+      );
+    });
+ 
+  // Handle navigation trees
+  let navTreePromises: Promise<void>[] = [];
+ 
+  Iif (options.indexerType === "sdk" && navigationTrees?.wallets) {
+    // SDK indexer: merge SDK section into existing wallets nav tree
+    const navTreeKey = `${options.branchId}/nav-tree:wallets`;
+    const existingTree = await redis.get<NavigationTree>(navTreeKey);
+ 
+    const mergedTree = mergeWalletsNavTree(
+      navigationTrees.wallets,
+      existingTree,
+      "sdk",
+    );
+ 
+    navTreePromises = [
+      redis.set(navTreeKey, JSON.stringify(mergedTree, null, 2)).then(() => {
+        console.info(
+          `✅ Updated wallets nav tree with SDK refs (${countItems(mergedTree)} total items) -> ${navTreeKey}`,
+        );
+      }),
+    ];
+  } else if (navigationTrees) {
+    // Main indexer: store all navigation trees
+    navTreePromises = Object.entries(navigationTrees).map(
+      async ([tab, navTree]) => {
+        const redisKey = `${options.branchId}/nav-tree:${tab}`;
+        let finalTree = navTree;
+ 
+        // Main indexer: preserve SDK references in wallets tab
+        Iif (tab === "wallets" && options.indexerType === "main") {
+          const existingTree = await redis.get<NavigationTree>(redisKey);
+          finalTree = mergeWalletsNavTree(navTree, existingTree, "main");
+        }
+ 
+        const itemCount = countItems(finalTree);
+        await redis.set(redisKey, JSON.stringify(finalTree, null, 2));
+        console.info(
+          `✅ Navigation tree for '${tab}' saved to Redis (${itemCount} items) -> ${redisKey}`,
+        );
+      },
+    );
+  }
+ 
+  await Promise.all([pathIndexPromise, ...navTreePromises]);
+};
+ 
+ +
+
+ + + + + + + + \ No newline at end of file diff --git a/coverage/utils/filesystem.ts.html b/coverage/utils/filesystem.ts.html new file mode 100644 index 000000000..be788ece7 --- /dev/null +++ b/coverage/utils/filesystem.ts.html @@ -0,0 +1,298 @@ + + + + + + Code coverage report for utils/filesystem.ts + + + + + + + + + +
+
+

All files / utils filesystem.ts

+
+ +
+ 12% + Statements + 3/25 +
+ + +
+ 0% + Branches + 0/4 +
+ + +
+ 0% + Functions + 0/3 +
+ + +
+ 12% + Lines + 3/25 +
+ + +
+

+ Press n or j to go to the next uncovered block, b, p or k for the previous block. +

+ +
+
+

+
1 +2 +3 +4 +5 +6 +7 +8 +9 +10 +11 +12 +13 +14 +15 +16 +17 +18 +19 +20 +21 +22 +23 +24 +25 +26 +27 +28 +29 +30 +31 +32 +33 +34 +35 +36 +37 +38 +39 +40 +41 +42 +43 +44 +45 +46 +47 +48 +49 +50 +51 +52 +53 +54 +55 +56 +57 +58 +59 +60 +61 +62 +63 +64 +65 +66 +67 +68 +69 +70 +71 +72  +  +  +  +  +  +  +  +  +  +2x +  +  +  +  +  +  +  +  +  +  +  +  +  +  +2x +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +2x +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  + 
import { promises as fs } from "fs";
+import matter from "gray-matter";
+import yaml from "js-yaml";
+import path from "path";
+ 
+import type { DocsYml } from "@/content-indexer/types/docsYaml.js";
+ 
+/**
+ * Reads a file from the local filesystem
+ */
+export const readLocalFile = async (
+  filePath: string,
+): Promise<string | null> => {
+  try {
+    const content = await fs.readFile(filePath, "utf-8");
+    return content;
+  } catch (error) {
+    console.warn(`Failed to read file: ${filePath}`, error);
+    return null;
+  }
+};
+ 
+/**
+ * Reads and parses a local docs.yml file
+ */
+export const readLocalDocsYml = async (
+  baseDir: string,
+): Promise<DocsYml | null> => {
+  try {
+    const docsYmlPath = path.join(baseDir, "docs.yml");
+    const content = await readLocalFile(docsYmlPath);
+ 
+    if (!content) {
+      throw new Error(`Failed to read docs.yml from ${docsYmlPath}`);
+    }
+ 
+    const docsYml = yaml.load(content) as DocsYml;
+    return docsYml;
+  } catch (error) {
+    console.error(`Error reading/parsing docs.yml from ${baseDir}:`, error);
+    return null;
+  }
+};
+ 
+/**
+ * Reads a local MDX file and parses its frontmatter
+ */
+export const readLocalMdxFile = async (
+  filePath: string,
+): Promise<{
+  frontmatter: Record<string, unknown>;
+  content: string;
+} | null> => {
+  try {
+    const fileContent = await readLocalFile(filePath);
+ 
+    if (!fileContent) {
+      return null;
+    }
+ 
+    const { data, content } = matter(fileContent);
+ 
+    return {
+      frontmatter: data,
+      content,
+    };
+  } catch (error) {
+    console.warn(`Failed to parse MDX file: ${filePath}`, error);
+    return null;
+  }
+};
+ 
+ +
+
+ + + + + + + + \ No newline at end of file diff --git a/coverage/utils/github.ts.html b/coverage/utils/github.ts.html new file mode 100644 index 000000000..4f85400e6 --- /dev/null +++ b/coverage/utils/github.ts.html @@ -0,0 +1,691 @@ + + + + + + Code coverage report for utils/github.ts + + + + + + + + + +
+
+

All files / utils github.ts

+
+ +
+ 6% + Statements + 3/50 +
+ + +
+ 0% + Branches + 0/30 +
+ + +
+ 0% + Functions + 0/8 +
+ + +
+ 6.25% + Lines + 3/48 +
+ + +
+

+ Press n or j to go to the next uncovered block, b, p or k for the previous block. +

+ +
+
+

+
1 +2 +3 +4 +5 +6 +7 +8 +9 +10 +11 +12 +13 +14 +15 +16 +17 +18 +19 +20 +21 +22 +23 +24 +25 +26 +27 +28 +29 +30 +31 +32 +33 +34 +35 +36 +37 +38 +39 +40 +41 +42 +43 +44 +45 +46 +47 +48 +49 +50 +51 +52 +53 +54 +55 +56 +57 +58 +59 +60 +61 +62 +63 +64 +65 +66 +67 +68 +69 +70 +71 +72 +73 +74 +75 +76 +77 +78 +79 +80 +81 +82 +83 +84 +85 +86 +87 +88 +89 +90 +91 +92 +93 +94 +95 +96 +97 +98 +99 +100 +101 +102 +103 +104 +105 +106 +107 +108 +109 +110 +111 +112 +113 +114 +115 +116 +117 +118 +119 +120 +121 +122 +123 +124 +125 +126 +127 +128 +129 +130 +131 +132 +133 +134 +135 +136 +137 +138 +139 +140 +141 +142 +143 +144 +145 +146 +147 +148 +149 +150 +151 +152 +153 +154 +155 +156 +157 +158 +159 +160 +161 +162 +163 +164 +165 +166 +167 +168 +169 +170 +171 +172 +173 +174 +175 +176 +177 +178 +179 +180 +181 +182 +183 +184 +185 +186 +187 +188 +189 +190 +191 +192 +193 +194 +195 +196 +197 +198 +199 +200 +201 +202 +203  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +10x +  +  +  +  +  +  +10x +  +  +  +  +  +  +  +10x +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  + 
import { Octokit, RequestError } from "octokit";
+ 
+type GetContentResponse = Awaited<
+  ReturnType<InstanceType<typeof Octokit>["rest"]["repos"]["getContent"]>
+>;
+type GetContentData = GetContentResponse["data"];
+ 
+type ContentDirectoryItem = Extract<GetContentData, Array<unknown>>[number];
+ 
+// ============================================================================
+// Repository Configuration
+// ============================================================================
+ 
+export interface RepoConfig {
+  owner: string;
+  repo: string;
+  branch: string;
+  docsPrefix: string; // "fern" or "docs" - folder where MDX files are stored
+  stripPathPrefix?: string; // Optional prefix to strip from paths in docs.yml (e.g., "wallets/")
+}
+ 
+export const DOCS_REPO: RepoConfig = {
+  owner: "alchemyplatform",
+  repo: "docs",
+  branch: "main",
+  docsPrefix: "fern",
+};
+ 
+export const WALLET_REPO: RepoConfig = {
+  owner: "alchemyplatform",
+  repo: "aa-sdk",
+  branch: "main",
+  docsPrefix: "docs",
+  stripPathPrefix: "wallets/", // aa-sdk docs.yml uses "wallets/pages/..." but actual files are at "docs/pages/..."
+};
+ 
+const octokit = new Octokit({
+  auth: process.env.GH_TOKEN,
+});
+ 
+function isRequestError(error: unknown): error is RequestError {
+  return error instanceof RequestError;
+}
+ 
+/**
+ * Fetch a single file's text content from GitHub
+ */
+export async function fetchFileFromGitHub(
+  filePath: string,
+  repoConfig: RepoConfig = DOCS_REPO,
+): Promise<string | null> {
+  try {
+    const { data } = await octokit.rest.repos.getContent({
+      owner: repoConfig.owner,
+      repo: repoConfig.repo,
+      path: filePath,
+      ref: repoConfig.branch,
+      mediaType: {
+        format: "raw",
+      },
+    });
+ 
+    // When using raw format, data is a string
+    return data as unknown as string;
+  } catch (error: unknown) {
+    if (isRequestError(error) && error.status === 404) {
+      return null; // File doesn't exist
+    }
+    console.error(`Error fetching ${filePath} from ${repoConfig.repo}:`, error);
+    return null;
+  }
+}
+ 
+/**
+ * Fetch directory contents from GitHub (non-recursive)
+ */
+export async function fetchGitHubDirectory(
+  dirPath: string,
+  repoConfig: RepoConfig = DOCS_REPO,
+): Promise<ContentDirectoryItem[]> {
+  try {
+    const { data } = await octokit.rest.repos.getContent({
+      owner: repoConfig.owner,
+      repo: repoConfig.repo,
+      path: dirPath,
+      ref: repoConfig.branch,
+    });
+ 
+    // GitHub API returns array for directories, object for files
+    if (!Array.isArray(data)) {
+      throw new Error(`Expected directory but got file: ${dirPath}`);
+    }
+ 
+    return data;
+  } catch (error) {
+    console.error(
+      `Error fetching directory ${dirPath} from ${repoConfig.repo}:`,
+      error,
+    );
+    throw error;
+  }
+}
+ 
+/**
+ * Fetch multiple files in parallel with rate limiting
+ */
+export async function fetchMultipleFiles(
+  filePaths: string[],
+  concurrency: number = 10,
+  repoConfig: RepoConfig = DOCS_REPO,
+): Promise<Map<string, string>> {
+  const results = new Map<string, string>();
+ 
+  // Process in batches to respect rate limits
+  // Octokit has built-in rate limiting, but we still batch for efficiency
+  for (let i = 0; i < filePaths.length; i += concurrency) {
+    const batch = filePaths.slice(i, i + concurrency);
+ 
+    const batchResults = await Promise.all(
+      batch.map(async (path) => {
+        const content = await fetchFileFromGitHub(path, repoConfig);
+        return { path, content };
+      }),
+    );
+ 
+    for (const { path, content } of batchResults) {
+      if (content) {
+        results.set(path, content);
+      }
+    }
+ 
+    // Small delay between batches to be conservative with rate limits
+    if (i + concurrency < filePaths.length) {
+      await new Promise((resolve) => setTimeout(resolve, 100));
+    }
+  }
+ 
+  return results;
+}
+ 
+/**
+ * Check if a file exists on GitHub
+ */
+export async function fileExistsOnGitHub(
+  filePath: string,
+  repoConfig: RepoConfig = DOCS_REPO,
+): Promise<boolean> {
+  try {
+    await octokit.rest.repos.getContent({
+      owner: repoConfig.owner,
+      repo: repoConfig.repo,
+      path: filePath,
+      ref: repoConfig.branch,
+    });
+    return true;
+  } catch (error: unknown) {
+    if (isRequestError(error) && error.status === 404) {
+      return false;
+    }
+    // For other errors, log but return false to be safe
+    console.error(`Error checking if ${filePath} exists:`, error);
+    return false;
+  }
+}
+ 
+/**
+ * Get file metadata without downloading content
+ */
+export async function getGitHubFileMetadata(
+  filePath: string,
+  repoConfig: RepoConfig = DOCS_REPO,
+): Promise<{ sha: string; size: number } | null> {
+  try {
+    const { data } = await octokit.rest.repos.getContent({
+      owner: repoConfig.owner,
+      repo: repoConfig.repo,
+      path: filePath,
+      ref: repoConfig.branch,
+    });
+ 
+    // Ensure we got a file, not a directory
+    if (Array.isArray(data)) {
+      return null;
+    }
+ 
+    // Type guard for file object
+    if ("sha" in data && "size" in data) {
+      return {
+        sha: data.sha,
+        size: data.size,
+      };
+    }
+ 
+    return null;
+  } catch (error: unknown) {
+    if (isRequestError(error) && error.status === 404) {
+      return null;
+    }
+    console.error(`Error fetching metadata for ${filePath}:`, error);
+    return null;
+  }
+}
+ 
+ +
+
+ + + + + + + + \ No newline at end of file diff --git a/coverage/utils/index.html b/coverage/utils/index.html new file mode 100644 index 000000000..d117c9c96 --- /dev/null +++ b/coverage/utils/index.html @@ -0,0 +1,236 @@ + + + + + + Code coverage report for utils + + + + + + + + + +
+
+

All files utils

+
+ +
+ 50.52% + Statements + 96/190 +
+ + +
+ 49.57% + Branches + 58/117 +
+ + +
+ 53.12% + Functions + 17/32 +
+ + +
+ 51.07% + Lines + 95/186 +
+ + +
+

+ Press n or j to go to the next uncovered block, b, p or k for the previous block. +

+ +
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
FileStatementsBranchesFunctionsLines
filesystem.ts +
+
12%3/250%0/40%0/312%3/25
github.ts +
+
6%3/500%0/300%0/86.25%3/48
nav-tree-merge.ts +
+
11.53%3/260%0/220%0/411.53%3/26
navigation-helpers.ts +
+
100%2/2100%0/0100%1/1100%2/2
normalization.ts +
+
100%6/6100%4/4100%2/2100%5/5
openapi.ts +
+
97.61%41/4297.5%39/40100%9/9100%41/41
openrpc.ts +
+
100%2/2100%4/4100%1/1100%2/2
test-factories.ts +
+
100%6/6100%3/3100%3/3100%6/6
truncate-record.ts +
+
96.77%30/3180%8/10100%1/196.77%30/31
+
+
+
+ + + + + + + + \ No newline at end of file diff --git a/coverage/utils/nav-tree-merge.ts.html b/coverage/utils/nav-tree-merge.ts.html new file mode 100644 index 000000000..b91d2bbb6 --- /dev/null +++ b/coverage/utils/nav-tree-merge.ts.html @@ -0,0 +1,364 @@ + + + + + + Code coverage report for utils/nav-tree-merge.ts + + + + + + + + + +
+
+

All files / utils nav-tree-merge.ts

+
+ +
+ 11.53% + Statements + 3/26 +
+ + +
+ 0% + Branches + 0/22 +
+ + +
+ 0% + Functions + 0/4 +
+ + +
+ 11.53% + Lines + 3/26 +
+ + +
+

+ Press n or j to go to the next uncovered block, b, p or k for the previous block. +

+ +
+
+

+
1 +2 +3 +4 +5 +6 +7 +8 +9 +10 +11 +12 +13 +14 +15 +16 +17 +18 +19 +20 +21 +22 +23 +24 +25 +26 +27 +28 +29 +30 +31 +32 +33 +34 +35 +36 +37 +38 +39 +40 +41 +42 +43 +44 +45 +46 +47 +48 +49 +50 +51 +52 +53 +54 +55 +56 +57 +58 +59 +60 +61 +62 +63 +64 +65 +66 +67 +68 +69 +70 +71 +72 +73 +74 +75 +76 +77 +78 +79 +80 +81 +82 +83 +84 +85 +86 +87 +88 +89 +90 +91 +92 +93 +94  +  +  +  +  +  +  +  +  +  +1x +  +  +  +  +  +  +  +  +  +  +  +  +  +1x +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +1x +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  + 
import type {
+  NavItem,
+  NavigationTree,
+} from "@/content-indexer/types/navigation.js";
+ 
+/**
+ * Checks if a navigation item is an SDK reference section.
+ * SDK sections are identified by title containing "sdk reference" (case-insensitive)
+ * WARNING: This is an assumption and will break if the title for this section is changed
+ */
+const isSDKReferenceSection = (item: NavItem): boolean => {
+  if (item.type === "section" || item.type === "api-section") {
+    return item.title.toLowerCase().includes("sdk reference");
+  }
+  return false;
+};
+ 
+/**
+ * Identifies SDK reference sections vs manual sections in a navigation tree.
+ * SDK sections are identified by title containing "sdk reference" (case-insensitive).
+ *
+ * @param tree - The navigation tree to separate
+ * @returns Object with sdk and manual sections
+ */
+export const separateSDKAndManualSections = (
+  tree: NavigationTree,
+): { sdk: NavigationTree; manual: NavigationTree } => {
+  return tree.reduce<{ sdk: NavigationTree; manual: NavigationTree }>(
+    (acc, item) => {
+      if (isSDKReferenceSection(item)) {
+        acc.sdk.push(item);
+      } else {
+        acc.manual.push(item);
+      }
+      return acc;
+    },
+    { sdk: [], manual: [] },
+  );
+};
+ 
+/**
+ * Merges navigation trees for the wallets tab, handling SDK references and manual sections.
+ * SDK References are always inserted second-to-last (before Resources section).
+ *
+ * @param newTree - New sections from current indexer run
+ * @param existingTree - Existing wallets navigation tree from Redis (or null if none)
+ * @param indexerType - Type of indexer: "sdk" means newTree is SDK refs, "main" means newTree is manual content
+ * @returns Merged tree with manual sections + SDK sections at second-to-last position
+ */
+export const mergeWalletsNavTree = (
+  newTree: NavigationTree,
+  existingTree: NavigationTree | null,
+  indexerType: "main" | "sdk",
+): NavigationTree => {
+  if (!existingTree) {
+    if (indexerType === "sdk") {
+      console.warn("⚠️  No existing wallets nav tree found, creating new one");
+    }
+    return newTree;
+  }
+ 
+  console.info("📖 Read existing wallets nav tree from Redis");
+ 
+  // Separate SDK and manual sections from existing tree
+  const { sdk: existingSDK, manual: existingManual } =
+    separateSDKAndManualSections(existingTree);
+ 
+  // Determine which sections are new and which to preserve
+  const manualSections = indexerType === "main" ? newTree : existingManual;
+  const sdkSections = indexerType === "sdk" ? newTree : existingSDK;
+ 
+  // Log preservation info
+  if (indexerType === "main" && sdkSections.length > 0) {
+    console.info(
+      `📖 Preserved ${sdkSections.length} SDK reference section(s) in wallets nav tree`,
+    );
+  }
+ 
+  // Handle edge cases
+  if (sdkSections.length === 0) {
+    return manualSections;
+  }
+  if (manualSections.length === 0) {
+    return sdkSections;
+  }
+ 
+  // Insert SDK sections at second-to-last position (before Resources)
+  return [
+    ...manualSections.slice(0, -1), // All manual sections except last
+    ...sdkSections, // SDK sections
+    manualSections[manualSections.length - 1], // Last section (Resources)
+  ];
+};
+ 
+ +
+
+ + + + + + + + \ No newline at end of file diff --git a/coverage/utils/navigation-helpers.ts.html b/coverage/utils/navigation-helpers.ts.html new file mode 100644 index 000000000..6d0c41c05 --- /dev/null +++ b/coverage/utils/navigation-helpers.ts.html @@ -0,0 +1,127 @@ + + + + + + Code coverage report for utils/navigation-helpers.ts + + + + + + + + + +
+
+

All files / utils navigation-helpers.ts

+
+ +
+ 100% + Statements + 2/2 +
+ + +
+ 100% + Branches + 0/0 +
+ + +
+ 100% + Functions + 1/1 +
+ + +
+ 100% + Lines + 2/2 +
+ + +
+

+ Press n or j to go to the next uncovered block, b, p or k for the previous block. +

+ +
+
+

+
1 +2 +3 +4 +5 +6 +7 +8 +9 +10 +11 +12 +13 +14 +15  +  +  +  +  +  +6x +  +  +20x +  +  +  +  + 
import type { NavItem } from "@/content-indexer/types/navigation.js";
+ 
+/**
+ * Creates breadcrumb-safe navigation item (without populated children).
+ * Prevents circular references in breadcrumb trails by creating a shallow copy.
+ */
+export const createBreadcrumbNavItem = (
+  title: string,
+  type: "api-section",
+): NavItem => ({
+  title,
+  type,
+  children: [],
+});
+ 
+ +
+
+ + + + + + + + \ No newline at end of file diff --git a/coverage/utils/normalization.ts.html b/coverage/utils/normalization.ts.html new file mode 100644 index 000000000..914d457d2 --- /dev/null +++ b/coverage/utils/normalization.ts.html @@ -0,0 +1,151 @@ + + + + + + Code coverage report for utils/normalization.ts + + + + + + + + + +
+
+

All files / utils normalization.ts

+
+ +
+ 100% + Statements + 6/6 +
+ + +
+ 100% + Branches + 4/4 +
+ + +
+ 100% + Functions + 2/2 +
+ + +
+ 100% + Lines + 5/5 +
+ + +
+

+ Press n or j to go to the next uncovered block, b, p or k for the previous block. +

+ +
+
+

+
1 +2 +3 +4 +5 +6 +7 +8 +9 +10 +11 +12 +13 +14 +15 +16 +17 +18 +19 +20 +21 +22 +23  +  +  +  +  +  +  +4x +30x +6x +  +  +  +  +  +  +4x +  +  +  +30x +  + 
import type { RepoConfig } from "@/content-indexer/utils/github.js";
+ 
+/**
+ * Normalizes a frontmatter slug by removing the "docs/" prefix.
+ * This prefix is used in some legacy frontmatter from the main docs repo
+ * but should be stripped when generating URL paths.
+ */
+export const normalizeSlug = (slug: string | undefined): string | undefined => {
+  if (!slug) return undefined;
+  return slug.replace(/^docs\//, "");
+};
+ 
+/**
+ * Normalizes a file path by stripping the repo's configured prefix.
+ * This ensures the stored filePath can be used directly with the repo's docsPrefix.
+ */
+export const normalizeFilePath = (
+  filePath: string,
+  repo: RepoConfig,
+): string => {
+  return filePath.replace(repo.stripPathPrefix || "", "");
+};
+ 
+ +
+
+ + + + + + + + \ No newline at end of file diff --git a/coverage/utils/openapi.ts.html b/coverage/utils/openapi.ts.html new file mode 100644 index 000000000..8594e8c0a --- /dev/null +++ b/coverage/utils/openapi.ts.html @@ -0,0 +1,532 @@ + + + + + + Code coverage report for utils/openapi.ts + + + + + + + + + +
+
+

All files / utils openapi.ts

+
+ +
+ 97.61% + Statements + 41/42 +
+ + +
+ 97.5% + Branches + 39/40 +
+ + +
+ 100% + Functions + 9/9 +
+ + +
+ 100% + Lines + 41/41 +
+ + +
+

+ Press n or j to go to the next uncovered block, b, p or k for the previous block. +

+ +
+
+

+
1 +2 +3 +4 +5 +6 +7 +8 +9 +10 +11 +12 +13 +14 +15 +16 +17 +18 +19 +20 +21 +22 +23 +24 +25 +26 +27 +28 +29 +30 +31 +32 +33 +34 +35 +36 +37 +38 +39 +40 +41 +42 +43 +44 +45 +46 +47 +48 +49 +50 +51 +52 +53 +54 +55 +56 +57 +58 +59 +60 +61 +62 +63 +64 +65 +66 +67 +68 +69 +70 +71 +72 +73 +74 +75 +76 +77 +78 +79 +80 +81 +82 +83 +84 +85 +86 +87 +88 +89 +90 +91 +92 +93 +94 +95 +96 +97 +98 +99 +100 +101 +102 +103 +104 +105 +106 +107 +108 +109 +110 +111 +112 +113 +114 +115 +116 +117 +118 +119 +120 +121 +122 +123 +124 +125 +126 +127 +128 +129 +130 +131 +132 +133 +134 +135 +136 +137 +138 +139 +140 +141 +142 +143 +144 +145 +146 +147 +148 +149 +150  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +5x +  +  +  +  +25x +23x +  +  +2x +1x +  +  +1x +  +  +  +  +  +  +5x +  +  +  +  +17x +17x +1x +  +  +  +16x +  +16x +  +  +  +  +  +16x +  +  +  +  +  +  +  +  +  +  +  +5x +  +  +22x +23x +  +23x +  +  +27x +  +  +  +  +25x +  +25x +25x +  +25x +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +5x +  +  +  +  +33x +  +  +33x +11x +11x +  +  +  +33x +33x +  +33x +  +  +  +  +  +  +5x +  +  +  +  +17x +17x +1x +  +  +16x +16x +1x +  +  +15x +15x +15x +  +15x +  + 
import { kebabCase } from "lodash-es";
+import type { OpenAPIV3 } from "openapi-types";
+ 
+import { HTTP_METHODS } from "@/content-indexer/constants/http.js";
+import type { PathBuilder } from "@/content-indexer/core/path-builder.js";
+ 
+export interface ExtractedOperation {
+  operationId: string;
+  path: string;
+  method: string;
+  tag?: string;
+}
+ 
+/**
+ * Extracts operationId from an OpenAPI operation object.
+ * Falls back to summary or generates from method + path if operationId is missing.
+ */
+const getOperationId = (
+  operation: Record<string, unknown>,
+  method: string,
+  path: string,
+): string => {
+  if ("operationId" in operation && operation.operationId) {
+    return operation.operationId as string;
+  }
+ 
+  if ("summary" in operation && operation.summary) {
+    return (operation.summary as string).replace(/^\//, "");
+  }
+ 
+  return `${method}_${path}`;
+};
+ 
+/**
+ * Extracts the title for an OpenAPI operation.
+ * Prefers the summary field, falls back to operationId.
+ */
+export const getOperationTitle = (
+  spec: { paths: Record<string, unknown> },
+  operationId: string,
+  operationPath: string,
+): string => {
+  const pathItem = spec.paths[operationPath];
+  if (!pathItem || typeof pathItem !== "object") {
+    return operationId;
+  }
+ 
+  // Find the operation with matching operationId
+  const operation = Object.values(pathItem).find(
+    (op: unknown) =>
+      typeof op === "object" &&
+      op !== null &&
+      "operationId" in op &&
+      (op as OpenAPIV3.OperationObject).operationId === operationId,
+  ) as OpenAPIV3.OperationObject | undefined;
+ 
+  return operation?.summary || operationId;
+};
+ 
+/**
+ * Extracts all operations from an OpenAPI paths object.
+ *
+ * Iterates through all paths and HTTP methods, extracting metadata for each operation including:
+ * - operationId (with fallback logic via getOperationId)
+ * - path (the URL path from the spec)
+ * - method (the HTTP method, normalized to uppercase)
+ * - tag (the first tag from the operation's tags array, used for grouping)
+ */
+export const extractOpenApiOperations = (
+  paths: Record<string, unknown>,
+): ExtractedOperation[] => {
+  return Object.entries(paths).flatMap(([path, pathItem]) => {
+    Iif (!pathItem || typeof pathItem !== "object") return [];
+ 
+    return Object.entries(pathItem)
+      .filter(
+        ([method, operation]) =>
+          (HTTP_METHODS as readonly string[]).includes(method) &&
+          operation &&
+          typeof operation === "object",
+      )
+      .map(([method, operation]) => {
+        const op = operation as Record<string, unknown>;
+        // Extract the first tag (Fern uses tags[0] for organization)
+        const tags = Array.isArray(op.tags) ? op.tags : [];
+        const tag = tags[0] as string | undefined;
+ 
+        return {
+          operationId: getOperationId(op, method, path),
+          path,
+          method: method.toUpperCase(),
+          tag,
+        };
+      });
+  });
+};
+ 
+/**
+ * Builds the final URL path for an OpenAPI operation.
+ *
+ * Constructs the path by:
+ * 1. Optionally adding a tag slug (for grouping operations by tag)
+ * 2. Adding the operation slug (kebab-cased operationId)
+ */
+export const buildOperationPath = (
+  apiPathBuilder: PathBuilder,
+  operationId: string,
+  tag?: string,
+): string => {
+  let pathBuilder = apiPathBuilder;
+ 
+  // Add tag slug to path if operation has a tag
+  if (tag) {
+    const tagSlug = kebabCase(tag);
+    pathBuilder = apiPathBuilder.apply({ urlSlug: tagSlug });
+  }
+ 
+  // Add operation slug to path
+  const operationSlug = kebabCase(operationId);
+  pathBuilder = pathBuilder.apply({ urlSlug: operationSlug });
+ 
+  return pathBuilder.get();
+};
+ 
+/**
+ * Extracts the description from an OpenAPI operation object.
+ * Falls back to summary if description is not available.
+ */
+export const getOperationDescription = (
+  spec: { paths: Record<string, unknown> },
+  path: string,
+  method: string,
+): string => {
+  const pathItem = spec.paths[path];
+  if (!pathItem || typeof pathItem !== "object") {
+    return "";
+  }
+ 
+  const operation = (pathItem as Record<string, unknown>)[method];
+  if (!operation || typeof operation !== "object") {
+    return "";
+  }
+ 
+  const operationObj = operation as Record<string, unknown>;
+  const description = operationObj.description as string | undefined;
+  const summary = operationObj.summary as string | undefined;
+ 
+  return description || summary || "";
+};
+ 
+ +
+
+ + + + + + + + \ No newline at end of file diff --git a/coverage/utils/openrpc.ts.html b/coverage/utils/openrpc.ts.html new file mode 100644 index 000000000..33a1bce70 --- /dev/null +++ b/coverage/utils/openrpc.ts.html @@ -0,0 +1,124 @@ + + + + + + Code coverage report for utils/openrpc.ts + + + + + + + + + +
+
+

All files / utils openrpc.ts

+
+ +
+ 100% + Statements + 2/2 +
+ + +
+ 100% + Branches + 4/4 +
+ + +
+ 100% + Functions + 1/1 +
+ + +
+ 100% + Lines + 2/2 +
+ + +
+

+ Press n or j to go to the next uncovered block, b, p or k for the previous block. +

+ +
+
+

+
1 +2 +3 +4 +5 +6 +7 +8 +9 +10 +11 +12 +13 +14  +  +  +  +  +5x +16x +  +  +  +  +  +  + 
import type { OpenRpcSpec } from "@/content-indexer/types/specs.js";
+ 
+/**
+ * Type guard to check if a spec is a valid OpenRPC spec with methods array.
+ */
+export const isValidOpenRpcSpec = (spec: unknown): spec is OpenRpcSpec => {
+  return (
+    typeof spec === "object" &&
+    spec !== null &&
+    "methods" in spec &&
+    Array.isArray((spec as { methods: unknown }).methods)
+  );
+};
+ 
+ +
+
+ + + + + + + + \ No newline at end of file diff --git a/coverage/utils/test-factories.ts.html b/coverage/utils/test-factories.ts.html new file mode 100644 index 000000000..58aa0e20c --- /dev/null +++ b/coverage/utils/test-factories.ts.html @@ -0,0 +1,232 @@ + + + + + + Code coverage report for utils/test-factories.ts + + + + + + + + + +
+
+

All files / utils test-factories.ts

+
+ +
+ 100% + Statements + 6/6 +
+ + +
+ 100% + Branches + 3/3 +
+ + +
+ 100% + Functions + 3/3 +
+ + +
+ 100% + Lines + 6/6 +
+ + +
+

+ Press n or j to go to the next uncovered block, b, p or k for the previous block. +

+ +
+
+

+
1 +2 +3 +4 +5 +6 +7 +8 +9 +10 +11 +12 +13 +14 +15 +16 +17 +18 +19 +20 +21 +22 +23 +24 +25 +26 +27 +28 +29 +30 +31 +32 +33 +34 +35 +36 +37 +38 +39 +40 +41 +42 +43 +44 +45 +46 +47 +48 +49 +50  +  +  +  +  +  +  +  +  +9x +  +19x +  +  +  +  +  +  +  +  +  +  +  +  +9x +  +11x +  +  +  +  +  +  +  +  +  +  +  +  +9x +  +16x +  +  +  +  +  +  +  + 
import type {
+  OpenApiSpec,
+  OpenRpcSpec,
+} from "@/content-indexer/types/specs.js";
+import type { RepoConfig } from "@/content-indexer/utils/github.js";
+ 
+/**
+ * Factory for creating OpenAPI spec with minimal required fields for testing
+ */
+export const openApiSpecFactory = (
+  overrides: Partial<OpenApiSpec> = {},
+): OpenApiSpec => ({
+  openapi: "3.0.0",
+  info: {
+    title: "Test API",
+    version: "1.0.0",
+  },
+  paths: {},
+  ...overrides,
+});
+ 
+/**
+ * Factory for creating OpenRPC spec with minimal required fields for testing
+ */
+export const openRpcSpecFactory = (
+  overrides: Partial<OpenRpcSpec> = {},
+): OpenRpcSpec => ({
+  openrpc: "1.0.0",
+  info: {
+    title: "Test API",
+    version: "1.0.0",
+  },
+  methods: [],
+  ...overrides,
+});
+ 
+/**
+ * Factory for creating RepoConfig with minimal required fields for testing
+ */
+export const repoConfigFactory = (
+  overrides: Partial<RepoConfig> = {},
+): RepoConfig => ({
+  owner: "test-owner",
+  repo: "test-repo",
+  branch: "main",
+  docsPrefix: "docs/",
+  stripPathPrefix: "",
+  ...overrides,
+});
+ 
+ +
+
+ + + + + + + + \ No newline at end of file diff --git a/coverage/utils/truncate-record.ts.html b/coverage/utils/truncate-record.ts.html new file mode 100644 index 000000000..6ea0aded9 --- /dev/null +++ b/coverage/utils/truncate-record.ts.html @@ -0,0 +1,316 @@ + + + + + + Code coverage report for utils/truncate-record.ts + + + + + + + + + +
+
+

All files / utils truncate-record.ts

+
+ +
+ 96.77% + Statements + 30/31 +
+ + +
+ 80% + Branches + 8/10 +
+ + +
+ 100% + Functions + 1/1 +
+ + +
+ 96.77% + Lines + 30/31 +
+ + +
+

+ Press n or j to go to the next uncovered block, b, p or k for the previous block. +

+ +
+
+

+
1 +2 +3 +4 +5 +6 +7 +8 +9 +10 +11 +12 +13 +14 +15 +16 +17 +18 +19 +20 +21 +22 +23 +24 +25 +26 +27 +28 +29 +30 +31 +32 +33 +34 +35 +36 +37 +38 +39 +40 +41 +42 +43 +44 +45 +46 +47 +48 +49 +50 +51 +52 +53 +54 +55 +56 +57 +58 +59 +60 +61 +62 +63 +64 +65 +66 +67 +68 +69 +70 +71 +72 +73 +74 +75 +76 +77 +78  +  +  +  +2x +2x +2x +  +  +  +  +  +2x +  +6x +6x +  +6x +6x +  +6x +2x +  +  +  +4x +4x +  +  +  +  +4x +  +  +  +  +  +  +4x +1x +  +  +  +  +  +  +3x +3x +3x +3x +  +3x +  +3x +  +  +3x +3x +3x +  +3x +3x +3x +  +  +3x +  +  +  +  +  +3x +  +  +  +6x +  + 
import removeMd from "remove-markdown";
+ 
+import type { AlgoliaRecord } from "@/content-indexer/types/algolia.js";
+ 
+const MAX_RECORD_BYTES = 100_000; // Algolia imposes a 100KB limit on each record
+const BUFFER_BYTES = 1_000;
+const MAX_ITERATIONS = 5;
+ 
+/**
+ * Truncate record content to ensure entire JSON payload fits within Algolia limit.
+ * Also strips markdown formatting from content for better search experience.
+ */
+export const truncateRecord = (rawRecord: AlgoliaRecord): AlgoliaRecord => {
+  // Strip markdown formatting to get clean, searchable text
+  const cleanedContent = removeMd(rawRecord.content);
+  const record = { ...rawRecord, content: cleanedContent };
+ 
+  const fullRecordJson = JSON.stringify(record);
+  const recordBytes = Buffer.byteLength(fullRecordJson, "utf8");
+ 
+  if (recordBytes <= MAX_RECORD_BYTES) {
+    return record; // Record is fine as-is - should be the case for over 99% of records
+  }
+ 
+  // Calculate overhead (everything except content field)
+  const recordWithoutContent = { ...record, content: "" };
+  const overheadBytes = Buffer.byteLength(
+    JSON.stringify(recordWithoutContent),
+    "utf8",
+  );
+ 
+  console.warn(
+    `⚠️  Record "${record.title}" (${record.path}) exceeds ${MAX_RECORD_BYTES} bytes\n`,
+    `   Total: ${recordBytes} bytes\n`,
+    `   Content: ${Buffer.byteLength(record.content, "utf8")} bytes\n`,
+    `   Overhead (all non-content data): ${overheadBytes} bytes`,
+  );
+ 
+  if (overheadBytes > MAX_RECORD_BYTES - 1000) {
+    throw new Error(
+      `Record overhead (${overheadBytes} bytes) is too large! Something is wrong with the record data.`,
+    );
+  }
+ 
+  // Iteratively truncate content while measuring full JSON record size
+  // This accounts for JSON escaping overhead (quotes, backslashes, etc.)
+  let truncatedContent = cleanedContent;
+  let truncatedRecord: AlgoliaRecord = { ...record, content: truncatedContent };
+  let currentBytes = recordBytes;
+  let iterations = 0;
+ 
+  while (currentBytes > MAX_RECORD_BYTES && iterations < MAX_ITERATIONS) {
+    // Calculate reduction ratio to reach target size
+    const reductionRatio = (MAX_RECORD_BYTES - BUFFER_BYTES) / currentBytes;
+ 
+    // Use code point-aware truncation to avoid splitting multi-byte UTF-8 characters (emoji, etc.)
+    const codePoints = Array.from(truncatedContent);
+    const targetCodePoints = Math.floor(codePoints.length * reductionRatio);
+    truncatedContent = codePoints.slice(0, targetCodePoints).join("") + "...";
+ 
+    truncatedRecord = { ...record, content: truncatedContent };
+    currentBytes = Buffer.byteLength(JSON.stringify(truncatedRecord), "utf8");
+    iterations++;
+  }
+ 
+  Iif (currentBytes > MAX_RECORD_BYTES) {
+    throw new Error(
+      `Failed to truncate record after ${MAX_ITERATIONS} iterations. Final size: ${currentBytes} bytes`,
+    );
+  }
+ 
+  console.warn(
+    `   ✓ Truncated to ${currentBytes} bytes (${truncatedContent.length} chars) in ${iterations} iteration${iterations === 1 ? "" : "s"}\n`,
+  );
+ 
+  return truncatedRecord;
+};
+ 
+ +
+
+ + + + + + + + \ No newline at end of file diff --git a/coverage/visitors/index.html b/coverage/visitors/index.html new file mode 100644 index 000000000..2b0c0738b --- /dev/null +++ b/coverage/visitors/index.html @@ -0,0 +1,176 @@ + + + + + + Code coverage report for visitors + + + + + + + + + +
+
+

All files visitors

+
+ +
+ 98.66% + Statements + 74/75 +
+ + +
+ 92.59% + Branches + 50/54 +
+ + +
+ 100% + Functions + 9/9 +
+ + +
+ 98.66% + Lines + 74/75 +
+ + +
+

+ Press n or j to go to the next uncovered block, b, p or k for the previous block. +

+ +
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
FileStatementsBranchesFunctionsLines
index.ts +
+
92.3%12/1390%9/10100%1/192.3%12/13
visit-api-reference.ts +
+
100%16/16100%14/14100%1/1100%16/16
visit-link.ts +
+
100%3/3100%0/0100%1/1100%3/3
visit-page.ts +
+
100%12/12100%12/12100%1/1100%12/12
visit-section.ts +
+
100%31/3183.33%15/18100%5/5100%31/31
+
+
+
+ + + + + + + + \ No newline at end of file diff --git a/coverage/visitors/index.ts.html b/coverage/visitors/index.ts.html new file mode 100644 index 000000000..835bee29c --- /dev/null +++ b/coverage/visitors/index.ts.html @@ -0,0 +1,313 @@ + + + + + + Code coverage report for visitors/index.ts + + + + + + + + + +
+
+

All files / visitors index.ts

+
+ +
+ 92.3% + Statements + 12/13 +
+ + +
+ 90% + Branches + 9/10 +
+ + +
+ 100% + Functions + 1/1 +
+ + +
+ 92.3% + Lines + 12/13 +
+ + +
+

+ Press n or j to go to the next uncovered block, b, p or k for the previous block. +

+ +
+
+

+
1 +2 +3 +4 +5 +6 +7 +8 +9 +10 +11 +12 +13 +14 +15 +16 +17 +18 +19 +20 +21 +22 +23 +24 +25 +26 +27 +28 +29 +30 +31 +32 +33 +34 +35 +36 +37 +38 +39 +40 +41 +42 +43 +44 +45 +46 +47 +48 +49 +50 +51 +52 +53 +54 +55 +56 +57 +58 +59 +60 +61 +62 +63 +64 +65 +66 +67 +68 +69 +70 +71 +72 +73 +74 +75 +76 +77  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +2x +20x +  +  +20x +1x +  +  +  +19x +2x +  +  +17x +13x +  +  +4x +3x +  +  +1x +1x +  +  +  +  +  + 
import type { ProcessingContext } from "@/content-indexer/collectors/processing-context.js";
+import type { ContentCache } from "@/content-indexer/core/content-cache.js";
+import type { PathBuilder } from "@/content-indexer/core/path-builder.js";
+import {
+  isApiConfig,
+  isChangelogConfig,
+  isLinkConfig,
+  isPageConfig,
+  isSectionConfig,
+  type NavigationItem,
+} from "@/content-indexer/types/docsYaml.js";
+import type { NavItem } from "@/content-indexer/types/navigation.js";
+import type { PathIndex } from "@/content-indexer/types/pathIndex.js";
+import type { RepoConfig } from "@/content-indexer/utils/github.js";
+ 
+import { visitApiReference } from "./visit-api-reference.js";
+import { visitLink } from "./visit-link.js";
+import { visitPage } from "./visit-page.js";
+import { visitSection } from "./visit-section.js";
+ 
+export interface VisitorConfigBase {
+  parentPath: PathBuilder;
+  tab: string;
+  repo: RepoConfig;
+  contentCache: ContentCache;
+  context: ProcessingContext;
+  navigationAncestors: NavItem[];
+}
+ 
+export interface VisitorConfig extends VisitorConfigBase {
+  item: NavigationItem;
+}
+ 
+export interface VisitorResult {
+  indexEntries: PathIndex;
+  navItem?: NavItem | NavItem[];
+}
+ 
+/**
+ * Dispatcher that routes navigation items to the appropriate visitor.
+ *
+ * Uses type guards to determine item type and delegates to specialized visitors:
+ * - Pages → visitPage
+ * - Sections → visitSection (recursive)
+ * - API references → visitApiReference
+ * - Links → visitLink
+ * - Changelog → skip (no processing needed)
+ */
+export const visitNavigationItem = (config: VisitorConfig): VisitorResult => {
+  const { item } = config;
+ 
+  // Skip changelog items
+  if (isChangelogConfig(item)) {
+    return { indexEntries: {}, navItem: undefined };
+  }
+ 
+  // Delegate to appropriate visitor based on item type
+  if (isLinkConfig(item)) {
+    return visitLink({ ...config, item });
+  }
+ 
+  if (isPageConfig(item)) {
+    return visitPage({ ...config, item });
+  }
+ 
+  if (isSectionConfig(item)) {
+    return visitSection({ ...config, item }, visitNavigationItem);
+  }
+ 
+  Eif (isApiConfig(item)) {
+    return visitApiReference({ ...config, item });
+  }
+ 
+  // Unknown item type - skip
+  return { indexEntries: {}, navItem: undefined };
+};
+ 
+ +
+
+ + + + + + + + \ No newline at end of file diff --git a/coverage/visitors/processors/index.html b/coverage/visitors/processors/index.html new file mode 100644 index 000000000..b437cecf6 --- /dev/null +++ b/coverage/visitors/processors/index.html @@ -0,0 +1,131 @@ + + + + + + Code coverage report for visitors/processors + + + + + + + + + +
+
+

All files visitors/processors

+
+ +
+ 100% + Statements + 58/58 +
+ + +
+ 96.77% + Branches + 30/31 +
+ + +
+ 100% + Functions + 8/8 +
+ + +
+ 100% + Lines + 58/58 +
+ + +
+

+ Press n or j to go to the next uncovered block, b, p or k for the previous block. +

+ +
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
FileStatementsBranchesFunctionsLines
process-openapi.ts +
+
100%36/3692.85%13/14100%6/6100%36/36
process-openrpc.ts +
+
100%22/22100%17/17100%2/2100%22/22
+
+
+
+ + + + + + + + \ No newline at end of file diff --git a/coverage/visitors/processors/process-openapi.ts.html b/coverage/visitors/processors/process-openapi.ts.html new file mode 100644 index 000000000..f46baa74b --- /dev/null +++ b/coverage/visitors/processors/process-openapi.ts.html @@ -0,0 +1,730 @@ + + + + + + Code coverage report for visitors/processors/process-openapi.ts + + + + + + + + + +
+
+

All files / visitors/processors process-openapi.ts

+
+ +
+ 100% + Statements + 36/36 +
+ + +
+ 92.85% + Branches + 13/14 +
+ + +
+ 100% + Functions + 6/6 +
+ + +
+ 100% + Lines + 36/36 +
+ + +
+

+ Press n or j to go to the next uncovered block, b, p or k for the previous block. +

+ +
+
+

+
1 +2 +3 +4 +5 +6 +7 +8 +9 +10 +11 +12 +13 +14 +15 +16 +17 +18 +19 +20 +21 +22 +23 +24 +25 +26 +27 +28 +29 +30 +31 +32 +33 +34 +35 +36 +37 +38 +39 +40 +41 +42 +43 +44 +45 +46 +47 +48 +49 +50 +51 +52 +53 +54 +55 +56 +57 +58 +59 +60 +61 +62 +63 +64 +65 +66 +67 +68 +69 +70 +71 +72 +73 +74 +75 +76 +77 +78 +79 +80 +81 +82 +83 +84 +85 +86 +87 +88 +89 +90 +91 +92 +93 +94 +95 +96 +97 +98 +99 +100 +101 +102 +103 +104 +105 +106 +107 +108 +109 +110 +111 +112 +113 +114 +115 +116 +117 +118 +119 +120 +121 +122 +123 +124 +125 +126 +127 +128 +129 +130 +131 +132 +133 +134 +135 +136 +137 +138 +139 +140 +141 +142 +143 +144 +145 +146 +147 +148 +149 +150 +151 +152 +153 +154 +155 +156 +157 +158 +159 +160 +161 +162 +163 +164 +165 +166 +167 +168 +169 +170 +171 +172 +173 +174 +175 +176 +177 +178 +179 +180 +181 +182 +183 +184 +185 +186 +187 +188 +189 +190 +191 +192 +193 +194 +195 +196 +197 +198 +199 +200 +201 +202 +203 +204 +205 +206 +207 +208 +209 +210 +211 +212 +213 +214 +215 +216  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +4x +  +  +  +  +  +14x +  +14x +15x +  +  +  +  +  +15x +  +  +  +  +  +  +  +  +14x +  +  +  +  +  +4x +  +  +  +  +  +  +  +  +  +12x +12x +13x +13x +13x +  +  +12x +  +12x +12x +13x +  +  +  +  +  +13x +  +  +  +  +  +  +13x +13x +  +  +  +  +  +13x +  +  +  +13x +  +  +  +  +  +  +  +  +  +13x +  +  +  +  +  +  +  +  +12x +4x +  +  +  +  +  +8x +  +  +  +12x +  +  +  +  +  +  +4x +  +  +  +  +  +  +  +  +14x +  +  +14x +14x +  +  +  +  +  +  +  +14x +2x +  +  +  +12x +  +  +  +  +14x +  +  +  +  +  +  +  +  +  +  +14x +  +  +  +  +  +  +  +14x +  + 
import type { ProcessingContext } from "@/content-indexer/collectors/processing-context.js";
+import type { PathBuilder } from "@/content-indexer/core/path-builder.js";
+import type { NavItem } from "@/content-indexer/types/navigation.js";
+import type { PathIndex } from "@/content-indexer/types/pathIndex.js";
+import type { OpenApiSpec } from "@/content-indexer/types/specs.js";
+import { createBreadcrumbNavItem } from "@/content-indexer/utils/navigation-helpers.js";
+import {
+  buildOperationPath,
+  extractOpenApiOperations,
+  getOperationDescription,
+  getOperationTitle,
+  type ExtractedOperation,
+} from "@/content-indexer/utils/openapi.js";
+import type {
+  VisitorConfig,
+  VisitorResult,
+} from "@/content-indexer/visitors/index.js";
+ 
+/**
+ * Configuration for processing an OpenAPI specification
+ */
+export interface ProcessOpenApiConfig {
+  spec: OpenApiSpec;
+  specUrl: string;
+  visitorConfig: VisitorConfig;
+  apiPathBuilder: PathBuilder;
+  apiTitle: string;
+  isHidden: boolean;
+  isFlattened: boolean;
+}
+ 
+interface BuildOpenApiIndexEntriesConfig {
+  operations: ExtractedOperation[];
+  apiPathBuilder: PathBuilder;
+  specUrl: string;
+  tab: string;
+}
+ 
+interface BuildOpenApiNavigationConfig {
+  operations: ExtractedOperation[];
+  apiPathBuilder: PathBuilder;
+  spec: OpenApiSpec;
+  context: ProcessingContext;
+  navigationAncestors: NavItem[];
+  apiSectionBreadcrumb: NavItem | undefined;
+  isHidden: boolean;
+}
+ 
+/**
+ * Builds path index entries for OpenAPI operations.
+ */
+const buildOpenApiIndexEntries = ({
+  operations,
+  apiPathBuilder,
+  specUrl,
+  tab,
+}: BuildOpenApiIndexEntriesConfig): PathIndex => {
+  const indexEntries: PathIndex = {};
+ 
+  operations.forEach((operation) => {
+    const finalPath = buildOperationPath(
+      apiPathBuilder,
+      operation.operationId,
+      operation.tag,
+    );
+ 
+    indexEntries[finalPath] = {
+      type: "openapi",
+      specUrl,
+      operationId: operation.operationId,
+      source: "docs-yml",
+      tab,
+    };
+  });
+ 
+  return indexEntries;
+};
+ 
+/**
+ * Builds navigation items for OpenAPI operations, grouped by tag.
+ */
+const buildOpenApiNavigation = ({
+  operations,
+  apiPathBuilder,
+  spec,
+  context,
+  navigationAncestors,
+  apiSectionBreadcrumb,
+  isHidden,
+}: BuildOpenApiNavigationConfig): NavItem[] => {
+  // Group operations by tag
+  const operationsByTag = new Map<string | undefined, ExtractedOperation[]>();
+  operations.forEach((operation) => {
+    const existing = operationsByTag.get(operation.tag) || [];
+    existing.push(operation);
+    operationsByTag.set(operation.tag, existing);
+  });
+ 
+  const tagSections: NavItem[] = [];
+ 
+  for (const [tag, tagOperations] of operationsByTag.entries()) {
+    const endpointNavItems: NavItem[] = tagOperations.map((operation) => {
+      const finalPath = buildOperationPath(
+        apiPathBuilder,
+        operation.operationId,
+        operation.tag,
+      );
+ 
+      const title = getOperationTitle(
+        spec,
+        operation.operationId,
+        operation.path,
+      );
+ 
+      // Build Algolia record if not hidden
+      Eif (!isHidden) {
+        const description = getOperationDescription(
+          spec,
+          operation.path,
+          operation.method.toLowerCase(),
+        );
+ 
+        const breadcrumbs = apiSectionBreadcrumb
+          ? [...navigationAncestors, apiSectionBreadcrumb]
+          : navigationAncestors;
+ 
+        context.addAlgoliaRecord({
+          pageType: "API Method",
+          path: finalPath,
+          title,
+          content: description,
+          httpMethod: operation.method,
+          breadcrumbs,
+        });
+      }
+ 
+      return {
+        title,
+        path: `/${finalPath}`,
+        method: operation.method,
+        type: "endpoint" as const,
+      };
+    });
+ 
+    // Wrap in tag section if tag exists
+    if (tag) {
+      tagSections.push({
+        title: tag,
+        type: "section",
+        children: endpointNavItems,
+      });
+    } else {
+      tagSections.push(...endpointNavItems);
+    }
+  }
+ 
+  return tagSections;
+};
+ 
+/**
+ * Processes an OpenAPI specification.
+ * Extracts operations, builds path index, navigation, and Algolia records.
+ */
+export const processOpenApiSpec = ({
+  spec,
+  specUrl,
+  visitorConfig,
+  apiPathBuilder,
+  apiTitle,
+  isHidden,
+  isFlattened,
+}: ProcessOpenApiConfig): VisitorResult => {
+  const { tab, context, navigationAncestors } = visitorConfig;
+ 
+  // Extract operations and build index entries
+  const operations = extractOpenApiOperations(spec.paths);
+  const indexEntries = buildOpenApiIndexEntries({
+    operations,
+    apiPathBuilder,
+    specUrl,
+    tab,
+  });
+ 
+  // Return early if hidden (index only, no navigation)
+  if (isHidden) {
+    return { indexEntries, navItem: undefined };
+  }
+ 
+  // Create breadcrumb for Algolia
+  const apiSectionBreadcrumb = isFlattened
+    ? undefined
+    : createBreadcrumbNavItem(apiTitle, "api-section");
+ 
+  // Build navigation items
+  const tagSections = buildOpenApiNavigation({
+    operations,
+    apiPathBuilder,
+    spec,
+    context,
+    navigationAncestors,
+    apiSectionBreadcrumb,
+    isHidden,
+  });
+ 
+  // Return flattened or wrapped navigation
+  const navItem: NavItem | NavItem[] = isFlattened
+    ? tagSections
+    : {
+        title: apiTitle,
+        type: "api-section",
+        children: tagSections,
+      };
+ 
+  return { indexEntries, navItem };
+};
+ 
+ +
+
+ + + + + + + + \ No newline at end of file diff --git a/coverage/visitors/processors/process-openrpc.ts.html b/coverage/visitors/processors/process-openrpc.ts.html new file mode 100644 index 000000000..d1711d04d --- /dev/null +++ b/coverage/visitors/processors/process-openrpc.ts.html @@ -0,0 +1,421 @@ + + + + + + Code coverage report for visitors/processors/process-openrpc.ts + + + + + + + + + +
+
+

All files / visitors/processors process-openrpc.ts

+
+ +
+ 100% + Statements + 22/22 +
+ + +
+ 100% + Branches + 17/17 +
+ + +
+ 100% + Functions + 2/2 +
+ + +
+ 100% + Lines + 22/22 +
+ + +
+

+ Press n or j to go to the next uncovered block, b, p or k for the previous block. +

+ +
+
+

+
1 +2 +3 +4 +5 +6 +7 +8 +9 +10 +11 +12 +13 +14 +15 +16 +17 +18 +19 +20 +21 +22 +23 +24 +25 +26 +27 +28 +29 +30 +31 +32 +33 +34 +35 +36 +37 +38 +39 +40 +41 +42 +43 +44 +45 +46 +47 +48 +49 +50 +51 +52 +53 +54 +55 +56 +57 +58 +59 +60 +61 +62 +63 +64 +65 +66 +67 +68 +69 +70 +71 +72 +73 +74 +75 +76 +77 +78 +79 +80 +81 +82 +83 +84 +85 +86 +87 +88 +89 +90 +91 +92 +93 +94 +95 +96 +97 +98 +99 +100 +101 +102 +103 +104 +105 +106 +107 +108 +109 +110 +111 +112 +113  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +4x +  +  +  +  +  +  +  +  +9x +  +9x +1x +1x +  +  +8x +8x +  +  +  +8x +  +  +  +  +9x +9x +9x +9x +  +  +9x +  +  +  +  +  +  +  +  +9x +8x +8x +  +  +  +8x +  +  +  +  +  +  +  +  +  +  +9x +  +  +  +  +  +  +  +  +9x +1x +  +  +  +7x +  +  +  +  +  +  +  +9x +  + 
import { kebabCase } from "lodash-es";
+ 
+import type { PathBuilder } from "@/content-indexer/core/path-builder.js";
+import type { NavItem } from "@/content-indexer/types/navigation.js";
+import type { PathIndex } from "@/content-indexer/types/pathIndex.js";
+import type { OpenRpcSpec } from "@/content-indexer/types/specs.js";
+import { createBreadcrumbNavItem } from "@/content-indexer/utils/navigation-helpers.js";
+import { isValidOpenRpcSpec } from "@/content-indexer/utils/openrpc.js";
+import type {
+  VisitorConfig,
+  VisitorResult,
+} from "@/content-indexer/visitors/index.js";
+ 
+/**
+ * Configuration for processing an OpenRPC specification
+ */
+export interface ProcessOpenRpcConfig {
+  spec: OpenRpcSpec;
+  specUrl: string;
+  visitorConfig: VisitorConfig;
+  apiPathBuilder: PathBuilder;
+  apiTitle: string;
+  isHidden: boolean;
+  isFlattened: boolean;
+}
+ 
+/**
+ * Processes an OpenRPC specification.
+ * Validates spec, builds path index, navigation, and Algolia records.
+ */
+export const processOpenRpcSpec = ({
+  spec,
+  specUrl,
+  visitorConfig,
+  apiPathBuilder,
+  apiTitle,
+  isHidden,
+  isFlattened,
+}: ProcessOpenRpcConfig): VisitorResult => {
+  const { tab, context, navigationAncestors } = visitorConfig;
+ 
+  if (!isValidOpenRpcSpec(spec)) {
+    console.error(`  ⚠️  Invalid OpenRPC spec for ${apiTitle}`);
+    return { indexEntries: {} };
+  }
+ 
+  const indexEntries: PathIndex = {};
+  const endpointNavItems: NavItem[] = [];
+ 
+  // Create breadcrumb for Algolia
+  const apiSectionBreadcrumb =
+    !isHidden && !isFlattened
+      ? createBreadcrumbNavItem(apiTitle, "api-section")
+      : undefined;
+ 
+  // Process each RPC method
+  spec.methods.forEach((method) => {
+    const slug = kebabCase(method.name);
+    const pathBuilder = apiPathBuilder.apply({ urlSlug: slug });
+    const finalPath = pathBuilder.get();
+ 
+    // Add to path index
+    indexEntries[finalPath] = {
+      type: "openrpc",
+      specUrl,
+      methodName: method.name,
+      source: "docs-yml",
+      tab,
+    };
+ 
+    // Build Algolia record if not hidden
+    if (!isHidden) {
+      const description = method.description || method.summary || "";
+      const breadcrumbs = apiSectionBreadcrumb
+        ? [...navigationAncestors, apiSectionBreadcrumb]
+        : navigationAncestors;
+ 
+      context.addAlgoliaRecord({
+        pageType: "API Method",
+        path: finalPath,
+        title: method.name,
+        content: description,
+        httpMethod: "POST",
+        breadcrumbs,
+      });
+    }
+ 
+    // Add navigation item
+    endpointNavItems.push({
+      title: method.name,
+      path: `/${finalPath}`,
+      method: "POST",
+      type: "endpoint",
+    });
+  });
+ 
+  // Return early if hidden
+  if (isHidden) {
+    return { indexEntries, navItem: undefined };
+  }
+ 
+  // Return flattened or wrapped navigation
+  const navItem: NavItem | NavItem[] = isFlattened
+    ? endpointNavItems
+    : {
+        title: apiTitle,
+        type: "api-section",
+        children: endpointNavItems,
+      };
+ 
+  return { indexEntries, navItem };
+};
+ 
+ +
+
+ + + + + + + + \ No newline at end of file diff --git a/coverage/visitors/visit-api-reference.ts.html b/coverage/visitors/visit-api-reference.ts.html new file mode 100644 index 000000000..6175148ac --- /dev/null +++ b/coverage/visitors/visit-api-reference.ts.html @@ -0,0 +1,307 @@ + + + + + + Code coverage report for visitors/visit-api-reference.ts + + + + + + + + + +
+
+

All files / visitors visit-api-reference.ts

+
+ +
+ 100% + Statements + 16/16 +
+ + +
+ 100% + Branches + 14/14 +
+ + +
+ 100% + Functions + 1/1 +
+ + +
+ 100% + Lines + 16/16 +
+ + +
+

+ Press n or j to go to the next uncovered block, b, p or k for the previous block. +

+ +
+
+

+
1 +2 +3 +4 +5 +6 +7 +8 +9 +10 +11 +12 +13 +14 +15 +16 +17 +18 +19 +20 +21 +22 +23 +24 +25 +26 +27 +28 +29 +30 +31 +32 +33 +34 +35 +36 +37 +38 +39 +40 +41 +42 +43 +44 +45 +46 +47 +48 +49 +50 +51 +52 +53 +54 +55 +56 +57 +58 +59 +60 +61 +62 +63 +64 +65 +66 +67 +68 +69 +70 +71 +72 +73 +74 +75  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +3x +8x +  +  +8x +8x +8x +8x +8x +  +  +8x +  +  +  +  +8x +8x +1x +  +  +1x +  +  +7x +  +  +7x +  +6x +  +  +  +  +  +  +  +  +  +  +1x +  +  +  +  +  +  +  +  +  +  + 
import { kebabCase } from "lodash-es";
+ 
+import type { ApiConfig } from "@/content-indexer/types/docsYaml.js";
+import type {
+  OpenApiSpec,
+  OpenRpcSpec,
+} from "@/content-indexer/types/specs.js";
+ 
+import type { VisitorConfigBase, VisitorResult } from "./index.js";
+import { processOpenApiSpec } from "./processors/process-openapi.js";
+import { processOpenRpcSpec } from "./processors/process-openrpc.js";
+ 
+export interface ApiVisitorConfig extends VisitorConfigBase {
+  item: ApiConfig;
+}
+ 
+/**
+ * Visits an API reference item from docs.yml.
+ *
+ * Handles both OpenAPI and OpenRPC specifications by delegating to
+ * spec-specific processors. Extracts config, loads cached spec,
+ * and routes to the appropriate processor.
+ */
+export const visitApiReference = (config: ApiVisitorConfig): VisitorResult => {
+  const { item: apiConfig, parentPath, contentCache } = config;
+ 
+  // Extract configuration
+  const apiName = apiConfig["api-name"];
+  const apiUrlSlug = apiConfig.slug ?? kebabCase(apiConfig.api);
+  const skipSlug = apiConfig["skip-slug"] ?? false;
+  const isHidden = apiConfig.hidden ?? false;
+  const isFlattened = apiConfig.flattened ?? false;
+ 
+  // Build path for this API
+  const apiPathBuilder = skipSlug
+    ? parentPath
+    : parentPath.apply({ urlSlug: apiUrlSlug });
+ 
+  // Retrieve cached spec
+  const cached = contentCache.getSpec(apiName);
+  if (!cached) {
+    console.warn(
+      `  ⚠️  No cached spec found for api-name: ${apiName} (skipping)`,
+    );
+    return { indexEntries: {} };
+  }
+ 
+  const { specType, spec, specUrl } = cached;
+ 
+  // Delegate to spec-specific processor
+  switch (specType) {
+    case "openapi":
+      return processOpenApiSpec({
+        spec: spec as OpenApiSpec,
+        specUrl,
+        visitorConfig: config,
+        apiPathBuilder,
+        apiTitle: apiConfig.api,
+        isHidden,
+        isFlattened,
+      });
+ 
+    case "openrpc":
+      return processOpenRpcSpec({
+        spec: spec as OpenRpcSpec,
+        specUrl,
+        visitorConfig: config,
+        apiPathBuilder,
+        apiTitle: apiConfig.api,
+        isHidden,
+        isFlattened,
+      });
+  }
+};
+ 
+ +
+
+ + + + + + + + \ No newline at end of file diff --git a/coverage/visitors/visit-link.ts.html b/coverage/visitors/visit-link.ts.html new file mode 100644 index 000000000..1797a5735 --- /dev/null +++ b/coverage/visitors/visit-link.ts.html @@ -0,0 +1,160 @@ + + + + + + Code coverage report for visitors/visit-link.ts + + + + + + + + + +
+
+

All files / visitors visit-link.ts

+
+ +
+ 100% + Statements + 3/3 +
+ + +
+ 100% + Branches + 0/0 +
+ + +
+ 100% + Functions + 1/1 +
+ + +
+ 100% + Lines + 3/3 +
+ + +
+

+ Press n or j to go to the next uncovered block, b, p or k for the previous block. +

+ +
+
+

+
1 +2 +3 +4 +5 +6 +7 +8 +9 +10 +11 +12 +13 +14 +15 +16 +17 +18 +19 +20 +21 +22 +23 +24 +25 +26  +  +  +  +  +  +  +  +  +  +  +  +  +3x +5x +  +5x +  +  +  +  +  +  +  +  + 
import type { LinkConfig } from "@/content-indexer/types/docsYaml.js";
+ 
+import type { VisitorConfigBase, VisitorResult } from "./index.js";
+ 
+export interface LinkVisitorConfig extends VisitorConfigBase {
+  item: LinkConfig;
+}
+ 
+/**
+ * Visits a link item from docs.yml.
+ *
+ * Links are external URLs - they only appear in navigation, not in path index.
+ */
+export const visitLink = (config: LinkVisitorConfig): VisitorResult => {
+  const { item: linkItem } = config;
+ 
+  return {
+    indexEntries: {},
+    navItem: {
+      title: linkItem.link,
+      href: linkItem.href,
+      type: "link",
+    },
+  };
+};
+ 
+ +
+
+ + + + + + + + \ No newline at end of file diff --git a/coverage/visitors/visit-page.ts.html b/coverage/visitors/visit-page.ts.html new file mode 100644 index 000000000..2e8259fce --- /dev/null +++ b/coverage/visitors/visit-page.ts.html @@ -0,0 +1,322 @@ + + + + + + Code coverage report for visitors/visit-page.ts + + + + + + + + + +
+
+

All files / visitors visit-page.ts

+
+ +
+ 100% + Statements + 12/12 +
+ + +
+ 100% + Branches + 12/12 +
+ + +
+ 100% + Functions + 1/1 +
+ + +
+ 100% + Lines + 12/12 +
+ + +
+

+ Press n or j to go to the next uncovered block, b, p or k for the previous block. +

+ +
+
+

+
1 +2 +3 +4 +5 +6 +7 +8 +9 +10 +11 +12 +13 +14 +15 +16 +17 +18 +19 +20 +21 +22 +23 +24 +25 +26 +27 +28 +29 +30 +31 +32 +33 +34 +35 +36 +37 +38 +39 +40 +41 +42 +43 +44 +45 +46 +47 +48 +49 +50 +51 +52 +53 +54 +55 +56 +57 +58 +59 +60 +61 +62 +63 +64 +65 +66 +67 +68 +69 +70 +71 +72 +73 +74 +75 +76 +77 +78 +79 +80  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +3x +  +  +  +  +  +  +  +  +  +22x +22x +22x +  +22x +  +  +  +  +22x +  +  +22x +  +  +  +  +  +  +  +  +  +  +  +22x +  +  +  +  +  +  +  +  +22x +4x +4x +  +  +  +  +  +  +  +  +22x +  + 
import { kebabCase } from "lodash-es";
+ 
+import type { PageConfig } from "@/content-indexer/types/docsYaml.js";
+import type { NavItem } from "@/content-indexer/types/navigation.js";
+import {
+  normalizeFilePath,
+  normalizeSlug,
+} from "@/content-indexer/utils/normalization.js";
+ 
+import type { VisitorConfigBase, VisitorResult } from "./index.js";
+ 
+export interface PageVisitorConfig extends VisitorConfigBase {
+  item: PageConfig;
+}
+ 
+/**
+ * Visits a page item from docs.yml.
+ *
+ * Builds:
+ * - Path index entry for URL routing
+ * - Navigation item for sidebar (unless hidden)
+ * - Algolia record for search (unless hidden)
+ */
+export const visitPage = ({
+  item: pageItem,
+  parentPath,
+  tab,
+  repo,
+  contentCache,
+  context,
+  navigationAncestors,
+}: PageVisitorConfig): VisitorResult => {
+  // Look up cached MDX content
+  const cached = contentCache.getMdxContent(pageItem.path);
+  const frontmatterSlug = normalizeSlug(cached?.frontmatter.slug);
+  const urlSlug = pageItem.slug ?? kebabCase(pageItem.page);
+ 
+  const pagePathBuilder = parentPath.apply({
+    fullSlug: frontmatterSlug?.split("/"),
+    urlSlug,
+  });
+ 
+  const finalPath = pagePathBuilder.get();
+ 
+  // Build index entry
+  const indexEntries = {
+    [finalPath]: {
+      type: "mdx" as const,
+      filePath: normalizeFilePath(pageItem.path, repo),
+      source: frontmatterSlug
+        ? ("frontmatter" as const)
+        : ("docs-yml" as const),
+      tab,
+    },
+  };
+ 
+  // Build nav item (skip if hidden)
+  const navItem: NavItem | undefined = pageItem.hidden
+    ? undefined
+    : {
+        title: pageItem.page,
+        path: `/${finalPath}`,
+        type: "page",
+      };
+ 
+  // Build Algolia record (if content available and not hidden)
+  if (cached && navItem) {
+    const title = cached.frontmatter.title || pageItem.page;
+    context.addAlgoliaRecord({
+      pageType: "Guide",
+      path: finalPath,
+      title,
+      content: cached.content,
+      breadcrumbs: navigationAncestors, // Excludes current page
+    });
+  }
+ 
+  return { indexEntries, navItem };
+};
+ 
+ +
+
+ + + + + + + + \ No newline at end of file diff --git a/coverage/visitors/visit-section.ts.html b/coverage/visitors/visit-section.ts.html new file mode 100644 index 000000000..c36e85a09 --- /dev/null +++ b/coverage/visitors/visit-section.ts.html @@ -0,0 +1,520 @@ + + + + + + Code coverage report for visitors/visit-section.ts + + + + + + + + + +
+
+

All files / visitors visit-section.ts

+
+ +
+ 100% + Statements + 31/31 +
+ + +
+ 83.33% + Branches + 15/18 +
+ + +
+ 100% + Functions + 5/5 +
+ + +
+ 100% + Lines + 31/31 +
+ + +
+

+ Press n or j to go to the next uncovered block, b, p or k for the previous block. +

+ +
+
+

+
1 +2 +3 +4 +5 +6 +7 +8 +9 +10 +11 +12 +13 +14 +15 +16 +17 +18 +19 +20 +21 +22 +23 +24 +25 +26 +27 +28 +29 +30 +31 +32 +33 +34 +35 +36 +37 +38 +39 +40 +41 +42 +43 +44 +45 +46 +47 +48 +49 +50 +51 +52 +53 +54 +55 +56 +57 +58 +59 +60 +61 +62 +63 +64 +65 +66 +67 +68 +69 +70 +71 +72 +73 +74 +75 +76 +77 +78 +79 +80 +81 +82 +83 +84 +85 +86 +87 +88 +89 +90 +91 +92 +93 +94 +95 +96 +97 +98 +99 +100 +101 +102 +103 +104 +105 +106 +107 +108 +109 +110 +111 +112 +113 +114 +115 +116 +117 +118 +119 +120 +121 +122 +123 +124 +125 +126 +127 +128 +129 +130 +131 +132 +133 +134 +135 +136 +137 +138 +139 +140 +141 +142 +143 +144 +145 +146  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +2x +  +  +  +  +  +  +  +  +  +  +  +12x +12x +12x +  +  +  +12x +  +  +12x +1x +1x +1x +  +1x +  +  +  +  +  +1x +1x +  +  +1x +  +  +  +  +  +  +  +1x +1x +1x +  +  +  +  +  +  +  +  +  +  +12x +  +  +  +  +  +  +12x +  +  +  +  +  +  +  +  +12x +  +  +  +  +  +  +  +12x +  +  +  +  +12x +15x +  +  +  +  +  +  +  +  +12x +15x +  +  +  +12x +15x +  +15x +  +  +12x +1x +  +  +  +11x +  +11x +  + 
import { kebabCase } from "lodash-es";
+ 
+import type { SectionConfig } from "@/content-indexer/types/docsYaml.js";
+import type { NavItem } from "@/content-indexer/types/navigation.js";
+import type { PathIndex } from "@/content-indexer/types/pathIndex.js";
+import {
+  normalizeFilePath,
+  normalizeSlug,
+} from "@/content-indexer/utils/normalization.js";
+ 
+import type {
+  VisitorConfig,
+  VisitorConfigBase,
+  VisitorResult,
+} from "./index.js";
+ 
+export interface SectionVisitorConfig extends VisitorConfigBase {
+  item: SectionConfig;
+}
+ 
+/**
+ * Visits a section item from docs.yml.
+ *
+ * Builds:
+ * - Path index entry for overview page (if exists)
+ * - Hierarchical navigation with children
+ * - Algolia record for overview page (if exists)
+ * - Recursively processes all child items
+ */
+export const visitSection = (
+  config: SectionVisitorConfig,
+  visitNavigationItem: (config: VisitorConfig) => VisitorResult,
+): VisitorResult => {
+  const {
+    item: sectionItem,
+    parentPath,
+    tab,
+    repo,
+    contentCache,
+    context,
+    navigationAncestors,
+  } = config;
+  const sectionUrlSlug = sectionItem.slug ?? kebabCase(sectionItem.section);
+  const skipSlug = sectionItem["skip-slug"] ?? false;
+ 
+  let sectionFullSlug: string[] | undefined;
+  let sectionPath: string | undefined;
+  const indexEntries: PathIndex = {};
+ 
+  // If there's an overview page, look up cached content
+  if (sectionItem.path) {
+    const cached = contentCache.getMdxContent(sectionItem.path);
+    const normalizedSlug = normalizeSlug(cached?.frontmatter.slug as string);
+    sectionFullSlug = normalizedSlug?.split("/");
+ 
+    const sectionPathBuilder = parentPath.apply({
+      fullSlug: sectionFullSlug,
+      urlSlug: sectionUrlSlug,
+      skipUrlSlug: skipSlug,
+    });
+ 
+    const finalPath = sectionPathBuilder.get();
+    sectionPath = `/${finalPath}`;
+ 
+    // Add overview page to index
+    indexEntries[finalPath] = {
+      type: "mdx",
+      filePath: normalizeFilePath(sectionItem.path, repo),
+      source: normalizedSlug ? "frontmatter" : "docs-yml",
+      tab,
+    };
+ 
+    // Build Algolia record for section overview page (if content available)
+    Eif (cached) {
+      const title = (cached.frontmatter.title as string) || sectionItem.section;
+      context.addAlgoliaRecord({
+        pageType: "Guide",
+        path: finalPath,
+        title,
+        content: cached.content,
+        breadcrumbs: navigationAncestors, // Excludes current section
+      });
+    }
+  }
+ 
+  // Create path builder for children
+  const childPathBuilder = parentPath.apply({
+    fullSlug: sectionFullSlug,
+    urlSlug: sectionUrlSlug,
+    skipUrlSlug: skipSlug,
+  });
+ 
+  // Build section nav item first (for navigation tree)
+  const sectionNavItem: NavItem = {
+    title: sectionItem.section,
+    path: sectionPath,
+    type: "section",
+    children: [], // Will be populated below
+  };
+ 
+  // Create breadcrumb (simple copy, no path computation needed)
+  // If section has no overview page, path will be undefined - this is OK
+  const sectionBreadcrumb: NavItem = {
+    title: sectionItem.section,
+    path: sectionPath, // undefined if no overview page
+    type: "section",
+    children: [],
+  };
+ 
+  // Update ancestors to include current section (using breadcrumb copy)
+  const childAncestors = sectionItem.hidden
+    ? navigationAncestors // Don't include hidden sections in breadcrumbs
+    : [...navigationAncestors, sectionBreadcrumb];
+ 
+  // Process all children with correct breadcrumbs
+  const childResults = sectionItem.contents.map((childItem) =>
+    visitNavigationItem({
+      ...config,
+      item: childItem,
+      parentPath: childPathBuilder,
+      navigationAncestors: childAncestors,
+    }),
+  );
+ 
+  // Merge child index entries
+  childResults.forEach((result) => {
+    Object.assign(indexEntries, result.indexEntries);
+  });
+ 
+  // Build children nav items (flatten arrays from API refs)
+  const children: NavItem[] = childResults
+    .map((result) => result.navItem)
+    .flat()
+    .filter((child): child is NavItem => child !== undefined);
+ 
+  // Only include section in nav if it has children and is not hidden
+  if (children.length === 0 || sectionItem.hidden) {
+    return { indexEntries, navItem: undefined };
+  }
+ 
+  // Update section nav item with children
+  sectionNavItem.children = children;
+ 
+  return { indexEntries, navItem: sectionNavItem };
+};
+ 
+ +
+
+ + + + + + + + \ No newline at end of file diff --git a/package.json b/package.json index f63f7c1e7..f25f13e62 100644 --- a/package.json +++ b/package.json @@ -26,6 +26,8 @@ "validate:rest": "./scripts/generate-open-api.sh --validate-only", "validate:rpc": "tsx ./scripts/validate-rpc.ts", "validate": "pnpm run validate:rest & pnpm run validate:rpc", + "test:run": "vitest run", + "test:coverage": "vitest run --coverage", "typecheck": "tsc --noEmit", "lint": "pnpm run lint:eslint & pnpm run lint:prettier & pnpm run typecheck", "lint:fix": "pnpm run lint:eslint:fix & pnpm run lint:prettier:fix", @@ -67,6 +69,7 @@ "@types/react": "^19.1.12", "@types/remove-markdown": "^0.3.4", "@typescript-eslint/parser": "^8.31.0", + "@vitest/coverage-v8": "^4.0.16", "chokidar": "^4.0.3", "eslint": "^9.25.1", "eslint-config-prettier": "^10.1.2", @@ -86,6 +89,7 @@ "tsx": "^4.20.6", "typescript": "^5.8.3", "typescript-eslint": "^8.31.0", + "vite-tsconfig-paths": "^6.0.3", "vitest": "^4.0.14" } } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 37561c3a7..79bc56c09 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -75,6 +75,9 @@ importers: '@typescript-eslint/parser': specifier: ^8.31.0 version: 8.31.0(eslint@9.25.1(jiti@2.4.2))(typescript@5.8.3) + '@vitest/coverage-v8': + specifier: ^4.0.16 + version: 4.0.16(vitest@4.0.16(@opentelemetry/api@1.9.0)(@types/node@22.14.1)(jiti@2.4.2)(tsx@4.21.0)(yaml@2.7.1)) chokidar: specifier: ^4.0.3 version: 4.0.3 @@ -132,6 +135,9 @@ importers: typescript-eslint: specifier: ^8.31.0 version: 8.31.0(eslint@9.25.1(jiti@2.4.2))(typescript@5.8.3) + vite-tsconfig-paths: + specifier: ^6.0.3 + version: 6.0.3(typescript@5.8.3)(vite@7.3.1(@types/node@22.14.1)(jiti@2.4.2)(tsx@4.21.0)(yaml@2.7.1)) vitest: specifier: ^4.0.14 version: 4.0.16(@opentelemetry/api@1.9.0)(@types/node@22.14.1)(jiti@2.4.2)(tsx@4.21.0)(yaml@2.7.1) @@ -210,15 +216,28 @@ packages: resolution: {integrity: sha512-4A/SCr/2KLd5jrtOMFzaKjVtAei3+2r/NChoBNoZ3EyP/+GlhoaEGoWOZUmFmoITP7zOJyHIMm+DYRd8o3PvHA==} engines: {node: '>=6.9.0'} + '@babel/helper-string-parser@7.27.1': + resolution: {integrity: sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==} + engines: {node: '>=6.9.0'} + '@babel/helper-validator-identifier@7.25.9': resolution: {integrity: sha512-Ed61U6XJc3CVRfkERJWDz4dJwKe7iLmmJsbOGu9wSloNSFttHV0I8g6UAgb7qnK5ly5bGLPd4oXZlxCdANBOWQ==} engines: {node: '>=6.9.0'} + '@babel/helper-validator-identifier@7.28.5': + resolution: {integrity: sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==} + engines: {node: '>=6.9.0'} + '@babel/parser@7.27.0': resolution: {integrity: sha512-iaepho73/2Pz7w2eMS0Q5f83+0RKI7i4xmiYeBmDzfRVbQtTOG7Ts0S4HzJVsTMGI9keU8rNfuZr8DKfSt7Yyg==} engines: {node: '>=6.0.0'} hasBin: true + '@babel/parser@7.28.5': + resolution: {integrity: sha512-KKBU1VGYR7ORr3At5HAtUQ+TV3SzRCXmA/8OdDZiLDBIZxVyzXuztPjfLd3BV1PRAQGCMWWSHYhL0F8d5uHBDQ==} + engines: {node: '>=6.0.0'} + hasBin: true + '@babel/runtime@7.27.0': resolution: {integrity: sha512-VtPOkrdPHZsKc/clNqyi9WUA8TINkZ4cGk63UUE3u4pmB2k+ZMQRDuIOagv8UVd6j7k0T3+RRIb7beKTebNbcw==} engines: {node: '>=6.9.0'} @@ -235,6 +254,14 @@ packages: resolution: {integrity: sha512-H45s8fVLYjbhFH62dIJ3WtmJ6RSPt/3DRO0ZcT2SUiYiQyz3BLVb9ADEnLl91m74aQPS3AzzeajZHYOalWe3bg==} engines: {node: '>=6.9.0'} + '@babel/types@7.28.5': + resolution: {integrity: sha512-qQ5m48eI/MFLQ5PxQj4PFaprjyCTLI37ElWMmNs0K8Lk3dVeOdNpB3ks8jc7yM5CDmVC73eMVk/trk3fgmrUpA==} + engines: {node: '>=6.9.0'} + + '@bcoe/v8-coverage@1.0.2': + resolution: {integrity: sha512-6zABk/ECA/QYSCQ1NGiVwwbQerUCZ+TQbp64Q3AgmfNvurHH0j8TtXa1qbShXA6qqkpAj4V5W8pP6mLe1mcMqA==} + engines: {node: '>=18'} + '@blakeembrey/deque@1.0.5': resolution: {integrity: sha512-6xnwtvp9DY1EINIKdTfvfeAtCYw4OqBZJhtiqkT3ivjnEfa25VQ3TsKvaFfKm8MyGIEfE95qLe+bNEt3nB0Ylg==} @@ -551,6 +578,9 @@ packages: '@jridgewell/trace-mapping@0.3.25': resolution: {integrity: sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==} + '@jridgewell/trace-mapping@0.3.31': + resolution: {integrity: sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==} + '@jsdevtools/ono@7.1.3': resolution: {integrity: sha512-4JQNk+3mVzK3xh2rqd6RB4J46qUR19azEHBneZyTZM+c456qOrbbM/5xcR8huNCCcbVt7+UmizG6GuUvPvKUYg==} @@ -1150,6 +1180,15 @@ packages: '@upstash/redis@1.36.1': resolution: {integrity: sha512-N6SjDcgXdOcTAF+7uNoY69o7hCspe9BcA7YjQdxVu5d25avljTwyLaHBW3krWjrP0FfocgMk94qyVtQbeDp39A==} + '@vitest/coverage-v8@4.0.16': + resolution: {integrity: sha512-2rNdjEIsPRzsdu6/9Eq0AYAzYdpP6Bx9cje9tL3FE5XzXRQF1fNU9pe/1yE8fCrS0HD+fBtt6gLPh6LI57tX7A==} + peerDependencies: + '@vitest/browser': 4.0.16 + vitest: 4.0.16 + peerDependenciesMeta: + '@vitest/browser': + optional: true + '@vitest/expect@4.0.16': resolution: {integrity: sha512-eshqULT2It7McaJkQGLkPjPjNph+uevROGuIMJdG3V+0BSR2w9u6J9Lwu+E8cK5TETlfou8GRijhafIMhXsimA==} @@ -1249,6 +1288,9 @@ packages: resolution: {integrity: sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==} engines: {node: '>=12'} + ast-v8-to-istanbul@0.3.10: + resolution: {integrity: sha512-p4K7vMz2ZSk3wN8l5o3y2bJAoZXT3VuJI5OLTATY/01CYWumWvwkUw0SqDBnNq6IiTO3qDa1eSQDibAV8g7XOQ==} + asynckit@0.4.0: resolution: {integrity: sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==} @@ -1801,6 +1843,9 @@ packages: resolution: {integrity: sha512-7ACyT3wmyp3I61S4fG682L0VA2RGD9otkqGJIwNUMF1SWUombIIk+af1unuDYgMm082aHYwD+mzJvv9Iu8dsgg==} engines: {node: '>=18'} + globrex@0.1.2: + resolution: {integrity: sha512-uHJgbwAMwNFf5mLst7IWLNg14x1CkeqglJb/K3doi4dw6q2IvAAmM/Y81kevy83wP+Sst+nutFTYOGg3d1lsxg==} + gopd@1.2.0: resolution: {integrity: sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==} engines: {node: '>= 0.4'} @@ -1840,6 +1885,9 @@ packages: resolution: {integrity: sha512-puUZAUKT5m8Zzvs72XWy3HtvVbTWljRE66cP60bxJzAqf2DgICo7lYTY2IHUmLnNpjYvw5bvmoHvPc0QO2a62w==} engines: {node: ^16.14.0 || >=18.0.0} + html-escaper@2.0.2: + resolution: {integrity: sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==} + http2-client@1.3.5: resolution: {integrity: sha512-EC2utToWl4RKfs5zd36Mxq7nzHHBuomZboI0yYL6Y0RmBgT7Sgkq4rQ0ezFTYoIsSs7Tm9SJe+o2FcAg6GBhGA==} @@ -1971,6 +2019,22 @@ packages: isomorphic-fetch@3.0.0: resolution: {integrity: sha512-qvUtwJ3j6qwsF3jLxkZ72qCgjMysPzDfeV240JHiGZsANBYd+EEuu35v7dfrJ9Up0Ak07D7GGSkGhCHTqg/5wA==} + istanbul-lib-coverage@3.2.2: + resolution: {integrity: sha512-O8dpsF+r0WV/8MNRKfnmrtCWhuKjxrq2w+jpzBL5UZKTi2LeVWnWOmWRxFlesJONmc+wLAGvKQZEOanko0LFTg==} + engines: {node: '>=8'} + + istanbul-lib-report@3.0.1: + resolution: {integrity: sha512-GCfE1mtsHGOELCU8e/Z7YWzpmybrx/+dSTfLrvY8qRmaY6zXTKWn6WQIjaAFw069icm6GVMNkgu0NzI4iPZUNw==} + engines: {node: '>=10'} + + istanbul-lib-source-maps@5.0.6: + resolution: {integrity: sha512-yg2d+Em4KizZC5niWhQaIomgf5WlL4vOOjZ5xGCmF8SnPE/mDWWXgvRExdcpCgh9lLRRa1/fSYp2ymmbJ1pI+A==} + engines: {node: '>=10'} + + istanbul-reports@3.2.0: + resolution: {integrity: sha512-HGYWWS/ehqTV3xN10i23tkPkpH46MLCIMFNCaaKNavAXTF1RkqxawEPtnjnGZ6XKSInBKkiOA5BKS+aZiY3AvA==} + engines: {node: '>=8'} + jackspeak@3.4.3: resolution: {integrity: sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==} @@ -2000,6 +2064,9 @@ packages: js-tokens@4.0.0: resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==} + js-tokens@9.0.1: + resolution: {integrity: sha512-mxa9E9ITFOt0ban3j6L5MpjwegGz6lBQmM1IJkWeBZGcMxto50+eWdjC/52xDbS2vy0k7vIMK0Fe2wfL9OQSpQ==} + js-yaml@3.14.2: resolution: {integrity: sha512-PMSmkqxr106Xa156c2M265Z+FTrPl+oxd/rgOQy2tijQeK5TxQ43psO1ZCwhVOSdnn+RzkzlRz/eY4BgJBYVpg==} hasBin: true @@ -2134,6 +2201,13 @@ packages: magic-string@0.30.21: resolution: {integrity: sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==} + magicast@0.5.1: + resolution: {integrity: sha512-xrHS24IxaLrvuo613F719wvOIv9xPHFWQHuvGUBmPnCA/3MQxKI3b+r7n1jAoDHmsbC5bRhTZYR77invLAxVnw==} + + make-dir@4.0.0: + resolution: {integrity: sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw==} + engines: {node: '>=10'} + mark.js@8.11.1: resolution: {integrity: sha512-1I+1qpDt4idfgLQG+BNWmrqku+7/2bi5nLf4YwF8y8zXvmfiTBY3PV3ZibfrjBueCByROpuBjLLFCajqkgYoLQ==} @@ -3101,6 +3175,16 @@ packages: peerDependencies: typescript: '>=4.8.4' + tsconfck@3.1.6: + resolution: {integrity: sha512-ks6Vjr/jEw0P1gmOVwutM3B7fWxoWBL2KRDb1JfqGVawBmO5UsvmWOQFGHBPl5yxYz4eERr19E6L7NMv+Fej4w==} + engines: {node: ^18 || >=20} + hasBin: true + peerDependencies: + typescript: ^5.0.0 + peerDependenciesMeta: + typescript: + optional: true + tslib@2.6.2: resolution: {integrity: sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==} @@ -3255,6 +3339,14 @@ packages: vfile@6.0.3: resolution: {integrity: sha512-KzIbH/9tXat2u30jf+smMwFCsno4wHVdNmzFyL+T/L3UGqqk6JKfVqOFOZEpZSHADH1k40ab6NUIXZq422ov3Q==} + vite-tsconfig-paths@6.0.3: + resolution: {integrity: sha512-7bL7FPX/DSviaZGYUKowWF1AiDVWjMjxNbE8lyaVGDezkedWqfGhlnQ4BZXre0ZN5P4kAgIJfAlgFDVyjrCIyg==} + peerDependencies: + vite: '*' + peerDependenciesMeta: + vite: + optional: true + vite@7.3.1: resolution: {integrity: sha512-w+N7Hifpc3gRjZ63vYBXA56dvvRlNWRczTdmCBBa+CotUzAPf5b7YMdMR/8CQoeYE5LX3W4wj6RYTgonm1b9DA==} engines: {node: ^20.19.0 || >=22.12.0} @@ -3529,12 +3621,20 @@ snapshots: '@babel/helper-string-parser@7.25.9': {} + '@babel/helper-string-parser@7.27.1': {} + '@babel/helper-validator-identifier@7.25.9': {} + '@babel/helper-validator-identifier@7.28.5': {} + '@babel/parser@7.27.0': dependencies: '@babel/types': 7.27.0 + '@babel/parser@7.28.5': + dependencies: + '@babel/types': 7.28.5 + '@babel/runtime@7.27.0': dependencies: regenerator-runtime: 0.14.1 @@ -3562,6 +3662,13 @@ snapshots: '@babel/helper-string-parser': 7.25.9 '@babel/helper-validator-identifier': 7.25.9 + '@babel/types@7.28.5': + dependencies: + '@babel/helper-string-parser': 7.27.1 + '@babel/helper-validator-identifier': 7.28.5 + + '@bcoe/v8-coverage@1.0.2': {} + '@blakeembrey/deque@1.0.5': {} '@blakeembrey/template@1.2.0': {} @@ -3780,6 +3887,11 @@ snapshots: '@jridgewell/resolve-uri': 3.1.2 '@jridgewell/sourcemap-codec': 1.5.0 + '@jridgewell/trace-mapping@0.3.31': + dependencies: + '@jridgewell/resolve-uri': 3.1.2 + '@jridgewell/sourcemap-codec': 1.5.5 + '@jsdevtools/ono@7.1.3': {} '@jsep-plugin/assignment@1.3.0(jsep@1.4.0)': @@ -4489,6 +4601,23 @@ snapshots: dependencies: uncrypto: 0.1.3 + '@vitest/coverage-v8@4.0.16(vitest@4.0.16(@opentelemetry/api@1.9.0)(@types/node@22.14.1)(jiti@2.4.2)(tsx@4.21.0)(yaml@2.7.1))': + dependencies: + '@bcoe/v8-coverage': 1.0.2 + '@vitest/utils': 4.0.16 + ast-v8-to-istanbul: 0.3.10 + istanbul-lib-coverage: 3.2.2 + istanbul-lib-report: 3.0.1 + istanbul-lib-source-maps: 5.0.6 + istanbul-reports: 3.2.0 + magicast: 0.5.1 + obug: 2.1.1 + std-env: 3.10.0 + tinyrainbow: 3.0.3 + vitest: 4.0.16(@opentelemetry/api@1.9.0)(@types/node@22.14.1)(jiti@2.4.2)(tsx@4.21.0)(yaml@2.7.1) + transitivePeerDependencies: + - supports-color + '@vitest/expect@4.0.16': dependencies: '@standard-schema/spec': 1.1.0 @@ -4597,6 +4726,12 @@ snapshots: assertion-error@2.0.1: {} + ast-v8-to-istanbul@0.3.10: + dependencies: + '@jridgewell/trace-mapping': 0.3.31 + estree-walker: 3.0.3 + js-tokens: 9.0.1 + asynckit@0.4.0: {} bail@2.0.2: {} @@ -5011,7 +5146,7 @@ snapshots: estree-walker@3.0.3: dependencies: - '@types/estree': 1.0.7 + '@types/estree': 1.0.8 esutils@2.0.3: {} @@ -5195,6 +5330,8 @@ snapshots: globals@15.15.0: {} + globrex@0.1.2: {} + gopd@1.2.0: {} graceful-fs@4.2.11: {} @@ -5233,6 +5370,8 @@ snapshots: dependencies: lru-cache: 10.4.3 + html-escaper@2.0.2: {} + http2-client@1.3.5: {} https-proxy-agent@7.0.6: @@ -5332,6 +5471,27 @@ snapshots: transitivePeerDependencies: - encoding + istanbul-lib-coverage@3.2.2: {} + + istanbul-lib-report@3.0.1: + dependencies: + istanbul-lib-coverage: 3.2.2 + make-dir: 4.0.0 + supports-color: 7.2.0 + + istanbul-lib-source-maps@5.0.6: + dependencies: + '@jridgewell/trace-mapping': 0.3.25 + debug: 4.4.0 + istanbul-lib-coverage: 3.2.2 + transitivePeerDependencies: + - supports-color + + istanbul-reports@3.2.0: + dependencies: + html-escaper: 2.0.2 + istanbul-lib-report: 3.0.1 + jackspeak@3.4.3: dependencies: '@isaacs/cliui': 8.0.2 @@ -5362,6 +5522,8 @@ snapshots: js-tokens@4.0.0: {} + js-tokens@9.0.1: {} + js-yaml@3.14.2: dependencies: argparse: 1.0.10 @@ -5501,6 +5663,16 @@ snapshots: dependencies: '@jridgewell/sourcemap-codec': 1.5.5 + magicast@0.5.1: + dependencies: + '@babel/parser': 7.28.5 + '@babel/types': 7.28.5 + source-map-js: 1.2.1 + + make-dir@4.0.0: + dependencies: + semver: 7.7.1 + mark.js@8.11.1: {} marked@4.3.0: {} @@ -7112,6 +7284,10 @@ snapshots: dependencies: typescript: 5.8.3 + tsconfck@3.1.6(typescript@5.8.3): + optionalDependencies: + typescript: 5.8.3 + tslib@2.6.2: {} tslib@2.8.1: {} @@ -7323,6 +7499,17 @@ snapshots: '@types/unist': 3.0.3 vfile-message: 4.0.2 + vite-tsconfig-paths@6.0.3(typescript@5.8.3)(vite@7.3.1(@types/node@22.14.1)(jiti@2.4.2)(tsx@4.21.0)(yaml@2.7.1)): + dependencies: + debug: 4.4.0 + globrex: 0.1.2 + tsconfck: 3.1.6(typescript@5.8.3) + optionalDependencies: + vite: 7.3.1(@types/node@22.14.1)(jiti@2.4.2)(tsx@4.21.0)(yaml@2.7.1) + transitivePeerDependencies: + - supports-color + - typescript + vite@7.3.1(@types/node@22.14.1)(jiti@2.4.2)(tsx@4.21.0)(yaml@2.7.1): dependencies: esbuild: 0.27.2 diff --git a/src/content-indexer/__tests__/index.test.ts b/src/content-indexer/__tests__/index.test.ts index ff01fdff5..672792548 100644 --- a/src/content-indexer/__tests__/index.test.ts +++ b/src/content-indexer/__tests__/index.test.ts @@ -5,6 +5,7 @@ import { buildAllOutputs } from "@/content-indexer/core/build-all-outputs.js"; import { ContentCache } from "@/content-indexer/core/content-cache.js"; import { scanDocsYml } from "@/content-indexer/core/scanner.js"; import { buildDocsContentIndex } from "@/content-indexer/indexers/main.js"; +import { readLocalDocsYml } from "@/content-indexer/utils/filesystem.js"; import { fetchFileFromGitHub } from "@/content-indexer/utils/github.js"; import { repoConfigFactory } from "@/content-indexer/utils/test-factories.js"; @@ -17,6 +18,14 @@ vi.mock("@/content-indexer/utils/github", async () => { }; }); +vi.mock("@/content-indexer/utils/filesystem", async () => { + const actual = await vi.importActual("@/content-indexer/utils/filesystem"); + return { + ...actual, + readLocalDocsYml: vi.fn(), + }; +}); + vi.mock("@/content-indexer/core/scanner", () => ({ scanDocsYml: vi.fn(), })); @@ -42,6 +51,16 @@ describe("buildDocsContentIndex", () => { }); test("should orchestrate all 3 phases successfully in preview mode", async () => { + const mockDocsYml = { + navigation: [ + { + tab: "guides", + layout: [{ page: "quickstart.mdx" }], + }, + ], + }; + vi.mocked(readLocalDocsYml).mockResolvedValue(mockDocsYml); + const mockScanResult = { mdxPaths: new Set(["quickstart.mdx"]), specNames: new Set(["ethereum-api"]), @@ -94,7 +113,7 @@ describe("buildDocsContentIndex", () => { expect(result).toEqual(mockResult); }); - test("should use GitHub API in production mode", async () => { + test("should read from GitHub for GitHub source type", async () => { const repoConfig = repoConfigFactory({ docsPrefix: "docs" }); const docsYmlContent = ` navigation: @@ -116,16 +135,21 @@ navigation: }); await buildDocsContentIndex({ - source: { type: "filesystem", basePath: "/test/fern" }, + source: { type: "github", repoConfig }, branchId: "main", repoConfig, mode: "production", }); - // Verify local filesystem was still used (both modes use local in main indexer) + // Verify GitHub API was used for docs.yml + expect(fetchFileFromGitHub).toHaveBeenCalledWith( + "docs/docs.yml", + repoConfig, + ); + // Verify GitHub source was used for batch fetch expect(batchFetchContent).toHaveBeenCalledWith(expect.any(Object), { - type: "filesystem", - basePath: "/test/fern", + type: "github", + repoConfig, }); }); }); diff --git a/vitest.config.ts b/vitest.config.ts new file mode 100644 index 000000000..6c6f7d616 --- /dev/null +++ b/vitest.config.ts @@ -0,0 +1,21 @@ +import tsconfigPaths from "vite-tsconfig-paths"; +import { defineConfig } from "vitest/config"; + +export default defineConfig({ + plugins: [tsconfigPaths()], + test: { + globals: true, + environment: "node", + coverage: { + provider: "v8", + reporter: ["text", "json", "html"], + exclude: [ + "**/__tests__/**", + "**/node_modules/**", + "**/dist/**", + "**/*.config.*", + "**/coverage/**", + ], + }, + }, +}); From d89c5e247cf406933e57f901026c71f2b7d81ad2 Mon Sep 17 00:00:00 2001 From: dslovinsky Date: Fri, 9 Jan 2026 10:41:11 -0800 Subject: [PATCH 12/27] docs: update content-indexer readme --- src/content-indexer/README.md | 406 ++++++++++++++++++++++++---------- 1 file changed, 285 insertions(+), 121 deletions(-) diff --git a/src/content-indexer/README.md b/src/content-indexer/README.md index 45466f2ca..b02e2ec37 100644 --- a/src/content-indexer/README.md +++ b/src/content-indexer/README.md @@ -1,26 +1,69 @@ # Content Indexer -Content Indexer is a single entry-point content indexing system that builds path -indexes, navigation trees, and Algolia search records from remote docs files. It -represents the core system that powers the Alchemy docs site's static generation -system. It processes `docs.yml` configuration files from multiple Github repos -to generate three key outputs: +Content Indexer is the indexing system that builds path indexes, navigation trees, and Algolia search records for the Alchemy docs site. It lives in the `docs` repository and processes documentation from three sources: + +1. **Main Docs** - Manual content from `docs/fern/docs.yml` (local filesystem) +2. **SDK References** - Generated content from `aa-sdk/docs/docs.yml` (GitHub API) +3. **Changelog** - Entries from `docs/fern/changelog/*.md` (local filesystem) + +## Three Indexers + +The system provides three independent indexers, each triggered by different content changes: + +### 1. Main Indexer (`pnpm index:main`) + +Indexes manual documentation content from the local `docs` repository. + +* **Trigger**: Changes to `docs/fern/docs.yml` or manual content files +* **Source**: Local filesystem (`docs/fern/`) +* **Modes**: `preview` (branch-scoped) and `production` (main branch) +* **Updates**: + * `{branch}/path-index:main` (Redis) + * `{branch}/nav-tree:{tab}` for all tabs (Redis) + * `{branch}_alchemy_docs` (Algolia) + +### 2. SDK Indexer (`pnpm index:sdk`) + +Indexes SDK reference documentation from the `aa-sdk` repository. + +* **Trigger**: Changes to `aa-sdk/docs/docs.yml` +* **Source**: GitHub API (aa-sdk repo) +* **Updates**: + * `{branch}/path-index:sdk` (Redis) + * `{branch}/nav-tree:wallets` (Redis, merged with existing manual content) + * `{branch}_alchemy_docs_sdk` (Algolia) + +### 3. Changelog Indexer (`pnpm index:changelog`) + +Indexes changelog entries from date-based markdown files. + +* **Trigger**: Changes to `docs/fern/changelog/*.md` +* **Source**: Local filesystem (`docs/fern/changelog/`) +* **Updates**: + * `{branch}/path-index:changelog` (Redis) + * No navigation tree (changelogs don't have a sidebar) + * `{branch}_alchemy_docs_changelog` (Algolia) + +## Key Outputs + +Each indexer generates up to three outputs: 1. **Path Index**: Maps URL paths to content sources (MDX files, API specs) 2. **Navigation Trees**: Hierarchical sidebar navigation for each documentation tab 3. **Algolia Index**: Searchable content records with metadata -The system is optimized for processing **4000+ pages** using a 3-phase parallel -fetching architecture that minimizes API calls. +All data is **branch-scoped** to support preview environments. ## Architecture -### 3-Phase Processing +### Main & SDK Indexers: 3-Phase Processing + +Both the main and SDK indexers use a unified `buildDocsContentIndex` function that processes `docs.yml` files through three phases: ```mermaid flowchart TD - Start[buildContentIndex] --> FetchYml[Fetch docs.yml] - FetchYml --> Phase1[Phase 1: SCAN] + Start[buildDocsContentIndex] --> ReadYml[Read docs.yml] + ReadYml --> Phase1[Phase 1: SCAN] Phase1 --> ScanDocs[scanDocsYml] ScanDocs --> CollectPaths[Collect all MDX paths] @@ -29,55 +72,108 @@ flowchart TD CollectPaths --> Phase2[Phase 2: BATCH FETCH] CollectSpecs --> Phase2 - Phase2 --> FetchMDX[Fetch all MDX files in parallel] - Phase2 --> FetchSpecs[Fetch all specs in parallel] - - FetchMDX --> Cache[ContentCache] - FetchSpecs --> Cache + Phase2 --> FetchContent[Fetch all content in parallel] + FetchContent --> Cache[ContentCache] Cache --> Phase3[Phase 3: PROCESS] Phase3 --> ProcessNav[visitNavigationItem with cache] - ProcessNav --> BuildIndex[Build PathIndex] - ProcessNav --> BuildNavTrees[Build Navigation Trees] - ProcessNav --> BuildAlgolia[Build Algolia Records with breadcrumbs] + ProcessNav --> BuildOutputs[Build all outputs] + + BuildOutputs --> PathIdx[PathIndex] + BuildOutputs --> NavTrees[Navigation Trees] + BuildOutputs --> Algolia[Algolia Records] + + PathIdx --> Upload[Upload in parallel] + NavTrees --> Upload + Algolia --> Upload - BuildIndex --> WritePhase[WRITE PHASE] - BuildNavTrees --> WritePhase - BuildAlgolia --> WritePhase + Upload --> Redis[storeToRedis] + Upload --> AlgoliaUpload[uploadToAlgolia] +``` + +**Key difference between main and SDK:** + +* **Main**: Reads from local filesystem (`docs/fern/`) +* **SDK**: Fetches from GitHub API (`aa-sdk/docs/`) + +### Changelog Indexer: Simpler Flow + +The changelog indexer reads date-based markdown files directly: - WritePhase --> Parallel[Upload in parallel] - Parallel --> Redis[storeToRedis] - Parallel --> Algolia[uploadToAlgolia] +```mermaid +flowchart TD + Start[buildChangelogIndex] --> ReadDir[Read fern/changelog/] + ReadDir --> ParseFiles[Parse date from filenames] + ParseFiles --> Parallel[Fetch all files in parallel] + Parallel --> BuildOutputs[Build PathIndex + Algolia records] + BuildOutputs --> Upload[Upload in parallel] + Upload --> Redis[storeToRedis] + Upload --> AlgoliaUpload[uploadToAlgolia] ``` +**Simpler because:** + +* No `docs.yml` to parse +* No navigation trees needed +* Direct file-to-route mapping + ### Why This Architecture? -With **4000+ pages**, GitHub API calls are the primary bottleneck. This 3-phase approach: +With **4000+ pages**, this 3-phase approach: -1. **Maximizes parallelization**: All fetches happen simultaneously -2. **Eliminates wait time**: No sequential blocking between fetches -3. **Single-pass processing**: Build all outputs together with all data - available +1. **Maximizes parallelization**: All content fetched simultaneously +2. **Eliminates duplicate fetches**: Single fetch per file, cached for all uses +3. **Single-pass processing**: Build all outputs in one traversal ## Key Concepts -### Constructed Data +### Branch-Scoped Keys + +All Redis keys and Algolia indices are scoped by branch to support preview environments: + +```text +Redis: +- main/path-index:main +- main/nav-tree:wallets +- feature-abc/path-index:main + +Algolia: +- main_alchemy_docs +- main_alchemy_docs_sdk +- feature-abc_alchemy_docs +``` + +### Wallets Navigation Tree Merging + +The wallets tab navigation tree requires special handling because it contains both: + +* **Manual content** (from main indexer) +* **SDK references** (from SDK indexer) + +**Bidirectional merging** ensures neither indexer overwrites the other: + +* **Main indexer**: Reads existing wallets tree → preserves SDK sections → merges with new manual sections +* **SDK indexer**: Reads existing wallets tree → preserves manual sections → merges with new SDK sections -* **Path Index**: Used by Next.js routing to determine which content to render - for a given URL - * Maps paths like `reference/ethereum-api-quickstart` to MDX files or API - operations - * Stored in Redis for fast lookup at runtime +SDK Reference sections are always positioned **second-to-last** (before Resources section). + +### Output Data Structures + +* **Path Index**: Used by Next.js routing to determine which content to render for a given URL + * Maps paths like `wallets/getting-started` to MDX files or API operations + * Contains metadata: type (mdx/openapi/openrpc), file path, source, tab + * Stored in Redis for fast lookup at runtime: `{branch}/path-index:{type}` * **Navigation Trees**: Used to render sidebar navigation * Hierarchical structure with sections, pages, and API endpoints - * One tree per tab (e.g., "reference", "guides") - * Stored in Redis + * One tree per top-level tab (guides, wallets, reference, etc.) + * Stored in Redis: `{branch}/nav-tree:{tab}` * **Algolia Index**: Used for site-wide search * Flat list of searchable pages with content, title, breadcrumbs - * Uploaded to Algolia for full-text search + * Markdown automatically stripped to plain text for better search results + * Uploaded to Algolia for full-text search: `{branch}_alchemy_docs[_{type}]` * Updated atomically to avoid search downtime ### ContentCache @@ -87,8 +183,11 @@ The `ContentCache` class provides O(1) lookup for all fetched content: * **MDX files**: Stores frontmatter and raw MDX body * **API specs**: Stores parsed OpenAPI/OpenRPC specifications -By fetching everything upfront in Phase 2, we eliminate duplicate GitHub API -calls during processing. +By fetching everything upfront in Phase 2, we eliminate duplicate API calls during processing. + +### Markdown Stripping + +All Algolia records automatically have markdown syntax stripped using the `remove-markdown` package in `truncateRecord()`. This ensures search results contain clean, readable text without formatting artifacts. ### Relatively Stable ObjectIDs for Algolia @@ -111,80 +210,76 @@ structure while maintaining uniqueness. ## Design Decisions -### 1. Three-Phase Architecture +### 1. Three Independent Indexers -**Why?** With 4000+ pages, GitHub API calls dominate execution time. By fetching -everything upfront in parallel, we significantly reduce total runtime. +**Why?** Content updates happen independently: -**Alternative considered:** Fetch-as-you-go +* Main docs change frequently (manual edits) +* SDK refs change when aa-sdk releases +* Changelog entries added weekly -* **Pros:** Simpler code, streaming approach -* **Cons:** Sequential fetches, slower for large repos +Running separate indexers allows efficient, targeted updates without re-processing unrelated content. -### 2. ContentCache for O(1) Lookups +### 2. Wallets Navigation Tree Merging -**Why?** Processing requires multiple lookups (ex. frontmatter for slugs, content -for Algolia). A Map-based cache makes these instant. +The wallets tab requires special handling because it contains both manual and SDK content. We use **bidirectional merging**: -### 3. Single-Pass Processing +* **Main indexer**: Preserves existing SDK sections when updating manual content +* **SDK indexer**: Preserves existing manual sections when updating SDK refs -**Why?** Build all outputs (index, nav, Algolia) in one traversal to avoid -processing each item multiple times. +This prevents either indexer from accidentally overwriting the other's content. The merge logic lives in `utils/nav-tree-merge.ts`. -**Alternative considered:** Separate passes for each output +### 3. Branch-Scoped Storage -* **Pros:** Simpler logic per pass -* **Cons:** Slower, harder to maintain breadcrumbs +All Redis keys and Algolia indices include the branch name to support preview environments: -### 4. Atomic Index Swap for Algolia +```text +main/path-index:main → Production +feature-xyz/path-index:main → Preview branch +``` -Ordinarily we could upload records individually to prod index and use `objectIDs` -to update records in place. The problem is our IDs are based on file paths, which -can change (files renamed, URLs restructured). When a path changes, we generate a -new objectID, leaving the old record orphaned in the index. Instead of managing -deletes and updates, we maintain separate indices for each content source (docs -vs wallets) and fully rebuild/replace the entire index on each run. This is done -via an atomic index swap. +This allows preview deployments to have independent data without interfering with production. -**How it works:** +### 4. Separate Algolia Indices -1. Upload to temporary index -2. Copy settings from production -3. Atomic move (replace production with temp index) +Main, SDK, and changelog content use separate Algolia indices: + +```text +- main_alchemy_docs +- main_alchemy_docs_sdk +- main_alchemy_docs_changelog +``` -**Why?** Ensure all records are up-to-date. Zero down-time: prevent users from ever seeing empty -search results during index updates. +**Why?** Each indexer runs independently, so separate indices allow atomic updates without affecting other content. The frontend searches all indices simultaneously using Algolia's multi-index feature. -**Alternative considered:** Update records in-place +### 5. Atomic Index Swap for Algolia -* **Pros:** Simpler -* **Cons:** High risk of orphaned records +Each indexer fully replaces its Algolia index on every run using atomic swap: -### 5. Separate Indices for Docs and Wallets +1. Upload to temporary index +2. Copy settings from production +3. Atomic move (replace production with temp) -Docs and Wallets content is indexed separately because their docs.yml is -maintained separately. That means we need to update one without the other. Since -we cannot update records in place, an atomic index swap of both docs and wallets -content would mean we need to generate both content simultaneously which is -inefficient. Instead, maintain separate indices and have the frontend search -both indices simultaneously. The same also applies to changelog which has a -separate indexer. +**Why?** Our objectIDs are content-based. When files move or are renamed, we generate new IDs, leaving old records orphaned. Full replacement ensures the index is always clean and up-to-date with zero search downtime. -**Why?** Independent update schedules +### 6. Markdown Stripping for Search -**How to search both:** Frontend uses `multipleQueries` API or InstantSearch's -multi-index feature. +All Algolia records have markdown syntax automatically stripped using `remove-markdown`. This happens in `truncateRecord()` before size checking, ensuring search results contain clean, readable text. ## Directory Structure ```text content-indexer/ +├── index.ts # CLI entry point with unified runner +├── indexers/ # Indexer implementations +│ ├── main.ts # buildDocsContentIndex (main & SDK) +│ └── changelog.ts # buildChangelogIndex ├── collectors/ # Output collectors (Phase 3) │ ├── algolia.ts # Collects Algolia search records │ ├── navigation-trees.ts # Collects navigation trees by tab │ ├── path-index.ts # Collects path index entries │ └── processing-context.ts # Unified context encapsulating all collectors -├── core/ # Core processing logic +├── core/ # Core processing logic (3-phase pipeline) │ ├── scanner.ts # Phase 1: Scan docs.yml for paths/specs │ ├── batch-fetcher.ts # Phase 2: Parallel fetch all content │ ├── content-cache.ts # Phase 2: In-memory cache for fetched content @@ -201,21 +296,26 @@ content-indexer/ │ └── process-openrpc.ts # Processes OpenRPC specifications ├── uploaders/ # Upload to external services │ ├── algolia.ts # Uploads to Algolia with atomic swap -│ └── redis.ts # Stores to Redis (path index & nav trees) +│ └── redis.ts # Stores to Redis with branch scoping ├── utils/ # Utility functions +│ ├── filesystem.ts # Local file reading utilities +│ ├── github.ts # GitHub API utilities +│ ├── nav-tree-merge.ts # Wallets nav tree merging logic │ ├── openapi.ts # OpenAPI-specific utilities │ ├── openrpc.ts # OpenRPC-specific utilities │ ├── navigation-helpers.ts # Navigation construction helpers │ ├── truncate-record.ts # Truncates Algolia records to size limit │ └── normalization.ts # Path normalization utilities -└── index.ts # Main entry point (buildContentIndex) +└── types/ # TypeScript type definitions + ├── indexer.ts # IndexerResult interface + └── ... # Other type definitions ``` -## Data Flow +## Data Flow (Main & SDK Indexers) ### Phase 1: Scan -First, fetch the `docs.yml` file from GitHub. Then scan it: +Read `docs.yml` (from filesystem or GitHub), then scan it: ```typescript scanDocsYml(docsYml) → { mdxPaths: Set, specNames: Set } @@ -230,12 +330,13 @@ scanDocsYml(docsYml) → { mdxPaths: Set, specNames: Set } ### Phase 2: Batch Fetch ```typescript -batchFetchContent(scanResult, repoConfig) → ContentCache +batchFetchContent(scanResult, source) → ContentCache ``` * Converts Sets to arrays and maps over them -* Fetches all MDX files in parallel with `Promise.all` -* Fetches all API specs in parallel with `Promise.all` +* Fetches all content in parallel with `Promise.all` + * **Filesystem source**: Reads local files with `fs.readFile` + * **GitHub source**: Fetches via GitHub API with `octokit` * Parses frontmatter from MDX files using `gray-matter` * Stores everything in `ContentCache` for O(1) lookup * **Maximum parallelization** - all I/O happens simultaneously @@ -243,7 +344,7 @@ batchFetchContent(scanResult, repoConfig) → ContentCache ### Phase 3: Process ```typescript -buildAllOutputs(docsYml, repo, cache) +buildAllOutputs(docsYml, contentCache, repoConfig) → { pathIndex, navigationTrees, algoliaRecords } ``` @@ -261,33 +362,51 @@ buildAllOutputs(docsYml, repo, cache) * Passes `navigationAncestors` through recursion for breadcrumbs * Returns all three outputs simultaneously -### Write Phase +### Upload Phase ```typescript Promise.all([ - storeToRedis(index, navigationTrees), - uploadToAlgolia(algoliaRecords), + storeToRedis(pathIndex, navigationTrees, { branchId, indexerType }), + uploadToAlgolia(algoliaRecords, { indexerType, branchId }), ]); ``` * Writes to Redis and Algolia in parallel +* Branch-scoped keys for preview support +* Special handling for wallets nav tree (bidirectional merge) * Algolia uses atomic swap (temp index → production) +## Changelog Indexer Flow + +The changelog indexer uses a simpler flow: + +1. **Read directory**: List all files in `fern/changelog/` +2. **Parse filenames**: Extract dates from `YYYY-MM-DD.md` pattern +3. **Fetch in parallel**: Read all files with `Promise.all` +4. **Build outputs**: Create path index + Algolia records (no nav trees) +5. **Upload**: Write to Redis and Algolia in parallel + ## Usage -### Running the Indexer +### Running the Indexers ```bash -# Index main docs -pnpm generate:content-index +# Main indexer (production mode - default) +pnpm index:main + +# Main indexer (preview mode - branch-scoped) +pnpm index:main:preview + +# SDK indexer (fetches from aa-sdk repo) +pnpm index:sdk -# Index wallet docs -pnpm generate:wallet-content-index +# Changelog indexer +pnpm index:changelog ``` ### Environment Variables -Required for Redis and Algolia upload: +Create a `.env` file (see `.env.example`): ```bash # Redis (required for path index and navigation trees) @@ -299,15 +418,56 @@ KV_URL=your_url # Algolia (required for search index) ALGOLIA_APP_ID=your_app_id ALGOLIA_ADMIN_API_KEY=your_admin_key -# Base name for indices (branch/type will be auto-appended) -# Examples: main_alchemy_docs, main_alchemy_docs_sdk, abc_alchemy_docs +# Base name for indices (branch and type will be auto-appended) +# Examples: main_alchemy_docs, main_alchemy_docs_sdk, feature-abc_alchemy_docs ALGOLIA_INDEX_NAME_BASE=alchemy_docs -# GitHub (optional - increases API rate limits) -GITHUB_TOKEN=your_personal_access_token +# GitHub (required for SDK indexer, optional for main - increases API rate limits) +GH_TOKEN=your_personal_access_token ``` -The indexer will skip uploads for any service with missing credentials. +### Testing + +```bash +# Run all tests +pnpm test:run + +# Run with coverage report +pnpm test:coverage + +# Watch mode +pnpm test +``` + +## Development + +### Running Locally + +1. Set up environment variables in `.env` +2. Run an indexer: + ```bash + pnpm index:main:preview + ``` +3. Check Redis/Algolia to verify data was written + +### Adding New Content Types + +To add a new content type that requires indexing: + +1. Create a new indexer in `indexers/` (or extend existing) +2. Add npm script to `package.json` +3. Update `index.ts` entry point to include new indexer type +4. Update `storeToRedis` and `uploadToAlgolia` if new storage patterns needed + +### Testing + +The test suite covers: + +* **Core logic**: 95-100% coverage for collectors, visitors, processors +* **Integration**: Full pipeline tests for each indexer +* **Edge cases**: Truncation, merging, error handling + +Low coverage in I/O utilities (`filesystem.ts`, `github.ts`) is expected and acceptable. ## Troubleshooting @@ -315,29 +475,33 @@ The indexer will skip uploads for any service with missing credentials. **Cause:** Spec name in docs.yml doesn't match filename in metadata.json -**Fix:** Check `API_NAME_TO_FILENAME` mapping in `lib/utils/apiSpecs.ts` +**Fix:** Check `API_NAME_TO_FILENAME` mapping in `utils/apiSpecs.ts` + +### "Failed to read docs.yml" -### "Failed to fetch MDX file" +**Cause:** -**Cause:** File path in docs.yml doesn't exist in GitHub repo +* (Main indexer) File doesn't exist locally at `fern/docs.yml` +* (SDK indexer) GitHub API authentication failed or file path incorrect **Fix:** -1. Verify file exists in GitHub -2. Check `repoConfig.stripPathPrefix` and `repoConfig.docsPrefix` are correct -3. Ensure path in docs.yml matches actual file path +1. Verify file exists at expected location +2. Check `GH_TOKEN` environment variable is set (for SDK indexer) +3. Verify `repoConfig.docsPrefix` is correct -### Slow indexing performance +### "Failed to read changelog file" -**Possible causes:** +**Cause:** Changelog file doesn't follow expected `YYYY-MM-DD.md` naming pattern -1. Check network connection to GitHub API -2. Verify GitHub API rate limits aren't being hit -3. Check if any individual file/spec is timing out +**Fix:** Ensure all changelog files in `fern/changelog/` are named like `2025-11-20.md` -### Algolia records missing breadcrumbs +### SDK References not appearing in wallets sidebar -**Cause:** Navigation ancestors not being passed through correctly +**Cause:** Main indexer ran after SDK indexer and didn't preserve SDK sections + +**Fix:** -**Fix:** Verify `visitNavigationItem` and visitor functions are receiving and -forwarding `navigationAncestors` array correctly through the visitor chain +1. Verify `mergeWalletsNavTree` is being called in `storeToRedis` +2. Check Redis to see if SDK sections exist: `GET main/nav-tree:wallets` +3. Re-run SDK indexer after main indexer to restore SDK sections From 98fa73b3809dfcce99d0253301da4f1c8965011d Mon Sep 17 00:00:00 2001 From: dslovinsky Date: Fri, 9 Jan 2026 10:45:17 -0800 Subject: [PATCH 13/27] fix: TS issue --- package.json | 3 +-- src/content-indexer/__tests__/index.test.ts | 2 +- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/package.json b/package.json index f25f13e62..d86cc5a10 100644 --- a/package.json +++ b/package.json @@ -41,8 +41,7 @@ "index:main": "tsx --env-file=.env src/content-indexer/index.ts --indexer=main --branch=main", "index:main:preview": "tsx --env-file=.env src/content-indexer/index.ts --indexer=main --mode=preview --branch=$(git rev-parse --abbrev-ref HEAD)", "index:sdk": "tsx --env-file=.env src/content-indexer/index.ts --indexer=sdk --branch=main", - "index:changelog": "tsx --env-file=.env src/content-indexer/index.ts --indexer=changelog --branch=main", - "index:watch": "tsx scripts/watch-and-index.ts" + "index:changelog": "tsx --env-file=.env src/content-indexer/index.ts --indexer=changelog --branch=main" }, "dependencies": { "@apidevtools/json-schema-ref-parser": "^12.0.2", diff --git a/src/content-indexer/__tests__/index.test.ts b/src/content-indexer/__tests__/index.test.ts index 672792548..1b661742a 100644 --- a/src/content-indexer/__tests__/index.test.ts +++ b/src/content-indexer/__tests__/index.test.ts @@ -55,7 +55,7 @@ describe("buildDocsContentIndex", () => { navigation: [ { tab: "guides", - layout: [{ page: "quickstart.mdx" }], + layout: [{ page: "quickstart.mdx", path: "quickstart.mdx" }], }, ], }; From dbad862d15a6171df20f666399a6032220bf4c21 Mon Sep 17 00:00:00 2001 From: dslovinsky Date: Fri, 9 Jan 2026 11:45:20 -0800 Subject: [PATCH 14/27] feat: only update algolia in prod & add TTL to preview redis entries --- src/content-indexer/README.md | 43 ++++++++++++++----- src/content-indexer/index.ts | 24 ++++++++--- .../uploaders/__tests__/redis.test.ts | 33 ++++++++++++-- src/content-indexer/uploaders/redis.ts | 25 ++++++++--- 4 files changed, 100 insertions(+), 25 deletions(-) diff --git a/src/content-indexer/README.md b/src/content-indexer/README.md index b02e2ec37..3003cf85a 100644 --- a/src/content-indexer/README.md +++ b/src/content-indexer/README.md @@ -16,11 +16,13 @@ Indexes manual documentation content from the local `docs` repository. * **Trigger**: Changes to `docs/fern/docs.yml` or manual content files * **Source**: Local filesystem (`docs/fern/`) -* **Modes**: `preview` (branch-scoped) and `production` (main branch) +* **Modes**: + * `production` - Full indexing with Algolia upload (default) + * `preview` - Branch-scoped indexing without Algolia upload * **Updates**: - * `{branch}/path-index:main` (Redis) - * `{branch}/nav-tree:{tab}` for all tabs (Redis) - * `{branch}_alchemy_docs` (Algolia) + * `{branch}/path-index:main` (Redis, 30-day TTL for preview branches) + * `{branch}/nav-tree:{tab}` for all tabs (Redis, 30-day TTL for preview branches) + * `{branch}_alchemy_docs` (Algolia, production mode only) ### 2. SDK Indexer (`pnpm index:sdk`) @@ -54,6 +56,21 @@ Each indexer generates up to three outputs: All data is **branch-scoped** to support preview environments. +### Preview Mode vs Production Mode + +**Production Mode** (default): + +* Uploads to both Redis and Algolia +* Redis keys have no expiration (permanent) +* Creates branch-scoped Algolia indices + +**Preview Mode** (`--mode=preview`): + +* Uploads to Redis only (skips Algolia) +* Redis keys expire after 30 days (automatic cleanup) +* Uses production Algolia indices for search +* Only available for main and changelog indexers (SDK indexer is production-only) + ## Architecture ### Main & SDK Indexers: 3-Phase Processing @@ -133,15 +150,19 @@ With **4000+ pages**, this 3-phase approach: All Redis keys and Algolia indices are scoped by branch to support preview environments: ```text -Redis: -- main/path-index:main -- main/nav-tree:wallets -- feature-abc/path-index:main +Redis (with TTL for preview branches): +- main/path-index:main (no expiration) +- main/nav-tree:wallets (no expiration) +- feature-abc/path-index:main (30-day TTL) +- feature-abc/nav-tree:wallets (30-day TTL) -Algolia: +Algolia (production mode only): - main_alchemy_docs - main_alchemy_docs_sdk -- feature-abc_alchemy_docs +- main_alchemy_docs_changelog + +Note: Preview branches do NOT create Algolia indices. +Preview environments use production search indices. ``` ### Wallets Navigation Tree Merging @@ -397,7 +418,7 @@ pnpm index:main # Main indexer (preview mode - branch-scoped) pnpm index:main:preview -# SDK indexer (fetches from aa-sdk repo) +# SDK indexer (fetches from aa-sdk repo, production only) pnpm index:sdk # Changelog indexer diff --git a/src/content-indexer/index.ts b/src/content-indexer/index.ts index 8d2b48d19..04f809f0c 100644 --- a/src/content-indexer/index.ts +++ b/src/content-indexer/index.ts @@ -94,15 +94,29 @@ const runIndexer = async ( const { pathIndex, algoliaRecords, navigationTrees } = await buildIndexResults(indexerType, branchId, mode); - console.info("\n📤 Uploading to Redis and Algolia..."); + const shouldUploadToAlgolia = mode !== "preview"; - await Promise.all([ + // Build upload promises array + const uploadPromises = [ storeToRedis(pathIndex, navigationTrees, { branchId, indexerType }), - uploadToAlgolia(algoliaRecords, { indexerType, branchId }), - ]); + ]; + + if (shouldUploadToAlgolia) { + uploadPromises.push( + uploadToAlgolia(algoliaRecords, { indexerType, branchId }), + ); + console.info("\n📤 Uploading to Redis and Algolia..."); + } else { + console.info("\n📤 Uploading to Redis..."); + console.info( + " ℹ️ Skipping Algolia upload (preview mode uses prod search)", + ); + } + + await Promise.all(uploadPromises); console.info( - `\n✅ ${indexerType.charAt(0).toUpperCase() + indexerType.slice(1)} indexer completed! (${Object.keys(pathIndex).length} routes, ${algoliaRecords.length} records)`, + `\n✅ ${indexerType.charAt(0).toUpperCase() + indexerType.slice(1)} indexer completed! (${Object.keys(pathIndex).length} routes${shouldUploadToAlgolia ? `, ${algoliaRecords.length} records` : ""})`, ); }; diff --git a/src/content-indexer/uploaders/__tests__/redis.test.ts b/src/content-indexer/uploaders/__tests__/redis.test.ts index 71094afa7..cbd11a3fb 100644 --- a/src/content-indexer/uploaders/__tests__/redis.test.ts +++ b/src/content-indexer/uploaders/__tests__/redis.test.ts @@ -21,7 +21,7 @@ describe("storeToRedis", () => { mockSet.mockClear(); }); - test("should store path index to Redis", async () => { + test("should store path index to Redis without TTL for main branch", async () => { const pathIndex: PathIndex = { "guides/quickstart": { type: "mdx", @@ -39,10 +39,33 @@ describe("storeToRedis", () => { expect(mockSet).toHaveBeenCalledWith( "main/path-index:main", JSON.stringify(pathIndex, null, 2), + {}, // No TTL for main branch ); }); - test("should store navigation trees to Redis", async () => { + test("should store path index to Redis with 30-day TTL for preview branches", async () => { + const pathIndex: PathIndex = { + "guides/quickstart": { + type: "mdx", + filePath: "fern/guides/quickstart.mdx", + source: "docs-yml", + tab: "guides", + }, + }; + + await storeToRedis(pathIndex, undefined, { + branchId: "feature-abc", + indexerType: "main", + }); + + expect(mockSet).toHaveBeenCalledWith( + "feature-abc/path-index:main", + JSON.stringify(pathIndex, null, 2), + { ex: 2592000 }, // 30 days in seconds + ); + }); + + test("should store navigation trees to Redis without TTL for main branch", async () => { const navigationTrees: NavigationTreesByTab = { guides: [ { @@ -61,10 +84,11 @@ describe("storeToRedis", () => { expect(mockSet).toHaveBeenCalledWith( "main/nav-tree:guides", JSON.stringify(navigationTrees.guides, null, 2), + {}, // No TTL for main branch ); }); - test("should store multiple navigation trees", async () => { + test("should store multiple navigation trees with correct TTL", async () => { const navigationTrees: NavigationTreesByTab = { guides: [ { @@ -90,10 +114,12 @@ describe("storeToRedis", () => { expect(mockSet).toHaveBeenCalledWith( "main/nav-tree:guides", JSON.stringify(navigationTrees.guides, null, 2), + {}, // No TTL for main branch ); expect(mockSet).toHaveBeenCalledWith( "main/nav-tree:reference", JSON.stringify(navigationTrees.reference, null, 2), + {}, // No TTL for main branch ); }); @@ -115,6 +141,7 @@ describe("storeToRedis", () => { expect(mockSet).toHaveBeenCalledWith( "main/path-index:sdk", JSON.stringify(pathIndex, null, 2), + {}, // No TTL for main branch ); }); diff --git a/src/content-indexer/uploaders/redis.ts b/src/content-indexer/uploaders/redis.ts index 883cfcc84..2ba69e979 100644 --- a/src/content-indexer/uploaders/redis.ts +++ b/src/content-indexer/uploaders/redis.ts @@ -1,3 +1,5 @@ +import type { SetCommandOptions } from "@upstash/redis"; + import type { NavigationTree, NavigationTreesByTab, @@ -6,6 +8,8 @@ import type { PathIndex } from "@/content-indexer/types/pathIndex.js"; import { mergeWalletsNavTree } from "@/content-indexer/utils/nav-tree-merge.js"; import { getRedis } from "@/content-indexer/utils/redis.js"; +const stringify = (data: unknown) => JSON.stringify(data, null, 2); + // Helper to count nav items recursively const countItems = (items: NavigationTree): number => { return items.reduce((sum, item) => { @@ -17,6 +21,8 @@ const countItems = (items: NavigationTree): number => { }, 0); }; +const PREVIEW_TTL_SECONDS = 30 * 24 * 60 * 60; // 30 days + /** * Stores path index and navigation trees to Redis with branch scoping. * @@ -36,13 +42,20 @@ export const storeToRedis = async ( ): Promise => { const redis = getRedis(); + // Determine TTL: no expiration for main branch, 30 days for preview branches + const isMainBranch = options.branchId === "main"; + const setOptions: SetCommandOptions = isMainBranch + ? {} + : { ex: PREVIEW_TTL_SECONDS }; + const ttlInfo = isMainBranch ? "" : " (30-day TTL)"; + // Store path index with branch scope const pathIndexKey = `${options.branchId}/path-index:${options.indexerType}`; const pathIndexPromise = redis - .set(pathIndexKey, JSON.stringify(pathIndex, null, 2)) + .set(pathIndexKey, stringify(pathIndex), setOptions) .then(() => { console.info( - `✅ Path index saved to Redis (${Object.keys(pathIndex).length} routes) -> ${pathIndexKey}`, + `✅ Path index saved to Redis (${Object.keys(pathIndex).length} routes) -> ${pathIndexKey}${ttlInfo}`, ); }); @@ -61,9 +74,9 @@ export const storeToRedis = async ( ); navTreePromises = [ - redis.set(navTreeKey, JSON.stringify(mergedTree, null, 2)).then(() => { + redis.set(navTreeKey, stringify(mergedTree), setOptions).then(() => { console.info( - `✅ Updated wallets nav tree with SDK refs (${countItems(mergedTree)} total items) -> ${navTreeKey}`, + `✅ Updated wallets nav tree with SDK refs (${countItems(mergedTree)} total items) -> ${navTreeKey}${ttlInfo}`, ); }), ]; @@ -81,9 +94,9 @@ export const storeToRedis = async ( } const itemCount = countItems(finalTree); - await redis.set(redisKey, JSON.stringify(finalTree, null, 2)); + await redis.set(redisKey, stringify(finalTree), setOptions); console.info( - `✅ Navigation tree for '${tab}' saved to Redis (${itemCount} items) -> ${redisKey}`, + `✅ Navigation tree for '${tab}' saved to Redis (${itemCount} items) -> ${redisKey}${ttlInfo}`, ); }, ); From 25525ad8be43ca9d9c2cd609d1686d1e68d45eec Mon Sep 17 00:00:00 2001 From: dslovinsky Date: Fri, 9 Jan 2026 12:55:53 -0800 Subject: [PATCH 15/27] build: remove unnecessary chokidar dep --- package.json | 1 - pnpm-lock.yaml | 17 ----------------- 2 files changed, 18 deletions(-) diff --git a/package.json b/package.json index d86cc5a10..9db7a6a70 100644 --- a/package.json +++ b/package.json @@ -69,7 +69,6 @@ "@types/remove-markdown": "^0.3.4", "@typescript-eslint/parser": "^8.31.0", "@vitest/coverage-v8": "^4.0.16", - "chokidar": "^4.0.3", "eslint": "^9.25.1", "eslint-config-prettier": "^10.1.2", "eslint-plugin-mdx": "^3.4.1", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 79bc56c09..23d3c2db3 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -78,9 +78,6 @@ importers: '@vitest/coverage-v8': specifier: ^4.0.16 version: 4.0.16(vitest@4.0.16(@opentelemetry/api@1.9.0)(@types/node@22.14.1)(jiti@2.4.2)(tsx@4.21.0)(yaml@2.7.1)) - chokidar: - specifier: ^4.0.3 - version: 4.0.3 eslint: specifier: ^9.25.1 version: 9.25.1(jiti@2.4.2) @@ -1378,10 +1375,6 @@ packages: resolution: {integrity: sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==} engines: {node: '>= 8.10.0'} - chokidar@4.0.3: - resolution: {integrity: sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==} - engines: {node: '>= 14.16.0'} - ci-info@4.2.0: resolution: {integrity: sha512-cYY9mypksY8NRqgDB1XD1RiJL338v/551niynFTGkZOO2LHuB2OmOYxDIe/ttN9AHwrqdum1360G3ald0W9kCg==} engines: {node: '>=8'} @@ -2714,10 +2707,6 @@ packages: resolution: {integrity: sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==} engines: {node: '>=8.10.0'} - readdirp@4.1.2: - resolution: {integrity: sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==} - engines: {node: '>= 14.18.0'} - redoc@2.4.0: resolution: {integrity: sha512-rFlfzFVWS9XJ6aYAs/bHnLhHP5FQEhwAHDBVgwb9L2FqDQ8Hu8rQ1G84iwaWXxZfPP9UWn7JdWkxI6MXr2ZDjw==} engines: {node: '>=6.9', npm: '>=3.0.0'} @@ -4814,10 +4803,6 @@ snapshots: optionalDependencies: fsevents: 2.3.3 - chokidar@4.0.3: - dependencies: - readdirp: 4.1.2 - ci-info@4.2.0: {} classnames@2.5.1: {} @@ -6386,8 +6371,6 @@ snapshots: dependencies: picomatch: 2.3.1 - readdirp@4.1.2: {} - redoc@2.4.0(core-js@3.41.0)(mobx@6.13.7)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)(styled-components@6.1.17(react-dom@19.1.0(react@19.1.0))(react@19.1.0)): dependencies: '@redocly/openapi-core': 1.34.2 From 3e45c4022cc572aec4518023cd58b973f547c141 Mon Sep 17 00:00:00 2001 From: dslovinsky Date: Fri, 9 Jan 2026 13:31:00 -0800 Subject: [PATCH 16/27] chore: remove Algolia base index name env var --- .env.example | 3 -- src/content-indexer/README.md | 3 -- .../uploaders/__tests__/algolia.test.ts | 48 ------------------- src/content-indexer/uploaders/algolia.ts | 12 ++--- 4 files changed, 3 insertions(+), 63 deletions(-) diff --git a/.env.example b/.env.example index f733d83c8..fc24425ae 100644 --- a/.env.example +++ b/.env.example @@ -26,6 +26,3 @@ KV_URL=your_kv_url_here ALGOLIA_APP_ID=your_app_id_here ALGOLIA_ADMIN_API_KEY=your_admin_api_key_here -# Base name for Algolia indices (branch and type will be auto-appended) -# Examples: main_alchemy_docs, main_alchemy_docs_sdk, abc_alchemy_docs -ALGOLIA_INDEX_NAME_BASE=alchemy_docs diff --git a/src/content-indexer/README.md b/src/content-indexer/README.md index 3003cf85a..65a25bd7a 100644 --- a/src/content-indexer/README.md +++ b/src/content-indexer/README.md @@ -439,9 +439,6 @@ KV_URL=your_url # Algolia (required for search index) ALGOLIA_APP_ID=your_app_id ALGOLIA_ADMIN_API_KEY=your_admin_key -# Base name for indices (branch and type will be auto-appended) -# Examples: main_alchemy_docs, main_alchemy_docs_sdk, feature-abc_alchemy_docs -ALGOLIA_INDEX_NAME_BASE=alchemy_docs # GitHub (required for SDK indexer, optional for main - increases API rate limits) GH_TOKEN=your_personal_access_token diff --git a/src/content-indexer/uploaders/__tests__/algolia.test.ts b/src/content-indexer/uploaders/__tests__/algolia.test.ts index 88c82f283..a67d78bfb 100644 --- a/src/content-indexer/uploaders/__tests__/algolia.test.ts +++ b/src/content-indexer/uploaders/__tests__/algolia.test.ts @@ -46,52 +46,4 @@ describe("uploadToAlgolia", () => { process.env.ALGOLIA_APP_ID = originalAppId; process.env.ALGOLIA_ADMIN_API_KEY = originalKey; }); - - test("should skip if base index name not configured", async () => { - const originalAppId = process.env.ALGOLIA_APP_ID; - const originalKey = process.env.ALGOLIA_ADMIN_API_KEY; - const originalBaseName = process.env.ALGOLIA_INDEX_NAME_BASE; - - process.env.ALGOLIA_APP_ID = "test-app-id"; - process.env.ALGOLIA_ADMIN_API_KEY = "test-key"; - delete process.env.ALGOLIA_INDEX_NAME_BASE; - - const consoleSpy = vi.spyOn(console, "warn").mockImplementation(() => {}); - const records: AlgoliaRecord[] = []; - - await uploadToAlgolia(records, { indexerType: "main", branchId: "main" }); - - expect(consoleSpy).toHaveBeenCalledWith( - expect.stringContaining("ALGOLIA_INDEX_NAME_BASE not set"), - ); - - consoleSpy.mockRestore(); - process.env.ALGOLIA_APP_ID = originalAppId; - process.env.ALGOLIA_ADMIN_API_KEY = originalKey; - process.env.ALGOLIA_INDEX_NAME_BASE = originalBaseName; - }); - - test("should skip if base index name not configured", async () => { - const originalAppId = process.env.ALGOLIA_APP_ID; - const originalKey = process.env.ALGOLIA_ADMIN_API_KEY; - const originalBaseName = process.env.ALGOLIA_INDEX_NAME_BASE; - - process.env.ALGOLIA_APP_ID = "test-app-id"; - process.env.ALGOLIA_ADMIN_API_KEY = "test-key"; - delete process.env.ALGOLIA_INDEX_NAME_BASE; - - const consoleSpy = vi.spyOn(console, "warn").mockImplementation(() => {}); - const records: AlgoliaRecord[] = []; - - await uploadToAlgolia(records, { indexerType: "main", branchId: "main" }); - - expect(consoleSpy).toHaveBeenCalledWith( - expect.stringContaining("ALGOLIA_INDEX_NAME_BASE not set"), - ); - - consoleSpy.mockRestore(); - process.env.ALGOLIA_APP_ID = originalAppId; - process.env.ALGOLIA_ADMIN_API_KEY = originalKey; - process.env.ALGOLIA_INDEX_NAME_BASE = originalBaseName; - }); }); diff --git a/src/content-indexer/uploaders/algolia.ts b/src/content-indexer/uploaders/algolia.ts index b6de96bd6..9154538cd 100644 --- a/src/content-indexer/uploaders/algolia.ts +++ b/src/content-indexer/uploaders/algolia.ts @@ -3,6 +3,8 @@ import { algoliasearch } from "algoliasearch"; import type { AlgoliaRecord } from "@/content-indexer/types/algolia.js"; import { truncateRecord } from "@/content-indexer/utils/truncate-record.js"; +const ALGOLIA_INDEX_NAME_BASE = "alchemy_docs"; + /** * Builds an Algolia index name with branch and type scoping. * Pattern: {branchId}_{baseName}[_{indexerType}] @@ -52,7 +54,6 @@ export const uploadToAlgolia = async ( ): Promise => { const appId = process.env.ALGOLIA_APP_ID; const adminKey = process.env.ALGOLIA_ADMIN_API_KEY; - const baseName = process.env.ALGOLIA_INDEX_NAME_BASE; if (!appId || !adminKey) { console.warn("⚠️ Algolia credentials not found. Skipping Algolia upload."); @@ -62,15 +63,8 @@ export const uploadToAlgolia = async ( return; } - if (!baseName) { - console.warn( - "⚠️ ALGOLIA_INDEX_NAME_BASE not set. Skipping Algolia upload.", - ); - return; - } - const targetIndexName = buildIndexName( - baseName, + ALGOLIA_INDEX_NAME_BASE, options.indexerType, options.branchId, ); From 06d45446c1e2bd0176fc55e9e16b4170b6608a1b Mon Sep 17 00:00:00 2001 From: dslovinsky Date: Fri, 9 Jan 2026 13:34:49 -0800 Subject: [PATCH 17/27] chore: remove unused redis env vars --- .env.example | 2 -- src/content-indexer/README.md | 2 -- 2 files changed, 4 deletions(-) diff --git a/.env.example b/.env.example index fc24425ae..564283a56 100644 --- a/.env.example +++ b/.env.example @@ -15,8 +15,6 @@ GH_TOKEN=your_github_token_here # Get these from your Upstash dashboard: https://console.upstash.com/ KV_REST_API_URL=your_kv_url_here KV_REST_API_TOKEN=your_kv_token_here -KV_REST_API_READ_ONLY_TOKEN=your_readonly_token_here -KV_URL=your_kv_url_here # ============================================================================ # Algolia (Required for all indexers) diff --git a/src/content-indexer/README.md b/src/content-indexer/README.md index 65a25bd7a..562efcb40 100644 --- a/src/content-indexer/README.md +++ b/src/content-indexer/README.md @@ -431,10 +431,8 @@ Create a `.env` file (see `.env.example`): ```bash # Redis (required for path index and navigation trees) -KV_REST_API_READ_ONLY_TOKEN=your_token KV_REST_API_TOKEN=your_token KV_REST_API_URL=your_url -KV_URL=your_url # Algolia (required for search index) ALGOLIA_APP_ID=your_app_id From 7ef0eafe16752b2550aaec72410354956bf15c4f Mon Sep 17 00:00:00 2001 From: dslovinsky Date: Fri, 9 Jan 2026 13:47:47 -0800 Subject: [PATCH 18/27] feat: create github actions to run indexers automatically --- .github/workflows/index-changelog.yml | 40 ++++++++++++++++++++++ .github/workflows/index-main-content.yml | 30 ++++++++++++++++ .github/workflows/index-sdk-references.yml | 28 +++++++++++++++ 3 files changed, 98 insertions(+) create mode 100644 .github/workflows/index-changelog.yml create mode 100644 .github/workflows/index-main-content.yml create mode 100644 .github/workflows/index-sdk-references.yml diff --git a/.github/workflows/index-changelog.yml b/.github/workflows/index-changelog.yml new file mode 100644 index 000000000..4c91baf01 --- /dev/null +++ b/.github/workflows/index-changelog.yml @@ -0,0 +1,40 @@ +name: Index Changelog + +on: + push: + branches: + - main + paths: + - "fern/changelog/**" + +jobs: + index-changelog: + name: Index Changelog + runs-on: ubuntu-latest + # Only run when files are added or removed, not modified + if: | + github.event.head_commit && + ( + github.event.head_commit.added[0] != null || + github.event.head_commit.removed[0] != null + ) && + ( + contains(join(github.event.head_commit.added, ','), 'fern/changelog/') || + contains(join(github.event.head_commit.removed, ','), 'fern/changelog/') + ) + permissions: + contents: read + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Setup pnpm + uses: ./.github/actions/setup-pnpm + + - name: Run changelog indexer + run: pnpm index:changelog + env: + KV_REST_API_TOKEN: ${{ secrets.KV_REST_API_TOKEN }} + KV_REST_API_URL: ${{ secrets.KV_REST_API_URL }} + ALGOLIA_APP_ID: ${{ secrets.ALGOLIA_APP_ID }} + ALGOLIA_ADMIN_API_KEY: ${{ secrets.ALGOLIA_ADMIN_API_KEY }} diff --git a/.github/workflows/index-main-content.yml b/.github/workflows/index-main-content.yml new file mode 100644 index 000000000..3da3fb772 --- /dev/null +++ b/.github/workflows/index-main-content.yml @@ -0,0 +1,30 @@ +name: Index Main Content + +on: + push: + branches: + - main + paths: + - "fern/docs.yml" + +jobs: + index-main: + name: Index Main Content + runs-on: ubuntu-latest + permissions: + contents: read + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Setup pnpm + uses: ./.github/actions/setup-pnpm + + - name: Run main content indexer + run: pnpm index:main + env: + KV_REST_API_TOKEN: ${{ secrets.KV_REST_API_TOKEN }} + KV_REST_API_URL: ${{ secrets.KV_REST_API_URL }} + ALGOLIA_APP_ID: ${{ secrets.ALGOLIA_APP_ID }} + ALGOLIA_ADMIN_API_KEY: ${{ secrets.ALGOLIA_ADMIN_API_KEY }} + GH_TOKEN: ${{ secrets.GH_TOKEN }} diff --git a/.github/workflows/index-sdk-references.yml b/.github/workflows/index-sdk-references.yml new file mode 100644 index 000000000..e08e58b60 --- /dev/null +++ b/.github/workflows/index-sdk-references.yml @@ -0,0 +1,28 @@ +name: Index SDK References + +on: + repository_dispatch: + types: + - index-sdk-references + +jobs: + index-sdk: + name: Index SDK References + runs-on: ubuntu-latest + permissions: + contents: read + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Setup pnpm + uses: ./.github/actions/setup-pnpm + + - name: Run SDK indexer + run: pnpm index:sdk + env: + KV_REST_API_TOKEN: ${{ secrets.KV_REST_API_TOKEN }} + KV_REST_API_URL: ${{ secrets.KV_REST_API_URL }} + ALGOLIA_APP_ID: ${{ secrets.ALGOLIA_APP_ID }} + ALGOLIA_ADMIN_API_KEY: ${{ secrets.ALGOLIA_ADMIN_API_KEY }} + GH_TOKEN: ${{ secrets.GH_TOKEN }} From 101ac90c89b056782597a9725fa6dbbd642e6cc2 Mon Sep 17 00:00:00 2001 From: dslovinsky Date: Fri, 9 Jan 2026 14:07:58 -0800 Subject: [PATCH 19/27] chore: remove coverage report from git --- coverage/base.css | 224 - coverage/block-navigation.js | 84 - coverage/collectors/algolia.ts.html | 331 - coverage/collectors/index.html | 161 - coverage/collectors/navigation-trees.ts.html | 259 - coverage/collectors/path-index.ts.html | 232 - .../collectors/processing-context.ts.html | 316 - coverage/constants/http.ts.html | 121 - coverage/constants/index.html | 116 - coverage/core/batch-fetcher.ts.html | 397 - coverage/core/build-all-outputs.ts.html | 340 - coverage/core/content-cache.ts.html | 343 - coverage/core/index.html | 176 - coverage/core/path-builder.ts.html | 259 - coverage/core/scanner.ts.html | 340 - coverage/coverage-final.json | 8298 ----------------- coverage/favicon.png | Bin 445 -> 0 bytes coverage/index.html | 236 - coverage/indexers/index.html | 116 - coverage/indexers/main.ts.html | 310 - coverage/prettify.css | 1 - coverage/prettify.js | 1008 -- coverage/sort-arrow-sprite.png | Bin 138 -> 0 bytes coverage/sorter.js | 209 - coverage/types/docsYaml.ts.html | 358 - coverage/types/index.html | 116 - coverage/uploaders/algolia.ts.html | 472 - coverage/uploaders/index.html | 131 - coverage/uploaders/redis.ts.html | 364 - coverage/utils/filesystem.ts.html | 298 - coverage/utils/github.ts.html | 691 -- coverage/utils/index.html | 236 - coverage/utils/nav-tree-merge.ts.html | 364 - coverage/utils/navigation-helpers.ts.html | 127 - coverage/utils/normalization.ts.html | 151 - coverage/utils/openapi.ts.html | 532 -- coverage/utils/openrpc.ts.html | 124 - coverage/utils/test-factories.ts.html | 232 - coverage/utils/truncate-record.ts.html | 316 - coverage/visitors/index.html | 176 - coverage/visitors/index.ts.html | 313 - coverage/visitors/processors/index.html | 131 - .../processors/process-openapi.ts.html | 730 -- .../processors/process-openrpc.ts.html | 421 - coverage/visitors/visit-api-reference.ts.html | 307 - coverage/visitors/visit-link.ts.html | 160 - coverage/visitors/visit-page.ts.html | 322 - coverage/visitors/visit-section.ts.html | 520 -- 48 files changed, 21469 deletions(-) delete mode 100644 coverage/base.css delete mode 100644 coverage/block-navigation.js delete mode 100644 coverage/collectors/algolia.ts.html delete mode 100644 coverage/collectors/index.html delete mode 100644 coverage/collectors/navigation-trees.ts.html delete mode 100644 coverage/collectors/path-index.ts.html delete mode 100644 coverage/collectors/processing-context.ts.html delete mode 100644 coverage/constants/http.ts.html delete mode 100644 coverage/constants/index.html delete mode 100644 coverage/core/batch-fetcher.ts.html delete mode 100644 coverage/core/build-all-outputs.ts.html delete mode 100644 coverage/core/content-cache.ts.html delete mode 100644 coverage/core/index.html delete mode 100644 coverage/core/path-builder.ts.html delete mode 100644 coverage/core/scanner.ts.html delete mode 100644 coverage/coverage-final.json delete mode 100644 coverage/favicon.png delete mode 100644 coverage/index.html delete mode 100644 coverage/indexers/index.html delete mode 100644 coverage/indexers/main.ts.html delete mode 100644 coverage/prettify.css delete mode 100644 coverage/prettify.js delete mode 100644 coverage/sort-arrow-sprite.png delete mode 100644 coverage/sorter.js delete mode 100644 coverage/types/docsYaml.ts.html delete mode 100644 coverage/types/index.html delete mode 100644 coverage/uploaders/algolia.ts.html delete mode 100644 coverage/uploaders/index.html delete mode 100644 coverage/uploaders/redis.ts.html delete mode 100644 coverage/utils/filesystem.ts.html delete mode 100644 coverage/utils/github.ts.html delete mode 100644 coverage/utils/index.html delete mode 100644 coverage/utils/nav-tree-merge.ts.html delete mode 100644 coverage/utils/navigation-helpers.ts.html delete mode 100644 coverage/utils/normalization.ts.html delete mode 100644 coverage/utils/openapi.ts.html delete mode 100644 coverage/utils/openrpc.ts.html delete mode 100644 coverage/utils/test-factories.ts.html delete mode 100644 coverage/utils/truncate-record.ts.html delete mode 100644 coverage/visitors/index.html delete mode 100644 coverage/visitors/index.ts.html delete mode 100644 coverage/visitors/processors/index.html delete mode 100644 coverage/visitors/processors/process-openapi.ts.html delete mode 100644 coverage/visitors/processors/process-openrpc.ts.html delete mode 100644 coverage/visitors/visit-api-reference.ts.html delete mode 100644 coverage/visitors/visit-link.ts.html delete mode 100644 coverage/visitors/visit-page.ts.html delete mode 100644 coverage/visitors/visit-section.ts.html diff --git a/coverage/base.css b/coverage/base.css deleted file mode 100644 index f418035b4..000000000 --- a/coverage/base.css +++ /dev/null @@ -1,224 +0,0 @@ -body, html { - margin:0; padding: 0; - height: 100%; -} -body { - font-family: Helvetica Neue, Helvetica, Arial; - font-size: 14px; - color:#333; -} -.small { font-size: 12px; } -*, *:after, *:before { - -webkit-box-sizing:border-box; - -moz-box-sizing:border-box; - box-sizing:border-box; - } -h1 { font-size: 20px; margin: 0;} -h2 { font-size: 14px; } -pre { - font: 12px/1.4 Consolas, "Liberation Mono", Menlo, Courier, monospace; - margin: 0; - padding: 0; - -moz-tab-size: 2; - -o-tab-size: 2; - tab-size: 2; -} -a { color:#0074D9; text-decoration:none; } -a:hover { text-decoration:underline; } -.strong { font-weight: bold; } -.space-top1 { padding: 10px 0 0 0; } -.pad2y { padding: 20px 0; } -.pad1y { padding: 10px 0; } -.pad2x { padding: 0 20px; } -.pad2 { padding: 20px; } -.pad1 { padding: 10px; } -.space-left2 { padding-left:55px; } -.space-right2 { padding-right:20px; } -.center { text-align:center; } -.clearfix { display:block; } -.clearfix:after { - content:''; - display:block; - height:0; - clear:both; - visibility:hidden; - } -.fl { float: left; } -@media only screen and (max-width:640px) { - .col3 { width:100%; max-width:100%; } - .hide-mobile { display:none!important; } -} - -.quiet { - color: #7f7f7f; - color: rgba(0,0,0,0.5); -} -.quiet a { opacity: 0.7; } - -.fraction { - font-family: Consolas, 'Liberation Mono', Menlo, Courier, monospace; - font-size: 10px; - color: #555; - background: #E8E8E8; - padding: 4px 5px; - border-radius: 3px; - vertical-align: middle; -} - -div.path a:link, div.path a:visited { color: #333; } -table.coverage { - border-collapse: collapse; - margin: 10px 0 0 0; - padding: 0; -} - -table.coverage td { - margin: 0; - padding: 0; - vertical-align: top; -} -table.coverage td.line-count { - text-align: right; - padding: 0 5px 0 20px; -} -table.coverage td.line-coverage { - text-align: right; - padding-right: 10px; - min-width:20px; -} - -table.coverage td span.cline-any { - display: inline-block; - padding: 0 5px; - width: 100%; -} -.missing-if-branch { - display: inline-block; - margin-right: 5px; - border-radius: 3px; - position: relative; - padding: 0 4px; - background: #333; - color: yellow; -} - -.skip-if-branch { - display: none; - margin-right: 10px; - position: relative; - padding: 0 4px; - background: #ccc; - color: white; -} -.missing-if-branch .typ, .skip-if-branch .typ { - color: inherit !important; -} -.coverage-summary { - border-collapse: collapse; - width: 100%; -} -.coverage-summary tr { border-bottom: 1px solid #bbb; } -.keyline-all { border: 1px solid #ddd; } -.coverage-summary td, .coverage-summary th { padding: 10px; } -.coverage-summary tbody { border: 1px solid #bbb; } -.coverage-summary td { border-right: 1px solid #bbb; } -.coverage-summary td:last-child { border-right: none; } -.coverage-summary th { - text-align: left; - font-weight: normal; - white-space: nowrap; -} -.coverage-summary th.file { border-right: none !important; } -.coverage-summary th.pct { } -.coverage-summary th.pic, -.coverage-summary th.abs, -.coverage-summary td.pct, -.coverage-summary td.abs { text-align: right; } -.coverage-summary td.file { white-space: nowrap; } -.coverage-summary td.pic { min-width: 120px !important; } -.coverage-summary tfoot td { } - -.coverage-summary .sorter { - height: 10px; - width: 7px; - display: inline-block; - margin-left: 0.5em; - background: url(sort-arrow-sprite.png) no-repeat scroll 0 0 transparent; -} -.coverage-summary .sorted .sorter { - background-position: 0 -20px; -} -.coverage-summary .sorted-desc .sorter { - background-position: 0 -10px; -} -.status-line { height: 10px; } -/* yellow */ -.cbranch-no { background: yellow !important; color: #111; } -/* dark red */ -.red.solid, .status-line.low, .low .cover-fill { background:#C21F39 } -.low .chart { border:1px solid #C21F39 } -.highlighted, -.highlighted .cstat-no, .highlighted .fstat-no, .highlighted .cbranch-no{ - background: #C21F39 !important; -} -/* medium red */ -.cstat-no, .fstat-no, .cbranch-no, .cbranch-no { background:#F6C6CE } -/* light red */ -.low, .cline-no { background:#FCE1E5 } -/* light green */ -.high, .cline-yes { background:rgb(230,245,208) } -/* medium green */ -.cstat-yes { background:rgb(161,215,106) } -/* dark green */ -.status-line.high, .high .cover-fill { background:rgb(77,146,33) } -.high .chart { border:1px solid rgb(77,146,33) } -/* dark yellow (gold) */ -.status-line.medium, .medium .cover-fill { background: #f9cd0b; } -.medium .chart { border:1px solid #f9cd0b; } -/* light yellow */ -.medium { background: #fff4c2; } - -.cstat-skip { background: #ddd; color: #111; } -.fstat-skip { background: #ddd; color: #111 !important; } -.cbranch-skip { background: #ddd !important; color: #111; } - -span.cline-neutral { background: #eaeaea; } - -.coverage-summary td.empty { - opacity: .5; - padding-top: 4px; - padding-bottom: 4px; - line-height: 1; - color: #888; -} - -.cover-fill, .cover-empty { - display:inline-block; - height: 12px; -} -.chart { - line-height: 0; -} -.cover-empty { - background: white; -} -.cover-full { - border-right: none !important; -} -pre.prettyprint { - border: none !important; - padding: 0 !important; - margin: 0 !important; -} -.com { color: #999 !important; } -.ignore-none { color: #999; font-weight: normal; } - -.wrapper { - min-height: 100%; - height: auto !important; - height: 100%; - margin: 0 auto -48px; -} -.footer, .push { - height: 48px; -} diff --git a/coverage/block-navigation.js b/coverage/block-navigation.js deleted file mode 100644 index 873182862..000000000 --- a/coverage/block-navigation.js +++ /dev/null @@ -1,84 +0,0 @@ -var jumpToCode = (function init() { - // Classes of code we would like to highlight in the file view - var missingCoverageClasses = [".cbranch-no", ".cstat-no", ".fstat-no"]; - - // Elements to highlight in the file listing view - var fileListingElements = ["td.pct.low"]; - - // We don't want to select elements that are direct descendants of another match - var notSelector = ":not(" + missingCoverageClasses.join("):not(") + ") > "; // becomes `:not(a):not(b) > ` - - // Selector that finds elements on the page to which we can jump - var selector = - fileListingElements.join(", ") + - ", " + - notSelector + - missingCoverageClasses.join(", " + notSelector); // becomes `:not(a):not(b) > a, :not(a):not(b) > b` - - // The NodeList of matching elements - var missingCoverageElements = document.querySelectorAll(selector); - - var currentIndex; - - function toggleClass(index) { - missingCoverageElements.item(currentIndex).classList.remove("highlighted"); - missingCoverageElements.item(index).classList.add("highlighted"); - } - - function makeCurrent(index) { - toggleClass(index); - currentIndex = index; - missingCoverageElements.item(index).scrollIntoView({ - behavior: "smooth", - block: "center", - inline: "center", - }); - } - - function goToPrevious() { - var nextIndex = 0; - if (typeof currentIndex !== "number" || currentIndex === 0) { - nextIndex = missingCoverageElements.length - 1; - } else if (missingCoverageElements.length > 1) { - nextIndex = currentIndex - 1; - } - - makeCurrent(nextIndex); - } - - function goToNext() { - var nextIndex = 0; - - if ( - typeof currentIndex === "number" && - currentIndex < missingCoverageElements.length - 1 - ) { - nextIndex = currentIndex + 1; - } - - makeCurrent(nextIndex); - } - - return function jump(event) { - if ( - document.getElementById("fileSearch") === document.activeElement && - document.activeElement != null - ) { - // if we're currently focused on the search input, we don't want to navigate - return; - } - - switch (event.which) { - case 78: // n - case 74: // j - goToNext(); - break; - case 66: // b - case 75: // k - case 80: // p - goToPrevious(); - break; - } - }; -})(); -window.addEventListener("keydown", jumpToCode); diff --git a/coverage/collectors/algolia.ts.html b/coverage/collectors/algolia.ts.html deleted file mode 100644 index 7a813f484..000000000 --- a/coverage/collectors/algolia.ts.html +++ /dev/null @@ -1,331 +0,0 @@ - - - - - - Code coverage report for collectors/algolia.ts - - - - - - - - - -
-
-

All files / collectors algolia.ts

-
- -
- 100% - Statements - 12/12 -
- - -
- 100% - Branches - 4/4 -
- - -
- 100% - Functions - 7/7 -
- - -
- 100% - Lines - 12/12 -
- - -
-

- Press n or j to go to the next uncovered block, b, p or k for the previous block. -

- -
-
-

-
1 -2 -3 -4 -5 -6 -7 -8 -9 -10 -11 -12 -13 -14 -15 -16 -17 -18 -19 -20 -21 -22 -23 -24 -25 -26 -27 -28 -29 -30 -31 -32 -33 -34 -35 -36 -37 -38 -39 -40 -41 -42 -43 -44 -45 -46 -47 -48 -49 -50 -51 -52 -53 -54 -55 -56 -57 -58 -59 -60 -61 -62 -63 -64 -65 -66 -67 -68 -69 -70 -71 -72 -73 -74 -75 -76 -77 -78 -79 -80 -81 -82 -83  -  -  -  -  -  -  -  -  -10x -40x -38x -37x -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -73x -  -  -  -  -  -  -  -  -  -  -40x -  -  -40x -40x -40x -  -40x -  -  -  -  -  -  -  -  -  -  -  -  -  -  -36x -  -  -  -  -  -  -  -40x -  -  - 
import { createHash } from "crypto";
- 
-import type { AlgoliaRecord } from "@/content-indexer/types/algolia.js";
-import type { NavItem } from "@/content-indexer/types/navigation.js";
- 
-/**
- * Extracts breadcrumb titles from NavItems for Algolia.
- * Returns only the titles in hierarchical order.
- */
-const extractBreadcrumbTitles = (navItems: NavItem[]): string[] => {
-  return navItems
-    .filter((item) => item.type !== "link") // Skip links
-    .map((item) => item.title);
-};
- 
-type AddRecordBaseParams = {
-  path: string;
-  title: string;
-  content: string;
-  breadcrumbs: NavItem[];
-};
- 
-type AddGuideRecordParams = AddRecordBaseParams & {
-  pageType: "Guide";
-  httpMethod?: never; // Not allowed for Guide
-};
- 
-type AddApiMethodRecordParams = AddRecordBaseParams & {
-  pageType: "API Method";
-  httpMethod: string; // Required for API Method
-};
-export type AddRecordParams = AddGuideRecordParams | AddApiMethodRecordParams;
- 
-/**
- * Collector for Algolia search records during content processing.
- * Records are built with all required data including breadcrumbs.
- */
-export class AlgoliaCollector {
-  private records: AlgoliaRecord[] = [];
- 
-  /**
-   * Add a search record for either MDX pages or API methods.
-   *
-   * ObjectID strategy:
-   * Uses hash of last breadcrumb + title for relatively stable, content-based identification.
-   * If we change the title or the last breadcrumb, the objectID will change,
-   * but this shouldn't matter as long as we continue to replace the entire index on each run.
-   */
-  addRecord(params: AddRecordParams): void {
-    const breadcrumbTitles = extractBreadcrumbTitles(params.breadcrumbs);
- 
-    // Generate stable objectID from last breadcrumb (most specific section) + title
-    const lastBreadcrumb = breadcrumbTitles.at(-1) || "unknown";
-    const stableId = `${lastBreadcrumb}:${params.title}`;
-    const objectID = this.generateHash(stableId);
- 
-    this.records.push({
-      objectID,
-      path: params.path,
-      pageType: params.pageType,
-      title: params.title,
-      content: params.content,
-      breadcrumbs: breadcrumbTitles,
-      ...(params.httpMethod && { httpMethod: params.httpMethod }),
-    });
-  }
- 
-  /**
-   * Get all built records.
-   */
-  getRecords(): AlgoliaRecord[] {
-    return this.records;
-  }
- 
-  /**
-   * Generate a stable hash-based objectID from a source string.
-   * Returns first 16 characters of SHA-256 hash for a clean ID format.
-   */
-  private generateHash(source: string): string {
-    return createHash("sha256").update(source).digest("hex").substring(0, 16);
-  }
-}
- 
- -
-
- - - - - - - - \ No newline at end of file diff --git a/coverage/collectors/index.html b/coverage/collectors/index.html deleted file mode 100644 index c5b5f3b08..000000000 --- a/coverage/collectors/index.html +++ /dev/null @@ -1,161 +0,0 @@ - - - - - - Code coverage report for collectors - - - - - - - - - -
-
-

All files collectors

-
- -
- 100% - Statements - 40/40 -
- - -
- 100% - Branches - 17/17 -
- - -
- 100% - Functions - 25/25 -
- - -
- 100% - Lines - 40/40 -
- - -
-

- Press n or j to go to the next uncovered block, b, p or k for the previous block. -

- -
-
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
FileStatementsBranchesFunctionsLines
algolia.ts -
-
100%12/12100%4/4100%7/7100%12/12
navigation-trees.ts -
-
100%11/11100%6/6100%7/7100%11/11
path-index.ts -
-
100%9/9100%4/4100%5/5100%9/9
processing-context.ts -
-
100%8/8100%3/3100%6/6100%8/8
-
-
-
- - - - - - - - \ No newline at end of file diff --git a/coverage/collectors/navigation-trees.ts.html b/coverage/collectors/navigation-trees.ts.html deleted file mode 100644 index c7f8f2804..000000000 --- a/coverage/collectors/navigation-trees.ts.html +++ /dev/null @@ -1,259 +0,0 @@ - - - - - - Code coverage report for collectors/navigation-trees.ts - - - - - - - - - -
-
-

All files / collectors navigation-trees.ts

-
- -
- 100% - Statements - 11/11 -
- - -
- 100% - Branches - 6/6 -
- - -
- 100% - Functions - 7/7 -
- - -
- 100% - Lines - 11/11 -
- - -
-

- Press n or j to go to the next uncovered block, b, p or k for the previous block. -

- -
-
-

-
1 -2 -3 -4 -5 -6 -7 -8 -9 -10 -11 -12 -13 -14 -15 -16 -17 -18 -19 -20 -21 -22 -23 -24 -25 -26 -27 -28 -29 -30 -31 -32 -33 -34 -35 -36 -37 -38 -39 -40 -41 -42 -43 -44 -45 -46 -47 -48 -49 -50 -51 -52 -53 -54 -55 -56 -57 -58 -59  -  -  -  -  -  -  -  -  -  -71x -  -  -  -  -  -22x -19x -  -22x -  -  -  -  -  -  -31x -  -  -  -  -  -  -3x -  -  -  -4x -4x -  -  -  -  -  -  -  -  -  -  -5x -  -7x -  -  -7x -  -  -  - 
import type {
-  NavItem,
-  NavigationTreesByTab,
-} from "@/content-indexer/types/navigation.js";
- 
-/**
- * Collector for accumulating navigation trees during content processing.
- * Organizes navigation items by tab.
- */
-export class NavigationTreesCollector {
-  private trees: NavigationTreesByTab = {};
- 
-  /**
-   * Add a navigation item to a specific tab's tree.
-   */
-  addItem(tab: string, item: NavItem): void {
-    if (!this.trees[tab]) {
-      this.trees[tab] = [];
-    }
-    this.trees[tab].push(item);
-  }
- 
-  /**
-   * Get the complete navigation trees.
-   */
-  getTrees(): NavigationTreesByTab {
-    return this.trees;
-  }
- 
-  /**
-   * Get statistics about the navigation trees.
-   */
-  getStats(): { tabCount: number; itemCounts: Record<string, number> } {
-    return {
-      tabCount: Object.keys(this.trees).length,
-      itemCounts: Object.entries(this.trees).reduce(
-        (acc, [tab, items]) => {
-          acc[tab] = this.countItems(items);
-          return acc;
-        },
-        {} as Record<string, number>,
-      ),
-    };
-  }
- 
-  /**
-   * Recursively count navigation items including nested children.
-   */
-  private countItems(items: NavItem[]): number {
-    return items.reduce((sum, item) => {
-      const childCount =
-        item.type === "section" || item.type === "api-section"
-          ? this.countItems(item.children)
-          : 0;
-      return sum + 1 + childCount;
-    }, 0);
-  }
-}
- 
- -
-
- - - - - - - - \ No newline at end of file diff --git a/coverage/collectors/path-index.ts.html b/coverage/collectors/path-index.ts.html deleted file mode 100644 index 54185d8dc..000000000 --- a/coverage/collectors/path-index.ts.html +++ /dev/null @@ -1,232 +0,0 @@ - - - - - - Code coverage report for collectors/path-index.ts - - - - - - - - - -
-
-

All files / collectors path-index.ts

-
- -
- 100% - Statements - 9/9 -
- - -
- 100% - Branches - 4/4 -
- - -
- 100% - Functions - 5/5 -
- - -
- 100% - Lines - 9/9 -
- - -
-

- Press n or j to go to the next uncovered block, b, p or k for the previous block. -

- -
-
-

-
1 -2 -3 -4 -5 -6 -7 -8 -9 -10 -11 -12 -13 -14 -15 -16 -17 -18 -19 -20 -21 -22 -23 -24 -25 -26 -27 -28 -29 -30 -31 -32 -33 -34 -35 -36 -37 -38 -39 -40 -41 -42 -43 -44 -45 -46 -47 -48 -49 -50  -  -  -  -  -  -  -  -  -  -71x -  -  -  -  -  -  -16x -1x -  -  -  -16x -  -  -  -  -  -  -32x -  -  -  -  -  -  -2x -2x -  -  -  -4x -4x -  -  -  -  -  -  - 
import type {
-  PathIndex,
-  PathIndexEntry,
-} from "@/content-indexer/types/pathIndex.js";
- 
-/**
- * Collector for accumulating path index entries during content processing.
- * Provides validation to prevent duplicate paths.
- */
-export class PathIndexCollector {
-  private index: PathIndex = {};
- 
-  /**
-   * Add a path index entry for URL routing.
-   * Warns if the path already exists to catch configuration errors.
-   */
-  add(path: string, entry: PathIndexEntry): void {
-    if (this.index[path]) {
-      console.warn(
-        `⚠️  Duplicate path detected: ${path} (overwriting previous entry)`,
-      );
-    }
-    this.index[path] = entry;
-  }
- 
-  /**
-   * Get the complete path index.
-   */
-  getIndex(): PathIndex {
-    return this.index;
-  }
- 
-  /**
-   * Get statistics about the index.
-   */
-  getStats(): { total: number; byType: Record<string, number> } {
-    const entries = Object.values(this.index);
-    return {
-      total: entries.length,
-      byType: entries.reduce(
-        (acc, entry) => {
-          acc[entry.type] = (acc[entry.type] || 0) + 1;
-          return acc;
-        },
-        {} as Record<string, number>,
-      ),
-    };
-  }
-}
- 
- -
-
- - - - - - - - \ No newline at end of file diff --git a/coverage/collectors/processing-context.ts.html b/coverage/collectors/processing-context.ts.html deleted file mode 100644 index 05f60643c..000000000 --- a/coverage/collectors/processing-context.ts.html +++ /dev/null @@ -1,316 +0,0 @@ - - - - - - Code coverage report for collectors/processing-context.ts - - - - - - - - - -
-
-

All files / collectors processing-context.ts

-
- -
- 100% - Statements - 8/8 -
- - -
- 100% - Branches - 3/3 -
- - -
- 100% - Functions - 6/6 -
- - -
- 100% - Lines - 8/8 -
- - -
-

- Press n or j to go to the next uncovered block, b, p or k for the previous block. -

- -
-
-

-
1 -2 -3 -4 -5 -6 -7 -8 -9 -10 -11 -12 -13 -14 -15 -16 -17 -18 -19 -20 -21 -22 -23 -24 -25 -26 -27 -28 -29 -30 -31 -32 -33 -34 -35 -36 -37 -38 -39 -40 -41 -42 -43 -44 -45 -46 -47 -48 -49 -50 -51 -52 -53 -54 -55 -56 -57 -58 -59 -60 -61 -62 -63 -64 -65 -66 -67 -68 -69 -70 -71 -72 -73 -74 -75 -76 -77 -78  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -64x -64x -64x -  -  -  -  -  -  -6x -  -  -  -  -  -  -12x -  -  -  -  -  -  -31x -  -  -  -  -  -  -26x -  -  -  -  -  -  -  -  -  -  -1x -  -  -  -  -  -  - 
import type { AlgoliaRecord } from "@/content-indexer/types/algolia.js";
-import type {
-  NavItem,
-  NavigationTreesByTab,
-} from "@/content-indexer/types/navigation.js";
-import type {
-  PathIndex,
-  PathIndexEntry,
-} from "@/content-indexer/types/pathIndex.js";
- 
-import { AlgoliaCollector, type AddRecordParams } from "./algolia.js";
-import { NavigationTreesCollector } from "./navigation-trees.js";
-import { PathIndexCollector } from "./path-index.js";
- 
-/**
- * Result of building all outputs from content processing.
- */
-export interface BuildAllOutputsResult {
-  pathIndex: PathIndex;
-  navigationTrees: NavigationTreesByTab;
-  algoliaRecords: AlgoliaRecord[];
-}
- 
-/**
- * Encapsulates the three output collectors for Phase 3 processing.
- * Provides a unified interface for accumulating results while traversing docs.yml.
- */
-export class ProcessingContext {
-  constructor(
-    private pathIndexCollector = new PathIndexCollector(),
-    private navigationTreesCollector = new NavigationTreesCollector(),
-    private algoliaCollector = new AlgoliaCollector(),
-  ) {}
- 
-  /**
-   * Add an entry to the path index for URL routing.
-   */
-  addPathIndexEntry(path: string, entry: PathIndexEntry): void {
-    this.pathIndexCollector.add(path, entry);
-  }
- 
-  /**
-   * Add a navigation item to a specific tab's tree.
-   */
-  addNavigationItem(tab: string, item: NavItem): void {
-    this.navigationTreesCollector.addItem(tab, item);
-  }
- 
-  /**
-   * Add a record to the Algolia index.
-   */
-  addAlgoliaRecord(params: AddRecordParams): void {
-    this.algoliaCollector.addRecord(params);
-  }
- 
-  /**
-   * Get all accumulated results.
-   */
-  getResults(): BuildAllOutputsResult {
-    return {
-      pathIndex: this.pathIndexCollector.getIndex(),
-      navigationTrees: this.navigationTreesCollector.getTrees(),
-      algoliaRecords: this.algoliaCollector.getRecords(),
-    };
-  }
- 
-  /**
-   * Get statistics about accumulated data.
-   */
-  getStats() {
-    return {
-      pathIndex: this.pathIndexCollector.getStats(),
-      navigationTrees: this.navigationTreesCollector.getStats(),
-      algoliaRecords: { count: this.algoliaCollector.getRecords().length },
-    };
-  }
-}
- 
- -
-
- - - - - - - - \ No newline at end of file diff --git a/coverage/constants/http.ts.html b/coverage/constants/http.ts.html deleted file mode 100644 index b34c66206..000000000 --- a/coverage/constants/http.ts.html +++ /dev/null @@ -1,121 +0,0 @@ - - - - - - Code coverage report for constants/http.ts - - - - - - - - - -
-
-

All files / constants http.ts

-
- -
- 100% - Statements - 1/1 -
- - -
- 100% - Branches - 0/0 -
- - -
- 100% - Functions - 0/0 -
- - -
- 100% - Lines - 1/1 -
- - -
-

- Press n or j to go to the next uncovered block, b, p or k for the previous block. -

- -
-
-

-
1 -2 -3 -4 -5 -6 -7 -8 -9 -10 -11 -12 -135x -  -  -  -  -  -  -  -  -  -  -  - 
export const HTTP_METHODS = [
-  "get",
-  "post",
-  "put",
-  "patch",
-  "delete",
-  "options",
-  "head",
-  "trace",
-] as const;
- 
-export type HttpMethod = Uppercase<(typeof HTTP_METHODS)[number]>;
- 
- -
-
- - - - - - - - \ No newline at end of file diff --git a/coverage/constants/index.html b/coverage/constants/index.html deleted file mode 100644 index 1c143b1a5..000000000 --- a/coverage/constants/index.html +++ /dev/null @@ -1,116 +0,0 @@ - - - - - - Code coverage report for constants - - - - - - - - - -
-
-

All files constants

-
- -
- 100% - Statements - 1/1 -
- - -
- 100% - Branches - 0/0 -
- - -
- 100% - Functions - 0/0 -
- - -
- 100% - Lines - 1/1 -
- - -
-

- Press n or j to go to the next uncovered block, b, p or k for the previous block. -

- -
-
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
FileStatementsBranchesFunctionsLines
http.ts -
-
100%1/1100%0/0100%0/0100%1/1
-
-
-
- - - - - - - - \ No newline at end of file diff --git a/coverage/core/batch-fetcher.ts.html b/coverage/core/batch-fetcher.ts.html deleted file mode 100644 index d6502cba5..000000000 --- a/coverage/core/batch-fetcher.ts.html +++ /dev/null @@ -1,397 +0,0 @@ - - - - - - Code coverage report for core/batch-fetcher.ts - - - - - - - - - -
-
-

All files / core batch-fetcher.ts

-
- -
- 85.71% - Statements - 24/28 -
- - -
- 62.5% - Branches - 10/16 -
- - -
- 100% - Functions - 3/3 -
- - -
- 85.71% - Lines - 24/28 -
- - -
-

- Press n or j to go to the next uncovered block, b, p or k for the previous block. -

- -
-
-

-
1 -2 -3 -4 -5 -6 -7 -8 -9 -10 -11 -12 -13 -14 -15 -16 -17 -18 -19 -20 -21 -22 -23 -24 -25 -26 -27 -28 -29 -30 -31 -32 -33 -34 -35 -36 -37 -38 -39 -40 -41 -42 -43 -44 -45 -46 -47 -48 -49 -50 -51 -52 -53 -54 -55 -56 -57 -58 -59 -60 -61 -62 -63 -64 -65 -66 -67 -68 -69 -70 -71 -72 -73 -74 -75 -76 -77 -78 -79 -80 -81 -82 -83 -84 -85 -86 -87 -88 -89 -90 -91 -92 -93 -94 -95 -96 -97 -98 -99 -100 -101 -102 -103 -104 -105  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -1x -  -  -  -6x -  -6x -6x -  -  -  -  -6x -6x -6x -  -  -  -  -  -  -  -  -  -  -  -  -6x -  -  -  -6x -  -6x -5x -4x -4x -  -  -  -  -  -  -1x -  -  -  -  -  -  -  -6x -5x -5x -4x -3x -  -  -1x -  -  -  -  -6x -  -6x -6x -  -  -  -6x -  - 
import matter from "gray-matter";
-import path from "path";
- 
-import { fetchApiSpec } from "@/content-indexer/utils/apiSpecs.js";
-import { readLocalMdxFile } from "@/content-indexer/utils/filesystem.js";
-import {
-  fetchFileFromGitHub,
-  type RepoConfig,
-} from "@/content-indexer/utils/github.js";
- 
-import { ContentCache } from "./content-cache.js";
-import type { ScanResult } from "./scanner.js";
- 
-/**
- * Content source configuration - either filesystem or GitHub API
- */
-export type ContentSource =
-  | { type: "filesystem"; basePath: string }
-  | { type: "github"; repoConfig: RepoConfig };
- 
-/**
- * Fetches all MDX files and API specs in parallel and populates the cache.
- * This is the core optimization: all I/O happens upfront in parallel.
- *
- * Supports two modes:
- * - filesystem: Reads from local filesystem (for preview mode)
- * - github: Fetches from GitHub API (for production/SDK indexer)
- *
- * @param scanResult - Result from scanDocsYml containing all paths and spec names
- * @param source - Content source (filesystem or GitHub)
- * @returns Populated ContentCache ready for processing
- */
-export const batchFetchContent = async (
-  scanResult: ScanResult,
-  source: ContentSource,
-): Promise<ContentCache> => {
-  const cache = new ContentCache();
- 
-  const sourceType = source.type;
-  console.info(
-    `   ${sourceType === "filesystem" ? "Reading" : "Fetching"} ${scanResult.mdxPaths.size} MDX files and ${scanResult.specNames.size} specs...`,
-  );
- 
-  // Fetch/read all MDX files in parallel
-  const mdxPromises = Array.from(scanResult.mdxPaths).map(async (mdxPath) => {
-    try {
-      Iif (source.type === "filesystem") {
-        // Read from local filesystem
-        const fullPath = path.join(source.basePath, mdxPath);
-        const result = await readLocalMdxFile(fullPath);
- 
-        if (result) {
-          cache.setMdxContent(mdxPath, {
-            frontmatter: result.frontmatter,
-            content: result.content,
-          });
-        }
-      } else {
-        // Fetch from GitHub API
-        const actualPath = mdxPath.replace(
-          source.repoConfig.stripPathPrefix || "",
-          "",
-        );
-        const fullPath = `${source.repoConfig.docsPrefix}/${actualPath}`;
- 
-        const content = await fetchFileFromGitHub(fullPath, source.repoConfig);
-        if (content) {
-          const { data, content: body } = matter(content);
-          cache.setMdxContent(mdxPath, {
-            frontmatter: data,
-            content: body,
-          });
-        }
-      }
-    } catch (error) {
-      console.warn(
-        `   ⚠️  Failed to ${source.type === "filesystem" ? "read" : "fetch"} MDX file: ${mdxPath}`,
-        error,
-      );
-    }
-  });
- 
-  // Fetch all API specs in parallel (always from remote)
-  const specPromises = Array.from(scanResult.specNames).map(async (apiName) => {
-    try {
-      const result = await fetchApiSpec(apiName);
-      if (result) {
-        cache.setSpec(apiName, result);
-      }
-    } catch (error) {
-      console.warn(`   ⚠️  Failed to fetch spec: ${apiName}`, error);
-    }
-  });
- 
-  // Wait for all fetches to complete
-  await Promise.all([...mdxPromises, ...specPromises]);
- 
-  const stats = cache.getStats();
-  console.info(
-    `   ✓ ${sourceType === "filesystem" ? "Read" : "Fetched"} ${stats.mdxCount}/${scanResult.mdxPaths.size} MDX files and ${stats.specCount}/${scanResult.specNames.size} specs`,
-  );
- 
-  return cache;
-};
- 
- -
-
- - - - - - - - \ No newline at end of file diff --git a/coverage/core/build-all-outputs.ts.html b/coverage/core/build-all-outputs.ts.html deleted file mode 100644 index 03878f6ff..000000000 --- a/coverage/core/build-all-outputs.ts.html +++ /dev/null @@ -1,340 +0,0 @@ - - - - - - Code coverage report for core/build-all-outputs.ts - - - - - - - - - -
-
-

All files / core build-all-outputs.ts

-
- -
- 100% - Statements - 22/22 -
- - -
- 86.66% - Branches - 13/15 -
- - -
- 100% - Functions - 6/6 -
- - -
- 100% - Lines - 20/20 -
- - -
-

- Press n or j to go to the next uncovered block, b, p or k for the previous block. -

- -
-
-

-
1 -2 -3 -4 -5 -6 -7 -8 -9 -10 -11 -12 -13 -14 -15 -16 -17 -18 -19 -20 -21 -22 -23 -24 -25 -26 -27 -28 -29 -30 -31 -32 -33 -34 -35 -36 -37 -38 -39 -40 -41 -42 -43 -44 -45 -46 -47 -48 -49 -50 -51 -52 -53 -54 -55 -56 -57 -58 -59 -60 -61 -62 -63 -64 -65 -66 -67 -68 -69 -70 -71 -72 -73 -74 -75 -76 -77 -78 -79 -80 -81 -82 -83 -84 -85 -86  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -1x -  -  -  -  -7x -  -  -7x -  -10x -2x -  -  -  -8x -  -  -8x -  -  -8x -10x -2x -2x -  -2x -  -  -  -  -  -  -8x -  -  -  -  -  -  -  -  -  -  -  -  -8x -  -8x -3x -  -  -  -8x -6x -  -  -7x -  -  -  -  -7x -  - 
import { kebabCase } from "lodash-es";
- 
-import {
-  ProcessingContext,
-  type BuildAllOutputsResult,
-} from "@/content-indexer/collectors/processing-context.js";
-import type { ContentCache } from "@/content-indexer/core/content-cache.js";
-import type { DocsYml } from "@/content-indexer/types/docsYaml.js";
-import { DOCS_REPO, type RepoConfig } from "@/content-indexer/utils/github.js";
-import { visitNavigationItem } from "@/content-indexer/visitors/index.js";
- 
-import { PathBuilder } from "./path-builder.js";
- 
-/**
- * Phase 3 of the content indexing pipeline.
- * Builds path index, navigation trees, and Algolia records in a single traversal.
- *
- * Uses visitor pattern to process each navigation item type and accumulates
- * results in ProcessingContext. Maintains navigation hierarchy and breadcrumb
- * context during traversal.
- */
-export const buildAllOutputs = (
-  docsYml: DocsYml,
-  contentCache: ContentCache,
-  repo: RepoConfig = DOCS_REPO,
-): BuildAllOutputsResult => {
-  const context = new ProcessingContext();
- 
-  // Process each tab in docs.yml
-  docsYml.navigation.forEach((navItem) => {
-    // Skip navigation items without a tab or layout
-    if (!navItem.tab || !navItem.layout) {
-      return;
-    }
- 
-    // Tab identifier for the index entries
-    const tab = kebabCase(navItem.tab);
- 
-    // Build base path for this tab
-    let basePathBuilder = PathBuilder.init();
- 
-    // Apply tab slug to path (use slug from tab config if available)
-    const tabConfig = docsYml.tabs?.[navItem.tab];
-    if (tabConfig) {
-      const tabSlugForPath = tabConfig.slug ?? tab;
-      const skipTabSlug = tabConfig["skip-slug"] ?? false;
- 
-      basePathBuilder = basePathBuilder.apply({
-        urlSlug: tabSlugForPath,
-        skipUrlSlug: skipTabSlug,
-      });
-    }
- 
-    // Visit all layout items using visitor pattern
-    const results = navItem.layout.map((layoutItem) =>
-      visitNavigationItem({
-        item: layoutItem,
-        parentPath: basePathBuilder,
-        tab,
-        repo,
-        contentCache,
-        context,
-        navigationAncestors: [], // Empty ancestors at top level
-      }),
-    );
- 
-    // Add results to context
-    results.forEach((result) => {
-      // Add path index entries
-      Object.entries(result.indexEntries).forEach(([path, entry]) => {
-        context.addPathIndexEntry(path, entry);
-      });
- 
-      // Add navigation items
-      if (result.navItem) {
-        const items = Array.isArray(result.navItem)
-          ? result.navItem
-          : [result.navItem];
-        items.forEach((item) => context.addNavigationItem(tab, item));
-      }
-    });
-  });
- 
-  return context.getResults();
-};
- 
- -
-
- - - - - - - - \ No newline at end of file diff --git a/coverage/core/content-cache.ts.html b/coverage/core/content-cache.ts.html deleted file mode 100644 index 0c9a120b5..000000000 --- a/coverage/core/content-cache.ts.html +++ /dev/null @@ -1,343 +0,0 @@ - - - - - - Code coverage report for core/content-cache.ts - - - - - - - - - -
-
-

All files / core content-cache.ts

-
- -
- 100% - Statements - 7/7 -
- - -
- 100% - Branches - 0/0 -
- - -
- 100% - Functions - 6/6 -
- - -
- 100% - Lines - 7/7 -
- - -
-

- Press n or j to go to the next uncovered block, b, p or k for the previous block. -

- -
-
-

-
1 -2 -3 -4 -5 -6 -7 -8 -9 -10 -11 -12 -13 -14 -15 -16 -17 -18 -19 -20 -21 -22 -23 -24 -25 -26 -27 -28 -29 -30 -31 -32 -33 -34 -35 -36 -37 -38 -39 -40 -41 -42 -43 -44 -45 -46 -47 -48 -49 -50 -51 -52 -53 -54 -55 -56 -57 -58 -59 -60 -61 -62 -63 -64 -65 -66 -67 -68 -69 -70 -71 -72 -73 -74 -75 -76 -77 -78 -79 -80 -81 -82 -83 -84 -85 -86 -87  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -75x -75x -  -  -  -  -  -  -18x -  -  -  -  -  -  -29x -  -  -  -  -  -  -17x -  -  -  -  -  -  -14x -  -  -  -  -  -  -12x -  -  -  -  -  - 
import type {
-  OpenApiSpec,
-  OpenRpcSpec,
-} from "@/content-indexer/types/specs.js";
- 
-/**
- * Cached MDX content with parsed frontmatter and body.
- */
-export interface MdxCacheEntry {
-  frontmatter: {
-    slug?: string;
-    title?: string;
-    sidebarTitle?: string;
-    [key: string]: unknown;
-  };
-  content: string; // Raw MDX body (without frontmatter)
-}
- 
-/**
- * Cached API spec with type information.
- */
-export interface SpecCacheEntry {
-  specType: "openrpc" | "openapi";
-  spec: OpenRpcSpec | OpenApiSpec;
-  specUrl: string;
-}
- 
-/**
- * Cache statistics returned by getStats().
- */
-export interface CacheStats {
-  mdxCount: number;
-  specCount: number;
-}
- 
-/**
- * In-memory cache for all fetched content.
- * Provides O(1) lookup for MDX files and API specs.
- */
-export class ContentCache {
-  private mdxCache: Map<string, MdxCacheEntry>;
-  private specCache: Map<string, SpecCacheEntry>;
- 
-  constructor() {
-    this.mdxCache = new Map();
-    this.specCache = new Map();
-  }
- 
-  /**
-   * Store MDX content by normalized file path.
-   */
-  setMdxContent(filePath: string, entry: MdxCacheEntry): void {
-    this.mdxCache.set(filePath, entry);
-  }
- 
-  /**
-   * Retrieve MDX content by file path.
-   */
-  getMdxContent(filePath: string): MdxCacheEntry | undefined {
-    return this.mdxCache.get(filePath);
-  }
- 
-  /**
-   * Store API spec by api-name.
-   */
-  setSpec(apiName: string, entry: SpecCacheEntry): void {
-    this.specCache.set(apiName, entry);
-  }
- 
-  /**
-   * Retrieve API spec by api-name.
-   */
-  getSpec(apiName: string): SpecCacheEntry | undefined {
-    return this.specCache.get(apiName);
-  }
- 
-  /**
-   * Get cache statistics for debugging.
-   */
-  getStats(): CacheStats {
-    return {
-      mdxCount: this.mdxCache.size,
-      specCount: this.specCache.size,
-    };
-  }
-}
- 
- -
-
- - - - - - - - \ No newline at end of file diff --git a/coverage/core/index.html b/coverage/core/index.html deleted file mode 100644 index 8751e0fb6..000000000 --- a/coverage/core/index.html +++ /dev/null @@ -1,176 +0,0 @@ - - - - - - Code coverage report for core - - - - - - - - - -
-
-

All files core

-
- -
- 94.62% - Statements - 88/93 -
- - -
- 80.35% - Branches - 45/56 -
- - -
- 100% - Functions - 24/24 -
- - -
- 94.5% - Lines - 86/91 -
- - -
-

- Press n or j to go to the next uncovered block, b, p or k for the previous block. -

- -
-
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
FileStatementsBranchesFunctionsLines
batch-fetcher.ts -
-
85.71%24/2862.5%10/16100%3/385.71%24/28
build-all-outputs.ts -
-
100%22/2286.66%13/15100%6/6100%20/20
content-cache.ts -
-
100%7/7100%0/0100%6/6100%7/7
path-builder.ts -
-
90.9%10/1188.88%8/9100%4/490.9%10/11
scanner.ts -
-
100%25/2587.5%14/16100%5/5100%25/25
-
-
-
- - - - - - - - \ No newline at end of file diff --git a/coverage/core/path-builder.ts.html b/coverage/core/path-builder.ts.html deleted file mode 100644 index 0a71caa78..000000000 --- a/coverage/core/path-builder.ts.html +++ /dev/null @@ -1,259 +0,0 @@ - - - - - - Code coverage report for core/path-builder.ts - - - - - - - - - -
-
-

All files / core path-builder.ts

-
- -
- 90.9% - Statements - 10/11 -
- - -
- 88.88% - Branches - 8/9 -
- - -
- 100% - Functions - 4/4 -
- - -
- 90.9% - Lines - 10/11 -
- - -
-

- Press n or j to go to the next uncovered block, b, p or k for the previous block. -

- -
-
-

-
1 -2 -3 -4 -5 -6 -7 -8 -9 -10 -11 -12 -13 -14 -15 -16 -17 -18 -19 -20 -21 -22 -23 -24 -25 -26 -27 -28 -29 -30 -31 -32 -33 -34 -35 -36 -37 -38 -39 -40 -41 -42 -43 -44 -45 -46 -47 -48 -49 -50 -51 -52 -53 -54 -55 -56 -57 -58 -59  -  -  -  -  -  -  -  -  -  -205x -  -  -  -  -  -  -90x -90x -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -115x -6x -  -  -  -109x -3x -  -  -  -106x -106x -  -  -  -  -  -  -  -  -  -79x -  -  - 
/**
- * PathBuilder mimics Fern's slug generation logic to build full URL paths.
- * Maintains an array of path segments and provides methods to build paths hierarchically.
- * @note Fern incorrectly refers to full paths as "slugs" in their terminology
- * @see https://buildwithfern.com/learn/docs/seo/configuring-slugs
- */
-export class PathBuilder {
-  private segments: string[];
- 
-  private constructor(segments: string[]) {
-    this.segments = segments;
-  }
- 
-  /**
-   * Creates a new PathBuilder instance with optional base path segments.
-   */
-  static init(basePath: string = ""): PathBuilder {
-    const segments = basePath ? basePath.split("/").filter(Boolean) : [];
-    return new PathBuilder(segments);
-  }
- 
-  /**
-   * Applies slug generation rules to create a new PathBuilder.
-   * Supports three modes:
-   * - fullSlug: Completely replaces the path (used for frontmatter overrides)
-   * - skipUrlSlug: Returns unchanged path (used for skip-slug sections)
-   * - urlSlug: Appends to existing path (default behavior)
-   */
-  apply(options: {
-    fullSlug?: string[];
-    urlSlug?: string;
-    skipUrlSlug?: boolean;
-  }): PathBuilder {
-    // If fullSlug is provided (from frontmatter), it completely overrides the path
-    if (options.fullSlug) {
-      return new PathBuilder(options.fullSlug.filter(Boolean));
-    }
- 
-    // If skipUrlSlug is true, don't add anything to the path
-    if (options.skipUrlSlug) {
-      return new PathBuilder([...this.segments]);
-    }
- 
-    // Otherwise, add the urlSlug to the path
-    Eif (options.urlSlug) {
-      return new PathBuilder([...this.segments, options.urlSlug]);
-    }
- 
-    return new PathBuilder([...this.segments]);
-  }
- 
-  /**
-   * Returns the full path as a string by joining all segments with "/".
-   */
-  get(): string {
-    return this.segments.filter(Boolean).join("/");
-  }
-}
- 
- -
-
- - - - - - - - \ No newline at end of file diff --git a/coverage/core/scanner.ts.html b/coverage/core/scanner.ts.html deleted file mode 100644 index 375f50b91..000000000 --- a/coverage/core/scanner.ts.html +++ /dev/null @@ -1,340 +0,0 @@ - - - - - - Code coverage report for core/scanner.ts - - - - - - - - - -
-
-

All files / core scanner.ts

-
- -
- 100% - Statements - 25/25 -
- - -
- 87.5% - Branches - 14/16 -
- - -
- 100% - Functions - 5/5 -
- - -
- 100% - Lines - 25/25 -
- - -
-

- Press n or j to go to the next uncovered block, b, p or k for the previous block. -

- -
-
-

-
1 -2 -3 -4 -5 -6 -7 -8 -9 -10 -11 -12 -13 -14 -15 -16 -17 -18 -19 -20 -21 -22 -23 -24 -25 -26 -27 -28 -29 -30 -31 -32 -33 -34 -35 -36 -37 -38 -39 -40 -41 -42 -43 -44 -45 -46 -47 -48 -49 -50 -51 -52 -53 -54 -55 -56 -57 -58 -59 -60 -61 -62 -63 -64 -65 -66 -67 -68 -69 -70 -71 -72 -73 -74 -75 -76 -77 -78 -79 -80 -81 -82 -83 -84 -85 -86  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -1x -  -23x -1x -  -  -  -22x -2x -  -  -  -20x -3x -3x -  -  -  -17x -12x -12x -  -  -  -5x -5x -1x -  -  -  -5x -6x -  -  -  -  -  -  -  -  -  -  -  -1x -10x -1x -  -  -9x -  -  -  -  -  -9x -10x -10x -17x -  -  -  -  -9x -  - 
import {
-  isApiConfig,
-  isChangelogConfig,
-  isLinkConfig,
-  isPageConfig,
-  isSectionConfig,
-  type DocsYml,
-  type NavigationItem,
-} from "@/content-indexer/types/docsYaml.js";
- 
-/**
- * Result of scanning docs.yml for all file paths and API specs.
- */
-export interface ScanResult {
-  mdxPaths: Set<string>; // All unique MDX file paths
-  specNames: Set<string>; // All unique api-name values
-}
- 
-/**
- * Recursively scans a navigation item to collect MDX paths and spec names.
- */
-const scanNavigationItem = (item: NavigationItem, result: ScanResult): void => {
-  // Skip changelog items
-  if (isChangelogConfig(item)) {
-    return;
-  }
- 
-  // Skip external links
-  if (isLinkConfig(item)) {
-    return;
-  }
- 
-  // Collect API spec names
-  if (isApiConfig(item)) {
-    result.specNames.add(item["api-name"]);
-    return;
-  }
- 
-  // Collect page paths
-  if (isPageConfig(item)) {
-    result.mdxPaths.add(item.path);
-    return;
-  }
- 
-  // Collect section overview paths and recurse into contents
-  Eif (isSectionConfig(item)) {
-    if (item.path) {
-      result.mdxPaths.add(item.path);
-    }
- 
-    // Recursively scan all child items
-    item.contents.forEach((childItem) => {
-      scanNavigationItem(childItem, result);
-    });
-  }
-};
- 
-/**
- * Scans the entire docs.yml to collect all MDX file paths and API spec names.
- * This enables batch fetching all content in parallel.
- *
- * @param docsYml - The parsed docs.yml configuration
- * @returns Object with Sets of unique MDX paths and spec names
- */
-export const scanDocsYml = (docsYml: DocsYml): ScanResult => {
-  if (!docsYml.navigation) {
-    throw new Error("Can't find navigation section in docs.yml");
-  }
- 
-  const result: ScanResult = {
-    mdxPaths: new Set(),
-    specNames: new Set(),
-  };
- 
-  // Scan all navigation items across all tabs
-  docsYml.navigation.forEach((navItem) => {
-    Eif (navItem.layout) {
-      navItem.layout.forEach((layoutItem) => {
-        scanNavigationItem(layoutItem, result);
-      });
-    }
-  });
- 
-  return result;
-};
- 
- -
-
- - - - - - - - \ No newline at end of file diff --git a/coverage/coverage-final.json b/coverage/coverage-final.json deleted file mode 100644 index 83f2dbc41..000000000 --- a/coverage/coverage-final.json +++ /dev/null @@ -1,8298 +0,0 @@ -{ - "/Users/danielslovinsky/Documents/projects/alchemy-docs/docs/src/content-indexer/collectors/algolia.ts": { - "path": "/Users/danielslovinsky/Documents/projects/alchemy-docs/docs/src/content-indexer/collectors/algolia.ts", - "statementMap": { - "0": { - "start": { "line": 10, "column": 32 }, - "end": { "line": 14, "column": null } - }, - "1": { - "start": { "line": 11, "column": 2 }, - "end": { "line": 13, "column": null } - }, - "2": { - "start": { "line": 12, "column": 22 }, - "end": { "line": 12, "column": 42 } - }, - "3": { - "start": { "line": 13, "column": 19 }, - "end": { "line": 13, "column": 29 } - }, - "4": { - "start": { "line": 39, "column": 2 }, - "end": { "line": 39, "column": null } - }, - "5": { - "start": { "line": 50, "column": 29 }, - "end": { "line": 50, "column": null } - }, - "6": { - "start": { "line": 53, "column": 27 }, - "end": { "line": 53, "column": null } - }, - "7": { - "start": { "line": 54, "column": 21 }, - "end": { "line": 54, "column": null } - }, - "8": { - "start": { "line": 55, "column": 21 }, - "end": { "line": 55, "column": null } - }, - "9": { - "start": { "line": 57, "column": 4 }, - "end": { "line": 65, "column": null } - }, - "10": { - "start": { "line": 72, "column": 4 }, - "end": { "line": 72, "column": null } - }, - "11": { - "start": { "line": 80, "column": 4 }, - "end": { "line": 80, "column": null } - } - }, - "fnMap": { - "0": { - "name": "(anonymous_0)", - "decl": { - "start": { "line": 10, "column": 32 }, - "end": { "line": 10, "column": 33 } - }, - "loc": { - "start": { "line": 10, "column": 67 }, - "end": { "line": 14, "column": null } - }, - "line": 10 - }, - "1": { - "name": "(anonymous_1)", - "decl": { - "start": { "line": 12, "column": 12 }, - "end": { "line": 12, "column": 13 } - }, - "loc": { - "start": { "line": 12, "column": 22 }, - "end": { "line": 12, "column": 42 } - }, - "line": 12 - }, - "2": { - "name": "(anonymous_2)", - "decl": { - "start": { "line": 13, "column": 9 }, - "end": { "line": 13, "column": 10 } - }, - "loc": { - "start": { "line": 13, "column": 19 }, - "end": { "line": 13, "column": 29 } - }, - "line": 13 - }, - "3": { - "name": "(anonymous_3)", - "decl": { - "start": { "line": 38, "column": 7 }, - "end": { "line": 38, "column": 13 } - }, - "loc": { - "start": { "line": 38, "column": 7 }, - "end": { "line": 39, "column": null } - }, - "line": 38 - }, - "4": { - "name": "(anonymous_4)", - "decl": { - "start": { "line": 49, "column": 2 }, - "end": { "line": 49, "column": 12 } - }, - "loc": { - "start": { "line": 49, "column": 43 }, - "end": { "line": 66, "column": null } - }, - "line": 49 - }, - "5": { - "name": "(anonymous_5)", - "decl": { - "start": { "line": 71, "column": 2 }, - "end": { "line": 71, "column": 32 } - }, - "loc": { - "start": { "line": 71, "column": 32 }, - "end": { "line": 73, "column": null } - }, - "line": 71 - }, - "6": { - "name": "(anonymous_6)", - "decl": { - "start": { "line": 79, "column": 10 }, - "end": { "line": 79, "column": 23 } - }, - "loc": { - "start": { "line": 79, "column": 47 }, - "end": { "line": 81, "column": null } - }, - "line": 79 - } - }, - "branchMap": { - "0": { - "loc": { - "start": { "line": 53, "column": 27 }, - "end": { "line": 53, "column": null } - }, - "type": "binary-expr", - "locations": [ - { - "start": { "line": 53, "column": 27 }, - "end": { "line": 53, "column": 54 } - }, - { - "start": { "line": 53, "column": 54 }, - "end": { "line": 53, "column": null } - } - ], - "line": 53 - }, - "1": { - "loc": { - "start": { "line": 64, "column": 10 }, - "end": { "line": 64, "column": null } - }, - "type": "binary-expr", - "locations": [ - { - "start": { "line": 64, "column": 10 }, - "end": { "line": 64, "column": 31 } - }, - { - "start": { "line": 64, "column": 31 }, - "end": { "line": 64, "column": null } - } - ], - "line": 64 - } - }, - "s": { - "0": 10, - "1": 40, - "2": 38, - "3": 37, - "4": 73, - "5": 40, - "6": 40, - "7": 40, - "8": 40, - "9": 40, - "10": 36, - "11": 40 - }, - "f": { "0": 40, "1": 38, "2": 37, "3": 73, "4": 40, "5": 36, "6": 40 }, - "b": { "0": [40, 11], "1": [40, 27] }, - "meta": { - "lastBranch": 2, - "lastFunction": 7, - "lastStatement": 12, - "seen": { - "s:10:32:14:Infinity": 0, - "f:10:32:10:33": 0, - "s:11:2:13:Infinity": 1, - "f:12:12:12:13": 1, - "s:12:22:12:42": 2, - "f:13:9:13:10": 2, - "s:13:19:13:29": 3, - "f:38:7:38:13": 3, - "s:39:2:39:Infinity": 4, - "f:49:2:49:12": 4, - "s:50:29:50:Infinity": 5, - "s:53:27:53:Infinity": 6, - "b:53:27:53:54:53:54:53:Infinity": 0, - "s:54:21:54:Infinity": 7, - "s:55:21:55:Infinity": 8, - "s:57:4:65:Infinity": 9, - "b:64:10:64:31:64:31:64:Infinity": 1, - "f:71:2:71:32": 5, - "s:72:4:72:Infinity": 10, - "f:79:10:79:23": 6, - "s:80:4:80:Infinity": 11 - } - } - }, - "/Users/danielslovinsky/Documents/projects/alchemy-docs/docs/src/content-indexer/collectors/navigation-trees.ts": { - "path": "/Users/danielslovinsky/Documents/projects/alchemy-docs/docs/src/content-indexer/collectors/navigation-trees.ts", - "statementMap": { - "0": { - "start": { "line": 11, "column": 2 }, - "end": { "line": 11, "column": null } - }, - "1": { - "start": { "line": 17, "column": 4 }, - "end": { "line": 19, "column": null } - }, - "2": { - "start": { "line": 18, "column": 6 }, - "end": { "line": 18, "column": null } - }, - "3": { - "start": { "line": 20, "column": 4 }, - "end": { "line": 20, "column": null } - }, - "4": { - "start": { "line": 27, "column": 4 }, - "end": { "line": 27, "column": null } - }, - "5": { - "start": { "line": 34, "column": 4 }, - "end": { "line": 43, "column": null } - }, - "6": { - "start": { "line": 38, "column": 10 }, - "end": { "line": 38, "column": null } - }, - "7": { - "start": { "line": 39, "column": 10 }, - "end": { "line": 39, "column": null } - }, - "8": { - "start": { "line": 50, "column": 4 }, - "end": { "line": 56, "column": null } - }, - "9": { - "start": { "line": 52, "column": 8 }, - "end": { "line": 54, "column": null } - }, - "10": { - "start": { "line": 55, "column": 6 }, - "end": { "line": 55, "column": null } - } - }, - "fnMap": { - "0": { - "name": "(anonymous_0)", - "decl": { - "start": { "line": 10, "column": 7 }, - "end": { "line": 10, "column": 13 } - }, - "loc": { - "start": { "line": 10, "column": 7 }, - "end": { "line": 11, "column": null } - }, - "line": 10 - }, - "1": { - "name": "(anonymous_1)", - "decl": { - "start": { "line": 16, "column": 2 }, - "end": { "line": 16, "column": 10 } - }, - "loc": { - "start": { "line": 16, "column": 44 }, - "end": { "line": 21, "column": null } - }, - "line": 16 - }, - "2": { - "name": "(anonymous_2)", - "decl": { - "start": { "line": 26, "column": 2 }, - "end": { "line": 26, "column": 35 } - }, - "loc": { - "start": { "line": 26, "column": 35 }, - "end": { "line": 28, "column": null } - }, - "line": 26 - }, - "3": { - "name": "(anonymous_3)", - "decl": { - "start": { "line": 33, "column": 2 }, - "end": { "line": 33, "column": 71 } - }, - "loc": { - "start": { "line": 33, "column": 71 }, - "end": { "line": 44, "column": null } - }, - "line": 33 - }, - "4": { - "name": "(anonymous_4)", - "decl": { - "start": { "line": 37, "column": 8 }, - "end": { "line": 37, "column": 9 } - }, - "loc": { - "start": { "line": 37, "column": 31 }, - "end": { "line": 40, "column": null } - }, - "line": 37 - }, - "5": { - "name": "(anonymous_5)", - "decl": { - "start": { "line": 49, "column": 10 }, - "end": { "line": 49, "column": 21 } - }, - "loc": { - "start": { "line": 49, "column": 47 }, - "end": { "line": 57, "column": null } - }, - "line": 49 - }, - "6": { - "name": "(anonymous_6)", - "decl": { - "start": { "line": 50, "column": 24 }, - "end": { "line": 50, "column": 25 } - }, - "loc": { - "start": { "line": 50, "column": 39 }, - "end": { "line": 56, "column": 7 } - }, - "line": 50 - } - }, - "branchMap": { - "0": { - "loc": { - "start": { "line": 17, "column": 4 }, - "end": { "line": 19, "column": null } - }, - "type": "if", - "locations": [ - { - "start": { "line": 17, "column": 4 }, - "end": { "line": 19, "column": null } - }, - { "start": {}, "end": {} } - ], - "line": 17 - }, - "1": { - "loc": { - "start": { "line": 52, "column": 8 }, - "end": { "line": 54, "column": null } - }, - "type": "cond-expr", - "locations": [ - { - "start": { "line": 53, "column": 12 }, - "end": { "line": 53, "column": null } - }, - { - "start": { "line": 54, "column": 12 }, - "end": { "line": 54, "column": null } - } - ], - "line": 52 - }, - "2": { - "loc": { - "start": { "line": 52, "column": 8 }, - "end": { "line": 52, "column": null } - }, - "type": "binary-expr", - "locations": [ - { - "start": { "line": 52, "column": 8 }, - "end": { "line": 52, "column": 35 } - }, - { - "start": { "line": 52, "column": 35 }, - "end": { "line": 52, "column": null } - } - ], - "line": 52 - } - }, - "s": { - "0": 71, - "1": 22, - "2": 19, - "3": 22, - "4": 31, - "5": 3, - "6": 4, - "7": 4, - "8": 5, - "9": 7, - "10": 7 - }, - "f": { "0": 71, "1": 22, "2": 31, "3": 3, "4": 4, "5": 5, "6": 7 }, - "b": { "0": [19, 3], "1": [1, 6], "2": [7, 6] }, - "meta": { - "lastBranch": 3, - "lastFunction": 7, - "lastStatement": 11, - "seen": { - "f:10:7:10:13": 0, - "s:11:2:11:Infinity": 0, - "f:16:2:16:10": 1, - "b:17:4:19:Infinity:undefined:undefined:undefined:undefined": 0, - "s:17:4:19:Infinity": 1, - "s:18:6:18:Infinity": 2, - "s:20:4:20:Infinity": 3, - "f:26:2:26:35": 2, - "s:27:4:27:Infinity": 4, - "f:33:2:33:71": 3, - "s:34:4:43:Infinity": 5, - "f:37:8:37:9": 4, - "s:38:10:38:Infinity": 6, - "s:39:10:39:Infinity": 7, - "f:49:10:49:21": 5, - "s:50:4:56:Infinity": 8, - "f:50:24:50:25": 6, - "s:52:8:54:Infinity": 9, - "b:53:12:53:Infinity:54:12:54:Infinity": 1, - "b:52:8:52:35:52:35:52:Infinity": 2, - "s:55:6:55:Infinity": 10 - } - } - }, - "/Users/danielslovinsky/Documents/projects/alchemy-docs/docs/src/content-indexer/collectors/path-index.ts": { - "path": "/Users/danielslovinsky/Documents/projects/alchemy-docs/docs/src/content-indexer/collectors/path-index.ts", - "statementMap": { - "0": { - "start": { "line": 11, "column": 2 }, - "end": { "line": 11, "column": null } - }, - "1": { - "start": { "line": 18, "column": 4 }, - "end": { "line": 22, "column": null } - }, - "2": { - "start": { "line": 19, "column": 6 }, - "end": { "line": 21, "column": null } - }, - "3": { - "start": { "line": 23, "column": 4 }, - "end": { "line": 23, "column": null } - }, - "4": { - "start": { "line": 30, "column": 4 }, - "end": { "line": 30, "column": null } - }, - "5": { - "start": { "line": 37, "column": 20 }, - "end": { "line": 37, "column": null } - }, - "6": { - "start": { "line": 38, "column": 4 }, - "end": { "line": 47, "column": null } - }, - "7": { - "start": { "line": 42, "column": 10 }, - "end": { "line": 42, "column": null } - }, - "8": { - "start": { "line": 43, "column": 10 }, - "end": { "line": 43, "column": null } - } - }, - "fnMap": { - "0": { - "name": "(anonymous_0)", - "decl": { - "start": { "line": 10, "column": 7 }, - "end": { "line": 10, "column": 13 } - }, - "loc": { - "start": { "line": 10, "column": 7 }, - "end": { "line": 11, "column": null } - }, - "line": 10 - }, - "1": { - "name": "(anonymous_1)", - "decl": { - "start": { "line": 17, "column": 2 }, - "end": { "line": 17, "column": 6 } - }, - "loc": { - "start": { "line": 17, "column": 49 }, - "end": { "line": 24, "column": null } - }, - "line": 17 - }, - "2": { - "name": "(anonymous_2)", - "decl": { - "start": { "line": 29, "column": 2 }, - "end": { "line": 29, "column": 24 } - }, - "loc": { - "start": { "line": 29, "column": 24 }, - "end": { "line": 31, "column": null } - }, - "line": 29 - }, - "3": { - "name": "(anonymous_3)", - "decl": { - "start": { "line": 36, "column": 2 }, - "end": { "line": 36, "column": 64 } - }, - "loc": { - "start": { "line": 36, "column": 64 }, - "end": { "line": 48, "column": null } - }, - "line": 36 - }, - "4": { - "name": "(anonymous_4)", - "decl": { - "start": { "line": 41, "column": 8 }, - "end": { "line": 41, "column": 9 } - }, - "loc": { - "start": { "line": 41, "column": 24 }, - "end": { "line": 44, "column": null } - }, - "line": 41 - } - }, - "branchMap": { - "0": { - "loc": { - "start": { "line": 18, "column": 4 }, - "end": { "line": 22, "column": null } - }, - "type": "if", - "locations": [ - { - "start": { "line": 18, "column": 4 }, - "end": { "line": 22, "column": null } - }, - { "start": {}, "end": {} } - ], - "line": 18 - }, - "1": { - "loc": { - "start": { "line": 42, "column": 29 }, - "end": { "line": 42, "column": 53 } - }, - "type": "binary-expr", - "locations": [ - { - "start": { "line": 42, "column": 29 }, - "end": { "line": 42, "column": 48 } - }, - { - "start": { "line": 42, "column": 48 }, - "end": { "line": 42, "column": 53 } - } - ], - "line": 42 - } - }, - "s": { - "0": 71, - "1": 16, - "2": 1, - "3": 16, - "4": 32, - "5": 2, - "6": 2, - "7": 4, - "8": 4 - }, - "f": { "0": 71, "1": 16, "2": 32, "3": 2, "4": 4 }, - "b": { "0": [1, 15], "1": [4, 4] }, - "meta": { - "lastBranch": 2, - "lastFunction": 5, - "lastStatement": 9, - "seen": { - "f:10:7:10:13": 0, - "s:11:2:11:Infinity": 0, - "f:17:2:17:6": 1, - "b:18:4:22:Infinity:undefined:undefined:undefined:undefined": 0, - "s:18:4:22:Infinity": 1, - "s:19:6:21:Infinity": 2, - "s:23:4:23:Infinity": 3, - "f:29:2:29:24": 2, - "s:30:4:30:Infinity": 4, - "f:36:2:36:64": 3, - "s:37:20:37:Infinity": 5, - "s:38:4:47:Infinity": 6, - "f:41:8:41:9": 4, - "s:42:10:42:Infinity": 7, - "b:42:29:42:48:42:48:42:53": 1, - "s:43:10:43:Infinity": 8 - } - } - }, - "/Users/danielslovinsky/Documents/projects/alchemy-docs/docs/src/content-indexer/collectors/processing-context.ts": { - "path": "/Users/danielslovinsky/Documents/projects/alchemy-docs/docs/src/content-indexer/collectors/processing-context.ts", - "statementMap": { - "0": { - "start": { "line": 30, "column": 12 }, - "end": { "line": 30, "column": 33 } - }, - "1": { - "start": { "line": 31, "column": 12 }, - "end": { "line": 31, "column": 39 } - }, - "2": { - "start": { "line": 32, "column": 12 }, - "end": { "line": 32, "column": 31 } - }, - "3": { - "start": { "line": 39, "column": 4 }, - "end": { "line": 39, "column": null } - }, - "4": { - "start": { "line": 46, "column": 4 }, - "end": { "line": 46, "column": null } - }, - "5": { - "start": { "line": 53, "column": 4 }, - "end": { "line": 53, "column": null } - }, - "6": { - "start": { "line": 60, "column": 4 }, - "end": { "line": 64, "column": null } - }, - "7": { - "start": { "line": 71, "column": 4 }, - "end": { "line": 75, "column": null } - } - }, - "fnMap": { - "0": { - "name": "(anonymous_0)", - "decl": { - "start": { "line": 29, "column": 2 }, - "end": { "line": 29, "column": null } - }, - "loc": { - "start": { "line": 33, "column": 4 }, - "end": { "line": 33, "column": null } - }, - "line": 33 - }, - "1": { - "name": "(anonymous_1)", - "decl": { - "start": { "line": 38, "column": 2 }, - "end": { "line": 38, "column": 20 } - }, - "loc": { - "start": { "line": 38, "column": 63 }, - "end": { "line": 40, "column": null } - }, - "line": 38 - }, - "2": { - "name": "(anonymous_2)", - "decl": { - "start": { "line": 45, "column": 2 }, - "end": { "line": 45, "column": 20 } - }, - "loc": { - "start": { "line": 45, "column": 54 }, - "end": { "line": 47, "column": null } - }, - "line": 45 - }, - "3": { - "name": "(anonymous_3)", - "decl": { - "start": { "line": 52, "column": 2 }, - "end": { "line": 52, "column": 19 } - }, - "loc": { - "start": { "line": 52, "column": 50 }, - "end": { "line": 54, "column": null } - }, - "line": 52 - }, - "4": { - "name": "(anonymous_4)", - "decl": { - "start": { "line": 59, "column": 2 }, - "end": { "line": 59, "column": 38 } - }, - "loc": { - "start": { "line": 59, "column": 38 }, - "end": { "line": 65, "column": null } - }, - "line": 59 - }, - "5": { - "name": "(anonymous_5)", - "decl": { - "start": { "line": 70, "column": 2 }, - "end": { "line": 70, "column": 13 } - }, - "loc": { - "start": { "line": 70, "column": 13 }, - "end": { "line": 76, "column": null } - }, - "line": 70 - } - }, - "branchMap": { - "0": { - "loc": { - "start": { "line": 30, "column": 12 }, - "end": { "line": 30, "column": null } - }, - "type": "default-arg", - "locations": [ - { - "start": { "line": 30, "column": 33 }, - "end": { "line": 30, "column": null } - } - ], - "line": 30 - }, - "1": { - "loc": { - "start": { "line": 31, "column": 12 }, - "end": { "line": 31, "column": null } - }, - "type": "default-arg", - "locations": [ - { - "start": { "line": 31, "column": 39 }, - "end": { "line": 31, "column": null } - } - ], - "line": 31 - }, - "2": { - "loc": { - "start": { "line": 32, "column": 12 }, - "end": { "line": 32, "column": null } - }, - "type": "default-arg", - "locations": [ - { - "start": { "line": 32, "column": 31 }, - "end": { "line": 32, "column": null } - } - ], - "line": 32 - } - }, - "s": { - "0": 64, - "1": 64, - "2": 64, - "3": 6, - "4": 12, - "5": 31, - "6": 26, - "7": 1 - }, - "f": { "0": 64, "1": 6, "2": 12, "3": 31, "4": 26, "5": 1 }, - "b": { "0": [64], "1": [64], "2": [64] }, - "meta": { - "lastBranch": 3, - "lastFunction": 6, - "lastStatement": 8, - "seen": { - "f:29:2:29:Infinity": 0, - "b:30:33:30:Infinity": 0, - "b:31:39:31:Infinity": 1, - "b:32:31:32:Infinity": 2, - "s:30:12:30:33": 0, - "s:31:12:31:39": 1, - "s:32:12:32:31": 2, - "f:38:2:38:20": 1, - "s:39:4:39:Infinity": 3, - "f:45:2:45:20": 2, - "s:46:4:46:Infinity": 4, - "f:52:2:52:19": 3, - "s:53:4:53:Infinity": 5, - "f:59:2:59:38": 4, - "s:60:4:64:Infinity": 6, - "f:70:2:70:13": 5, - "s:71:4:75:Infinity": 7 - } - } - }, - "/Users/danielslovinsky/Documents/projects/alchemy-docs/docs/src/content-indexer/constants/http.ts": { - "path": "/Users/danielslovinsky/Documents/projects/alchemy-docs/docs/src/content-indexer/constants/http.ts", - "statementMap": { - "0": { - "start": { "line": 1, "column": 28 }, - "end": { "line": 10, "column": null } - } - }, - "fnMap": {}, - "branchMap": {}, - "s": { "0": 5 }, - "f": {}, - "b": {}, - "meta": { - "lastBranch": 0, - "lastFunction": 0, - "lastStatement": 1, - "seen": { "s:1:28:10:Infinity": 0 } - } - }, - "/Users/danielslovinsky/Documents/projects/alchemy-docs/docs/src/content-indexer/core/batch-fetcher.ts": { - "path": "/Users/danielslovinsky/Documents/projects/alchemy-docs/docs/src/content-indexer/core/batch-fetcher.ts", - "statementMap": { - "0": { - "start": { "line": 33, "column": 33 }, - "end": { "line": 104, "column": null } - }, - "1": { - "start": { "line": 37, "column": 16 }, - "end": { "line": 37, "column": null } - }, - "2": { - "start": { "line": 39, "column": 21 }, - "end": { "line": 39, "column": null } - }, - "3": { - "start": { "line": 40, "column": 2 }, - "end": { "line": 42, "column": null } - }, - "4": { - "start": { "line": 45, "column": 22 }, - "end": { "line": 81, "column": null } - }, - "5": { - "start": { "line": 46, "column": 4 }, - "end": { "line": 80, "column": null } - }, - "6": { - "start": { "line": 47, "column": 6 }, - "end": { "line": 74, "column": null } - }, - "7": { - "start": { "line": 49, "column": 25 }, - "end": { "line": 49, "column": null } - }, - "8": { - "start": { "line": 50, "column": 23 }, - "end": { "line": 50, "column": null } - }, - "9": { - "start": { "line": 52, "column": 8 }, - "end": { "line": 57, "column": null } - }, - "10": { - "start": { "line": 53, "column": 10 }, - "end": { "line": 56, "column": null } - }, - "11": { - "start": { "line": 60, "column": 27 }, - "end": { "line": 63, "column": null } - }, - "12": { - "start": { "line": 64, "column": 25 }, - "end": { "line": 64, "column": null } - }, - "13": { - "start": { "line": 66, "column": 24 }, - "end": { "line": 66, "column": null } - }, - "14": { - "start": { "line": 67, "column": 8 }, - "end": { "line": 73, "column": null } - }, - "15": { - "start": { "line": 68, "column": 38 }, - "end": { "line": 68, "column": null } - }, - "16": { - "start": { "line": 69, "column": 10 }, - "end": { "line": 72, "column": null } - }, - "17": { - "start": { "line": 76, "column": 6 }, - "end": { "line": 79, "column": null } - }, - "18": { - "start": { "line": 84, "column": 23 }, - "end": { "line": 93, "column": null } - }, - "19": { - "start": { "line": 85, "column": 4 }, - "end": { "line": 92, "column": null } - }, - "20": { - "start": { "line": 86, "column": 21 }, - "end": { "line": 86, "column": null } - }, - "21": { - "start": { "line": 87, "column": 6 }, - "end": { "line": 89, "column": null } - }, - "22": { - "start": { "line": 88, "column": 8 }, - "end": { "line": 88, "column": null } - }, - "23": { - "start": { "line": 91, "column": 6 }, - "end": { "line": 91, "column": null } - }, - "24": { - "start": { "line": 96, "column": 2 }, - "end": { "line": 96, "column": null } - }, - "25": { - "start": { "line": 98, "column": 16 }, - "end": { "line": 98, "column": null } - }, - "26": { - "start": { "line": 99, "column": 2 }, - "end": { "line": 101, "column": null } - }, - "27": { - "start": { "line": 103, "column": 2 }, - "end": { "line": 103, "column": null } - } - }, - "fnMap": { - "0": { - "name": "(anonymous_0)", - "decl": { - "start": { "line": 33, "column": 33 }, - "end": { "line": 33, "column": null } - }, - "loc": { - "start": { "line": 36, "column": 28 }, - "end": { "line": 104, "column": null } - }, - "line": 36 - }, - "1": { - "name": "(anonymous_1)", - "decl": { - "start": { "line": 45, "column": 58 }, - "end": { "line": 45, "column": 65 } - }, - "loc": { - "start": { "line": 45, "column": 77 }, - "end": { "line": 81, "column": 3 } - }, - "line": 45 - }, - "2": { - "name": "(anonymous_2)", - "decl": { - "start": { "line": 84, "column": 60 }, - "end": { "line": 84, "column": 67 } - }, - "loc": { - "start": { "line": 84, "column": 79 }, - "end": { "line": 93, "column": 3 } - }, - "line": 84 - } - }, - "branchMap": { - "0": { - "loc": { - "start": { "line": 41, "column": 10 }, - "end": { "line": 41, "column": 62 } - }, - "type": "cond-expr", - "locations": [ - { - "start": { "line": 41, "column": 40 }, - "end": { "line": 41, "column": 52 } - }, - { - "start": { "line": 41, "column": 52 }, - "end": { "line": 41, "column": 62 } - } - ], - "line": 41 - }, - "1": { - "loc": { - "start": { "line": 47, "column": 6 }, - "end": { "line": 74, "column": null } - }, - "type": "if", - "locations": [ - { - "start": { "line": 47, "column": 6 }, - "end": { "line": 74, "column": null } - }, - { - "start": { "line": 58, "column": 13 }, - "end": { "line": 74, "column": null } - } - ], - "line": 47 - }, - "2": { - "loc": { - "start": { "line": 52, "column": 8 }, - "end": { "line": 57, "column": null } - }, - "type": "if", - "locations": [ - { - "start": { "line": 52, "column": 8 }, - "end": { "line": 57, "column": null } - }, - { "start": {}, "end": {} } - ], - "line": 52 - }, - "3": { - "loc": { - "start": { "line": 61, "column": 10 }, - "end": { "line": 61, "column": null } - }, - "type": "binary-expr", - "locations": [ - { - "start": { "line": 61, "column": 10 }, - "end": { "line": 61, "column": 47 } - }, - { - "start": { "line": 61, "column": 47 }, - "end": { "line": 61, "column": null } - } - ], - "line": 61 - }, - "4": { - "loc": { - "start": { "line": 67, "column": 8 }, - "end": { "line": 73, "column": null } - }, - "type": "if", - "locations": [ - { - "start": { "line": 67, "column": 8 }, - "end": { "line": 73, "column": null } - }, - { "start": {}, "end": {} } - ], - "line": 67 - }, - "5": { - "loc": { - "start": { "line": 77, "column": 28 }, - "end": { "line": 77, "column": 75 } - }, - "type": "cond-expr", - "locations": [ - { - "start": { "line": 77, "column": 59 }, - "end": { "line": 77, "column": 68 } - }, - { - "start": { "line": 77, "column": 68 }, - "end": { "line": 77, "column": 75 } - } - ], - "line": 77 - }, - "6": { - "loc": { - "start": { "line": 87, "column": 6 }, - "end": { "line": 89, "column": null } - }, - "type": "if", - "locations": [ - { - "start": { "line": 87, "column": 6 }, - "end": { "line": 89, "column": null } - }, - { "start": {}, "end": {} } - ], - "line": 87 - }, - "7": { - "loc": { - "start": { "line": 100, "column": 12 }, - "end": { "line": 100, "column": 60 } - }, - "type": "cond-expr", - "locations": [ - { - "start": { "line": 100, "column": 42 }, - "end": { "line": 100, "column": 51 } - }, - { - "start": { "line": 100, "column": 51 }, - "end": { "line": 100, "column": 60 } - } - ], - "line": 100 - } - }, - "s": { - "0": 1, - "1": 6, - "2": 6, - "3": 6, - "4": 6, - "5": 6, - "6": 6, - "7": 0, - "8": 0, - "9": 0, - "10": 0, - "11": 6, - "12": 6, - "13": 6, - "14": 5, - "15": 4, - "16": 4, - "17": 1, - "18": 6, - "19": 5, - "20": 5, - "21": 4, - "22": 3, - "23": 1, - "24": 6, - "25": 6, - "26": 6, - "27": 6 - }, - "f": { "0": 6, "1": 6, "2": 5 }, - "b": { - "0": [0, 6], - "1": [0, 6], - "2": [0, 0], - "3": [6, 5], - "4": [4, 1], - "5": [0, 1], - "6": [3, 1], - "7": [0, 6] - }, - "meta": { - "lastBranch": 8, - "lastFunction": 3, - "lastStatement": 28, - "seen": { - "s:33:33:104:Infinity": 0, - "f:33:33:33:Infinity": 0, - "s:37:16:37:Infinity": 1, - "s:39:21:39:Infinity": 2, - "s:40:2:42:Infinity": 3, - "b:41:40:41:52:41:52:41:62": 0, - "s:45:22:81:Infinity": 4, - "f:45:58:45:65": 1, - "s:46:4:80:Infinity": 5, - "b:47:6:74:Infinity:58:13:74:Infinity": 1, - "s:47:6:74:Infinity": 6, - "s:49:25:49:Infinity": 7, - "s:50:23:50:Infinity": 8, - "b:52:8:57:Infinity:undefined:undefined:undefined:undefined": 2, - "s:52:8:57:Infinity": 9, - "s:53:10:56:Infinity": 10, - "s:60:27:63:Infinity": 11, - "b:61:10:61:47:61:47:61:Infinity": 3, - "s:64:25:64:Infinity": 12, - "s:66:24:66:Infinity": 13, - "b:67:8:73:Infinity:undefined:undefined:undefined:undefined": 4, - "s:67:8:73:Infinity": 14, - "s:68:38:68:Infinity": 15, - "s:69:10:72:Infinity": 16, - "s:76:6:79:Infinity": 17, - "b:77:59:77:68:77:68:77:75": 5, - "s:84:23:93:Infinity": 18, - "f:84:60:84:67": 2, - "s:85:4:92:Infinity": 19, - "s:86:21:86:Infinity": 20, - "b:87:6:89:Infinity:undefined:undefined:undefined:undefined": 6, - "s:87:6:89:Infinity": 21, - "s:88:8:88:Infinity": 22, - "s:91:6:91:Infinity": 23, - "s:96:2:96:Infinity": 24, - "s:98:16:98:Infinity": 25, - "s:99:2:101:Infinity": 26, - "b:100:42:100:51:100:51:100:60": 7, - "s:103:2:103:Infinity": 27 - } - } - }, - "/Users/danielslovinsky/Documents/projects/alchemy-docs/docs/src/content-indexer/core/build-all-outputs.ts": { - "path": "/Users/danielslovinsky/Documents/projects/alchemy-docs/docs/src/content-indexer/core/build-all-outputs.ts", - "statementMap": { - "0": { - "start": { "line": 22, "column": 31 }, - "end": { "line": 85, "column": null } - }, - "1": { - "start": { "line": 27, "column": 18 }, - "end": { "line": 27, "column": null } - }, - "2": { - "start": { "line": 30, "column": 2 }, - "end": { "line": 82, "column": null } - }, - "3": { - "start": { "line": 32, "column": 4 }, - "end": { "line": 34, "column": null } - }, - "4": { - "start": { "line": 33, "column": 6 }, - "end": { "line": 33, "column": null } - }, - "5": { - "start": { "line": 37, "column": 10 }, - "end": { "line": 37, "column": null } - }, - "6": { - "start": { "line": 40, "column": 26 }, - "end": { "line": 40, "column": null } - }, - "7": { - "start": { "line": 43, "column": 22 }, - "end": { "line": 43, "column": null } - }, - "8": { - "start": { "line": 44, "column": 4 }, - "end": { "line": 52, "column": null } - }, - "9": { - "start": { "line": 45, "column": 29 }, - "end": { "line": 45, "column": null } - }, - "10": { - "start": { "line": 46, "column": 26 }, - "end": { "line": 46, "column": null } - }, - "11": { - "start": { "line": 48, "column": 6 }, - "end": { "line": 51, "column": null } - }, - "12": { - "start": { "line": 55, "column": 20 }, - "end": { "line": 65, "column": null } - }, - "13": { - "start": { "line": 55, "column": 40 }, - "end": { "line": 64, "column": null } - }, - "14": { - "start": { "line": 68, "column": 4 }, - "end": { "line": 81, "column": null } - }, - "15": { - "start": { "line": 70, "column": 6 }, - "end": { "line": 72, "column": null } - }, - "16": { - "start": { "line": 71, "column": 8 }, - "end": { "line": 71, "column": null } - }, - "17": { - "start": { "line": 75, "column": 6 }, - "end": { "line": 80, "column": null } - }, - "18": { - "start": { "line": 76, "column": 22 }, - "end": { "line": 78, "column": null } - }, - "19": { - "start": { "line": 79, "column": 8 }, - "end": { "line": 79, "column": null } - }, - "20": { - "start": { "line": 79, "column": 32 }, - "end": { "line": 79, "column": 68 } - }, - "21": { - "start": { "line": 84, "column": 2 }, - "end": { "line": 84, "column": null } - } - }, - "fnMap": { - "0": { - "name": "(anonymous_0)", - "decl": { - "start": { "line": 22, "column": 31 }, - "end": { "line": 22, "column": null } - }, - "loc": { - "start": { "line": 26, "column": 28 }, - "end": { "line": 85, "column": null } - }, - "line": 26 - }, - "1": { - "name": "(anonymous_1)", - "decl": { - "start": { "line": 30, "column": 29 }, - "end": { "line": 30, "column": 30 } - }, - "loc": { - "start": { "line": 30, "column": 42 }, - "end": { "line": 82, "column": 3 } - }, - "line": 30 - }, - "2": { - "name": "(anonymous_2)", - "decl": { - "start": { "line": 55, "column": 39 }, - "end": { "line": 55, "column": 40 } - }, - "loc": { - "start": { "line": 55, "column": 40 }, - "end": { "line": 64, "column": null } - }, - "line": 55 - }, - "3": { - "name": "(anonymous_3)", - "decl": { - "start": { "line": 68, "column": 20 }, - "end": { "line": 68, "column": 21 } - }, - "loc": { - "start": { "line": 68, "column": 32 }, - "end": { "line": 81, "column": 5 } - }, - "line": 68 - }, - "4": { - "name": "(anonymous_4)", - "decl": { - "start": { "line": 70, "column": 50 }, - "end": { "line": 70, "column": 51 } - }, - "loc": { - "start": { "line": 70, "column": 69 }, - "end": { "line": 72, "column": 7 } - }, - "line": 70 - }, - "5": { - "name": "(anonymous_5)", - "decl": { - "start": { "line": 79, "column": 22 }, - "end": { "line": 79, "column": 23 } - }, - "loc": { - "start": { "line": 79, "column": 32 }, - "end": { "line": 79, "column": 68 } - }, - "line": 79 - } - }, - "branchMap": { - "0": { - "loc": { - "start": { "line": 25, "column": 2 }, - "end": { "line": 25, "column": null } - }, - "type": "default-arg", - "locations": [ - { - "start": { "line": 25, "column": 21 }, - "end": { "line": 25, "column": null } - } - ], - "line": 25 - }, - "1": { - "loc": { - "start": { "line": 32, "column": 4 }, - "end": { "line": 34, "column": null } - }, - "type": "if", - "locations": [ - { - "start": { "line": 32, "column": 4 }, - "end": { "line": 34, "column": null } - }, - { "start": {}, "end": {} } - ], - "line": 32 - }, - "2": { - "loc": { - "start": { "line": 32, "column": 8 }, - "end": { "line": 32, "column": 41 } - }, - "type": "binary-expr", - "locations": [ - { - "start": { "line": 32, "column": 8 }, - "end": { "line": 32, "column": 24 } - }, - { - "start": { "line": 32, "column": 24 }, - "end": { "line": 32, "column": 41 } - } - ], - "line": 32 - }, - "3": { - "loc": { - "start": { "line": 44, "column": 4 }, - "end": { "line": 52, "column": null } - }, - "type": "if", - "locations": [ - { - "start": { "line": 44, "column": 4 }, - "end": { "line": 52, "column": null } - }, - { "start": {}, "end": {} } - ], - "line": 44 - }, - "4": { - "loc": { - "start": { "line": 45, "column": 29 }, - "end": { "line": 45, "column": null } - }, - "type": "binary-expr", - "locations": [ - { - "start": { "line": 45, "column": 29 }, - "end": { "line": 45, "column": 47 } - }, - { - "start": { "line": 45, "column": 47 }, - "end": { "line": 45, "column": null } - } - ], - "line": 45 - }, - "5": { - "loc": { - "start": { "line": 46, "column": 26 }, - "end": { "line": 46, "column": null } - }, - "type": "binary-expr", - "locations": [ - { - "start": { "line": 46, "column": 26 }, - "end": { "line": 46, "column": 52 } - }, - { - "start": { "line": 46, "column": 52 }, - "end": { "line": 46, "column": null } - } - ], - "line": 46 - }, - "6": { - "loc": { - "start": { "line": 75, "column": 6 }, - "end": { "line": 80, "column": null } - }, - "type": "if", - "locations": [ - { - "start": { "line": 75, "column": 6 }, - "end": { "line": 80, "column": null } - }, - { "start": {}, "end": {} } - ], - "line": 75 - }, - "7": { - "loc": { - "start": { "line": 76, "column": 22 }, - "end": { "line": 78, "column": null } - }, - "type": "cond-expr", - "locations": [ - { - "start": { "line": 77, "column": 12 }, - "end": { "line": 77, "column": null } - }, - { - "start": { "line": 78, "column": 12 }, - "end": { "line": 78, "column": null } - } - ], - "line": 76 - } - }, - "s": { - "0": 1, - "1": 7, - "2": 7, - "3": 10, - "4": 2, - "5": 8, - "6": 8, - "7": 8, - "8": 10, - "9": 2, - "10": 2, - "11": 2, - "12": 8, - "13": 8, - "14": 8, - "15": 8, - "16": 3, - "17": 8, - "18": 6, - "19": 6, - "20": 7, - "21": 7 - }, - "f": { "0": 7, "1": 10, "2": 8, "3": 8, "4": 3, "5": 7 }, - "b": { - "0": [7], - "1": [2, 8], - "2": [10, 9], - "3": [2, 8], - "4": [2, 0], - "5": [2, 0], - "6": [6, 2], - "7": [1, 5] - }, - "meta": { - "lastBranch": 8, - "lastFunction": 6, - "lastStatement": 22, - "seen": { - "s:22:31:85:Infinity": 0, - "f:22:31:22:Infinity": 0, - "b:25:21:25:Infinity": 0, - "s:27:18:27:Infinity": 1, - "s:30:2:82:Infinity": 2, - "f:30:29:30:30": 1, - "b:32:4:34:Infinity:undefined:undefined:undefined:undefined": 1, - "s:32:4:34:Infinity": 3, - "b:32:8:32:24:32:24:32:41": 2, - "s:33:6:33:Infinity": 4, - "s:37:10:37:Infinity": 5, - "s:40:26:40:Infinity": 6, - "s:43:22:43:Infinity": 7, - "b:44:4:52:Infinity:undefined:undefined:undefined:undefined": 3, - "s:44:4:52:Infinity": 8, - "s:45:29:45:Infinity": 9, - "b:45:29:45:47:45:47:45:Infinity": 4, - "s:46:26:46:Infinity": 10, - "b:46:26:46:52:46:52:46:Infinity": 5, - "s:48:6:51:Infinity": 11, - "s:55:20:65:Infinity": 12, - "f:55:39:55:40": 2, - "s:55:40:64:Infinity": 13, - "s:68:4:81:Infinity": 14, - "f:68:20:68:21": 3, - "s:70:6:72:Infinity": 15, - "f:70:50:70:51": 4, - "s:71:8:71:Infinity": 16, - "b:75:6:80:Infinity:undefined:undefined:undefined:undefined": 6, - "s:75:6:80:Infinity": 17, - "s:76:22:78:Infinity": 18, - "b:77:12:77:Infinity:78:12:78:Infinity": 7, - "s:79:8:79:Infinity": 19, - "f:79:22:79:23": 5, - "s:79:32:79:68": 20, - "s:84:2:84:Infinity": 21 - } - } - }, - "/Users/danielslovinsky/Documents/projects/alchemy-docs/docs/src/content-indexer/core/content-cache.ts": { - "path": "/Users/danielslovinsky/Documents/projects/alchemy-docs/docs/src/content-indexer/core/content-cache.ts", - "statementMap": { - "0": { - "start": { "line": 45, "column": 4 }, - "end": { "line": 45, "column": null } - }, - "1": { - "start": { "line": 46, "column": 4 }, - "end": { "line": 46, "column": null } - }, - "2": { - "start": { "line": 53, "column": 4 }, - "end": { "line": 53, "column": null } - }, - "3": { - "start": { "line": 60, "column": 4 }, - "end": { "line": 60, "column": null } - }, - "4": { - "start": { "line": 67, "column": 4 }, - "end": { "line": 67, "column": null } - }, - "5": { - "start": { "line": 74, "column": 4 }, - "end": { "line": 74, "column": null } - }, - "6": { - "start": { "line": 81, "column": 4 }, - "end": { "line": 84, "column": null } - } - }, - "fnMap": { - "0": { - "name": "(anonymous_0)", - "decl": { - "start": { "line": 44, "column": 2 }, - "end": { "line": 44, "column": 16 } - }, - "loc": { - "start": { "line": 44, "column": 16 }, - "end": { "line": 47, "column": null } - }, - "line": 44 - }, - "1": { - "name": "(anonymous_1)", - "decl": { - "start": { "line": 52, "column": 2 }, - "end": { "line": 52, "column": 16 } - }, - "loc": { - "start": { "line": 52, "column": 62 }, - "end": { "line": 54, "column": null } - }, - "line": 52 - }, - "2": { - "name": "(anonymous_2)", - "decl": { - "start": { "line": 59, "column": 2 }, - "end": { "line": 59, "column": 16 } - }, - "loc": { - "start": { "line": 59, "column": 61 }, - "end": { "line": 61, "column": null } - }, - "line": 59 - }, - "3": { - "name": "(anonymous_3)", - "decl": { - "start": { "line": 66, "column": 2 }, - "end": { "line": 66, "column": 10 } - }, - "loc": { - "start": { "line": 66, "column": 56 }, - "end": { "line": 68, "column": null } - }, - "line": 66 - }, - "4": { - "name": "(anonymous_4)", - "decl": { - "start": { "line": 73, "column": 2 }, - "end": { "line": 73, "column": 10 } - }, - "loc": { - "start": { "line": 73, "column": 55 }, - "end": { "line": 75, "column": null } - }, - "line": 73 - }, - "5": { - "name": "(anonymous_5)", - "decl": { - "start": { "line": 80, "column": 2 }, - "end": { "line": 80, "column": 25 } - }, - "loc": { - "start": { "line": 80, "column": 25 }, - "end": { "line": 85, "column": null } - }, - "line": 80 - } - }, - "branchMap": {}, - "s": { "0": 75, "1": 75, "2": 18, "3": 29, "4": 17, "5": 14, "6": 12 }, - "f": { "0": 75, "1": 18, "2": 29, "3": 17, "4": 14, "5": 12 }, - "b": {}, - "meta": { - "lastBranch": 0, - "lastFunction": 6, - "lastStatement": 7, - "seen": { - "f:44:2:44:16": 0, - "s:45:4:45:Infinity": 0, - "s:46:4:46:Infinity": 1, - "f:52:2:52:16": 1, - "s:53:4:53:Infinity": 2, - "f:59:2:59:16": 2, - "s:60:4:60:Infinity": 3, - "f:66:2:66:10": 3, - "s:67:4:67:Infinity": 4, - "f:73:2:73:10": 4, - "s:74:4:74:Infinity": 5, - "f:80:2:80:25": 5, - "s:81:4:84:Infinity": 6 - } - } - }, - "/Users/danielslovinsky/Documents/projects/alchemy-docs/docs/src/content-indexer/core/path-builder.ts": { - "path": "/Users/danielslovinsky/Documents/projects/alchemy-docs/docs/src/content-indexer/core/path-builder.ts", - "statementMap": { - "0": { - "start": { "line": 11, "column": 4 }, - "end": { "line": 11, "column": null } - }, - "1": { - "start": { "line": 18, "column": 21 }, - "end": { "line": 18, "column": null } - }, - "2": { - "start": { "line": 19, "column": 4 }, - "end": { "line": 19, "column": null } - }, - "3": { - "start": { "line": 35, "column": 4 }, - "end": { "line": 37, "column": null } - }, - "4": { - "start": { "line": 36, "column": 6 }, - "end": { "line": 36, "column": null } - }, - "5": { - "start": { "line": 40, "column": 4 }, - "end": { "line": 42, "column": null } - }, - "6": { - "start": { "line": 41, "column": 6 }, - "end": { "line": 41, "column": null } - }, - "7": { - "start": { "line": 45, "column": 4 }, - "end": { "line": 47, "column": null } - }, - "8": { - "start": { "line": 46, "column": 6 }, - "end": { "line": 46, "column": null } - }, - "9": { - "start": { "line": 49, "column": 4 }, - "end": { "line": 49, "column": null } - }, - "10": { - "start": { "line": 56, "column": 4 }, - "end": { "line": 56, "column": null } - } - }, - "fnMap": { - "0": { - "name": "(anonymous_0)", - "decl": { - "start": { "line": 10, "column": 10 }, - "end": { "line": 10, "column": 22 } - }, - "loc": { - "start": { "line": 10, "column": 42 }, - "end": { "line": 12, "column": null } - }, - "line": 10 - }, - "1": { - "name": "(anonymous_1)", - "decl": { - "start": { "line": 17, "column": 9 }, - "end": { "line": 17, "column": 14 } - }, - "loc": { - "start": { "line": 17, "column": 50 }, - "end": { "line": 20, "column": null } - }, - "line": 17 - }, - "2": { - "name": "(anonymous_2)", - "decl": { - "start": { "line": 29, "column": 2 }, - "end": { "line": 29, "column": 8 } - }, - "loc": { - "start": { "line": 33, "column": 18 }, - "end": { "line": 50, "column": null } - }, - "line": 33 - }, - "3": { - "name": "(anonymous_3)", - "decl": { - "start": { "line": 55, "column": 2 }, - "end": { "line": 55, "column": 16 } - }, - "loc": { - "start": { "line": 55, "column": 16 }, - "end": { "line": 57, "column": null } - }, - "line": 55 - } - }, - "branchMap": { - "0": { - "loc": { - "start": { "line": 17, "column": 14 }, - "end": { "line": 17, "column": 50 } - }, - "type": "default-arg", - "locations": [ - { - "start": { "line": 17, "column": 33 }, - "end": { "line": 17, "column": 50 } - } - ], - "line": 17 - }, - "1": { - "loc": { - "start": { "line": 18, "column": 21 }, - "end": { "line": 18, "column": null } - }, - "type": "cond-expr", - "locations": [ - { - "start": { "line": 18, "column": 32 }, - "end": { "line": 18, "column": 70 } - }, - { - "start": { "line": 18, "column": 70 }, - "end": { "line": 18, "column": null } - } - ], - "line": 18 - }, - "2": { - "loc": { - "start": { "line": 35, "column": 4 }, - "end": { "line": 37, "column": null } - }, - "type": "if", - "locations": [ - { - "start": { "line": 35, "column": 4 }, - "end": { "line": 37, "column": null } - }, - { "start": {}, "end": {} } - ], - "line": 35 - }, - "3": { - "loc": { - "start": { "line": 40, "column": 4 }, - "end": { "line": 42, "column": null } - }, - "type": "if", - "locations": [ - { - "start": { "line": 40, "column": 4 }, - "end": { "line": 42, "column": null } - }, - { "start": {}, "end": {} } - ], - "line": 40 - }, - "4": { - "loc": { - "start": { "line": 45, "column": 4 }, - "end": { "line": 47, "column": null } - }, - "type": "if", - "locations": [ - { - "start": { "line": 45, "column": 4 }, - "end": { "line": 47, "column": null } - }, - { "start": {}, "end": {} } - ], - "line": 45 - } - }, - "s": { - "0": 205, - "1": 90, - "2": 90, - "3": 115, - "4": 6, - "5": 109, - "6": 3, - "7": 106, - "8": 106, - "9": 0, - "10": 79 - }, - "f": { "0": 205, "1": 10, "2": 115, "3": 79 }, - "b": { - "0": [90], - "1": [50, 40], - "2": [6, 109], - "3": [3, 106], - "4": [106, 0] - }, - "meta": { - "lastBranch": 5, - "lastFunction": 4, - "lastStatement": 11, - "seen": { - "f:10:10:10:22": 0, - "s:11:4:11:Infinity": 0, - "f:17:9:17:14": 1, - "b:17:33:17:50": 0, - "s:18:21:18:Infinity": 1, - "b:18:32:18:70:18:70:18:Infinity": 1, - "s:19:4:19:Infinity": 2, - "f:29:2:29:8": 2, - "b:35:4:37:Infinity:undefined:undefined:undefined:undefined": 2, - "s:35:4:37:Infinity": 3, - "s:36:6:36:Infinity": 4, - "b:40:4:42:Infinity:undefined:undefined:undefined:undefined": 3, - "s:40:4:42:Infinity": 5, - "s:41:6:41:Infinity": 6, - "b:45:4:47:Infinity:undefined:undefined:undefined:undefined": 4, - "s:45:4:47:Infinity": 7, - "s:46:6:46:Infinity": 8, - "s:49:4:49:Infinity": 9, - "f:55:2:55:16": 3, - "s:56:4:56:Infinity": 10 - } - } - }, - "/Users/danielslovinsky/Documents/projects/alchemy-docs/docs/src/content-indexer/core/scanner.ts": { - "path": "/Users/danielslovinsky/Documents/projects/alchemy-docs/docs/src/content-indexer/core/scanner.ts", - "statementMap": { - "0": { - "start": { "line": 22, "column": 27 }, - "end": { "line": 56, "column": null } - }, - "1": { - "start": { "line": 24, "column": 2 }, - "end": { "line": 26, "column": null } - }, - "2": { - "start": { "line": 25, "column": 4 }, - "end": { "line": 25, "column": null } - }, - "3": { - "start": { "line": 29, "column": 2 }, - "end": { "line": 31, "column": null } - }, - "4": { - "start": { "line": 30, "column": 4 }, - "end": { "line": 30, "column": null } - }, - "5": { - "start": { "line": 34, "column": 2 }, - "end": { "line": 37, "column": null } - }, - "6": { - "start": { "line": 35, "column": 4 }, - "end": { "line": 35, "column": null } - }, - "7": { - "start": { "line": 36, "column": 4 }, - "end": { "line": 36, "column": null } - }, - "8": { - "start": { "line": 40, "column": 2 }, - "end": { "line": 43, "column": null } - }, - "9": { - "start": { "line": 41, "column": 4 }, - "end": { "line": 41, "column": null } - }, - "10": { - "start": { "line": 42, "column": 4 }, - "end": { "line": 42, "column": null } - }, - "11": { - "start": { "line": 46, "column": 2 }, - "end": { "line": 55, "column": null } - }, - "12": { - "start": { "line": 47, "column": 4 }, - "end": { "line": 49, "column": null } - }, - "13": { - "start": { "line": 48, "column": 6 }, - "end": { "line": 48, "column": null } - }, - "14": { - "start": { "line": 52, "column": 4 }, - "end": { "line": 54, "column": null } - }, - "15": { - "start": { "line": 53, "column": 6 }, - "end": { "line": 53, "column": null } - }, - "16": { - "start": { "line": 65, "column": 27 }, - "end": { "line": 85, "column": null } - }, - "17": { - "start": { "line": 66, "column": 2 }, - "end": { "line": 68, "column": null } - }, - "18": { - "start": { "line": 67, "column": 4 }, - "end": { "line": 67, "column": null } - }, - "19": { - "start": { "line": 70, "column": 29 }, - "end": { "line": 73, "column": null } - }, - "20": { - "start": { "line": 76, "column": 2 }, - "end": { "line": 82, "column": null } - }, - "21": { - "start": { "line": 77, "column": 4 }, - "end": { "line": 81, "column": null } - }, - "22": { - "start": { "line": 78, "column": 6 }, - "end": { "line": 80, "column": null } - }, - "23": { - "start": { "line": 79, "column": 8 }, - "end": { "line": 79, "column": null } - }, - "24": { - "start": { "line": 84, "column": 2 }, - "end": { "line": 84, "column": null } - } - }, - "fnMap": { - "0": { - "name": "(anonymous_0)", - "decl": { - "start": { "line": 22, "column": 27 }, - "end": { "line": 22, "column": 28 } - }, - "loc": { - "start": { "line": 22, "column": 79 }, - "end": { "line": 56, "column": null } - }, - "line": 22 - }, - "1": { - "name": "(anonymous_1)", - "decl": { - "start": { "line": 52, "column": 26 }, - "end": { "line": 52, "column": 27 } - }, - "loc": { - "start": { "line": 52, "column": 41 }, - "end": { "line": 54, "column": 5 } - }, - "line": 52 - }, - "2": { - "name": "(anonymous_2)", - "decl": { - "start": { "line": 65, "column": 27 }, - "end": { "line": 65, "column": 28 } - }, - "loc": { - "start": { "line": 65, "column": 61 }, - "end": { "line": 85, "column": null } - }, - "line": 65 - }, - "3": { - "name": "(anonymous_3)", - "decl": { - "start": { "line": 76, "column": 29 }, - "end": { "line": 76, "column": 30 } - }, - "loc": { - "start": { "line": 76, "column": 42 }, - "end": { "line": 82, "column": 3 } - }, - "line": 76 - }, - "4": { - "name": "(anonymous_4)", - "decl": { - "start": { "line": 78, "column": 29 }, - "end": { "line": 78, "column": 30 } - }, - "loc": { - "start": { "line": 78, "column": 45 }, - "end": { "line": 80, "column": 7 } - }, - "line": 78 - } - }, - "branchMap": { - "0": { - "loc": { - "start": { "line": 24, "column": 2 }, - "end": { "line": 26, "column": null } - }, - "type": "if", - "locations": [ - { - "start": { "line": 24, "column": 2 }, - "end": { "line": 26, "column": null } - }, - { "start": {}, "end": {} } - ], - "line": 24 - }, - "1": { - "loc": { - "start": { "line": 29, "column": 2 }, - "end": { "line": 31, "column": null } - }, - "type": "if", - "locations": [ - { - "start": { "line": 29, "column": 2 }, - "end": { "line": 31, "column": null } - }, - { "start": {}, "end": {} } - ], - "line": 29 - }, - "2": { - "loc": { - "start": { "line": 34, "column": 2 }, - "end": { "line": 37, "column": null } - }, - "type": "if", - "locations": [ - { - "start": { "line": 34, "column": 2 }, - "end": { "line": 37, "column": null } - }, - { "start": {}, "end": {} } - ], - "line": 34 - }, - "3": { - "loc": { - "start": { "line": 40, "column": 2 }, - "end": { "line": 43, "column": null } - }, - "type": "if", - "locations": [ - { - "start": { "line": 40, "column": 2 }, - "end": { "line": 43, "column": null } - }, - { "start": {}, "end": {} } - ], - "line": 40 - }, - "4": { - "loc": { - "start": { "line": 46, "column": 2 }, - "end": { "line": 55, "column": null } - }, - "type": "if", - "locations": [ - { - "start": { "line": 46, "column": 2 }, - "end": { "line": 55, "column": null } - }, - { "start": {}, "end": {} } - ], - "line": 46 - }, - "5": { - "loc": { - "start": { "line": 47, "column": 4 }, - "end": { "line": 49, "column": null } - }, - "type": "if", - "locations": [ - { - "start": { "line": 47, "column": 4 }, - "end": { "line": 49, "column": null } - }, - { "start": {}, "end": {} } - ], - "line": 47 - }, - "6": { - "loc": { - "start": { "line": 66, "column": 2 }, - "end": { "line": 68, "column": null } - }, - "type": "if", - "locations": [ - { - "start": { "line": 66, "column": 2 }, - "end": { "line": 68, "column": null } - }, - { "start": {}, "end": {} } - ], - "line": 66 - }, - "7": { - "loc": { - "start": { "line": 77, "column": 4 }, - "end": { "line": 81, "column": null } - }, - "type": "if", - "locations": [ - { - "start": { "line": 77, "column": 4 }, - "end": { "line": 81, "column": null } - }, - { "start": {}, "end": {} } - ], - "line": 77 - } - }, - "s": { - "0": 1, - "1": 23, - "2": 1, - "3": 22, - "4": 2, - "5": 20, - "6": 3, - "7": 3, - "8": 17, - "9": 12, - "10": 12, - "11": 5, - "12": 5, - "13": 1, - "14": 5, - "15": 6, - "16": 1, - "17": 10, - "18": 1, - "19": 9, - "20": 9, - "21": 10, - "22": 10, - "23": 17, - "24": 9 - }, - "f": { "0": 23, "1": 6, "2": 10, "3": 10, "4": 17 }, - "b": { - "0": [1, 22], - "1": [2, 20], - "2": [3, 17], - "3": [12, 5], - "4": [5, 0], - "5": [1, 4], - "6": [1, 9], - "7": [10, 0] - }, - "meta": { - "lastBranch": 8, - "lastFunction": 5, - "lastStatement": 25, - "seen": { - "s:22:27:56:Infinity": 0, - "f:22:27:22:28": 0, - "b:24:2:26:Infinity:undefined:undefined:undefined:undefined": 0, - "s:24:2:26:Infinity": 1, - "s:25:4:25:Infinity": 2, - "b:29:2:31:Infinity:undefined:undefined:undefined:undefined": 1, - "s:29:2:31:Infinity": 3, - "s:30:4:30:Infinity": 4, - "b:34:2:37:Infinity:undefined:undefined:undefined:undefined": 2, - "s:34:2:37:Infinity": 5, - "s:35:4:35:Infinity": 6, - "s:36:4:36:Infinity": 7, - "b:40:2:43:Infinity:undefined:undefined:undefined:undefined": 3, - "s:40:2:43:Infinity": 8, - "s:41:4:41:Infinity": 9, - "s:42:4:42:Infinity": 10, - "b:46:2:55:Infinity:undefined:undefined:undefined:undefined": 4, - "s:46:2:55:Infinity": 11, - "b:47:4:49:Infinity:undefined:undefined:undefined:undefined": 5, - "s:47:4:49:Infinity": 12, - "s:48:6:48:Infinity": 13, - "s:52:4:54:Infinity": 14, - "f:52:26:52:27": 1, - "s:53:6:53:Infinity": 15, - "s:65:27:85:Infinity": 16, - "f:65:27:65:28": 2, - "b:66:2:68:Infinity:undefined:undefined:undefined:undefined": 6, - "s:66:2:68:Infinity": 17, - "s:67:4:67:Infinity": 18, - "s:70:29:73:Infinity": 19, - "s:76:2:82:Infinity": 20, - "f:76:29:76:30": 3, - "b:77:4:81:Infinity:undefined:undefined:undefined:undefined": 7, - "s:77:4:81:Infinity": 21, - "s:78:6:80:Infinity": 22, - "f:78:29:78:30": 4, - "s:79:8:79:Infinity": 23, - "s:84:2:84:Infinity": 24 - } - } - }, - "/Users/danielslovinsky/Documents/projects/alchemy-docs/docs/src/content-indexer/indexers/main.ts": { - "path": "/Users/danielslovinsky/Documents/projects/alchemy-docs/docs/src/content-indexer/indexers/main.ts", - "statementMap": { - "0": { - "start": { "line": 33, "column": 37 }, - "end": { "line": 75, "column": null } - }, - "1": { - "start": { "line": 36, "column": 2 }, - "end": { "line": 36, "column": null } - }, - "2": { - "start": { "line": 40, "column": 2 }, - "end": { "line": 53, "column": null } - }, - "3": { - "start": { "line": 41, "column": 19 }, - "end": { "line": 41, "column": null } - }, - "4": { - "start": { "line": 42, "column": 4 }, - "end": { "line": 44, "column": null } - }, - "5": { - "start": { "line": 43, "column": 6 }, - "end": { "line": 43, "column": null } - }, - "6": { - "start": { "line": 45, "column": 4 }, - "end": { "line": 45, "column": null } - }, - "7": { - "start": { "line": 47, "column": 24 }, - "end": { "line": 47, "column": null } - }, - "8": { - "start": { "line": 48, "column": 20 }, - "end": { "line": 48, "column": null } - }, - "9": { - "start": { "line": 49, "column": 4 }, - "end": { "line": 51, "column": null } - }, - "10": { - "start": { "line": 50, "column": 6 }, - "end": { "line": 50, "column": null } - }, - "11": { - "start": { "line": 52, "column": 4 }, - "end": { "line": 52, "column": null } - }, - "12": { - "start": { "line": 56, "column": 2 }, - "end": { "line": 56, "column": null } - }, - "13": { - "start": { "line": 57, "column": 8 }, - "end": { "line": 57, "column": null } - }, - "14": { - "start": { "line": 58, "column": 2 }, - "end": { "line": 60, "column": null } - }, - "15": { - "start": { "line": 63, "column": 2 }, - "end": { "line": 63, "column": null } - }, - "16": { - "start": { "line": 64, "column": 23 }, - "end": { "line": 64, "column": null } - }, - "17": { - "start": { "line": 67, "column": 2 }, - "end": { "line": 67, "column": null } - }, - "18": { - "start": { "line": 68, "column": 8 }, - "end": { "line": 68, "column": null } - }, - "19": { - "start": { "line": 70, "column": 2 }, - "end": { "line": 72, "column": null } - }, - "20": { - "start": { "line": 74, "column": 2 }, - "end": { "line": 74, "column": null } - } - }, - "fnMap": { - "0": { - "name": "(anonymous_0)", - "decl": { - "start": { "line": 33, "column": 37 }, - "end": { "line": 33, "column": null } - }, - "loc": { - "start": { "line": 35, "column": 29 }, - "end": { "line": 75, "column": null } - }, - "line": 35 - } - }, - "branchMap": { - "0": { - "loc": { - "start": { "line": 40, "column": 2 }, - "end": { "line": 53, "column": null } - }, - "type": "if", - "locations": [ - { - "start": { "line": 40, "column": 2 }, - "end": { "line": 53, "column": null } - }, - { - "start": { "line": 46, "column": 9 }, - "end": { "line": 53, "column": null } - } - ], - "line": 40 - }, - "1": { - "loc": { - "start": { "line": 42, "column": 4 }, - "end": { "line": 44, "column": null } - }, - "type": "if", - "locations": [ - { - "start": { "line": 42, "column": 4 }, - "end": { "line": 44, "column": null } - }, - { "start": {}, "end": {} } - ], - "line": 42 - }, - "2": { - "loc": { - "start": { "line": 49, "column": 4 }, - "end": { "line": 51, "column": null } - }, - "type": "if", - "locations": [ - { - "start": { "line": 49, "column": 4 }, - "end": { "line": 51, "column": null } - }, - { "start": {}, "end": {} } - ], - "line": 49 - } - }, - "s": { - "0": 1, - "1": 2, - "2": 2, - "3": 1, - "4": 1, - "5": 0, - "6": 1, - "7": 1, - "8": 1, - "9": 1, - "10": 0, - "11": 1, - "12": 2, - "13": 2, - "14": 2, - "15": 2, - "16": 2, - "17": 2, - "18": 2, - "19": 2, - "20": 2 - }, - "f": { "0": 2 }, - "b": { "0": [1, 1], "1": [0, 1], "2": [0, 1] }, - "meta": { - "lastBranch": 3, - "lastFunction": 1, - "lastStatement": 21, - "seen": { - "s:33:37:75:Infinity": 0, - "f:33:37:33:Infinity": 0, - "s:36:2:36:Infinity": 1, - "b:40:2:53:Infinity:46:9:53:Infinity": 0, - "s:40:2:53:Infinity": 2, - "s:41:19:41:Infinity": 3, - "b:42:4:44:Infinity:undefined:undefined:undefined:undefined": 1, - "s:42:4:44:Infinity": 4, - "s:43:6:43:Infinity": 5, - "s:45:4:45:Infinity": 6, - "s:47:24:47:Infinity": 7, - "s:48:20:48:Infinity": 8, - "b:49:4:51:Infinity:undefined:undefined:undefined:undefined": 2, - "s:49:4:51:Infinity": 9, - "s:50:6:50:Infinity": 10, - "s:52:4:52:Infinity": 11, - "s:56:2:56:Infinity": 12, - "s:57:8:57:Infinity": 13, - "s:58:2:60:Infinity": 14, - "s:63:2:63:Infinity": 15, - "s:64:23:64:Infinity": 16, - "s:67:2:67:Infinity": 17, - "s:68:8:68:Infinity": 18, - "s:70:2:72:Infinity": 19, - "s:74:2:74:Infinity": 20 - } - } - }, - "/Users/danielslovinsky/Documents/projects/alchemy-docs/docs/src/content-indexer/types/docsYaml.ts": { - "path": "/Users/danielslovinsky/Documents/projects/alchemy-docs/docs/src/content-indexer/types/docsYaml.ts", - "statementMap": { - "0": { - "start": { "line": 65, "column": 28 }, - "end": { "line": 67, "column": null } - }, - "1": { - "start": { "line": 66, "column": 2 }, - "end": { "line": 66, "column": null } - }, - "2": { - "start": { "line": 69, "column": 31 }, - "end": { "line": 75, "column": null } - }, - "3": { - "start": { "line": 72, "column": 2 }, - "end": { "line": 73, "column": null } - }, - "4": { - "start": { "line": 77, "column": 28 }, - "end": { "line": 79, "column": null } - }, - "5": { - "start": { "line": 78, "column": 2 }, - "end": { "line": 78, "column": null } - }, - "6": { - "start": { "line": 81, "column": 27 }, - "end": { "line": 85, "column": null } - }, - "7": { - "start": { "line": 82, "column": 2 }, - "end": { "line": 83, "column": null } - }, - "8": { - "start": { "line": 87, "column": 33 }, - "end": { "line": 91, "column": null } - }, - "9": { - "start": { "line": 90, "column": 2 }, - "end": { "line": 90, "column": null } - } - }, - "fnMap": { - "0": { - "name": "(anonymous_0)", - "decl": { - "start": { "line": 65, "column": 28 }, - "end": { "line": 65, "column": 29 } - }, - "loc": { - "start": { "line": 65, "column": 74 }, - "end": { "line": 67, "column": null } - }, - "line": 65 - }, - "1": { - "name": "(anonymous_1)", - "decl": { - "start": { "line": 69, "column": 31 }, - "end": { "line": 69, "column": null } - }, - "loc": { - "start": { "line": 71, "column": 28 }, - "end": { "line": 75, "column": null } - }, - "line": 71 - }, - "2": { - "name": "(anonymous_2)", - "decl": { - "start": { "line": 77, "column": 28 }, - "end": { "line": 77, "column": 29 } - }, - "loc": { - "start": { "line": 77, "column": 74 }, - "end": { "line": 79, "column": null } - }, - "line": 77 - }, - "3": { - "name": "(anonymous_3)", - "decl": { - "start": { "line": 81, "column": 27 }, - "end": { "line": 81, "column": 28 } - }, - "loc": { - "start": { "line": 81, "column": 72 }, - "end": { "line": 85, "column": null } - }, - "line": 81 - }, - "4": { - "name": "(anonymous_4)", - "decl": { - "start": { "line": 87, "column": 33 }, - "end": { "line": 87, "column": null } - }, - "loc": { - "start": { "line": 89, "column": 30 }, - "end": { "line": 91, "column": null } - }, - "line": 89 - } - }, - "branchMap": { - "0": { - "loc": { - "start": { "line": 66, "column": 9 }, - "end": { "line": 66, "column": null } - }, - "type": "binary-expr", - "locations": [ - { - "start": { "line": 66, "column": 9 }, - "end": { "line": 66, "column": 17 } - }, - { - "start": { "line": 66, "column": 17 }, - "end": { "line": 66, "column": 45 } - }, - { - "start": { "line": 66, "column": 45 }, - "end": { "line": 66, "column": 63 } - }, - { - "start": { "line": 66, "column": 63 }, - "end": { "line": 66, "column": null } - } - ], - "line": 66 - }, - "1": { - "loc": { - "start": { "line": 73, "column": 4 }, - "end": { "line": 73, "column": null } - }, - "type": "binary-expr", - "locations": [ - { - "start": { "line": 73, "column": 4 }, - "end": { "line": 73, "column": 12 } - }, - { - "start": { "line": 73, "column": 12 }, - "end": { "line": 73, "column": 40 } - }, - { - "start": { "line": 73, "column": 40 }, - "end": { "line": 73, "column": 61 } - }, - { - "start": { "line": 73, "column": 61 }, - "end": { "line": 73, "column": null } - } - ], - "line": 73 - }, - "2": { - "loc": { - "start": { "line": 78, "column": 9 }, - "end": { "line": 78, "column": null } - }, - "type": "binary-expr", - "locations": [ - { - "start": { "line": 78, "column": 9 }, - "end": { "line": 78, "column": 17 } - }, - { - "start": { "line": 78, "column": 17 }, - "end": { "line": 78, "column": 45 } - }, - { - "start": { "line": 78, "column": 45 }, - "end": { "line": 78, "column": 63 } - }, - { - "start": { "line": 78, "column": 63 }, - "end": { "line": 78, "column": null } - } - ], - "line": 78 - }, - "3": { - "loc": { - "start": { "line": 83, "column": 4 }, - "end": { "line": 83, "column": null } - }, - "type": "binary-expr", - "locations": [ - { - "start": { "line": 83, "column": 4 }, - "end": { "line": 83, "column": 12 } - }, - { - "start": { "line": 83, "column": 12 }, - "end": { "line": 83, "column": 40 } - }, - { - "start": { "line": 83, "column": 40 }, - "end": { "line": 83, "column": 57 } - }, - { - "start": { "line": 83, "column": 57 }, - "end": { "line": 83, "column": null } - } - ], - "line": 83 - }, - "4": { - "loc": { - "start": { "line": 90, "column": 9 }, - "end": { "line": 90, "column": null } - }, - "type": "binary-expr", - "locations": [ - { - "start": { "line": 90, "column": 9 }, - "end": { "line": 90, "column": 17 } - }, - { - "start": { "line": 90, "column": 17 }, - "end": { "line": 90, "column": 45 } - }, - { - "start": { "line": 90, "column": 45 }, - "end": { "line": 90, "column": null } - } - ], - "line": 90 - } - }, - "s": { - "0": 3, - "1": 34, - "2": 3, - "3": 9, - "4": 3, - "5": 41, - "6": 3, - "7": 21, - "8": 3, - "9": 43 - }, - "f": { "0": 34, "1": 9, "2": 41, "3": 21, "4": 43 }, - "b": { - "0": [34, 34, 34, 25], - "1": [9, 9, 9, 8], - "2": [41, 41, 41, 4], - "3": [21, 21, 21, 4], - "4": [43, 43, 43] - }, - "meta": { - "lastBranch": 5, - "lastFunction": 5, - "lastStatement": 10, - "seen": { - "s:65:28:67:Infinity": 0, - "f:65:28:65:29": 0, - "s:66:2:66:Infinity": 1, - "b:66:9:66:17:66:17:66:45:66:45:66:63:66:63:66:Infinity": 0, - "s:69:31:75:Infinity": 2, - "f:69:31:69:Infinity": 1, - "s:72:2:73:Infinity": 3, - "b:73:4:73:12:73:12:73:40:73:40:73:61:73:61:73:Infinity": 1, - "s:77:28:79:Infinity": 4, - "f:77:28:77:29": 2, - "s:78:2:78:Infinity": 5, - "b:78:9:78:17:78:17:78:45:78:45:78:63:78:63:78:Infinity": 2, - "s:81:27:85:Infinity": 6, - "f:81:27:81:28": 3, - "s:82:2:83:Infinity": 7, - "b:83:4:83:12:83:12:83:40:83:40:83:57:83:57:83:Infinity": 3, - "s:87:33:91:Infinity": 8, - "f:87:33:87:Infinity": 4, - "s:90:2:90:Infinity": 9, - "b:90:9:90:17:90:17:90:45:90:45:90:Infinity": 4 - } - } - }, - "/Users/danielslovinsky/Documents/projects/alchemy-docs/docs/src/content-indexer/uploaders/algolia.ts": { - "path": "/Users/danielslovinsky/Documents/projects/alchemy-docs/docs/src/content-indexer/uploaders/algolia.ts", - "statementMap": { - "0": { - "start": { "line": 16, "column": 23 }, - "end": { "line": 29, "column": null } - }, - "1": { - "start": { "line": 21, "column": 16 }, - "end": { "line": 21, "column": null } - }, - "2": { - "start": { "line": 24, "column": 2 }, - "end": { "line": 26, "column": null } - }, - "3": { - "start": { "line": 25, "column": 4 }, - "end": { "line": 25, "column": null } - }, - "4": { - "start": { "line": 28, "column": 2 }, - "end": { "line": 28, "column": null } - }, - "5": { - "start": { "line": 46, "column": 31 }, - "end": { "line": 129, "column": null } - }, - "6": { - "start": { "line": 53, "column": 16 }, - "end": { "line": 53, "column": null } - }, - "7": { - "start": { "line": 54, "column": 19 }, - "end": { "line": 54, "column": null } - }, - "8": { - "start": { "line": 55, "column": 19 }, - "end": { "line": 55, "column": null } - }, - "9": { - "start": { "line": 57, "column": 2 }, - "end": { "line": 63, "column": null } - }, - "10": { - "start": { "line": 58, "column": 4 }, - "end": { "line": 58, "column": null } - }, - "11": { - "start": { "line": 59, "column": 4 }, - "end": { "line": 61, "column": null } - }, - "12": { - "start": { "line": 62, "column": 4 }, - "end": { "line": 62, "column": null } - }, - "13": { - "start": { "line": 65, "column": 2 }, - "end": { "line": 70, "column": null } - }, - "14": { - "start": { "line": 66, "column": 4 }, - "end": { "line": 68, "column": null } - }, - "15": { - "start": { "line": 69, "column": 4 }, - "end": { "line": 69, "column": null } - }, - "16": { - "start": { "line": 72, "column": 26 }, - "end": { "line": 76, "column": null } - }, - "17": { - "start": { "line": 78, "column": 8 }, - "end": { "line": 78, "column": null } - }, - "18": { - "start": { "line": 79, "column": 24 }, - "end": { "line": 79, "column": null } - }, - "19": { - "start": { "line": 81, "column": 2 }, - "end": { "line": 83, "column": null } - }, - "20": { - "start": { "line": 86, "column": 27 }, - "end": { "line": 86, "column": null } - }, - "21": { - "start": { "line": 88, "column": 2 }, - "end": { "line": 128, "column": null } - }, - "22": { - "start": { "line": 90, "column": 4 }, - "end": { "line": 93, "column": null } - }, - "23": { - "start": { "line": 95, "column": 4 }, - "end": { "line": 95, "column": null } - }, - "24": { - "start": { "line": 98, "column": 4 }, - "end": { "line": 112, "column": null } - }, - "25": { - "start": { "line": 99, "column": 6 }, - "end": { "line": 106, "column": null } - }, - "26": { - "start": { "line": 107, "column": 6 }, - "end": { "line": 107, "column": null } - }, - "27": { - "start": { "line": 109, "column": 6 }, - "end": { "line": 111, "column": null } - }, - "28": { - "start": { "line": 115, "column": 4 }, - "end": { "line": 115, "column": null } - }, - "29": { - "start": { "line": 116, "column": 4 }, - "end": { "line": 122, "column": null } - }, - "30": { - "start": { "line": 124, "column": 4 }, - "end": { "line": 124, "column": null } - }, - "31": { - "start": { "line": 126, "column": 4 }, - "end": { "line": 126, "column": null } - }, - "32": { - "start": { "line": 127, "column": 4 }, - "end": { "line": 127, "column": null } - } - }, - "fnMap": { - "0": { - "name": "(anonymous_0)", - "decl": { - "start": { "line": 16, "column": 23 }, - "end": { "line": 16, "column": null } - }, - "loc": { - "start": { "line": 20, "column": 13 }, - "end": { "line": 29, "column": null } - }, - "line": 20 - }, - "1": { - "name": "(anonymous_1)", - "decl": { - "start": { "line": 46, "column": 31 }, - "end": { "line": 46, "column": null } - }, - "loc": { - "start": { "line": 52, "column": 20 }, - "end": { "line": 129, "column": null } - }, - "line": 52 - } - }, - "branchMap": { - "0": { - "loc": { - "start": { "line": 24, "column": 2 }, - "end": { "line": 26, "column": null } - }, - "type": "if", - "locations": [ - { - "start": { "line": 24, "column": 2 }, - "end": { "line": 26, "column": null } - }, - { "start": {}, "end": {} } - ], - "line": 24 - }, - "1": { - "loc": { - "start": { "line": 57, "column": 2 }, - "end": { "line": 63, "column": null } - }, - "type": "if", - "locations": [ - { - "start": { "line": 57, "column": 2 }, - "end": { "line": 63, "column": null } - }, - { "start": {}, "end": {} } - ], - "line": 57 - }, - "2": { - "loc": { - "start": { "line": 57, "column": 6 }, - "end": { "line": 57, "column": 27 } - }, - "type": "binary-expr", - "locations": [ - { - "start": { "line": 57, "column": 6 }, - "end": { "line": 57, "column": 16 } - }, - { - "start": { "line": 57, "column": 16 }, - "end": { "line": 57, "column": 27 } - } - ], - "line": 57 - }, - "3": { - "loc": { - "start": { "line": 65, "column": 2 }, - "end": { "line": 70, "column": null } - }, - "type": "if", - "locations": [ - { - "start": { "line": 65, "column": 2 }, - "end": { "line": 70, "column": null } - }, - { "start": {}, "end": {} } - ], - "line": 65 - } - }, - "s": { - "0": 1, - "1": 0, - "2": 0, - "3": 0, - "4": 0, - "5": 1, - "6": 4, - "7": 4, - "8": 4, - "9": 4, - "10": 2, - "11": 2, - "12": 2, - "13": 2, - "14": 2, - "15": 2, - "16": 0, - "17": 0, - "18": 0, - "19": 0, - "20": 0, - "21": 0, - "22": 0, - "23": 0, - "24": 0, - "25": 0, - "26": 0, - "27": 0, - "28": 0, - "29": 0, - "30": 0, - "31": 0, - "32": 0 - }, - "f": { "0": 0, "1": 4 }, - "b": { "0": [0, 0], "1": [2, 2], "2": [4, 3], "3": [2, 0] }, - "meta": { - "lastBranch": 4, - "lastFunction": 2, - "lastStatement": 33, - "seen": { - "s:16:23:29:Infinity": 0, - "f:16:23:16:Infinity": 0, - "s:21:16:21:Infinity": 1, - "b:24:2:26:Infinity:undefined:undefined:undefined:undefined": 0, - "s:24:2:26:Infinity": 2, - "s:25:4:25:Infinity": 3, - "s:28:2:28:Infinity": 4, - "s:46:31:129:Infinity": 5, - "f:46:31:46:Infinity": 1, - "s:53:16:53:Infinity": 6, - "s:54:19:54:Infinity": 7, - "s:55:19:55:Infinity": 8, - "b:57:2:63:Infinity:undefined:undefined:undefined:undefined": 1, - "s:57:2:63:Infinity": 9, - "b:57:6:57:16:57:16:57:27": 2, - "s:58:4:58:Infinity": 10, - "s:59:4:61:Infinity": 11, - "s:62:4:62:Infinity": 12, - "b:65:2:70:Infinity:undefined:undefined:undefined:undefined": 3, - "s:65:2:70:Infinity": 13, - "s:66:4:68:Infinity": 14, - "s:69:4:69:Infinity": 15, - "s:72:26:76:Infinity": 16, - "s:78:8:78:Infinity": 17, - "s:79:24:79:Infinity": 18, - "s:81:2:83:Infinity": 19, - "s:86:27:86:Infinity": 20, - "s:88:2:128:Infinity": 21, - "s:90:4:93:Infinity": 22, - "s:95:4:95:Infinity": 23, - "s:98:4:112:Infinity": 24, - "s:99:6:106:Infinity": 25, - "s:107:6:107:Infinity": 26, - "s:109:6:111:Infinity": 27, - "s:115:4:115:Infinity": 28, - "s:116:4:122:Infinity": 29, - "s:124:4:124:Infinity": 30, - "s:126:4:126:Infinity": 31, - "s:127:4:127:Infinity": 32 - } - } - }, - "/Users/danielslovinsky/Documents/projects/alchemy-docs/docs/src/content-indexer/uploaders/redis.ts": { - "path": "/Users/danielslovinsky/Documents/projects/alchemy-docs/docs/src/content-indexer/uploaders/redis.ts", - "statementMap": { - "0": { - "start": { "line": 10, "column": 19 }, - "end": { "line": 18, "column": null } - }, - "1": { - "start": { "line": 11, "column": 2 }, - "end": { "line": 17, "column": null } - }, - "2": { - "start": { "line": 13, "column": 6 }, - "end": { "line": 15, "column": null } - }, - "3": { - "start": { "line": 16, "column": 4 }, - "end": { "line": 16, "column": null } - }, - "4": { - "start": { "line": 29, "column": 28 }, - "end": { "line": 93, "column": null } - }, - "5": { - "start": { "line": 37, "column": 8 }, - "end": { "line": 37, "column": null } - }, - "6": { - "start": { "line": 40, "column": 23 }, - "end": { "line": 40, "column": null } - }, - "7": { - "start": { "line": 41, "column": 27 }, - "end": { "line": 47, "column": null } - }, - "8": { - "start": { "line": 44, "column": 6 }, - "end": { "line": 46, "column": null } - }, - "9": { - "start": { "line": 50, "column": 41 }, - "end": { "line": 50, "column": null } - }, - "10": { - "start": { "line": 52, "column": 2 }, - "end": { "line": 90, "column": null } - }, - "11": { - "start": { "line": 54, "column": 23 }, - "end": { "line": 54, "column": null } - }, - "12": { - "start": { "line": 55, "column": 25 }, - "end": { "line": 55, "column": null } - }, - "13": { - "start": { "line": 57, "column": 10 }, - "end": { "line": 61, "column": null } - }, - "14": { - "start": { "line": 63, "column": 4 }, - "end": { "line": 69, "column": null } - }, - "15": { - "start": { "line": 65, "column": 8 }, - "end": { "line": 67, "column": null } - }, - "16": { - "start": { "line": 70, "column": 2 }, - "end": { "line": 90, "column": null } - }, - "17": { - "start": { "line": 72, "column": 4 }, - "end": { "line": 89, "column": null } - }, - "18": { - "start": { "line": 74, "column": 25 }, - "end": { "line": 74, "column": null } - }, - "19": { - "start": { "line": 75, "column": 24 }, - "end": { "line": 75, "column": null } - }, - "20": { - "start": { "line": 78, "column": 8 }, - "end": { "line": 81, "column": null } - }, - "21": { - "start": { "line": 79, "column": 31 }, - "end": { "line": 79, "column": null } - }, - "22": { - "start": { "line": 80, "column": 10 }, - "end": { "line": 80, "column": null } - }, - "23": { - "start": { "line": 83, "column": 26 }, - "end": { "line": 83, "column": null } - }, - "24": { - "start": { "line": 84, "column": 8 }, - "end": { "line": 84, "column": null } - }, - "25": { - "start": { "line": 85, "column": 8 }, - "end": { "line": 87, "column": null } - }, - "26": { - "start": { "line": 92, "column": 2 }, - "end": { "line": 92, "column": null } - } - }, - "fnMap": { - "0": { - "name": "(anonymous_0)", - "decl": { - "start": { "line": 10, "column": 19 }, - "end": { "line": 10, "column": 20 } - }, - "loc": { - "start": { "line": 10, "column": 54 }, - "end": { "line": 18, "column": null } - }, - "line": 10 - }, - "1": { - "name": "(anonymous_1)", - "decl": { - "start": { "line": 11, "column": 22 }, - "end": { "line": 11, "column": 23 } - }, - "loc": { - "start": { "line": 11, "column": 37 }, - "end": { "line": 17, "column": 5 } - }, - "line": 11 - }, - "2": { - "name": "(anonymous_2)", - "decl": { - "start": { "line": 29, "column": 28 }, - "end": { "line": 29, "column": null } - }, - "loc": { - "start": { "line": 36, "column": 20 }, - "end": { "line": 93, "column": null } - }, - "line": 36 - }, - "3": { - "name": "(anonymous_3)", - "decl": { - "start": { "line": 43, "column": 10 }, - "end": { "line": 43, "column": 16 } - }, - "loc": { - "start": { "line": 43, "column": 16 }, - "end": { "line": 47, "column": 5 } - }, - "line": 43 - }, - "4": { - "name": "(anonymous_4)", - "decl": { - "start": { "line": 64, "column": 70 }, - "end": { "line": 64, "column": 76 } - }, - "loc": { - "start": { "line": 64, "column": 76 }, - "end": { "line": 68, "column": 7 } - }, - "line": 64 - }, - "5": { - "name": "(anonymous_5)", - "decl": { - "start": { "line": 73, "column": 6 }, - "end": { "line": 73, "column": 13 } - }, - "loc": { - "start": { "line": 73, "column": 32 }, - "end": { "line": 88, "column": null } - }, - "line": 73 - } - }, - "branchMap": { - "0": { - "loc": { - "start": { "line": 13, "column": 6 }, - "end": { "line": 15, "column": null } - }, - "type": "cond-expr", - "locations": [ - { - "start": { "line": 14, "column": 10 }, - "end": { "line": 14, "column": null } - }, - { - "start": { "line": 15, "column": 10 }, - "end": { "line": 15, "column": null } - } - ], - "line": 13 - }, - "1": { - "loc": { - "start": { "line": 13, "column": 6 }, - "end": { "line": 13, "column": null } - }, - "type": "binary-expr", - "locations": [ - { - "start": { "line": 13, "column": 6 }, - "end": { "line": 13, "column": 33 } - }, - { - "start": { "line": 13, "column": 33 }, - "end": { "line": 13, "column": null } - } - ], - "line": 13 - }, - "2": { - "loc": { - "start": { "line": 52, "column": 2 }, - "end": { "line": 90, "column": null } - }, - "type": "if", - "locations": [ - { - "start": { "line": 52, "column": 2 }, - "end": { "line": 90, "column": null } - }, - { - "start": { "line": 70, "column": 2 }, - "end": { "line": 90, "column": null } - } - ], - "line": 52 - }, - "3": { - "loc": { - "start": { "line": 52, "column": 6 }, - "end": { "line": 52, "column": 65 } - }, - "type": "binary-expr", - "locations": [ - { - "start": { "line": 52, "column": 6 }, - "end": { "line": 52, "column": 39 } - }, - { - "start": { "line": 52, "column": 39 }, - "end": { "line": 52, "column": 65 } - } - ], - "line": 52 - }, - "4": { - "loc": { - "start": { "line": 70, "column": 2 }, - "end": { "line": 90, "column": null } - }, - "type": "if", - "locations": [ - { - "start": { "line": 70, "column": 2 }, - "end": { "line": 90, "column": null } - }, - { "start": {}, "end": {} } - ], - "line": 70 - }, - "5": { - "loc": { - "start": { "line": 78, "column": 8 }, - "end": { "line": 81, "column": null } - }, - "type": "if", - "locations": [ - { - "start": { "line": 78, "column": 8 }, - "end": { "line": 81, "column": null } - }, - { "start": {}, "end": {} } - ], - "line": 78 - }, - "6": { - "loc": { - "start": { "line": 78, "column": 12 }, - "end": { "line": 78, "column": 65 } - }, - "type": "binary-expr", - "locations": [ - { - "start": { "line": 78, "column": 12 }, - "end": { "line": 78, "column": 33 } - }, - { - "start": { "line": 78, "column": 33 }, - "end": { "line": 78, "column": 65 } - } - ], - "line": 78 - } - }, - "s": { - "0": 1, - "1": 5, - "2": 5, - "3": 5, - "4": 1, - "5": 5, - "6": 5, - "7": 5, - "8": 5, - "9": 5, - "10": 5, - "11": 0, - "12": 0, - "13": 0, - "14": 0, - "15": 0, - "16": 5, - "17": 3, - "18": 5, - "19": 5, - "20": 5, - "21": 0, - "22": 0, - "23": 5, - "24": 5, - "25": 5, - "26": 5 - }, - "f": { "0": 5, "1": 5, "2": 5, "3": 5, "4": 0, "5": 5 }, - "b": { - "0": [0, 5], - "1": [5, 5], - "2": [0, 5], - "3": [5, 1], - "4": [3, 2], - "5": [0, 5], - "6": [5, 0] - }, - "meta": { - "lastBranch": 7, - "lastFunction": 6, - "lastStatement": 27, - "seen": { - "s:10:19:18:Infinity": 0, - "f:10:19:10:20": 0, - "s:11:2:17:Infinity": 1, - "f:11:22:11:23": 1, - "s:13:6:15:Infinity": 2, - "b:14:10:14:Infinity:15:10:15:Infinity": 0, - "b:13:6:13:33:13:33:13:Infinity": 1, - "s:16:4:16:Infinity": 3, - "s:29:28:93:Infinity": 4, - "f:29:28:29:Infinity": 2, - "s:37:8:37:Infinity": 5, - "s:40:23:40:Infinity": 6, - "s:41:27:47:Infinity": 7, - "f:43:10:43:16": 3, - "s:44:6:46:Infinity": 8, - "s:50:41:50:Infinity": 9, - "b:52:2:90:Infinity:70:2:90:Infinity": 2, - "s:52:2:90:Infinity": 10, - "b:52:6:52:39:52:39:52:65": 3, - "s:54:23:54:Infinity": 11, - "s:55:25:55:Infinity": 12, - "s:57:10:61:Infinity": 13, - "s:63:4:69:Infinity": 14, - "f:64:70:64:76": 4, - "s:65:8:67:Infinity": 15, - "b:70:2:90:Infinity:undefined:undefined:undefined:undefined": 4, - "s:70:2:90:Infinity": 16, - "s:72:4:89:Infinity": 17, - "f:73:6:73:13": 5, - "s:74:25:74:Infinity": 18, - "s:75:24:75:Infinity": 19, - "b:78:8:81:Infinity:undefined:undefined:undefined:undefined": 5, - "s:78:8:81:Infinity": 20, - "b:78:12:78:33:78:33:78:65": 6, - "s:79:31:79:Infinity": 21, - "s:80:10:80:Infinity": 22, - "s:83:26:83:Infinity": 23, - "s:84:8:84:Infinity": 24, - "s:85:8:87:Infinity": 25, - "s:92:2:92:Infinity": 26 - } - } - }, - "/Users/danielslovinsky/Documents/projects/alchemy-docs/docs/src/content-indexer/utils/filesystem.ts": { - "path": "/Users/danielslovinsky/Documents/projects/alchemy-docs/docs/src/content-indexer/utils/filesystem.ts", - "statementMap": { - "0": { - "start": { "line": 11, "column": 29 }, - "end": { "line": 21, "column": null } - }, - "1": { - "start": { "line": 14, "column": 2 }, - "end": { "line": 20, "column": null } - }, - "2": { - "start": { "line": 15, "column": 20 }, - "end": { "line": 15, "column": null } - }, - "3": { - "start": { "line": 16, "column": 4 }, - "end": { "line": 16, "column": null } - }, - "4": { - "start": { "line": 18, "column": 4 }, - "end": { "line": 18, "column": null } - }, - "5": { - "start": { "line": 19, "column": 4 }, - "end": { "line": 19, "column": null } - }, - "6": { - "start": { "line": 26, "column": 32 }, - "end": { "line": 43, "column": null } - }, - "7": { - "start": { "line": 29, "column": 2 }, - "end": { "line": 42, "column": null } - }, - "8": { - "start": { "line": 30, "column": 24 }, - "end": { "line": 30, "column": null } - }, - "9": { - "start": { "line": 31, "column": 20 }, - "end": { "line": 31, "column": null } - }, - "10": { - "start": { "line": 33, "column": 4 }, - "end": { "line": 35, "column": null } - }, - "11": { - "start": { "line": 34, "column": 6 }, - "end": { "line": 34, "column": null } - }, - "12": { - "start": { "line": 37, "column": 20 }, - "end": { "line": 37, "column": null } - }, - "13": { - "start": { "line": 38, "column": 4 }, - "end": { "line": 38, "column": null } - }, - "14": { - "start": { "line": 40, "column": 4 }, - "end": { "line": 40, "column": null } - }, - "15": { - "start": { "line": 41, "column": 4 }, - "end": { "line": 41, "column": null } - }, - "16": { - "start": { "line": 48, "column": 32 }, - "end": { "line": 71, "column": null } - }, - "17": { - "start": { "line": 54, "column": 2 }, - "end": { "line": 70, "column": null } - }, - "18": { - "start": { "line": 55, "column": 24 }, - "end": { "line": 55, "column": null } - }, - "19": { - "start": { "line": 57, "column": 4 }, - "end": { "line": 59, "column": null } - }, - "20": { - "start": { "line": 58, "column": 6 }, - "end": { "line": 58, "column": null } - }, - "21": { - "start": { "line": 61, "column": 26 }, - "end": { "line": 61, "column": null } - }, - "22": { - "start": { "line": 63, "column": 4 }, - "end": { "line": 66, "column": null } - }, - "23": { - "start": { "line": 68, "column": 4 }, - "end": { "line": 68, "column": null } - }, - "24": { - "start": { "line": 69, "column": 4 }, - "end": { "line": 69, "column": null } - } - }, - "fnMap": { - "0": { - "name": "(anonymous_0)", - "decl": { - "start": { "line": 11, "column": 29 }, - "end": { "line": 11, "column": null } - }, - "loc": { - "start": { "line": 13, "column": 29 }, - "end": { "line": 21, "column": null } - }, - "line": 13 - }, - "1": { - "name": "(anonymous_1)", - "decl": { - "start": { "line": 26, "column": 32 }, - "end": { "line": 26, "column": null } - }, - "loc": { - "start": { "line": 28, "column": 30 }, - "end": { "line": 43, "column": null } - }, - "line": 28 - }, - "2": { - "name": "(anonymous_2)", - "decl": { - "start": { "line": 48, "column": 32 }, - "end": { "line": 48, "column": null } - }, - "loc": { - "start": { "line": 53, "column": 13 }, - "end": { "line": 71, "column": null } - }, - "line": 53 - } - }, - "branchMap": { - "0": { - "loc": { - "start": { "line": 33, "column": 4 }, - "end": { "line": 35, "column": null } - }, - "type": "if", - "locations": [ - { - "start": { "line": 33, "column": 4 }, - "end": { "line": 35, "column": null } - }, - { "start": {}, "end": {} } - ], - "line": 33 - }, - "1": { - "loc": { - "start": { "line": 57, "column": 4 }, - "end": { "line": 59, "column": null } - }, - "type": "if", - "locations": [ - { - "start": { "line": 57, "column": 4 }, - "end": { "line": 59, "column": null } - }, - { "start": {}, "end": {} } - ], - "line": 57 - } - }, - "s": { - "0": 2, - "1": 0, - "2": 0, - "3": 0, - "4": 0, - "5": 0, - "6": 2, - "7": 0, - "8": 0, - "9": 0, - "10": 0, - "11": 0, - "12": 0, - "13": 0, - "14": 0, - "15": 0, - "16": 2, - "17": 0, - "18": 0, - "19": 0, - "20": 0, - "21": 0, - "22": 0, - "23": 0, - "24": 0 - }, - "f": { "0": 0, "1": 0, "2": 0 }, - "b": { "0": [0, 0], "1": [0, 0] }, - "meta": { - "lastBranch": 2, - "lastFunction": 3, - "lastStatement": 25, - "seen": { - "s:11:29:21:Infinity": 0, - "f:11:29:11:Infinity": 0, - "s:14:2:20:Infinity": 1, - "s:15:20:15:Infinity": 2, - "s:16:4:16:Infinity": 3, - "s:18:4:18:Infinity": 4, - "s:19:4:19:Infinity": 5, - "s:26:32:43:Infinity": 6, - "f:26:32:26:Infinity": 1, - "s:29:2:42:Infinity": 7, - "s:30:24:30:Infinity": 8, - "s:31:20:31:Infinity": 9, - "b:33:4:35:Infinity:undefined:undefined:undefined:undefined": 0, - "s:33:4:35:Infinity": 10, - "s:34:6:34:Infinity": 11, - "s:37:20:37:Infinity": 12, - "s:38:4:38:Infinity": 13, - "s:40:4:40:Infinity": 14, - "s:41:4:41:Infinity": 15, - "s:48:32:71:Infinity": 16, - "f:48:32:48:Infinity": 2, - "s:54:2:70:Infinity": 17, - "s:55:24:55:Infinity": 18, - "b:57:4:59:Infinity:undefined:undefined:undefined:undefined": 1, - "s:57:4:59:Infinity": 19, - "s:58:6:58:Infinity": 20, - "s:61:26:61:Infinity": 21, - "s:63:4:66:Infinity": 22, - "s:68:4:68:Infinity": 23, - "s:69:4:69:Infinity": 24 - } - } - }, - "/Users/danielslovinsky/Documents/projects/alchemy-docs/docs/src/content-indexer/utils/github.ts": { - "path": "/Users/danielslovinsky/Documents/projects/alchemy-docs/docs/src/content-indexer/utils/github.ts", - "statementMap": { - "0": { - "start": { "line": 22, "column": 37 }, - "end": { "line": 27, "column": null } - }, - "1": { - "start": { "line": 29, "column": 39 }, - "end": { "line": 35, "column": null } - }, - "2": { - "start": { "line": 37, "column": 16 }, - "end": { "line": 39, "column": null } - }, - "3": { - "start": { "line": 42, "column": 2 }, - "end": { "line": 42, "column": null } - }, - "4": { - "start": { "line": 52, "column": 2 }, - "end": { "line": 71, "column": null } - }, - "5": { - "start": { "line": 53, "column": 21 }, - "end": { "line": 61, "column": null } - }, - "6": { - "start": { "line": 64, "column": 4 }, - "end": { "line": 64, "column": null } - }, - "7": { - "start": { "line": 66, "column": 4 }, - "end": { "line": 68, "column": null } - }, - "8": { - "start": { "line": 67, "column": 6 }, - "end": { "line": 67, "column": null } - }, - "9": { - "start": { "line": 69, "column": 4 }, - "end": { "line": 69, "column": null } - }, - "10": { - "start": { "line": 70, "column": 4 }, - "end": { "line": 70, "column": null } - }, - "11": { - "start": { "line": 81, "column": 2 }, - "end": { "line": 101, "column": null } - }, - "12": { - "start": { "line": 82, "column": 21 }, - "end": { "line": 87, "column": null } - }, - "13": { - "start": { "line": 90, "column": 4 }, - "end": { "line": 92, "column": null } - }, - "14": { - "start": { "line": 91, "column": 6 }, - "end": { "line": 91, "column": null } - }, - "15": { - "start": { "line": 94, "column": 4 }, - "end": { "line": 94, "column": null } - }, - "16": { - "start": { "line": 96, "column": 4 }, - "end": { "line": 99, "column": null } - }, - "17": { - "start": { "line": 100, "column": 4 }, - "end": { "line": 100, "column": null } - }, - "18": { - "start": { "line": 112, "column": 18 }, - "end": { "line": 112, "column": null } - }, - "19": { - "start": { "line": 116, "column": 2 }, - "end": { "line": 136, "column": null } - }, - "20": { - "start": { "line": 116, "column": 15 }, - "end": { "line": 116, "column": 18 } - }, - "21": { - "start": { "line": 117, "column": 18 }, - "end": { "line": 117, "column": null } - }, - "22": { - "start": { "line": 119, "column": 25 }, - "end": { "line": 124, "column": null } - }, - "23": { - "start": { "line": 121, "column": 24 }, - "end": { "line": 121, "column": null } - }, - "24": { - "start": { "line": 122, "column": 8 }, - "end": { "line": 122, "column": null } - }, - "25": { - "start": { "line": 126, "column": 4 }, - "end": { "line": 130, "column": null } - }, - "26": { - "start": { "line": 127, "column": 6 }, - "end": { "line": 129, "column": null } - }, - "27": { - "start": { "line": 128, "column": 8 }, - "end": { "line": 128, "column": null } - }, - "28": { - "start": { "line": 133, "column": 4 }, - "end": { "line": 135, "column": null } - }, - "29": { - "start": { "line": 134, "column": 6 }, - "end": { "line": 134, "column": null } - }, - "30": { - "start": { "line": 134, "column": 37 }, - "end": { "line": 134, "column": 61 } - }, - "31": { - "start": { "line": 138, "column": 2 }, - "end": { "line": 138, "column": null } - }, - "32": { - "start": { "line": 148, "column": 2 }, - "end": { "line": 163, "column": null } - }, - "33": { - "start": { "line": 149, "column": 4 }, - "end": { "line": 154, "column": null } - }, - "34": { - "start": { "line": 155, "column": 4 }, - "end": { "line": 155, "column": null } - }, - "35": { - "start": { "line": 157, "column": 4 }, - "end": { "line": 159, "column": null } - }, - "36": { - "start": { "line": 158, "column": 6 }, - "end": { "line": 158, "column": null } - }, - "37": { - "start": { "line": 161, "column": 4 }, - "end": { "line": 161, "column": null } - }, - "38": { - "start": { "line": 162, "column": 4 }, - "end": { "line": 162, "column": null } - }, - "39": { - "start": { "line": 173, "column": 2 }, - "end": { "line": 201, "column": null } - }, - "40": { - "start": { "line": 174, "column": 21 }, - "end": { "line": 179, "column": null } - }, - "41": { - "start": { "line": 182, "column": 4 }, - "end": { "line": 184, "column": null } - }, - "42": { - "start": { "line": 183, "column": 6 }, - "end": { "line": 183, "column": null } - }, - "43": { - "start": { "line": 187, "column": 4 }, - "end": { "line": 192, "column": null } - }, - "44": { - "start": { "line": 188, "column": 6 }, - "end": { "line": 191, "column": null } - }, - "45": { - "start": { "line": 194, "column": 4 }, - "end": { "line": 194, "column": null } - }, - "46": { - "start": { "line": 196, "column": 4 }, - "end": { "line": 198, "column": null } - }, - "47": { - "start": { "line": 197, "column": 6 }, - "end": { "line": 197, "column": null } - }, - "48": { - "start": { "line": 199, "column": 4 }, - "end": { "line": 199, "column": null } - }, - "49": { - "start": { "line": 200, "column": 4 }, - "end": { "line": 200, "column": null } - } - }, - "fnMap": { - "0": { - "name": "isRequestError", - "decl": { - "start": { "line": 41, "column": 9 }, - "end": { "line": 41, "column": 24 } - }, - "loc": { - "start": { "line": 41, "column": 63 }, - "end": { "line": 43, "column": null } - }, - "line": 41 - }, - "1": { - "name": "fetchFileFromGitHub", - "decl": { - "start": { "line": 48, "column": 22 }, - "end": { "line": 48, "column": null } - }, - "loc": { - "start": { "line": 51, "column": 26 }, - "end": { "line": 72, "column": null } - }, - "line": 51 - }, - "2": { - "name": "fetchGitHubDirectory", - "decl": { - "start": { "line": 77, "column": 22 }, - "end": { "line": 77, "column": null } - }, - "loc": { - "start": { "line": 80, "column": 35 }, - "end": { "line": 102, "column": null } - }, - "line": 80 - }, - "3": { - "name": "fetchMultipleFiles", - "decl": { - "start": { "line": 107, "column": 22 }, - "end": { "line": 107, "column": null } - }, - "loc": { - "start": { "line": 111, "column": 32 }, - "end": { "line": 139, "column": null } - }, - "line": 111 - }, - "4": { - "name": "(anonymous_4)", - "decl": { - "start": { "line": 120, "column": 16 }, - "end": { "line": 120, "column": 23 } - }, - "loc": { - "start": { "line": 120, "column": 32 }, - "end": { "line": 123, "column": 7 } - }, - "line": 120 - }, - "5": { - "name": "(anonymous_5)", - "decl": { - "start": { "line": 134, "column": 24 }, - "end": { "line": 134, "column": 25 } - }, - "loc": { - "start": { "line": 134, "column": 37 }, - "end": { "line": 134, "column": 61 } - }, - "line": 134 - }, - "6": { - "name": "fileExistsOnGitHub", - "decl": { - "start": { "line": 144, "column": 22 }, - "end": { "line": 144, "column": null } - }, - "loc": { - "start": { "line": 147, "column": 20 }, - "end": { "line": 164, "column": null } - }, - "line": 147 - }, - "7": { - "name": "getGitHubFileMetadata", - "decl": { - "start": { "line": 169, "column": 22 }, - "end": { "line": 169, "column": null } - }, - "loc": { - "start": { "line": 172, "column": 49 }, - "end": { "line": 202, "column": null } - }, - "line": 172 - } - }, - "branchMap": { - "0": { - "loc": { - "start": { "line": 50, "column": 2 }, - "end": { "line": 50, "column": null } - }, - "type": "default-arg", - "locations": [ - { - "start": { "line": 50, "column": 27 }, - "end": { "line": 50, "column": null } - } - ], - "line": 50 - }, - "1": { - "loc": { - "start": { "line": 66, "column": 4 }, - "end": { "line": 68, "column": null } - }, - "type": "if", - "locations": [ - { - "start": { "line": 66, "column": 4 }, - "end": { "line": 68, "column": null } - }, - { "start": {}, "end": {} } - ], - "line": 66 - }, - "2": { - "loc": { - "start": { "line": 66, "column": 8 }, - "end": { "line": 66, "column": 55 } - }, - "type": "binary-expr", - "locations": [ - { - "start": { "line": 66, "column": 8 }, - "end": { "line": 66, "column": 33 } - }, - { - "start": { "line": 66, "column": 33 }, - "end": { "line": 66, "column": 55 } - } - ], - "line": 66 - }, - "3": { - "loc": { - "start": { "line": 79, "column": 2 }, - "end": { "line": 79, "column": null } - }, - "type": "default-arg", - "locations": [ - { - "start": { "line": 79, "column": 27 }, - "end": { "line": 79, "column": null } - } - ], - "line": 79 - }, - "4": { - "loc": { - "start": { "line": 90, "column": 4 }, - "end": { "line": 92, "column": null } - }, - "type": "if", - "locations": [ - { - "start": { "line": 90, "column": 4 }, - "end": { "line": 92, "column": null } - }, - { "start": {}, "end": {} } - ], - "line": 90 - }, - "5": { - "loc": { - "start": { "line": 109, "column": 2 }, - "end": { "line": 109, "column": null } - }, - "type": "default-arg", - "locations": [ - { - "start": { "line": 109, "column": 24 }, - "end": { "line": 109, "column": null } - } - ], - "line": 109 - }, - "6": { - "loc": { - "start": { "line": 110, "column": 2 }, - "end": { "line": 110, "column": null } - }, - "type": "default-arg", - "locations": [ - { - "start": { "line": 110, "column": 27 }, - "end": { "line": 110, "column": null } - } - ], - "line": 110 - }, - "7": { - "loc": { - "start": { "line": 127, "column": 6 }, - "end": { "line": 129, "column": null } - }, - "type": "if", - "locations": [ - { - "start": { "line": 127, "column": 6 }, - "end": { "line": 129, "column": null } - }, - { "start": {}, "end": {} } - ], - "line": 127 - }, - "8": { - "loc": { - "start": { "line": 133, "column": 4 }, - "end": { "line": 135, "column": null } - }, - "type": "if", - "locations": [ - { - "start": { "line": 133, "column": 4 }, - "end": { "line": 135, "column": null } - }, - { "start": {}, "end": {} } - ], - "line": 133 - }, - "9": { - "loc": { - "start": { "line": 146, "column": 2 }, - "end": { "line": 146, "column": null } - }, - "type": "default-arg", - "locations": [ - { - "start": { "line": 146, "column": 27 }, - "end": { "line": 146, "column": null } - } - ], - "line": 146 - }, - "10": { - "loc": { - "start": { "line": 157, "column": 4 }, - "end": { "line": 159, "column": null } - }, - "type": "if", - "locations": [ - { - "start": { "line": 157, "column": 4 }, - "end": { "line": 159, "column": null } - }, - { "start": {}, "end": {} } - ], - "line": 157 - }, - "11": { - "loc": { - "start": { "line": 157, "column": 8 }, - "end": { "line": 157, "column": 55 } - }, - "type": "binary-expr", - "locations": [ - { - "start": { "line": 157, "column": 8 }, - "end": { "line": 157, "column": 33 } - }, - { - "start": { "line": 157, "column": 33 }, - "end": { "line": 157, "column": 55 } - } - ], - "line": 157 - }, - "12": { - "loc": { - "start": { "line": 171, "column": 2 }, - "end": { "line": 171, "column": null } - }, - "type": "default-arg", - "locations": [ - { - "start": { "line": 171, "column": 27 }, - "end": { "line": 171, "column": null } - } - ], - "line": 171 - }, - "13": { - "loc": { - "start": { "line": 182, "column": 4 }, - "end": { "line": 184, "column": null } - }, - "type": "if", - "locations": [ - { - "start": { "line": 182, "column": 4 }, - "end": { "line": 184, "column": null } - }, - { "start": {}, "end": {} } - ], - "line": 182 - }, - "14": { - "loc": { - "start": { "line": 187, "column": 4 }, - "end": { "line": 192, "column": null } - }, - "type": "if", - "locations": [ - { - "start": { "line": 187, "column": 4 }, - "end": { "line": 192, "column": null } - }, - { "start": {}, "end": {} } - ], - "line": 187 - }, - "15": { - "loc": { - "start": { "line": 187, "column": 8 }, - "end": { "line": 187, "column": 41 } - }, - "type": "binary-expr", - "locations": [ - { - "start": { "line": 187, "column": 8 }, - "end": { "line": 187, "column": 25 } - }, - { - "start": { "line": 187, "column": 25 }, - "end": { "line": 187, "column": 41 } - } - ], - "line": 187 - }, - "16": { - "loc": { - "start": { "line": 196, "column": 4 }, - "end": { "line": 198, "column": null } - }, - "type": "if", - "locations": [ - { - "start": { "line": 196, "column": 4 }, - "end": { "line": 198, "column": null } - }, - { "start": {}, "end": {} } - ], - "line": 196 - }, - "17": { - "loc": { - "start": { "line": 196, "column": 8 }, - "end": { "line": 196, "column": 55 } - }, - "type": "binary-expr", - "locations": [ - { - "start": { "line": 196, "column": 8 }, - "end": { "line": 196, "column": 33 } - }, - { - "start": { "line": 196, "column": 33 }, - "end": { "line": 196, "column": 55 } - } - ], - "line": 196 - } - }, - "s": { - "0": 10, - "1": 10, - "2": 10, - "3": 0, - "4": 0, - "5": 0, - "6": 0, - "7": 0, - "8": 0, - "9": 0, - "10": 0, - "11": 0, - "12": 0, - "13": 0, - "14": 0, - "15": 0, - "16": 0, - "17": 0, - "18": 0, - "19": 0, - "20": 0, - "21": 0, - "22": 0, - "23": 0, - "24": 0, - "25": 0, - "26": 0, - "27": 0, - "28": 0, - "29": 0, - "30": 0, - "31": 0, - "32": 0, - "33": 0, - "34": 0, - "35": 0, - "36": 0, - "37": 0, - "38": 0, - "39": 0, - "40": 0, - "41": 0, - "42": 0, - "43": 0, - "44": 0, - "45": 0, - "46": 0, - "47": 0, - "48": 0, - "49": 0 - }, - "f": { "0": 0, "1": 0, "2": 0, "3": 0, "4": 0, "5": 0, "6": 0, "7": 0 }, - "b": { - "0": [0], - "1": [0, 0], - "2": [0, 0], - "3": [0], - "4": [0, 0], - "5": [0], - "6": [0], - "7": [0, 0], - "8": [0, 0], - "9": [0], - "10": [0, 0], - "11": [0, 0], - "12": [0], - "13": [0, 0], - "14": [0, 0], - "15": [0, 0], - "16": [0, 0], - "17": [0, 0] - }, - "meta": { - "lastBranch": 18, - "lastFunction": 8, - "lastStatement": 50, - "seen": { - "s:22:37:27:Infinity": 0, - "s:29:39:35:Infinity": 1, - "s:37:16:39:Infinity": 2, - "f:41:9:41:24": 0, - "s:42:2:42:Infinity": 3, - "f:48:22:48:Infinity": 1, - "b:50:27:50:Infinity": 0, - "s:52:2:71:Infinity": 4, - "s:53:21:61:Infinity": 5, - "s:64:4:64:Infinity": 6, - "b:66:4:68:Infinity:undefined:undefined:undefined:undefined": 1, - "s:66:4:68:Infinity": 7, - "b:66:8:66:33:66:33:66:55": 2, - "s:67:6:67:Infinity": 8, - "s:69:4:69:Infinity": 9, - "s:70:4:70:Infinity": 10, - "f:77:22:77:Infinity": 2, - "b:79:27:79:Infinity": 3, - "s:81:2:101:Infinity": 11, - "s:82:21:87:Infinity": 12, - "b:90:4:92:Infinity:undefined:undefined:undefined:undefined": 4, - "s:90:4:92:Infinity": 13, - "s:91:6:91:Infinity": 14, - "s:94:4:94:Infinity": 15, - "s:96:4:99:Infinity": 16, - "s:100:4:100:Infinity": 17, - "f:107:22:107:Infinity": 3, - "b:109:24:109:Infinity": 5, - "b:110:27:110:Infinity": 6, - "s:112:18:112:Infinity": 18, - "s:116:2:136:Infinity": 19, - "s:116:15:116:18": 20, - "s:117:18:117:Infinity": 21, - "s:119:25:124:Infinity": 22, - "f:120:16:120:23": 4, - "s:121:24:121:Infinity": 23, - "s:122:8:122:Infinity": 24, - "s:126:4:130:Infinity": 25, - "b:127:6:129:Infinity:undefined:undefined:undefined:undefined": 7, - "s:127:6:129:Infinity": 26, - "s:128:8:128:Infinity": 27, - "b:133:4:135:Infinity:undefined:undefined:undefined:undefined": 8, - "s:133:4:135:Infinity": 28, - "s:134:6:134:Infinity": 29, - "f:134:24:134:25": 5, - "s:134:37:134:61": 30, - "s:138:2:138:Infinity": 31, - "f:144:22:144:Infinity": 6, - "b:146:27:146:Infinity": 9, - "s:148:2:163:Infinity": 32, - "s:149:4:154:Infinity": 33, - "s:155:4:155:Infinity": 34, - "b:157:4:159:Infinity:undefined:undefined:undefined:undefined": 10, - "s:157:4:159:Infinity": 35, - "b:157:8:157:33:157:33:157:55": 11, - "s:158:6:158:Infinity": 36, - "s:161:4:161:Infinity": 37, - "s:162:4:162:Infinity": 38, - "f:169:22:169:Infinity": 7, - "b:171:27:171:Infinity": 12, - "s:173:2:201:Infinity": 39, - "s:174:21:179:Infinity": 40, - "b:182:4:184:Infinity:undefined:undefined:undefined:undefined": 13, - "s:182:4:184:Infinity": 41, - "s:183:6:183:Infinity": 42, - "b:187:4:192:Infinity:undefined:undefined:undefined:undefined": 14, - "s:187:4:192:Infinity": 43, - "b:187:8:187:25:187:25:187:41": 15, - "s:188:6:191:Infinity": 44, - "s:194:4:194:Infinity": 45, - "b:196:4:198:Infinity:undefined:undefined:undefined:undefined": 16, - "s:196:4:198:Infinity": 46, - "b:196:8:196:33:196:33:196:55": 17, - "s:197:6:197:Infinity": 47, - "s:199:4:199:Infinity": 48, - "s:200:4:200:Infinity": 49 - } - } - }, - "/Users/danielslovinsky/Documents/projects/alchemy-docs/docs/src/content-indexer/utils/nav-tree-merge.ts": { - "path": "/Users/danielslovinsky/Documents/projects/alchemy-docs/docs/src/content-indexer/utils/nav-tree-merge.ts", - "statementMap": { - "0": { - "start": { "line": 11, "column": 30 }, - "end": { "line": 16, "column": null } - }, - "1": { - "start": { "line": 12, "column": 2 }, - "end": { "line": 14, "column": null } - }, - "2": { - "start": { "line": 13, "column": 4 }, - "end": { "line": 13, "column": null } - }, - "3": { - "start": { "line": 15, "column": 2 }, - "end": { "line": 15, "column": null } - }, - "4": { - "start": { "line": 25, "column": 44 }, - "end": { "line": 39, "column": null } - }, - "5": { - "start": { "line": 28, "column": 2 }, - "end": { "line": 38, "column": null } - }, - "6": { - "start": { "line": 30, "column": 6 }, - "end": { "line": 34, "column": null } - }, - "7": { - "start": { "line": 31, "column": 8 }, - "end": { "line": 31, "column": null } - }, - "8": { - "start": { "line": 33, "column": 8 }, - "end": { "line": 33, "column": null } - }, - "9": { - "start": { "line": 35, "column": 6 }, - "end": { "line": 35, "column": null } - }, - "10": { - "start": { "line": 50, "column": 35 }, - "end": { "line": 93, "column": null } - }, - "11": { - "start": { "line": 55, "column": 2 }, - "end": { "line": 60, "column": null } - }, - "12": { - "start": { "line": 56, "column": 4 }, - "end": { "line": 58, "column": null } - }, - "13": { - "start": { "line": 57, "column": 6 }, - "end": { "line": 57, "column": null } - }, - "14": { - "start": { "line": 59, "column": 4 }, - "end": { "line": 59, "column": null } - }, - "15": { - "start": { "line": 62, "column": 2 }, - "end": { "line": 62, "column": null } - }, - "16": { - "start": { "line": 66, "column": 4 }, - "end": { "line": 66, "column": null } - }, - "17": { - "start": { "line": 69, "column": 25 }, - "end": { "line": 69, "column": null } - }, - "18": { - "start": { "line": 70, "column": 22 }, - "end": { "line": 70, "column": null } - }, - "19": { - "start": { "line": 73, "column": 2 }, - "end": { "line": 77, "column": null } - }, - "20": { - "start": { "line": 74, "column": 4 }, - "end": { "line": 76, "column": null } - }, - "21": { - "start": { "line": 80, "column": 2 }, - "end": { "line": 82, "column": null } - }, - "22": { - "start": { "line": 81, "column": 4 }, - "end": { "line": 81, "column": null } - }, - "23": { - "start": { "line": 83, "column": 2 }, - "end": { "line": 85, "column": null } - }, - "24": { - "start": { "line": 84, "column": 4 }, - "end": { "line": 84, "column": null } - }, - "25": { - "start": { "line": 88, "column": 2 }, - "end": { "line": 92, "column": null } - } - }, - "fnMap": { - "0": { - "name": "(anonymous_0)", - "decl": { - "start": { "line": 11, "column": 30 }, - "end": { "line": 11, "column": 31 } - }, - "loc": { - "start": { "line": 11, "column": 58 }, - "end": { "line": 16, "column": null } - }, - "line": 11 - }, - "1": { - "name": "(anonymous_1)", - "decl": { - "start": { "line": 25, "column": 44 }, - "end": { "line": 25, "column": null } - }, - "loc": { - "start": { "line": 27, "column": 54 }, - "end": { "line": 39, "column": null } - }, - "line": 27 - }, - "2": { - "name": "(anonymous_2)", - "decl": { - "start": { "line": 29, "column": 4 }, - "end": { "line": 29, "column": 5 } - }, - "loc": { - "start": { "line": 29, "column": 19 }, - "end": { "line": 36, "column": null } - }, - "line": 29 - }, - "3": { - "name": "(anonymous_3)", - "decl": { - "start": { "line": 50, "column": 35 }, - "end": { "line": 50, "column": null } - }, - "loc": { - "start": { "line": 54, "column": 21 }, - "end": { "line": 93, "column": null } - }, - "line": 54 - } - }, - "branchMap": { - "0": { - "loc": { - "start": { "line": 12, "column": 2 }, - "end": { "line": 14, "column": null } - }, - "type": "if", - "locations": [ - { - "start": { "line": 12, "column": 2 }, - "end": { "line": 14, "column": null } - }, - { "start": {}, "end": {} } - ], - "line": 12 - }, - "1": { - "loc": { - "start": { "line": 12, "column": 6 }, - "end": { "line": 12, "column": 62 } - }, - "type": "binary-expr", - "locations": [ - { - "start": { "line": 12, "column": 6 }, - "end": { "line": 12, "column": 33 } - }, - { - "start": { "line": 12, "column": 33 }, - "end": { "line": 12, "column": 62 } - } - ], - "line": 12 - }, - "2": { - "loc": { - "start": { "line": 30, "column": 6 }, - "end": { "line": 34, "column": null } - }, - "type": "if", - "locations": [ - { - "start": { "line": 30, "column": 6 }, - "end": { "line": 34, "column": null } - }, - { - "start": { "line": 32, "column": 13 }, - "end": { "line": 34, "column": null } - } - ], - "line": 30 - }, - "3": { - "loc": { - "start": { "line": 55, "column": 2 }, - "end": { "line": 60, "column": null } - }, - "type": "if", - "locations": [ - { - "start": { "line": 55, "column": 2 }, - "end": { "line": 60, "column": null } - }, - { "start": {}, "end": {} } - ], - "line": 55 - }, - "4": { - "loc": { - "start": { "line": 56, "column": 4 }, - "end": { "line": 58, "column": null } - }, - "type": "if", - "locations": [ - { - "start": { "line": 56, "column": 4 }, - "end": { "line": 58, "column": null } - }, - { "start": {}, "end": {} } - ], - "line": 56 - }, - "5": { - "loc": { - "start": { "line": 69, "column": 25 }, - "end": { "line": 69, "column": null } - }, - "type": "cond-expr", - "locations": [ - { - "start": { "line": 69, "column": 50 }, - "end": { "line": 69, "column": 60 } - }, - { - "start": { "line": 69, "column": 60 }, - "end": { "line": 69, "column": null } - } - ], - "line": 69 - }, - "6": { - "loc": { - "start": { "line": 70, "column": 22 }, - "end": { "line": 70, "column": null } - }, - "type": "cond-expr", - "locations": [ - { - "start": { "line": 70, "column": 46 }, - "end": { "line": 70, "column": 56 } - }, - { - "start": { "line": 70, "column": 56 }, - "end": { "line": 70, "column": null } - } - ], - "line": 70 - }, - "7": { - "loc": { - "start": { "line": 73, "column": 2 }, - "end": { "line": 77, "column": null } - }, - "type": "if", - "locations": [ - { - "start": { "line": 73, "column": 2 }, - "end": { "line": 77, "column": null } - }, - { "start": {}, "end": {} } - ], - "line": 73 - }, - "8": { - "loc": { - "start": { "line": 73, "column": 6 }, - "end": { "line": 73, "column": 56 } - }, - "type": "binary-expr", - "locations": [ - { - "start": { "line": 73, "column": 6 }, - "end": { "line": 73, "column": 32 } - }, - { - "start": { "line": 73, "column": 32 }, - "end": { "line": 73, "column": 56 } - } - ], - "line": 73 - }, - "9": { - "loc": { - "start": { "line": 80, "column": 2 }, - "end": { "line": 82, "column": null } - }, - "type": "if", - "locations": [ - { - "start": { "line": 80, "column": 2 }, - "end": { "line": 82, "column": null } - }, - { "start": {}, "end": {} } - ], - "line": 80 - }, - "10": { - "loc": { - "start": { "line": 83, "column": 2 }, - "end": { "line": 85, "column": null } - }, - "type": "if", - "locations": [ - { - "start": { "line": 83, "column": 2 }, - "end": { "line": 85, "column": null } - }, - { "start": {}, "end": {} } - ], - "line": 83 - } - }, - "s": { - "0": 1, - "1": 0, - "2": 0, - "3": 0, - "4": 1, - "5": 0, - "6": 0, - "7": 0, - "8": 0, - "9": 0, - "10": 1, - "11": 0, - "12": 0, - "13": 0, - "14": 0, - "15": 0, - "16": 0, - "17": 0, - "18": 0, - "19": 0, - "20": 0, - "21": 0, - "22": 0, - "23": 0, - "24": 0, - "25": 0 - }, - "f": { "0": 0, "1": 0, "2": 0, "3": 0 }, - "b": { - "0": [0, 0], - "1": [0, 0], - "2": [0, 0], - "3": [0, 0], - "4": [0, 0], - "5": [0, 0], - "6": [0, 0], - "7": [0, 0], - "8": [0, 0], - "9": [0, 0], - "10": [0, 0] - }, - "meta": { - "lastBranch": 11, - "lastFunction": 4, - "lastStatement": 26, - "seen": { - "s:11:30:16:Infinity": 0, - "f:11:30:11:31": 0, - "b:12:2:14:Infinity:undefined:undefined:undefined:undefined": 0, - "s:12:2:14:Infinity": 1, - "b:12:6:12:33:12:33:12:62": 1, - "s:13:4:13:Infinity": 2, - "s:15:2:15:Infinity": 3, - "s:25:44:39:Infinity": 4, - "f:25:44:25:Infinity": 1, - "s:28:2:38:Infinity": 5, - "f:29:4:29:5": 2, - "b:30:6:34:Infinity:32:13:34:Infinity": 2, - "s:30:6:34:Infinity": 6, - "s:31:8:31:Infinity": 7, - "s:33:8:33:Infinity": 8, - "s:35:6:35:Infinity": 9, - "s:50:35:93:Infinity": 10, - "f:50:35:50:Infinity": 3, - "b:55:2:60:Infinity:undefined:undefined:undefined:undefined": 3, - "s:55:2:60:Infinity": 11, - "b:56:4:58:Infinity:undefined:undefined:undefined:undefined": 4, - "s:56:4:58:Infinity": 12, - "s:57:6:57:Infinity": 13, - "s:59:4:59:Infinity": 14, - "s:62:2:62:Infinity": 15, - "s:66:4:66:Infinity": 16, - "s:69:25:69:Infinity": 17, - "b:69:50:69:60:69:60:69:Infinity": 5, - "s:70:22:70:Infinity": 18, - "b:70:46:70:56:70:56:70:Infinity": 6, - "b:73:2:77:Infinity:undefined:undefined:undefined:undefined": 7, - "s:73:2:77:Infinity": 19, - "b:73:6:73:32:73:32:73:56": 8, - "s:74:4:76:Infinity": 20, - "b:80:2:82:Infinity:undefined:undefined:undefined:undefined": 9, - "s:80:2:82:Infinity": 21, - "s:81:4:81:Infinity": 22, - "b:83:2:85:Infinity:undefined:undefined:undefined:undefined": 10, - "s:83:2:85:Infinity": 23, - "s:84:4:84:Infinity": 24, - "s:88:2:92:Infinity": 25 - } - } - }, - "/Users/danielslovinsky/Documents/projects/alchemy-docs/docs/src/content-indexer/utils/navigation-helpers.ts": { - "path": "/Users/danielslovinsky/Documents/projects/alchemy-docs/docs/src/content-indexer/utils/navigation-helpers.ts", - "statementMap": { - "0": { - "start": { "line": 7, "column": 39 }, - "end": { "line": 14, "column": null } - }, - "1": { - "start": { "line": 10, "column": 15 }, - "end": { "line": 14, "column": null } - } - }, - "fnMap": { - "0": { - "name": "(anonymous_0)", - "decl": { - "start": { "line": 7, "column": 39 }, - "end": { "line": 7, "column": null } - }, - "loc": { - "start": { "line": 10, "column": 15 }, - "end": { "line": 14, "column": null } - }, - "line": 10 - } - }, - "branchMap": {}, - "s": { "0": 6, "1": 20 }, - "f": { "0": 20 }, - "b": {}, - "meta": { - "lastBranch": 0, - "lastFunction": 1, - "lastStatement": 2, - "seen": { - "s:7:39:14:Infinity": 0, - "f:7:39:7:Infinity": 0, - "s:10:15:14:Infinity": 1 - } - } - }, - "/Users/danielslovinsky/Documents/projects/alchemy-docs/docs/src/content-indexer/utils/normalization.ts": { - "path": "/Users/danielslovinsky/Documents/projects/alchemy-docs/docs/src/content-indexer/utils/normalization.ts", - "statementMap": { - "0": { - "start": { "line": 8, "column": 29 }, - "end": { "line": 11, "column": null } - }, - "1": { - "start": { "line": 9, "column": 2 }, - "end": { "line": 9, "column": null } - }, - "2": { - "start": { "line": 9, "column": 13 }, - "end": { "line": 9, "column": null } - }, - "3": { - "start": { "line": 10, "column": 2 }, - "end": { "line": 10, "column": null } - }, - "4": { - "start": { "line": 17, "column": 33 }, - "end": { "line": 22, "column": null } - }, - "5": { - "start": { "line": 21, "column": 2 }, - "end": { "line": 21, "column": null } - } - }, - "fnMap": { - "0": { - "name": "(anonymous_0)", - "decl": { - "start": { "line": 8, "column": 29 }, - "end": { "line": 8, "column": 30 } - }, - "loc": { - "start": { "line": 8, "column": 79 }, - "end": { "line": 11, "column": null } - }, - "line": 8 - }, - "1": { - "name": "(anonymous_1)", - "decl": { - "start": { "line": 17, "column": 33 }, - "end": { "line": 17, "column": null } - }, - "loc": { - "start": { "line": 20, "column": 13 }, - "end": { "line": 22, "column": null } - }, - "line": 20 - } - }, - "branchMap": { - "0": { - "loc": { - "start": { "line": 9, "column": 2 }, - "end": { "line": 9, "column": null } - }, - "type": "if", - "locations": [ - { - "start": { "line": 9, "column": 2 }, - "end": { "line": 9, "column": null } - }, - { "start": {}, "end": {} } - ], - "line": 9 - }, - "1": { - "loc": { - "start": { "line": 21, "column": 26 }, - "end": { "line": 21, "column": 54 } - }, - "type": "binary-expr", - "locations": [ - { - "start": { "line": 21, "column": 26 }, - "end": { "line": 21, "column": 50 } - }, - { - "start": { "line": 21, "column": 50 }, - "end": { "line": 21, "column": 54 } - } - ], - "line": 21 - } - }, - "s": { "0": 4, "1": 30, "2": 24, "3": 6, "4": 4, "5": 30 }, - "f": { "0": 30, "1": 30 }, - "b": { "0": [24, 6], "1": [30, 26] }, - "meta": { - "lastBranch": 2, - "lastFunction": 2, - "lastStatement": 6, - "seen": { - "s:8:29:11:Infinity": 0, - "f:8:29:8:30": 0, - "b:9:2:9:Infinity:undefined:undefined:undefined:undefined": 0, - "s:9:2:9:Infinity": 1, - "s:9:13:9:Infinity": 2, - "s:10:2:10:Infinity": 3, - "s:17:33:22:Infinity": 4, - "f:17:33:17:Infinity": 1, - "s:21:2:21:Infinity": 5, - "b:21:26:21:50:21:50:21:54": 1 - } - } - }, - "/Users/danielslovinsky/Documents/projects/alchemy-docs/docs/src/content-indexer/utils/openapi.ts": { - "path": "/Users/danielslovinsky/Documents/projects/alchemy-docs/docs/src/content-indexer/utils/openapi.ts", - "statementMap": { - "0": { - "start": { "line": 18, "column": 23 }, - "end": { "line": 32, "column": null } - }, - "1": { - "start": { "line": 23, "column": 2 }, - "end": { "line": 25, "column": null } - }, - "2": { - "start": { "line": 24, "column": 4 }, - "end": { "line": 24, "column": null } - }, - "3": { - "start": { "line": 27, "column": 2 }, - "end": { "line": 29, "column": null } - }, - "4": { - "start": { "line": 28, "column": 4 }, - "end": { "line": 28, "column": null } - }, - "5": { - "start": { "line": 31, "column": 2 }, - "end": { "line": 31, "column": null } - }, - "6": { - "start": { "line": 38, "column": 33 }, - "end": { "line": 58, "column": null } - }, - "7": { - "start": { "line": 43, "column": 19 }, - "end": { "line": 43, "column": null } - }, - "8": { - "start": { "line": 44, "column": 2 }, - "end": { "line": 46, "column": null } - }, - "9": { - "start": { "line": 45, "column": 4 }, - "end": { "line": 45, "column": null } - }, - "10": { - "start": { "line": 49, "column": 20 }, - "end": { "line": 55, "column": null } - }, - "11": { - "start": { "line": 51, "column": 6 }, - "end": { "line": 54, "column": null } - }, - "12": { - "start": { "line": 57, "column": 2 }, - "end": { "line": 57, "column": null } - }, - "13": { - "start": { "line": 69, "column": 40 }, - "end": { "line": 96, "column": null } - }, - "14": { - "start": { "line": 72, "column": 2 }, - "end": { "line": 95, "column": null } - }, - "15": { - "start": { "line": 73, "column": 4 }, - "end": { "line": 73, "column": null } - }, - "16": { - "start": { "line": 73, "column": 51 }, - "end": { "line": 73, "column": null } - }, - "17": { - "start": { "line": 75, "column": 4 }, - "end": { "line": 94, "column": null } - }, - "18": { - "start": { "line": 78, "column": 11 }, - "end": { "line": 80, "column": null } - }, - "19": { - "start": { "line": 83, "column": 19 }, - "end": { "line": 83, "column": null } - }, - "20": { - "start": { "line": 85, "column": 21 }, - "end": { "line": 85, "column": null } - }, - "21": { - "start": { "line": 86, "column": 20 }, - "end": { "line": 86, "column": null } - }, - "22": { - "start": { "line": 88, "column": 8 }, - "end": { "line": 93, "column": null } - }, - "23": { - "start": { "line": 105, "column": 34 }, - "end": { "line": 123, "column": null } - }, - "24": { - "start": { "line": 110, "column": 20 }, - "end": { "line": 110, "column": null } - }, - "25": { - "start": { "line": 113, "column": 2 }, - "end": { "line": 116, "column": null } - }, - "26": { - "start": { "line": 114, "column": 10 }, - "end": { "line": 114, "column": null } - }, - "27": { - "start": { "line": 115, "column": 4 }, - "end": { "line": 115, "column": null } - }, - "28": { - "start": { "line": 119, "column": 8 }, - "end": { "line": 119, "column": null } - }, - "29": { - "start": { "line": 120, "column": 2 }, - "end": { "line": 120, "column": null } - }, - "30": { - "start": { "line": 122, "column": 2 }, - "end": { "line": 122, "column": null } - }, - "31": { - "start": { "line": 129, "column": 39 }, - "end": { "line": 149, "column": null } - }, - "32": { - "start": { "line": 134, "column": 19 }, - "end": { "line": 134, "column": null } - }, - "33": { - "start": { "line": 135, "column": 2 }, - "end": { "line": 137, "column": null } - }, - "34": { - "start": { "line": 136, "column": 4 }, - "end": { "line": 136, "column": null } - }, - "35": { - "start": { "line": 139, "column": 21 }, - "end": { "line": 139, "column": null } - }, - "36": { - "start": { "line": 140, "column": 2 }, - "end": { "line": 142, "column": null } - }, - "37": { - "start": { "line": 141, "column": 4 }, - "end": { "line": 141, "column": null } - }, - "38": { - "start": { "line": 144, "column": 23 }, - "end": { "line": 144, "column": null } - }, - "39": { - "start": { "line": 145, "column": 22 }, - "end": { "line": 145, "column": null } - }, - "40": { - "start": { "line": 146, "column": 18 }, - "end": { "line": 146, "column": null } - }, - "41": { - "start": { "line": 148, "column": 2 }, - "end": { "line": 148, "column": null } - } - }, - "fnMap": { - "0": { - "name": "(anonymous_0)", - "decl": { - "start": { "line": 18, "column": 23 }, - "end": { "line": 18, "column": null } - }, - "loc": { - "start": { "line": 22, "column": 13 }, - "end": { "line": 32, "column": null } - }, - "line": 22 - }, - "1": { - "name": "(anonymous_1)", - "decl": { - "start": { "line": 38, "column": 33 }, - "end": { "line": 38, "column": null } - }, - "loc": { - "start": { "line": 42, "column": 13 }, - "end": { "line": 58, "column": null } - }, - "line": 42 - }, - "2": { - "name": "(anonymous_2)", - "decl": { - "start": { "line": 50, "column": 4 }, - "end": { "line": 50, "column": 5 } - }, - "loc": { - "start": { "line": 51, "column": 6 }, - "end": { "line": 54, "column": null } - }, - "line": 51 - }, - "3": { - "name": "(anonymous_3)", - "decl": { - "start": { "line": 69, "column": 40 }, - "end": { "line": 69, "column": null } - }, - "loc": { - "start": { "line": 71, "column": 27 }, - "end": { "line": 96, "column": null } - }, - "line": 71 - }, - "4": { - "name": "(anonymous_4)", - "decl": { - "start": { "line": 72, "column": 39 }, - "end": { "line": 72, "column": 40 } - }, - "loc": { - "start": { "line": 72, "column": 61 }, - "end": { "line": 95, "column": 3 } - }, - "line": 72 - }, - "5": { - "name": "(anonymous_5)", - "decl": { - "start": { "line": 77, "column": 8 }, - "end": { "line": 77, "column": 9 } - }, - "loc": { - "start": { "line": 78, "column": 11 }, - "end": { "line": 80, "column": null } - }, - "line": 78 - }, - "6": { - "name": "(anonymous_6)", - "decl": { - "start": { "line": 82, "column": 11 }, - "end": { "line": 82, "column": 12 } - }, - "loc": { - "start": { "line": 82, "column": 36 }, - "end": { "line": 94, "column": 7 } - }, - "line": 82 - }, - "7": { - "name": "(anonymous_7)", - "decl": { - "start": { "line": 105, "column": 34 }, - "end": { "line": 105, "column": null } - }, - "loc": { - "start": { "line": 109, "column": 13 }, - "end": { "line": 123, "column": null } - }, - "line": 109 - }, - "8": { - "name": "(anonymous_8)", - "decl": { - "start": { "line": 129, "column": 39 }, - "end": { "line": 129, "column": null } - }, - "loc": { - "start": { "line": 133, "column": 13 }, - "end": { "line": 149, "column": null } - }, - "line": 133 - } - }, - "branchMap": { - "0": { - "loc": { - "start": { "line": 23, "column": 2 }, - "end": { "line": 25, "column": null } - }, - "type": "if", - "locations": [ - { - "start": { "line": 23, "column": 2 }, - "end": { "line": 25, "column": null } - }, - { "start": {}, "end": {} } - ], - "line": 23 - }, - "1": { - "loc": { - "start": { "line": 23, "column": 6 }, - "end": { "line": 23, "column": 59 } - }, - "type": "binary-expr", - "locations": [ - { - "start": { "line": 23, "column": 6 }, - "end": { "line": 23, "column": 36 } - }, - { - "start": { "line": 23, "column": 36 }, - "end": { "line": 23, "column": 59 } - } - ], - "line": 23 - }, - "2": { - "loc": { - "start": { "line": 27, "column": 2 }, - "end": { "line": 29, "column": null } - }, - "type": "if", - "locations": [ - { - "start": { "line": 27, "column": 2 }, - "end": { "line": 29, "column": null } - }, - { "start": {}, "end": {} } - ], - "line": 27 - }, - "3": { - "loc": { - "start": { "line": 27, "column": 6 }, - "end": { "line": 27, "column": 51 } - }, - "type": "binary-expr", - "locations": [ - { - "start": { "line": 27, "column": 6 }, - "end": { "line": 27, "column": 32 } - }, - { - "start": { "line": 27, "column": 32 }, - "end": { "line": 27, "column": 51 } - } - ], - "line": 27 - }, - "4": { - "loc": { - "start": { "line": 44, "column": 2 }, - "end": { "line": 46, "column": null } - }, - "type": "if", - "locations": [ - { - "start": { "line": 44, "column": 2 }, - "end": { "line": 46, "column": null } - }, - { "start": {}, "end": {} } - ], - "line": 44 - }, - "5": { - "loc": { - "start": { "line": 44, "column": 6 }, - "end": { "line": 44, "column": 49 } - }, - "type": "binary-expr", - "locations": [ - { - "start": { "line": 44, "column": 6 }, - "end": { "line": 44, "column": 19 } - }, - { - "start": { "line": 44, "column": 19 }, - "end": { "line": 44, "column": 49 } - } - ], - "line": 44 - }, - "6": { - "loc": { - "start": { "line": 51, "column": 6 }, - "end": { "line": 54, "column": null } - }, - "type": "binary-expr", - "locations": [ - { - "start": { "line": 51, "column": 6 }, - "end": { "line": 51, "column": null } - }, - { - "start": { "line": 52, "column": 6 }, - "end": { "line": 52, "column": null } - }, - { - "start": { "line": 53, "column": 6 }, - "end": { "line": 53, "column": null } - }, - { - "start": { "line": 54, "column": 7 }, - "end": { "line": 54, "column": null } - } - ], - "line": 51 - }, - "7": { - "loc": { - "start": { "line": 57, "column": 9 }, - "end": { "line": 57, "column": null } - }, - "type": "binary-expr", - "locations": [ - { - "start": { "line": 57, "column": 9 }, - "end": { "line": 57, "column": 31 } - }, - { - "start": { "line": 57, "column": 31 }, - "end": { "line": 57, "column": null } - } - ], - "line": 57 - }, - "8": { - "loc": { - "start": { "line": 73, "column": 4 }, - "end": { "line": 73, "column": null } - }, - "type": "if", - "locations": [ - { - "start": { "line": 73, "column": 4 }, - "end": { "line": 73, "column": null } - }, - { "start": {}, "end": {} } - ], - "line": 73 - }, - "9": { - "loc": { - "start": { "line": 73, "column": 8 }, - "end": { "line": 73, "column": 51 } - }, - "type": "binary-expr", - "locations": [ - { - "start": { "line": 73, "column": 8 }, - "end": { "line": 73, "column": 21 } - }, - { - "start": { "line": 73, "column": 21 }, - "end": { "line": 73, "column": 51 } - } - ], - "line": 73 - }, - "10": { - "loc": { - "start": { "line": 78, "column": 11 }, - "end": { "line": 80, "column": null } - }, - "type": "binary-expr", - "locations": [ - { - "start": { "line": 78, "column": 11 }, - "end": { "line": 78, "column": null } - }, - { - "start": { "line": 79, "column": 10 }, - "end": { "line": 79, "column": null } - }, - { - "start": { "line": 80, "column": 10 }, - "end": { "line": 80, "column": null } - } - ], - "line": 78 - }, - "11": { - "loc": { - "start": { "line": 85, "column": 21 }, - "end": { "line": 85, "column": null } - }, - "type": "cond-expr", - "locations": [ - { - "start": { "line": 85, "column": 46 }, - "end": { "line": 85, "column": 56 } - }, - { - "start": { "line": 85, "column": 56 }, - "end": { "line": 85, "column": null } - } - ], - "line": 85 - }, - "12": { - "loc": { - "start": { "line": 113, "column": 2 }, - "end": { "line": 116, "column": null } - }, - "type": "if", - "locations": [ - { - "start": { "line": 113, "column": 2 }, - "end": { "line": 116, "column": null } - }, - { "start": {}, "end": {} } - ], - "line": 113 - }, - "13": { - "loc": { - "start": { "line": 135, "column": 2 }, - "end": { "line": 137, "column": null } - }, - "type": "if", - "locations": [ - { - "start": { "line": 135, "column": 2 }, - "end": { "line": 137, "column": null } - }, - { "start": {}, "end": {} } - ], - "line": 135 - }, - "14": { - "loc": { - "start": { "line": 135, "column": 6 }, - "end": { "line": 135, "column": 49 } - }, - "type": "binary-expr", - "locations": [ - { - "start": { "line": 135, "column": 6 }, - "end": { "line": 135, "column": 19 } - }, - { - "start": { "line": 135, "column": 19 }, - "end": { "line": 135, "column": 49 } - } - ], - "line": 135 - }, - "15": { - "loc": { - "start": { "line": 140, "column": 2 }, - "end": { "line": 142, "column": null } - }, - "type": "if", - "locations": [ - { - "start": { "line": 140, "column": 2 }, - "end": { "line": 142, "column": null } - }, - { "start": {}, "end": {} } - ], - "line": 140 - }, - "16": { - "loc": { - "start": { "line": 140, "column": 6 }, - "end": { "line": 140, "column": 51 } - }, - "type": "binary-expr", - "locations": [ - { - "start": { "line": 140, "column": 6 }, - "end": { "line": 140, "column": 20 } - }, - { - "start": { "line": 140, "column": 20 }, - "end": { "line": 140, "column": 51 } - } - ], - "line": 140 - }, - "17": { - "loc": { - "start": { "line": 148, "column": 9 }, - "end": { "line": 148, "column": null } - }, - "type": "binary-expr", - "locations": [ - { - "start": { "line": 148, "column": 9 }, - "end": { "line": 148, "column": 24 } - }, - { - "start": { "line": 148, "column": 24 }, - "end": { "line": 148, "column": 35 } - }, - { - "start": { "line": 148, "column": 35 }, - "end": { "line": 148, "column": null } - } - ], - "line": 148 - } - }, - "s": { - "0": 5, - "1": 25, - "2": 23, - "3": 2, - "4": 1, - "5": 1, - "6": 5, - "7": 17, - "8": 17, - "9": 1, - "10": 16, - "11": 16, - "12": 16, - "13": 5, - "14": 22, - "15": 23, - "16": 0, - "17": 23, - "18": 27, - "19": 25, - "20": 25, - "21": 25, - "22": 25, - "23": 5, - "24": 33, - "25": 33, - "26": 11, - "27": 11, - "28": 33, - "29": 33, - "30": 33, - "31": 5, - "32": 17, - "33": 17, - "34": 1, - "35": 16, - "36": 16, - "37": 1, - "38": 15, - "39": 15, - "40": 15, - "41": 15 - }, - "f": { - "0": 25, - "1": 17, - "2": 16, - "3": 22, - "4": 23, - "5": 27, - "6": 25, - "7": 33, - "8": 17 - }, - "b": { - "0": [23, 2], - "1": [25, 23], - "2": [1, 1], - "3": [2, 1], - "4": [1, 16], - "5": [17, 16], - "6": [16, 16, 16, 16], - "7": [16, 11], - "8": [0, 23], - "9": [23, 23], - "10": [27, 25, 25], - "11": [7, 18], - "12": [11, 22], - "13": [1, 16], - "14": [17, 16], - "15": [1, 15], - "16": [16, 15], - "17": [15, 11, 9] - }, - "meta": { - "lastBranch": 18, - "lastFunction": 9, - "lastStatement": 42, - "seen": { - "s:18:23:32:Infinity": 0, - "f:18:23:18:Infinity": 0, - "b:23:2:25:Infinity:undefined:undefined:undefined:undefined": 0, - "s:23:2:25:Infinity": 1, - "b:23:6:23:36:23:36:23:59": 1, - "s:24:4:24:Infinity": 2, - "b:27:2:29:Infinity:undefined:undefined:undefined:undefined": 2, - "s:27:2:29:Infinity": 3, - "b:27:6:27:32:27:32:27:51": 3, - "s:28:4:28:Infinity": 4, - "s:31:2:31:Infinity": 5, - "s:38:33:58:Infinity": 6, - "f:38:33:38:Infinity": 1, - "s:43:19:43:Infinity": 7, - "b:44:2:46:Infinity:undefined:undefined:undefined:undefined": 4, - "s:44:2:46:Infinity": 8, - "b:44:6:44:19:44:19:44:49": 5, - "s:45:4:45:Infinity": 9, - "s:49:20:55:Infinity": 10, - "f:50:4:50:5": 2, - "s:51:6:54:Infinity": 11, - "b:51:6:51:Infinity:52:6:52:Infinity:53:6:53:Infinity:54:7:54:Infinity": 6, - "s:57:2:57:Infinity": 12, - "b:57:9:57:31:57:31:57:Infinity": 7, - "s:69:40:96:Infinity": 13, - "f:69:40:69:Infinity": 3, - "s:72:2:95:Infinity": 14, - "f:72:39:72:40": 4, - "b:73:4:73:Infinity:undefined:undefined:undefined:undefined": 8, - "s:73:4:73:Infinity": 15, - "b:73:8:73:21:73:21:73:51": 9, - "s:73:51:73:Infinity": 16, - "s:75:4:94:Infinity": 17, - "f:77:8:77:9": 5, - "s:78:11:80:Infinity": 18, - "b:78:11:78:Infinity:79:10:79:Infinity:80:10:80:Infinity": 10, - "f:82:11:82:12": 6, - "s:83:19:83:Infinity": 19, - "s:85:21:85:Infinity": 20, - "b:85:46:85:56:85:56:85:Infinity": 11, - "s:86:20:86:Infinity": 21, - "s:88:8:93:Infinity": 22, - "s:105:34:123:Infinity": 23, - "f:105:34:105:Infinity": 7, - "s:110:20:110:Infinity": 24, - "b:113:2:116:Infinity:undefined:undefined:undefined:undefined": 12, - "s:113:2:116:Infinity": 25, - "s:114:10:114:Infinity": 26, - "s:115:4:115:Infinity": 27, - "s:119:8:119:Infinity": 28, - "s:120:2:120:Infinity": 29, - "s:122:2:122:Infinity": 30, - "s:129:39:149:Infinity": 31, - "f:129:39:129:Infinity": 8, - "s:134:19:134:Infinity": 32, - "b:135:2:137:Infinity:undefined:undefined:undefined:undefined": 13, - "s:135:2:137:Infinity": 33, - "b:135:6:135:19:135:19:135:49": 14, - "s:136:4:136:Infinity": 34, - "s:139:21:139:Infinity": 35, - "b:140:2:142:Infinity:undefined:undefined:undefined:undefined": 15, - "s:140:2:142:Infinity": 36, - "b:140:6:140:20:140:20:140:51": 16, - "s:141:4:141:Infinity": 37, - "s:144:23:144:Infinity": 38, - "s:145:22:145:Infinity": 39, - "s:146:18:146:Infinity": 40, - "s:148:2:148:Infinity": 41, - "b:148:9:148:24:148:24:148:35:148:35:148:Infinity": 17 - } - } - }, - "/Users/danielslovinsky/Documents/projects/alchemy-docs/docs/src/content-indexer/utils/openrpc.ts": { - "path": "/Users/danielslovinsky/Documents/projects/alchemy-docs/docs/src/content-indexer/utils/openrpc.ts", - "statementMap": { - "0": { - "start": { "line": 6, "column": 34 }, - "end": { "line": 13, "column": null } - }, - "1": { - "start": { "line": 7, "column": 2 }, - "end": { "line": 11, "column": null } - } - }, - "fnMap": { - "0": { - "name": "(anonymous_0)", - "decl": { - "start": { "line": 6, "column": 34 }, - "end": { "line": 6, "column": 35 } - }, - "loc": { - "start": { "line": 6, "column": 74 }, - "end": { "line": 13, "column": null } - }, - "line": 6 - } - }, - "branchMap": { - "0": { - "loc": { - "start": { "line": 8, "column": 4 }, - "end": { "line": 11, "column": null } - }, - "type": "binary-expr", - "locations": [ - { - "start": { "line": 8, "column": 4 }, - "end": { "line": 8, "column": null } - }, - { - "start": { "line": 9, "column": 4 }, - "end": { "line": 9, "column": null } - }, - { - "start": { "line": 10, "column": 4 }, - "end": { "line": 10, "column": null } - }, - { - "start": { "line": 11, "column": 4 }, - "end": { "line": 11, "column": null } - } - ], - "line": 8 - } - }, - "s": { "0": 5, "1": 16 }, - "f": { "0": 16 }, - "b": { "0": [16, 14, 13, 11] }, - "meta": { - "lastBranch": 1, - "lastFunction": 1, - "lastStatement": 2, - "seen": { - "s:6:34:13:Infinity": 0, - "f:6:34:6:35": 0, - "s:7:2:11:Infinity": 1, - "b:8:4:8:Infinity:9:4:9:Infinity:10:4:10:Infinity:11:4:11:Infinity": 0 - } - } - }, - "/Users/danielslovinsky/Documents/projects/alchemy-docs/docs/src/content-indexer/utils/test-factories.ts": { - "path": "/Users/danielslovinsky/Documents/projects/alchemy-docs/docs/src/content-indexer/utils/test-factories.ts", - "statementMap": { - "0": { - "start": { "line": 10, "column": 34 }, - "end": { "line": 20, "column": null } - }, - "1": { - "start": { "line": 12, "column": 19 }, - "end": { "line": 20, "column": null } - }, - "2": { - "start": { "line": 25, "column": 34 }, - "end": { "line": 35, "column": null } - }, - "3": { - "start": { "line": 27, "column": 19 }, - "end": { "line": 35, "column": null } - }, - "4": { - "start": { "line": 40, "column": 33 }, - "end": { "line": 49, "column": null } - }, - "5": { - "start": { "line": 42, "column": 18 }, - "end": { "line": 49, "column": null } - } - }, - "fnMap": { - "0": { - "name": "(anonymous_0)", - "decl": { - "start": { "line": 10, "column": 34 }, - "end": { "line": 10, "column": null } - }, - "loc": { - "start": { "line": 12, "column": 19 }, - "end": { "line": 20, "column": null } - }, - "line": 12 - }, - "1": { - "name": "(anonymous_1)", - "decl": { - "start": { "line": 25, "column": 34 }, - "end": { "line": 25, "column": null } - }, - "loc": { - "start": { "line": 27, "column": 19 }, - "end": { "line": 35, "column": null } - }, - "line": 27 - }, - "2": { - "name": "(anonymous_2)", - "decl": { - "start": { "line": 40, "column": 33 }, - "end": { "line": 40, "column": null } - }, - "loc": { - "start": { "line": 42, "column": 18 }, - "end": { "line": 49, "column": null } - }, - "line": 42 - } - }, - "branchMap": { - "0": { - "loc": { - "start": { "line": 11, "column": 2 }, - "end": { "line": 11, "column": null } - }, - "type": "default-arg", - "locations": [ - { - "start": { "line": 11, "column": 36 }, - "end": { "line": 11, "column": null } - } - ], - "line": 11 - }, - "1": { - "loc": { - "start": { "line": 26, "column": 2 }, - "end": { "line": 26, "column": null } - }, - "type": "default-arg", - "locations": [ - { - "start": { "line": 26, "column": 36 }, - "end": { "line": 26, "column": null } - } - ], - "line": 26 - }, - "2": { - "loc": { - "start": { "line": 41, "column": 2 }, - "end": { "line": 41, "column": null } - }, - "type": "default-arg", - "locations": [ - { - "start": { "line": 41, "column": 35 }, - "end": { "line": 41, "column": null } - } - ], - "line": 41 - } - }, - "s": { "0": 9, "1": 19, "2": 9, "3": 11, "4": 9, "5": 16 }, - "f": { "0": 19, "1": 11, "2": 16 }, - "b": { "0": [19], "1": [11], "2": [16] }, - "meta": { - "lastBranch": 3, - "lastFunction": 3, - "lastStatement": 6, - "seen": { - "s:10:34:20:Infinity": 0, - "f:10:34:10:Infinity": 0, - "s:12:19:20:Infinity": 1, - "b:11:36:11:Infinity": 0, - "s:25:34:35:Infinity": 2, - "f:25:34:25:Infinity": 1, - "s:27:19:35:Infinity": 3, - "b:26:36:26:Infinity": 1, - "s:40:33:49:Infinity": 4, - "f:40:33:40:Infinity": 2, - "s:42:18:49:Infinity": 5, - "b:41:35:41:Infinity": 2 - } - } - }, - "/Users/danielslovinsky/Documents/projects/alchemy-docs/docs/src/content-indexer/utils/truncate-record.ts": { - "path": "/Users/danielslovinsky/Documents/projects/alchemy-docs/docs/src/content-indexer/utils/truncate-record.ts", - "statementMap": { - "0": { - "start": { "line": 5, "column": 25 }, - "end": { "line": 5, "column": null } - }, - "1": { - "start": { "line": 6, "column": 21 }, - "end": { "line": 6, "column": null } - }, - "2": { - "start": { "line": 7, "column": 23 }, - "end": { "line": 7, "column": null } - }, - "3": { - "start": { "line": 13, "column": 30 }, - "end": { "line": 77, "column": null } - }, - "4": { - "start": { "line": 15, "column": 8 }, - "end": { "line": 15, "column": null } - }, - "5": { - "start": { "line": 16, "column": 17 }, - "end": { "line": 16, "column": null } - }, - "6": { - "start": { "line": 18, "column": 25 }, - "end": { "line": 18, "column": null } - }, - "7": { - "start": { "line": 19, "column": 22 }, - "end": { "line": 19, "column": null } - }, - "8": { - "start": { "line": 21, "column": 2 }, - "end": { "line": 23, "column": null } - }, - "9": { - "start": { "line": 22, "column": 4 }, - "end": { "line": 22, "column": null } - }, - "10": { - "start": { "line": 26, "column": 31 }, - "end": { "line": 26, "column": null } - }, - "11": { - "start": { "line": 27, "column": 24 }, - "end": { "line": 30, "column": null } - }, - "12": { - "start": { "line": 32, "column": 2 }, - "end": { "line": 37, "column": null } - }, - "13": { - "start": { "line": 39, "column": 2 }, - "end": { "line": 43, "column": null } - }, - "14": { - "start": { "line": 40, "column": 4 }, - "end": { "line": 42, "column": null } - }, - "15": { - "start": { "line": 47, "column": 25 }, - "end": { "line": 47, "column": null } - }, - "16": { - "start": { "line": 48, "column": 39 }, - "end": { "line": 48, "column": null } - }, - "17": { - "start": { "line": 49, "column": 21 }, - "end": { "line": 49, "column": null } - }, - "18": { - "start": { "line": 50, "column": 19 }, - "end": { "line": 50, "column": null } - }, - "19": { - "start": { "line": 52, "column": 2 }, - "end": { "line": 64, "column": null } - }, - "20": { - "start": { "line": 54, "column": 10 }, - "end": { "line": 54, "column": null } - }, - "21": { - "start": { "line": 57, "column": 23 }, - "end": { "line": 57, "column": null } - }, - "22": { - "start": { "line": 58, "column": 29 }, - "end": { "line": 58, "column": null } - }, - "23": { - "start": { "line": 59, "column": 4 }, - "end": { "line": 59, "column": null } - }, - "24": { - "start": { "line": 61, "column": 4 }, - "end": { "line": 61, "column": null } - }, - "25": { - "start": { "line": 62, "column": 4 }, - "end": { "line": 62, "column": null } - }, - "26": { - "start": { "line": 63, "column": 4 }, - "end": { "line": 63, "column": null } - }, - "27": { - "start": { "line": 66, "column": 2 }, - "end": { "line": 70, "column": null } - }, - "28": { - "start": { "line": 67, "column": 4 }, - "end": { "line": 69, "column": null } - }, - "29": { - "start": { "line": 72, "column": 2 }, - "end": { "line": 74, "column": null } - }, - "30": { - "start": { "line": 76, "column": 2 }, - "end": { "line": 76, "column": null } - } - }, - "fnMap": { - "0": { - "name": "(anonymous_0)", - "decl": { - "start": { "line": 13, "column": 30 }, - "end": { "line": 13, "column": 31 } - }, - "loc": { - "start": { "line": 13, "column": 75 }, - "end": { "line": 77, "column": null } - }, - "line": 13 - } - }, - "branchMap": { - "0": { - "loc": { - "start": { "line": 21, "column": 2 }, - "end": { "line": 23, "column": null } - }, - "type": "if", - "locations": [ - { - "start": { "line": 21, "column": 2 }, - "end": { "line": 23, "column": null } - }, - { "start": {}, "end": {} } - ], - "line": 21 - }, - "1": { - "loc": { - "start": { "line": 39, "column": 2 }, - "end": { "line": 43, "column": null } - }, - "type": "if", - "locations": [ - { - "start": { "line": 39, "column": 2 }, - "end": { "line": 43, "column": null } - }, - { "start": {}, "end": {} } - ], - "line": 39 - }, - "2": { - "loc": { - "start": { "line": 52, "column": 9 }, - "end": { "line": 52, "column": 73 } - }, - "type": "binary-expr", - "locations": [ - { - "start": { "line": 52, "column": 9 }, - "end": { "line": 52, "column": 44 } - }, - { - "start": { "line": 52, "column": 44 }, - "end": { "line": 52, "column": 73 } - } - ], - "line": 52 - }, - "3": { - "loc": { - "start": { "line": 66, "column": 2 }, - "end": { "line": 70, "column": null } - }, - "type": "if", - "locations": [ - { - "start": { "line": 66, "column": 2 }, - "end": { "line": 70, "column": null } - }, - { "start": {}, "end": {} } - ], - "line": 66 - }, - "4": { - "loc": { - "start": { "line": 73, "column": 108 }, - "end": { "line": 73, "column": 135 } - }, - "type": "cond-expr", - "locations": [ - { - "start": { "line": 73, "column": 127 }, - "end": { "line": 73, "column": 132 } - }, - { - "start": { "line": 73, "column": 132 }, - "end": { "line": 73, "column": 135 } - } - ], - "line": 73 - } - }, - "s": { - "0": 2, - "1": 2, - "2": 2, - "3": 2, - "4": 6, - "5": 6, - "6": 6, - "7": 6, - "8": 6, - "9": 2, - "10": 4, - "11": 4, - "12": 4, - "13": 4, - "14": 1, - "15": 3, - "16": 3, - "17": 3, - "18": 3, - "19": 3, - "20": 3, - "21": 3, - "22": 3, - "23": 3, - "24": 3, - "25": 3, - "26": 3, - "27": 3, - "28": 0, - "29": 3, - "30": 6 - }, - "f": { "0": 6 }, - "b": { "0": [2, 4], "1": [1, 3], "2": [3, 3], "3": [0, 3], "4": [3, 0] }, - "meta": { - "lastBranch": 5, - "lastFunction": 1, - "lastStatement": 31, - "seen": { - "s:5:25:5:Infinity": 0, - "s:6:21:6:Infinity": 1, - "s:7:23:7:Infinity": 2, - "s:13:30:77:Infinity": 3, - "f:13:30:13:31": 0, - "s:15:8:15:Infinity": 4, - "s:16:17:16:Infinity": 5, - "s:18:25:18:Infinity": 6, - "s:19:22:19:Infinity": 7, - "b:21:2:23:Infinity:undefined:undefined:undefined:undefined": 0, - "s:21:2:23:Infinity": 8, - "s:22:4:22:Infinity": 9, - "s:26:31:26:Infinity": 10, - "s:27:24:30:Infinity": 11, - "s:32:2:37:Infinity": 12, - "b:39:2:43:Infinity:undefined:undefined:undefined:undefined": 1, - "s:39:2:43:Infinity": 13, - "s:40:4:42:Infinity": 14, - "s:47:25:47:Infinity": 15, - "s:48:39:48:Infinity": 16, - "s:49:21:49:Infinity": 17, - "s:50:19:50:Infinity": 18, - "s:52:2:64:Infinity": 19, - "b:52:9:52:44:52:44:52:73": 2, - "s:54:10:54:Infinity": 20, - "s:57:23:57:Infinity": 21, - "s:58:29:58:Infinity": 22, - "s:59:4:59:Infinity": 23, - "s:61:4:61:Infinity": 24, - "s:62:4:62:Infinity": 25, - "s:63:4:63:Infinity": 26, - "b:66:2:70:Infinity:undefined:undefined:undefined:undefined": 3, - "s:66:2:70:Infinity": 27, - "s:67:4:69:Infinity": 28, - "s:72:2:74:Infinity": 29, - "b:73:127:73:132:73:132:73:135": 4, - "s:76:2:76:Infinity": 30 - } - } - }, - "/Users/danielslovinsky/Documents/projects/alchemy-docs/docs/src/content-indexer/visitors/index.ts": { - "path": "/Users/danielslovinsky/Documents/projects/alchemy-docs/docs/src/content-indexer/visitors/index.ts", - "statementMap": { - "0": { - "start": { "line": 49, "column": 35 }, - "end": { "line": 76, "column": null } - }, - "1": { - "start": { "line": 50, "column": 19 }, - "end": { "line": 50, "column": null } - }, - "2": { - "start": { "line": 53, "column": 2 }, - "end": { "line": 55, "column": null } - }, - "3": { - "start": { "line": 54, "column": 4 }, - "end": { "line": 54, "column": null } - }, - "4": { - "start": { "line": 58, "column": 2 }, - "end": { "line": 60, "column": null } - }, - "5": { - "start": { "line": 59, "column": 4 }, - "end": { "line": 59, "column": null } - }, - "6": { - "start": { "line": 62, "column": 2 }, - "end": { "line": 64, "column": null } - }, - "7": { - "start": { "line": 63, "column": 4 }, - "end": { "line": 63, "column": null } - }, - "8": { - "start": { "line": 66, "column": 2 }, - "end": { "line": 68, "column": null } - }, - "9": { - "start": { "line": 67, "column": 4 }, - "end": { "line": 67, "column": null } - }, - "10": { - "start": { "line": 70, "column": 2 }, - "end": { "line": 72, "column": null } - }, - "11": { - "start": { "line": 71, "column": 4 }, - "end": { "line": 71, "column": null } - }, - "12": { - "start": { "line": 75, "column": 2 }, - "end": { "line": 75, "column": null } - } - }, - "fnMap": { - "0": { - "name": "(anonymous_0)", - "decl": { - "start": { "line": 49, "column": 35 }, - "end": { "line": 49, "column": 36 } - }, - "loc": { - "start": { "line": 49, "column": 77 }, - "end": { "line": 76, "column": null } - }, - "line": 49 - } - }, - "branchMap": { - "0": { - "loc": { - "start": { "line": 53, "column": 2 }, - "end": { "line": 55, "column": null } - }, - "type": "if", - "locations": [ - { - "start": { "line": 53, "column": 2 }, - "end": { "line": 55, "column": null } - }, - { "start": {}, "end": {} } - ], - "line": 53 - }, - "1": { - "loc": { - "start": { "line": 58, "column": 2 }, - "end": { "line": 60, "column": null } - }, - "type": "if", - "locations": [ - { - "start": { "line": 58, "column": 2 }, - "end": { "line": 60, "column": null } - }, - { "start": {}, "end": {} } - ], - "line": 58 - }, - "2": { - "loc": { - "start": { "line": 62, "column": 2 }, - "end": { "line": 64, "column": null } - }, - "type": "if", - "locations": [ - { - "start": { "line": 62, "column": 2 }, - "end": { "line": 64, "column": null } - }, - { "start": {}, "end": {} } - ], - "line": 62 - }, - "3": { - "loc": { - "start": { "line": 66, "column": 2 }, - "end": { "line": 68, "column": null } - }, - "type": "if", - "locations": [ - { - "start": { "line": 66, "column": 2 }, - "end": { "line": 68, "column": null } - }, - { "start": {}, "end": {} } - ], - "line": 66 - }, - "4": { - "loc": { - "start": { "line": 70, "column": 2 }, - "end": { "line": 72, "column": null } - }, - "type": "if", - "locations": [ - { - "start": { "line": 70, "column": 2 }, - "end": { "line": 72, "column": null } - }, - { "start": {}, "end": {} } - ], - "line": 70 - } - }, - "s": { - "0": 2, - "1": 20, - "2": 20, - "3": 1, - "4": 19, - "5": 2, - "6": 17, - "7": 13, - "8": 4, - "9": 3, - "10": 1, - "11": 1, - "12": 0 - }, - "f": { "0": 20 }, - "b": { "0": [1, 19], "1": [2, 17], "2": [13, 4], "3": [3, 1], "4": [1, 0] }, - "meta": { - "lastBranch": 5, - "lastFunction": 1, - "lastStatement": 13, - "seen": { - "s:49:35:76:Infinity": 0, - "f:49:35:49:36": 0, - "s:50:19:50:Infinity": 1, - "b:53:2:55:Infinity:undefined:undefined:undefined:undefined": 0, - "s:53:2:55:Infinity": 2, - "s:54:4:54:Infinity": 3, - "b:58:2:60:Infinity:undefined:undefined:undefined:undefined": 1, - "s:58:2:60:Infinity": 4, - "s:59:4:59:Infinity": 5, - "b:62:2:64:Infinity:undefined:undefined:undefined:undefined": 2, - "s:62:2:64:Infinity": 6, - "s:63:4:63:Infinity": 7, - "b:66:2:68:Infinity:undefined:undefined:undefined:undefined": 3, - "s:66:2:68:Infinity": 8, - "s:67:4:67:Infinity": 9, - "b:70:2:72:Infinity:undefined:undefined:undefined:undefined": 4, - "s:70:2:72:Infinity": 10, - "s:71:4:71:Infinity": 11, - "s:75:2:75:Infinity": 12 - } - } - }, - "/Users/danielslovinsky/Documents/projects/alchemy-docs/docs/src/content-indexer/visitors/visit-api-reference.ts": { - "path": "/Users/danielslovinsky/Documents/projects/alchemy-docs/docs/src/content-indexer/visitors/visit-api-reference.ts", - "statementMap": { - "0": { - "start": { "line": 24, "column": 33 }, - "end": { "line": 74, "column": null } - }, - "1": { - "start": { "line": 25, "column": 56 }, - "end": { "line": 25, "column": null } - }, - "2": { - "start": { "line": 28, "column": 18 }, - "end": { "line": 28, "column": null } - }, - "3": { - "start": { "line": 29, "column": 21 }, - "end": { "line": 29, "column": null } - }, - "4": { - "start": { "line": 30, "column": 19 }, - "end": { "line": 30, "column": null } - }, - "5": { - "start": { "line": 31, "column": 19 }, - "end": { "line": 31, "column": null } - }, - "6": { - "start": { "line": 32, "column": 22 }, - "end": { "line": 32, "column": null } - }, - "7": { - "start": { "line": 35, "column": 25 }, - "end": { "line": 37, "column": null } - }, - "8": { - "start": { "line": 40, "column": 17 }, - "end": { "line": 40, "column": null } - }, - "9": { - "start": { "line": 41, "column": 2 }, - "end": { "line": 46, "column": null } - }, - "10": { - "start": { "line": 42, "column": 4 }, - "end": { "line": 44, "column": null } - }, - "11": { - "start": { "line": 45, "column": 4 }, - "end": { "line": 45, "column": null } - }, - "12": { - "start": { "line": 48, "column": 38 }, - "end": { "line": 48, "column": null } - }, - "13": { - "start": { "line": 51, "column": 2 }, - "end": { "line": 73, "column": null } - }, - "14": { - "start": { "line": 53, "column": 6 }, - "end": { "line": 61, "column": null } - }, - "15": { - "start": { "line": 64, "column": 6 }, - "end": { "line": 72, "column": null } - } - }, - "fnMap": { - "0": { - "name": "(anonymous_0)", - "decl": { - "start": { "line": 24, "column": 33 }, - "end": { "line": 24, "column": 34 } - }, - "loc": { - "start": { "line": 24, "column": 78 }, - "end": { "line": 74, "column": null } - }, - "line": 24 - } - }, - "branchMap": { - "0": { - "loc": { - "start": { "line": 29, "column": 21 }, - "end": { "line": 29, "column": null } - }, - "type": "binary-expr", - "locations": [ - { - "start": { "line": 29, "column": 21 }, - "end": { "line": 29, "column": 39 } - }, - { - "start": { "line": 29, "column": 31 }, - "end": { "line": 29, "column": null } - } - ], - "line": 29 - }, - "1": { - "loc": { - "start": { "line": 30, "column": 19 }, - "end": { "line": 30, "column": null } - }, - "type": "binary-expr", - "locations": [ - { - "start": { "line": 30, "column": 19 }, - "end": { "line": 30, "column": 45 } - }, - { - "start": { "line": 30, "column": 45 }, - "end": { "line": 30, "column": null } - } - ], - "line": 30 - }, - "2": { - "loc": { - "start": { "line": 31, "column": 19 }, - "end": { "line": 31, "column": null } - }, - "type": "binary-expr", - "locations": [ - { - "start": { "line": 31, "column": 19 }, - "end": { "line": 31, "column": 39 } - }, - { - "start": { "line": 31, "column": 39 }, - "end": { "line": 31, "column": null } - } - ], - "line": 31 - }, - "3": { - "loc": { - "start": { "line": 32, "column": 22 }, - "end": { "line": 32, "column": null } - }, - "type": "binary-expr", - "locations": [ - { - "start": { "line": 32, "column": 22 }, - "end": { "line": 32, "column": 45 } - }, - { - "start": { "line": 32, "column": 45 }, - "end": { "line": 32, "column": null } - } - ], - "line": 32 - }, - "4": { - "loc": { - "start": { "line": 35, "column": 25 }, - "end": { "line": 37, "column": null } - }, - "type": "cond-expr", - "locations": [ - { - "start": { "line": 36, "column": 6 }, - "end": { "line": 36, "column": null } - }, - { - "start": { "line": 37, "column": 6 }, - "end": { "line": 37, "column": null } - } - ], - "line": 35 - }, - "5": { - "loc": { - "start": { "line": 41, "column": 2 }, - "end": { "line": 46, "column": null } - }, - "type": "if", - "locations": [ - { - "start": { "line": 41, "column": 2 }, - "end": { "line": 46, "column": null } - }, - { "start": {}, "end": {} } - ], - "line": 41 - }, - "6": { - "loc": { - "start": { "line": 51, "column": 2 }, - "end": { "line": 73, "column": null } - }, - "type": "switch", - "locations": [ - { - "start": { "line": 52, "column": 4 }, - "end": { "line": 61, "column": null } - }, - { - "start": { "line": 63, "column": 4 }, - "end": { "line": 72, "column": null } - } - ], - "line": 51 - } - }, - "s": { - "0": 3, - "1": 8, - "2": 8, - "3": 8, - "4": 8, - "5": 8, - "6": 8, - "7": 8, - "8": 8, - "9": 8, - "10": 1, - "11": 1, - "12": 7, - "13": 7, - "14": 6, - "15": 1 - }, - "f": { "0": 8 }, - "b": { - "0": [8, 7], - "1": [8, 7], - "2": [8, 7], - "3": [8, 7], - "4": [1, 7], - "5": [1, 7], - "6": [7, 1] - }, - "meta": { - "lastBranch": 7, - "lastFunction": 1, - "lastStatement": 16, - "seen": { - "s:24:33:74:Infinity": 0, - "f:24:33:24:34": 0, - "s:25:56:25:Infinity": 1, - "s:28:18:28:Infinity": 2, - "s:29:21:29:Infinity": 3, - "b:29:21:29:39:29:31:29:Infinity": 0, - "s:30:19:30:Infinity": 4, - "b:30:19:30:45:30:45:30:Infinity": 1, - "s:31:19:31:Infinity": 5, - "b:31:19:31:39:31:39:31:Infinity": 2, - "s:32:22:32:Infinity": 6, - "b:32:22:32:45:32:45:32:Infinity": 3, - "s:35:25:37:Infinity": 7, - "b:36:6:36:Infinity:37:6:37:Infinity": 4, - "s:40:17:40:Infinity": 8, - "b:41:2:46:Infinity:undefined:undefined:undefined:undefined": 5, - "s:41:2:46:Infinity": 9, - "s:42:4:44:Infinity": 10, - "s:45:4:45:Infinity": 11, - "s:48:38:48:Infinity": 12, - "b:52:4:61:Infinity:63:4:72:Infinity": 6, - "s:51:2:73:Infinity": 13, - "s:53:6:61:Infinity": 14, - "s:64:6:72:Infinity": 15 - } - } - }, - "/Users/danielslovinsky/Documents/projects/alchemy-docs/docs/src/content-indexer/visitors/visit-link.ts": { - "path": "/Users/danielslovinsky/Documents/projects/alchemy-docs/docs/src/content-indexer/visitors/visit-link.ts", - "statementMap": { - "0": { - "start": { "line": 14, "column": 25 }, - "end": { "line": 25, "column": null } - }, - "1": { - "start": { "line": 15, "column": 29 }, - "end": { "line": 15, "column": null } - }, - "2": { - "start": { "line": 17, "column": 2 }, - "end": { "line": 24, "column": null } - } - }, - "fnMap": { - "0": { - "name": "(anonymous_0)", - "decl": { - "start": { "line": 14, "column": 25 }, - "end": { "line": 14, "column": 26 } - }, - "loc": { - "start": { "line": 14, "column": 71 }, - "end": { "line": 25, "column": null } - }, - "line": 14 - } - }, - "branchMap": {}, - "s": { "0": 3, "1": 5, "2": 5 }, - "f": { "0": 5 }, - "b": {}, - "meta": { - "lastBranch": 0, - "lastFunction": 1, - "lastStatement": 3, - "seen": { - "s:14:25:25:Infinity": 0, - "f:14:25:14:26": 0, - "s:15:29:15:Infinity": 1, - "s:17:2:24:Infinity": 2 - } - } - }, - "/Users/danielslovinsky/Documents/projects/alchemy-docs/docs/src/content-indexer/visitors/visit-page.ts": { - "path": "/Users/danielslovinsky/Documents/projects/alchemy-docs/docs/src/content-indexer/visitors/visit-page.ts", - "statementMap": { - "0": { - "start": { "line": 24, "column": 25 }, - "end": { "line": 79, "column": null } - }, - "1": { - "start": { "line": 34, "column": 17 }, - "end": { "line": 34, "column": null } - }, - "2": { - "start": { "line": 35, "column": 8 }, - "end": { "line": 35, "column": null } - }, - "3": { - "start": { "line": 36, "column": 18 }, - "end": { "line": 36, "column": null } - }, - "4": { - "start": { "line": 38, "column": 26 }, - "end": { "line": 41, "column": null } - }, - "5": { - "start": { "line": 43, "column": 20 }, - "end": { "line": 43, "column": null } - }, - "6": { - "start": { "line": 46, "column": 23 }, - "end": { "line": 55, "column": null } - }, - "7": { - "start": { "line": 58, "column": 39 }, - "end": { "line": 64, "column": null } - }, - "8": { - "start": { "line": 67, "column": 2 }, - "end": { "line": 76, "column": null } - }, - "9": { - "start": { "line": 68, "column": 18 }, - "end": { "line": 68, "column": null } - }, - "10": { - "start": { "line": 69, "column": 4 }, - "end": { "line": 75, "column": null } - }, - "11": { - "start": { "line": 78, "column": 2 }, - "end": { "line": 78, "column": null } - } - }, - "fnMap": { - "0": { - "name": "(anonymous_0)", - "decl": { - "start": { "line": 24, "column": 25 }, - "end": { "line": 24, "column": 26 } - }, - "loc": { - "start": { "line": 32, "column": 40 }, - "end": { "line": 79, "column": null } - }, - "line": 32 - } - }, - "branchMap": { - "0": { - "loc": { - "start": { "line": 36, "column": 18 }, - "end": { "line": 36, "column": null } - }, - "type": "binary-expr", - "locations": [ - { - "start": { "line": 36, "column": 18 }, - "end": { "line": 36, "column": 35 } - }, - { - "start": { "line": 36, "column": 27 }, - "end": { "line": 36, "column": null } - } - ], - "line": 36 - }, - "1": { - "loc": { - "start": { "line": 50, "column": 14 }, - "end": { "line": 52, "column": null } - }, - "type": "cond-expr", - "locations": [ - { - "start": { "line": 51, "column": 11 }, - "end": { "line": 51, "column": null } - }, - { - "start": { "line": 52, "column": 11 }, - "end": { "line": 52, "column": null } - } - ], - "line": 50 - }, - "2": { - "loc": { - "start": { "line": 58, "column": 39 }, - "end": { "line": 64, "column": null } - }, - "type": "cond-expr", - "locations": [ - { - "start": { "line": 59, "column": 6 }, - "end": { "line": 59, "column": null } - }, - { - "start": { "line": 60, "column": 6 }, - "end": { "line": 64, "column": null } - } - ], - "line": 58 - }, - "3": { - "loc": { - "start": { "line": 67, "column": 2 }, - "end": { "line": 76, "column": null } - }, - "type": "if", - "locations": [ - { - "start": { "line": 67, "column": 2 }, - "end": { "line": 76, "column": null } - }, - { "start": {}, "end": {} } - ], - "line": 67 - }, - "4": { - "loc": { - "start": { "line": 67, "column": 6 }, - "end": { "line": 67, "column": 25 } - }, - "type": "binary-expr", - "locations": [ - { - "start": { "line": 67, "column": 6 }, - "end": { "line": 67, "column": 16 } - }, - { - "start": { "line": 67, "column": 16 }, - "end": { "line": 67, "column": 25 } - } - ], - "line": 67 - }, - "5": { - "loc": { - "start": { "line": 68, "column": 18 }, - "end": { "line": 68, "column": null } - }, - "type": "binary-expr", - "locations": [ - { - "start": { "line": 68, "column": 18 }, - "end": { "line": 68, "column": 46 } - }, - { - "start": { "line": 68, "column": 46 }, - "end": { "line": 68, "column": null } - } - ], - "line": 68 - } - }, - "s": { - "0": 3, - "1": 22, - "2": 22, - "3": 22, - "4": 22, - "5": 22, - "6": 22, - "7": 22, - "8": 22, - "9": 4, - "10": 4, - "11": 22 - }, - "f": { "0": 22 }, - "b": { - "0": [22, 21], - "1": [1, 21], - "2": [2, 20], - "3": [4, 18], - "4": [22, 5], - "5": [4, 1] - }, - "meta": { - "lastBranch": 6, - "lastFunction": 1, - "lastStatement": 12, - "seen": { - "s:24:25:79:Infinity": 0, - "f:24:25:24:26": 0, - "s:34:17:34:Infinity": 1, - "s:35:8:35:Infinity": 2, - "s:36:18:36:Infinity": 3, - "b:36:18:36:35:36:27:36:Infinity": 0, - "s:38:26:41:Infinity": 4, - "s:43:20:43:Infinity": 5, - "s:46:23:55:Infinity": 6, - "b:51:11:51:Infinity:52:11:52:Infinity": 1, - "s:58:39:64:Infinity": 7, - "b:59:6:59:Infinity:60:6:64:Infinity": 2, - "b:67:2:76:Infinity:undefined:undefined:undefined:undefined": 3, - "s:67:2:76:Infinity": 8, - "b:67:6:67:16:67:16:67:25": 4, - "s:68:18:68:Infinity": 9, - "b:68:18:68:46:68:46:68:Infinity": 5, - "s:69:4:75:Infinity": 10, - "s:78:2:78:Infinity": 11 - } - } - }, - "/Users/danielslovinsky/Documents/projects/alchemy-docs/docs/src/content-indexer/visitors/visit-section.ts": { - "path": "/Users/danielslovinsky/Documents/projects/alchemy-docs/docs/src/content-indexer/visitors/visit-section.ts", - "statementMap": { - "0": { - "start": { "line": 30, "column": 28 }, - "end": { "line": 145, "column": null } - }, - "1": { - "start": { "line": 42, "column": 6 }, - "end": { "line": 42, "column": null } - }, - "2": { - "start": { "line": 43, "column": 25 }, - "end": { "line": 43, "column": null } - }, - "3": { - "start": { "line": 44, "column": 19 }, - "end": { "line": 44, "column": null } - }, - "4": { - "start": { "line": 48, "column": 34 }, - "end": { "line": 48, "column": null } - }, - "5": { - "start": { "line": 51, "column": 2 }, - "end": { "line": 84, "column": null } - }, - "6": { - "start": { "line": 52, "column": 19 }, - "end": { "line": 52, "column": null } - }, - "7": { - "start": { "line": 53, "column": 10 }, - "end": { "line": 53, "column": null } - }, - "8": { - "start": { "line": 54, "column": 4 }, - "end": { "line": 54, "column": null } - }, - "9": { - "start": { "line": 56, "column": 31 }, - "end": { "line": 60, "column": null } - }, - "10": { - "start": { "line": 62, "column": 22 }, - "end": { "line": 62, "column": null } - }, - "11": { - "start": { "line": 63, "column": 4 }, - "end": { "line": 63, "column": null } - }, - "12": { - "start": { "line": 66, "column": 4 }, - "end": { "line": 71, "column": null } - }, - "13": { - "start": { "line": 74, "column": 4 }, - "end": { "line": 83, "column": null } - }, - "14": { - "start": { "line": 75, "column": 21 }, - "end": { "line": 75, "column": null } - }, - "15": { - "start": { "line": 76, "column": 6 }, - "end": { "line": 82, "column": null } - }, - "16": { - "start": { "line": 87, "column": 27 }, - "end": { "line": 91, "column": null } - }, - "17": { - "start": { "line": 94, "column": 34 }, - "end": { "line": 99, "column": null } - }, - "18": { - "start": { "line": 103, "column": 37 }, - "end": { "line": 108, "column": null } - }, - "19": { - "start": { "line": 111, "column": 25 }, - "end": { "line": 113, "column": null } - }, - "20": { - "start": { "line": 116, "column": 23 }, - "end": { "line": 123, "column": null } - }, - "21": { - "start": { "line": 117, "column": 4 }, - "end": { "line": 122, "column": null } - }, - "22": { - "start": { "line": 126, "column": 2 }, - "end": { "line": 128, "column": null } - }, - "23": { - "start": { "line": 127, "column": 4 }, - "end": { "line": 127, "column": null } - }, - "24": { - "start": { "line": 131, "column": 30 }, - "end": { "line": 134, "column": null } - }, - "25": { - "start": { "line": 132, "column": 21 }, - "end": { "line": 132, "column": 35 } - }, - "26": { - "start": { "line": 134, "column": 41 }, - "end": { "line": 134, "column": 60 } - }, - "27": { - "start": { "line": 137, "column": 2 }, - "end": { "line": 139, "column": null } - }, - "28": { - "start": { "line": 138, "column": 4 }, - "end": { "line": 138, "column": null } - }, - "29": { - "start": { "line": 142, "column": 2 }, - "end": { "line": 142, "column": null } - }, - "30": { - "start": { "line": 144, "column": 2 }, - "end": { "line": 144, "column": null } - } - }, - "fnMap": { - "0": { - "name": "(anonymous_0)", - "decl": { - "start": { "line": 30, "column": 28 }, - "end": { "line": 30, "column": null } - }, - "loc": { - "start": { "line": 33, "column": 20 }, - "end": { "line": 145, "column": null } - }, - "line": 33 - }, - "1": { - "name": "(anonymous_1)", - "decl": { - "start": { "line": 116, "column": 48 }, - "end": { "line": 116, "column": 49 } - }, - "loc": { - "start": { "line": 117, "column": 4 }, - "end": { "line": 122, "column": null } - }, - "line": 117 - }, - "2": { - "name": "(anonymous_2)", - "decl": { - "start": { "line": 126, "column": 23 }, - "end": { "line": 126, "column": 24 } - }, - "loc": { - "start": { "line": 126, "column": 35 }, - "end": { "line": 128, "column": 3 } - }, - "line": 126 - }, - "3": { - "name": "(anonymous_3)", - "decl": { - "start": { "line": 132, "column": 9 }, - "end": { "line": 132, "column": 10 } - }, - "loc": { - "start": { "line": 132, "column": 21 }, - "end": { "line": 132, "column": 35 } - }, - "line": 132 - }, - "4": { - "name": "(anonymous_4)", - "decl": { - "start": { "line": 134, "column": 12 }, - "end": { "line": 134, "column": 13 } - }, - "loc": { - "start": { "line": 134, "column": 41 }, - "end": { "line": 134, "column": 60 } - }, - "line": 134 - } - }, - "branchMap": { - "0": { - "loc": { - "start": { "line": 43, "column": 25 }, - "end": { "line": 43, "column": null } - }, - "type": "binary-expr", - "locations": [ - { - "start": { "line": 43, "column": 25 }, - "end": { "line": 43, "column": 45 } - }, - { - "start": { "line": 43, "column": 37 }, - "end": { "line": 43, "column": null } - } - ], - "line": 43 - }, - "1": { - "loc": { - "start": { "line": 44, "column": 19 }, - "end": { "line": 44, "column": null } - }, - "type": "binary-expr", - "locations": [ - { - "start": { "line": 44, "column": 19 }, - "end": { "line": 44, "column": 47 } - }, - { - "start": { "line": 44, "column": 47 }, - "end": { "line": 44, "column": null } - } - ], - "line": 44 - }, - "2": { - "loc": { - "start": { "line": 51, "column": 2 }, - "end": { "line": 84, "column": null } - }, - "type": "if", - "locations": [ - { - "start": { "line": 51, "column": 2 }, - "end": { "line": 84, "column": null } - }, - { "start": {}, "end": {} } - ], - "line": 51 - }, - "3": { - "loc": { - "start": { "line": 69, "column": 14 }, - "end": { "line": 69, "column": null } - }, - "type": "cond-expr", - "locations": [ - { - "start": { "line": 69, "column": 31 }, - "end": { "line": 69, "column": 47 } - }, - { - "start": { "line": 69, "column": 47 }, - "end": { "line": 69, "column": null } - } - ], - "line": 69 - }, - "4": { - "loc": { - "start": { "line": 74, "column": 4 }, - "end": { "line": 83, "column": null } - }, - "type": "if", - "locations": [ - { - "start": { "line": 74, "column": 4 }, - "end": { "line": 83, "column": null } - }, - { "start": {}, "end": {} } - ], - "line": 74 - }, - "5": { - "loc": { - "start": { "line": 75, "column": 21 }, - "end": { "line": 75, "column": null } - }, - "type": "binary-expr", - "locations": [ - { - "start": { "line": 75, "column": 21 }, - "end": { "line": 75, "column": 60 } - }, - { - "start": { "line": 75, "column": 60 }, - "end": { "line": 75, "column": null } - } - ], - "line": 75 - }, - "6": { - "loc": { - "start": { "line": 111, "column": 25 }, - "end": { "line": 113, "column": null } - }, - "type": "cond-expr", - "locations": [ - { - "start": { "line": 112, "column": 6 }, - "end": { "line": 112, "column": null } - }, - { - "start": { "line": 113, "column": 6 }, - "end": { "line": 113, "column": null } - } - ], - "line": 111 - }, - "7": { - "loc": { - "start": { "line": 137, "column": 2 }, - "end": { "line": 139, "column": null } - }, - "type": "if", - "locations": [ - { - "start": { "line": 137, "column": 2 }, - "end": { "line": 139, "column": null } - }, - { "start": {}, "end": {} } - ], - "line": 137 - }, - "8": { - "loc": { - "start": { "line": 137, "column": 6 }, - "end": { "line": 137, "column": 51 } - }, - "type": "binary-expr", - "locations": [ - { - "start": { "line": 137, "column": 6 }, - "end": { "line": 137, "column": 31 } - }, - { - "start": { "line": 137, "column": 31 }, - "end": { "line": 137, "column": 51 } - } - ], - "line": 137 - } - }, - "s": { - "0": 2, - "1": 12, - "2": 12, - "3": 12, - "4": 12, - "5": 12, - "6": 1, - "7": 1, - "8": 1, - "9": 1, - "10": 1, - "11": 1, - "12": 1, - "13": 1, - "14": 1, - "15": 1, - "16": 12, - "17": 12, - "18": 12, - "19": 12, - "20": 12, - "21": 15, - "22": 12, - "23": 15, - "24": 12, - "25": 15, - "26": 15, - "27": 12, - "28": 1, - "29": 11, - "30": 11 - }, - "f": { "0": 12, "1": 15, "2": 15, "3": 15, "4": 15 }, - "b": { - "0": [12, 11], - "1": [12, 11], - "2": [1, 11], - "3": [0, 1], - "4": [1, 0], - "5": [1, 0], - "6": [1, 11], - "7": [1, 11], - "8": [12, 12] - }, - "meta": { - "lastBranch": 9, - "lastFunction": 5, - "lastStatement": 31, - "seen": { - "s:30:28:145:Infinity": 0, - "f:30:28:30:Infinity": 0, - "s:42:6:42:Infinity": 1, - "s:43:25:43:Infinity": 2, - "b:43:25:43:45:43:37:43:Infinity": 0, - "s:44:19:44:Infinity": 3, - "b:44:19:44:47:44:47:44:Infinity": 1, - "s:48:34:48:Infinity": 4, - "b:51:2:84:Infinity:undefined:undefined:undefined:undefined": 2, - "s:51:2:84:Infinity": 5, - "s:52:19:52:Infinity": 6, - "s:53:10:53:Infinity": 7, - "s:54:4:54:Infinity": 8, - "s:56:31:60:Infinity": 9, - "s:62:22:62:Infinity": 10, - "s:63:4:63:Infinity": 11, - "s:66:4:71:Infinity": 12, - "b:69:31:69:47:69:47:69:Infinity": 3, - "b:74:4:83:Infinity:undefined:undefined:undefined:undefined": 4, - "s:74:4:83:Infinity": 13, - "s:75:21:75:Infinity": 14, - "b:75:21:75:60:75:60:75:Infinity": 5, - "s:76:6:82:Infinity": 15, - "s:87:27:91:Infinity": 16, - "s:94:34:99:Infinity": 17, - "s:103:37:108:Infinity": 18, - "s:111:25:113:Infinity": 19, - "b:112:6:112:Infinity:113:6:113:Infinity": 6, - "s:116:23:123:Infinity": 20, - "f:116:48:116:49": 1, - "s:117:4:122:Infinity": 21, - "s:126:2:128:Infinity": 22, - "f:126:23:126:24": 2, - "s:127:4:127:Infinity": 23, - "s:131:30:134:Infinity": 24, - "f:132:9:132:10": 3, - "s:132:21:132:35": 25, - "f:134:12:134:13": 4, - "s:134:41:134:60": 26, - "b:137:2:139:Infinity:undefined:undefined:undefined:undefined": 7, - "s:137:2:139:Infinity": 27, - "b:137:6:137:31:137:31:137:51": 8, - "s:138:4:138:Infinity": 28, - "s:142:2:142:Infinity": 29, - "s:144:2:144:Infinity": 30 - } - } - }, - "/Users/danielslovinsky/Documents/projects/alchemy-docs/docs/src/content-indexer/visitors/processors/process-openapi.ts": { - "path": "/Users/danielslovinsky/Documents/projects/alchemy-docs/docs/src/content-indexer/visitors/processors/process-openapi.ts", - "statementMap": { - "0": { - "start": { "line": 52, "column": 33 }, - "end": { "line": 77, "column": null } - }, - "1": { - "start": { "line": 58, "column": 34 }, - "end": { "line": 58, "column": null } - }, - "2": { - "start": { "line": 60, "column": 2 }, - "end": { "line": 74, "column": null } - }, - "3": { - "start": { "line": 61, "column": 10 }, - "end": { "line": 65, "column": null } - }, - "4": { - "start": { "line": 67, "column": 4 }, - "end": { "line": 73, "column": null } - }, - "5": { - "start": { "line": 76, "column": 2 }, - "end": { "line": 76, "column": null } - }, - "6": { - "start": { "line": 82, "column": 31 }, - "end": { "line": 158, "column": null } - }, - "7": { - "start": { "line": 92, "column": 26 }, - "end": { "line": 92, "column": null } - }, - "8": { - "start": { "line": 93, "column": 2 }, - "end": { "line": 97, "column": null } - }, - "9": { - "start": { "line": 94, "column": 21 }, - "end": { "line": 94, "column": null } - }, - "10": { - "start": { "line": 95, "column": 4 }, - "end": { "line": 95, "column": null } - }, - "11": { - "start": { "line": 96, "column": 4 }, - "end": { "line": 96, "column": null } - }, - "12": { - "start": { "line": 99, "column": 33 }, - "end": { "line": 99, "column": null } - }, - "13": { - "start": { "line": 101, "column": 2 }, - "end": { "line": 155, "column": null } - }, - "14": { - "start": { "line": 102, "column": 40 }, - "end": { "line": 143, "column": null } - }, - "15": { - "start": { "line": 103, "column": 12 }, - "end": { "line": 107, "column": null } - }, - "16": { - "start": { "line": 109, "column": 12 }, - "end": { "line": 113, "column": null } - }, - "17": { - "start": { "line": 116, "column": 6 }, - "end": { "line": 135, "column": null } - }, - "18": { - "start": { "line": 117, "column": 14 }, - "end": { "line": 121, "column": null } - }, - "19": { - "start": { "line": 123, "column": 28 }, - "end": { "line": 125, "column": null } - }, - "20": { - "start": { "line": 127, "column": 8 }, - "end": { "line": 134, "column": null } - }, - "21": { - "start": { "line": 137, "column": 6 }, - "end": { "line": 142, "column": null } - }, - "22": { - "start": { "line": 146, "column": 4 }, - "end": { "line": 154, "column": null } - }, - "23": { - "start": { "line": 147, "column": 6 }, - "end": { "line": 151, "column": null } - }, - "24": { - "start": { "line": 153, "column": 6 }, - "end": { "line": 153, "column": null } - }, - "25": { - "start": { "line": 157, "column": 2 }, - "end": { "line": 157, "column": null } - }, - "26": { - "start": { "line": 164, "column": 34 }, - "end": { "line": 215, "column": null } - }, - "27": { - "start": { "line": 173, "column": 48 }, - "end": { "line": 173, "column": null } - }, - "28": { - "start": { "line": 176, "column": 8 }, - "end": { "line": 176, "column": null } - }, - "29": { - "start": { "line": 177, "column": 23 }, - "end": { "line": 182, "column": null } - }, - "30": { - "start": { "line": 185, "column": 2 }, - "end": { "line": 187, "column": null } - }, - "31": { - "start": { "line": 186, "column": 4 }, - "end": { "line": 186, "column": null } - }, - "32": { - "start": { "line": 190, "column": 31 }, - "end": { "line": 192, "column": null } - }, - "33": { - "start": { "line": 195, "column": 22 }, - "end": { "line": 203, "column": null } - }, - "34": { - "start": { "line": 206, "column": 39 }, - "end": { "line": 212, "column": null } - }, - "35": { - "start": { "line": 214, "column": 2 }, - "end": { "line": 214, "column": null } - } - }, - "fnMap": { - "0": { - "name": "(anonymous_0)", - "decl": { - "start": { "line": 52, "column": 33 }, - "end": { "line": 52, "column": 34 } - }, - "loc": { - "start": { "line": 57, "column": 49 }, - "end": { "line": 77, "column": null } - }, - "line": 57 - }, - "1": { - "name": "(anonymous_1)", - "decl": { - "start": { "line": 60, "column": 21 }, - "end": { "line": 60, "column": 22 } - }, - "loc": { - "start": { "line": 60, "column": 36 }, - "end": { "line": 74, "column": 3 } - }, - "line": 60 - }, - "2": { - "name": "(anonymous_2)", - "decl": { - "start": { "line": 82, "column": 31 }, - "end": { "line": 82, "column": 32 } - }, - "loc": { - "start": { "line": 90, "column": 47 }, - "end": { "line": 158, "column": null } - }, - "line": 90 - }, - "3": { - "name": "(anonymous_3)", - "decl": { - "start": { "line": 93, "column": 21 }, - "end": { "line": 93, "column": 22 } - }, - "loc": { - "start": { "line": 93, "column": 36 }, - "end": { "line": 97, "column": 3 } - }, - "line": 93 - }, - "4": { - "name": "(anonymous_4)", - "decl": { - "start": { "line": 102, "column": 58 }, - "end": { "line": 102, "column": 59 } - }, - "loc": { - "start": { "line": 102, "column": 73 }, - "end": { "line": 143, "column": 5 } - }, - "line": 102 - }, - "5": { - "name": "(anonymous_5)", - "decl": { - "start": { "line": 164, "column": 34 }, - "end": { "line": 164, "column": 35 } - }, - "loc": { - "start": { "line": 172, "column": 43 }, - "end": { "line": 215, "column": null } - }, - "line": 172 - } - }, - "branchMap": { - "0": { - "loc": { - "start": { "line": 94, "column": 21 }, - "end": { "line": 94, "column": null } - }, - "type": "binary-expr", - "locations": [ - { - "start": { "line": 94, "column": 21 }, - "end": { "line": 94, "column": 59 } - }, - { - "start": { "line": 94, "column": 59 }, - "end": { "line": 94, "column": null } - } - ], - "line": 94 - }, - "1": { - "loc": { - "start": { "line": 116, "column": 6 }, - "end": { "line": 135, "column": null } - }, - "type": "if", - "locations": [ - { - "start": { "line": 116, "column": 6 }, - "end": { "line": 135, "column": null } - }, - { "start": {}, "end": {} } - ], - "line": 116 - }, - "2": { - "loc": { - "start": { "line": 123, "column": 28 }, - "end": { "line": 125, "column": null } - }, - "type": "cond-expr", - "locations": [ - { - "start": { "line": 124, "column": 12 }, - "end": { "line": 124, "column": null } - }, - { - "start": { "line": 125, "column": 12 }, - "end": { "line": 125, "column": null } - } - ], - "line": 123 - }, - "3": { - "loc": { - "start": { "line": 146, "column": 4 }, - "end": { "line": 154, "column": null } - }, - "type": "if", - "locations": [ - { - "start": { "line": 146, "column": 4 }, - "end": { "line": 154, "column": null } - }, - { - "start": { "line": 152, "column": 11 }, - "end": { "line": 154, "column": null } - } - ], - "line": 146 - }, - "4": { - "loc": { - "start": { "line": 185, "column": 2 }, - "end": { "line": 187, "column": null } - }, - "type": "if", - "locations": [ - { - "start": { "line": 185, "column": 2 }, - "end": { "line": 187, "column": null } - }, - { "start": {}, "end": {} } - ], - "line": 185 - }, - "5": { - "loc": { - "start": { "line": 190, "column": 31 }, - "end": { "line": 192, "column": null } - }, - "type": "cond-expr", - "locations": [ - { - "start": { "line": 191, "column": 6 }, - "end": { "line": 191, "column": null } - }, - { - "start": { "line": 191, "column": 6 }, - "end": { "line": 192, "column": null } - } - ], - "line": 190 - }, - "6": { - "loc": { - "start": { "line": 206, "column": 39 }, - "end": { "line": 212, "column": null } - }, - "type": "cond-expr", - "locations": [ - { - "start": { "line": 207, "column": 6 }, - "end": { "line": 207, "column": null } - }, - { - "start": { "line": 208, "column": 6 }, - "end": { "line": 212, "column": null } - } - ], - "line": 206 - } - }, - "s": { - "0": 4, - "1": 14, - "2": 14, - "3": 15, - "4": 15, - "5": 14, - "6": 4, - "7": 12, - "8": 12, - "9": 13, - "10": 13, - "11": 13, - "12": 12, - "13": 12, - "14": 12, - "15": 13, - "16": 13, - "17": 13, - "18": 13, - "19": 13, - "20": 13, - "21": 13, - "22": 12, - "23": 4, - "24": 8, - "25": 12, - "26": 4, - "27": 14, - "28": 14, - "29": 14, - "30": 14, - "31": 2, - "32": 12, - "33": 14, - "34": 14, - "35": 14 - }, - "f": { "0": 14, "1": 15, "2": 12, "3": 13, "4": 13, "5": 14 }, - "b": { - "0": [13, 12], - "1": [13, 0], - "2": [12, 1], - "3": [4, 8], - "4": [2, 12], - "5": [1, 11], - "6": [1, 11] - }, - "meta": { - "lastBranch": 7, - "lastFunction": 6, - "lastStatement": 36, - "seen": { - "s:52:33:77:Infinity": 0, - "f:52:33:52:34": 0, - "s:58:34:58:Infinity": 1, - "s:60:2:74:Infinity": 2, - "f:60:21:60:22": 1, - "s:61:10:65:Infinity": 3, - "s:67:4:73:Infinity": 4, - "s:76:2:76:Infinity": 5, - "s:82:31:158:Infinity": 6, - "f:82:31:82:32": 2, - "s:92:26:92:Infinity": 7, - "s:93:2:97:Infinity": 8, - "f:93:21:93:22": 3, - "s:94:21:94:Infinity": 9, - "b:94:21:94:59:94:59:94:Infinity": 0, - "s:95:4:95:Infinity": 10, - "s:96:4:96:Infinity": 11, - "s:99:33:99:Infinity": 12, - "s:101:2:155:Infinity": 13, - "s:102:40:143:Infinity": 14, - "f:102:58:102:59": 4, - "s:103:12:107:Infinity": 15, - "s:109:12:113:Infinity": 16, - "b:116:6:135:Infinity:undefined:undefined:undefined:undefined": 1, - "s:116:6:135:Infinity": 17, - "s:117:14:121:Infinity": 18, - "s:123:28:125:Infinity": 19, - "b:124:12:124:Infinity:125:12:125:Infinity": 2, - "s:127:8:134:Infinity": 20, - "s:137:6:142:Infinity": 21, - "b:146:4:154:Infinity:152:11:154:Infinity": 3, - "s:146:4:154:Infinity": 22, - "s:147:6:151:Infinity": 23, - "s:153:6:153:Infinity": 24, - "s:157:2:157:Infinity": 25, - "s:164:34:215:Infinity": 26, - "f:164:34:164:35": 5, - "s:173:48:173:Infinity": 27, - "s:176:8:176:Infinity": 28, - "s:177:23:182:Infinity": 29, - "b:185:2:187:Infinity:undefined:undefined:undefined:undefined": 4, - "s:185:2:187:Infinity": 30, - "s:186:4:186:Infinity": 31, - "s:190:31:192:Infinity": 32, - "b:191:6:191:Infinity:191:6:192:Infinity": 5, - "s:195:22:203:Infinity": 33, - "s:206:39:212:Infinity": 34, - "b:207:6:207:Infinity:208:6:212:Infinity": 6, - "s:214:2:214:Infinity": 35 - } - } - }, - "/Users/danielslovinsky/Documents/projects/alchemy-docs/docs/src/content-indexer/visitors/processors/process-openrpc.ts": { - "path": "/Users/danielslovinsky/Documents/projects/alchemy-docs/docs/src/content-indexer/visitors/processors/process-openrpc.ts", - "statementMap": { - "0": { - "start": { "line": 31, "column": 34 }, - "end": { "line": 112, "column": null } - }, - "1": { - "start": { "line": 40, "column": 48 }, - "end": { "line": 40, "column": null } - }, - "2": { - "start": { "line": 42, "column": 2 }, - "end": { "line": 45, "column": null } - }, - "3": { - "start": { "line": 43, "column": 4 }, - "end": { "line": 43, "column": null } - }, - "4": { - "start": { "line": 44, "column": 4 }, - "end": { "line": 44, "column": null } - }, - "5": { - "start": { "line": 47, "column": 34 }, - "end": { "line": 47, "column": null } - }, - "6": { - "start": { "line": 48, "column": 38 }, - "end": { "line": 48, "column": null } - }, - "7": { - "start": { "line": 52, "column": 4 }, - "end": { "line": 54, "column": null } - }, - "8": { - "start": { "line": 57, "column": 2 }, - "end": { "line": 95, "column": null } - }, - "9": { - "start": { "line": 58, "column": 10 }, - "end": { "line": 58, "column": null } - }, - "10": { - "start": { "line": 59, "column": 24 }, - "end": { "line": 59, "column": null } - }, - "11": { - "start": { "line": 60, "column": 22 }, - "end": { "line": 60, "column": null } - }, - "12": { - "start": { "line": 63, "column": 4 }, - "end": { "line": 69, "column": null } - }, - "13": { - "start": { "line": 72, "column": 4 }, - "end": { "line": 86, "column": null } - }, - "14": { - "start": { "line": 73, "column": 26 }, - "end": { "line": 73, "column": null } - }, - "15": { - "start": { "line": 74, "column": 26 }, - "end": { "line": 76, "column": null } - }, - "16": { - "start": { "line": 78, "column": 6 }, - "end": { "line": 85, "column": null } - }, - "17": { - "start": { "line": 89, "column": 4 }, - "end": { "line": 94, "column": null } - }, - "18": { - "start": { "line": 98, "column": 2 }, - "end": { "line": 100, "column": null } - }, - "19": { - "start": { "line": 99, "column": 4 }, - "end": { "line": 99, "column": null } - }, - "20": { - "start": { "line": 103, "column": 39 }, - "end": { "line": 109, "column": null } - }, - "21": { - "start": { "line": 111, "column": 2 }, - "end": { "line": 111, "column": null } - } - }, - "fnMap": { - "0": { - "name": "(anonymous_0)", - "decl": { - "start": { "line": 31, "column": 34 }, - "end": { "line": 31, "column": 35 } - }, - "loc": { - "start": { "line": 39, "column": 43 }, - "end": { "line": 112, "column": null } - }, - "line": 39 - }, - "1": { - "name": "(anonymous_1)", - "decl": { - "start": { "line": 57, "column": 23 }, - "end": { "line": 57, "column": 24 } - }, - "loc": { - "start": { "line": 57, "column": 35 }, - "end": { "line": 95, "column": 3 } - }, - "line": 57 - } - }, - "branchMap": { - "0": { - "loc": { - "start": { "line": 42, "column": 2 }, - "end": { "line": 45, "column": null } - }, - "type": "if", - "locations": [ - { - "start": { "line": 42, "column": 2 }, - "end": { "line": 45, "column": null } - }, - { "start": {}, "end": {} } - ], - "line": 42 - }, - "1": { - "loc": { - "start": { "line": 52, "column": 4 }, - "end": { "line": 54, "column": null } - }, - "type": "cond-expr", - "locations": [ - { - "start": { "line": 52, "column": 18 }, - "end": { "line": 53, "column": null } - }, - { - "start": { "line": 54, "column": 8 }, - "end": { "line": 54, "column": null } - } - ], - "line": 52 - }, - "2": { - "loc": { - "start": { "line": 52, "column": 4 }, - "end": { "line": 52, "column": null } - }, - "type": "binary-expr", - "locations": [ - { - "start": { "line": 52, "column": 4 }, - "end": { "line": 52, "column": 17 } - }, - { - "start": { "line": 52, "column": 17 }, - "end": { "line": 52, "column": null } - } - ], - "line": 52 - }, - "3": { - "loc": { - "start": { "line": 72, "column": 4 }, - "end": { "line": 86, "column": null } - }, - "type": "if", - "locations": [ - { - "start": { "line": 72, "column": 4 }, - "end": { "line": 86, "column": null } - }, - { "start": {}, "end": {} } - ], - "line": 72 - }, - "4": { - "loc": { - "start": { "line": 73, "column": 26 }, - "end": { "line": 73, "column": null } - }, - "type": "binary-expr", - "locations": [ - { - "start": { "line": 73, "column": 26 }, - "end": { "line": 73, "column": 48 } - }, - { - "start": { "line": 73, "column": 48 }, - "end": { "line": 73, "column": 66 } - }, - { - "start": { "line": 73, "column": 66 }, - "end": { "line": 73, "column": null } - } - ], - "line": 73 - }, - "5": { - "loc": { - "start": { "line": 74, "column": 26 }, - "end": { "line": 76, "column": null } - }, - "type": "cond-expr", - "locations": [ - { - "start": { "line": 75, "column": 10 }, - "end": { "line": 75, "column": null } - }, - { - "start": { "line": 76, "column": 10 }, - "end": { "line": 76, "column": null } - } - ], - "line": 74 - }, - "6": { - "loc": { - "start": { "line": 98, "column": 2 }, - "end": { "line": 100, "column": null } - }, - "type": "if", - "locations": [ - { - "start": { "line": 98, "column": 2 }, - "end": { "line": 100, "column": null } - }, - { "start": {}, "end": {} } - ], - "line": 98 - }, - "7": { - "loc": { - "start": { "line": 103, "column": 39 }, - "end": { "line": 109, "column": null } - }, - "type": "cond-expr", - "locations": [ - { - "start": { "line": 104, "column": 6 }, - "end": { "line": 104, "column": null } - }, - { - "start": { "line": 105, "column": 6 }, - "end": { "line": 109, "column": null } - } - ], - "line": 103 - } - }, - "s": { - "0": 4, - "1": 9, - "2": 9, - "3": 1, - "4": 1, - "5": 8, - "6": 8, - "7": 8, - "8": 9, - "9": 9, - "10": 9, - "11": 9, - "12": 9, - "13": 9, - "14": 8, - "15": 8, - "16": 8, - "17": 9, - "18": 9, - "19": 1, - "20": 7, - "21": 9 - }, - "f": { "0": 9, "1": 9 }, - "b": { - "0": [1, 8], - "1": [6, 2], - "2": [8, 7], - "3": [8, 1], - "4": [8, 4, 3], - "5": [7, 1], - "6": [1, 8], - "7": [1, 6] - }, - "meta": { - "lastBranch": 8, - "lastFunction": 2, - "lastStatement": 22, - "seen": { - "s:31:34:112:Infinity": 0, - "f:31:34:31:35": 0, - "s:40:48:40:Infinity": 1, - "b:42:2:45:Infinity:undefined:undefined:undefined:undefined": 0, - "s:42:2:45:Infinity": 2, - "s:43:4:43:Infinity": 3, - "s:44:4:44:Infinity": 4, - "s:47:34:47:Infinity": 5, - "s:48:38:48:Infinity": 6, - "s:52:4:54:Infinity": 7, - "b:52:18:53:Infinity:54:8:54:Infinity": 1, - "b:52:4:52:17:52:17:52:Infinity": 2, - "s:57:2:95:Infinity": 8, - "f:57:23:57:24": 1, - "s:58:10:58:Infinity": 9, - "s:59:24:59:Infinity": 10, - "s:60:22:60:Infinity": 11, - "s:63:4:69:Infinity": 12, - "b:72:4:86:Infinity:undefined:undefined:undefined:undefined": 3, - "s:72:4:86:Infinity": 13, - "s:73:26:73:Infinity": 14, - "b:73:26:73:48:73:48:73:66:73:66:73:Infinity": 4, - "s:74:26:76:Infinity": 15, - "b:75:10:75:Infinity:76:10:76:Infinity": 5, - "s:78:6:85:Infinity": 16, - "s:89:4:94:Infinity": 17, - "b:98:2:100:Infinity:undefined:undefined:undefined:undefined": 6, - "s:98:2:100:Infinity": 18, - "s:99:4:99:Infinity": 19, - "s:103:39:109:Infinity": 20, - "b:104:6:104:Infinity:105:6:109:Infinity": 7, - "s:111:2:111:Infinity": 21 - } - } - } -} diff --git a/coverage/favicon.png b/coverage/favicon.png deleted file mode 100644 index c1525b811a167671e9de1fa78aab9f5c0b61cef7..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 445 zcmV;u0Yd(XP))rP{nL}Ln%S7`m{0DjX9TLF* zFCb$4Oi7vyLOydb!7n&^ItCzb-%BoB`=x@N2jll2Nj`kauio%aw_@fe&*}LqlFT43 z8doAAe))z_%=P%v^@JHp3Hjhj^6*Kr_h|g_Gr?ZAa&y>wxHE99Gk>A)2MplWz2xdG zy8VD2J|Uf#EAw*bo5O*PO_}X2Tob{%bUoO2G~T`@%S6qPyc}VkhV}UifBuRk>%5v( z)x7B{I~z*k<7dv#5tC+m{km(D087J4O%+<<;K|qwefb6@GSX45wCK}Sn*> - - - - Code coverage report for All files - - - - - - - - - -
-
-

All files

-
- -
- 76.27% - Statements - 418/548 -
- - -
- 73.91% - Branches - 238/322 -
- - -
- 84.82% - Functions - 95/112 -
- - -
- 76.56% - Lines - 415/542 -
- - -
-

- Press n or j to go to the next uncovered block, b, p or k for the previous block. -

- -
-
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
FileStatementsBranchesFunctionsLines
collectors -
-
100%40/40100%17/17100%25/25100%40/40
constants -
-
100%1/1100%0/0100%0/0100%1/1
core -
-
94.62%88/9380.35%45/56100%24/2494.5%86/91
indexers -
-
90.47%19/2166.66%4/6100%1/190.47%19/21
types -
-
100%10/10100%19/19100%5/5100%10/10
uploaders -
-
53.33%32/6068.18%15/2275%6/853.33%32/60
utils -
-
50.52%96/19049.57%58/11753.12%17/3251.07%95/186
visitors -
-
98.66%74/7592.59%50/54100%9/998.66%74/75
visitors/processors -
-
100%58/5896.77%30/31100%8/8100%58/58
-
-
-
- - - - - - - - \ No newline at end of file diff --git a/coverage/indexers/index.html b/coverage/indexers/index.html deleted file mode 100644 index 7e5371c46..000000000 --- a/coverage/indexers/index.html +++ /dev/null @@ -1,116 +0,0 @@ - - - - - - Code coverage report for indexers - - - - - - - - - -
-
-

All files indexers

-
- -
- 90.47% - Statements - 19/21 -
- - -
- 66.66% - Branches - 4/6 -
- - -
- 100% - Functions - 1/1 -
- - -
- 90.47% - Lines - 19/21 -
- - -
-

- Press n or j to go to the next uncovered block, b, p or k for the previous block. -

- -
-
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
FileStatementsBranchesFunctionsLines
main.ts -
-
90.47%19/2166.66%4/6100%1/190.47%19/21
-
-
-
- - - - - - - - \ No newline at end of file diff --git a/coverage/indexers/main.ts.html b/coverage/indexers/main.ts.html deleted file mode 100644 index d5c714b91..000000000 --- a/coverage/indexers/main.ts.html +++ /dev/null @@ -1,310 +0,0 @@ - - - - - - Code coverage report for indexers/main.ts - - - - - - - - - -
-
-

All files / indexers main.ts

-
- -
- 90.47% - Statements - 19/21 -
- - -
- 66.66% - Branches - 4/6 -
- - -
- 100% - Functions - 1/1 -
- - -
- 90.47% - Lines - 19/21 -
- - -
-

- Press n or j to go to the next uncovered block, b, p or k for the previous block. -

- -
-
-

-
1 -2 -3 -4 -5 -6 -7 -8 -9 -10 -11 -12 -13 -14 -15 -16 -17 -18 -19 -20 -21 -22 -23 -24 -25 -26 -27 -28 -29 -30 -31 -32 -33 -34 -35 -36 -37 -38 -39 -40 -41 -42 -43 -44 -45 -46 -47 -48 -49 -50 -51 -52 -53 -54 -55 -56 -57 -58 -59 -60 -61 -62 -63 -64 -65 -66 -67 -68 -69 -70 -71 -72 -73 -74 -75 -76  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -1x -  -  -2x -  -  -  -2x -1x -1x -  -  -1x -  -1x -1x -1x -  -  -1x -  -  -  -2x -2x -2x -  -  -  -  -2x -2x -  -  -2x -2x -  -2x -  -  -  -2x -  - 
import yaml from "js-yaml";
- 
-import {
-  batchFetchContent,
-  type ContentSource,
-} from "@/content-indexer/core/batch-fetcher.js";
-import { buildAllOutputs } from "@/content-indexer/core/build-all-outputs.js";
-import { scanDocsYml } from "@/content-indexer/core/scanner.js";
-import type { DocsYml } from "@/content-indexer/types/docsYaml.js";
-import type { IndexerResult } from "@/content-indexer/types/indexer.js";
-import { readLocalDocsYml } from "@/content-indexer/utils/filesystem.js";
-import {
-  fetchFileFromGitHub,
-  type RepoConfig,
-} from "@/content-indexer/utils/github.js";
- 
-export interface DocsIndexerConfig {
-  source: ContentSource; // filesystem or github
-  repoConfig: RepoConfig;
-  branchId: string;
-  mode?: "preview" | "production"; // Only relevant for logging
-}
- 
-/**
- * Unified docs content indexer.
- * Handles both main docs (local filesystem) and SDK refs (GitHub API).
- *
- * Processes docs.yml through 3 phases:
- * 1. SCAN - Parse docs.yml to discover all paths and specs
- * 2. BATCH FETCH - Read all content (filesystem or GitHub)
- * 3. PROCESS - Build path index, navigation trees, and Algolia records
- */
-export const buildDocsContentIndex = async (
-  config: DocsIndexerConfig,
-): Promise<IndexerResult> => {
-  console.info(`🔍 Building content index (branch: ${config.branchId})...`);
- 
-  // Read docs.yml based on source type
-  let docsYml: DocsYml;
-  if (config.source.type === "filesystem") {
-    const result = await readLocalDocsYml(config.source.basePath);
-    Iif (!result) {
-      throw new Error(`Failed to read docs.yml from ${config.source.basePath}`);
-    }
-    docsYml = result;
-  } else {
-    const docsYmlPath = `${config.repoConfig.docsPrefix}/docs.yml`;
-    const content = await fetchFileFromGitHub(docsYmlPath, config.repoConfig);
-    Iif (!content) {
-      throw new Error(`Failed to fetch ${docsYmlPath} from GitHub`);
-    }
-    docsYml = yaml.load(content) as DocsYml;
-  }
- 
-  // PHASE 1: SCAN
-  console.info("📋 Phase 1: Scanning docs.yml...");
-  const scanResult = scanDocsYml(docsYml);
-  console.info(
-    `   Found ${scanResult.mdxPaths.size} MDX files, ${scanResult.specNames.size} specs`,
-  );
- 
-  // PHASE 2: BATCH FETCH
-  console.info("📥 Phase 2: Fetching content...");
-  const contentCache = await batchFetchContent(scanResult, config.source);
- 
-  // PHASE 3: PROCESS
-  console.info("⚙️  Phase 3: Processing...");
-  const outputs = buildAllOutputs(docsYml, contentCache, config.repoConfig);
- 
-  console.info(
-    `📊 Generated ${Object.keys(outputs.pathIndex).length} routes, ${outputs.algoliaRecords.length} Algolia records`,
-  );
- 
-  return outputs;
-};
- 
- -
-
- - - - - - - - \ No newline at end of file diff --git a/coverage/prettify.css b/coverage/prettify.css deleted file mode 100644 index b317a7cda..000000000 --- a/coverage/prettify.css +++ /dev/null @@ -1 +0,0 @@ -.pln{color:#000}@media screen{.str{color:#080}.kwd{color:#008}.com{color:#800}.typ{color:#606}.lit{color:#066}.pun,.opn,.clo{color:#660}.tag{color:#008}.atn{color:#606}.atv{color:#080}.dec,.var{color:#606}.fun{color:red}}@media print,projection{.str{color:#060}.kwd{color:#006;font-weight:bold}.com{color:#600;font-style:italic}.typ{color:#404;font-weight:bold}.lit{color:#044}.pun,.opn,.clo{color:#440}.tag{color:#006;font-weight:bold}.atn{color:#404}.atv{color:#060}}pre.prettyprint{padding:2px;border:1px solid #888}ol.linenums{margin-top:0;margin-bottom:0}li.L0,li.L1,li.L2,li.L3,li.L5,li.L6,li.L7,li.L8{list-style-type:none}li.L1,li.L3,li.L5,li.L7,li.L9{background:#eee} diff --git a/coverage/prettify.js b/coverage/prettify.js deleted file mode 100644 index 87aafe233..000000000 --- a/coverage/prettify.js +++ /dev/null @@ -1,1008 +0,0 @@ -/* eslint-disable */ -window.PR_SHOULD_USE_CONTINUATION = true; -(function () { - var h = ["break,continue,do,else,for,if,return,while"]; - var u = [ - h, - "auto,case,char,const,default,double,enum,extern,float,goto,int,long,register,short,signed,sizeof,static,struct,switch,typedef,union,unsigned,void,volatile", - ]; - var p = [ - u, - "catch,class,delete,false,import,new,operator,private,protected,public,this,throw,true,try,typeof", - ]; - var l = [ - p, - "alignof,align_union,asm,axiom,bool,concept,concept_map,const_cast,constexpr,decltype,dynamic_cast,explicit,export,friend,inline,late_check,mutable,namespace,nullptr,reinterpret_cast,static_assert,static_cast,template,typeid,typename,using,virtual,where", - ]; - var x = [ - p, - "abstract,boolean,byte,extends,final,finally,implements,import,instanceof,null,native,package,strictfp,super,synchronized,throws,transient", - ]; - var R = [ - x, - "as,base,by,checked,decimal,delegate,descending,dynamic,event,fixed,foreach,from,group,implicit,in,interface,internal,into,is,lock,object,out,override,orderby,params,partial,readonly,ref,sbyte,sealed,stackalloc,string,select,uint,ulong,unchecked,unsafe,ushort,var", - ]; - var r = - "all,and,by,catch,class,else,extends,false,finally,for,if,in,is,isnt,loop,new,no,not,null,of,off,on,or,return,super,then,true,try,unless,until,when,while,yes"; - var w = [ - p, - "debugger,eval,export,function,get,null,set,undefined,var,with,Infinity,NaN", - ]; - var s = - "caller,delete,die,do,dump,elsif,eval,exit,foreach,for,goto,if,import,last,local,my,next,no,our,print,package,redo,require,sub,undef,unless,until,use,wantarray,while,BEGIN,END"; - var I = [ - h, - "and,as,assert,class,def,del,elif,except,exec,finally,from,global,import,in,is,lambda,nonlocal,not,or,pass,print,raise,try,with,yield,False,True,None", - ]; - var f = [ - h, - "alias,and,begin,case,class,def,defined,elsif,end,ensure,false,in,module,next,nil,not,or,redo,rescue,retry,self,super,then,true,undef,unless,until,when,yield,BEGIN,END", - ]; - var H = [h, "case,done,elif,esac,eval,fi,function,in,local,set,then,until"]; - var A = [l, R, w, s + I, f, H]; - var e = - /^(DIR|FILE|vector|(de|priority_)?queue|list|stack|(const_)?iterator|(multi)?(set|map)|bitset|u?(int|float)\d*)/; - var C = "str"; - var z = "kwd"; - var j = "com"; - var O = "typ"; - var G = "lit"; - var L = "pun"; - var F = "pln"; - var m = "tag"; - var E = "dec"; - var J = "src"; - var P = "atn"; - var n = "atv"; - var N = "nocode"; - var M = - "(?:^^\\.?|[+-]|\\!|\\!=|\\!==|\\#|\\%|\\%=|&|&&|&&=|&=|\\(|\\*|\\*=|\\+=|\\,|\\-=|\\->|\\/|\\/=|:|::|\\;|<|<<|<<=|<=|=|==|===|>|>=|>>|>>=|>>>|>>>=|\\?|\\@|\\[|\\^|\\^=|\\^\\^|\\^\\^=|\\{|\\||\\|=|\\|\\||\\|\\|=|\\~|break|case|continue|delete|do|else|finally|instanceof|return|throw|try|typeof)\\s*"; - function k(Z) { - var ad = 0; - var S = false; - var ac = false; - for (var V = 0, U = Z.length; V < U; ++V) { - var ae = Z[V]; - if (ae.ignoreCase) { - ac = true; - } else { - if ( - /[a-z]/i.test( - ae.source.replace(/\\u[0-9a-f]{4}|\\x[0-9a-f]{2}|\\[^ux]/gi, ""), - ) - ) { - S = true; - ac = false; - break; - } - } - } - var Y = { b: 8, t: 9, n: 10, v: 11, f: 12, r: 13 }; - function ab(ah) { - var ag = ah.charCodeAt(0); - if (ag !== 92) { - return ag; - } - var af = ah.charAt(1); - ag = Y[af]; - if (ag) { - return ag; - } else { - if ("0" <= af && af <= "7") { - return parseInt(ah.substring(1), 8); - } else { - if (af === "u" || af === "x") { - return parseInt(ah.substring(2), 16); - } else { - return ah.charCodeAt(1); - } - } - } - } - function T(af) { - if (af < 32) { - return (af < 16 ? "\\x0" : "\\x") + af.toString(16); - } - var ag = String.fromCharCode(af); - if (ag === "\\" || ag === "-" || ag === "[" || ag === "]") { - ag = "\\" + ag; - } - return ag; - } - function X(am) { - var aq = am - .substring(1, am.length - 1) - .match( - new RegExp( - "\\\\u[0-9A-Fa-f]{4}|\\\\x[0-9A-Fa-f]{2}|\\\\[0-3][0-7]{0,2}|\\\\[0-7]{1,2}|\\\\[\\s\\S]|-|[^-\\\\]", - "g", - ), - ); - var ak = []; - var af = []; - var ao = aq[0] === "^"; - for (var ar = ao ? 1 : 0, aj = aq.length; ar < aj; ++ar) { - var ah = aq[ar]; - if (/\\[bdsw]/i.test(ah)) { - ak.push(ah); - } else { - var ag = ab(ah); - var al; - if (ar + 2 < aj && "-" === aq[ar + 1]) { - al = ab(aq[ar + 2]); - ar += 2; - } else { - al = ag; - } - af.push([ag, al]); - if (!(al < 65 || ag > 122)) { - if (!(al < 65 || ag > 90)) { - af.push([Math.max(65, ag) | 32, Math.min(al, 90) | 32]); - } - if (!(al < 97 || ag > 122)) { - af.push([Math.max(97, ag) & ~32, Math.min(al, 122) & ~32]); - } - } - } - } - af.sort(function (av, au) { - return av[0] - au[0] || au[1] - av[1]; - }); - var ai = []; - var ap = [NaN, NaN]; - for (var ar = 0; ar < af.length; ++ar) { - var at = af[ar]; - if (at[0] <= ap[1] + 1) { - ap[1] = Math.max(ap[1], at[1]); - } else { - ai.push((ap = at)); - } - } - var an = ["["]; - if (ao) { - an.push("^"); - } - an.push.apply(an, ak); - for (var ar = 0; ar < ai.length; ++ar) { - var at = ai[ar]; - an.push(T(at[0])); - if (at[1] > at[0]) { - if (at[1] + 1 > at[0]) { - an.push("-"); - } - an.push(T(at[1])); - } - } - an.push("]"); - return an.join(""); - } - function W(al) { - var aj = al.source.match( - new RegExp( - "(?:\\[(?:[^\\x5C\\x5D]|\\\\[\\s\\S])*\\]|\\\\u[A-Fa-f0-9]{4}|\\\\x[A-Fa-f0-9]{2}|\\\\[0-9]+|\\\\[^ux0-9]|\\(\\?[:!=]|[\\(\\)\\^]|[^\\x5B\\x5C\\(\\)\\^]+)", - "g", - ), - ); - var ah = aj.length; - var an = []; - for (var ak = 0, am = 0; ak < ah; ++ak) { - var ag = aj[ak]; - if (ag === "(") { - ++am; - } else { - if ("\\" === ag.charAt(0)) { - var af = +ag.substring(1); - if (af && af <= am) { - an[af] = -1; - } - } - } - } - for (var ak = 1; ak < an.length; ++ak) { - if (-1 === an[ak]) { - an[ak] = ++ad; - } - } - for (var ak = 0, am = 0; ak < ah; ++ak) { - var ag = aj[ak]; - if (ag === "(") { - ++am; - if (an[am] === undefined) { - aj[ak] = "(?:"; - } - } else { - if ("\\" === ag.charAt(0)) { - var af = +ag.substring(1); - if (af && af <= am) { - aj[ak] = "\\" + an[am]; - } - } - } - } - for (var ak = 0, am = 0; ak < ah; ++ak) { - if ("^" === aj[ak] && "^" !== aj[ak + 1]) { - aj[ak] = ""; - } - } - if (al.ignoreCase && S) { - for (var ak = 0; ak < ah; ++ak) { - var ag = aj[ak]; - var ai = ag.charAt(0); - if (ag.length >= 2 && ai === "[") { - aj[ak] = X(ag); - } else { - if (ai !== "\\") { - aj[ak] = ag.replace(/[a-zA-Z]/g, function (ao) { - var ap = ao.charCodeAt(0); - return "[" + String.fromCharCode(ap & ~32, ap | 32) + "]"; - }); - } - } - } - } - return aj.join(""); - } - var aa = []; - for (var V = 0, U = Z.length; V < U; ++V) { - var ae = Z[V]; - if (ae.global || ae.multiline) { - throw new Error("" + ae); - } - aa.push("(?:" + W(ae) + ")"); - } - return new RegExp(aa.join("|"), ac ? "gi" : "g"); - } - function a(V) { - var U = /(?:^|\s)nocode(?:\s|$)/; - var X = []; - var T = 0; - var Z = []; - var W = 0; - var S; - if (V.currentStyle) { - S = V.currentStyle.whiteSpace; - } else { - if (window.getComputedStyle) { - S = document.defaultView - .getComputedStyle(V, null) - .getPropertyValue("white-space"); - } - } - var Y = S && "pre" === S.substring(0, 3); - function aa(ab) { - switch (ab.nodeType) { - case 1: - if (U.test(ab.className)) { - return; - } - for (var ae = ab.firstChild; ae; ae = ae.nextSibling) { - aa(ae); - } - var ad = ab.nodeName; - if ("BR" === ad || "LI" === ad) { - X[W] = "\n"; - Z[W << 1] = T++; - Z[(W++ << 1) | 1] = ab; - } - break; - case 3: - case 4: - var ac = ab.nodeValue; - if (ac.length) { - if (!Y) { - ac = ac.replace(/[ \t\r\n]+/g, " "); - } else { - ac = ac.replace(/\r\n?/g, "\n"); - } - X[W] = ac; - Z[W << 1] = T; - T += ac.length; - Z[(W++ << 1) | 1] = ab; - } - break; - } - } - aa(V); - return { sourceCode: X.join("").replace(/\n$/, ""), spans: Z }; - } - function B(S, U, W, T) { - if (!U) { - return; - } - var V = { sourceCode: U, basePos: S }; - W(V); - T.push.apply(T, V.decorations); - } - var v = /\S/; - function o(S) { - var V = undefined; - for (var U = S.firstChild; U; U = U.nextSibling) { - var T = U.nodeType; - V = T === 1 ? (V ? S : U) : T === 3 ? (v.test(U.nodeValue) ? S : V) : V; - } - return V === S ? undefined : V; - } - function g(U, T) { - var S = {}; - var V; - (function () { - var ad = U.concat(T); - var ah = []; - var ag = {}; - for (var ab = 0, Z = ad.length; ab < Z; ++ab) { - var Y = ad[ab]; - var ac = Y[3]; - if (ac) { - for (var ae = ac.length; --ae >= 0; ) { - S[ac.charAt(ae)] = Y; - } - } - var af = Y[1]; - var aa = "" + af; - if (!ag.hasOwnProperty(aa)) { - ah.push(af); - ag[aa] = null; - } - } - ah.push(/[\0-\uffff]/); - V = k(ah); - })(); - var X = T.length; - var W = function (ah) { - var Z = ah.sourceCode, - Y = ah.basePos; - var ad = [Y, F]; - var af = 0; - var an = Z.match(V) || []; - var aj = {}; - for (var ae = 0, aq = an.length; ae < aq; ++ae) { - var ag = an[ae]; - var ap = aj[ag]; - var ai = void 0; - var am; - if (typeof ap === "string") { - am = false; - } else { - var aa = S[ag.charAt(0)]; - if (aa) { - ai = ag.match(aa[1]); - ap = aa[0]; - } else { - for (var ao = 0; ao < X; ++ao) { - aa = T[ao]; - ai = ag.match(aa[1]); - if (ai) { - ap = aa[0]; - break; - } - } - if (!ai) { - ap = F; - } - } - am = ap.length >= 5 && "lang-" === ap.substring(0, 5); - if (am && !(ai && typeof ai[1] === "string")) { - am = false; - ap = J; - } - if (!am) { - aj[ag] = ap; - } - } - var ab = af; - af += ag.length; - if (!am) { - ad.push(Y + ab, ap); - } else { - var al = ai[1]; - var ak = ag.indexOf(al); - var ac = ak + al.length; - if (ai[2]) { - ac = ag.length - ai[2].length; - ak = ac - al.length; - } - var ar = ap.substring(5); - B(Y + ab, ag.substring(0, ak), W, ad); - B(Y + ab + ak, al, q(ar, al), ad); - B(Y + ab + ac, ag.substring(ac), W, ad); - } - } - ah.decorations = ad; - }; - return W; - } - function i(T) { - var W = [], - S = []; - if (T.tripleQuotedStrings) { - W.push([ - C, - /^(?:\'\'\'(?:[^\'\\]|\\[\s\S]|\'{1,2}(?=[^\']))*(?:\'\'\'|$)|\"\"\"(?:[^\"\\]|\\[\s\S]|\"{1,2}(?=[^\"]))*(?:\"\"\"|$)|\'(?:[^\\\']|\\[\s\S])*(?:\'|$)|\"(?:[^\\\"]|\\[\s\S])*(?:\"|$))/, - null, - "'\"", - ]); - } else { - if (T.multiLineStrings) { - W.push([ - C, - /^(?:\'(?:[^\\\']|\\[\s\S])*(?:\'|$)|\"(?:[^\\\"]|\\[\s\S])*(?:\"|$)|\`(?:[^\\\`]|\\[\s\S])*(?:\`|$))/, - null, - "'\"`", - ]); - } else { - W.push([ - C, - /^(?:\'(?:[^\\\'\r\n]|\\.)*(?:\'|$)|\"(?:[^\\\"\r\n]|\\.)*(?:\"|$))/, - null, - "\"'", - ]); - } - } - if (T.verbatimStrings) { - S.push([C, /^@\"(?:[^\"]|\"\")*(?:\"|$)/, null]); - } - var Y = T.hashComments; - if (Y) { - if (T.cStyleComments) { - if (Y > 1) { - W.push([j, /^#(?:##(?:[^#]|#(?!##))*(?:###|$)|.*)/, null, "#"]); - } else { - W.push([ - j, - /^#(?:(?:define|elif|else|endif|error|ifdef|include|ifndef|line|pragma|undef|warning)\b|[^\r\n]*)/, - null, - "#", - ]); - } - S.push([ - C, - /^<(?:(?:(?:\.\.\/)*|\/?)(?:[\w-]+(?:\/[\w-]+)+)?[\w-]+\.h|[a-z]\w*)>/, - null, - ]); - } else { - W.push([j, /^#[^\r\n]*/, null, "#"]); - } - } - if (T.cStyleComments) { - S.push([j, /^\/\/[^\r\n]*/, null]); - S.push([j, /^\/\*[\s\S]*?(?:\*\/|$)/, null]); - } - if (T.regexLiterals) { - var X = - "/(?=[^/*])(?:[^/\\x5B\\x5C]|\\x5C[\\s\\S]|\\x5B(?:[^\\x5C\\x5D]|\\x5C[\\s\\S])*(?:\\x5D|$))+/"; - S.push(["lang-regex", new RegExp("^" + M + "(" + X + ")")]); - } - var V = T.types; - if (V) { - S.push([O, V]); - } - var U = ("" + T.keywords).replace(/^ | $/g, ""); - if (U.length) { - S.push([ - z, - new RegExp("^(?:" + U.replace(/[\s,]+/g, "|") + ")\\b"), - null, - ]); - } - W.push([F, /^\s+/, null, " \r\n\t\xA0"]); - S.push( - [G, /^@[a-z_$][a-z_$@0-9]*/i, null], - [O, /^(?:[@_]?[A-Z]+[a-z][A-Za-z_$@0-9]*|\w+_t\b)/, null], - [F, /^[a-z_$][a-z_$@0-9]*/i, null], - [ - G, - new RegExp( - "^(?:0x[a-f0-9]+|(?:\\d(?:_\\d+)*\\d*(?:\\.\\d*)?|\\.\\d\\+)(?:e[+\\-]?\\d+)?)[a-z]*", - "i", - ), - null, - "0123456789", - ], - [F, /^\\[\s\S]?/, null], - [L, /^.[^\s\w\.$@\'\"\`\/\#\\]*/, null], - ); - return g(W, S); - } - var K = i({ - keywords: A, - hashComments: true, - cStyleComments: true, - multiLineStrings: true, - regexLiterals: true, - }); - function Q(V, ag) { - var U = /(?:^|\s)nocode(?:\s|$)/; - var ab = /\r\n?|\n/; - var ac = V.ownerDocument; - var S; - if (V.currentStyle) { - S = V.currentStyle.whiteSpace; - } else { - if (window.getComputedStyle) { - S = ac.defaultView - .getComputedStyle(V, null) - .getPropertyValue("white-space"); - } - } - var Z = S && "pre" === S.substring(0, 3); - var af = ac.createElement("LI"); - while (V.firstChild) { - af.appendChild(V.firstChild); - } - var W = [af]; - function ae(al) { - switch (al.nodeType) { - case 1: - if (U.test(al.className)) { - break; - } - if ("BR" === al.nodeName) { - ad(al); - if (al.parentNode) { - al.parentNode.removeChild(al); - } - } else { - for (var an = al.firstChild; an; an = an.nextSibling) { - ae(an); - } - } - break; - case 3: - case 4: - if (Z) { - var am = al.nodeValue; - var aj = am.match(ab); - if (aj) { - var ai = am.substring(0, aj.index); - al.nodeValue = ai; - var ah = am.substring(aj.index + aj[0].length); - if (ah) { - var ak = al.parentNode; - ak.insertBefore(ac.createTextNode(ah), al.nextSibling); - } - ad(al); - if (!ai) { - al.parentNode.removeChild(al); - } - } - } - break; - } - } - function ad(ak) { - while (!ak.nextSibling) { - ak = ak.parentNode; - if (!ak) { - return; - } - } - function ai(al, ar) { - var aq = ar ? al.cloneNode(false) : al; - var ao = al.parentNode; - if (ao) { - var ap = ai(ao, 1); - var an = al.nextSibling; - ap.appendChild(aq); - for (var am = an; am; am = an) { - an = am.nextSibling; - ap.appendChild(am); - } - } - return aq; - } - var ah = ai(ak.nextSibling, 0); - for (var aj; (aj = ah.parentNode) && aj.nodeType === 1; ) { - ah = aj; - } - W.push(ah); - } - for (var Y = 0; Y < W.length; ++Y) { - ae(W[Y]); - } - if (ag === (ag | 0)) { - W[0].setAttribute("value", ag); - } - var aa = ac.createElement("OL"); - aa.className = "linenums"; - var X = Math.max(0, (ag - 1) | 0) || 0; - for (var Y = 0, T = W.length; Y < T; ++Y) { - af = W[Y]; - af.className = "L" + ((Y + X) % 10); - if (!af.firstChild) { - af.appendChild(ac.createTextNode("\xA0")); - } - aa.appendChild(af); - } - V.appendChild(aa); - } - function D(ac) { - var aj = /\bMSIE\b/.test(navigator.userAgent); - var am = /\n/g; - var al = ac.sourceCode; - var an = al.length; - var V = 0; - var aa = ac.spans; - var T = aa.length; - var ah = 0; - var X = ac.decorations; - var Y = X.length; - var Z = 0; - X[Y] = an; - var ar, aq; - for (aq = ar = 0; aq < Y; ) { - if (X[aq] !== X[aq + 2]) { - X[ar++] = X[aq++]; - X[ar++] = X[aq++]; - } else { - aq += 2; - } - } - Y = ar; - for (aq = ar = 0; aq < Y; ) { - var at = X[aq]; - var ab = X[aq + 1]; - var W = aq + 2; - while (W + 2 <= Y && X[W + 1] === ab) { - W += 2; - } - X[ar++] = at; - X[ar++] = ab; - aq = W; - } - Y = X.length = ar; - var ae = null; - while (ah < T) { - var af = aa[ah]; - var S = aa[ah + 2] || an; - var ag = X[Z]; - var ap = X[Z + 2] || an; - var W = Math.min(S, ap); - var ak = aa[ah + 1]; - var U; - if (ak.nodeType !== 1 && (U = al.substring(V, W))) { - if (aj) { - U = U.replace(am, "\r"); - } - ak.nodeValue = U; - var ai = ak.ownerDocument; - var ao = ai.createElement("SPAN"); - ao.className = X[Z + 1]; - var ad = ak.parentNode; - ad.replaceChild(ao, ak); - ao.appendChild(ak); - if (V < S) { - aa[ah + 1] = ak = ai.createTextNode(al.substring(W, S)); - ad.insertBefore(ak, ao.nextSibling); - } - } - V = W; - if (V >= S) { - ah += 2; - } - if (V >= ap) { - Z += 2; - } - } - } - var t = {}; - function c(U, V) { - for (var S = V.length; --S >= 0; ) { - var T = V[S]; - if (!t.hasOwnProperty(T)) { - t[T] = U; - } else { - if (window.console) { - console.warn("cannot override language handler %s", T); - } - } - } - } - function q(T, S) { - if (!(T && t.hasOwnProperty(T))) { - T = /^\s*]*(?:>|$)/], - [j, /^<\!--[\s\S]*?(?:-\->|$)/], - ["lang-", /^<\?([\s\S]+?)(?:\?>|$)/], - ["lang-", /^<%([\s\S]+?)(?:%>|$)/], - [L, /^(?:<[%?]|[%?]>)/], - ["lang-", /^]*>([\s\S]+?)<\/xmp\b[^>]*>/i], - ["lang-js", /^]*>([\s\S]*?)(<\/script\b[^>]*>)/i], - ["lang-css", /^]*>([\s\S]*?)(<\/style\b[^>]*>)/i], - ["lang-in.tag", /^(<\/?[a-z][^<>]*>)/i], - ], - ), - ["default-markup", "htm", "html", "mxml", "xhtml", "xml", "xsl"], - ); - c( - g( - [ - [F, /^[\s]+/, null, " \t\r\n"], - [n, /^(?:\"[^\"]*\"?|\'[^\']*\'?)/, null, "\"'"], - ], - [ - [m, /^^<\/?[a-z](?:[\w.:-]*\w)?|\/?>$/i], - [P, /^(?!style[\s=]|on)[a-z](?:[\w:-]*\w)?/i], - ["lang-uq.val", /^=\s*([^>\'\"\s]*(?:[^>\'\"\s\/]|\/(?=\s)))/], - [L, /^[=<>\/]+/], - ["lang-js", /^on\w+\s*=\s*\"([^\"]+)\"/i], - ["lang-js", /^on\w+\s*=\s*\'([^\']+)\'/i], - ["lang-js", /^on\w+\s*=\s*([^\"\'>\s]+)/i], - ["lang-css", /^style\s*=\s*\"([^\"]+)\"/i], - ["lang-css", /^style\s*=\s*\'([^\']+)\'/i], - ["lang-css", /^style\s*=\s*([^\"\'>\s]+)/i], - ], - ), - ["in.tag"], - ); - c(g([], [[n, /^[\s\S]+/]]), ["uq.val"]); - c(i({ keywords: l, hashComments: true, cStyleComments: true, types: e }), [ - "c", - "cc", - "cpp", - "cxx", - "cyc", - "m", - ]); - c(i({ keywords: "null,true,false" }), ["json"]); - c( - i({ - keywords: R, - hashComments: true, - cStyleComments: true, - verbatimStrings: true, - types: e, - }), - ["cs"], - ); - c(i({ keywords: x, cStyleComments: true }), ["java"]); - c(i({ keywords: H, hashComments: true, multiLineStrings: true }), [ - "bsh", - "csh", - "sh", - ]); - c( - i({ - keywords: I, - hashComments: true, - multiLineStrings: true, - tripleQuotedStrings: true, - }), - ["cv", "py"], - ); - c( - i({ - keywords: s, - hashComments: true, - multiLineStrings: true, - regexLiterals: true, - }), - ["perl", "pl", "pm"], - ); - c( - i({ - keywords: f, - hashComments: true, - multiLineStrings: true, - regexLiterals: true, - }), - ["rb"], - ); - c(i({ keywords: w, cStyleComments: true, regexLiterals: true }), ["js"]); - c( - i({ - keywords: r, - hashComments: 3, - cStyleComments: true, - multilineStrings: true, - tripleQuotedStrings: true, - regexLiterals: true, - }), - ["coffee"], - ); - c(g([], [[C, /^[\s\S]+/]]), ["regex"]); - function d(V) { - var U = V.langExtension; - try { - var S = a(V.sourceNode); - var T = S.sourceCode; - V.sourceCode = T; - V.spans = S.spans; - V.basePos = 0; - q(U, T)(V); - D(V); - } catch (W) { - if ("console" in window) { - console.log(W && W.stack ? W.stack : W); - } - } - } - function y(W, V, U) { - var S = document.createElement("PRE"); - S.innerHTML = W; - if (U) { - Q(S, U); - } - var T = { langExtension: V, numberLines: U, sourceNode: S }; - d(T); - return S.innerHTML; - } - function b(ad) { - function Y(af) { - return document.getElementsByTagName(af); - } - var ac = [Y("pre"), Y("code"), Y("xmp")]; - var T = []; - for (var aa = 0; aa < ac.length; ++aa) { - for (var Z = 0, V = ac[aa].length; Z < V; ++Z) { - T.push(ac[aa][Z]); - } - } - ac = null; - var W = Date; - if (!W.now) { - W = { - now: function () { - return +new Date(); - }, - }; - } - var X = 0; - var S; - var ab = /\blang(?:uage)?-([\w.]+)(?!\S)/; - var ae = /\bprettyprint\b/; - function U() { - var ag = window.PR_SHOULD_USE_CONTINUATION ? W.now() + 250 : Infinity; - for (; X < T.length && W.now() < ag; X++) { - var aj = T[X]; - var ai = aj.className; - if (ai.indexOf("prettyprint") >= 0) { - var ah = ai.match(ab); - var am; - if (!ah && (am = o(aj)) && "CODE" === am.tagName) { - ah = am.className.match(ab); - } - if (ah) { - ah = ah[1]; - } - var al = false; - for (var ak = aj.parentNode; ak; ak = ak.parentNode) { - if ( - (ak.tagName === "pre" || - ak.tagName === "code" || - ak.tagName === "xmp") && - ak.className && - ak.className.indexOf("prettyprint") >= 0 - ) { - al = true; - break; - } - } - if (!al) { - var af = aj.className.match(/\blinenums\b(?::(\d+))?/); - af = af ? (af[1] && af[1].length ? +af[1] : true) : false; - if (af) { - Q(aj, af); - } - S = { langExtension: ah, sourceNode: aj, numberLines: af }; - d(S); - } - } - } - if (X < T.length) { - setTimeout(U, 250); - } else { - if (ad) { - ad(); - } - } - } - U(); - } - window.prettyPrintOne = y; - window.prettyPrint = b; - window.PR = { - createSimpleLexer: g, - registerLangHandler: c, - sourceDecorator: i, - PR_ATTRIB_NAME: P, - PR_ATTRIB_VALUE: n, - PR_COMMENT: j, - PR_DECLARATION: E, - PR_KEYWORD: z, - PR_LITERAL: G, - PR_NOCODE: N, - PR_PLAIN: F, - PR_PUNCTUATION: L, - PR_SOURCE: J, - PR_STRING: C, - PR_TAG: m, - PR_TYPE: O, - }; -})(); -PR.registerLangHandler( - PR.createSimpleLexer( - [], - [ - [PR.PR_DECLARATION, /^]*(?:>|$)/], - [PR.PR_COMMENT, /^<\!--[\s\S]*?(?:-\->|$)/], - [PR.PR_PUNCTUATION, /^(?:<[%?]|[%?]>)/], - ["lang-", /^<\?([\s\S]+?)(?:\?>|$)/], - ["lang-", /^<%([\s\S]+?)(?:%>|$)/], - ["lang-", /^]*>([\s\S]+?)<\/xmp\b[^>]*>/i], - [ - "lang-handlebars", - /^]*type\s*=\s*['"]?text\/x-handlebars-template['"]?\b[^>]*>([\s\S]*?)(<\/script\b[^>]*>)/i, - ], - ["lang-js", /^]*>([\s\S]*?)(<\/script\b[^>]*>)/i], - ["lang-css", /^]*>([\s\S]*?)(<\/style\b[^>]*>)/i], - ["lang-in.tag", /^(<\/?[a-z][^<>]*>)/i], - [PR.PR_DECLARATION, /^{{[#^>/]?\s*[\w.][^}]*}}/], - [PR.PR_DECLARATION, /^{{&?\s*[\w.][^}]*}}/], - [PR.PR_DECLARATION, /^{{{>?\s*[\w.][^}]*}}}/], - [PR.PR_COMMENT, /^{{![^}]*}}/], - ], - ), - ["handlebars", "hbs"], -); -PR.registerLangHandler( - PR.createSimpleLexer( - [[PR.PR_PLAIN, /^[ \t\r\n\f]+/, null, " \t\r\n\f"]], - [ - [ - PR.PR_STRING, - /^\"(?:[^\n\r\f\\\"]|\\(?:\r\n?|\n|\f)|\\[\s\S])*\"/, - null, - ], - [ - PR.PR_STRING, - /^\'(?:[^\n\r\f\\\']|\\(?:\r\n?|\n|\f)|\\[\s\S])*\'/, - null, - ], - ["lang-css-str", /^url\(([^\)\"\']*)\)/i], - [ - PR.PR_KEYWORD, - /^(?:url|rgb|\!important|@import|@page|@media|@charset|inherit)(?=[^\-\w]|$)/i, - null, - ], - [ - "lang-css-kw", - /^(-?(?:[_a-z]|(?:\\[0-9a-f]+ ?))(?:[_a-z0-9\-]|\\(?:\\[0-9a-f]+ ?))*)\s*:/i, - ], - [PR.PR_COMMENT, /^\/\*[^*]*\*+(?:[^\/*][^*]*\*+)*\//], - [PR.PR_COMMENT, /^(?:)/], - [PR.PR_LITERAL, /^(?:\d+|\d*\.\d+)(?:%|[a-z]+)?/i], - [PR.PR_LITERAL, /^#(?:[0-9a-f]{3}){1,2}/i], - [ - PR.PR_PLAIN, - /^-?(?:[_a-z]|(?:\\[\da-f]+ ?))(?:[_a-z\d\-]|\\(?:\\[\da-f]+ ?))*/i, - ], - [PR.PR_PUNCTUATION, /^[^\s\w\'\"]+/], - ], - ), - ["css"], -); -PR.registerLangHandler( - PR.createSimpleLexer( - [], - [ - [ - PR.PR_KEYWORD, - /^-?(?:[_a-z]|(?:\\[\da-f]+ ?))(?:[_a-z\d\-]|\\(?:\\[\da-f]+ ?))*/i, - ], - ], - ), - ["css-kw"], -); -PR.registerLangHandler( - PR.createSimpleLexer([], [[PR.PR_STRING, /^[^\)\"\']+/]]), - ["css-str"], -); diff --git a/coverage/sort-arrow-sprite.png b/coverage/sort-arrow-sprite.png deleted file mode 100644 index 6ed68316eb3f65dec9063332d2f69bf3093bbfab..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 138 zcmeAS@N?(olHy`uVBq!ia0vp^>_9Bd!3HEZxJ@+%Qh}Z>jv*C{$p!i!8j}?a+@3A= zIAGwzjijN=FBi!|L1t?LM;Q;gkwn>2cAy-KV{dn nf0J1DIvEHQu*n~6U}x}qyky7vi4|9XhBJ7&`njxgN@xNA8m%nc diff --git a/coverage/sorter.js b/coverage/sorter.js deleted file mode 100644 index dae7b4a41..000000000 --- a/coverage/sorter.js +++ /dev/null @@ -1,209 +0,0 @@ -/* eslint-disable */ -var addSorting = (function () { - "use strict"; - var cols, - currentSort = { - index: 0, - desc: false, - }; - - // returns the summary table element - function getTable() { - return document.querySelector(".coverage-summary"); - } - // returns the thead element of the summary table - function getTableHeader() { - return getTable().querySelector("thead tr"); - } - // returns the tbody element of the summary table - function getTableBody() { - return getTable().querySelector("tbody"); - } - // returns the th element for nth column - function getNthColumn(n) { - return getTableHeader().querySelectorAll("th")[n]; - } - - function onFilterInput() { - const searchValue = document.getElementById("fileSearch").value; - const rows = document.getElementsByTagName("tbody")[0].children; - - // Try to create a RegExp from the searchValue. If it fails (invalid regex), - // it will be treated as a plain text search - let searchRegex; - try { - searchRegex = new RegExp(searchValue, "i"); // 'i' for case-insensitive - } catch (error) { - searchRegex = null; - } - - for (let i = 0; i < rows.length; i++) { - const row = rows[i]; - let isMatch = false; - - if (searchRegex) { - // If a valid regex was created, use it for matching - isMatch = searchRegex.test(row.textContent); - } else { - // Otherwise, fall back to the original plain text search - isMatch = row.textContent - .toLowerCase() - .includes(searchValue.toLowerCase()); - } - - row.style.display = isMatch ? "" : "none"; - } - } - - // loads the search box - function addSearchBox() { - var template = document.getElementById("filterTemplate"); - var templateClone = template.content.cloneNode(true); - templateClone.getElementById("fileSearch").oninput = onFilterInput; - template.parentElement.appendChild(templateClone); - } - - // loads all columns - function loadColumns() { - var colNodes = getTableHeader().querySelectorAll("th"), - colNode, - cols = [], - col, - i; - - for (i = 0; i < colNodes.length; i += 1) { - colNode = colNodes[i]; - col = { - key: colNode.getAttribute("data-col"), - sortable: !colNode.getAttribute("data-nosort"), - type: colNode.getAttribute("data-type") || "string", - }; - cols.push(col); - if (col.sortable) { - col.defaultDescSort = col.type === "number"; - colNode.innerHTML = colNode.innerHTML + ''; - } - } - return cols; - } - // attaches a data attribute to every tr element with an object - // of data values keyed by column name - function loadRowData(tableRow) { - var tableCols = tableRow.querySelectorAll("td"), - colNode, - col, - data = {}, - i, - val; - for (i = 0; i < tableCols.length; i += 1) { - colNode = tableCols[i]; - col = cols[i]; - val = colNode.getAttribute("data-value"); - if (col.type === "number") { - val = Number(val); - } - data[col.key] = val; - } - return data; - } - // loads all row data - function loadData() { - var rows = getTableBody().querySelectorAll("tr"), - i; - - for (i = 0; i < rows.length; i += 1) { - rows[i].data = loadRowData(rows[i]); - } - } - // sorts the table using the data for the ith column - function sortByIndex(index, desc) { - var key = cols[index].key, - sorter = function (a, b) { - a = a.data[key]; - b = b.data[key]; - return a < b ? -1 : a > b ? 1 : 0; - }, - finalSorter = sorter, - tableBody = document.querySelector(".coverage-summary tbody"), - rowNodes = tableBody.querySelectorAll("tr"), - rows = [], - i; - - if (desc) { - finalSorter = function (a, b) { - return -1 * sorter(a, b); - }; - } - - for (i = 0; i < rowNodes.length; i += 1) { - rows.push(rowNodes[i]); - tableBody.removeChild(rowNodes[i]); - } - - rows.sort(finalSorter); - - for (i = 0; i < rows.length; i += 1) { - tableBody.appendChild(rows[i]); - } - } - // removes sort indicators for current column being sorted - function removeSortIndicators() { - var col = getNthColumn(currentSort.index), - cls = col.className; - - cls = cls.replace(/ sorted$/, "").replace(/ sorted-desc$/, ""); - col.className = cls; - } - // adds sort indicators for current column being sorted - function addSortIndicators() { - getNthColumn(currentSort.index).className += currentSort.desc - ? " sorted-desc" - : " sorted"; - } - // adds event listeners for all sorter widgets - function enableUI() { - var i, - el, - ithSorter = function ithSorter(i) { - var col = cols[i]; - - return function () { - var desc = col.defaultDescSort; - - if (currentSort.index === i) { - desc = !currentSort.desc; - } - sortByIndex(i, desc); - removeSortIndicators(); - currentSort.index = i; - currentSort.desc = desc; - addSortIndicators(); - }; - }; - for (i = 0; i < cols.length; i += 1) { - if (cols[i].sortable) { - // add the click event handler on the th so users - // dont have to click on those tiny arrows - el = getNthColumn(i).querySelector(".sorter").parentElement; - if (el.addEventListener) { - el.addEventListener("click", ithSorter(i)); - } else { - el.attachEvent("onclick", ithSorter(i)); - } - } - } - } - // adds sorting functionality to the UI - return function () { - if (!getTable()) { - return; - } - cols = loadColumns(); - loadData(); - addSearchBox(); - addSortIndicators(); - enableUI(); - }; -})(); - -window.addEventListener("load", addSorting); diff --git a/coverage/types/docsYaml.ts.html b/coverage/types/docsYaml.ts.html deleted file mode 100644 index 06ddac807..000000000 --- a/coverage/types/docsYaml.ts.html +++ /dev/null @@ -1,358 +0,0 @@ - - - - - - Code coverage report for types/docsYaml.ts - - - - - - - - - -
-
-

All files / types docsYaml.ts

-
- -
- 100% - Statements - 10/10 -
- - -
- 100% - Branches - 19/19 -
- - -
- 100% - Functions - 5/5 -
- - -
- 100% - Lines - 10/10 -
- - -
-

- Press n or j to go to the next uncovered block, b, p or k for the previous block. -

- -
-
-

-
1 -2 -3 -4 -5 -6 -7 -8 -9 -10 -11 -12 -13 -14 -15 -16 -17 -18 -19 -20 -21 -22 -23 -24 -25 -26 -27 -28 -29 -30 -31 -32 -33 -34 -35 -36 -37 -38 -39 -40 -41 -42 -43 -44 -45 -46 -47 -48 -49 -50 -51 -52 -53 -54 -55 -56 -57 -58 -59 -60 -61 -62 -63 -64 -65 -66 -67 -68 -69 -70 -71 -72 -73 -74 -75 -76 -77 -78 -79 -80 -81 -82 -83 -84 -85 -86 -87 -88 -89 -90 -91 -92  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -3x -34x -  -  -3x -  -  -9x -  -  -  -  -3x -41x -  -  -3x -21x -  -  -  -  -3x -  -  -43x -  - 
// Types for parsing Fern's docs.yml structure
- 
-export interface PageConfig {
-  page: string;
-  path: string;
-  slug?: string;
-  hidden?: boolean;
-  noindex?: boolean;
-}
- 
-export interface SectionConfig {
-  section: string;
-  slug?: string;
-  "skip-slug"?: boolean;
-  hidden?: boolean;
-  contents: NavigationItem[];
-  path?: string; // Optional overview page
-}
- 
-export interface LinkConfig {
-  link: string;
-  href: string;
-}
- 
-export interface ApiConfig {
-  api: string;
-  "api-name": string;
-  slug?: string;
-  "skip-slug"?: boolean;
-  hidden?: boolean;
-  flattened?: boolean;
-  paginated?: boolean;
-}
- 
-export interface ChangelogConfig {
-  changelog: string;
-  slug?: string;
-}
- 
-export type NavigationItem =
-  | PageConfig
-  | SectionConfig
-  | LinkConfig
-  | ApiConfig
-  | ChangelogConfig;
- 
-export interface TabConfig {
-  "display-name": string;
-  slug?: string;
-  "skip-slug"?: boolean;
-}
- 
-export interface DocsYml {
-  tabs?: Record<string, TabConfig>;
-  navigation: Array<{
-    tab: string;
-    layout: NavigationItem[];
-  }>;
-}
- 
-// ============================================================================
-// Type Guards
-// ============================================================================
- 
-export const isPageConfig = (item: NavigationItem): item is PageConfig => {
-  return item && typeof item === "object" && "page" in item && "path" in item;
-};
- 
-export const isSectionConfig = (
-  item: NavigationItem,
-): item is SectionConfig => {
-  return (
-    item && typeof item === "object" && "section" in item && "contents" in item
-  );
-};
- 
-export const isLinkConfig = (item: NavigationItem): item is LinkConfig => {
-  return item && typeof item === "object" && "link" in item && "href" in item;
-};
- 
-export const isApiConfig = (item: NavigationItem): item is ApiConfig => {
-  return (
-    item && typeof item === "object" && "api" in item && "api-name" in item
-  );
-};
- 
-export const isChangelogConfig = (
-  item: NavigationItem,
-): item is ChangelogConfig => {
-  return item && typeof item === "object" && "changelog" in item;
-};
- 
- -
-
- - - - - - - - \ No newline at end of file diff --git a/coverage/types/index.html b/coverage/types/index.html deleted file mode 100644 index e9aa67bbc..000000000 --- a/coverage/types/index.html +++ /dev/null @@ -1,116 +0,0 @@ - - - - - - Code coverage report for types - - - - - - - - - -
-
-

All files types

-
- -
- 100% - Statements - 10/10 -
- - -
- 100% - Branches - 19/19 -
- - -
- 100% - Functions - 5/5 -
- - -
- 100% - Lines - 10/10 -
- - -
-

- Press n or j to go to the next uncovered block, b, p or k for the previous block. -

- -
-
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
FileStatementsBranchesFunctionsLines
docsYaml.ts -
-
100%10/10100%19/19100%5/5100%10/10
-
-
-
- - - - - - - - \ No newline at end of file diff --git a/coverage/uploaders/algolia.ts.html b/coverage/uploaders/algolia.ts.html deleted file mode 100644 index a0dcf5c72..000000000 --- a/coverage/uploaders/algolia.ts.html +++ /dev/null @@ -1,472 +0,0 @@ - - - - - - Code coverage report for uploaders/algolia.ts - - - - - - - - - -
-
-

All files / uploaders algolia.ts

-
- -
- 36.36% - Statements - 12/33 -
- - -
- 62.5% - Branches - 5/8 -
- - -
- 50% - Functions - 1/2 -
- - -
- 36.36% - Lines - 12/33 -
- - -
-

- Press n or j to go to the next uncovered block, b, p or k for the previous block. -

- -
-
-

-
1 -2 -3 -4 -5 -6 -7 -8 -9 -10 -11 -12 -13 -14 -15 -16 -17 -18 -19 -20 -21 -22 -23 -24 -25 -26 -27 -28 -29 -30 -31 -32 -33 -34 -35 -36 -37 -38 -39 -40 -41 -42 -43 -44 -45 -46 -47 -48 -49 -50 -51 -52 -53 -54 -55 -56 -57 -58 -59 -60 -61 -62 -63 -64 -65 -66 -67 -68 -69 -70 -71 -72 -73 -74 -75 -76 -77 -78 -79 -80 -81 -82 -83 -84 -85 -86 -87 -88 -89 -90 -91 -92 -93 -94 -95 -96 -97 -98 -99 -100 -101 -102 -103 -104 -105 -106 -107 -108 -109 -110 -111 -112 -113 -114 -115 -116 -117 -118 -119 -120 -121 -122 -123 -124 -125 -126 -127 -128 -129 -130  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -1x -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -1x -  -  -  -  -  -  -4x -4x -4x -  -4x -2x -2x -  -  -2x -  -  -2x -2x -  -  -2x -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  - 
import { algoliasearch } from "algoliasearch";
- 
-import type { AlgoliaRecord } from "@/content-indexer/types/algolia.js";
-import { truncateRecord } from "@/content-indexer/utils/truncate-record.js";
- 
-/**
- * Builds an Algolia index name with branch and type scoping.
- * Pattern: {branchId}_{baseName}[_{indexerType}]
- *
- * Examples:
- * - main_alchemy_docs (main branch, main content)
- * - main_alchemy_docs_sdk (main branch, SDK content)
- * - abc_alchemy_docs (branch-abc, main content)
- * - abc_alchemy_docs_sdk (branch-abc, SDK content)
- */
-const buildIndexName = (
-  base: string,
-  indexerType: "main" | "sdk" | "changelog",
-  branchId: string,
-): string => {
-  const parts = [branchId, base];
- 
-  // Add type suffix (except for main content)
-  if (indexerType !== "main") {
-    parts.push(indexerType);
-  }
- 
-  return parts.join("_");
-};
- 
-/**
- * Uploads records to Algolia using atomic index swap for zero-downtime updates.
- *
- * Process:
- * 1. Upload all records to a temporary index
- * 2. Copy settings/synonyms from production index (if exists)
- * 3. Atomically swap temp index to production
- *
- * This ensures users never see empty search results during updates.
- *
- * @param records - Algolia records to upload
- * @param options - Configuration options
- * @param options.indexerType - Type of indexer ("main", "sdk", or "changelog")
- * @param options.branchId - Branch identifier for index naming (e.g., "main", "branch-abc")
- */
-export const uploadToAlgolia = async (
-  records: AlgoliaRecord[],
-  options: {
-    indexerType: "main" | "sdk" | "changelog";
-    branchId: string;
-  },
-): Promise<void> => {
-  const appId = process.env.ALGOLIA_APP_ID;
-  const adminKey = process.env.ALGOLIA_ADMIN_API_KEY;
-  const baseName = process.env.ALGOLIA_INDEX_NAME_BASE;
- 
-  if (!appId || !adminKey) {
-    console.warn("⚠️  Algolia credentials not found. Skipping Algolia upload.");
-    console.warn(
-      "   Set ALGOLIA_APP_ID and ALGOLIA_ADMIN_API_KEY to enable search indexing.",
-    );
-    return;
-  }
- 
-  Eif (!baseName) {
-    console.warn(
-      "⚠️  ALGOLIA_INDEX_NAME_BASE not set. Skipping Algolia upload.",
-    );
-    return;
-  }
- 
-  const targetIndexName = buildIndexName(
-    baseName,
-    options.indexerType,
-    options.branchId,
-  );
- 
-  const client = algoliasearch(appId, adminKey);
-  const tempIndexName = `${targetIndexName}_temp`;
- 
-  console.info(
-    `📤 Uploading ${records.length} records to Algolia (${targetIndexName})...`,
-  );
- 
-  // Truncate records to fit Algolia's 100KB limit
-  const truncatedRecords = records.map(truncateRecord);
- 
-  try {
-    // 1. Upload all records to temporary index
-    await client.saveObjects({
-      indexName: tempIndexName,
-      objects: truncatedRecords as unknown as Array<Record<string, unknown>>,
-    });
- 
-    console.info(`   ✓ Uploaded ${records.length} records to ${tempIndexName}`);
- 
-    // 2. Copy settings/synonyms from production index (if it exists)
-    try {
-      await client.operationIndex({
-        indexName: targetIndexName,
-        operationIndexParams: {
-          operation: "copy",
-          destination: tempIndexName,
-          scope: ["settings", "synonyms", "rules"],
-        },
-      });
-      console.info("   ✓ Copied settings from production index");
-    } catch (_error) {
-      console.info(
-        "   ℹ️  No existing production index found (might be first run)",
-      );
-    }
- 
-    // 3. Atomic swap: move temp index to production
-    console.info(`   🔄 Swapping ${tempIndexName} → ${targetIndexName}...`);
-    await client.operationIndex({
-      indexName: tempIndexName,
-      operationIndexParams: {
-        operation: "move",
-        destination: targetIndexName,
-      },
-    });
- 
-    console.info(`✅ Successfully updated Algolia index: ${targetIndexName}`);
-  } catch (error) {
-    console.error("❌ Failed to upload to Algolia:", error);
-    throw error;
-  }
-};
- 
- -
-
- - - - - - - - \ No newline at end of file diff --git a/coverage/uploaders/index.html b/coverage/uploaders/index.html deleted file mode 100644 index e55596976..000000000 --- a/coverage/uploaders/index.html +++ /dev/null @@ -1,131 +0,0 @@ - - - - - - Code coverage report for uploaders - - - - - - - - - -
-
-

All files uploaders

-
- -
- 53.33% - Statements - 32/60 -
- - -
- 68.18% - Branches - 15/22 -
- - -
- 75% - Functions - 6/8 -
- - -
- 53.33% - Lines - 32/60 -
- - -
-

- Press n or j to go to the next uncovered block, b, p or k for the previous block. -

- -
-
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
FileStatementsBranchesFunctionsLines
algolia.ts -
-
36.36%12/3362.5%5/850%1/236.36%12/33
redis.ts -
-
74.07%20/2771.42%10/1483.33%5/674.07%20/27
-
-
-
- - - - - - - - \ No newline at end of file diff --git a/coverage/uploaders/redis.ts.html b/coverage/uploaders/redis.ts.html deleted file mode 100644 index 77398b556..000000000 --- a/coverage/uploaders/redis.ts.html +++ /dev/null @@ -1,364 +0,0 @@ - - - - - - Code coverage report for uploaders/redis.ts - - - - - - - - - -
-
-

All files / uploaders redis.ts

-
- -
- 74.07% - Statements - 20/27 -
- - -
- 71.42% - Branches - 10/14 -
- - -
- 83.33% - Functions - 5/6 -
- - -
- 74.07% - Lines - 20/27 -
- - -
-

- Press n or j to go to the next uncovered block, b, p or k for the previous block. -

- -
-
-

-
1 -2 -3 -4 -5 -6 -7 -8 -9 -10 -11 -12 -13 -14 -15 -16 -17 -18 -19 -20 -21 -22 -23 -24 -25 -26 -27 -28 -29 -30 -31 -32 -33 -34 -35 -36 -37 -38 -39 -40 -41 -42 -43 -44 -45 -46 -47 -48 -49 -50 -51 -52 -53 -54 -55 -56 -57 -58 -59 -60 -61 -62 -63 -64 -65 -66 -67 -68 -69 -70 -71 -72 -73 -74 -75 -76 -77 -78 -79 -80 -81 -82 -83 -84 -85 -86 -87 -88 -89 -90 -91 -92 -93 -94  -  -  -  -  -  -  -  -  -1x -5x -  -5x -  -  -5x -  -  -  -  -  -  -  -  -  -  -  -  -1x -  -  -  -  -  -  -  -5x -  -  -5x -5x -  -  -5x -  -  -  -  -  -5x -  -5x -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -5x -  -3x -  -5x -5x -  -  -5x -  -  -  -  -5x -5x -5x -  -  -  -  -  -  -5x -  - 
import type {
-  NavigationTree,
-  NavigationTreesByTab,
-} from "@/content-indexer/types/navigation.js";
-import type { PathIndex } from "@/content-indexer/types/pathIndex.js";
-import { mergeWalletsNavTree } from "@/content-indexer/utils/nav-tree-merge.js";
-import { getRedis } from "@/content-indexer/utils/redis.js";
- 
-// Helper to count nav items recursively
-const countItems = (items: NavigationTree): number => {
-  return items.reduce((sum, item) => {
-    const childCount =
-      item.type === "section" || item.type === "api-section"
-        ? countItems(item.children)
-        : 0;
-    return sum + 1 + childCount;
-  }, 0);
-};
- 
-/**
- * Stores path index and navigation trees to Redis with branch scoping.
- *
- * @param pathIndex - The path index to store
- * @param navigationTrees - Navigation trees (optional for SDK/changelog indexers)
- * @param options - Configuration options
- * @param options.branchId - Branch identifier for Redis keys (e.g., "main", "branch-abc123")
- * @param options.indexerType - Type of indexer ("main", "sdk", or "changelog")
- */
-export const storeToRedis = async (
-  pathIndex: PathIndex,
-  navigationTrees: NavigationTreesByTab | undefined,
-  options: {
-    branchId: string;
-    indexerType: "main" | "sdk" | "changelog";
-  },
-): Promise<void> => {
-  const redis = getRedis();
- 
-  // Store path index with branch scope
-  const pathIndexKey = `${options.branchId}/path-index:${options.indexerType}`;
-  const pathIndexPromise = redis
-    .set(pathIndexKey, JSON.stringify(pathIndex, null, 2))
-    .then(() => {
-      console.info(
-        `✅ Path index saved to Redis (${Object.keys(pathIndex).length} routes) -> ${pathIndexKey}`,
-      );
-    });
- 
-  // Handle navigation trees
-  let navTreePromises: Promise<void>[] = [];
- 
-  Iif (options.indexerType === "sdk" && navigationTrees?.wallets) {
-    // SDK indexer: merge SDK section into existing wallets nav tree
-    const navTreeKey = `${options.branchId}/nav-tree:wallets`;
-    const existingTree = await redis.get<NavigationTree>(navTreeKey);
- 
-    const mergedTree = mergeWalletsNavTree(
-      navigationTrees.wallets,
-      existingTree,
-      "sdk",
-    );
- 
-    navTreePromises = [
-      redis.set(navTreeKey, JSON.stringify(mergedTree, null, 2)).then(() => {
-        console.info(
-          `✅ Updated wallets nav tree with SDK refs (${countItems(mergedTree)} total items) -> ${navTreeKey}`,
-        );
-      }),
-    ];
-  } else if (navigationTrees) {
-    // Main indexer: store all navigation trees
-    navTreePromises = Object.entries(navigationTrees).map(
-      async ([tab, navTree]) => {
-        const redisKey = `${options.branchId}/nav-tree:${tab}`;
-        let finalTree = navTree;
- 
-        // Main indexer: preserve SDK references in wallets tab
-        Iif (tab === "wallets" && options.indexerType === "main") {
-          const existingTree = await redis.get<NavigationTree>(redisKey);
-          finalTree = mergeWalletsNavTree(navTree, existingTree, "main");
-        }
- 
-        const itemCount = countItems(finalTree);
-        await redis.set(redisKey, JSON.stringify(finalTree, null, 2));
-        console.info(
-          `✅ Navigation tree for '${tab}' saved to Redis (${itemCount} items) -> ${redisKey}`,
-        );
-      },
-    );
-  }
- 
-  await Promise.all([pathIndexPromise, ...navTreePromises]);
-};
- 
- -
-
- - - - - - - - \ No newline at end of file diff --git a/coverage/utils/filesystem.ts.html b/coverage/utils/filesystem.ts.html deleted file mode 100644 index be788ece7..000000000 --- a/coverage/utils/filesystem.ts.html +++ /dev/null @@ -1,298 +0,0 @@ - - - - - - Code coverage report for utils/filesystem.ts - - - - - - - - - -
-
-

All files / utils filesystem.ts

-
- -
- 12% - Statements - 3/25 -
- - -
- 0% - Branches - 0/4 -
- - -
- 0% - Functions - 0/3 -
- - -
- 12% - Lines - 3/25 -
- - -
-

- Press n or j to go to the next uncovered block, b, p or k for the previous block. -

- -
-
-

-
1 -2 -3 -4 -5 -6 -7 -8 -9 -10 -11 -12 -13 -14 -15 -16 -17 -18 -19 -20 -21 -22 -23 -24 -25 -26 -27 -28 -29 -30 -31 -32 -33 -34 -35 -36 -37 -38 -39 -40 -41 -42 -43 -44 -45 -46 -47 -48 -49 -50 -51 -52 -53 -54 -55 -56 -57 -58 -59 -60 -61 -62 -63 -64 -65 -66 -67 -68 -69 -70 -71 -72  -  -  -  -  -  -  -  -  -  -2x -  -  -  -  -  -  -  -  -  -  -  -  -  -  -2x -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -2x -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  - 
import { promises as fs } from "fs";
-import matter from "gray-matter";
-import yaml from "js-yaml";
-import path from "path";
- 
-import type { DocsYml } from "@/content-indexer/types/docsYaml.js";
- 
-/**
- * Reads a file from the local filesystem
- */
-export const readLocalFile = async (
-  filePath: string,
-): Promise<string | null> => {
-  try {
-    const content = await fs.readFile(filePath, "utf-8");
-    return content;
-  } catch (error) {
-    console.warn(`Failed to read file: ${filePath}`, error);
-    return null;
-  }
-};
- 
-/**
- * Reads and parses a local docs.yml file
- */
-export const readLocalDocsYml = async (
-  baseDir: string,
-): Promise<DocsYml | null> => {
-  try {
-    const docsYmlPath = path.join(baseDir, "docs.yml");
-    const content = await readLocalFile(docsYmlPath);
- 
-    if (!content) {
-      throw new Error(`Failed to read docs.yml from ${docsYmlPath}`);
-    }
- 
-    const docsYml = yaml.load(content) as DocsYml;
-    return docsYml;
-  } catch (error) {
-    console.error(`Error reading/parsing docs.yml from ${baseDir}:`, error);
-    return null;
-  }
-};
- 
-/**
- * Reads a local MDX file and parses its frontmatter
- */
-export const readLocalMdxFile = async (
-  filePath: string,
-): Promise<{
-  frontmatter: Record<string, unknown>;
-  content: string;
-} | null> => {
-  try {
-    const fileContent = await readLocalFile(filePath);
- 
-    if (!fileContent) {
-      return null;
-    }
- 
-    const { data, content } = matter(fileContent);
- 
-    return {
-      frontmatter: data,
-      content,
-    };
-  } catch (error) {
-    console.warn(`Failed to parse MDX file: ${filePath}`, error);
-    return null;
-  }
-};
- 
- -
-
- - - - - - - - \ No newline at end of file diff --git a/coverage/utils/github.ts.html b/coverage/utils/github.ts.html deleted file mode 100644 index 4f85400e6..000000000 --- a/coverage/utils/github.ts.html +++ /dev/null @@ -1,691 +0,0 @@ - - - - - - Code coverage report for utils/github.ts - - - - - - - - - -
-
-

All files / utils github.ts

-
- -
- 6% - Statements - 3/50 -
- - -
- 0% - Branches - 0/30 -
- - -
- 0% - Functions - 0/8 -
- - -
- 6.25% - Lines - 3/48 -
- - -
-

- Press n or j to go to the next uncovered block, b, p or k for the previous block. -

- -
-
-

-
1 -2 -3 -4 -5 -6 -7 -8 -9 -10 -11 -12 -13 -14 -15 -16 -17 -18 -19 -20 -21 -22 -23 -24 -25 -26 -27 -28 -29 -30 -31 -32 -33 -34 -35 -36 -37 -38 -39 -40 -41 -42 -43 -44 -45 -46 -47 -48 -49 -50 -51 -52 -53 -54 -55 -56 -57 -58 -59 -60 -61 -62 -63 -64 -65 -66 -67 -68 -69 -70 -71 -72 -73 -74 -75 -76 -77 -78 -79 -80 -81 -82 -83 -84 -85 -86 -87 -88 -89 -90 -91 -92 -93 -94 -95 -96 -97 -98 -99 -100 -101 -102 -103 -104 -105 -106 -107 -108 -109 -110 -111 -112 -113 -114 -115 -116 -117 -118 -119 -120 -121 -122 -123 -124 -125 -126 -127 -128 -129 -130 -131 -132 -133 -134 -135 -136 -137 -138 -139 -140 -141 -142 -143 -144 -145 -146 -147 -148 -149 -150 -151 -152 -153 -154 -155 -156 -157 -158 -159 -160 -161 -162 -163 -164 -165 -166 -167 -168 -169 -170 -171 -172 -173 -174 -175 -176 -177 -178 -179 -180 -181 -182 -183 -184 -185 -186 -187 -188 -189 -190 -191 -192 -193 -194 -195 -196 -197 -198 -199 -200 -201 -202 -203  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -10x -  -  -  -  -  -  -10x -  -  -  -  -  -  -  -10x -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  - 
import { Octokit, RequestError } from "octokit";
- 
-type GetContentResponse = Awaited<
-  ReturnType<InstanceType<typeof Octokit>["rest"]["repos"]["getContent"]>
->;
-type GetContentData = GetContentResponse["data"];
- 
-type ContentDirectoryItem = Extract<GetContentData, Array<unknown>>[number];
- 
-// ============================================================================
-// Repository Configuration
-// ============================================================================
- 
-export interface RepoConfig {
-  owner: string;
-  repo: string;
-  branch: string;
-  docsPrefix: string; // "fern" or "docs" - folder where MDX files are stored
-  stripPathPrefix?: string; // Optional prefix to strip from paths in docs.yml (e.g., "wallets/")
-}
- 
-export const DOCS_REPO: RepoConfig = {
-  owner: "alchemyplatform",
-  repo: "docs",
-  branch: "main",
-  docsPrefix: "fern",
-};
- 
-export const WALLET_REPO: RepoConfig = {
-  owner: "alchemyplatform",
-  repo: "aa-sdk",
-  branch: "main",
-  docsPrefix: "docs",
-  stripPathPrefix: "wallets/", // aa-sdk docs.yml uses "wallets/pages/..." but actual files are at "docs/pages/..."
-};
- 
-const octokit = new Octokit({
-  auth: process.env.GH_TOKEN,
-});
- 
-function isRequestError(error: unknown): error is RequestError {
-  return error instanceof RequestError;
-}
- 
-/**
- * Fetch a single file's text content from GitHub
- */
-export async function fetchFileFromGitHub(
-  filePath: string,
-  repoConfig: RepoConfig = DOCS_REPO,
-): Promise<string | null> {
-  try {
-    const { data } = await octokit.rest.repos.getContent({
-      owner: repoConfig.owner,
-      repo: repoConfig.repo,
-      path: filePath,
-      ref: repoConfig.branch,
-      mediaType: {
-        format: "raw",
-      },
-    });
- 
-    // When using raw format, data is a string
-    return data as unknown as string;
-  } catch (error: unknown) {
-    if (isRequestError(error) && error.status === 404) {
-      return null; // File doesn't exist
-    }
-    console.error(`Error fetching ${filePath} from ${repoConfig.repo}:`, error);
-    return null;
-  }
-}
- 
-/**
- * Fetch directory contents from GitHub (non-recursive)
- */
-export async function fetchGitHubDirectory(
-  dirPath: string,
-  repoConfig: RepoConfig = DOCS_REPO,
-): Promise<ContentDirectoryItem[]> {
-  try {
-    const { data } = await octokit.rest.repos.getContent({
-      owner: repoConfig.owner,
-      repo: repoConfig.repo,
-      path: dirPath,
-      ref: repoConfig.branch,
-    });
- 
-    // GitHub API returns array for directories, object for files
-    if (!Array.isArray(data)) {
-      throw new Error(`Expected directory but got file: ${dirPath}`);
-    }
- 
-    return data;
-  } catch (error) {
-    console.error(
-      `Error fetching directory ${dirPath} from ${repoConfig.repo}:`,
-      error,
-    );
-    throw error;
-  }
-}
- 
-/**
- * Fetch multiple files in parallel with rate limiting
- */
-export async function fetchMultipleFiles(
-  filePaths: string[],
-  concurrency: number = 10,
-  repoConfig: RepoConfig = DOCS_REPO,
-): Promise<Map<string, string>> {
-  const results = new Map<string, string>();
- 
-  // Process in batches to respect rate limits
-  // Octokit has built-in rate limiting, but we still batch for efficiency
-  for (let i = 0; i < filePaths.length; i += concurrency) {
-    const batch = filePaths.slice(i, i + concurrency);
- 
-    const batchResults = await Promise.all(
-      batch.map(async (path) => {
-        const content = await fetchFileFromGitHub(path, repoConfig);
-        return { path, content };
-      }),
-    );
- 
-    for (const { path, content } of batchResults) {
-      if (content) {
-        results.set(path, content);
-      }
-    }
- 
-    // Small delay between batches to be conservative with rate limits
-    if (i + concurrency < filePaths.length) {
-      await new Promise((resolve) => setTimeout(resolve, 100));
-    }
-  }
- 
-  return results;
-}
- 
-/**
- * Check if a file exists on GitHub
- */
-export async function fileExistsOnGitHub(
-  filePath: string,
-  repoConfig: RepoConfig = DOCS_REPO,
-): Promise<boolean> {
-  try {
-    await octokit.rest.repos.getContent({
-      owner: repoConfig.owner,
-      repo: repoConfig.repo,
-      path: filePath,
-      ref: repoConfig.branch,
-    });
-    return true;
-  } catch (error: unknown) {
-    if (isRequestError(error) && error.status === 404) {
-      return false;
-    }
-    // For other errors, log but return false to be safe
-    console.error(`Error checking if ${filePath} exists:`, error);
-    return false;
-  }
-}
- 
-/**
- * Get file metadata without downloading content
- */
-export async function getGitHubFileMetadata(
-  filePath: string,
-  repoConfig: RepoConfig = DOCS_REPO,
-): Promise<{ sha: string; size: number } | null> {
-  try {
-    const { data } = await octokit.rest.repos.getContent({
-      owner: repoConfig.owner,
-      repo: repoConfig.repo,
-      path: filePath,
-      ref: repoConfig.branch,
-    });
- 
-    // Ensure we got a file, not a directory
-    if (Array.isArray(data)) {
-      return null;
-    }
- 
-    // Type guard for file object
-    if ("sha" in data && "size" in data) {
-      return {
-        sha: data.sha,
-        size: data.size,
-      };
-    }
- 
-    return null;
-  } catch (error: unknown) {
-    if (isRequestError(error) && error.status === 404) {
-      return null;
-    }
-    console.error(`Error fetching metadata for ${filePath}:`, error);
-    return null;
-  }
-}
- 
- -
-
- - - - - - - - \ No newline at end of file diff --git a/coverage/utils/index.html b/coverage/utils/index.html deleted file mode 100644 index d117c9c96..000000000 --- a/coverage/utils/index.html +++ /dev/null @@ -1,236 +0,0 @@ - - - - - - Code coverage report for utils - - - - - - - - - -
-
-

All files utils

-
- -
- 50.52% - Statements - 96/190 -
- - -
- 49.57% - Branches - 58/117 -
- - -
- 53.12% - Functions - 17/32 -
- - -
- 51.07% - Lines - 95/186 -
- - -
-

- Press n or j to go to the next uncovered block, b, p or k for the previous block. -

- -
-
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
FileStatementsBranchesFunctionsLines
filesystem.ts -
-
12%3/250%0/40%0/312%3/25
github.ts -
-
6%3/500%0/300%0/86.25%3/48
nav-tree-merge.ts -
-
11.53%3/260%0/220%0/411.53%3/26
navigation-helpers.ts -
-
100%2/2100%0/0100%1/1100%2/2
normalization.ts -
-
100%6/6100%4/4100%2/2100%5/5
openapi.ts -
-
97.61%41/4297.5%39/40100%9/9100%41/41
openrpc.ts -
-
100%2/2100%4/4100%1/1100%2/2
test-factories.ts -
-
100%6/6100%3/3100%3/3100%6/6
truncate-record.ts -
-
96.77%30/3180%8/10100%1/196.77%30/31
-
-
-
- - - - - - - - \ No newline at end of file diff --git a/coverage/utils/nav-tree-merge.ts.html b/coverage/utils/nav-tree-merge.ts.html deleted file mode 100644 index b91d2bbb6..000000000 --- a/coverage/utils/nav-tree-merge.ts.html +++ /dev/null @@ -1,364 +0,0 @@ - - - - - - Code coverage report for utils/nav-tree-merge.ts - - - - - - - - - -
-
-

All files / utils nav-tree-merge.ts

-
- -
- 11.53% - Statements - 3/26 -
- - -
- 0% - Branches - 0/22 -
- - -
- 0% - Functions - 0/4 -
- - -
- 11.53% - Lines - 3/26 -
- - -
-

- Press n or j to go to the next uncovered block, b, p or k for the previous block. -

- -
-
-

-
1 -2 -3 -4 -5 -6 -7 -8 -9 -10 -11 -12 -13 -14 -15 -16 -17 -18 -19 -20 -21 -22 -23 -24 -25 -26 -27 -28 -29 -30 -31 -32 -33 -34 -35 -36 -37 -38 -39 -40 -41 -42 -43 -44 -45 -46 -47 -48 -49 -50 -51 -52 -53 -54 -55 -56 -57 -58 -59 -60 -61 -62 -63 -64 -65 -66 -67 -68 -69 -70 -71 -72 -73 -74 -75 -76 -77 -78 -79 -80 -81 -82 -83 -84 -85 -86 -87 -88 -89 -90 -91 -92 -93 -94  -  -  -  -  -  -  -  -  -  -1x -  -  -  -  -  -  -  -  -  -  -  -  -  -1x -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -1x -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  - 
import type {
-  NavItem,
-  NavigationTree,
-} from "@/content-indexer/types/navigation.js";
- 
-/**
- * Checks if a navigation item is an SDK reference section.
- * SDK sections are identified by title containing "sdk reference" (case-insensitive)
- * WARNING: This is an assumption and will break if the title for this section is changed
- */
-const isSDKReferenceSection = (item: NavItem): boolean => {
-  if (item.type === "section" || item.type === "api-section") {
-    return item.title.toLowerCase().includes("sdk reference");
-  }
-  return false;
-};
- 
-/**
- * Identifies SDK reference sections vs manual sections in a navigation tree.
- * SDK sections are identified by title containing "sdk reference" (case-insensitive).
- *
- * @param tree - The navigation tree to separate
- * @returns Object with sdk and manual sections
- */
-export const separateSDKAndManualSections = (
-  tree: NavigationTree,
-): { sdk: NavigationTree; manual: NavigationTree } => {
-  return tree.reduce<{ sdk: NavigationTree; manual: NavigationTree }>(
-    (acc, item) => {
-      if (isSDKReferenceSection(item)) {
-        acc.sdk.push(item);
-      } else {
-        acc.manual.push(item);
-      }
-      return acc;
-    },
-    { sdk: [], manual: [] },
-  );
-};
- 
-/**
- * Merges navigation trees for the wallets tab, handling SDK references and manual sections.
- * SDK References are always inserted second-to-last (before Resources section).
- *
- * @param newTree - New sections from current indexer run
- * @param existingTree - Existing wallets navigation tree from Redis (or null if none)
- * @param indexerType - Type of indexer: "sdk" means newTree is SDK refs, "main" means newTree is manual content
- * @returns Merged tree with manual sections + SDK sections at second-to-last position
- */
-export const mergeWalletsNavTree = (
-  newTree: NavigationTree,
-  existingTree: NavigationTree | null,
-  indexerType: "main" | "sdk",
-): NavigationTree => {
-  if (!existingTree) {
-    if (indexerType === "sdk") {
-      console.warn("⚠️  No existing wallets nav tree found, creating new one");
-    }
-    return newTree;
-  }
- 
-  console.info("📖 Read existing wallets nav tree from Redis");
- 
-  // Separate SDK and manual sections from existing tree
-  const { sdk: existingSDK, manual: existingManual } =
-    separateSDKAndManualSections(existingTree);
- 
-  // Determine which sections are new and which to preserve
-  const manualSections = indexerType === "main" ? newTree : existingManual;
-  const sdkSections = indexerType === "sdk" ? newTree : existingSDK;
- 
-  // Log preservation info
-  if (indexerType === "main" && sdkSections.length > 0) {
-    console.info(
-      `📖 Preserved ${sdkSections.length} SDK reference section(s) in wallets nav tree`,
-    );
-  }
- 
-  // Handle edge cases
-  if (sdkSections.length === 0) {
-    return manualSections;
-  }
-  if (manualSections.length === 0) {
-    return sdkSections;
-  }
- 
-  // Insert SDK sections at second-to-last position (before Resources)
-  return [
-    ...manualSections.slice(0, -1), // All manual sections except last
-    ...sdkSections, // SDK sections
-    manualSections[manualSections.length - 1], // Last section (Resources)
-  ];
-};
- 
- -
-
- - - - - - - - \ No newline at end of file diff --git a/coverage/utils/navigation-helpers.ts.html b/coverage/utils/navigation-helpers.ts.html deleted file mode 100644 index 6d0c41c05..000000000 --- a/coverage/utils/navigation-helpers.ts.html +++ /dev/null @@ -1,127 +0,0 @@ - - - - - - Code coverage report for utils/navigation-helpers.ts - - - - - - - - - -
-
-

All files / utils navigation-helpers.ts

-
- -
- 100% - Statements - 2/2 -
- - -
- 100% - Branches - 0/0 -
- - -
- 100% - Functions - 1/1 -
- - -
- 100% - Lines - 2/2 -
- - -
-

- Press n or j to go to the next uncovered block, b, p or k for the previous block. -

- -
-
-

-
1 -2 -3 -4 -5 -6 -7 -8 -9 -10 -11 -12 -13 -14 -15  -  -  -  -  -  -6x -  -  -20x -  -  -  -  - 
import type { NavItem } from "@/content-indexer/types/navigation.js";
- 
-/**
- * Creates breadcrumb-safe navigation item (without populated children).
- * Prevents circular references in breadcrumb trails by creating a shallow copy.
- */
-export const createBreadcrumbNavItem = (
-  title: string,
-  type: "api-section",
-): NavItem => ({
-  title,
-  type,
-  children: [],
-});
- 
- -
-
- - - - - - - - \ No newline at end of file diff --git a/coverage/utils/normalization.ts.html b/coverage/utils/normalization.ts.html deleted file mode 100644 index 914d457d2..000000000 --- a/coverage/utils/normalization.ts.html +++ /dev/null @@ -1,151 +0,0 @@ - - - - - - Code coverage report for utils/normalization.ts - - - - - - - - - -
-
-

All files / utils normalization.ts

-
- -
- 100% - Statements - 6/6 -
- - -
- 100% - Branches - 4/4 -
- - -
- 100% - Functions - 2/2 -
- - -
- 100% - Lines - 5/5 -
- - -
-

- Press n or j to go to the next uncovered block, b, p or k for the previous block. -

- -
-
-

-
1 -2 -3 -4 -5 -6 -7 -8 -9 -10 -11 -12 -13 -14 -15 -16 -17 -18 -19 -20 -21 -22 -23  -  -  -  -  -  -  -4x -30x -6x -  -  -  -  -  -  -4x -  -  -  -30x -  - 
import type { RepoConfig } from "@/content-indexer/utils/github.js";
- 
-/**
- * Normalizes a frontmatter slug by removing the "docs/" prefix.
- * This prefix is used in some legacy frontmatter from the main docs repo
- * but should be stripped when generating URL paths.
- */
-export const normalizeSlug = (slug: string | undefined): string | undefined => {
-  if (!slug) return undefined;
-  return slug.replace(/^docs\//, "");
-};
- 
-/**
- * Normalizes a file path by stripping the repo's configured prefix.
- * This ensures the stored filePath can be used directly with the repo's docsPrefix.
- */
-export const normalizeFilePath = (
-  filePath: string,
-  repo: RepoConfig,
-): string => {
-  return filePath.replace(repo.stripPathPrefix || "", "");
-};
- 
- -
-
- - - - - - - - \ No newline at end of file diff --git a/coverage/utils/openapi.ts.html b/coverage/utils/openapi.ts.html deleted file mode 100644 index 8594e8c0a..000000000 --- a/coverage/utils/openapi.ts.html +++ /dev/null @@ -1,532 +0,0 @@ - - - - - - Code coverage report for utils/openapi.ts - - - - - - - - - -
-
-

All files / utils openapi.ts

-
- -
- 97.61% - Statements - 41/42 -
- - -
- 97.5% - Branches - 39/40 -
- - -
- 100% - Functions - 9/9 -
- - -
- 100% - Lines - 41/41 -
- - -
-

- Press n or j to go to the next uncovered block, b, p or k for the previous block. -

- -
-
-

-
1 -2 -3 -4 -5 -6 -7 -8 -9 -10 -11 -12 -13 -14 -15 -16 -17 -18 -19 -20 -21 -22 -23 -24 -25 -26 -27 -28 -29 -30 -31 -32 -33 -34 -35 -36 -37 -38 -39 -40 -41 -42 -43 -44 -45 -46 -47 -48 -49 -50 -51 -52 -53 -54 -55 -56 -57 -58 -59 -60 -61 -62 -63 -64 -65 -66 -67 -68 -69 -70 -71 -72 -73 -74 -75 -76 -77 -78 -79 -80 -81 -82 -83 -84 -85 -86 -87 -88 -89 -90 -91 -92 -93 -94 -95 -96 -97 -98 -99 -100 -101 -102 -103 -104 -105 -106 -107 -108 -109 -110 -111 -112 -113 -114 -115 -116 -117 -118 -119 -120 -121 -122 -123 -124 -125 -126 -127 -128 -129 -130 -131 -132 -133 -134 -135 -136 -137 -138 -139 -140 -141 -142 -143 -144 -145 -146 -147 -148 -149 -150  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -5x -  -  -  -  -25x -23x -  -  -2x -1x -  -  -1x -  -  -  -  -  -  -5x -  -  -  -  -17x -17x -1x -  -  -  -16x -  -16x -  -  -  -  -  -16x -  -  -  -  -  -  -  -  -  -  -  -5x -  -  -22x -23x -  -23x -  -  -27x -  -  -  -  -25x -  -25x -25x -  -25x -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -5x -  -  -  -  -33x -  -  -33x -11x -11x -  -  -  -33x -33x -  -33x -  -  -  -  -  -  -5x -  -  -  -  -17x -17x -1x -  -  -16x -16x -1x -  -  -15x -15x -15x -  -15x -  - 
import { kebabCase } from "lodash-es";
-import type { OpenAPIV3 } from "openapi-types";
- 
-import { HTTP_METHODS } from "@/content-indexer/constants/http.js";
-import type { PathBuilder } from "@/content-indexer/core/path-builder.js";
- 
-export interface ExtractedOperation {
-  operationId: string;
-  path: string;
-  method: string;
-  tag?: string;
-}
- 
-/**
- * Extracts operationId from an OpenAPI operation object.
- * Falls back to summary or generates from method + path if operationId is missing.
- */
-const getOperationId = (
-  operation: Record<string, unknown>,
-  method: string,
-  path: string,
-): string => {
-  if ("operationId" in operation && operation.operationId) {
-    return operation.operationId as string;
-  }
- 
-  if ("summary" in operation && operation.summary) {
-    return (operation.summary as string).replace(/^\//, "");
-  }
- 
-  return `${method}_${path}`;
-};
- 
-/**
- * Extracts the title for an OpenAPI operation.
- * Prefers the summary field, falls back to operationId.
- */
-export const getOperationTitle = (
-  spec: { paths: Record<string, unknown> },
-  operationId: string,
-  operationPath: string,
-): string => {
-  const pathItem = spec.paths[operationPath];
-  if (!pathItem || typeof pathItem !== "object") {
-    return operationId;
-  }
- 
-  // Find the operation with matching operationId
-  const operation = Object.values(pathItem).find(
-    (op: unknown) =>
-      typeof op === "object" &&
-      op !== null &&
-      "operationId" in op &&
-      (op as OpenAPIV3.OperationObject).operationId === operationId,
-  ) as OpenAPIV3.OperationObject | undefined;
- 
-  return operation?.summary || operationId;
-};
- 
-/**
- * Extracts all operations from an OpenAPI paths object.
- *
- * Iterates through all paths and HTTP methods, extracting metadata for each operation including:
- * - operationId (with fallback logic via getOperationId)
- * - path (the URL path from the spec)
- * - method (the HTTP method, normalized to uppercase)
- * - tag (the first tag from the operation's tags array, used for grouping)
- */
-export const extractOpenApiOperations = (
-  paths: Record<string, unknown>,
-): ExtractedOperation[] => {
-  return Object.entries(paths).flatMap(([path, pathItem]) => {
-    Iif (!pathItem || typeof pathItem !== "object") return [];
- 
-    return Object.entries(pathItem)
-      .filter(
-        ([method, operation]) =>
-          (HTTP_METHODS as readonly string[]).includes(method) &&
-          operation &&
-          typeof operation === "object",
-      )
-      .map(([method, operation]) => {
-        const op = operation as Record<string, unknown>;
-        // Extract the first tag (Fern uses tags[0] for organization)
-        const tags = Array.isArray(op.tags) ? op.tags : [];
-        const tag = tags[0] as string | undefined;
- 
-        return {
-          operationId: getOperationId(op, method, path),
-          path,
-          method: method.toUpperCase(),
-          tag,
-        };
-      });
-  });
-};
- 
-/**
- * Builds the final URL path for an OpenAPI operation.
- *
- * Constructs the path by:
- * 1. Optionally adding a tag slug (for grouping operations by tag)
- * 2. Adding the operation slug (kebab-cased operationId)
- */
-export const buildOperationPath = (
-  apiPathBuilder: PathBuilder,
-  operationId: string,
-  tag?: string,
-): string => {
-  let pathBuilder = apiPathBuilder;
- 
-  // Add tag slug to path if operation has a tag
-  if (tag) {
-    const tagSlug = kebabCase(tag);
-    pathBuilder = apiPathBuilder.apply({ urlSlug: tagSlug });
-  }
- 
-  // Add operation slug to path
-  const operationSlug = kebabCase(operationId);
-  pathBuilder = pathBuilder.apply({ urlSlug: operationSlug });
- 
-  return pathBuilder.get();
-};
- 
-/**
- * Extracts the description from an OpenAPI operation object.
- * Falls back to summary if description is not available.
- */
-export const getOperationDescription = (
-  spec: { paths: Record<string, unknown> },
-  path: string,
-  method: string,
-): string => {
-  const pathItem = spec.paths[path];
-  if (!pathItem || typeof pathItem !== "object") {
-    return "";
-  }
- 
-  const operation = (pathItem as Record<string, unknown>)[method];
-  if (!operation || typeof operation !== "object") {
-    return "";
-  }
- 
-  const operationObj = operation as Record<string, unknown>;
-  const description = operationObj.description as string | undefined;
-  const summary = operationObj.summary as string | undefined;
- 
-  return description || summary || "";
-};
- 
- -
-
- - - - - - - - \ No newline at end of file diff --git a/coverage/utils/openrpc.ts.html b/coverage/utils/openrpc.ts.html deleted file mode 100644 index 33a1bce70..000000000 --- a/coverage/utils/openrpc.ts.html +++ /dev/null @@ -1,124 +0,0 @@ - - - - - - Code coverage report for utils/openrpc.ts - - - - - - - - - -
-
-

All files / utils openrpc.ts

-
- -
- 100% - Statements - 2/2 -
- - -
- 100% - Branches - 4/4 -
- - -
- 100% - Functions - 1/1 -
- - -
- 100% - Lines - 2/2 -
- - -
-

- Press n or j to go to the next uncovered block, b, p or k for the previous block. -

- -
-
-

-
1 -2 -3 -4 -5 -6 -7 -8 -9 -10 -11 -12 -13 -14  -  -  -  -  -5x -16x -  -  -  -  -  -  - 
import type { OpenRpcSpec } from "@/content-indexer/types/specs.js";
- 
-/**
- * Type guard to check if a spec is a valid OpenRPC spec with methods array.
- */
-export const isValidOpenRpcSpec = (spec: unknown): spec is OpenRpcSpec => {
-  return (
-    typeof spec === "object" &&
-    spec !== null &&
-    "methods" in spec &&
-    Array.isArray((spec as { methods: unknown }).methods)
-  );
-};
- 
- -
-
- - - - - - - - \ No newline at end of file diff --git a/coverage/utils/test-factories.ts.html b/coverage/utils/test-factories.ts.html deleted file mode 100644 index 58aa0e20c..000000000 --- a/coverage/utils/test-factories.ts.html +++ /dev/null @@ -1,232 +0,0 @@ - - - - - - Code coverage report for utils/test-factories.ts - - - - - - - - - -
-
-

All files / utils test-factories.ts

-
- -
- 100% - Statements - 6/6 -
- - -
- 100% - Branches - 3/3 -
- - -
- 100% - Functions - 3/3 -
- - -
- 100% - Lines - 6/6 -
- - -
-

- Press n or j to go to the next uncovered block, b, p or k for the previous block. -

- -
-
-

-
1 -2 -3 -4 -5 -6 -7 -8 -9 -10 -11 -12 -13 -14 -15 -16 -17 -18 -19 -20 -21 -22 -23 -24 -25 -26 -27 -28 -29 -30 -31 -32 -33 -34 -35 -36 -37 -38 -39 -40 -41 -42 -43 -44 -45 -46 -47 -48 -49 -50  -  -  -  -  -  -  -  -  -9x -  -19x -  -  -  -  -  -  -  -  -  -  -  -  -9x -  -11x -  -  -  -  -  -  -  -  -  -  -  -  -9x -  -16x -  -  -  -  -  -  -  - 
import type {
-  OpenApiSpec,
-  OpenRpcSpec,
-} from "@/content-indexer/types/specs.js";
-import type { RepoConfig } from "@/content-indexer/utils/github.js";
- 
-/**
- * Factory for creating OpenAPI spec with minimal required fields for testing
- */
-export const openApiSpecFactory = (
-  overrides: Partial<OpenApiSpec> = {},
-): OpenApiSpec => ({
-  openapi: "3.0.0",
-  info: {
-    title: "Test API",
-    version: "1.0.0",
-  },
-  paths: {},
-  ...overrides,
-});
- 
-/**
- * Factory for creating OpenRPC spec with minimal required fields for testing
- */
-export const openRpcSpecFactory = (
-  overrides: Partial<OpenRpcSpec> = {},
-): OpenRpcSpec => ({
-  openrpc: "1.0.0",
-  info: {
-    title: "Test API",
-    version: "1.0.0",
-  },
-  methods: [],
-  ...overrides,
-});
- 
-/**
- * Factory for creating RepoConfig with minimal required fields for testing
- */
-export const repoConfigFactory = (
-  overrides: Partial<RepoConfig> = {},
-): RepoConfig => ({
-  owner: "test-owner",
-  repo: "test-repo",
-  branch: "main",
-  docsPrefix: "docs/",
-  stripPathPrefix: "",
-  ...overrides,
-});
- 
- -
-
- - - - - - - - \ No newline at end of file diff --git a/coverage/utils/truncate-record.ts.html b/coverage/utils/truncate-record.ts.html deleted file mode 100644 index 6ea0aded9..000000000 --- a/coverage/utils/truncate-record.ts.html +++ /dev/null @@ -1,316 +0,0 @@ - - - - - - Code coverage report for utils/truncate-record.ts - - - - - - - - - -
-
-

All files / utils truncate-record.ts

-
- -
- 96.77% - Statements - 30/31 -
- - -
- 80% - Branches - 8/10 -
- - -
- 100% - Functions - 1/1 -
- - -
- 96.77% - Lines - 30/31 -
- - -
-

- Press n or j to go to the next uncovered block, b, p or k for the previous block. -

- -
-
-

-
1 -2 -3 -4 -5 -6 -7 -8 -9 -10 -11 -12 -13 -14 -15 -16 -17 -18 -19 -20 -21 -22 -23 -24 -25 -26 -27 -28 -29 -30 -31 -32 -33 -34 -35 -36 -37 -38 -39 -40 -41 -42 -43 -44 -45 -46 -47 -48 -49 -50 -51 -52 -53 -54 -55 -56 -57 -58 -59 -60 -61 -62 -63 -64 -65 -66 -67 -68 -69 -70 -71 -72 -73 -74 -75 -76 -77 -78  -  -  -  -2x -2x -2x -  -  -  -  -  -2x -  -6x -6x -  -6x -6x -  -6x -2x -  -  -  -4x -4x -  -  -  -  -4x -  -  -  -  -  -  -4x -1x -  -  -  -  -  -  -3x -3x -3x -3x -  -3x -  -3x -  -  -3x -3x -3x -  -3x -3x -3x -  -  -3x -  -  -  -  -  -3x -  -  -  -6x -  - 
import removeMd from "remove-markdown";
- 
-import type { AlgoliaRecord } from "@/content-indexer/types/algolia.js";
- 
-const MAX_RECORD_BYTES = 100_000; // Algolia imposes a 100KB limit on each record
-const BUFFER_BYTES = 1_000;
-const MAX_ITERATIONS = 5;
- 
-/**
- * Truncate record content to ensure entire JSON payload fits within Algolia limit.
- * Also strips markdown formatting from content for better search experience.
- */
-export const truncateRecord = (rawRecord: AlgoliaRecord): AlgoliaRecord => {
-  // Strip markdown formatting to get clean, searchable text
-  const cleanedContent = removeMd(rawRecord.content);
-  const record = { ...rawRecord, content: cleanedContent };
- 
-  const fullRecordJson = JSON.stringify(record);
-  const recordBytes = Buffer.byteLength(fullRecordJson, "utf8");
- 
-  if (recordBytes <= MAX_RECORD_BYTES) {
-    return record; // Record is fine as-is - should be the case for over 99% of records
-  }
- 
-  // Calculate overhead (everything except content field)
-  const recordWithoutContent = { ...record, content: "" };
-  const overheadBytes = Buffer.byteLength(
-    JSON.stringify(recordWithoutContent),
-    "utf8",
-  );
- 
-  console.warn(
-    `⚠️  Record "${record.title}" (${record.path}) exceeds ${MAX_RECORD_BYTES} bytes\n`,
-    `   Total: ${recordBytes} bytes\n`,
-    `   Content: ${Buffer.byteLength(record.content, "utf8")} bytes\n`,
-    `   Overhead (all non-content data): ${overheadBytes} bytes`,
-  );
- 
-  if (overheadBytes > MAX_RECORD_BYTES - 1000) {
-    throw new Error(
-      `Record overhead (${overheadBytes} bytes) is too large! Something is wrong with the record data.`,
-    );
-  }
- 
-  // Iteratively truncate content while measuring full JSON record size
-  // This accounts for JSON escaping overhead (quotes, backslashes, etc.)
-  let truncatedContent = cleanedContent;
-  let truncatedRecord: AlgoliaRecord = { ...record, content: truncatedContent };
-  let currentBytes = recordBytes;
-  let iterations = 0;
- 
-  while (currentBytes > MAX_RECORD_BYTES && iterations < MAX_ITERATIONS) {
-    // Calculate reduction ratio to reach target size
-    const reductionRatio = (MAX_RECORD_BYTES - BUFFER_BYTES) / currentBytes;
- 
-    // Use code point-aware truncation to avoid splitting multi-byte UTF-8 characters (emoji, etc.)
-    const codePoints = Array.from(truncatedContent);
-    const targetCodePoints = Math.floor(codePoints.length * reductionRatio);
-    truncatedContent = codePoints.slice(0, targetCodePoints).join("") + "...";
- 
-    truncatedRecord = { ...record, content: truncatedContent };
-    currentBytes = Buffer.byteLength(JSON.stringify(truncatedRecord), "utf8");
-    iterations++;
-  }
- 
-  Iif (currentBytes > MAX_RECORD_BYTES) {
-    throw new Error(
-      `Failed to truncate record after ${MAX_ITERATIONS} iterations. Final size: ${currentBytes} bytes`,
-    );
-  }
- 
-  console.warn(
-    `   ✓ Truncated to ${currentBytes} bytes (${truncatedContent.length} chars) in ${iterations} iteration${iterations === 1 ? "" : "s"}\n`,
-  );
- 
-  return truncatedRecord;
-};
- 
- -
-
- - - - - - - - \ No newline at end of file diff --git a/coverage/visitors/index.html b/coverage/visitors/index.html deleted file mode 100644 index 2b0c0738b..000000000 --- a/coverage/visitors/index.html +++ /dev/null @@ -1,176 +0,0 @@ - - - - - - Code coverage report for visitors - - - - - - - - - -
-
-

All files visitors

-
- -
- 98.66% - Statements - 74/75 -
- - -
- 92.59% - Branches - 50/54 -
- - -
- 100% - Functions - 9/9 -
- - -
- 98.66% - Lines - 74/75 -
- - -
-

- Press n or j to go to the next uncovered block, b, p or k for the previous block. -

- -
-
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
FileStatementsBranchesFunctionsLines
index.ts -
-
92.3%12/1390%9/10100%1/192.3%12/13
visit-api-reference.ts -
-
100%16/16100%14/14100%1/1100%16/16
visit-link.ts -
-
100%3/3100%0/0100%1/1100%3/3
visit-page.ts -
-
100%12/12100%12/12100%1/1100%12/12
visit-section.ts -
-
100%31/3183.33%15/18100%5/5100%31/31
-
-
-
- - - - - - - - \ No newline at end of file diff --git a/coverage/visitors/index.ts.html b/coverage/visitors/index.ts.html deleted file mode 100644 index 835bee29c..000000000 --- a/coverage/visitors/index.ts.html +++ /dev/null @@ -1,313 +0,0 @@ - - - - - - Code coverage report for visitors/index.ts - - - - - - - - - -
-
-

All files / visitors index.ts

-
- -
- 92.3% - Statements - 12/13 -
- - -
- 90% - Branches - 9/10 -
- - -
- 100% - Functions - 1/1 -
- - -
- 92.3% - Lines - 12/13 -
- - -
-

- Press n or j to go to the next uncovered block, b, p or k for the previous block. -

- -
-
-

-
1 -2 -3 -4 -5 -6 -7 -8 -9 -10 -11 -12 -13 -14 -15 -16 -17 -18 -19 -20 -21 -22 -23 -24 -25 -26 -27 -28 -29 -30 -31 -32 -33 -34 -35 -36 -37 -38 -39 -40 -41 -42 -43 -44 -45 -46 -47 -48 -49 -50 -51 -52 -53 -54 -55 -56 -57 -58 -59 -60 -61 -62 -63 -64 -65 -66 -67 -68 -69 -70 -71 -72 -73 -74 -75 -76 -77  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -2x -20x -  -  -20x -1x -  -  -  -19x -2x -  -  -17x -13x -  -  -4x -3x -  -  -1x -1x -  -  -  -  -  - 
import type { ProcessingContext } from "@/content-indexer/collectors/processing-context.js";
-import type { ContentCache } from "@/content-indexer/core/content-cache.js";
-import type { PathBuilder } from "@/content-indexer/core/path-builder.js";
-import {
-  isApiConfig,
-  isChangelogConfig,
-  isLinkConfig,
-  isPageConfig,
-  isSectionConfig,
-  type NavigationItem,
-} from "@/content-indexer/types/docsYaml.js";
-import type { NavItem } from "@/content-indexer/types/navigation.js";
-import type { PathIndex } from "@/content-indexer/types/pathIndex.js";
-import type { RepoConfig } from "@/content-indexer/utils/github.js";
- 
-import { visitApiReference } from "./visit-api-reference.js";
-import { visitLink } from "./visit-link.js";
-import { visitPage } from "./visit-page.js";
-import { visitSection } from "./visit-section.js";
- 
-export interface VisitorConfigBase {
-  parentPath: PathBuilder;
-  tab: string;
-  repo: RepoConfig;
-  contentCache: ContentCache;
-  context: ProcessingContext;
-  navigationAncestors: NavItem[];
-}
- 
-export interface VisitorConfig extends VisitorConfigBase {
-  item: NavigationItem;
-}
- 
-export interface VisitorResult {
-  indexEntries: PathIndex;
-  navItem?: NavItem | NavItem[];
-}
- 
-/**
- * Dispatcher that routes navigation items to the appropriate visitor.
- *
- * Uses type guards to determine item type and delegates to specialized visitors:
- * - Pages → visitPage
- * - Sections → visitSection (recursive)
- * - API references → visitApiReference
- * - Links → visitLink
- * - Changelog → skip (no processing needed)
- */
-export const visitNavigationItem = (config: VisitorConfig): VisitorResult => {
-  const { item } = config;
- 
-  // Skip changelog items
-  if (isChangelogConfig(item)) {
-    return { indexEntries: {}, navItem: undefined };
-  }
- 
-  // Delegate to appropriate visitor based on item type
-  if (isLinkConfig(item)) {
-    return visitLink({ ...config, item });
-  }
- 
-  if (isPageConfig(item)) {
-    return visitPage({ ...config, item });
-  }
- 
-  if (isSectionConfig(item)) {
-    return visitSection({ ...config, item }, visitNavigationItem);
-  }
- 
-  Eif (isApiConfig(item)) {
-    return visitApiReference({ ...config, item });
-  }
- 
-  // Unknown item type - skip
-  return { indexEntries: {}, navItem: undefined };
-};
- 
- -
-
- - - - - - - - \ No newline at end of file diff --git a/coverage/visitors/processors/index.html b/coverage/visitors/processors/index.html deleted file mode 100644 index b437cecf6..000000000 --- a/coverage/visitors/processors/index.html +++ /dev/null @@ -1,131 +0,0 @@ - - - - - - Code coverage report for visitors/processors - - - - - - - - - -
-
-

All files visitors/processors

-
- -
- 100% - Statements - 58/58 -
- - -
- 96.77% - Branches - 30/31 -
- - -
- 100% - Functions - 8/8 -
- - -
- 100% - Lines - 58/58 -
- - -
-

- Press n or j to go to the next uncovered block, b, p or k for the previous block. -

- -
-
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
FileStatementsBranchesFunctionsLines
process-openapi.ts -
-
100%36/3692.85%13/14100%6/6100%36/36
process-openrpc.ts -
-
100%22/22100%17/17100%2/2100%22/22
-
-
-
- - - - - - - - \ No newline at end of file diff --git a/coverage/visitors/processors/process-openapi.ts.html b/coverage/visitors/processors/process-openapi.ts.html deleted file mode 100644 index f46baa74b..000000000 --- a/coverage/visitors/processors/process-openapi.ts.html +++ /dev/null @@ -1,730 +0,0 @@ - - - - - - Code coverage report for visitors/processors/process-openapi.ts - - - - - - - - - -
-
-

All files / visitors/processors process-openapi.ts

-
- -
- 100% - Statements - 36/36 -
- - -
- 92.85% - Branches - 13/14 -
- - -
- 100% - Functions - 6/6 -
- - -
- 100% - Lines - 36/36 -
- - -
-

- Press n or j to go to the next uncovered block, b, p or k for the previous block. -

- -
-
-

-
1 -2 -3 -4 -5 -6 -7 -8 -9 -10 -11 -12 -13 -14 -15 -16 -17 -18 -19 -20 -21 -22 -23 -24 -25 -26 -27 -28 -29 -30 -31 -32 -33 -34 -35 -36 -37 -38 -39 -40 -41 -42 -43 -44 -45 -46 -47 -48 -49 -50 -51 -52 -53 -54 -55 -56 -57 -58 -59 -60 -61 -62 -63 -64 -65 -66 -67 -68 -69 -70 -71 -72 -73 -74 -75 -76 -77 -78 -79 -80 -81 -82 -83 -84 -85 -86 -87 -88 -89 -90 -91 -92 -93 -94 -95 -96 -97 -98 -99 -100 -101 -102 -103 -104 -105 -106 -107 -108 -109 -110 -111 -112 -113 -114 -115 -116 -117 -118 -119 -120 -121 -122 -123 -124 -125 -126 -127 -128 -129 -130 -131 -132 -133 -134 -135 -136 -137 -138 -139 -140 -141 -142 -143 -144 -145 -146 -147 -148 -149 -150 -151 -152 -153 -154 -155 -156 -157 -158 -159 -160 -161 -162 -163 -164 -165 -166 -167 -168 -169 -170 -171 -172 -173 -174 -175 -176 -177 -178 -179 -180 -181 -182 -183 -184 -185 -186 -187 -188 -189 -190 -191 -192 -193 -194 -195 -196 -197 -198 -199 -200 -201 -202 -203 -204 -205 -206 -207 -208 -209 -210 -211 -212 -213 -214 -215 -216  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -4x -  -  -  -  -  -14x -  -14x -15x -  -  -  -  -  -15x -  -  -  -  -  -  -  -  -14x -  -  -  -  -  -4x -  -  -  -  -  -  -  -  -  -12x -12x -13x -13x -13x -  -  -12x -  -12x -12x -13x -  -  -  -  -  -13x -  -  -  -  -  -  -13x -13x -  -  -  -  -  -13x -  -  -  -13x -  -  -  -  -  -  -  -  -  -13x -  -  -  -  -  -  -  -  -12x -4x -  -  -  -  -  -8x -  -  -  -12x -  -  -  -  -  -  -4x -  -  -  -  -  -  -  -  -14x -  -  -14x -14x -  -  -  -  -  -  -  -14x -2x -  -  -  -12x -  -  -  -  -14x -  -  -  -  -  -  -  -  -  -  -14x -  -  -  -  -  -  -  -14x -  - 
import type { ProcessingContext } from "@/content-indexer/collectors/processing-context.js";
-import type { PathBuilder } from "@/content-indexer/core/path-builder.js";
-import type { NavItem } from "@/content-indexer/types/navigation.js";
-import type { PathIndex } from "@/content-indexer/types/pathIndex.js";
-import type { OpenApiSpec } from "@/content-indexer/types/specs.js";
-import { createBreadcrumbNavItem } from "@/content-indexer/utils/navigation-helpers.js";
-import {
-  buildOperationPath,
-  extractOpenApiOperations,
-  getOperationDescription,
-  getOperationTitle,
-  type ExtractedOperation,
-} from "@/content-indexer/utils/openapi.js";
-import type {
-  VisitorConfig,
-  VisitorResult,
-} from "@/content-indexer/visitors/index.js";
- 
-/**
- * Configuration for processing an OpenAPI specification
- */
-export interface ProcessOpenApiConfig {
-  spec: OpenApiSpec;
-  specUrl: string;
-  visitorConfig: VisitorConfig;
-  apiPathBuilder: PathBuilder;
-  apiTitle: string;
-  isHidden: boolean;
-  isFlattened: boolean;
-}
- 
-interface BuildOpenApiIndexEntriesConfig {
-  operations: ExtractedOperation[];
-  apiPathBuilder: PathBuilder;
-  specUrl: string;
-  tab: string;
-}
- 
-interface BuildOpenApiNavigationConfig {
-  operations: ExtractedOperation[];
-  apiPathBuilder: PathBuilder;
-  spec: OpenApiSpec;
-  context: ProcessingContext;
-  navigationAncestors: NavItem[];
-  apiSectionBreadcrumb: NavItem | undefined;
-  isHidden: boolean;
-}
- 
-/**
- * Builds path index entries for OpenAPI operations.
- */
-const buildOpenApiIndexEntries = ({
-  operations,
-  apiPathBuilder,
-  specUrl,
-  tab,
-}: BuildOpenApiIndexEntriesConfig): PathIndex => {
-  const indexEntries: PathIndex = {};
- 
-  operations.forEach((operation) => {
-    const finalPath = buildOperationPath(
-      apiPathBuilder,
-      operation.operationId,
-      operation.tag,
-    );
- 
-    indexEntries[finalPath] = {
-      type: "openapi",
-      specUrl,
-      operationId: operation.operationId,
-      source: "docs-yml",
-      tab,
-    };
-  });
- 
-  return indexEntries;
-};
- 
-/**
- * Builds navigation items for OpenAPI operations, grouped by tag.
- */
-const buildOpenApiNavigation = ({
-  operations,
-  apiPathBuilder,
-  spec,
-  context,
-  navigationAncestors,
-  apiSectionBreadcrumb,
-  isHidden,
-}: BuildOpenApiNavigationConfig): NavItem[] => {
-  // Group operations by tag
-  const operationsByTag = new Map<string | undefined, ExtractedOperation[]>();
-  operations.forEach((operation) => {
-    const existing = operationsByTag.get(operation.tag) || [];
-    existing.push(operation);
-    operationsByTag.set(operation.tag, existing);
-  });
- 
-  const tagSections: NavItem[] = [];
- 
-  for (const [tag, tagOperations] of operationsByTag.entries()) {
-    const endpointNavItems: NavItem[] = tagOperations.map((operation) => {
-      const finalPath = buildOperationPath(
-        apiPathBuilder,
-        operation.operationId,
-        operation.tag,
-      );
- 
-      const title = getOperationTitle(
-        spec,
-        operation.operationId,
-        operation.path,
-      );
- 
-      // Build Algolia record if not hidden
-      Eif (!isHidden) {
-        const description = getOperationDescription(
-          spec,
-          operation.path,
-          operation.method.toLowerCase(),
-        );
- 
-        const breadcrumbs = apiSectionBreadcrumb
-          ? [...navigationAncestors, apiSectionBreadcrumb]
-          : navigationAncestors;
- 
-        context.addAlgoliaRecord({
-          pageType: "API Method",
-          path: finalPath,
-          title,
-          content: description,
-          httpMethod: operation.method,
-          breadcrumbs,
-        });
-      }
- 
-      return {
-        title,
-        path: `/${finalPath}`,
-        method: operation.method,
-        type: "endpoint" as const,
-      };
-    });
- 
-    // Wrap in tag section if tag exists
-    if (tag) {
-      tagSections.push({
-        title: tag,
-        type: "section",
-        children: endpointNavItems,
-      });
-    } else {
-      tagSections.push(...endpointNavItems);
-    }
-  }
- 
-  return tagSections;
-};
- 
-/**
- * Processes an OpenAPI specification.
- * Extracts operations, builds path index, navigation, and Algolia records.
- */
-export const processOpenApiSpec = ({
-  spec,
-  specUrl,
-  visitorConfig,
-  apiPathBuilder,
-  apiTitle,
-  isHidden,
-  isFlattened,
-}: ProcessOpenApiConfig): VisitorResult => {
-  const { tab, context, navigationAncestors } = visitorConfig;
- 
-  // Extract operations and build index entries
-  const operations = extractOpenApiOperations(spec.paths);
-  const indexEntries = buildOpenApiIndexEntries({
-    operations,
-    apiPathBuilder,
-    specUrl,
-    tab,
-  });
- 
-  // Return early if hidden (index only, no navigation)
-  if (isHidden) {
-    return { indexEntries, navItem: undefined };
-  }
- 
-  // Create breadcrumb for Algolia
-  const apiSectionBreadcrumb = isFlattened
-    ? undefined
-    : createBreadcrumbNavItem(apiTitle, "api-section");
- 
-  // Build navigation items
-  const tagSections = buildOpenApiNavigation({
-    operations,
-    apiPathBuilder,
-    spec,
-    context,
-    navigationAncestors,
-    apiSectionBreadcrumb,
-    isHidden,
-  });
- 
-  // Return flattened or wrapped navigation
-  const navItem: NavItem | NavItem[] = isFlattened
-    ? tagSections
-    : {
-        title: apiTitle,
-        type: "api-section",
-        children: tagSections,
-      };
- 
-  return { indexEntries, navItem };
-};
- 
- -
-
- - - - - - - - \ No newline at end of file diff --git a/coverage/visitors/processors/process-openrpc.ts.html b/coverage/visitors/processors/process-openrpc.ts.html deleted file mode 100644 index d1711d04d..000000000 --- a/coverage/visitors/processors/process-openrpc.ts.html +++ /dev/null @@ -1,421 +0,0 @@ - - - - - - Code coverage report for visitors/processors/process-openrpc.ts - - - - - - - - - -
-
-

All files / visitors/processors process-openrpc.ts

-
- -
- 100% - Statements - 22/22 -
- - -
- 100% - Branches - 17/17 -
- - -
- 100% - Functions - 2/2 -
- - -
- 100% - Lines - 22/22 -
- - -
-

- Press n or j to go to the next uncovered block, b, p or k for the previous block. -

- -
-
-

-
1 -2 -3 -4 -5 -6 -7 -8 -9 -10 -11 -12 -13 -14 -15 -16 -17 -18 -19 -20 -21 -22 -23 -24 -25 -26 -27 -28 -29 -30 -31 -32 -33 -34 -35 -36 -37 -38 -39 -40 -41 -42 -43 -44 -45 -46 -47 -48 -49 -50 -51 -52 -53 -54 -55 -56 -57 -58 -59 -60 -61 -62 -63 -64 -65 -66 -67 -68 -69 -70 -71 -72 -73 -74 -75 -76 -77 -78 -79 -80 -81 -82 -83 -84 -85 -86 -87 -88 -89 -90 -91 -92 -93 -94 -95 -96 -97 -98 -99 -100 -101 -102 -103 -104 -105 -106 -107 -108 -109 -110 -111 -112 -113  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -4x -  -  -  -  -  -  -  -  -9x -  -9x -1x -1x -  -  -8x -8x -  -  -  -8x -  -  -  -  -9x -9x -9x -9x -  -  -9x -  -  -  -  -  -  -  -  -9x -8x -8x -  -  -  -8x -  -  -  -  -  -  -  -  -  -  -9x -  -  -  -  -  -  -  -  -9x -1x -  -  -  -7x -  -  -  -  -  -  -  -9x -  - 
import { kebabCase } from "lodash-es";
- 
-import type { PathBuilder } from "@/content-indexer/core/path-builder.js";
-import type { NavItem } from "@/content-indexer/types/navigation.js";
-import type { PathIndex } from "@/content-indexer/types/pathIndex.js";
-import type { OpenRpcSpec } from "@/content-indexer/types/specs.js";
-import { createBreadcrumbNavItem } from "@/content-indexer/utils/navigation-helpers.js";
-import { isValidOpenRpcSpec } from "@/content-indexer/utils/openrpc.js";
-import type {
-  VisitorConfig,
-  VisitorResult,
-} from "@/content-indexer/visitors/index.js";
- 
-/**
- * Configuration for processing an OpenRPC specification
- */
-export interface ProcessOpenRpcConfig {
-  spec: OpenRpcSpec;
-  specUrl: string;
-  visitorConfig: VisitorConfig;
-  apiPathBuilder: PathBuilder;
-  apiTitle: string;
-  isHidden: boolean;
-  isFlattened: boolean;
-}
- 
-/**
- * Processes an OpenRPC specification.
- * Validates spec, builds path index, navigation, and Algolia records.
- */
-export const processOpenRpcSpec = ({
-  spec,
-  specUrl,
-  visitorConfig,
-  apiPathBuilder,
-  apiTitle,
-  isHidden,
-  isFlattened,
-}: ProcessOpenRpcConfig): VisitorResult => {
-  const { tab, context, navigationAncestors } = visitorConfig;
- 
-  if (!isValidOpenRpcSpec(spec)) {
-    console.error(`  ⚠️  Invalid OpenRPC spec for ${apiTitle}`);
-    return { indexEntries: {} };
-  }
- 
-  const indexEntries: PathIndex = {};
-  const endpointNavItems: NavItem[] = [];
- 
-  // Create breadcrumb for Algolia
-  const apiSectionBreadcrumb =
-    !isHidden && !isFlattened
-      ? createBreadcrumbNavItem(apiTitle, "api-section")
-      : undefined;
- 
-  // Process each RPC method
-  spec.methods.forEach((method) => {
-    const slug = kebabCase(method.name);
-    const pathBuilder = apiPathBuilder.apply({ urlSlug: slug });
-    const finalPath = pathBuilder.get();
- 
-    // Add to path index
-    indexEntries[finalPath] = {
-      type: "openrpc",
-      specUrl,
-      methodName: method.name,
-      source: "docs-yml",
-      tab,
-    };
- 
-    // Build Algolia record if not hidden
-    if (!isHidden) {
-      const description = method.description || method.summary || "";
-      const breadcrumbs = apiSectionBreadcrumb
-        ? [...navigationAncestors, apiSectionBreadcrumb]
-        : navigationAncestors;
- 
-      context.addAlgoliaRecord({
-        pageType: "API Method",
-        path: finalPath,
-        title: method.name,
-        content: description,
-        httpMethod: "POST",
-        breadcrumbs,
-      });
-    }
- 
-    // Add navigation item
-    endpointNavItems.push({
-      title: method.name,
-      path: `/${finalPath}`,
-      method: "POST",
-      type: "endpoint",
-    });
-  });
- 
-  // Return early if hidden
-  if (isHidden) {
-    return { indexEntries, navItem: undefined };
-  }
- 
-  // Return flattened or wrapped navigation
-  const navItem: NavItem | NavItem[] = isFlattened
-    ? endpointNavItems
-    : {
-        title: apiTitle,
-        type: "api-section",
-        children: endpointNavItems,
-      };
- 
-  return { indexEntries, navItem };
-};
- 
- -
-
- - - - - - - - \ No newline at end of file diff --git a/coverage/visitors/visit-api-reference.ts.html b/coverage/visitors/visit-api-reference.ts.html deleted file mode 100644 index 6175148ac..000000000 --- a/coverage/visitors/visit-api-reference.ts.html +++ /dev/null @@ -1,307 +0,0 @@ - - - - - - Code coverage report for visitors/visit-api-reference.ts - - - - - - - - - -
-
-

All files / visitors visit-api-reference.ts

-
- -
- 100% - Statements - 16/16 -
- - -
- 100% - Branches - 14/14 -
- - -
- 100% - Functions - 1/1 -
- - -
- 100% - Lines - 16/16 -
- - -
-

- Press n or j to go to the next uncovered block, b, p or k for the previous block. -

- -
-
-

-
1 -2 -3 -4 -5 -6 -7 -8 -9 -10 -11 -12 -13 -14 -15 -16 -17 -18 -19 -20 -21 -22 -23 -24 -25 -26 -27 -28 -29 -30 -31 -32 -33 -34 -35 -36 -37 -38 -39 -40 -41 -42 -43 -44 -45 -46 -47 -48 -49 -50 -51 -52 -53 -54 -55 -56 -57 -58 -59 -60 -61 -62 -63 -64 -65 -66 -67 -68 -69 -70 -71 -72 -73 -74 -75  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -3x -8x -  -  -8x -8x -8x -8x -8x -  -  -8x -  -  -  -  -8x -8x -1x -  -  -1x -  -  -7x -  -  -7x -  -6x -  -  -  -  -  -  -  -  -  -  -1x -  -  -  -  -  -  -  -  -  -  - 
import { kebabCase } from "lodash-es";
- 
-import type { ApiConfig } from "@/content-indexer/types/docsYaml.js";
-import type {
-  OpenApiSpec,
-  OpenRpcSpec,
-} from "@/content-indexer/types/specs.js";
- 
-import type { VisitorConfigBase, VisitorResult } from "./index.js";
-import { processOpenApiSpec } from "./processors/process-openapi.js";
-import { processOpenRpcSpec } from "./processors/process-openrpc.js";
- 
-export interface ApiVisitorConfig extends VisitorConfigBase {
-  item: ApiConfig;
-}
- 
-/**
- * Visits an API reference item from docs.yml.
- *
- * Handles both OpenAPI and OpenRPC specifications by delegating to
- * spec-specific processors. Extracts config, loads cached spec,
- * and routes to the appropriate processor.
- */
-export const visitApiReference = (config: ApiVisitorConfig): VisitorResult => {
-  const { item: apiConfig, parentPath, contentCache } = config;
- 
-  // Extract configuration
-  const apiName = apiConfig["api-name"];
-  const apiUrlSlug = apiConfig.slug ?? kebabCase(apiConfig.api);
-  const skipSlug = apiConfig["skip-slug"] ?? false;
-  const isHidden = apiConfig.hidden ?? false;
-  const isFlattened = apiConfig.flattened ?? false;
- 
-  // Build path for this API
-  const apiPathBuilder = skipSlug
-    ? parentPath
-    : parentPath.apply({ urlSlug: apiUrlSlug });
- 
-  // Retrieve cached spec
-  const cached = contentCache.getSpec(apiName);
-  if (!cached) {
-    console.warn(
-      `  ⚠️  No cached spec found for api-name: ${apiName} (skipping)`,
-    );
-    return { indexEntries: {} };
-  }
- 
-  const { specType, spec, specUrl } = cached;
- 
-  // Delegate to spec-specific processor
-  switch (specType) {
-    case "openapi":
-      return processOpenApiSpec({
-        spec: spec as OpenApiSpec,
-        specUrl,
-        visitorConfig: config,
-        apiPathBuilder,
-        apiTitle: apiConfig.api,
-        isHidden,
-        isFlattened,
-      });
- 
-    case "openrpc":
-      return processOpenRpcSpec({
-        spec: spec as OpenRpcSpec,
-        specUrl,
-        visitorConfig: config,
-        apiPathBuilder,
-        apiTitle: apiConfig.api,
-        isHidden,
-        isFlattened,
-      });
-  }
-};
- 
- -
-
- - - - - - - - \ No newline at end of file diff --git a/coverage/visitors/visit-link.ts.html b/coverage/visitors/visit-link.ts.html deleted file mode 100644 index 1797a5735..000000000 --- a/coverage/visitors/visit-link.ts.html +++ /dev/null @@ -1,160 +0,0 @@ - - - - - - Code coverage report for visitors/visit-link.ts - - - - - - - - - -
-
-

All files / visitors visit-link.ts

-
- -
- 100% - Statements - 3/3 -
- - -
- 100% - Branches - 0/0 -
- - -
- 100% - Functions - 1/1 -
- - -
- 100% - Lines - 3/3 -
- - -
-

- Press n or j to go to the next uncovered block, b, p or k for the previous block. -

- -
-
-

-
1 -2 -3 -4 -5 -6 -7 -8 -9 -10 -11 -12 -13 -14 -15 -16 -17 -18 -19 -20 -21 -22 -23 -24 -25 -26  -  -  -  -  -  -  -  -  -  -  -  -  -3x -5x -  -5x -  -  -  -  -  -  -  -  - 
import type { LinkConfig } from "@/content-indexer/types/docsYaml.js";
- 
-import type { VisitorConfigBase, VisitorResult } from "./index.js";
- 
-export interface LinkVisitorConfig extends VisitorConfigBase {
-  item: LinkConfig;
-}
- 
-/**
- * Visits a link item from docs.yml.
- *
- * Links are external URLs - they only appear in navigation, not in path index.
- */
-export const visitLink = (config: LinkVisitorConfig): VisitorResult => {
-  const { item: linkItem } = config;
- 
-  return {
-    indexEntries: {},
-    navItem: {
-      title: linkItem.link,
-      href: linkItem.href,
-      type: "link",
-    },
-  };
-};
- 
- -
-
- - - - - - - - \ No newline at end of file diff --git a/coverage/visitors/visit-page.ts.html b/coverage/visitors/visit-page.ts.html deleted file mode 100644 index 2e8259fce..000000000 --- a/coverage/visitors/visit-page.ts.html +++ /dev/null @@ -1,322 +0,0 @@ - - - - - - Code coverage report for visitors/visit-page.ts - - - - - - - - - -
-
-

All files / visitors visit-page.ts

-
- -
- 100% - Statements - 12/12 -
- - -
- 100% - Branches - 12/12 -
- - -
- 100% - Functions - 1/1 -
- - -
- 100% - Lines - 12/12 -
- - -
-

- Press n or j to go to the next uncovered block, b, p or k for the previous block. -

- -
-
-

-
1 -2 -3 -4 -5 -6 -7 -8 -9 -10 -11 -12 -13 -14 -15 -16 -17 -18 -19 -20 -21 -22 -23 -24 -25 -26 -27 -28 -29 -30 -31 -32 -33 -34 -35 -36 -37 -38 -39 -40 -41 -42 -43 -44 -45 -46 -47 -48 -49 -50 -51 -52 -53 -54 -55 -56 -57 -58 -59 -60 -61 -62 -63 -64 -65 -66 -67 -68 -69 -70 -71 -72 -73 -74 -75 -76 -77 -78 -79 -80  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -3x -  -  -  -  -  -  -  -  -  -22x -22x -22x -  -22x -  -  -  -  -22x -  -  -22x -  -  -  -  -  -  -  -  -  -  -  -22x -  -  -  -  -  -  -  -  -22x -4x -4x -  -  -  -  -  -  -  -  -22x -  - 
import { kebabCase } from "lodash-es";
- 
-import type { PageConfig } from "@/content-indexer/types/docsYaml.js";
-import type { NavItem } from "@/content-indexer/types/navigation.js";
-import {
-  normalizeFilePath,
-  normalizeSlug,
-} from "@/content-indexer/utils/normalization.js";
- 
-import type { VisitorConfigBase, VisitorResult } from "./index.js";
- 
-export interface PageVisitorConfig extends VisitorConfigBase {
-  item: PageConfig;
-}
- 
-/**
- * Visits a page item from docs.yml.
- *
- * Builds:
- * - Path index entry for URL routing
- * - Navigation item for sidebar (unless hidden)
- * - Algolia record for search (unless hidden)
- */
-export const visitPage = ({
-  item: pageItem,
-  parentPath,
-  tab,
-  repo,
-  contentCache,
-  context,
-  navigationAncestors,
-}: PageVisitorConfig): VisitorResult => {
-  // Look up cached MDX content
-  const cached = contentCache.getMdxContent(pageItem.path);
-  const frontmatterSlug = normalizeSlug(cached?.frontmatter.slug);
-  const urlSlug = pageItem.slug ?? kebabCase(pageItem.page);
- 
-  const pagePathBuilder = parentPath.apply({
-    fullSlug: frontmatterSlug?.split("/"),
-    urlSlug,
-  });
- 
-  const finalPath = pagePathBuilder.get();
- 
-  // Build index entry
-  const indexEntries = {
-    [finalPath]: {
-      type: "mdx" as const,
-      filePath: normalizeFilePath(pageItem.path, repo),
-      source: frontmatterSlug
-        ? ("frontmatter" as const)
-        : ("docs-yml" as const),
-      tab,
-    },
-  };
- 
-  // Build nav item (skip if hidden)
-  const navItem: NavItem | undefined = pageItem.hidden
-    ? undefined
-    : {
-        title: pageItem.page,
-        path: `/${finalPath}`,
-        type: "page",
-      };
- 
-  // Build Algolia record (if content available and not hidden)
-  if (cached && navItem) {
-    const title = cached.frontmatter.title || pageItem.page;
-    context.addAlgoliaRecord({
-      pageType: "Guide",
-      path: finalPath,
-      title,
-      content: cached.content,
-      breadcrumbs: navigationAncestors, // Excludes current page
-    });
-  }
- 
-  return { indexEntries, navItem };
-};
- 
- -
-
- - - - - - - - \ No newline at end of file diff --git a/coverage/visitors/visit-section.ts.html b/coverage/visitors/visit-section.ts.html deleted file mode 100644 index c36e85a09..000000000 --- a/coverage/visitors/visit-section.ts.html +++ /dev/null @@ -1,520 +0,0 @@ - - - - - - Code coverage report for visitors/visit-section.ts - - - - - - - - - -
-
-

All files / visitors visit-section.ts

-
- -
- 100% - Statements - 31/31 -
- - -
- 83.33% - Branches - 15/18 -
- - -
- 100% - Functions - 5/5 -
- - -
- 100% - Lines - 31/31 -
- - -
-

- Press n or j to go to the next uncovered block, b, p or k for the previous block. -

- -
-
-

-
1 -2 -3 -4 -5 -6 -7 -8 -9 -10 -11 -12 -13 -14 -15 -16 -17 -18 -19 -20 -21 -22 -23 -24 -25 -26 -27 -28 -29 -30 -31 -32 -33 -34 -35 -36 -37 -38 -39 -40 -41 -42 -43 -44 -45 -46 -47 -48 -49 -50 -51 -52 -53 -54 -55 -56 -57 -58 -59 -60 -61 -62 -63 -64 -65 -66 -67 -68 -69 -70 -71 -72 -73 -74 -75 -76 -77 -78 -79 -80 -81 -82 -83 -84 -85 -86 -87 -88 -89 -90 -91 -92 -93 -94 -95 -96 -97 -98 -99 -100 -101 -102 -103 -104 -105 -106 -107 -108 -109 -110 -111 -112 -113 -114 -115 -116 -117 -118 -119 -120 -121 -122 -123 -124 -125 -126 -127 -128 -129 -130 -131 -132 -133 -134 -135 -136 -137 -138 -139 -140 -141 -142 -143 -144 -145 -146  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -2x -  -  -  -  -  -  -  -  -  -  -  -12x -12x -12x -  -  -  -12x -  -  -12x -1x -1x -1x -  -1x -  -  -  -  -  -1x -1x -  -  -1x -  -  -  -  -  -  -  -1x -1x -1x -  -  -  -  -  -  -  -  -  -  -12x -  -  -  -  -  -  -12x -  -  -  -  -  -  -  -  -12x -  -  -  -  -  -  -  -12x -  -  -  -  -12x -15x -  -  -  -  -  -  -  -  -12x -15x -  -  -  -12x -15x -  -15x -  -  -12x -1x -  -  -  -11x -  -11x -  - 
import { kebabCase } from "lodash-es";
- 
-import type { SectionConfig } from "@/content-indexer/types/docsYaml.js";
-import type { NavItem } from "@/content-indexer/types/navigation.js";
-import type { PathIndex } from "@/content-indexer/types/pathIndex.js";
-import {
-  normalizeFilePath,
-  normalizeSlug,
-} from "@/content-indexer/utils/normalization.js";
- 
-import type {
-  VisitorConfig,
-  VisitorConfigBase,
-  VisitorResult,
-} from "./index.js";
- 
-export interface SectionVisitorConfig extends VisitorConfigBase {
-  item: SectionConfig;
-}
- 
-/**
- * Visits a section item from docs.yml.
- *
- * Builds:
- * - Path index entry for overview page (if exists)
- * - Hierarchical navigation with children
- * - Algolia record for overview page (if exists)
- * - Recursively processes all child items
- */
-export const visitSection = (
-  config: SectionVisitorConfig,
-  visitNavigationItem: (config: VisitorConfig) => VisitorResult,
-): VisitorResult => {
-  const {
-    item: sectionItem,
-    parentPath,
-    tab,
-    repo,
-    contentCache,
-    context,
-    navigationAncestors,
-  } = config;
-  const sectionUrlSlug = sectionItem.slug ?? kebabCase(sectionItem.section);
-  const skipSlug = sectionItem["skip-slug"] ?? false;
- 
-  let sectionFullSlug: string[] | undefined;
-  let sectionPath: string | undefined;
-  const indexEntries: PathIndex = {};
- 
-  // If there's an overview page, look up cached content
-  if (sectionItem.path) {
-    const cached = contentCache.getMdxContent(sectionItem.path);
-    const normalizedSlug = normalizeSlug(cached?.frontmatter.slug as string);
-    sectionFullSlug = normalizedSlug?.split("/");
- 
-    const sectionPathBuilder = parentPath.apply({
-      fullSlug: sectionFullSlug,
-      urlSlug: sectionUrlSlug,
-      skipUrlSlug: skipSlug,
-    });
- 
-    const finalPath = sectionPathBuilder.get();
-    sectionPath = `/${finalPath}`;
- 
-    // Add overview page to index
-    indexEntries[finalPath] = {
-      type: "mdx",
-      filePath: normalizeFilePath(sectionItem.path, repo),
-      source: normalizedSlug ? "frontmatter" : "docs-yml",
-      tab,
-    };
- 
-    // Build Algolia record for section overview page (if content available)
-    Eif (cached) {
-      const title = (cached.frontmatter.title as string) || sectionItem.section;
-      context.addAlgoliaRecord({
-        pageType: "Guide",
-        path: finalPath,
-        title,
-        content: cached.content,
-        breadcrumbs: navigationAncestors, // Excludes current section
-      });
-    }
-  }
- 
-  // Create path builder for children
-  const childPathBuilder = parentPath.apply({
-    fullSlug: sectionFullSlug,
-    urlSlug: sectionUrlSlug,
-    skipUrlSlug: skipSlug,
-  });
- 
-  // Build section nav item first (for navigation tree)
-  const sectionNavItem: NavItem = {
-    title: sectionItem.section,
-    path: sectionPath,
-    type: "section",
-    children: [], // Will be populated below
-  };
- 
-  // Create breadcrumb (simple copy, no path computation needed)
-  // If section has no overview page, path will be undefined - this is OK
-  const sectionBreadcrumb: NavItem = {
-    title: sectionItem.section,
-    path: sectionPath, // undefined if no overview page
-    type: "section",
-    children: [],
-  };
- 
-  // Update ancestors to include current section (using breadcrumb copy)
-  const childAncestors = sectionItem.hidden
-    ? navigationAncestors // Don't include hidden sections in breadcrumbs
-    : [...navigationAncestors, sectionBreadcrumb];
- 
-  // Process all children with correct breadcrumbs
-  const childResults = sectionItem.contents.map((childItem) =>
-    visitNavigationItem({
-      ...config,
-      item: childItem,
-      parentPath: childPathBuilder,
-      navigationAncestors: childAncestors,
-    }),
-  );
- 
-  // Merge child index entries
-  childResults.forEach((result) => {
-    Object.assign(indexEntries, result.indexEntries);
-  });
- 
-  // Build children nav items (flatten arrays from API refs)
-  const children: NavItem[] = childResults
-    .map((result) => result.navItem)
-    .flat()
-    .filter((child): child is NavItem => child !== undefined);
- 
-  // Only include section in nav if it has children and is not hidden
-  if (children.length === 0 || sectionItem.hidden) {
-    return { indexEntries, navItem: undefined };
-  }
- 
-  // Update section nav item with children
-  sectionNavItem.children = children;
- 
-  return { indexEntries, navItem: sectionNavItem };
-};
- 
- -
-
- - - - - - - - \ No newline at end of file From f4f03315bf03fae74f3fb57cd8a0e8347e5c400b Mon Sep 17 00:00:00 2001 From: dslovinsky Date: Fri, 9 Jan 2026 14:09:04 -0800 Subject: [PATCH 20/27] chore: add coverage/ to gitignore --- .gitignore | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.gitignore b/.gitignore index 6492c543d..8fdaa6c0a 100644 --- a/.gitignore +++ b/.gitignore @@ -36,3 +36,5 @@ fern/api-specs/ # local files *.local +# vitests +coverage/ From 470017b54588cb49fa5cb8e4cfe568919b4f1ac2 Mon Sep 17 00:00:00 2001 From: dslovinsky Date: Fri, 9 Jan 2026 14:50:55 -0800 Subject: [PATCH 21/27] chore: remove repo dispatch trigger for GH pages deploy --- .github/workflows/gh-pages-deploy.yml | 2 -- 1 file changed, 2 deletions(-) diff --git a/.github/workflows/gh-pages-deploy.yml b/.github/workflows/gh-pages-deploy.yml index f39f45165..6aec52833 100644 --- a/.github/workflows/gh-pages-deploy.yml +++ b/.github/workflows/gh-pages-deploy.yml @@ -8,8 +8,6 @@ on: - main types: - completed - repository_dispatch: - types: [trigger-gh-pages-deploy] concurrency: group: "pages" cancel-in-progress: false From 5f21596ad6bd0903f63b4ab6fa8a838bd447e2ee Mon Sep 17 00:00:00 2001 From: dslovinsky Date: Mon, 12 Jan 2026 16:49:01 -0800 Subject: [PATCH 22/27] feat: add description field to algolia records --- src/content-indexer/collectors/algolia.ts | 2 + src/content-indexer/types/algolia.ts | 1 + .../utils/__tests__/openapi.test.ts | 25 ++++-- src/content-indexer/utils/openapi.ts | 76 ++++++++++++------- .../visitors/processors/process-openapi.ts | 16 ++-- .../visitors/processors/process-openrpc.ts | 9 ++- src/content-indexer/visitors/visit-page.ts | 5 ++ 7 files changed, 92 insertions(+), 42 deletions(-) diff --git a/src/content-indexer/collectors/algolia.ts b/src/content-indexer/collectors/algolia.ts index a8d6c2729..45a312427 100644 --- a/src/content-indexer/collectors/algolia.ts +++ b/src/content-indexer/collectors/algolia.ts @@ -16,6 +16,7 @@ const extractBreadcrumbTitles = (navItems: NavItem[]): string[] => { type AddRecordBaseParams = { path: string; title: string; + description?: string; content: string; breadcrumbs: NavItem[]; }; @@ -62,6 +63,7 @@ export class AlgoliaCollector { content: params.content, breadcrumbs: breadcrumbTitles, ...(params.httpMethod && { httpMethod: params.httpMethod }), + ...(params.description && { description: params.description }), }); } diff --git a/src/content-indexer/types/algolia.ts b/src/content-indexer/types/algolia.ts index 98c34260e..2bc35e6ea 100644 --- a/src/content-indexer/types/algolia.ts +++ b/src/content-indexer/types/algolia.ts @@ -7,6 +7,7 @@ export interface AlgoliaRecord { path: string; // Full pathname without leading slash (e.g., "reference/ethereum/eth-getbalance"). pageType: "API Method" | "Guide" | "Changelog"; title: string; + description?: string; // Brief 1-2 sentence summary of the content breadcrumbs: string[]; // Navigation ancestry titles for context (e.g., ["NFT API", "NFT API Endpoints"]) httpMethod?: string; // For API methods: "GET" | "POST" | etc. content: string; // MDX content or endpoint description diff --git a/src/content-indexer/utils/__tests__/openapi.test.ts b/src/content-indexer/utils/__tests__/openapi.test.ts index a75ac27c4..0278bcfa7 100644 --- a/src/content-indexer/utils/__tests__/openapi.test.ts +++ b/src/content-indexer/utils/__tests__/openapi.test.ts @@ -5,6 +5,7 @@ import { PathBuilder } from "@/content-indexer/core/path-builder.js"; import { buildOperationPath, extractOpenApiOperations, + getOperation, getOperationDescription, getOperationTitle, } from "../openapi.js"; @@ -135,7 +136,8 @@ describe("openapi utils", () => { }, }; - const title = getOperationTitle(spec, "getUsers", "/users"); + const operation = getOperation(spec, "/users", "get"); + const title = getOperationTitle(operation, "getUsers"); expect(title).toBe("Get All Users"); }); @@ -150,7 +152,8 @@ describe("openapi utils", () => { }, }; - const title = getOperationTitle(spec, "getUsers", "/users"); + const operation = getOperation(spec, "/users", "get"); + const title = getOperationTitle(operation, "getUsers"); expect(title).toBe("getUsers"); }); @@ -159,7 +162,8 @@ describe("openapi utils", () => { paths: {}, }; - const title = getOperationTitle(spec, "getUsers", "/users"); + const operation = getOperation(spec, "/users", "get"); + const title = getOperationTitle(operation, "getUsers"); expect(title).toBe("getUsers"); }); @@ -175,7 +179,8 @@ describe("openapi utils", () => { }, }; - const title = getOperationTitle(spec, "getUsers", "/users"); + const operation = getOperation(spec, "/users", "get"); + const title = getOperationTitle(operation, "getUsers"); expect(title).toBe("getUsers"); }); }); @@ -192,7 +197,8 @@ describe("openapi utils", () => { }, }; - const description = getOperationDescription(spec, "/users", "get"); + const operation = getOperation(spec, "/users", "get"); + const description = getOperationDescription(operation); expect(description).toBe("Retrieves all users from the system"); }); @@ -201,7 +207,8 @@ describe("openapi utils", () => { paths: {}, }; - const description = getOperationDescription(spec, "/users", "get"); + const operation = getOperation(spec, "/users", "get"); + const description = getOperationDescription(operation); expect(description).toBe(""); }); @@ -216,7 +223,8 @@ describe("openapi utils", () => { }, }; - const description = getOperationDescription(spec, "/users", "get"); + const operation = getOperation(spec, "/users", "get"); + const description = getOperationDescription(operation); expect(description).toBe(""); }); @@ -231,7 +239,8 @@ describe("openapi utils", () => { }, }; - const description = getOperationDescription(spec, "/users", "get"); + const operation = getOperation(spec, "/users", "get"); + const description = getOperationDescription(operation); expect(description).toBe(""); }); }); diff --git a/src/content-indexer/utils/openapi.ts b/src/content-indexer/utils/openapi.ts index 4ba06524a..e63efcbca 100644 --- a/src/content-indexer/utils/openapi.ts +++ b/src/content-indexer/utils/openapi.ts @@ -1,5 +1,6 @@ import { kebabCase } from "lodash-es"; import type { OpenAPIV3 } from "openapi-types"; +import removeMd from "remove-markdown"; import { HTTP_METHODS } from "@/content-indexer/constants/http.js"; import type { PathBuilder } from "@/content-indexer/core/path-builder.js"; @@ -11,6 +12,28 @@ export interface ExtractedOperation { tag?: string; } +/** + * Retrieves an operation object from a spec given a path and method. + * Returns undefined if the path or method doesn't exist. + */ +export const getOperation = ( + spec: { paths: Record }, + path: string, + method: string, +): OpenAPIV3.OperationObject | undefined => { + const pathItem = spec.paths[path]; + if (!pathItem || typeof pathItem !== "object") { + return undefined; + } + + const operation = (pathItem as Record)[method]; + if (!operation || typeof operation !== "object") { + return undefined; + } + + return operation as OpenAPIV3.OperationObject; +}; + /** * Extracts operationId from an OpenAPI operation object. * Falls back to summary or generates from method + path if operationId is missing. @@ -36,25 +59,14 @@ const getOperationId = ( * Prefers the summary field, falls back to operationId. */ export const getOperationTitle = ( - spec: { paths: Record }, + operation: OpenAPIV3.OperationObject | undefined, operationId: string, - operationPath: string, ): string => { - const pathItem = spec.paths[operationPath]; - if (!pathItem || typeof pathItem !== "object") { + if (!operation) { return operationId; } - // Find the operation with matching operationId - const operation = Object.values(pathItem).find( - (op: unknown) => - typeof op === "object" && - op !== null && - "operationId" in op && - (op as OpenAPIV3.OperationObject).operationId === operationId, - ) as OpenAPIV3.OperationObject | undefined; - - return operation?.summary || operationId; + return operation.summary || operationId; }; /** @@ -127,23 +139,35 @@ export const buildOperationPath = ( * Falls back to summary if description is not available. */ export const getOperationDescription = ( - spec: { paths: Record }, - path: string, - method: string, + operation: OpenAPIV3.OperationObject | undefined, ): string => { - const pathItem = spec.paths[path]; - if (!pathItem || typeof pathItem !== "object") { + if (!operation) { return ""; } - const operation = (pathItem as Record)[method]; - if (!operation || typeof operation !== "object") { - return ""; + return operation.description + ? removeMd(operation.description) + : operation.summary || ""; +}; + +/** + * Extracts a brief summary for an OpenAPI operation to use in search tooltip + */ +export const getOperationSummary = ( + operation: OpenAPIV3.OperationObject | undefined, +): string | undefined => { + if (!operation) { + return undefined; + } + + if (operation.summary) { + return operation.summary; } - const operationObj = operation as Record; - const description = operationObj.description as string | undefined; - const summary = operationObj.summary as string | undefined; + // If no summary but description exists, strip markdown and use it + if (operation.description) { + return removeMd(operation.description); + } - return description || summary || ""; + return undefined; }; diff --git a/src/content-indexer/visitors/processors/process-openapi.ts b/src/content-indexer/visitors/processors/process-openapi.ts index 479d64ba2..3e02ac057 100644 --- a/src/content-indexer/visitors/processors/process-openapi.ts +++ b/src/content-indexer/visitors/processors/process-openapi.ts @@ -7,7 +7,9 @@ import { createBreadcrumbNavItem } from "@/content-indexer/utils/navigation-help import { buildOperationPath, extractOpenApiOperations, + getOperation, getOperationDescription, + getOperationSummary, getOperationTitle, type ExtractedOperation, } from "@/content-indexer/utils/openapi.js"; @@ -106,19 +108,18 @@ const buildOpenApiNavigation = ({ operation.tag, ); - const title = getOperationTitle( + const operationObj = getOperation( spec, - operation.operationId, operation.path, + operation.method.toLowerCase(), ); + const title = getOperationTitle(operationObj, operation.operationId); + // Build Algolia record if not hidden if (!isHidden) { - const description = getOperationDescription( - spec, - operation.path, - operation.method.toLowerCase(), - ); + const description = getOperationDescription(operationObj); + const summary = getOperationSummary(operationObj); const breadcrumbs = apiSectionBreadcrumb ? [...navigationAncestors, apiSectionBreadcrumb] @@ -131,6 +132,7 @@ const buildOpenApiNavigation = ({ content: description, httpMethod: operation.method, breadcrumbs, + description: summary, }); } diff --git a/src/content-indexer/visitors/processors/process-openrpc.ts b/src/content-indexer/visitors/processors/process-openrpc.ts index 01925f4b8..20c2c62b4 100644 --- a/src/content-indexer/visitors/processors/process-openrpc.ts +++ b/src/content-indexer/visitors/processors/process-openrpc.ts @@ -1,4 +1,5 @@ import { kebabCase } from "lodash-es"; +import removeMd from "remove-markdown"; import type { PathBuilder } from "@/content-indexer/core/path-builder.js"; import type { NavItem } from "@/content-indexer/types/navigation.js"; @@ -70,7 +71,12 @@ export const processOpenRpcSpec = ({ // Build Algolia record if not hidden if (!isHidden) { - const description = method.description || method.summary || ""; + const description = method.description + ? removeMd(method.description) + : method.summary || ""; + const summary = + method.summary || + (method.description ? removeMd(method.description) : undefined); const breadcrumbs = apiSectionBreadcrumb ? [...navigationAncestors, apiSectionBreadcrumb] : navigationAncestors; @@ -82,6 +88,7 @@ export const processOpenRpcSpec = ({ content: description, httpMethod: "POST", breadcrumbs, + description: summary, }); } diff --git a/src/content-indexer/visitors/visit-page.ts b/src/content-indexer/visitors/visit-page.ts index de2c1932f..3fa632954 100644 --- a/src/content-indexer/visitors/visit-page.ts +++ b/src/content-indexer/visitors/visit-page.ts @@ -66,12 +66,17 @@ export const visitPage = ({ // Build Algolia record (if content available and not hidden) if (cached && navItem) { const title = cached.frontmatter.title || pageItem.page; + const descriptionRaw = + cached.frontmatter.description || cached.frontmatter.subtitle; + const description = + typeof descriptionRaw === "string" ? descriptionRaw : undefined; context.addAlgoliaRecord({ pageType: "Guide", path: finalPath, title, content: cached.content, breadcrumbs: navigationAncestors, // Excludes current page + description, }); } From 74b2b39b70b6af416852a3c0d20f1c8075bf82d8 Mon Sep 17 00:00:00 2001 From: dslovinsky Date: Mon, 12 Jan 2026 16:54:34 -0800 Subject: [PATCH 23/27] fix: add missing algolia descriptions for section pages --- src/content-indexer/visitors/visit-section.ts | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/content-indexer/visitors/visit-section.ts b/src/content-indexer/visitors/visit-section.ts index da697c33e..ef5262f5f 100644 --- a/src/content-indexer/visitors/visit-section.ts +++ b/src/content-indexer/visitors/visit-section.ts @@ -73,12 +73,17 @@ export const visitSection = ( // Build Algolia record for section overview page (if content available) if (cached) { const title = (cached.frontmatter.title as string) || sectionItem.section; + const descriptionRaw = + cached.frontmatter.description || cached.frontmatter.subtitle; + const description = + typeof descriptionRaw === "string" ? descriptionRaw : undefined; context.addAlgoliaRecord({ pageType: "Guide", path: finalPath, title, content: cached.content, breadcrumbs: navigationAncestors, // Excludes current section + description, }); } } From b5b4d96ef6be105e737072e680209d32ed1a9931 Mon Sep 17 00:00:00 2001 From: dslovinsky Date: Mon, 12 Jan 2026 23:07:28 -0800 Subject: [PATCH 24/27] chore: remove unused func --- src/content-indexer/utils/github.ts | 37 ----------------------------- 1 file changed, 37 deletions(-) diff --git a/src/content-indexer/utils/github.ts b/src/content-indexer/utils/github.ts index 962743967..cc75ce5d7 100644 --- a/src/content-indexer/utils/github.ts +++ b/src/content-indexer/utils/github.ts @@ -101,43 +101,6 @@ export async function fetchGitHubDirectory( } } -/** - * Fetch multiple files in parallel with rate limiting - */ -export async function fetchMultipleFiles( - filePaths: string[], - concurrency: number = 10, - repoConfig: RepoConfig = DOCS_REPO, -): Promise> { - const results = new Map(); - - // Process in batches to respect rate limits - // Octokit has built-in rate limiting, but we still batch for efficiency - for (let i = 0; i < filePaths.length; i += concurrency) { - const batch = filePaths.slice(i, i + concurrency); - - const batchResults = await Promise.all( - batch.map(async (path) => { - const content = await fetchFileFromGitHub(path, repoConfig); - return { path, content }; - }), - ); - - for (const { path, content } of batchResults) { - if (content) { - results.set(path, content); - } - } - - // Small delay between batches to be conservative with rate limits - if (i + concurrency < filePaths.length) { - await new Promise((resolve) => setTimeout(resolve, 100)); - } - } - - return results; -} - /** * Check if a file exists on GitHub */ From b085a0c62c513c4f942343cdce781e957e96830e Mon Sep 17 00:00:00 2001 From: dslovinsky Date: Mon, 12 Jan 2026 23:36:34 -0800 Subject: [PATCH 25/27] fix: use paths instead of title/breadcrumb for algolia objectID seed --- src/content-indexer/README.md | 22 ++++---- .../collectors/__tests__/algolia.test.ts | 51 +++++++++++++++---- src/content-indexer/collectors/algolia.ts | 13 ++--- 3 files changed, 56 insertions(+), 30 deletions(-) diff --git a/src/content-indexer/README.md b/src/content-indexer/README.md index 562efcb40..e4909ccf2 100644 --- a/src/content-indexer/README.md +++ b/src/content-indexer/README.md @@ -210,24 +210,24 @@ By fetching everything upfront in Phase 2, we eliminate duplicate API calls duri All Algolia records automatically have markdown syntax stripped using the `remove-markdown` package in `truncateRecord()`. This ensures search results contain clean, readable text without formatting artifacts. -### Relatively Stable ObjectIDs for Algolia +### Stable ObjectIDs for Algolia Algolia requires unique `objectID` for each record. We generate deterministic -hashes (SHA-256, first 16 chars) from content-based identifiers: +hashes (SHA-256, first 16 chars) from URL paths: -* **All pages (MDX and API methods)**: Hash of last breadcrumb + title (e.g., hash of - `"NFT API Endpoints:getNFTsForCollection"`) - * Based on logical position in navigation hierarchy + page title - * Stable as long as content structure and title don't change - * Changes when page is renamed or moved to different section +* **All pages (MDX and API methods)**: Hash of the URL path (e.g., hash of + `"reference/eth-getbalance"`) + * Uniqueness: URLs are guaranteed unique by the routing system + * Stability: Paths are designed to be stable. Changes happen but infrequently + * Enables incremental index updates in the future * Generates clean IDs like `"a3f2c8e1b9d4f6a7"` -**Why this approach?** Since we replace the entire index on each run (atomic swap), -absolute stability isn't critical. Content-based IDs are simple and work consistently for all content types. -There is no field that provides absolute stability since everything can change. +**Why path-based?** Paths are the web's natural unique identifier and are specifically +designed to be stable. Unlike titles or breadcrumbs, URL changes are typically +rare and intentional (considered breaking changes for SEO and bookmarks). **Why hashes?** Provides compact, opaque identifiers that don't expose internal -structure while maintaining uniqueness. +structure while maintaining the URL's uniqueness guarantee. ## Design Decisions diff --git a/src/content-indexer/collectors/__tests__/algolia.test.ts b/src/content-indexer/collectors/__tests__/algolia.test.ts index 820c97ef1..46bed6cf7 100644 --- a/src/content-indexer/collectors/__tests__/algolia.test.ts +++ b/src/content-indexer/collectors/__tests__/algolia.test.ts @@ -60,7 +60,7 @@ describe("AlgoliaCollector", () => { expect(records[0].breadcrumbs).toEqual(["NFT API", "NFT API Endpoints"]); }); - test("should generate stable objectID from last breadcrumb + title", () => { + test("should generate stable objectID from path", () => { const collector = new AlgoliaCollector(); collector.addRecord({ pageType: "API Method", @@ -138,15 +138,15 @@ describe("AlgoliaCollector", () => { const records = collector.getRecords(); expect(records[0].breadcrumbs).toEqual([]); - // ObjectID should still be generated (using "unknown" + title) + // ObjectID should still be generated (using path) expect(records[0].objectID).toBeDefined(); }); - test("should generate consistent objectID for same last breadcrumb + title", () => { + test("should generate consistent objectID for same path", () => { const collector1 = new AlgoliaCollector(); collector1.addRecord({ pageType: "API Method", - path: "reference/path1", + path: "reference/eth-getbalance", title: "eth_getBalance", content: "Content 1", httpMethod: "POST", @@ -159,19 +159,48 @@ describe("AlgoliaCollector", () => { const collector2 = new AlgoliaCollector(); collector2.addRecord({ pageType: "API Method", - path: "reference/different-path", - title: "eth_getBalance", - content: "Content 2", - httpMethod: "GET", + path: "reference/eth-getbalance", // Same path + title: "eth_getBalance_v2", // Different title + content: "Content 2", // Different content + httpMethod: "GET", // Different method breadcrumbs: [ - { title: "API", type: "section", children: [] }, - { title: "Ethereum Endpoints", type: "section", children: [] }, + { title: "Different API", type: "section", children: [] }, // Different breadcrumbs ], }); const records1 = collector1.getRecords(); const records2 = collector2.getRecords(); - // Same last breadcrumb + title should generate same objectID + // Same path should generate same objectID, regardless of other metadata expect(records1[0].objectID).toBe(records2[0].objectID); }); + + test("should generate different objectIDs for different paths", () => { + const collector = new AlgoliaCollector(); + collector.addRecord({ + pageType: "API Method", + path: "reference/eth-getbalance", + title: "eth_getBalance", + content: "Content", + httpMethod: "POST", + breadcrumbs: [ + { title: "API", type: "section", children: [] }, + { title: "Ethereum Endpoints", type: "section", children: [] }, + ], + }); + collector.addRecord({ + pageType: "API Method", + path: "reference/eth-blocknumber", + title: "eth_getBalance", // Same title + content: "Content", + httpMethod: "POST", + breadcrumbs: [ + { title: "API", type: "section", children: [] }, + { title: "Ethereum Endpoints", type: "section", children: [] }, // Same breadcrumbs + ], + }); + + const records = collector.getRecords(); + // Different paths should generate different objectIDs, even with same title/breadcrumbs + expect(records[0].objectID).not.toBe(records[1].objectID); + }); }); diff --git a/src/content-indexer/collectors/algolia.ts b/src/content-indexer/collectors/algolia.ts index 45a312427..e67195d6a 100644 --- a/src/content-indexer/collectors/algolia.ts +++ b/src/content-indexer/collectors/algolia.ts @@ -43,17 +43,14 @@ export class AlgoliaCollector { * Add a search record for either MDX pages or API methods. * * ObjectID strategy: - * Uses hash of last breadcrumb + title for relatively stable, content-based identification. - * If we change the title or the last breadcrumb, the objectID will change, - * but this shouldn't matter as long as we continue to replace the entire index on each run. + * Uses hash of the URL path for stable, unique identification. + * - Uniqueness: URLs are guaranteed unique by the routing system + * - Stability: Paths are designed to be stable (SEO, bookmarks, external links) + * - Enables incremental index updates in the future */ addRecord(params: AddRecordParams): void { const breadcrumbTitles = extractBreadcrumbTitles(params.breadcrumbs); - - // Generate stable objectID from last breadcrumb (most specific section) + title - const lastBreadcrumb = breadcrumbTitles.at(-1) || "unknown"; - const stableId = `${lastBreadcrumb}:${params.title}`; - const objectID = this.generateHash(stableId); + const objectID = this.generateHash(params.path); this.records.push({ objectID, From 42e4aaeb90830b7fc66ecebd9fd85057a3d71187 Mon Sep 17 00:00:00 2001 From: dslovinsky Date: Tue, 13 Jan 2026 00:03:50 -0800 Subject: [PATCH 26/27] fix: docs homepage bug caused by .js imports --- fern/components/CodeConsole/codeData.ts | 258 ++++++++++++------------ fern/components/CodeConsole/index.tsx | 8 +- fern/components/Footer.tsx | 20 +- tsconfig.json | 2 + 4 files changed, 145 insertions(+), 143 deletions(-) diff --git a/fern/components/CodeConsole/codeData.ts b/fern/components/CodeConsole/codeData.ts index 279d287e3..48084ba27 100644 --- a/fern/components/CodeConsole/codeData.ts +++ b/fern/components/CodeConsole/codeData.ts @@ -1,132 +1,132 @@ -import alchemyGetAssetTransfersEthereumRequest from "./code-samples/alchemy_getAssetTransfers/ethereum-request.js"; -import alchemyGetAssetTransfersEthereumResponse from "./code-samples/alchemy_getAssetTransfers/ethereum-response.js"; -import alchemyGetTokenBalancesEthereumRequest from "./code-samples/alchemy_getTokenBalances/ethereum-request.js"; -import alchemyGetTokenBalancesEthereumResponse from "./code-samples/alchemy_getTokenBalances/ethereum-response.js"; -import ethBlockNumberArbitrumRequest from "./code-samples/eth_blockNumber/arbitrum-request.js"; -import ethBlockNumberArbitrumResponse from "./code-samples/eth_blockNumber/arbitrum-response.js"; -import ethBlockNumberBaseRequest from "./code-samples/eth_blockNumber/base-request.js"; -import ethBlockNumberBaseResponse from "./code-samples/eth_blockNumber/base-response.js"; -import ethBlockNumberEthereumRequest from "./code-samples/eth_blockNumber/ethereum-request.js"; -import ethBlockNumberEthereumResponse from "./code-samples/eth_blockNumber/ethereum-response.js"; -import ethBlockNumberOptimismRequest from "./code-samples/eth_blockNumber/optimism-request.js"; -import ethBlockNumberOptimismResponse from "./code-samples/eth_blockNumber/optimism-response.js"; -import ethBlockNumberPolygonRequest from "./code-samples/eth_blockNumber/polygon-request.js"; -import ethBlockNumberPolygonResponse from "./code-samples/eth_blockNumber/polygon-response.js"; -import ethChainIdArbitrumRequest from "./code-samples/eth_chainId/arbitrum-request.js"; -import ethChainIdArbitrumResponse from "./code-samples/eth_chainId/arbitrum-response.js"; -import ethChainIdBaseRequest from "./code-samples/eth_chainId/base-request.js"; -import ethChainIdBaseResponse from "./code-samples/eth_chainId/base-response.js"; -import ethChainIdEthereumRequest from "./code-samples/eth_chainId/ethereum-request.js"; -import ethChainIdEthereumResponse from "./code-samples/eth_chainId/ethereum-response.js"; -import ethChainIdOptimismRequest from "./code-samples/eth_chainId/optimism-request.js"; -import ethChainIdOptimismResponse from "./code-samples/eth_chainId/optimism-response.js"; -import ethChainIdPolygonRequest from "./code-samples/eth_chainId/polygon-request.js"; -import ethChainIdPolygonResponse from "./code-samples/eth_chainId/polygon-response.js"; -import ethEstimateGasArbitrumRequest from "./code-samples/eth_estimateGas/arbitrum-request.js"; -import ethEstimateGasArbitrumResponse from "./code-samples/eth_estimateGas/arbitrum-response.js"; -import ethEstimateGasBaseRequest from "./code-samples/eth_estimateGas/base-request.js"; -import ethEstimateGasBaseResponse from "./code-samples/eth_estimateGas/base-response.js"; -import ethEstimateGasEthereumRequest from "./code-samples/eth_estimateGas/ethereum-request.js"; -import ethEstimateGasEthereumResponse from "./code-samples/eth_estimateGas/ethereum-response.js"; -import ethEstimateGasOptimismRequest from "./code-samples/eth_estimateGas/optimism-request.js"; -import ethEstimateGasOptimismResponse from "./code-samples/eth_estimateGas/optimism-response.js"; -import ethEstimateGasPolygonRequest from "./code-samples/eth_estimateGas/polygon-request.js"; -import ethEstimateGasPolygonResponse from "./code-samples/eth_estimateGas/polygon-response.js"; -import ethGasPriceArbitrumRequest from "./code-samples/eth_gasPrice/arbitrum-request.js"; -import ethGasPriceArbitrumResponse from "./code-samples/eth_gasPrice/arbitrum-response.js"; -import ethGasPriceBaseRequest from "./code-samples/eth_gasPrice/base-request.js"; -import ethGasPriceBaseResponse from "./code-samples/eth_gasPrice/base-response.js"; -import ethGasPriceEthereumRequest from "./code-samples/eth_gasPrice/ethereum-request.js"; -import ethGasPriceEthereumResponse from "./code-samples/eth_gasPrice/ethereum-response.js"; -import ethGasPriceOptimismRequest from "./code-samples/eth_gasPrice/optimism-request.js"; -import ethGasPriceOptimismResponse from "./code-samples/eth_gasPrice/optimism-response.js"; -import ethGasPricePolygonRequest from "./code-samples/eth_gasPrice/polygon-request.js"; -import ethGasPricePolygonResponse from "./code-samples/eth_gasPrice/polygon-response.js"; -import ethGetBalanceArbitrumRequest from "./code-samples/eth_getBalance/arbitrum-request.js"; -import ethGetBalanceArbitrumResponse from "./code-samples/eth_getBalance/arbitrum-response.js"; -import ethGetBalanceBaseRequest from "./code-samples/eth_getBalance/base-request.js"; -import ethGetBalanceBaseResponse from "./code-samples/eth_getBalance/base-response.js"; -import ethGetBalanceEthereumRequest from "./code-samples/eth_getBalance/ethereum-request.js"; -import ethGetBalanceEthereumResponse from "./code-samples/eth_getBalance/ethereum-response.js"; -import ethGetBalanceOptimismRequest from "./code-samples/eth_getBalance/optimism-request.js"; -import ethGetBalanceOptimismResponse from "./code-samples/eth_getBalance/optimism-response.js"; -import ethGetBalancePolygonRequest from "./code-samples/eth_getBalance/polygon-request.js"; -import ethGetBalancePolygonResponse from "./code-samples/eth_getBalance/polygon-response.js"; -import ethGetBlockByNumberArbitrumRequest from "./code-samples/eth_getBlockByNumber/arbitrum-request.js"; -import ethGetBlockByNumberArbitrumResponse from "./code-samples/eth_getBlockByNumber/arbitrum-response.js"; -import ethGetBlockByNumberBaseRequest from "./code-samples/eth_getBlockByNumber/base-request.js"; -import ethGetBlockByNumberBaseResponse from "./code-samples/eth_getBlockByNumber/base-response.js"; -import ethGetBlockByNumberEthereumRequest from "./code-samples/eth_getBlockByNumber/ethereum-request.js"; -import ethGetBlockByNumberEthereumResponse from "./code-samples/eth_getBlockByNumber/ethereum-response.js"; -import ethGetBlockByNumberOptimismRequest from "./code-samples/eth_getBlockByNumber/optimism-request.js"; -import ethGetBlockByNumberOptimismResponse from "./code-samples/eth_getBlockByNumber/optimism-response.js"; -import ethGetBlockByNumberPolygonRequest from "./code-samples/eth_getBlockByNumber/polygon-request.js"; -import ethGetBlockByNumberPolygonResponse from "./code-samples/eth_getBlockByNumber/polygon-response.js"; -import ethGetBlockReceiptsArbitrumRequest from "./code-samples/eth_getBlockReceipts/arbitrum-request.js"; -import ethGetBlockReceiptsArbitrumResponse from "./code-samples/eth_getBlockReceipts/arbitrum-response.js"; -import ethGetBlockReceiptsBaseRequest from "./code-samples/eth_getBlockReceipts/base-request.js"; -import ethGetBlockReceiptsBaseResponse from "./code-samples/eth_getBlockReceipts/base-response.js"; -import ethGetBlockReceiptsEthereumRequest from "./code-samples/eth_getBlockReceipts/ethereum-request.js"; -import ethGetBlockReceiptsEthereumResponse from "./code-samples/eth_getBlockReceipts/ethereum-response.js"; -import ethGetBlockReceiptsOptimismRequest from "./code-samples/eth_getBlockReceipts/optimism-request.js"; -import ethGetBlockReceiptsOptimismResponse from "./code-samples/eth_getBlockReceipts/optimism-response.js"; -import ethGetBlockReceiptsPolygonRequest from "./code-samples/eth_getBlockReceipts/polygon-request.js"; -import ethGetBlockReceiptsPolygonResponse from "./code-samples/eth_getBlockReceipts/polygon-response.js"; -import ethGetLogsArbitrumRequest from "./code-samples/eth_getLogs/arbitrum-request.js"; -import ethGetLogsArbitrumResponse from "./code-samples/eth_getLogs/arbitrum-response.js"; -import ethGetLogsBaseRequest from "./code-samples/eth_getLogs/base-request.js"; -import ethGetLogsBaseResponse from "./code-samples/eth_getLogs/base-response.js"; -import ethGetLogsEthereumRequest from "./code-samples/eth_getLogs/ethereum-request.js"; -import ethGetLogsEthereumResponse from "./code-samples/eth_getLogs/ethereum-response.js"; -import ethGetLogsOptimismRequest from "./code-samples/eth_getLogs/optimism-request.js"; -import ethGetLogsOptimismResponse from "./code-samples/eth_getLogs/optimism-response.js"; -import ethGetLogsPolygonRequest from "./code-samples/eth_getLogs/polygon-request.js"; -import ethGetLogsPolygonResponse from "./code-samples/eth_getLogs/polygon-response.js"; -import ethGetTransactionByHashArbitrumRequest from "./code-samples/eth_getTransactionByHash/arbitrum-request.js"; -import ethGetTransactionByHashArbitrumResponse from "./code-samples/eth_getTransactionByHash/arbitrum-response.js"; -import ethGetTransactionByHashBaseRequest from "./code-samples/eth_getTransactionByHash/base-request.js"; -import ethGetTransactionByHashBaseResponse from "./code-samples/eth_getTransactionByHash/base-response.js"; -import ethGetTransactionByHashEthereumRequest from "./code-samples/eth_getTransactionByHash/ethereum-request.js"; -import ethGetTransactionByHashEthereumResponse from "./code-samples/eth_getTransactionByHash/ethereum-response.js"; -import ethGetTransactionByHashOptimismRequest from "./code-samples/eth_getTransactionByHash/optimism-request.js"; -import ethGetTransactionByHashOptimismResponse from "./code-samples/eth_getTransactionByHash/optimism-response.js"; -import ethGetTransactionByHashPolygonRequest from "./code-samples/eth_getTransactionByHash/polygon-request.js"; -import ethGetTransactionByHashPolygonResponse from "./code-samples/eth_getTransactionByHash/polygon-response.js"; -import ethGetTransactionCountArbitrumRequest from "./code-samples/eth_getTransactionCount/arbitrum-request.js"; -import ethGetTransactionCountArbitrumResponse from "./code-samples/eth_getTransactionCount/arbitrum-response.js"; -import ethGetTransactionCountBaseRequest from "./code-samples/eth_getTransactionCount/base-request.js"; -import ethGetTransactionCountBaseResponse from "./code-samples/eth_getTransactionCount/base-response.js"; -import ethGetTransactionCountEthereumRequest from "./code-samples/eth_getTransactionCount/ethereum-request.js"; -import ethGetTransactionCountEthereumResponse from "./code-samples/eth_getTransactionCount/ethereum-response.js"; -import ethGetTransactionCountOptimismRequest from "./code-samples/eth_getTransactionCount/optimism-request.js"; -import ethGetTransactionCountOptimismResponse from "./code-samples/eth_getTransactionCount/optimism-response.js"; -import ethGetTransactionCountPolygonRequest from "./code-samples/eth_getTransactionCount/polygon-request.js"; -import ethGetTransactionCountPolygonResponse from "./code-samples/eth_getTransactionCount/polygon-response.js"; -import ethGetTransactionReceiptArbitrumRequest from "./code-samples/eth_getTransactionReceipt/arbitrum-request.js"; -import ethGetTransactionReceiptArbitrumResponse from "./code-samples/eth_getTransactionReceipt/arbitrum-response.js"; -import ethGetTransactionReceiptBaseRequest from "./code-samples/eth_getTransactionReceipt/base-request.js"; -import ethGetTransactionReceiptBaseResponse from "./code-samples/eth_getTransactionReceipt/base-response.js"; -import ethGetTransactionReceiptEthereumRequest from "./code-samples/eth_getTransactionReceipt/ethereum-request.js"; -import ethGetTransactionReceiptEthereumResponse from "./code-samples/eth_getTransactionReceipt/ethereum-response.js"; -import ethGetTransactionReceiptOptimismRequest from "./code-samples/eth_getTransactionReceipt/optimism-request.js"; -import ethGetTransactionReceiptOptimismResponse from "./code-samples/eth_getTransactionReceipt/optimism-response.js"; -import ethGetTransactionReceiptPolygonRequest from "./code-samples/eth_getTransactionReceipt/polygon-request.js"; -import ethGetTransactionReceiptPolygonResponse from "./code-samples/eth_getTransactionReceipt/polygon-response.js"; -import getAccountInfoSolanaRequest from "./code-samples/getAccountInfo/solana-request.js"; -import getAccountInfoSolanaResponse from "./code-samples/getAccountInfo/solana-response.js"; -import getBalanceSolanaRequest from "./code-samples/getBalance/solana-request.js"; -import getBalanceSolanaResponse from "./code-samples/getBalance/solana-response.js"; -import getLatestBlockhashSolanaRequest from "./code-samples/getLatestBlockhash/solana-request.js"; -import getLatestBlockhashSolanaResponse from "./code-samples/getLatestBlockhash/solana-response.js"; -import getSignaturesForAddressSolanaRequest from "./code-samples/getSignaturesForAddress/solana-request.js"; -import getSignaturesForAddressSolanaResponse from "./code-samples/getSignaturesForAddress/solana-response.js"; -import getTokenAccountBalanceSolanaRequest from "./code-samples/getTokenAccountBalance/solana-request.js"; -import getTokenAccountBalanceSolanaResponse from "./code-samples/getTokenAccountBalance/solana-response.js"; -import getTokenAccountsByOwnerSolanaRequest from "./code-samples/getTokenAccountsByOwner/solana-request.js"; -import getTokenAccountsByOwnerSolanaResponse from "./code-samples/getTokenAccountsByOwner/solana-response.js"; -import getTransactionSolanaRequest from "./code-samples/getTransaction/solana-request.js"; -import getTransactionSolanaResponse from "./code-samples/getTransaction/solana-response.js"; -import type { Option } from "./CodeblockSelect.js"; +import alchemyGetAssetTransfersEthereumRequest from "./code-samples/alchemy_getAssetTransfers/ethereum-request.ts"; +import alchemyGetAssetTransfersEthereumResponse from "./code-samples/alchemy_getAssetTransfers/ethereum-response.ts"; +import alchemyGetTokenBalancesEthereumRequest from "./code-samples/alchemy_getTokenBalances/ethereum-request.ts"; +import alchemyGetTokenBalancesEthereumResponse from "./code-samples/alchemy_getTokenBalances/ethereum-response.ts"; +import ethBlockNumberArbitrumRequest from "./code-samples/eth_blockNumber/arbitrum-request.ts"; +import ethBlockNumberArbitrumResponse from "./code-samples/eth_blockNumber/arbitrum-response.ts"; +import ethBlockNumberBaseRequest from "./code-samples/eth_blockNumber/base-request.ts"; +import ethBlockNumberBaseResponse from "./code-samples/eth_blockNumber/base-response.ts"; +import ethBlockNumberEthereumRequest from "./code-samples/eth_blockNumber/ethereum-request.ts"; +import ethBlockNumberEthereumResponse from "./code-samples/eth_blockNumber/ethereum-response.ts"; +import ethBlockNumberOptimismRequest from "./code-samples/eth_blockNumber/optimism-request.ts"; +import ethBlockNumberOptimismResponse from "./code-samples/eth_blockNumber/optimism-response.ts"; +import ethBlockNumberPolygonRequest from "./code-samples/eth_blockNumber/polygon-request.ts"; +import ethBlockNumberPolygonResponse from "./code-samples/eth_blockNumber/polygon-response.ts"; +import ethChainIdArbitrumRequest from "./code-samples/eth_chainId/arbitrum-request.ts"; +import ethChainIdArbitrumResponse from "./code-samples/eth_chainId/arbitrum-response.ts"; +import ethChainIdBaseRequest from "./code-samples/eth_chainId/base-request.ts"; +import ethChainIdBaseResponse from "./code-samples/eth_chainId/base-response.ts"; +import ethChainIdEthereumRequest from "./code-samples/eth_chainId/ethereum-request.ts"; +import ethChainIdEthereumResponse from "./code-samples/eth_chainId/ethereum-response.ts"; +import ethChainIdOptimismRequest from "./code-samples/eth_chainId/optimism-request.ts"; +import ethChainIdOptimismResponse from "./code-samples/eth_chainId/optimism-response.ts"; +import ethChainIdPolygonRequest from "./code-samples/eth_chainId/polygon-request.ts"; +import ethChainIdPolygonResponse from "./code-samples/eth_chainId/polygon-response.ts"; +import ethEstimateGasArbitrumRequest from "./code-samples/eth_estimateGas/arbitrum-request.ts"; +import ethEstimateGasArbitrumResponse from "./code-samples/eth_estimateGas/arbitrum-response.ts"; +import ethEstimateGasBaseRequest from "./code-samples/eth_estimateGas/base-request.ts"; +import ethEstimateGasBaseResponse from "./code-samples/eth_estimateGas/base-response.ts"; +import ethEstimateGasEthereumRequest from "./code-samples/eth_estimateGas/ethereum-request.ts"; +import ethEstimateGasEthereumResponse from "./code-samples/eth_estimateGas/ethereum-response.ts"; +import ethEstimateGasOptimismRequest from "./code-samples/eth_estimateGas/optimism-request.ts"; +import ethEstimateGasOptimismResponse from "./code-samples/eth_estimateGas/optimism-response.ts"; +import ethEstimateGasPolygonRequest from "./code-samples/eth_estimateGas/polygon-request.ts"; +import ethEstimateGasPolygonResponse from "./code-samples/eth_estimateGas/polygon-response.ts"; +import ethGasPriceArbitrumRequest from "./code-samples/eth_gasPrice/arbitrum-request.ts"; +import ethGasPriceArbitrumResponse from "./code-samples/eth_gasPrice/arbitrum-response.ts"; +import ethGasPriceBaseRequest from "./code-samples/eth_gasPrice/base-request.ts"; +import ethGasPriceBaseResponse from "./code-samples/eth_gasPrice/base-response.ts"; +import ethGasPriceEthereumRequest from "./code-samples/eth_gasPrice/ethereum-request.ts"; +import ethGasPriceEthereumResponse from "./code-samples/eth_gasPrice/ethereum-response.ts"; +import ethGasPriceOptimismRequest from "./code-samples/eth_gasPrice/optimism-request.ts"; +import ethGasPriceOptimismResponse from "./code-samples/eth_gasPrice/optimism-response.ts"; +import ethGasPricePolygonRequest from "./code-samples/eth_gasPrice/polygon-request.ts"; +import ethGasPricePolygonResponse from "./code-samples/eth_gasPrice/polygon-response.ts"; +import ethGetBalanceArbitrumRequest from "./code-samples/eth_getBalance/arbitrum-request.ts"; +import ethGetBalanceArbitrumResponse from "./code-samples/eth_getBalance/arbitrum-response.ts"; +import ethGetBalanceBaseRequest from "./code-samples/eth_getBalance/base-request.ts"; +import ethGetBalanceBaseResponse from "./code-samples/eth_getBalance/base-response.ts"; +import ethGetBalanceEthereumRequest from "./code-samples/eth_getBalance/ethereum-request.ts"; +import ethGetBalanceEthereumResponse from "./code-samples/eth_getBalance/ethereum-response.ts"; +import ethGetBalanceOptimismRequest from "./code-samples/eth_getBalance/optimism-request.ts"; +import ethGetBalanceOptimismResponse from "./code-samples/eth_getBalance/optimism-response.ts"; +import ethGetBalancePolygonRequest from "./code-samples/eth_getBalance/polygon-request.ts"; +import ethGetBalancePolygonResponse from "./code-samples/eth_getBalance/polygon-response.ts"; +import ethGetBlockByNumberArbitrumRequest from "./code-samples/eth_getBlockByNumber/arbitrum-request.ts"; +import ethGetBlockByNumberArbitrumResponse from "./code-samples/eth_getBlockByNumber/arbitrum-response.ts"; +import ethGetBlockByNumberBaseRequest from "./code-samples/eth_getBlockByNumber/base-request.ts"; +import ethGetBlockByNumberBaseResponse from "./code-samples/eth_getBlockByNumber/base-response.ts"; +import ethGetBlockByNumberEthereumRequest from "./code-samples/eth_getBlockByNumber/ethereum-request.ts"; +import ethGetBlockByNumberEthereumResponse from "./code-samples/eth_getBlockByNumber/ethereum-response.ts"; +import ethGetBlockByNumberOptimismRequest from "./code-samples/eth_getBlockByNumber/optimism-request.ts"; +import ethGetBlockByNumberOptimismResponse from "./code-samples/eth_getBlockByNumber/optimism-response.ts"; +import ethGetBlockByNumberPolygonRequest from "./code-samples/eth_getBlockByNumber/polygon-request.ts"; +import ethGetBlockByNumberPolygonResponse from "./code-samples/eth_getBlockByNumber/polygon-response.ts"; +import ethGetBlockReceiptsArbitrumRequest from "./code-samples/eth_getBlockReceipts/arbitrum-request.ts"; +import ethGetBlockReceiptsArbitrumResponse from "./code-samples/eth_getBlockReceipts/arbitrum-response.ts"; +import ethGetBlockReceiptsBaseRequest from "./code-samples/eth_getBlockReceipts/base-request.ts"; +import ethGetBlockReceiptsBaseResponse from "./code-samples/eth_getBlockReceipts/base-response.ts"; +import ethGetBlockReceiptsEthereumRequest from "./code-samples/eth_getBlockReceipts/ethereum-request.ts"; +import ethGetBlockReceiptsEthereumResponse from "./code-samples/eth_getBlockReceipts/ethereum-response.ts"; +import ethGetBlockReceiptsOptimismRequest from "./code-samples/eth_getBlockReceipts/optimism-request.ts"; +import ethGetBlockReceiptsOptimismResponse from "./code-samples/eth_getBlockReceipts/optimism-response.ts"; +import ethGetBlockReceiptsPolygonRequest from "./code-samples/eth_getBlockReceipts/polygon-request.ts"; +import ethGetBlockReceiptsPolygonResponse from "./code-samples/eth_getBlockReceipts/polygon-response.ts"; +import ethGetLogsArbitrumRequest from "./code-samples/eth_getLogs/arbitrum-request.ts"; +import ethGetLogsArbitrumResponse from "./code-samples/eth_getLogs/arbitrum-response.ts"; +import ethGetLogsBaseRequest from "./code-samples/eth_getLogs/base-request.ts"; +import ethGetLogsBaseResponse from "./code-samples/eth_getLogs/base-response.ts"; +import ethGetLogsEthereumRequest from "./code-samples/eth_getLogs/ethereum-request.ts"; +import ethGetLogsEthereumResponse from "./code-samples/eth_getLogs/ethereum-response.ts"; +import ethGetLogsOptimismRequest from "./code-samples/eth_getLogs/optimism-request.ts"; +import ethGetLogsOptimismResponse from "./code-samples/eth_getLogs/optimism-response.ts"; +import ethGetLogsPolygonRequest from "./code-samples/eth_getLogs/polygon-request.ts"; +import ethGetLogsPolygonResponse from "./code-samples/eth_getLogs/polygon-response.ts"; +import ethGetTransactionByHashArbitrumRequest from "./code-samples/eth_getTransactionByHash/arbitrum-request.ts"; +import ethGetTransactionByHashArbitrumResponse from "./code-samples/eth_getTransactionByHash/arbitrum-response.ts"; +import ethGetTransactionByHashBaseRequest from "./code-samples/eth_getTransactionByHash/base-request.ts"; +import ethGetTransactionByHashBaseResponse from "./code-samples/eth_getTransactionByHash/base-response.ts"; +import ethGetTransactionByHashEthereumRequest from "./code-samples/eth_getTransactionByHash/ethereum-request.ts"; +import ethGetTransactionByHashEthereumResponse from "./code-samples/eth_getTransactionByHash/ethereum-response.ts"; +import ethGetTransactionByHashOptimismRequest from "./code-samples/eth_getTransactionByHash/optimism-request.ts"; +import ethGetTransactionByHashOptimismResponse from "./code-samples/eth_getTransactionByHash/optimism-response.ts"; +import ethGetTransactionByHashPolygonRequest from "./code-samples/eth_getTransactionByHash/polygon-request.ts"; +import ethGetTransactionByHashPolygonResponse from "./code-samples/eth_getTransactionByHash/polygon-response.ts"; +import ethGetTransactionCountArbitrumRequest from "./code-samples/eth_getTransactionCount/arbitrum-request.ts"; +import ethGetTransactionCountArbitrumResponse from "./code-samples/eth_getTransactionCount/arbitrum-response.ts"; +import ethGetTransactionCountBaseRequest from "./code-samples/eth_getTransactionCount/base-request.ts"; +import ethGetTransactionCountBaseResponse from "./code-samples/eth_getTransactionCount/base-response.ts"; +import ethGetTransactionCountEthereumRequest from "./code-samples/eth_getTransactionCount/ethereum-request.ts"; +import ethGetTransactionCountEthereumResponse from "./code-samples/eth_getTransactionCount/ethereum-response.ts"; +import ethGetTransactionCountOptimismRequest from "./code-samples/eth_getTransactionCount/optimism-request.ts"; +import ethGetTransactionCountOptimismResponse from "./code-samples/eth_getTransactionCount/optimism-response.ts"; +import ethGetTransactionCountPolygonRequest from "./code-samples/eth_getTransactionCount/polygon-request.ts"; +import ethGetTransactionCountPolygonResponse from "./code-samples/eth_getTransactionCount/polygon-response.ts"; +import ethGetTransactionReceiptArbitrumRequest from "./code-samples/eth_getTransactionReceipt/arbitrum-request.ts"; +import ethGetTransactionReceiptArbitrumResponse from "./code-samples/eth_getTransactionReceipt/arbitrum-response.ts"; +import ethGetTransactionReceiptBaseRequest from "./code-samples/eth_getTransactionReceipt/base-request.ts"; +import ethGetTransactionReceiptBaseResponse from "./code-samples/eth_getTransactionReceipt/base-response.ts"; +import ethGetTransactionReceiptEthereumRequest from "./code-samples/eth_getTransactionReceipt/ethereum-request.ts"; +import ethGetTransactionReceiptEthereumResponse from "./code-samples/eth_getTransactionReceipt/ethereum-response.ts"; +import ethGetTransactionReceiptOptimismRequest from "./code-samples/eth_getTransactionReceipt/optimism-request.ts"; +import ethGetTransactionReceiptOptimismResponse from "./code-samples/eth_getTransactionReceipt/optimism-response.ts"; +import ethGetTransactionReceiptPolygonRequest from "./code-samples/eth_getTransactionReceipt/polygon-request.ts"; +import ethGetTransactionReceiptPolygonResponse from "./code-samples/eth_getTransactionReceipt/polygon-response.ts"; +import getAccountInfoSolanaRequest from "./code-samples/getAccountInfo/solana-request.ts"; +import getAccountInfoSolanaResponse from "./code-samples/getAccountInfo/solana-response.ts"; +import getBalanceSolanaRequest from "./code-samples/getBalance/solana-request.ts"; +import getBalanceSolanaResponse from "./code-samples/getBalance/solana-response.ts"; +import getLatestBlockhashSolanaRequest from "./code-samples/getLatestBlockhash/solana-request.ts"; +import getLatestBlockhashSolanaResponse from "./code-samples/getLatestBlockhash/solana-response.ts"; +import getSignaturesForAddressSolanaRequest from "./code-samples/getSignaturesForAddress/solana-request.ts"; +import getSignaturesForAddressSolanaResponse from "./code-samples/getSignaturesForAddress/solana-response.ts"; +import getTokenAccountBalanceSolanaRequest from "./code-samples/getTokenAccountBalance/solana-request.ts"; +import getTokenAccountBalanceSolanaResponse from "./code-samples/getTokenAccountBalance/solana-response.ts"; +import getTokenAccountsByOwnerSolanaRequest from "./code-samples/getTokenAccountsByOwner/solana-request.ts"; +import getTokenAccountsByOwnerSolanaResponse from "./code-samples/getTokenAccountsByOwner/solana-response.ts"; +import getTransactionSolanaRequest from "./code-samples/getTransaction/solana-request.ts"; +import getTransactionSolanaResponse from "./code-samples/getTransaction/solana-response.ts"; +import type { Option } from "./CodeblockSelect.ts"; export const CODE_SAMPLES = { arbitrum: { diff --git a/fern/components/CodeConsole/index.tsx b/fern/components/CodeConsole/index.tsx index 7ee402f5a..69b169bff 100644 --- a/fern/components/CodeConsole/index.tsx +++ b/fern/components/CodeConsole/index.tsx @@ -1,4 +1,4 @@ -import { CodeblockSelect } from "./CodeblockSelect.js"; +import { CodeblockSelect } from "./CodeblockSelect.tsx"; import { CHAIN_OPTIONS, CODE_SAMPLES, @@ -6,9 +6,9 @@ import { type Method, getDefaultMethodForChain, getMethodOptionsForChain, -} from "./codeData.js"; -import { highlightCode } from "./highlightCode.js"; -import useTheme from "./useTheme.js"; +} from "./codeData.ts"; +import { highlightCode } from "./highlightCode.ts"; +import useTheme from "./useTheme.tsx"; export const CodeConsole = () => { const { isDark } = useTheme(); diff --git a/fern/components/Footer.tsx b/fern/components/Footer.tsx index 0cfd827f3..2e6cdc68b 100644 --- a/fern/components/Footer.tsx +++ b/fern/components/Footer.tsx @@ -1,13 +1,13 @@ -import BuiltByFern from "./BuiltWithFern.js"; -import AlchemyLogo from "./icons/AlchemyLogo.js"; -import AlchemyUniversityIcon from "./icons/AlchemyUniversityIcon.js"; -import DiscordIcon from "./icons/DiscordIcon.js"; -import EmailIcon from "./icons/EmailIcon.js"; -import NewsletterIcon from "./icons/NewsletterIcon.js"; -import RobotIcon from "./icons/RobotIcon.js"; -import StatusIcon from "./icons/StatusIcon.js"; -import SupportHubIcon from "./icons/SupportHubIcon.js"; -import XIcon from "./icons/XIcon.js"; +import BuiltByFern from "./BuiltWithFern.tsx"; +import AlchemyLogo from "./icons/AlchemyLogo.tsx"; +import AlchemyUniversityIcon from "./icons/AlchemyUniversityIcon.tsx"; +import DiscordIcon from "./icons/DiscordIcon.tsx"; +import EmailIcon from "./icons/EmailIcon.tsx"; +import NewsletterIcon from "./icons/NewsletterIcon.tsx"; +import RobotIcon from "./icons/RobotIcon.tsx"; +import StatusIcon from "./icons/StatusIcon.tsx"; +import SupportHubIcon from "./icons/SupportHubIcon.tsx"; +import XIcon from "./icons/XIcon.tsx"; /** * CONFIG diff --git a/tsconfig.json b/tsconfig.json index 14d21c6c1..1718bfccf 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -9,6 +9,8 @@ "strict": true, "skipLibCheck": true, "baseUrl": ".", + "allowImportingTsExtensions": true, + "noEmit": true, "paths": { "@/*": ["src/*"] } From fc4e01726635f04af7ef4998e1372d7bb7a4fe1a Mon Sep 17 00:00:00 2001 From: dslovinsky Date: Tue, 13 Jan 2026 00:10:28 -0800 Subject: [PATCH 27/27] chore: use .ts imports rather than .js where possible --- fern/components/CodeConsole/codeData.ts | 2 +- scripts/generate-rpc.ts | 2 +- scripts/validate-rpc.ts | 4 ++-- src/content-indexer/__tests__/index.test.ts | 16 +++++++-------- .../collectors/__tests__/algolia.test.ts | 2 +- .../__tests__/navigation-trees.test.ts | 2 +- .../collectors/__tests__/path-index.test.ts | 2 +- .../__tests__/processing-context.test.ts | 4 ++-- src/content-indexer/collectors/algolia.ts | 4 ++-- .../collectors/processing-context.ts | 8 ++++---- src/content-indexer/constants/metadata.ts | 2 +- .../core/__tests__/batch-fetcher.test.ts | 8 ++++---- .../core/__tests__/build-all-outputs.test.ts | 10 +++++----- .../core/__tests__/content-cache.test.ts | 2 +- .../core/__tests__/path-builder.test.ts | 2 +- .../core/__tests__/scanner.test.ts | 4 ++-- src/content-indexer/core/batch-fetcher.ts | 8 ++++---- src/content-indexer/core/build-all-outputs.ts | 10 +++++----- src/content-indexer/index.ts | 12 +++++------ src/content-indexer/indexers/changelog.ts | 10 +++++----- src/content-indexer/indexers/main.ts | 10 +++++----- src/content-indexer/types/indexer.ts | 6 +++--- .../uploaders/__tests__/algolia.test.ts | 4 ++-- .../uploaders/__tests__/redis.test.ts | 6 +++--- src/content-indexer/uploaders/algolia.ts | 4 ++-- src/content-indexer/uploaders/redis.ts | 6 +++--- .../__tests__/navigation-helpers.test.ts | 2 +- .../utils/__tests__/normalization.test.ts | 2 +- .../utils/__tests__/openapi.test.ts | 2 +- .../utils/__tests__/openrpc.test.ts | 4 ++-- .../utils/__tests__/truncate-record.test.ts | 4 ++-- src/content-indexer/utils/apiSpecs.ts | 2 +- src/content-indexer/utils/filesystem.ts | 2 +- .../utils/navigation-helpers.ts | 2 +- src/content-indexer/utils/normalization.ts | 2 +- src/content-indexer/utils/openapi.ts | 4 ++-- src/content-indexer/utils/openrpc.ts | 2 +- src/content-indexer/utils/test-factories.ts | 2 +- src/content-indexer/utils/truncate-record.ts | 2 +- .../visitors/__tests__/index.test.ts | 12 +++++------ .../__tests__/visit-api-reference.test.ts | 10 +++++----- .../visitors/__tests__/visit-link.test.ts | 10 +++++----- .../visitors/__tests__/visit-page.test.ts | 10 +++++----- .../visitors/__tests__/visit-section.test.ts | 12 +++++------ src/content-indexer/visitors/index.ts | 20 +++++++++---------- .../__tests__/process-openapi.test.ts | 12 +++++------ .../__tests__/process-openrpc.test.ts | 14 ++++++------- .../visitors/processors/process-openapi.ts | 12 +++++------ .../visitors/processors/process-openrpc.ts | 12 +++++------ .../visitors/visit-api-reference.ts | 8 ++++---- src/content-indexer/visitors/visit-link.ts | 4 ++-- src/content-indexer/visitors/visit-page.ts | 6 +++--- src/content-indexer/visitors/visit-section.ts | 6 +++--- src/utils/generateRpcSpecs.ts | 6 +++--- src/utils/generationHelpers.ts | 2 +- src/utils/validateRpcSpec.ts | 2 +- 56 files changed, 170 insertions(+), 170 deletions(-) diff --git a/fern/components/CodeConsole/codeData.ts b/fern/components/CodeConsole/codeData.ts index 48084ba27..99d3676a7 100644 --- a/fern/components/CodeConsole/codeData.ts +++ b/fern/components/CodeConsole/codeData.ts @@ -126,7 +126,7 @@ import getTokenAccountsByOwnerSolanaRequest from "./code-samples/getTokenAccount import getTokenAccountsByOwnerSolanaResponse from "./code-samples/getTokenAccountsByOwner/solana-response.ts"; import getTransactionSolanaRequest from "./code-samples/getTransaction/solana-request.ts"; import getTransactionSolanaResponse from "./code-samples/getTransaction/solana-response.ts"; -import type { Option } from "./CodeblockSelect.ts"; +import type { Option } from "./CodeblockSelect.tsx"; export const CODE_SAMPLES = { arbitrum: { diff --git a/scripts/generate-rpc.ts b/scripts/generate-rpc.ts index d1d6c639a..091645e6f 100644 --- a/scripts/generate-rpc.ts +++ b/scripts/generate-rpc.ts @@ -1,6 +1,6 @@ import { mkdirSync, readdirSync } from "fs"; -import { generateOpenRpcSpec } from "../src/utils/generateRpcSpecs.js"; +import { generateOpenRpcSpec } from "../src/utils/generateRpcSpecs.ts"; import { type DerefErrorGroup, handleDerefErrors, diff --git a/scripts/validate-rpc.ts b/scripts/validate-rpc.ts index afb43a95b..a73a25e3c 100644 --- a/scripts/validate-rpc.ts +++ b/scripts/validate-rpc.ts @@ -1,7 +1,7 @@ import { existsSync, readFileSync } from "fs"; -import { findFilesOfType } from "../src/utils/findFilesOfType.js"; -import { validateRpcSpec } from "../src/utils/validateRpcSpec.js"; +import { findFilesOfType } from "../src/utils/findFilesOfType.ts"; +import { validateRpcSpec } from "../src/utils/validateRpcSpec.ts"; const validateOpenRpcSpecs = async (directory: string) => { if (!directory) { diff --git a/src/content-indexer/__tests__/index.test.ts b/src/content-indexer/__tests__/index.test.ts index 1b661742a..f89cbdadd 100644 --- a/src/content-indexer/__tests__/index.test.ts +++ b/src/content-indexer/__tests__/index.test.ts @@ -1,13 +1,13 @@ import { afterEach, beforeEach, describe, expect, test, vi } from "vitest"; -import { batchFetchContent } from "@/content-indexer/core/batch-fetcher.js"; -import { buildAllOutputs } from "@/content-indexer/core/build-all-outputs.js"; -import { ContentCache } from "@/content-indexer/core/content-cache.js"; -import { scanDocsYml } from "@/content-indexer/core/scanner.js"; -import { buildDocsContentIndex } from "@/content-indexer/indexers/main.js"; -import { readLocalDocsYml } from "@/content-indexer/utils/filesystem.js"; -import { fetchFileFromGitHub } from "@/content-indexer/utils/github.js"; -import { repoConfigFactory } from "@/content-indexer/utils/test-factories.js"; +import { batchFetchContent } from "@/content-indexer/core/batch-fetcher.ts"; +import { buildAllOutputs } from "@/content-indexer/core/build-all-outputs.ts"; +import { ContentCache } from "@/content-indexer/core/content-cache.ts"; +import { scanDocsYml } from "@/content-indexer/core/scanner.ts"; +import { buildDocsContentIndex } from "@/content-indexer/indexers/main.ts"; +import { readLocalDocsYml } from "@/content-indexer/utils/filesystem.ts"; +import { fetchFileFromGitHub } from "@/content-indexer/utils/github.ts"; +import { repoConfigFactory } from "@/content-indexer/utils/test-factories.ts"; // Mock dependencies vi.mock("@/content-indexer/utils/github", async () => { diff --git a/src/content-indexer/collectors/__tests__/algolia.test.ts b/src/content-indexer/collectors/__tests__/algolia.test.ts index 46bed6cf7..3a4de838d 100644 --- a/src/content-indexer/collectors/__tests__/algolia.test.ts +++ b/src/content-indexer/collectors/__tests__/algolia.test.ts @@ -1,6 +1,6 @@ import { describe, expect, test } from "vitest"; -import { AlgoliaCollector } from "../algolia.js"; +import { AlgoliaCollector } from "../algolia.ts"; describe("AlgoliaCollector", () => { test("should initialize with empty records", () => { diff --git a/src/content-indexer/collectors/__tests__/navigation-trees.test.ts b/src/content-indexer/collectors/__tests__/navigation-trees.test.ts index 6a212cc58..6871f1ee4 100644 --- a/src/content-indexer/collectors/__tests__/navigation-trees.test.ts +++ b/src/content-indexer/collectors/__tests__/navigation-trees.test.ts @@ -1,6 +1,6 @@ import { describe, expect, test } from "vitest"; -import { NavigationTreesCollector } from "../navigation-trees.js"; +import { NavigationTreesCollector } from "../navigation-trees.ts"; describe("NavigationTreesCollector", () => { test("should initialize with empty trees", () => { diff --git a/src/content-indexer/collectors/__tests__/path-index.test.ts b/src/content-indexer/collectors/__tests__/path-index.test.ts index 5c5a6ad77..1caca3f04 100644 --- a/src/content-indexer/collectors/__tests__/path-index.test.ts +++ b/src/content-indexer/collectors/__tests__/path-index.test.ts @@ -1,6 +1,6 @@ import { describe, expect, test } from "vitest"; -import { PathIndexCollector } from "../path-index.js"; +import { PathIndexCollector } from "../path-index.ts"; describe("PathIndexCollector", () => { test("should initialize with empty index", () => { diff --git a/src/content-indexer/collectors/__tests__/processing-context.test.ts b/src/content-indexer/collectors/__tests__/processing-context.test.ts index 050bf28b9..994548db7 100644 --- a/src/content-indexer/collectors/__tests__/processing-context.test.ts +++ b/src/content-indexer/collectors/__tests__/processing-context.test.ts @@ -1,8 +1,8 @@ import { describe, expect, test } from "vitest"; -import type { NavItem } from "@/content-indexer/types/navigation.js"; +import type { NavItem } from "@/content-indexer/types/navigation.ts"; -import { ProcessingContext } from "../processing-context.js"; +import { ProcessingContext } from "../processing-context.ts"; describe("ProcessingContext", () => { test("should initialize with empty state", () => { diff --git a/src/content-indexer/collectors/algolia.ts b/src/content-indexer/collectors/algolia.ts index e67195d6a..7958382ca 100644 --- a/src/content-indexer/collectors/algolia.ts +++ b/src/content-indexer/collectors/algolia.ts @@ -1,7 +1,7 @@ import { createHash } from "crypto"; -import type { AlgoliaRecord } from "@/content-indexer/types/algolia.js"; -import type { NavItem } from "@/content-indexer/types/navigation.js"; +import type { AlgoliaRecord } from "@/content-indexer/types/algolia.ts"; +import type { NavItem } from "@/content-indexer/types/navigation.ts"; /** * Extracts breadcrumb titles from NavItems for Algolia. diff --git a/src/content-indexer/collectors/processing-context.ts b/src/content-indexer/collectors/processing-context.ts index 9392b2b88..70f630ee9 100644 --- a/src/content-indexer/collectors/processing-context.ts +++ b/src/content-indexer/collectors/processing-context.ts @@ -1,4 +1,4 @@ -import type { AlgoliaRecord } from "@/content-indexer/types/algolia.js"; +import type { AlgoliaRecord } from "@/content-indexer/types/algolia.ts"; import type { NavItem, NavigationTreesByTab, @@ -8,9 +8,9 @@ import type { PathIndexEntry, } from "@/content-indexer/types/pathIndex.js"; -import { AlgoliaCollector, type AddRecordParams } from "./algolia.js"; -import { NavigationTreesCollector } from "./navigation-trees.js"; -import { PathIndexCollector } from "./path-index.js"; +import { AlgoliaCollector, type AddRecordParams } from "./algolia.ts"; +import { NavigationTreesCollector } from "./navigation-trees.ts"; +import { PathIndexCollector } from "./path-index.ts"; /** * Result of building all outputs from content processing. diff --git a/src/content-indexer/constants/metadata.ts b/src/content-indexer/constants/metadata.ts index 2889d80bb..040916f2b 100644 --- a/src/content-indexer/constants/metadata.ts +++ b/src/content-indexer/constants/metadata.ts @@ -1,4 +1,4 @@ -import { DOCS_BASE_URL } from "./links.js"; +import { DOCS_BASE_URL } from "./links.ts"; // TODO: This file is copied from docs-site but not used in the content indexer // It's kept for potential future use but Next.js types are stubbed diff --git a/src/content-indexer/core/__tests__/batch-fetcher.test.ts b/src/content-indexer/core/__tests__/batch-fetcher.test.ts index 3a8f4f95a..2446a1207 100644 --- a/src/content-indexer/core/__tests__/batch-fetcher.test.ts +++ b/src/content-indexer/core/__tests__/batch-fetcher.test.ts @@ -1,14 +1,14 @@ import { afterEach, beforeEach, describe, expect, test, vi } from "vitest"; -import type { OpenApiSpec } from "@/content-indexer/types/specs.js"; -import { fetchApiSpec } from "@/content-indexer/utils/apiSpecs.js"; -import { fetchFileFromGitHub } from "@/content-indexer/utils/github.js"; +import type { OpenApiSpec } from "@/content-indexer/types/specs.ts"; +import { fetchApiSpec } from "@/content-indexer/utils/apiSpecs.ts"; +import { fetchFileFromGitHub } from "@/content-indexer/utils/github.ts"; import { openApiSpecFactory, repoConfigFactory, } from "@/content-indexer/utils/test-factories.js"; -import { batchFetchContent } from "../batch-fetcher.js"; +import { batchFetchContent } from "../batch-fetcher.ts"; // Mock dependencies vi.mock("@/content-indexer/utils/github", async () => { diff --git a/src/content-indexer/core/__tests__/build-all-outputs.test.ts b/src/content-indexer/core/__tests__/build-all-outputs.test.ts index 6f8f4441b..0e5d355ce 100644 --- a/src/content-indexer/core/__tests__/build-all-outputs.test.ts +++ b/src/content-indexer/core/__tests__/build-all-outputs.test.ts @@ -1,11 +1,11 @@ import { beforeEach, describe, expect, test, vi } from "vitest"; -import type { DocsYml } from "@/content-indexer/types/docsYaml.js"; -import { repoConfigFactory } from "@/content-indexer/utils/test-factories.js"; -import { visitNavigationItem } from "@/content-indexer/visitors/index.js"; +import type { DocsYml } from "@/content-indexer/types/docsYaml.ts"; +import { repoConfigFactory } from "@/content-indexer/utils/test-factories.ts"; +import { visitNavigationItem } from "@/content-indexer/visitors/index.ts"; -import { buildAllOutputs } from "../build-all-outputs.js"; -import { ContentCache } from "../content-cache.js"; +import { buildAllOutputs } from "../build-all-outputs.ts"; +import { ContentCache } from "../content-cache.ts"; // Mock the visitor vi.mock("@/content-indexer/visitors", () => ({ diff --git a/src/content-indexer/core/__tests__/content-cache.test.ts b/src/content-indexer/core/__tests__/content-cache.test.ts index c563786ee..8f14a039f 100644 --- a/src/content-indexer/core/__tests__/content-cache.test.ts +++ b/src/content-indexer/core/__tests__/content-cache.test.ts @@ -5,7 +5,7 @@ import { openRpcSpecFactory, } from "@/content-indexer/utils/test-factories.js"; -import { ContentCache } from "../content-cache.js"; +import { ContentCache } from "../content-cache.ts"; describe("ContentCache", () => { test("should initialize with empty caches", () => { diff --git a/src/content-indexer/core/__tests__/path-builder.test.ts b/src/content-indexer/core/__tests__/path-builder.test.ts index 84198fbf9..757a59266 100644 --- a/src/content-indexer/core/__tests__/path-builder.test.ts +++ b/src/content-indexer/core/__tests__/path-builder.test.ts @@ -1,6 +1,6 @@ import { describe, expect, test } from "vitest"; -import { PathBuilder } from "../path-builder.js"; +import { PathBuilder } from "../path-builder.ts"; describe("PathBuilder", () => { test("should initialize with empty parts", () => { diff --git a/src/content-indexer/core/__tests__/scanner.test.ts b/src/content-indexer/core/__tests__/scanner.test.ts index 848386696..ab3ee8c02 100644 --- a/src/content-indexer/core/__tests__/scanner.test.ts +++ b/src/content-indexer/core/__tests__/scanner.test.ts @@ -1,8 +1,8 @@ import { describe, expect, test } from "vitest"; -import type { DocsYml } from "@/content-indexer/types/docsYaml.js"; +import type { DocsYml } from "@/content-indexer/types/docsYaml.ts"; -import { scanDocsYml } from "../scanner.js"; +import { scanDocsYml } from "../scanner.ts"; describe("scanner", () => { test("should throw error if navigation is missing", () => { diff --git a/src/content-indexer/core/batch-fetcher.ts b/src/content-indexer/core/batch-fetcher.ts index d19891f30..c6e3ae4bb 100644 --- a/src/content-indexer/core/batch-fetcher.ts +++ b/src/content-indexer/core/batch-fetcher.ts @@ -1,15 +1,15 @@ import matter from "gray-matter"; import path from "path"; -import { fetchApiSpec } from "@/content-indexer/utils/apiSpecs.js"; -import { readLocalMdxFile } from "@/content-indexer/utils/filesystem.js"; +import { fetchApiSpec } from "@/content-indexer/utils/apiSpecs.ts"; +import { readLocalMdxFile } from "@/content-indexer/utils/filesystem.ts"; import { fetchFileFromGitHub, type RepoConfig, } from "@/content-indexer/utils/github.js"; -import { ContentCache } from "./content-cache.js"; -import type { ScanResult } from "./scanner.js"; +import { ContentCache } from "./content-cache.ts"; +import type { ScanResult } from "./scanner.ts"; /** * Content source configuration - either filesystem or GitHub API diff --git a/src/content-indexer/core/build-all-outputs.ts b/src/content-indexer/core/build-all-outputs.ts index fa384d3e2..2b0811226 100644 --- a/src/content-indexer/core/build-all-outputs.ts +++ b/src/content-indexer/core/build-all-outputs.ts @@ -4,12 +4,12 @@ import { ProcessingContext, type BuildAllOutputsResult, } from "@/content-indexer/collectors/processing-context.js"; -import type { ContentCache } from "@/content-indexer/core/content-cache.js"; -import type { DocsYml } from "@/content-indexer/types/docsYaml.js"; -import { DOCS_REPO, type RepoConfig } from "@/content-indexer/utils/github.js"; -import { visitNavigationItem } from "@/content-indexer/visitors/index.js"; +import type { ContentCache } from "@/content-indexer/core/content-cache.ts"; +import type { DocsYml } from "@/content-indexer/types/docsYaml.ts"; +import { DOCS_REPO, type RepoConfig } from "@/content-indexer/utils/github.ts"; +import { visitNavigationItem } from "@/content-indexer/visitors/index.ts"; -import { PathBuilder } from "./path-builder.js"; +import { PathBuilder } from "./path-builder.ts"; /** * Phase 3 of the content indexing pipeline. diff --git a/src/content-indexer/index.ts b/src/content-indexer/index.ts index 04f809f0c..c308d5f61 100644 --- a/src/content-indexer/index.ts +++ b/src/content-indexer/index.ts @@ -1,12 +1,12 @@ #!/usr/bin/env tsx import path from "path"; -import { buildChangelogIndex } from "@/content-indexer/indexers/changelog.js"; -import { buildDocsContentIndex } from "@/content-indexer/indexers/main.js"; -import type { IndexerResult } from "@/content-indexer/types/indexer.js"; -import { uploadToAlgolia } from "@/content-indexer/uploaders/algolia.js"; -import { storeToRedis } from "@/content-indexer/uploaders/redis.js"; -import { DOCS_REPO, WALLET_REPO } from "@/content-indexer/utils/github.js"; +import { buildChangelogIndex } from "@/content-indexer/indexers/changelog.ts"; +import { buildDocsContentIndex } from "@/content-indexer/indexers/main.ts"; +import type { IndexerResult } from "@/content-indexer/types/indexer.ts"; +import { uploadToAlgolia } from "@/content-indexer/uploaders/algolia.ts"; +import { storeToRedis } from "@/content-indexer/uploaders/redis.ts"; +import { DOCS_REPO, WALLET_REPO } from "@/content-indexer/utils/github.ts"; // ============================================================================ // CLI Argument Parsing diff --git a/src/content-indexer/indexers/changelog.ts b/src/content-indexer/indexers/changelog.ts index 3913b31d5..b4e173786 100644 --- a/src/content-indexer/indexers/changelog.ts +++ b/src/content-indexer/indexers/changelog.ts @@ -1,11 +1,11 @@ import { promises as fs } from "fs"; import path from "path"; -import type { AlgoliaRecord } from "@/content-indexer/types/algolia.js"; -import type { IndexerResult } from "@/content-indexer/types/indexer.js"; -import type { PathIndex } from "@/content-indexer/types/pathIndex.js"; -import { readLocalFile } from "@/content-indexer/utils/filesystem.js"; -import { truncateRecord } from "@/content-indexer/utils/truncate-record.js"; +import type { AlgoliaRecord } from "@/content-indexer/types/algolia.ts"; +import type { IndexerResult } from "@/content-indexer/types/indexer.ts"; +import type { PathIndex } from "@/content-indexer/types/pathIndex.ts"; +import { readLocalFile } from "@/content-indexer/utils/filesystem.ts"; +import { truncateRecord } from "@/content-indexer/utils/truncate-record.ts"; export interface ChangelogIndexerConfig { localBasePath: string; // Path to fern/changelog/ directory diff --git a/src/content-indexer/indexers/main.ts b/src/content-indexer/indexers/main.ts index 40f4cde1d..d26921aaf 100644 --- a/src/content-indexer/indexers/main.ts +++ b/src/content-indexer/indexers/main.ts @@ -4,11 +4,11 @@ import { batchFetchContent, type ContentSource, } from "@/content-indexer/core/batch-fetcher.js"; -import { buildAllOutputs } from "@/content-indexer/core/build-all-outputs.js"; -import { scanDocsYml } from "@/content-indexer/core/scanner.js"; -import type { DocsYml } from "@/content-indexer/types/docsYaml.js"; -import type { IndexerResult } from "@/content-indexer/types/indexer.js"; -import { readLocalDocsYml } from "@/content-indexer/utils/filesystem.js"; +import { buildAllOutputs } from "@/content-indexer/core/build-all-outputs.ts"; +import { scanDocsYml } from "@/content-indexer/core/scanner.ts"; +import type { DocsYml } from "@/content-indexer/types/docsYaml.ts"; +import type { IndexerResult } from "@/content-indexer/types/indexer.ts"; +import { readLocalDocsYml } from "@/content-indexer/utils/filesystem.ts"; import { fetchFileFromGitHub, type RepoConfig, diff --git a/src/content-indexer/types/indexer.ts b/src/content-indexer/types/indexer.ts index bfa27ca49..a7d80ed45 100644 --- a/src/content-indexer/types/indexer.ts +++ b/src/content-indexer/types/indexer.ts @@ -1,6 +1,6 @@ -import type { AlgoliaRecord } from "@/content-indexer/types/algolia.js"; -import type { NavigationTreesByTab } from "@/content-indexer/types/navigation.js"; -import type { PathIndex } from "@/content-indexer/types/pathIndex.js"; +import type { AlgoliaRecord } from "@/content-indexer/types/algolia.ts"; +import type { NavigationTreesByTab } from "@/content-indexer/types/navigation.ts"; +import type { PathIndex } from "@/content-indexer/types/pathIndex.ts"; /** * Standard result structure returned by all indexers. diff --git a/src/content-indexer/uploaders/__tests__/algolia.test.ts b/src/content-indexer/uploaders/__tests__/algolia.test.ts index a67d78bfb..9d9f45ce4 100644 --- a/src/content-indexer/uploaders/__tests__/algolia.test.ts +++ b/src/content-indexer/uploaders/__tests__/algolia.test.ts @@ -1,8 +1,8 @@ import { describe, expect, test, vi } from "vitest"; -import type { AlgoliaRecord } from "@/content-indexer/types/algolia.js"; +import type { AlgoliaRecord } from "@/content-indexer/types/algolia.ts"; -import { uploadToAlgolia } from "../algolia.js"; +import { uploadToAlgolia } from "../algolia.ts"; describe("uploadToAlgolia", () => { test("should skip if no ALGOLIA_APP_ID", async () => { diff --git a/src/content-indexer/uploaders/__tests__/redis.test.ts b/src/content-indexer/uploaders/__tests__/redis.test.ts index cbd11a3fb..bdc745a0e 100644 --- a/src/content-indexer/uploaders/__tests__/redis.test.ts +++ b/src/content-indexer/uploaders/__tests__/redis.test.ts @@ -1,9 +1,9 @@ import { beforeEach, describe, expect, test, vi } from "vitest"; -import type { NavigationTreesByTab } from "@/content-indexer/types/navigation.js"; -import type { PathIndex } from "@/content-indexer/types/pathIndex.js"; +import type { NavigationTreesByTab } from "@/content-indexer/types/navigation.ts"; +import type { PathIndex } from "@/content-indexer/types/pathIndex.ts"; -import { storeToRedis } from "../redis.js"; +import { storeToRedis } from "../redis.ts"; // Mock Redis const mockSet = vi.fn().mockResolvedValue("OK"); diff --git a/src/content-indexer/uploaders/algolia.ts b/src/content-indexer/uploaders/algolia.ts index 9154538cd..91c47ade4 100644 --- a/src/content-indexer/uploaders/algolia.ts +++ b/src/content-indexer/uploaders/algolia.ts @@ -1,7 +1,7 @@ import { algoliasearch } from "algoliasearch"; -import type { AlgoliaRecord } from "@/content-indexer/types/algolia.js"; -import { truncateRecord } from "@/content-indexer/utils/truncate-record.js"; +import type { AlgoliaRecord } from "@/content-indexer/types/algolia.ts"; +import { truncateRecord } from "@/content-indexer/utils/truncate-record.ts"; const ALGOLIA_INDEX_NAME_BASE = "alchemy_docs"; diff --git a/src/content-indexer/uploaders/redis.ts b/src/content-indexer/uploaders/redis.ts index 2ba69e979..88c8584b9 100644 --- a/src/content-indexer/uploaders/redis.ts +++ b/src/content-indexer/uploaders/redis.ts @@ -4,9 +4,9 @@ import type { NavigationTree, NavigationTreesByTab, } from "@/content-indexer/types/navigation.js"; -import type { PathIndex } from "@/content-indexer/types/pathIndex.js"; -import { mergeWalletsNavTree } from "@/content-indexer/utils/nav-tree-merge.js"; -import { getRedis } from "@/content-indexer/utils/redis.js"; +import type { PathIndex } from "@/content-indexer/types/pathIndex.ts"; +import { mergeWalletsNavTree } from "@/content-indexer/utils/nav-tree-merge.ts"; +import { getRedis } from "@/content-indexer/utils/redis.ts"; const stringify = (data: unknown) => JSON.stringify(data, null, 2); diff --git a/src/content-indexer/utils/__tests__/navigation-helpers.test.ts b/src/content-indexer/utils/__tests__/navigation-helpers.test.ts index 5ca6b378f..04f58f3b5 100644 --- a/src/content-indexer/utils/__tests__/navigation-helpers.test.ts +++ b/src/content-indexer/utils/__tests__/navigation-helpers.test.ts @@ -1,6 +1,6 @@ import { describe, expect, test } from "vitest"; -import { createBreadcrumbNavItem } from "../navigation-helpers.js"; +import { createBreadcrumbNavItem } from "../navigation-helpers.ts"; describe("navigation-helpers", () => { describe("createBreadcrumbNavItem", () => { diff --git a/src/content-indexer/utils/__tests__/normalization.test.ts b/src/content-indexer/utils/__tests__/normalization.test.ts index b9f04000f..24f0c516d 100644 --- a/src/content-indexer/utils/__tests__/normalization.test.ts +++ b/src/content-indexer/utils/__tests__/normalization.test.ts @@ -4,7 +4,7 @@ import { normalizeFilePath, normalizeSlug, } from "@/content-indexer/utils/normalization.js"; -import { repoConfigFactory } from "@/content-indexer/utils/test-factories.js"; +import { repoConfigFactory } from "@/content-indexer/utils/test-factories.ts"; describe("normalization utils", () => { describe("normalizeSlug", () => { diff --git a/src/content-indexer/utils/__tests__/openapi.test.ts b/src/content-indexer/utils/__tests__/openapi.test.ts index 0278bcfa7..372cdcfac 100644 --- a/src/content-indexer/utils/__tests__/openapi.test.ts +++ b/src/content-indexer/utils/__tests__/openapi.test.ts @@ -1,6 +1,6 @@ import { describe, expect, test } from "vitest"; -import { PathBuilder } from "@/content-indexer/core/path-builder.js"; +import { PathBuilder } from "@/content-indexer/core/path-builder.ts"; import { buildOperationPath, diff --git a/src/content-indexer/utils/__tests__/openrpc.test.ts b/src/content-indexer/utils/__tests__/openrpc.test.ts index 56003bc48..51bcea332 100644 --- a/src/content-indexer/utils/__tests__/openrpc.test.ts +++ b/src/content-indexer/utils/__tests__/openrpc.test.ts @@ -1,8 +1,8 @@ import { describe, expect, test } from "vitest"; -import type { OpenRpcSpec } from "@/content-indexer/types/specs.js"; +import type { OpenRpcSpec } from "@/content-indexer/types/specs.ts"; -import { isValidOpenRpcSpec } from "../openrpc.js"; +import { isValidOpenRpcSpec } from "../openrpc.ts"; describe("openrpc utils", () => { describe("isValidOpenRpcSpec", () => { diff --git a/src/content-indexer/utils/__tests__/truncate-record.test.ts b/src/content-indexer/utils/__tests__/truncate-record.test.ts index 031bd36b8..d21d243c3 100644 --- a/src/content-indexer/utils/__tests__/truncate-record.test.ts +++ b/src/content-indexer/utils/__tests__/truncate-record.test.ts @@ -1,8 +1,8 @@ import { describe, expect, test, vi } from "vitest"; -import type { AlgoliaRecord } from "@/content-indexer/types/algolia.js"; +import type { AlgoliaRecord } from "@/content-indexer/types/algolia.ts"; -import { truncateRecord } from "../truncate-record.js"; +import { truncateRecord } from "../truncate-record.ts"; describe("truncateRecord", () => { test("should return record unchanged if under size limit", () => { diff --git a/src/content-indexer/utils/apiSpecs.ts b/src/content-indexer/utils/apiSpecs.ts index 4f05ce60e..bdaf662dd 100644 --- a/src/content-indexer/utils/apiSpecs.ts +++ b/src/content-indexer/utils/apiSpecs.ts @@ -5,7 +5,7 @@ import type { SpecType, } from "@/content-indexer/types/specs.js"; -import { fetchWithRetries } from "./fetchWithRetries.js"; +import { fetchWithRetries } from "./fetchWithRetries.ts"; interface MetadataJson { files: string[]; diff --git a/src/content-indexer/utils/filesystem.ts b/src/content-indexer/utils/filesystem.ts index 6e821d2f8..054841a6e 100644 --- a/src/content-indexer/utils/filesystem.ts +++ b/src/content-indexer/utils/filesystem.ts @@ -3,7 +3,7 @@ import matter from "gray-matter"; import yaml from "js-yaml"; import path from "path"; -import type { DocsYml } from "@/content-indexer/types/docsYaml.js"; +import type { DocsYml } from "@/content-indexer/types/docsYaml.ts"; /** * Reads a file from the local filesystem diff --git a/src/content-indexer/utils/navigation-helpers.ts b/src/content-indexer/utils/navigation-helpers.ts index f62c9e78e..590cfeb99 100644 --- a/src/content-indexer/utils/navigation-helpers.ts +++ b/src/content-indexer/utils/navigation-helpers.ts @@ -1,4 +1,4 @@ -import type { NavItem } from "@/content-indexer/types/navigation.js"; +import type { NavItem } from "@/content-indexer/types/navigation.ts"; /** * Creates breadcrumb-safe navigation item (without populated children). diff --git a/src/content-indexer/utils/normalization.ts b/src/content-indexer/utils/normalization.ts index 5134ecc07..195116efa 100644 --- a/src/content-indexer/utils/normalization.ts +++ b/src/content-indexer/utils/normalization.ts @@ -1,4 +1,4 @@ -import type { RepoConfig } from "@/content-indexer/utils/github.js"; +import type { RepoConfig } from "@/content-indexer/utils/github.ts"; /** * Normalizes a frontmatter slug by removing the "docs/" prefix. diff --git a/src/content-indexer/utils/openapi.ts b/src/content-indexer/utils/openapi.ts index e63efcbca..945ebfebc 100644 --- a/src/content-indexer/utils/openapi.ts +++ b/src/content-indexer/utils/openapi.ts @@ -2,8 +2,8 @@ import { kebabCase } from "lodash-es"; import type { OpenAPIV3 } from "openapi-types"; import removeMd from "remove-markdown"; -import { HTTP_METHODS } from "@/content-indexer/constants/http.js"; -import type { PathBuilder } from "@/content-indexer/core/path-builder.js"; +import { HTTP_METHODS } from "@/content-indexer/constants/http.ts"; +import type { PathBuilder } from "@/content-indexer/core/path-builder.ts"; export interface ExtractedOperation { operationId: string; diff --git a/src/content-indexer/utils/openrpc.ts b/src/content-indexer/utils/openrpc.ts index 4edd24338..89fc5a69c 100644 --- a/src/content-indexer/utils/openrpc.ts +++ b/src/content-indexer/utils/openrpc.ts @@ -1,4 +1,4 @@ -import type { OpenRpcSpec } from "@/content-indexer/types/specs.js"; +import type { OpenRpcSpec } from "@/content-indexer/types/specs.ts"; /** * Type guard to check if a spec is a valid OpenRPC spec with methods array. diff --git a/src/content-indexer/utils/test-factories.ts b/src/content-indexer/utils/test-factories.ts index d35891d10..0b80e6cd4 100644 --- a/src/content-indexer/utils/test-factories.ts +++ b/src/content-indexer/utils/test-factories.ts @@ -2,7 +2,7 @@ import type { OpenApiSpec, OpenRpcSpec, } from "@/content-indexer/types/specs.js"; -import type { RepoConfig } from "@/content-indexer/utils/github.js"; +import type { RepoConfig } from "@/content-indexer/utils/github.ts"; /** * Factory for creating OpenAPI spec with minimal required fields for testing diff --git a/src/content-indexer/utils/truncate-record.ts b/src/content-indexer/utils/truncate-record.ts index 35dfaa67b..3fa7d8a75 100644 --- a/src/content-indexer/utils/truncate-record.ts +++ b/src/content-indexer/utils/truncate-record.ts @@ -1,6 +1,6 @@ import removeMd from "remove-markdown"; -import type { AlgoliaRecord } from "@/content-indexer/types/algolia.js"; +import type { AlgoliaRecord } from "@/content-indexer/types/algolia.ts"; const MAX_RECORD_BYTES = 100_000; // Algolia imposes a 100KB limit on each record const BUFFER_BYTES = 1_000; diff --git a/src/content-indexer/visitors/__tests__/index.test.ts b/src/content-indexer/visitors/__tests__/index.test.ts index a0d599ca0..d436c5c93 100644 --- a/src/content-indexer/visitors/__tests__/index.test.ts +++ b/src/content-indexer/visitors/__tests__/index.test.ts @@ -1,12 +1,12 @@ import { describe, expect, test } from "vitest"; -import { ProcessingContext } from "@/content-indexer/collectors/processing-context.js"; -import { ContentCache } from "@/content-indexer/core/content-cache.js"; -import { PathBuilder } from "@/content-indexer/core/path-builder.js"; -import { DOCS_REPO } from "@/content-indexer/utils/github.js"; -import { openApiSpecFactory } from "@/content-indexer/utils/test-factories.js"; +import { ProcessingContext } from "@/content-indexer/collectors/processing-context.ts"; +import { ContentCache } from "@/content-indexer/core/content-cache.ts"; +import { PathBuilder } from "@/content-indexer/core/path-builder.ts"; +import { DOCS_REPO } from "@/content-indexer/utils/github.ts"; +import { openApiSpecFactory } from "@/content-indexer/utils/test-factories.ts"; -import { visitNavigationItem } from "../index.js"; +import { visitNavigationItem } from "../index.ts"; describe("visitNavigationItem dispatcher", () => { test("should route page config to visitPage", () => { diff --git a/src/content-indexer/visitors/__tests__/visit-api-reference.test.ts b/src/content-indexer/visitors/__tests__/visit-api-reference.test.ts index 205e14b32..8452535db 100644 --- a/src/content-indexer/visitors/__tests__/visit-api-reference.test.ts +++ b/src/content-indexer/visitors/__tests__/visit-api-reference.test.ts @@ -1,15 +1,15 @@ import { describe, expect, test } from "vitest"; -import { ProcessingContext } from "@/content-indexer/collectors/processing-context.js"; -import { ContentCache } from "@/content-indexer/core/content-cache.js"; -import { PathBuilder } from "@/content-indexer/core/path-builder.js"; -import { DOCS_REPO } from "@/content-indexer/utils/github.js"; +import { ProcessingContext } from "@/content-indexer/collectors/processing-context.ts"; +import { ContentCache } from "@/content-indexer/core/content-cache.ts"; +import { PathBuilder } from "@/content-indexer/core/path-builder.ts"; +import { DOCS_REPO } from "@/content-indexer/utils/github.ts"; import { openApiSpecFactory, openRpcSpecFactory, } from "@/content-indexer/utils/test-factories.js"; -import { visitApiReference } from "../visit-api-reference.js"; +import { visitApiReference } from "../visit-api-reference.ts"; describe("visitApiReference", () => { test("should return empty result if spec not in cache", () => { diff --git a/src/content-indexer/visitors/__tests__/visit-link.test.ts b/src/content-indexer/visitors/__tests__/visit-link.test.ts index 25ef44d89..fea50803e 100644 --- a/src/content-indexer/visitors/__tests__/visit-link.test.ts +++ b/src/content-indexer/visitors/__tests__/visit-link.test.ts @@ -1,11 +1,11 @@ import { describe, expect, test } from "vitest"; -import { ProcessingContext } from "@/content-indexer/collectors/processing-context.js"; -import { ContentCache } from "@/content-indexer/core/content-cache.js"; -import { PathBuilder } from "@/content-indexer/core/path-builder.js"; -import { DOCS_REPO } from "@/content-indexer/utils/github.js"; +import { ProcessingContext } from "@/content-indexer/collectors/processing-context.ts"; +import { ContentCache } from "@/content-indexer/core/content-cache.ts"; +import { PathBuilder } from "@/content-indexer/core/path-builder.ts"; +import { DOCS_REPO } from "@/content-indexer/utils/github.ts"; -import { visitLink } from "../visit-link.js"; +import { visitLink } from "../visit-link.ts"; describe("visitLink", () => { test("should create link nav item", () => { diff --git a/src/content-indexer/visitors/__tests__/visit-page.test.ts b/src/content-indexer/visitors/__tests__/visit-page.test.ts index d0dcd1ae3..ae4631668 100644 --- a/src/content-indexer/visitors/__tests__/visit-page.test.ts +++ b/src/content-indexer/visitors/__tests__/visit-page.test.ts @@ -1,11 +1,11 @@ import { describe, expect, test } from "vitest"; -import { ProcessingContext } from "@/content-indexer/collectors/processing-context.js"; -import { ContentCache } from "@/content-indexer/core/content-cache.js"; -import { PathBuilder } from "@/content-indexer/core/path-builder.js"; -import { DOCS_REPO } from "@/content-indexer/utils/github.js"; +import { ProcessingContext } from "@/content-indexer/collectors/processing-context.ts"; +import { ContentCache } from "@/content-indexer/core/content-cache.ts"; +import { PathBuilder } from "@/content-indexer/core/path-builder.ts"; +import { DOCS_REPO } from "@/content-indexer/utils/github.ts"; -import { visitPage } from "../visit-page.js"; +import { visitPage } from "../visit-page.ts"; describe("visitPage", () => { test("should create path index entry for page", () => { diff --git a/src/content-indexer/visitors/__tests__/visit-section.test.ts b/src/content-indexer/visitors/__tests__/visit-section.test.ts index 35f55a377..45e7b6505 100644 --- a/src/content-indexer/visitors/__tests__/visit-section.test.ts +++ b/src/content-indexer/visitors/__tests__/visit-section.test.ts @@ -1,12 +1,12 @@ import { describe, expect, test } from "vitest"; -import { ProcessingContext } from "@/content-indexer/collectors/processing-context.js"; -import { ContentCache } from "@/content-indexer/core/content-cache.js"; -import { PathBuilder } from "@/content-indexer/core/path-builder.js"; -import { DOCS_REPO } from "@/content-indexer/utils/github.js"; +import { ProcessingContext } from "@/content-indexer/collectors/processing-context.ts"; +import { ContentCache } from "@/content-indexer/core/content-cache.ts"; +import { PathBuilder } from "@/content-indexer/core/path-builder.ts"; +import { DOCS_REPO } from "@/content-indexer/utils/github.ts"; -import { visitNavigationItem } from "../index.js"; -import { visitSection } from "../visit-section.js"; +import { visitNavigationItem } from "../index.ts"; +import { visitSection } from "../visit-section.ts"; describe("visitSection", () => { test("should create section nav item with children", () => { diff --git a/src/content-indexer/visitors/index.ts b/src/content-indexer/visitors/index.ts index d61e02697..5d2a6d636 100644 --- a/src/content-indexer/visitors/index.ts +++ b/src/content-indexer/visitors/index.ts @@ -1,6 +1,6 @@ -import type { ProcessingContext } from "@/content-indexer/collectors/processing-context.js"; -import type { ContentCache } from "@/content-indexer/core/content-cache.js"; -import type { PathBuilder } from "@/content-indexer/core/path-builder.js"; +import type { ProcessingContext } from "@/content-indexer/collectors/processing-context.ts"; +import type { ContentCache } from "@/content-indexer/core/content-cache.ts"; +import type { PathBuilder } from "@/content-indexer/core/path-builder.ts"; import { isApiConfig, isChangelogConfig, @@ -9,14 +9,14 @@ import { isSectionConfig, type NavigationItem, } from "@/content-indexer/types/docsYaml.js"; -import type { NavItem } from "@/content-indexer/types/navigation.js"; -import type { PathIndex } from "@/content-indexer/types/pathIndex.js"; -import type { RepoConfig } from "@/content-indexer/utils/github.js"; +import type { NavItem } from "@/content-indexer/types/navigation.ts"; +import type { PathIndex } from "@/content-indexer/types/pathIndex.ts"; +import type { RepoConfig } from "@/content-indexer/utils/github.ts"; -import { visitApiReference } from "./visit-api-reference.js"; -import { visitLink } from "./visit-link.js"; -import { visitPage } from "./visit-page.js"; -import { visitSection } from "./visit-section.js"; +import { visitApiReference } from "./visit-api-reference.ts"; +import { visitLink } from "./visit-link.ts"; +import { visitPage } from "./visit-page.ts"; +import { visitSection } from "./visit-section.ts"; export interface VisitorConfigBase { parentPath: PathBuilder; diff --git a/src/content-indexer/visitors/processors/__tests__/process-openapi.test.ts b/src/content-indexer/visitors/processors/__tests__/process-openapi.test.ts index cbb12831f..e75f06cec 100644 --- a/src/content-indexer/visitors/processors/__tests__/process-openapi.test.ts +++ b/src/content-indexer/visitors/processors/__tests__/process-openapi.test.ts @@ -1,12 +1,12 @@ import { describe, expect, test } from "vitest"; -import { ProcessingContext } from "@/content-indexer/collectors/processing-context.js"; -import { ContentCache } from "@/content-indexer/core/content-cache.js"; -import { PathBuilder } from "@/content-indexer/core/path-builder.js"; -import { DOCS_REPO } from "@/content-indexer/utils/github.js"; -import { openApiSpecFactory } from "@/content-indexer/utils/test-factories.js"; +import { ProcessingContext } from "@/content-indexer/collectors/processing-context.ts"; +import { ContentCache } from "@/content-indexer/core/content-cache.ts"; +import { PathBuilder } from "@/content-indexer/core/path-builder.ts"; +import { DOCS_REPO } from "@/content-indexer/utils/github.ts"; +import { openApiSpecFactory } from "@/content-indexer/utils/test-factories.ts"; -import { processOpenApiSpec } from "../process-openapi.js"; +import { processOpenApiSpec } from "../process-openapi.ts"; describe("processOpenApiSpec", () => { test("should process operations and create index entries", () => { diff --git a/src/content-indexer/visitors/processors/__tests__/process-openrpc.test.ts b/src/content-indexer/visitors/processors/__tests__/process-openrpc.test.ts index eaebe8c25..040cd78e6 100644 --- a/src/content-indexer/visitors/processors/__tests__/process-openrpc.test.ts +++ b/src/content-indexer/visitors/processors/__tests__/process-openrpc.test.ts @@ -1,13 +1,13 @@ import { describe, expect, test, vi } from "vitest"; -import { ProcessingContext } from "@/content-indexer/collectors/processing-context.js"; -import { ContentCache } from "@/content-indexer/core/content-cache.js"; -import { PathBuilder } from "@/content-indexer/core/path-builder.js"; -import type { OpenRpcSpec } from "@/content-indexer/types/specs.js"; -import { DOCS_REPO } from "@/content-indexer/utils/github.js"; -import { openRpcSpecFactory } from "@/content-indexer/utils/test-factories.js"; +import { ProcessingContext } from "@/content-indexer/collectors/processing-context.ts"; +import { ContentCache } from "@/content-indexer/core/content-cache.ts"; +import { PathBuilder } from "@/content-indexer/core/path-builder.ts"; +import type { OpenRpcSpec } from "@/content-indexer/types/specs.ts"; +import { DOCS_REPO } from "@/content-indexer/utils/github.ts"; +import { openRpcSpecFactory } from "@/content-indexer/utils/test-factories.ts"; -import { processOpenRpcSpec } from "../process-openrpc.js"; +import { processOpenRpcSpec } from "../process-openrpc.ts"; describe("processOpenRpcSpec", () => { test("should return empty result for invalid spec", () => { diff --git a/src/content-indexer/visitors/processors/process-openapi.ts b/src/content-indexer/visitors/processors/process-openapi.ts index 3e02ac057..4bd63fa65 100644 --- a/src/content-indexer/visitors/processors/process-openapi.ts +++ b/src/content-indexer/visitors/processors/process-openapi.ts @@ -1,9 +1,9 @@ -import type { ProcessingContext } from "@/content-indexer/collectors/processing-context.js"; -import type { PathBuilder } from "@/content-indexer/core/path-builder.js"; -import type { NavItem } from "@/content-indexer/types/navigation.js"; -import type { PathIndex } from "@/content-indexer/types/pathIndex.js"; -import type { OpenApiSpec } from "@/content-indexer/types/specs.js"; -import { createBreadcrumbNavItem } from "@/content-indexer/utils/navigation-helpers.js"; +import type { ProcessingContext } from "@/content-indexer/collectors/processing-context.ts"; +import type { PathBuilder } from "@/content-indexer/core/path-builder.ts"; +import type { NavItem } from "@/content-indexer/types/navigation.ts"; +import type { PathIndex } from "@/content-indexer/types/pathIndex.ts"; +import type { OpenApiSpec } from "@/content-indexer/types/specs.ts"; +import { createBreadcrumbNavItem } from "@/content-indexer/utils/navigation-helpers.ts"; import { buildOperationPath, extractOpenApiOperations, diff --git a/src/content-indexer/visitors/processors/process-openrpc.ts b/src/content-indexer/visitors/processors/process-openrpc.ts index 20c2c62b4..21efc5a7c 100644 --- a/src/content-indexer/visitors/processors/process-openrpc.ts +++ b/src/content-indexer/visitors/processors/process-openrpc.ts @@ -1,12 +1,12 @@ import { kebabCase } from "lodash-es"; import removeMd from "remove-markdown"; -import type { PathBuilder } from "@/content-indexer/core/path-builder.js"; -import type { NavItem } from "@/content-indexer/types/navigation.js"; -import type { PathIndex } from "@/content-indexer/types/pathIndex.js"; -import type { OpenRpcSpec } from "@/content-indexer/types/specs.js"; -import { createBreadcrumbNavItem } from "@/content-indexer/utils/navigation-helpers.js"; -import { isValidOpenRpcSpec } from "@/content-indexer/utils/openrpc.js"; +import type { PathBuilder } from "@/content-indexer/core/path-builder.ts"; +import type { NavItem } from "@/content-indexer/types/navigation.ts"; +import type { PathIndex } from "@/content-indexer/types/pathIndex.ts"; +import type { OpenRpcSpec } from "@/content-indexer/types/specs.ts"; +import { createBreadcrumbNavItem } from "@/content-indexer/utils/navigation-helpers.ts"; +import { isValidOpenRpcSpec } from "@/content-indexer/utils/openrpc.ts"; import type { VisitorConfig, VisitorResult, diff --git a/src/content-indexer/visitors/visit-api-reference.ts b/src/content-indexer/visitors/visit-api-reference.ts index 7fc27d1d3..0249aa3cd 100644 --- a/src/content-indexer/visitors/visit-api-reference.ts +++ b/src/content-indexer/visitors/visit-api-reference.ts @@ -1,14 +1,14 @@ import { kebabCase } from "lodash-es"; -import type { ApiConfig } from "@/content-indexer/types/docsYaml.js"; +import type { ApiConfig } from "@/content-indexer/types/docsYaml.ts"; import type { OpenApiSpec, OpenRpcSpec, } from "@/content-indexer/types/specs.js"; -import type { VisitorConfigBase, VisitorResult } from "./index.js"; -import { processOpenApiSpec } from "./processors/process-openapi.js"; -import { processOpenRpcSpec } from "./processors/process-openrpc.js"; +import type { VisitorConfigBase, VisitorResult } from "./index.ts"; +import { processOpenApiSpec } from "./processors/process-openapi.ts"; +import { processOpenRpcSpec } from "./processors/process-openrpc.ts"; export interface ApiVisitorConfig extends VisitorConfigBase { item: ApiConfig; diff --git a/src/content-indexer/visitors/visit-link.ts b/src/content-indexer/visitors/visit-link.ts index 8b478289a..c26f1c616 100644 --- a/src/content-indexer/visitors/visit-link.ts +++ b/src/content-indexer/visitors/visit-link.ts @@ -1,6 +1,6 @@ -import type { LinkConfig } from "@/content-indexer/types/docsYaml.js"; +import type { LinkConfig } from "@/content-indexer/types/docsYaml.ts"; -import type { VisitorConfigBase, VisitorResult } from "./index.js"; +import type { VisitorConfigBase, VisitorResult } from "./index.ts"; export interface LinkVisitorConfig extends VisitorConfigBase { item: LinkConfig; diff --git a/src/content-indexer/visitors/visit-page.ts b/src/content-indexer/visitors/visit-page.ts index 3fa632954..864f1a84a 100644 --- a/src/content-indexer/visitors/visit-page.ts +++ b/src/content-indexer/visitors/visit-page.ts @@ -1,13 +1,13 @@ import { kebabCase } from "lodash-es"; -import type { PageConfig } from "@/content-indexer/types/docsYaml.js"; -import type { NavItem } from "@/content-indexer/types/navigation.js"; +import type { PageConfig } from "@/content-indexer/types/docsYaml.ts"; +import type { NavItem } from "@/content-indexer/types/navigation.ts"; import { normalizeFilePath, normalizeSlug, } from "@/content-indexer/utils/normalization.js"; -import type { VisitorConfigBase, VisitorResult } from "./index.js"; +import type { VisitorConfigBase, VisitorResult } from "./index.ts"; export interface PageVisitorConfig extends VisitorConfigBase { item: PageConfig; diff --git a/src/content-indexer/visitors/visit-section.ts b/src/content-indexer/visitors/visit-section.ts index ef5262f5f..03cb06644 100644 --- a/src/content-indexer/visitors/visit-section.ts +++ b/src/content-indexer/visitors/visit-section.ts @@ -1,8 +1,8 @@ import { kebabCase } from "lodash-es"; -import type { SectionConfig } from "@/content-indexer/types/docsYaml.js"; -import type { NavItem } from "@/content-indexer/types/navigation.js"; -import type { PathIndex } from "@/content-indexer/types/pathIndex.js"; +import type { SectionConfig } from "@/content-indexer/types/docsYaml.ts"; +import type { NavItem } from "@/content-indexer/types/navigation.ts"; +import type { PathIndex } from "@/content-indexer/types/pathIndex.ts"; import { normalizeFilePath, normalizeSlug, diff --git a/src/utils/generateRpcSpecs.ts b/src/utils/generateRpcSpecs.ts index a74b00c03..fae58e884 100644 --- a/src/utils/generateRpcSpecs.ts +++ b/src/utils/generateRpcSpecs.ts @@ -1,8 +1,8 @@ import { dereference } from "@apidevtools/json-schema-ref-parser"; -import type { DerefedOpenRpcDoc } from "../types/openRpc.js"; -import { formatOpenRpcDoc, writeOpenRpcDoc } from "./generationHelpers.js"; -import { validateRpcSpec } from "./validateRpcSpec.js"; +import type { DerefedOpenRpcDoc } from "../types/openRpc.ts"; +import { formatOpenRpcDoc, writeOpenRpcDoc } from "./generationHelpers.ts"; +import { validateRpcSpec } from "./validateRpcSpec.ts"; /** * Generates an OpenRPC specification for the Alchemy JSON-RPC API. diff --git a/src/utils/generationHelpers.ts b/src/utils/generationHelpers.ts index a163aed81..4e8bd07f8 100644 --- a/src/utils/generationHelpers.ts +++ b/src/utils/generationHelpers.ts @@ -2,7 +2,7 @@ import type { JSONSchema, OpenrpcDocument } from "@open-rpc/meta-schema"; import { writeFileSync } from "fs"; import mergeAllOf from "json-schema-merge-allof"; -import type { DerefedOpenRpcDoc } from "../types/openRpc.js"; +import type { DerefedOpenRpcDoc } from "../types/openRpc.ts"; /** * Formats an OpenRPC document by removing components, merging allOf schemas, and sorting methods diff --git a/src/utils/validateRpcSpec.ts b/src/utils/validateRpcSpec.ts index a8a40b17f..1174bb7b9 100644 --- a/src/utils/validateRpcSpec.ts +++ b/src/utils/validateRpcSpec.ts @@ -1,6 +1,6 @@ import { validateOpenRPCDocument } from "@open-rpc/schema-utils-js"; -import type { DerefedOpenRpcDoc } from "../types/openRpc.js"; +import type { DerefedOpenRpcDoc } from "../types/openRpc.ts"; interface ValidationError { keyword: string;