From e13da2eebce2bf044b1ea9bc080cd0891f946eb3 Mon Sep 17 00:00:00 2001 From: Steve Calvert Date: Fri, 27 Feb 2026 10:31:50 -0800 Subject: [PATCH 1/4] feat: add API sidebar sync script and CI gate MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Adds scripts/sync-api-sidebar.mjs which validates and auto-fixes sidebars.ts against generated .api.mdx files using AST manipulation (jscodeshift) and prettier for formatting. - `pnpm sidebar:check` — exits 1 with a list of missing entries (CI gate) - `pnpm sidebar:fix` — inserts missing entries via AST, then runs prettier Hooks sidebar:fix into all three openapi:regenerate:* scripts so that sidebar updates are atomic with API page regeneration. Adds a sidebar:check step to ci.yml before the build to prevent gaps from landing silently on main. Co-Authored-By: Claude Sonnet 4.6 (1M context) --- .github/workflows/ci.yml | 3 + package.json | 9 +- pnpm-lock.yaml | 242 ++++++++++++++++++++++++++++++++--- scripts/sync-api-sidebar.mjs | 228 +++++++++++++++++++++++++++++++++ 4 files changed, 463 insertions(+), 19 deletions(-) create mode 100644 scripts/sync-api-sidebar.mjs diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 94de11ab..24eddc47 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -40,6 +40,9 @@ jobs: echo "No usage of support@glean.com found" fi + - name: Check API sidebar completeness + run: pnpm sidebar:check + - name: Build website run: pnpm build env: diff --git a/package.json b/package.json index 788ba33f..60e64276 100644 --- a/package.json +++ b/package.json @@ -41,9 +41,11 @@ "format:check": "prettier --check .", "links:check": "scripts/check-links.sh https://developers.glean.com true", "links:check:local": "pnpm build && (pnpm serve --port 8888 & SERVER_PID=$!; sleep 5; scripts/check-links.sh http://localhost:8888 true; RESULT=$?; kill $SERVER_PID 2>/dev/null || true; exit $RESULT)", - "openapi:regenerate:all": "pnpm run openapi:clean:before:all && pnpm run openapi:transform:all && pnpm run generate:deprecations && pnpm run generate:deprecations:rss && pnpm run openapi:generate:all && pnpm run openapi:clean:after:all", - "openapi:regenerate:client": "pnpm run openapi:clean:before:client && pnpm run openapi:transform:client && pnpm run generate:deprecations && pnpm run generate:deprecations:rss && pnpm run openapi:generate:client && pnpm run openapi:clean:after:client", - "openapi:regenerate:indexing": "pnpm run openapi:clean:before:indexing && pnpm run openapi:transform:indexing && pnpm run generate:deprecations && pnpm run generate:deprecations:rss && pnpm run openapi:generate:indexing && pnpm run openapi:clean:after:indexing", + "sidebar:check": "node scripts/sync-api-sidebar.mjs --check", + "sidebar:fix": "node scripts/sync-api-sidebar.mjs --fix", + "openapi:regenerate:all": "pnpm run openapi:clean:before:all && pnpm run openapi:transform:all && pnpm run generate:deprecations && pnpm run generate:deprecations:rss && pnpm run openapi:generate:all && pnpm run openapi:clean:after:all && pnpm run sidebar:fix", + "openapi:regenerate:client": "pnpm run openapi:clean:before:client && pnpm run openapi:transform:client && pnpm run generate:deprecations && pnpm run generate:deprecations:rss && pnpm run openapi:generate:client && pnpm run openapi:clean:after:client && pnpm run sidebar:fix", + "openapi:regenerate:indexing": "pnpm run openapi:clean:before:indexing && pnpm run openapi:transform:indexing && pnpm run generate:deprecations && pnpm run generate:deprecations:rss && pnpm run openapi:generate:indexing && pnpm run openapi:clean:after:indexing && pnpm run sidebar:fix", "openapi:clean:before:all": "pnpm run openapi:clean:before:client && pnpm run openapi:clean:before:indexing", "openapi:clean:before:client": "find docs/api/client-api -type f ! -name 'overview.mdx' -delete", "openapi:clean:before:indexing": "find docs/api/indexing-api -type f ! -name '*-overview.mdx' -delete", @@ -134,6 +136,7 @@ "@vitest/ui": "^4.0.18", "fast-levenshtein": "^3.0.0", "fixturify-project": "^7.1.3", + "jscodeshift": "^17.3.0", "jsdom": "^27.4.0", "nock": "^14.0.0", "npm-run-all": "^4.1.5", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index b8a6a60e..e8f8fdc7 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -168,6 +168,9 @@ importers: fixturify-project: specifier: ^7.1.3 version: 7.1.3 + jscodeshift: + specifier: ^17.3.0 + version: 17.3.0(@babel/preset-env@7.28.3(@babel/core@7.28.5)) jsdom: specifier: ^27.4.0 version: 27.4.0 @@ -477,6 +480,10 @@ packages: resolution: {integrity: sha512-1gn1Up5YXka3YYAHGKpbideQ5Yjf1tDa9qYcgysz+cNCXukyLl6DjPXhD3VRwSb8c0J9tA4b2+rHEZtc6R0tlw==} engines: {node: '>=6.9.0'} + '@babel/helper-plugin-utils@7.28.6': + resolution: {integrity: sha512-S9gzZ/bz83GRysI7gAD4wPT/AI3uCnY+9xn+Mx/KPs2JwHJIz1W8PZkg2cqyt3RNOBM8ejcXhV6y8Og7ly/Dug==} + engines: {node: '>=6.9.0'} + '@babel/helper-remap-async-to-generator@7.27.1': resolution: {integrity: sha512-7fiA521aVw8lSPeI4ZOD3vRFkoqkJcS+z4hFo82bFSH/2tNd6eJ5qCVMS5OzDmZh/kaHQeBaeyxK6wljcPtveA==} engines: {node: '>=6.9.0'} @@ -563,6 +570,12 @@ packages: peerDependencies: '@babel/core': ^7.0.0-0 + '@babel/plugin-syntax-flow@7.28.6': + resolution: {integrity: sha512-D+OrJumc9McXNEBI/JmFnc/0uCM2/Y3PEBG3gfV3QIYkKv5pvnpzFrl1kYCrcHJP8nOeFB/SHi1IHz29pNGuew==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + '@babel/plugin-syntax-import-assertions@7.27.1': resolution: {integrity: sha512-UT/Jrhw57xg4ILHLFnzFpPDlMbcdEicaAtjPQpbj9wa8T4r5KVWCimHcL/460g8Ht0DMxDyjsLgiWSkVjnwPFg==} engines: {node: '>=6.9.0'} @@ -695,6 +708,12 @@ packages: peerDependencies: '@babel/core': ^7.0.0-0 + '@babel/plugin-transform-flow-strip-types@7.27.1': + resolution: {integrity: sha512-G5eDKsu50udECw7DL2AcsysXiQyB7Nfg521t2OAJ4tbfTJ27doHLeF/vlI1NZGlLdbb/v+ibvtL1YBQqYOwJGg==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + '@babel/plugin-transform-for-of@7.27.1': resolution: {integrity: sha512-BfbWFFEJFQzLCQ5N8VocnCtA8J1CLkNTe2Ms2wocj75dd6VpiqS5Z5quTYcUoo4Yq+DN0rtikODccuv7RU81sw==} engines: {node: '>=6.9.0'} @@ -959,6 +978,12 @@ packages: peerDependencies: '@babel/core': ^7.0.0-0 + '@babel/preset-flow@7.27.1': + resolution: {integrity: sha512-ez3a2it5Fn6P54W8QkbfIyyIbxlXvcxyWHHvno1Wg0Ej5eiJY5hBb8ExttoIOJJk7V2dZE6prP7iby5q2aQ0Lg==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + '@babel/preset-modules@0.1.6-no-external-plugins': resolution: {integrity: sha512-HrcgcIESLm9aIR842yhJ5RWan/gebQUJ6E/E5+rf0y9o6oj7w0Br+sWuL6kEQ/o/AdfvR1Je9jG18/gnpwjEyA==} peerDependencies: @@ -976,6 +1001,12 @@ packages: peerDependencies: '@babel/core': ^7.0.0-0 + '@babel/register@7.28.6': + resolution: {integrity: sha512-pgcbbEl/dWQYb6L6Yew6F94rdwygfuv+vJ/tXfwIOYAfPB6TNWpXUMEtEq3YuTeHRdvMIhvz13bkT9CNaS+wqA==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + '@babel/runtime-corejs3@7.28.4': resolution: {integrity: sha512-h7iEYiW4HebClDEhtvFObtPmIvrd1SSfpI9EhOeKk4CtIK/ngBWFpuhCzhdmRKtg71ylcue+9I6dv54XYO1epQ==} engines: {node: '>=6.9.0'} @@ -3840,6 +3871,10 @@ packages: resolution: {integrity: sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==} engines: {node: '>=12'} + ast-types@0.16.1: + resolution: {integrity: sha512-6t10qk83GOG8p0vKmaCr8eiilZwO171AvbROMtvvNiwrTly62t+7XkA8RdIIVbpMhCASAsxgAzdRSwh6nw/5Dg==} + engines: {node: '>=4'} + astral-regex@2.0.0: resolution: {integrity: sha512-Z7tMw1ytTXt5jqMcOP+OQteU1VuNK9Y02uuJtKQ1Sv69jXQKKg5cibLwGJow8yzZP+eAc18EmLGPal0bp36rvQ==} engines: {node: '>=8'} @@ -4300,6 +4335,9 @@ packages: common-path-prefix@3.0.0: resolution: {integrity: sha512-QE33hToZseCH3jS0qN96O/bSh3kaw/h+Tq7ngyY9eWDUnTlTNUyqfqvCXioLe5Na5jFsL78ra/wuBU4iuEgd4w==} + commondir@1.0.1: + resolution: {integrity: sha512-W9pAhw0ja1Edb5GVdIF1mjZw/ASI0AlShXM83UUGe2DVr5TdAPEA1OA8m/g8zWp9x6On7gqufY+FatDbC3MDQg==} + compressible@2.0.18: resolution: {integrity: sha512-AF3r7P5dWxL8MxyITRMlORQNaOA2IkAFaTr4k7BUumjPtRpGDTZpl0Pb1XCO6JeDCBdp126Cgs9sMxqSjgYyRg==} engines: {node: '>= 0.6'} @@ -5326,10 +5364,18 @@ packages: resolution: {integrity: sha512-S8KoZgRZN+a5rNwqTxlZZePjT/4cnm0ROV70LedRHZ0p8u9fRID0hJUZQpkKLzro8LfmC8sx23bY6tVNxv8pQA==} engines: {node: '>= 18.0.0'} + find-cache-dir@2.1.0: + resolution: {integrity: sha512-Tq6PixE0w/VMFfCgbONnkiQIVol/JJL7nRMi20fqzA4NRs9AfeqMGeRdPi3wIhYkxjeBaWh2rxwapn5Tu3IqOQ==} + engines: {node: '>=6'} + find-cache-dir@4.0.0: resolution: {integrity: sha512-9ZonPT4ZAK4a+1pUPVPZJapbi7O5qbbJPdYw/NOQWZZbVLdDTYM3A4R9z/DpAM08IDaFGsvPgiGZ82WEwUDWjg==} engines: {node: '>=14.16'} + find-up@3.0.0: + resolution: {integrity: sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==} + engines: {node: '>=6'} + find-up@5.0.0: resolution: {integrity: sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==} engines: {node: '>=10'} @@ -5360,6 +5406,10 @@ packages: flexsearch@0.7.43: resolution: {integrity: sha512-c5o/+Um8aqCSOXGcZoqZOm+NqtVwNsvVpWv6lfmSclU954O3wvQKxxK8zj74fPaSJbXpSLTs4PRhh+wnoCXnKg==} + flow-parser@0.303.0: + resolution: {integrity: sha512-SGifrPA0IQqN4S3MFZ3KPqphxWd+VehHEwpQPEjWQGtJSnPsFakrlqNQH88MLyJGi/Z7WO15FMylybGONwqwxA==} + engines: {node: '>=0.4.0'} + follow-redirects@1.15.11: resolution: {integrity: sha512-deG2P0JfjrTxl50XGCDyfI97ZGVCxIpfKYmfyrQ54n5FO/0gfIES8C/Psl6kWVDolizcaaxZJnTS0QSMxvnsBQ==} engines: {node: '>=4.0'} @@ -5492,12 +5542,12 @@ packages: glob@7.2.3: resolution: {integrity: sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==} - deprecated: Glob versions prior to v9 are no longer supported + deprecated: Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me glob@8.1.0: resolution: {integrity: sha512-r8hpEjiQEYlF2QU0df3dS+nxxSIreXQS1qRhMJM0Q5NDdR386C7jb7Hwwod8Fgiuex+k0GFjgft18yvxm5XoCQ==} engines: {node: '>=12'} - deprecated: Glob versions prior to v9 are no longer supported + deprecated: Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me global-dirs@3.0.1: resolution: {integrity: sha512-NBcGGFbBA9s1VzD41QXDG+3++t9Mn5t1FpLdhESY6oKY4gYTFpX4wO3sqGUa0Srjtbfj3szX0RnemmrVRUdULA==} @@ -6194,6 +6244,16 @@ packages: resolution: {integrity: sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==} hasBin: true + jscodeshift@17.3.0: + resolution: {integrity: sha512-LjFrGOIORqXBU+jwfC9nbkjmQfFldtMIoS6d9z2LG/lkmyNXsJAySPT+2SWXJEoE68/bCWcxKpXH37npftgmow==} + engines: {node: '>=16'} + hasBin: true + peerDependencies: + '@babel/preset-env': ^7.1.6 + peerDependenciesMeta: + '@babel/preset-env': + optional: true + jsdom@27.4.0: resolution: {integrity: sha512-mjzqwWRD9Y1J1KUi7W97Gja1bwOOM5Ug0EZ6UDK3xS7j7mndrkwozHtSblfomlzyB4NepioNt+B2sOSzczVgtQ==} engines: {node: ^20.19.0 || ^22.12.0 || >=24.0.0} @@ -6396,6 +6456,10 @@ packages: resolution: {integrity: sha512-arhlxbFRmoQHl33a0Zkle/YWlmNwoyt6QNZEIJcqNbdrsix5Lvc4HyyI3EnwxTYlZYc32EbYrQ8SzEZ7dqgg9A==} engines: {node: '>=14'} + locate-path@3.0.0: + resolution: {integrity: sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==} + engines: {node: '>=6'} + locate-path@6.0.0: resolution: {integrity: sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==} engines: {node: '>=10'} @@ -6465,6 +6529,10 @@ packages: magic-string@0.30.21: resolution: {integrity: sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==} + make-dir@2.1.0: + resolution: {integrity: sha512-LS9X+dc8KLxXCb8dni79fLIIUA5VyZoyjSMCwTluaXA0o27cCK0bhXkpgw+sTXVpPy/lSO57ilRixqk0vDmtRA==} + engines: {node: '>=6'} + make-error@1.3.6: resolution: {integrity: sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==} @@ -7130,6 +7198,10 @@ packages: resolution: {integrity: sha512-LICb2p9CB7FS+0eR1oqWnHhp0FljGLZCWBE9aix0Uye9W8LTQPwMTYVGWQWIw9RdQiDg4+epXQODwIYJtSJaow==} engines: {node: '>=4'} + p-limit@2.3.0: + resolution: {integrity: sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==} + engines: {node: '>=6'} + p-limit@3.1.0: resolution: {integrity: sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==} engines: {node: '>=10'} @@ -7138,6 +7210,10 @@ packages: resolution: {integrity: sha512-5b0R4txpzjPWVw/cXXUResoD4hb6U/x9BH08L7nw+GN1sezDzPdxeRvpc9c433fZhBan/wusjbCsqwqm4EIBIQ==} engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + p-locate@3.0.0: + resolution: {integrity: sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==} + engines: {node: '>=6'} + p-locate@5.0.0: resolution: {integrity: sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==} engines: {node: '>=10'} @@ -7174,6 +7250,10 @@ packages: resolution: {integrity: sha512-rhIwUycgwwKcP9yTOOFK/AKsAopjjCakVqLHePO3CC6Mir1Z99xT+R63jZxAT5lFZLa2inS5h+ZS2GvR99/FBg==} engines: {node: '>=8'} + p-try@2.2.0: + resolution: {integrity: sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==} + engines: {node: '>=6'} + package-json@8.1.1: resolution: {integrity: sha512-cbH9IAIJHNj9uXi196JVsRlt7cHKak6u/e6AkL/bkRelZ7rlL3X1YKxsZwa36xipOEKAsdtmaG6aAJoM1fx2zA==} engines: {node: '>=14.16'} @@ -7235,6 +7315,10 @@ packages: path-data-parser@0.1.0: resolution: {integrity: sha512-NOnmBpt5Y2RWbuv0LMzsayp3lVylAHLPUTut412ZA3l+C4uw4ZVkQbjShYCQ8TCpUMdPapr4YjUqLYD6v68j+w==} + path-exists@3.0.0: + resolution: {integrity: sha512-bpC7GYwiDYQ4wYLe+FA8lhRjhQCMcQGuSgGGqDkg/QerRWw9CmGRT0iSOVRSZJ29NMLZgIzqaljJ63oaL4NIJQ==} + engines: {node: '>=4'} + path-exists@4.0.0: resolution: {integrity: sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==} engines: {node: '>=8'} @@ -7335,6 +7419,10 @@ packages: resolution: {integrity: sha512-C3FsVNH1udSEX48gGX1xfvwTWfsYWj5U+8/uK15BGzIGrKoUpghX8hWZwa/OFnakBiiVNmBvemTJR5mcy7iPcg==} engines: {node: '>=4'} + pify@4.0.1: + resolution: {integrity: sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g==} + engines: {node: '>=6'} + pirates@4.0.7: resolution: {integrity: sha512-TfySrs/5nm8fQJDcBDuUng3VOUKsd7S+zqvbOTiGXHfxX4wK31ard+hoNuvkicM/2YFzlpDgABOevKSsB4G/FA==} engines: {node: '>= 6'} @@ -7343,6 +7431,10 @@ packages: resolution: {integrity: sha512-wQ0b/W4Fr01qtpHlqSqspcj3EhBvimsdh0KlHhH8HRZnMsEa0ea2fTULOXOS9ccQr3om+GcGRk4e+isrZWV8qQ==} engines: {node: '>=16.20.0'} + pkg-dir@3.0.0: + resolution: {integrity: sha512-/E57AYkoeQ25qkxMj5PBOVgF8Kiu/h7cYS30Z5+R7WaiCCBfLq58ZI/dSeaEKb9WVJV5n/03QwrN3IeWIFllvw==} + engines: {node: '>=6'} + pkg-dir@7.0.0: resolution: {integrity: sha512-Ie9z/WINcxxLp27BKOCHGde4ITq9UklYKDzVo1nhk5sqGEXU3FpkwP5GM2voTGJkGd9B3Otl+Q4uwSOeSUtOBA==} engines: {node: '>=14.16'} @@ -8045,6 +8137,10 @@ packages: resolution: {integrity: sha512-wnWtnywepjg/eHIgWR97R7UuM5i+qHLA195qdN9UPKvcMqfn60+67S8sPPW3vDlSEfYHoFkKU8IvpCNty3zQvQ==} engines: {node: '>=10'} + recast@0.23.11: + resolution: {integrity: sha512-YTUo+Flmw4ZXiWfQKGcwwc11KnoRAYgzAE2E7mXKCjSviTKShtxBsN6YUUBB2gtaBzKzeKunxhUwNHQuRryhWA==} + engines: {node: '>= 4'} + rechoir@0.6.2: resolution: {integrity: sha512-HFM8rkZ+i3zrV+4LQjwQ0W+ez98pApMGM3HUrN04j3CqzPOzl9nmP15Y8YXNm8QHGv/eacOVEjqhmWpkRV0NAw==} engines: {node: '>= 0.10'} @@ -8879,6 +8975,10 @@ packages: resolution: {integrity: sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw==} engines: {node: '>=0.6.0'} + tmp@0.2.5: + resolution: {integrity: sha512-voyz6MApa1rQGUxT3E+BK7/ROe8itEx7vD8/HEvt4xwXucvQ5G5oeEiHkmHZJuBO21RpOf+YYm9MOivj709jow==} + engines: {node: '>=14.14'} + to-regex-range@5.0.1: resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==} engines: {node: '>=8.0'} @@ -9883,7 +9983,7 @@ snapshots: '@babel/helper-optimise-call-expression': 7.27.1 '@babel/helper-replace-supers': 7.27.1(@babel/core@7.28.5) '@babel/helper-skip-transparent-expression-wrappers': 7.27.1 - '@babel/traverse': 7.28.4 + '@babel/traverse': 7.28.5 semver: 6.3.1 transitivePeerDependencies: - supports-color @@ -9910,7 +10010,7 @@ snapshots: '@babel/helper-member-expression-to-functions@7.27.1': dependencies: - '@babel/traverse': 7.28.4 + '@babel/traverse': 7.28.5 '@babel/types': 7.28.5 transitivePeerDependencies: - supports-color @@ -9937,12 +10037,14 @@ snapshots: '@babel/helper-plugin-utils@7.27.1': {} + '@babel/helper-plugin-utils@7.28.6': {} + '@babel/helper-remap-async-to-generator@7.27.1(@babel/core@7.28.5)': dependencies: '@babel/core': 7.28.5 '@babel/helper-annotate-as-pure': 7.27.3 '@babel/helper-wrap-function': 7.28.3 - '@babel/traverse': 7.28.4 + '@babel/traverse': 7.28.5 transitivePeerDependencies: - supports-color @@ -9951,13 +10053,13 @@ snapshots: '@babel/core': 7.28.5 '@babel/helper-member-expression-to-functions': 7.27.1 '@babel/helper-optimise-call-expression': 7.27.1 - '@babel/traverse': 7.28.4 + '@babel/traverse': 7.28.5 transitivePeerDependencies: - supports-color '@babel/helper-skip-transparent-expression-wrappers@7.27.1': dependencies: - '@babel/traverse': 7.28.4 + '@babel/traverse': 7.28.5 '@babel/types': 7.28.5 transitivePeerDependencies: - supports-color @@ -9973,7 +10075,7 @@ snapshots: '@babel/helper-wrap-function@7.28.3': dependencies: '@babel/template': 7.27.2 - '@babel/traverse': 7.28.4 + '@babel/traverse': 7.28.5 '@babel/types': 7.28.5 transitivePeerDependencies: - supports-color @@ -9991,7 +10093,7 @@ snapshots: dependencies: '@babel/core': 7.28.5 '@babel/helper-plugin-utils': 7.27.1 - '@babel/traverse': 7.28.4 + '@babel/traverse': 7.28.5 transitivePeerDependencies: - supports-color @@ -10018,7 +10120,7 @@ snapshots: dependencies: '@babel/core': 7.28.5 '@babel/helper-plugin-utils': 7.27.1 - '@babel/traverse': 7.28.4 + '@babel/traverse': 7.28.5 transitivePeerDependencies: - supports-color @@ -10031,6 +10133,11 @@ snapshots: '@babel/core': 7.28.5 '@babel/helper-plugin-utils': 7.27.1 + '@babel/plugin-syntax-flow@7.28.6(@babel/core@7.28.5)': + dependencies: + '@babel/core': 7.28.5 + '@babel/helper-plugin-utils': 7.28.6 + '@babel/plugin-syntax-import-assertions@7.27.1(@babel/core@7.28.5)': dependencies: '@babel/core': 7.28.5 @@ -10067,7 +10174,7 @@ snapshots: '@babel/core': 7.28.5 '@babel/helper-plugin-utils': 7.27.1 '@babel/helper-remap-async-to-generator': 7.27.1(@babel/core@7.28.5) - '@babel/traverse': 7.28.4 + '@babel/traverse': 7.28.5 transitivePeerDependencies: - supports-color @@ -10114,7 +10221,7 @@ snapshots: '@babel/helper-globals': 7.28.0 '@babel/helper-plugin-utils': 7.27.1 '@babel/helper-replace-supers': 7.27.1(@babel/core@7.28.5) - '@babel/traverse': 7.28.4 + '@babel/traverse': 7.28.5 transitivePeerDependencies: - supports-color @@ -10128,7 +10235,7 @@ snapshots: dependencies: '@babel/core': 7.28.5 '@babel/helper-plugin-utils': 7.27.1 - '@babel/traverse': 7.28.4 + '@babel/traverse': 7.28.5 transitivePeerDependencies: - supports-color @@ -10172,6 +10279,12 @@ snapshots: '@babel/core': 7.28.5 '@babel/helper-plugin-utils': 7.27.1 + '@babel/plugin-transform-flow-strip-types@7.27.1(@babel/core@7.28.5)': + dependencies: + '@babel/core': 7.28.5 + '@babel/helper-plugin-utils': 7.27.1 + '@babel/plugin-syntax-flow': 7.28.6(@babel/core@7.28.5) + '@babel/plugin-transform-for-of@7.27.1(@babel/core@7.28.5)': dependencies: '@babel/core': 7.28.5 @@ -10185,7 +10298,7 @@ snapshots: '@babel/core': 7.28.5 '@babel/helper-compilation-targets': 7.27.2 '@babel/helper-plugin-utils': 7.27.1 - '@babel/traverse': 7.28.4 + '@babel/traverse': 7.28.5 transitivePeerDependencies: - supports-color @@ -10231,7 +10344,7 @@ snapshots: '@babel/helper-module-transforms': 7.28.3(@babel/core@7.28.5) '@babel/helper-plugin-utils': 7.27.1 '@babel/helper-validator-identifier': 7.28.5 - '@babel/traverse': 7.28.4 + '@babel/traverse': 7.28.5 transitivePeerDependencies: - supports-color @@ -10271,7 +10384,7 @@ snapshots: '@babel/helper-plugin-utils': 7.27.1 '@babel/plugin-transform-destructuring': 7.28.0(@babel/core@7.28.5) '@babel/plugin-transform-parameters': 7.27.7(@babel/core@7.28.5) - '@babel/traverse': 7.28.4 + '@babel/traverse': 7.28.5 transitivePeerDependencies: - supports-color @@ -10533,6 +10646,13 @@ snapshots: transitivePeerDependencies: - supports-color + '@babel/preset-flow@7.27.1(@babel/core@7.28.5)': + dependencies: + '@babel/core': 7.28.5 + '@babel/helper-plugin-utils': 7.27.1 + '@babel/helper-validator-option': 7.27.1 + '@babel/plugin-transform-flow-strip-types': 7.27.1(@babel/core@7.28.5) + '@babel/preset-modules@0.1.6-no-external-plugins(@babel/core@7.28.5)': dependencies: '@babel/core': 7.28.5 @@ -10563,6 +10683,15 @@ snapshots: transitivePeerDependencies: - supports-color + '@babel/register@7.28.6(@babel/core@7.28.5)': + dependencies: + '@babel/core': 7.28.5 + clone-deep: 4.0.1 + find-cache-dir: 2.1.0 + make-dir: 2.1.0 + pirates: 4.0.7 + source-map-support: 0.5.21 + '@babel/runtime-corejs3@7.28.4': dependencies: core-js-pure: 3.45.1 @@ -14199,6 +14328,10 @@ snapshots: assertion-error@2.0.1: {} + ast-types@0.16.1: + dependencies: + tslib: 2.8.1 + astral-regex@2.0.0: {} astring@1.9.0: {} @@ -14684,6 +14817,8 @@ snapshots: common-path-prefix@3.0.0: {} + commondir@1.0.1: {} + compressible@2.0.18: dependencies: mime-db: 1.54.0 @@ -15959,11 +16094,21 @@ snapshots: transitivePeerDependencies: - supports-color + find-cache-dir@2.1.0: + dependencies: + commondir: 1.0.1 + make-dir: 2.1.0 + pkg-dir: 3.0.0 + find-cache-dir@4.0.0: dependencies: common-path-prefix: 3.0.0 pkg-dir: 7.0.0 + find-up@3.0.0: + dependencies: + locate-path: 3.0.0 + find-up@5.0.0: dependencies: locate-path: 6.0.0 @@ -16012,6 +16157,8 @@ snapshots: flexsearch@0.7.43: {} + flow-parser@0.303.0: {} + follow-redirects@1.15.11: {} for-each@0.3.5: @@ -16964,6 +17111,31 @@ snapshots: dependencies: argparse: 2.0.1 + jscodeshift@17.3.0(@babel/preset-env@7.28.3(@babel/core@7.28.5)): + dependencies: + '@babel/core': 7.28.5 + '@babel/parser': 7.28.5 + '@babel/plugin-transform-class-properties': 7.27.1(@babel/core@7.28.5) + '@babel/plugin-transform-modules-commonjs': 7.27.1(@babel/core@7.28.5) + '@babel/plugin-transform-nullish-coalescing-operator': 7.27.1(@babel/core@7.28.5) + '@babel/plugin-transform-optional-chaining': 7.27.1(@babel/core@7.28.5) + '@babel/plugin-transform-private-methods': 7.27.1(@babel/core@7.28.5) + '@babel/preset-flow': 7.27.1(@babel/core@7.28.5) + '@babel/preset-typescript': 7.27.1(@babel/core@7.28.5) + '@babel/register': 7.28.6(@babel/core@7.28.5) + flow-parser: 0.303.0 + graceful-fs: 4.2.11 + micromatch: 4.0.8 + neo-async: 2.6.2 + picocolors: 1.1.1 + recast: 0.23.11 + tmp: 0.2.5 + write-file-atomic: 5.0.1 + optionalDependencies: + '@babel/preset-env': 7.28.3(@babel/core@7.28.5) + transitivePeerDependencies: + - supports-color + jsdom@27.4.0: dependencies: '@acemir/cssom': 0.9.31 @@ -17157,6 +17329,11 @@ snapshots: pkg-types: 2.3.0 quansync: 0.2.11 + locate-path@3.0.0: + dependencies: + p-locate: 3.0.0 + path-exists: 3.0.0 + locate-path@6.0.0: dependencies: p-locate: 5.0.0 @@ -17213,6 +17390,11 @@ snapshots: dependencies: '@jridgewell/sourcemap-codec': 1.5.5 + make-dir@2.1.0: + dependencies: + pify: 4.0.1 + semver: 5.7.2 + make-error@1.3.6: {} map-age-cleaner@0.1.3: @@ -18183,6 +18365,10 @@ snapshots: p-finally@1.0.0: {} + p-limit@2.3.0: + dependencies: + p-try: 2.2.0 + p-limit@3.1.0: dependencies: yocto-queue: 0.1.0 @@ -18191,6 +18377,10 @@ snapshots: dependencies: yocto-queue: 1.2.1 + p-locate@3.0.0: + dependencies: + p-limit: 2.3.0 + p-locate@5.0.0: dependencies: p-limit: 3.1.0 @@ -18224,6 +18414,8 @@ snapshots: dependencies: p-finally: 1.0.0 + p-try@2.2.0: {} + package-json@8.1.1: dependencies: got: 12.6.1 @@ -18296,6 +18488,8 @@ snapshots: path-data-parser@0.1.0: {} + path-exists@3.0.0: {} + path-exists@4.0.0: {} path-exists@5.0.0: {} @@ -18366,10 +18560,16 @@ snapshots: pify@3.0.0: {} + pify@4.0.1: {} + pirates@4.0.7: {} pkce-challenge@5.0.1: {} + pkg-dir@3.0.0: + dependencies: + find-up: 3.0.0 + pkg-dir@7.0.0: dependencies: find-up: 6.3.0 @@ -19156,6 +19356,14 @@ snapshots: realpath-missing@1.1.0: {} + recast@0.23.11: + dependencies: + ast-types: 0.16.1 + esprima: 4.0.1 + source-map: 0.6.1 + tiny-invariant: 1.3.3 + tslib: 2.8.1 + rechoir@0.6.2: dependencies: resolve: 1.22.11 @@ -20168,6 +20376,8 @@ snapshots: dependencies: os-tmpdir: 1.0.2 + tmp@0.2.5: {} + to-regex-range@5.0.1: dependencies: is-number: 7.0.0 diff --git a/scripts/sync-api-sidebar.mjs b/scripts/sync-api-sidebar.mjs new file mode 100644 index 00000000..91dec82b --- /dev/null +++ b/scripts/sync-api-sidebar.mjs @@ -0,0 +1,228 @@ +#!/usr/bin/env node + +/** + * Validates and auto-fixes sidebars.ts against generated .api.mdx files. + * + * Usage: + * node scripts/sync-api-sidebar.mjs --check CI gate: exits 1 if any .api.mdx + * file is missing from sidebars.ts + * node scripts/sync-api-sidebar.mjs --fix Inserts missing entries into + * sidebars.ts using the frontmatter + * from each .api.mdx file + * + * Files excluded from scanning: + * - overview.mdx / *-overview.mdx (category overview pages, manually managed) + * - *.info.mdx (API info pages generated by openapi-docs) + * - *.tag.mdx (tag pages generated by openapi-docs) + */ + +import fs from 'fs'; +import path from 'path'; +import { fileURLToPath } from 'url'; +import jscodeshift from 'jscodeshift'; +import * as prettier from 'prettier'; + +const j = jscodeshift.withParser('tsx'); + +const __dirname = path.dirname(fileURLToPath(import.meta.url)); +const REPO_ROOT = path.resolve(__dirname, '..'); +const DOCS_ROOT = path.join(REPO_ROOT, 'docs'); +const SIDEBARS_PATH = path.join(REPO_ROOT, 'sidebars.ts'); + +const API_DIRS = [ + path.join(DOCS_ROOT, 'api/client-api'), + path.join(DOCS_ROOT, 'api/indexing-api'), +]; + +function shouldSkip(filename) { + return ( + filename === 'overview.mdx' || + filename.endsWith('-overview.mdx') || + filename.endsWith('.info.mdx') || + filename.endsWith('.tag.mdx') + ); +} + +function parseFrontmatterField(yaml, name) { + const re = new RegExp(`^${name}:\\s*"?([^"\\n]+?)"?\\s*$`, 'm'); + const m = yaml.match(re); + return m ? m[1] : null; +} + +function parseFrontmatter(content) { + const match = content.match(/^---\n([\s\S]*?)\n---/); + if (!match) return null; + const yaml = match[1]; + + const sidebarLabel = + parseFrontmatterField(yaml, 'sidebar_label') ?? + parseFrontmatterField(yaml, 'title'); + const sidebarClassName = parseFrontmatterField(yaml, 'sidebar_class_name'); + + // "post api-method" → "api-method post" (sidebar uses base-class-first order) + const className = sidebarClassName + ? sidebarClassName.split(' ').reverse().join(' ') + : null; + + return { label: sidebarLabel, className }; +} + +function collectApiDocs(dir) { + const results = []; + if (!fs.existsSync(dir)) return results; + + for (const entry of fs.readdirSync(dir, { withFileTypes: true })) { + if (entry.isDirectory()) { + results.push(...collectApiDocs(path.join(dir, entry.name))); + } else if (entry.name.endsWith('.api.mdx') && !shouldSkip(entry.name)) { + const fullPath = path.join(dir, entry.name); + const docId = path.relative(DOCS_ROOT, fullPath).replace(/\.api\.mdx$/, ''); + const content = fs.readFileSync(fullPath, 'utf8'); + const frontmatter = parseFrontmatter(content); + if (frontmatter) { + results.push({ docId, filePath: fullPath, ...frontmatter }); + } else { + console.warn(`Warning: could not parse frontmatter in ${fullPath}`); + } + } + } + return results; +} + +function isOrphaned(docId) { + const base = path.join(DOCS_ROOT, docId); + return ( + !fs.existsSync(base + '.api.mdx') && + !fs.existsSync(base + '.mdx') && + !fs.existsSync(base + '-overview.mdx') + ); +} + +function extractSidebarIds(root) { + const ids = new Set(); + root + .find(j.ObjectProperty, { key: { name: 'id' } }) + .filter((p) => { + const v = p.node.value; + return v.type === 'StringLiteral' && v.value.startsWith('api/'); + }) + .forEach((p) => ids.add(p.node.value.value)); + return ids; +} + +function makeEntryNode(docId, label, className) { + const props = [ + j.objectProperty(j.identifier('type'), j.stringLiteral('doc')), + j.objectProperty(j.identifier('id'), j.stringLiteral(docId)), + j.objectProperty(j.identifier('label'), j.stringLiteral(label)), + ]; + if (className) { + props.push(j.objectProperty(j.identifier('className'), j.stringLiteral(className))); + } + return j.objectExpression(props); +} + +function insertEntry(root, doc) { + const segments = doc.docId.split('/'); + const overviewId = segments.slice(0, -1).join('/') + '/overview'; + + let inserted = false; + + root + .find(j.ObjectProperty, { key: { name: 'items' } }) + .filter((p) => { + const arr = p.node.value; + if (arr.type !== 'ArrayExpression') return false; + return arr.elements.some((el) => { + if (!el || el.type !== 'ObjectExpression') return false; + return el.properties.some( + (prop) => + prop.type === 'ObjectProperty' && + prop.key?.name === 'id' && + prop.value?.type === 'StringLiteral' && + prop.value?.value === overviewId + ); + }); + }) + .forEach((p) => { + p.node.value.elements.push(makeEntryNode(doc.docId, doc.label, doc.className)); + inserted = true; + }); + + return inserted; +} + +const mode = process.argv[2]; +if (mode !== '--check' && mode !== '--fix') { + console.error('Usage: node scripts/sync-api-sidebar.mjs --check | --fix'); + process.exit(1); +} + +const allDocs = API_DIRS.flatMap(collectApiDocs); +const sidebarSource = fs.readFileSync(SIDEBARS_PATH, 'utf8'); +const root = j(sidebarSource); +const existingIds = extractSidebarIds(root); +const missing = allDocs.filter((d) => !existingIds.has(d.docId)); +const orphaned = [...existingIds].filter(isOrphaned); + +if (mode === '--check') { + if (orphaned.length > 0) { + console.warn( + `Warning: ${orphaned.length} sidebar ID(s) have no corresponding .api.mdx file (investigate or clean up manually):` + ); + for (const id of orphaned) { + console.warn(` - ${id}`); + } + console.warn(''); + } + + if (missing.length === 0) { + console.log('✓ sidebars.ts is complete — no missing API entries.'); + process.exit(0); + } + + console.error(`✗ ${missing.length} .api.mdx file(s) are missing from sidebars.ts:\n`); + for (const doc of missing) { + console.error(` - ${doc.docId}`); + } + console.error('\nRun `node scripts/sync-api-sidebar.mjs --fix` to auto-insert them.'); + process.exit(1); +} + +if (missing.length === 0) { + console.log('✓ sidebars.ts is already complete — nothing to do.'); + process.exit(0); +} + +const warnings = []; + +for (const doc of missing) { + const ok = insertEntry(root, doc); + if (!ok) { + warnings.push( + `Could not find sidebar category for '${doc.docId.split('/').slice(0, -1).join('/')}/overview' — add '${doc.docId}' manually` + ); + } +} + +const transformed = root.toSource(); +const prettierConfig = await prettier.resolveConfig(SIDEBARS_PATH); +const formatted = await prettier.format(transformed, { + ...prettierConfig, + filepath: SIDEBARS_PATH, +}); +fs.writeFileSync(SIDEBARS_PATH, formatted, 'utf8'); + +console.log(`✓ Inserted ${missing.length - warnings.length} entry(ies) into sidebars.ts:`); +for (const doc of missing) { + console.log(` + ${doc.docId} "${doc.label}"`); +} + +if (warnings.length > 0) { + console.warn('\n⚠ The following entries could not be auto-inserted (new category?):'); + for (const w of warnings) { + console.warn(` ! ${w}`); + } + console.warn('\nAdd them manually to sidebars.ts.'); + process.exit(1); +} From 525f1a2d9b4d667529d18dd2f9283de6767122f6 Mon Sep 17 00:00:00 2001 From: Steve Calvert Date: Fri, 27 Feb 2026 10:32:02 -0800 Subject: [PATCH 2/4] fix(sidebar): add missing client API entries via sidebar:fix Runs pnpm sidebar:fix to insert 5 entries that were present as generated .api.mdx files but missing from sidebars.ts: - api/client-api/authentication/checkdatasourceauth - api/client-api/governance/createfindingsexport - api/client-api/governance/deletefindingsexport - api/client-api/governance/downloadfindingsexport - api/client-api/governance/listfindingsexports Co-Authored-By: Claude Sonnet 4.6 (1M context) --- sidebars.ts | 30 ++++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/sidebars.ts b/sidebars.ts index bb6e3628..c67c1d4f 100644 --- a/sidebars.ts +++ b/sidebars.ts @@ -614,6 +614,12 @@ const baseSidebars: SidebarsConfig = { label: 'Create authentication token', className: 'api-method post', }, + { + type: 'doc', + id: 'api/client-api/authentication/checkdatasourceauth', + label: 'Check datasource authorization', + className: 'api-method post', + }, ], }, { @@ -867,6 +873,30 @@ const baseSidebars: SidebarsConfig = { label: 'Hide or unhide docs', className: 'api-method post', }, + { + type: 'doc', + id: 'api/client-api/governance/createfindingsexport', + label: 'Creates findings export', + className: 'api-method post', + }, + { + type: 'doc', + id: 'api/client-api/governance/deletefindingsexport', + label: 'Deletes findings export', + className: 'api-method delete', + }, + { + type: 'doc', + id: 'api/client-api/governance/downloadfindingsexport', + label: 'Downloads findings export', + className: 'api-method get', + }, + { + type: 'doc', + id: 'api/client-api/governance/listfindingsexports', + label: 'Lists findings exports', + className: 'api-method get', + }, ], }, { From 3948813f24d2e4d7dc949bcb15f83c95116efdef Mon Sep 17 00:00:00 2001 From: Steve Calvert Date: Fri, 27 Feb 2026 11:07:50 -0800 Subject: [PATCH 3/4] style: apply prettier formatting to sync-api-sidebar.mjs Co-Authored-By: Claude Sonnet 4.6 (1M context) --- scripts/sync-api-sidebar.mjs | 34 ++++++++++++++++++++++++---------- 1 file changed, 24 insertions(+), 10 deletions(-) diff --git a/scripts/sync-api-sidebar.mjs b/scripts/sync-api-sidebar.mjs index 91dec82b..2c15472d 100644 --- a/scripts/sync-api-sidebar.mjs +++ b/scripts/sync-api-sidebar.mjs @@ -76,7 +76,9 @@ function collectApiDocs(dir) { results.push(...collectApiDocs(path.join(dir, entry.name))); } else if (entry.name.endsWith('.api.mdx') && !shouldSkip(entry.name)) { const fullPath = path.join(dir, entry.name); - const docId = path.relative(DOCS_ROOT, fullPath).replace(/\.api\.mdx$/, ''); + const docId = path + .relative(DOCS_ROOT, fullPath) + .replace(/\.api\.mdx$/, ''); const content = fs.readFileSync(fullPath, 'utf8'); const frontmatter = parseFrontmatter(content); if (frontmatter) { @@ -117,7 +119,9 @@ function makeEntryNode(docId, label, className) { j.objectProperty(j.identifier('label'), j.stringLiteral(label)), ]; if (className) { - props.push(j.objectProperty(j.identifier('className'), j.stringLiteral(className))); + props.push( + j.objectProperty(j.identifier('className'), j.stringLiteral(className)), + ); } return j.objectExpression(props); } @@ -140,12 +144,14 @@ function insertEntry(root, doc) { prop.type === 'ObjectProperty' && prop.key?.name === 'id' && prop.value?.type === 'StringLiteral' && - prop.value?.value === overviewId + prop.value?.value === overviewId, ); }); }) .forEach((p) => { - p.node.value.elements.push(makeEntryNode(doc.docId, doc.label, doc.className)); + p.node.value.elements.push( + makeEntryNode(doc.docId, doc.label, doc.className), + ); inserted = true; }); @@ -168,7 +174,7 @@ const orphaned = [...existingIds].filter(isOrphaned); if (mode === '--check') { if (orphaned.length > 0) { console.warn( - `Warning: ${orphaned.length} sidebar ID(s) have no corresponding .api.mdx file (investigate or clean up manually):` + `Warning: ${orphaned.length} sidebar ID(s) have no corresponding .api.mdx file (investigate or clean up manually):`, ); for (const id of orphaned) { console.warn(` - ${id}`); @@ -181,11 +187,15 @@ if (mode === '--check') { process.exit(0); } - console.error(`✗ ${missing.length} .api.mdx file(s) are missing from sidebars.ts:\n`); + console.error( + `✗ ${missing.length} .api.mdx file(s) are missing from sidebars.ts:\n`, + ); for (const doc of missing) { console.error(` - ${doc.docId}`); } - console.error('\nRun `node scripts/sync-api-sidebar.mjs --fix` to auto-insert them.'); + console.error( + '\nRun `node scripts/sync-api-sidebar.mjs --fix` to auto-insert them.', + ); process.exit(1); } @@ -200,7 +210,7 @@ for (const doc of missing) { const ok = insertEntry(root, doc); if (!ok) { warnings.push( - `Could not find sidebar category for '${doc.docId.split('/').slice(0, -1).join('/')}/overview' — add '${doc.docId}' manually` + `Could not find sidebar category for '${doc.docId.split('/').slice(0, -1).join('/')}/overview' — add '${doc.docId}' manually`, ); } } @@ -213,13 +223,17 @@ const formatted = await prettier.format(transformed, { }); fs.writeFileSync(SIDEBARS_PATH, formatted, 'utf8'); -console.log(`✓ Inserted ${missing.length - warnings.length} entry(ies) into sidebars.ts:`); +console.log( + `✓ Inserted ${missing.length - warnings.length} entry(ies) into sidebars.ts:`, +); for (const doc of missing) { console.log(` + ${doc.docId} "${doc.label}"`); } if (warnings.length > 0) { - console.warn('\n⚠ The following entries could not be auto-inserted (new category?):'); + console.warn( + '\n⚠ The following entries could not be auto-inserted (new category?):', + ); for (const w of warnings) { console.warn(` ! ${w}`); } From 83c0555851434550efcf22dde12cb0a0e5868db8 Mon Sep 17 00:00:00 2001 From: Steve Calvert Date: Fri, 27 Feb 2026 11:22:36 -0800 Subject: [PATCH 4/4] refactor: use node:util parseArgs for CLI argument parsing Co-Authored-By: Claude Sonnet 4.6 (1M context) --- scripts/sync-api-sidebar.mjs | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/scripts/sync-api-sidebar.mjs b/scripts/sync-api-sidebar.mjs index 2c15472d..be8e3089 100644 --- a/scripts/sync-api-sidebar.mjs +++ b/scripts/sync-api-sidebar.mjs @@ -19,6 +19,7 @@ import fs from 'fs'; import path from 'path'; import { fileURLToPath } from 'url'; +import { parseArgs } from 'node:util'; import jscodeshift from 'jscodeshift'; import * as prettier from 'prettier'; @@ -158,8 +159,14 @@ function insertEntry(root, doc) { return inserted; } -const mode = process.argv[2]; -if (mode !== '--check' && mode !== '--fix') { +const { values: args } = parseArgs({ + options: { + check: { type: 'boolean', default: false }, + fix: { type: 'boolean', default: false }, + }, +}); + +if (!args.check && !args.fix) { console.error('Usage: node scripts/sync-api-sidebar.mjs --check | --fix'); process.exit(1); } @@ -171,7 +178,7 @@ const existingIds = extractSidebarIds(root); const missing = allDocs.filter((d) => !existingIds.has(d.docId)); const orphaned = [...existingIds].filter(isOrphaned); -if (mode === '--check') { +if (args.check) { if (orphaned.length > 0) { console.warn( `Warning: ${orphaned.length} sidebar ID(s) have no corresponding .api.mdx file (investigate or clean up manually):`,