diff --git a/extensions/numpy-documentation-search/CHANGELOG.md b/extensions/numpy-documentation-search/CHANGELOG.md index e01d9343138..257f610bdb2 100644 --- a/extensions/numpy-documentation-search/CHANGELOG.md +++ b/extensions/numpy-documentation-search/CHANGELOG.md @@ -1,13 +1,27 @@ # NumPy Documentation Search Changelog +## [1.3.0] - 2026-04-21 + +### Added + +- Add guided recovery when live access to `numpy.org` fails, including in-app setup steps for downloaded local docs +- Add configurable documentation source preferences for auto and local directory modes + +### Changed + +- Fall back to local documentation when remote inventory loading fails +- Load inline documentation previews from a configured local docs directory when network access is unavailable + ## [1.2.3] - 2025-10-09 ### Changed + - Refresh README to focus on a concise Raycast user overview with key in-app features ## [1.2.2] - 2025-10-07 ### Fixed + - Fix TypeScript compilation errors in strict mode - Updated Cheerio type annotations to use `Cheerio` with proper imports from domhandler - Added explicit type annotations for `.each()` callback parameters to resolve implicit any errors @@ -16,11 +30,13 @@ ## [1.2.1] - 2025-10-07 ### Fixed + - Remove ufunc assignment notation from function signatures - Universal function signatures no longer show `= ` suffix ## [1.2.0] - 2025-10-07 ### Added + - **Prefix Toggle Preference**: New user preference to display 'np.' instead of 'numpy.' prefix throughout the extension - Default setting uses 'np.' prefix (e.g., 'np.array' instead of 'numpy.array') - Applies to search results (title and subtitle), function signatures, and all action menus @@ -30,11 +46,13 @@ ## [1.1.1] - 2025-10-07 ### Fixed + - Remove hash fragments from documentation URLs - URLs now open to the clean page URL (e.g., `https://numpy.org/doc/stable/reference/generated/numpy.absolute.html`) instead of including the anchor fragment (e.g., `https://numpy.org/doc/stable/reference/generated/numpy.absolute.html#numpy.absolute`) ## [1.1.0] - 2025-10-07 ### Fixed + - Preserve inline code blocks in parameter descriptions - code references like `endpoint`, `False`, and `num + 1` now appear with proper backtick formatting instead of plain text ## [1.0.0] - 2025-10-07 diff --git a/extensions/numpy-documentation-search/README.md b/extensions/numpy-documentation-search/README.md index 013556d3c52..f4922d3fd21 100644 --- a/extensions/numpy-documentation-search/README.md +++ b/extensions/numpy-documentation-search/README.md @@ -8,3 +8,17 @@ A Raycast extension that lets you search the NumPy API reference and preview det - **Rich previews**: See inline call signatures, parameter tables, return values, and version notes without leaving Raycast. - **Doc deep links**: Open the exact section on numpy.org in your browser when you need the full documentation context. - **Copy-ready snippets**: Copy function signatures or doc URLs directly from the command for fast handoff into notebooks, scripts, or PRs. + +## Certificate-Restricted Networks + +If your work network blocks access to `https://numpy.org/doc/stable/`, the extension now shows a guided recovery state instead of only surfacing the fetch error. + +You can point the command at downloaded local documentation: + +1. Obtain a generated NumPy docs copy from [numpy/doc](https://github.com/numpy/doc) or from another machine that can reach `numpy.org`. +2. Extract the download locally and select the downloaded docs folder in Raycast. The extension will load the documentation from its `stable` subfolder. +3. Open the command preferences and set: + - `Documentation Source` to `Local Docs Directory`, or leave it on `Online` + - `Local Docs Directory` to the downloaded docs folder + +If no local docs are configured, the extension requires live access to `numpy.org`. Local HTML docs provide full inline previews without network access. diff --git a/extensions/numpy-documentation-search/package-lock.json b/extensions/numpy-documentation-search/package-lock.json index bb8c7f25b1a..22ab6cc2fdc 100644 --- a/extensions/numpy-documentation-search/package-lock.json +++ b/extensions/numpy-documentation-search/package-lock.json @@ -483,9 +483,9 @@ } }, "node_modules/@eslint/config-array/node_modules/brace-expansion": { - "version": "1.1.13", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.13.tgz", - "integrity": "sha512-9ZLprWS6EENmhEOpjCYW2c8VkmOvckIJZfkr7rBW6dObmfgJ/L1GpSYW5Hpo9lDz4D1+n0Ckz8rU7FwHDQiG/w==", + "version": "1.1.12", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", + "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", "dev": true, "license": "MIT", "dependencies": { @@ -494,9 +494,9 @@ } }, "node_modules/@eslint/config-array/node_modules/minimatch": { - "version": "3.1.5", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", - "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", "dev": true, "license": "ISC", "dependencies": { @@ -557,9 +557,9 @@ } }, "node_modules/@eslint/eslintrc/node_modules/brace-expansion": { - "version": "1.1.13", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.13.tgz", - "integrity": "sha512-9ZLprWS6EENmhEOpjCYW2c8VkmOvckIJZfkr7rBW6dObmfgJ/L1GpSYW5Hpo9lDz4D1+n0Ckz8rU7FwHDQiG/w==", + "version": "1.1.12", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", + "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", "dev": true, "license": "MIT", "dependencies": { @@ -581,9 +581,9 @@ } }, "node_modules/@eslint/eslintrc/node_modules/minimatch": { - "version": "3.1.5", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", - "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", "dev": true, "license": "ISC", "dependencies": { @@ -1234,9 +1234,9 @@ } }, "node_modules/@rollup/rollup-android-arm-eabi": { - "version": "4.60.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.60.1.tgz", - "integrity": "sha512-d6FinEBLdIiK+1uACUttJKfgZREXrF0Qc2SmLII7W2AD8FfiZ9Wjd+rD/iRuf5s5dWrr1GgwXCvPqOuDquOowA==", + "version": "4.52.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.52.4.tgz", + "integrity": "sha512-BTm2qKNnWIQ5auf4deoetINJm2JzvihvGb9R6K/ETwKLql/Bb3Eg2H1FBp1gUb4YGbydMA3jcmQTR73q7J+GAA==", "cpu": [ "arm" ], @@ -1248,9 +1248,9 @@ ] }, "node_modules/@rollup/rollup-android-arm64": { - "version": "4.60.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.60.1.tgz", - "integrity": "sha512-YjG/EwIDvvYI1YvYbHvDz/BYHtkY4ygUIXHnTdLhG+hKIQFBiosfWiACWortsKPKU/+dUwQQCKQM3qrDe8c9BA==", + "version": "4.52.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.52.4.tgz", + "integrity": "sha512-P9LDQiC5vpgGFgz7GSM6dKPCiqR3XYN1WwJKA4/BUVDjHpYsf3iBEmVz62uyq20NGYbiGPR5cNHI7T1HqxNs2w==", "cpu": [ "arm64" ], @@ -1262,9 +1262,9 @@ ] }, "node_modules/@rollup/rollup-darwin-arm64": { - "version": "4.60.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.60.1.tgz", - "integrity": "sha512-mjCpF7GmkRtSJwon+Rq1N8+pI+8l7w5g9Z3vWj4T7abguC4Czwi3Yu/pFaLvA3TTeMVjnu3ctigusqWUfjZzvw==", + "version": "4.52.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.52.4.tgz", + "integrity": "sha512-QRWSW+bVccAvZF6cbNZBJwAehmvG9NwfWHwMy4GbWi/BQIA/laTIktebT2ipVjNncqE6GLPxOok5hsECgAxGZg==", "cpu": [ "arm64" ], @@ -1276,9 +1276,9 @@ ] }, "node_modules/@rollup/rollup-darwin-x64": { - "version": "4.60.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.60.1.tgz", - "integrity": "sha512-haZ7hJ1JT4e9hqkoT9R/19XW2QKqjfJVv+i5AGg57S+nLk9lQnJ1F/eZloRO3o9Scy9CM3wQ9l+dkXtcBgN5Ew==", + "version": "4.52.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.52.4.tgz", + "integrity": "sha512-hZgP05pResAkRJxL1b+7yxCnXPGsXU0fG9Yfd6dUaoGk+FhdPKCJ5L1Sumyxn8kvw8Qi5PvQ8ulenUbRjzeCTw==", "cpu": [ "x64" ], @@ -1290,9 +1290,9 @@ ] }, "node_modules/@rollup/rollup-freebsd-arm64": { - "version": "4.60.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.60.1.tgz", - "integrity": "sha512-czw90wpQq3ZsAVBlinZjAYTKduOjTywlG7fEeWKUA7oCmpA8xdTkxZZlwNJKWqILlq0wehoZcJYfBvOyhPTQ6w==", + "version": "4.52.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.52.4.tgz", + "integrity": "sha512-xmc30VshuBNUd58Xk4TKAEcRZHaXlV+tCxIXELiE9sQuK3kG8ZFgSPi57UBJt8/ogfhAF5Oz4ZSUBN77weM+mQ==", "cpu": [ "arm64" ], @@ -1304,9 +1304,9 @@ ] }, "node_modules/@rollup/rollup-freebsd-x64": { - "version": "4.60.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.60.1.tgz", - "integrity": "sha512-KVB2rqsxTHuBtfOeySEyzEOB7ltlB/ux38iu2rBQzkjbwRVlkhAGIEDiiYnO2kFOkJp+Z7pUXKyrRRFuFUKt+g==", + "version": "4.52.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.52.4.tgz", + "integrity": "sha512-WdSLpZFjOEqNZGmHflxyifolwAiZmDQzuOzIq9L27ButpCVpD7KzTRtEG1I0wMPFyiyUdOO+4t8GvrnBLQSwpw==", "cpu": [ "x64" ], @@ -1318,9 +1318,9 @@ ] }, "node_modules/@rollup/rollup-linux-arm-gnueabihf": { - "version": "4.60.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.60.1.tgz", - "integrity": "sha512-L+34Qqil+v5uC0zEubW7uByo78WOCIrBvci69E7sFASRl0X7b/MB6Cqd1lky/CtcSVTydWa2WZwFuWexjS5o6g==", + "version": "4.52.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.52.4.tgz", + "integrity": "sha512-xRiOu9Of1FZ4SxVbB0iEDXc4ddIcjCv2aj03dmW8UrZIW7aIQ9jVJdLBIhxBI+MaTnGAKyvMwPwQnoOEvP7FgQ==", "cpu": [ "arm" ], @@ -1332,9 +1332,9 @@ ] }, "node_modules/@rollup/rollup-linux-arm-musleabihf": { - "version": "4.60.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.60.1.tgz", - "integrity": "sha512-n83O8rt4v34hgFzlkb1ycniJh7IR5RCIqt6mz1VRJD6pmhRi0CXdmfnLu9dIUS6buzh60IvACM842Ffb3xd6Gg==", + "version": "4.52.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.52.4.tgz", + "integrity": "sha512-FbhM2p9TJAmEIEhIgzR4soUcsW49e9veAQCziwbR+XWB2zqJ12b4i/+hel9yLiD8pLncDH4fKIPIbt5238341Q==", "cpu": [ "arm" ], @@ -1346,9 +1346,9 @@ ] }, "node_modules/@rollup/rollup-linux-arm64-gnu": { - "version": "4.60.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.60.1.tgz", - "integrity": "sha512-Nql7sTeAzhTAja3QXeAI48+/+GjBJ+QmAH13snn0AJSNL50JsDqotyudHyMbO2RbJkskbMbFJfIJKWA6R1LCJQ==", + "version": "4.52.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.52.4.tgz", + "integrity": "sha512-4n4gVwhPHR9q/g8lKCyz0yuaD0MvDf7dV4f9tHt0C73Mp8h38UCtSCSE6R9iBlTbXlmA8CjpsZoujhszefqueg==", "cpu": [ "arm64" ], @@ -1360,9 +1360,9 @@ ] }, "node_modules/@rollup/rollup-linux-arm64-musl": { - "version": "4.60.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.60.1.tgz", - "integrity": "sha512-+pUymDhd0ys9GcKZPPWlFiZ67sTWV5UU6zOJat02M1+PiuSGDziyRuI/pPue3hoUwm2uGfxdL+trT6Z9rxnlMA==", + "version": "4.52.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.52.4.tgz", + "integrity": "sha512-u0n17nGA0nvi/11gcZKsjkLj1QIpAuPFQbR48Subo7SmZJnGxDpspyw2kbpuoQnyK+9pwf3pAoEXerJs/8Mi9g==", "cpu": [ "arm64" ], @@ -1374,23 +1374,9 @@ ] }, "node_modules/@rollup/rollup-linux-loong64-gnu": { - "version": "4.60.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.60.1.tgz", - "integrity": "sha512-VSvgvQeIcsEvY4bKDHEDWcpW4Yw7BtlKG1GUT4FzBUlEKQK0rWHYBqQt6Fm2taXS+1bXvJT6kICu5ZwqKCnvlQ==", - "cpu": [ - "loong64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-loong64-musl": { - "version": "4.60.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-musl/-/rollup-linux-loong64-musl-4.60.1.tgz", - "integrity": "sha512-4LqhUomJqwe641gsPp6xLfhqWMbQV04KtPp7/dIp0nzPxAkNY1AbwL5W0MQpcalLYk07vaW9Kp1PBhdpZYYcEw==", + "version": "4.52.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.52.4.tgz", + "integrity": "sha512-0G2c2lpYtbTuXo8KEJkDkClE/+/2AFPdPAbmaHoE870foRFs4pBrDehilMcrSScrN/fB/1HTaWO4bqw+ewBzMQ==", "cpu": [ "loong64" ], @@ -1402,23 +1388,9 @@ ] }, "node_modules/@rollup/rollup-linux-ppc64-gnu": { - "version": "4.60.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.60.1.tgz", - "integrity": "sha512-tLQQ9aPvkBxOc/EUT6j3pyeMD6Hb8QF2BTBnCQWP/uu1lhc9AIrIjKnLYMEroIz/JvtGYgI9dF3AxHZNaEH0rw==", - "cpu": [ - "ppc64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-ppc64-musl": { - "version": "4.60.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-musl/-/rollup-linux-ppc64-musl-4.60.1.tgz", - "integrity": "sha512-RMxFhJwc9fSXP6PqmAz4cbv3kAyvD1etJFjTx4ONqFP9DkTkXsAMU4v3Vyc5BgzC+anz7nS/9tp4obsKfqkDHg==", + "version": "4.52.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.52.4.tgz", + "integrity": "sha512-teSACug1GyZHmPDv14VNbvZFX779UqWTsd7KtTM9JIZRDI5NUwYSIS30kzI8m06gOPB//jtpqlhmraQ68b5X2g==", "cpu": [ "ppc64" ], @@ -1430,9 +1402,9 @@ ] }, "node_modules/@rollup/rollup-linux-riscv64-gnu": { - "version": "4.60.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.60.1.tgz", - "integrity": "sha512-QKgFl+Yc1eEk6MmOBfRHYF6lTxiiiV3/z/BRrbSiW2I7AFTXoBFvdMEyglohPj//2mZS4hDOqeB0H1ACh3sBbg==", + "version": "4.52.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.52.4.tgz", + "integrity": "sha512-/MOEW3aHjjs1p4Pw1Xk4+3egRevx8Ji9N6HUIA1Ifh8Q+cg9dremvFCUbOX2Zebz80BwJIgCBUemjqhU5XI5Eg==", "cpu": [ "riscv64" ], @@ -1444,9 +1416,9 @@ ] }, "node_modules/@rollup/rollup-linux-riscv64-musl": { - "version": "4.60.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.60.1.tgz", - "integrity": "sha512-RAjXjP/8c6ZtzatZcA1RaQr6O1TRhzC+adn8YZDnChliZHviqIjmvFwHcxi4JKPSDAt6Uhf/7vqcBzQJy0PDJg==", + "version": "4.52.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.52.4.tgz", + "integrity": "sha512-1HHmsRyh845QDpEWzOFtMCph5Ts+9+yllCrREuBR/vg2RogAQGGBRC8lDPrPOMnrdOJ+mt1WLMOC2Kao/UwcvA==", "cpu": [ "riscv64" ], @@ -1458,9 +1430,9 @@ ] }, "node_modules/@rollup/rollup-linux-s390x-gnu": { - "version": "4.60.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.60.1.tgz", - "integrity": "sha512-wcuocpaOlaL1COBYiA89O6yfjlp3RwKDeTIA0hM7OpmhR1Bjo9j31G1uQVpDlTvwxGn2nQs65fBFL5UFd76FcQ==", + "version": "4.52.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.52.4.tgz", + "integrity": "sha512-seoeZp4L/6D1MUyjWkOMRU6/iLmCU2EjbMTyAG4oIOs1/I82Y5lTeaxW0KBfkUdHAWN7j25bpkt0rjnOgAcQcA==", "cpu": [ "s390x" ], @@ -1472,9 +1444,9 @@ ] }, "node_modules/@rollup/rollup-linux-x64-gnu": { - "version": "4.60.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.60.1.tgz", - "integrity": "sha512-77PpsFQUCOiZR9+LQEFg9GClyfkNXj1MP6wRnzYs0EeWbPcHs02AXu4xuUbM1zhwn3wqaizle3AEYg5aeoohhg==", + "version": "4.52.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.52.4.tgz", + "integrity": "sha512-Wi6AXf0k0L7E2gteNsNHUs7UMwCIhsCTs6+tqQ5GPwVRWMaflqGec4Sd8n6+FNFDw9vGcReqk2KzBDhCa1DLYg==", "cpu": [ "x64" ], @@ -1486,9 +1458,9 @@ ] }, "node_modules/@rollup/rollup-linux-x64-musl": { - "version": "4.60.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.60.1.tgz", - "integrity": "sha512-5cIATbk5vynAjqqmyBjlciMJl1+R/CwX9oLk/EyiFXDWd95KpHdrOJT//rnUl4cUcskrd0jCCw3wpZnhIHdD9w==", + "version": "4.52.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.52.4.tgz", + "integrity": "sha512-dtBZYjDmCQ9hW+WgEkaffvRRCKm767wWhxsFW3Lw86VXz/uJRuD438/XvbZT//B96Vs8oTA8Q4A0AfHbrxP9zw==", "cpu": [ "x64" ], @@ -1499,24 +1471,10 @@ "linux" ] }, - "node_modules/@rollup/rollup-openbsd-x64": { - "version": "4.60.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-openbsd-x64/-/rollup-openbsd-x64-4.60.1.tgz", - "integrity": "sha512-cl0w09WsCi17mcmWqqglez9Gk8isgeWvoUZ3WiJFYSR3zjBQc2J5/ihSjpl+VLjPqjQ/1hJRcqBfLjssREQILw==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "openbsd" - ] - }, "node_modules/@rollup/rollup-openharmony-arm64": { - "version": "4.60.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.60.1.tgz", - "integrity": "sha512-4Cv23ZrONRbNtbZa37mLSueXUCtN7MXccChtKpUnQNgF010rjrjfHx3QxkS2PI7LqGT5xXyYs1a7LbzAwT0iCA==", + "version": "4.52.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.52.4.tgz", + "integrity": "sha512-1ox+GqgRWqaB1RnyZXL8PD6E5f7YyRUJYnCqKpNzxzP0TkaUh112NDrR9Tt+C8rJ4x5G9Mk8PQR3o7Ku2RKqKA==", "cpu": [ "arm64" ], @@ -1528,9 +1486,9 @@ ] }, "node_modules/@rollup/rollup-win32-arm64-msvc": { - "version": "4.60.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.60.1.tgz", - "integrity": "sha512-i1okWYkA4FJICtr7KpYzFpRTHgy5jdDbZiWfvny21iIKky5YExiDXP+zbXzm3dUcFpkEeYNHgQ5fuG236JPq0g==", + "version": "4.52.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.52.4.tgz", + "integrity": "sha512-8GKr640PdFNXwzIE0IrkMWUNUomILLkfeHjXBi/nUvFlpZP+FA8BKGKpacjW6OUUHaNI6sUURxR2U2g78FOHWQ==", "cpu": [ "arm64" ], @@ -1542,9 +1500,9 @@ ] }, "node_modules/@rollup/rollup-win32-ia32-msvc": { - "version": "4.60.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.60.1.tgz", - "integrity": "sha512-u09m3CuwLzShA0EYKMNiFgcjjzwqtUMLmuCJLeZWjjOYA3IT2Di09KaxGBTP9xVztWyIWjVdsB2E9goMjZvTQg==", + "version": "4.52.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.52.4.tgz", + "integrity": "sha512-AIy/jdJ7WtJ/F6EcfOb2GjR9UweO0n43jNObQMb6oGxkYTfLcnN7vYYpG+CN3lLxrQkzWnMOoNSHTW54pgbVxw==", "cpu": [ "ia32" ], @@ -1556,9 +1514,9 @@ ] }, "node_modules/@rollup/rollup-win32-x64-gnu": { - "version": "4.60.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.60.1.tgz", - "integrity": "sha512-k+600V9Zl1CM7eZxJgMyTUzmrmhB/0XZnF4pRypKAlAgxmedUA+1v9R+XOFv56W4SlHEzfeMtzujLJD22Uz5zg==", + "version": "4.52.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.52.4.tgz", + "integrity": "sha512-UF9KfsH9yEam0UjTwAgdK0anlQ7c8/pWPU2yVjyWcF1I1thABt6WXE47cI71pGiZ8wGvxohBoLnxM04L/wj8mQ==", "cpu": [ "x64" ], @@ -1570,9 +1528,9 @@ ] }, "node_modules/@rollup/rollup-win32-x64-msvc": { - "version": "4.60.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.60.1.tgz", - "integrity": "sha512-lWMnixq/QzxyhTV6NjQJ4SFo1J6PvOX8vUx5Wb4bBPsEb+8xZ89Bz6kOXpfXj9ak9AHTQVQzlgzBEc1SyM27xQ==", + "version": "4.52.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.52.4.tgz", + "integrity": "sha512-bf9PtUa0u8IXDVxzRToFQKsNCRz9qLYfR/MpECxl4mRoWYjAeFjgxj1XdZr2M/GNVpT05p+LgQOHopYDlUu6/w==", "cpu": [ "x64" ], @@ -2027,9 +1985,9 @@ } }, "node_modules/ajv": { - "version": "6.14.0", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.14.0.tgz", - "integrity": "sha512-IWrosm/yrn43eiKqkfkHis7QioDleaXQHdDVPKg0FSwwd/DuvyX79TZnFOnYpB7dcsFAMmtFztZuXPDvSePkFw==", + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", "dev": true, "license": "MIT", "dependencies": { @@ -2127,9 +2085,9 @@ "license": "ISC" }, "node_modules/brace-expansion": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.3.tgz", - "integrity": "sha512-MCV/fYJEbqx68aE58kv2cA/kiky1G8vux3OR6/jbS+jIMe/6fJWa0DTzJU7dqijOWYwHi1t29FlfYI9uytqlpA==", + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", + "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", "license": "MIT", "dependencies": { "balanced-match": "^1.0.0" @@ -2736,9 +2694,9 @@ } }, "node_modules/eslint/node_modules/brace-expansion": { - "version": "1.1.13", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.13.tgz", - "integrity": "sha512-9ZLprWS6EENmhEOpjCYW2c8VkmOvckIJZfkr7rBW6dObmfgJ/L1GpSYW5Hpo9lDz4D1+n0Ckz8rU7FwHDQiG/w==", + "version": "1.1.12", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", + "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", "dev": true, "license": "MIT", "dependencies": { @@ -2760,9 +2718,9 @@ } }, "node_modules/eslint/node_modules/minimatch": { - "version": "3.1.5", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", - "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", "dev": true, "license": "ISC", "dependencies": { @@ -2964,9 +2922,9 @@ } }, "node_modules/filelist/node_modules/minimatch": { - "version": "5.1.9", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.9.tgz", - "integrity": "sha512-7o1wEA2RyMP7Iu7GNba9vc0RWWGACJOCZBJX2GJWip0ikV+wcOsgVuY9uE8CPiyQhkGFSlhuSkZPavN7u1c2Fw==", + "version": "5.1.6", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz", + "integrity": "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==", "license": "ISC", "dependencies": { "brace-expansion": "^2.0.1" @@ -3020,9 +2978,9 @@ } }, "node_modules/flatted": { - "version": "3.4.2", - "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.4.2.tgz", - "integrity": "sha512-PjDse7RzhcPkIJwy5t7KPWQSZ9cAbzQXcafsetQoD7sOJRQlGikNbx7yZp2OotDnJyrDcbyRq3Ttb18iYOqkxA==", + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.3.3.tgz", + "integrity": "sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg==", "dev": true, "license": "ISC" }, @@ -3286,9 +3244,9 @@ "license": "MIT" }, "node_modules/js-yaml": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.1.tgz", - "integrity": "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==", + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", + "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", "dev": true, "license": "MIT", "dependencies": { @@ -3420,12 +3378,12 @@ } }, "node_modules/minimatch": { - "version": "9.0.9", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.9.tgz", - "integrity": "sha512-OBwBN9AL4dqmETlpS2zasx+vTeWclWzkblfZk7KTA5j3jeOONz/tRCnZomUyvNg83wL5Zv9Ss6HMJXAgL8R2Yg==", + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", + "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", "license": "ISC", "dependencies": { - "brace-expansion": "^2.0.2" + "brace-expansion": "^2.0.1" }, "engines": { "node": ">=16 || 14 >=14.17" @@ -3659,9 +3617,9 @@ "license": "ISC" }, "node_modules/picomatch": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.2.tgz", - "integrity": "sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA==", + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", "dev": true, "license": "MIT", "engines": { @@ -3788,9 +3746,9 @@ } }, "node_modules/rollup": { - "version": "4.60.1", - "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.60.1.tgz", - "integrity": "sha512-VmtB2rFU/GroZ4oL8+ZqXgSA38O6GR8KSIvWmEFv63pQ0G6KaBH9s07PO8XTXP4vI+3UJUEypOfjkGfmSBBR0w==", + "version": "4.52.4", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.52.4.tgz", + "integrity": "sha512-CLEVl+MnPAiKh5pl4dEWSyMTpuflgNQiLGhMv8ezD5W/qP8AKvmYpCOKRRNOh7oRKnauBZ4SyeYkMS+1VSyKwQ==", "dev": true, "license": "MIT", "dependencies": { @@ -3804,31 +3762,28 @@ "npm": ">=8.0.0" }, "optionalDependencies": { - "@rollup/rollup-android-arm-eabi": "4.60.1", - "@rollup/rollup-android-arm64": "4.60.1", - "@rollup/rollup-darwin-arm64": "4.60.1", - "@rollup/rollup-darwin-x64": "4.60.1", - "@rollup/rollup-freebsd-arm64": "4.60.1", - "@rollup/rollup-freebsd-x64": "4.60.1", - "@rollup/rollup-linux-arm-gnueabihf": "4.60.1", - "@rollup/rollup-linux-arm-musleabihf": "4.60.1", - "@rollup/rollup-linux-arm64-gnu": "4.60.1", - "@rollup/rollup-linux-arm64-musl": "4.60.1", - "@rollup/rollup-linux-loong64-gnu": "4.60.1", - "@rollup/rollup-linux-loong64-musl": "4.60.1", - "@rollup/rollup-linux-ppc64-gnu": "4.60.1", - "@rollup/rollup-linux-ppc64-musl": "4.60.1", - "@rollup/rollup-linux-riscv64-gnu": "4.60.1", - "@rollup/rollup-linux-riscv64-musl": "4.60.1", - "@rollup/rollup-linux-s390x-gnu": "4.60.1", - "@rollup/rollup-linux-x64-gnu": "4.60.1", - "@rollup/rollup-linux-x64-musl": "4.60.1", - "@rollup/rollup-openbsd-x64": "4.60.1", - "@rollup/rollup-openharmony-arm64": "4.60.1", - "@rollup/rollup-win32-arm64-msvc": "4.60.1", - "@rollup/rollup-win32-ia32-msvc": "4.60.1", - "@rollup/rollup-win32-x64-gnu": "4.60.1", - "@rollup/rollup-win32-x64-msvc": "4.60.1", + "@rollup/rollup-android-arm-eabi": "4.52.4", + "@rollup/rollup-android-arm64": "4.52.4", + "@rollup/rollup-darwin-arm64": "4.52.4", + "@rollup/rollup-darwin-x64": "4.52.4", + "@rollup/rollup-freebsd-arm64": "4.52.4", + "@rollup/rollup-freebsd-x64": "4.52.4", + "@rollup/rollup-linux-arm-gnueabihf": "4.52.4", + "@rollup/rollup-linux-arm-musleabihf": "4.52.4", + "@rollup/rollup-linux-arm64-gnu": "4.52.4", + "@rollup/rollup-linux-arm64-musl": "4.52.4", + "@rollup/rollup-linux-loong64-gnu": "4.52.4", + "@rollup/rollup-linux-ppc64-gnu": "4.52.4", + "@rollup/rollup-linux-riscv64-gnu": "4.52.4", + "@rollup/rollup-linux-riscv64-musl": "4.52.4", + "@rollup/rollup-linux-s390x-gnu": "4.52.4", + "@rollup/rollup-linux-x64-gnu": "4.52.4", + "@rollup/rollup-linux-x64-musl": "4.52.4", + "@rollup/rollup-openharmony-arm64": "4.52.4", + "@rollup/rollup-win32-arm64-msvc": "4.52.4", + "@rollup/rollup-win32-ia32-msvc": "4.52.4", + "@rollup/rollup-win32-x64-gnu": "4.52.4", + "@rollup/rollup-win32-x64-msvc": "4.52.4", "fsevents": "~2.3.2" } }, @@ -4070,9 +4025,9 @@ } }, "node_modules/tinyglobby/node_modules/picomatch": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz", - "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==", + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", + "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", "license": "MIT", "engines": { "node": ">=12" @@ -4207,9 +4162,9 @@ } }, "node_modules/undici": { - "version": "7.24.6", - "resolved": "https://registry.npmjs.org/undici/-/undici-7.24.6.tgz", - "integrity": "sha512-Xi4agocCbRzt0yYMZGMA6ApD7gvtUFaxm4ZmeacWI4cZxaF6C+8I8QfofC20NAePiB/IcvZmzkJ7XPa471AEtA==", + "version": "7.16.0", + "resolved": "https://registry.npmjs.org/undici/-/undici-7.16.0.tgz", + "integrity": "sha512-QEg3HPMll0o3t2ourKwOeUAZ159Kn9mx5pnzHRQO8+Wixmh88YdZRiIwat0iNzNNXn0yoEtXJqFpyW7eM8BV7g==", "license": "MIT", "engines": { "node": ">=20.18.1" @@ -4232,9 +4187,9 @@ } }, "node_modules/vite": { - "version": "7.2.7", - "resolved": "https://registry.npmjs.org/vite/-/vite-7.2.7.tgz", - "integrity": "sha512-ITcnkFeR3+fI8P1wMgItjGrR10170d8auB4EpMLPqmx6uxElH3a/hHGQabSHKdqd4FXWO1nFIp9rRn7JQ34ACQ==", + "version": "7.1.9", + "resolved": "https://registry.npmjs.org/vite/-/vite-7.1.9.tgz", + "integrity": "sha512-4nVGliEpxmhCL8DslSAUdxlB6+SMrhB0a1v5ijlh1xB1nEPuy1mxaHxysVucLHuWryAxLWg6a5ei+U4TLn/rFg==", "dev": true, "license": "MIT", "dependencies": { @@ -4348,9 +4303,9 @@ } }, "node_modules/vite/node_modules/picomatch": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz", - "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==", + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", + "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", "dev": true, "license": "MIT", "engines": { @@ -4434,9 +4389,9 @@ } }, "node_modules/vitest/node_modules/picomatch": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz", - "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==", + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", + "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", "dev": true, "license": "MIT", "engines": { diff --git a/extensions/numpy-documentation-search/package.json b/extensions/numpy-documentation-search/package.json index 88e37e3c3c3..a0ae950135f 100644 --- a/extensions/numpy-documentation-search/package.json +++ b/extensions/numpy-documentation-search/package.json @@ -1,7 +1,7 @@ { "$schema": "https://www.raycast.com/schemas/extension.json", "name": "numpy-documentation-search", - "title": "NumPy Documentation Search", + "title": "Numpy Documentation Search", "description": "Quickly search through official NumPy documentation", "icon": "extension-icon.png", "author": "FariaF22", @@ -18,13 +18,38 @@ "commands": [ { "name": "numpy-docs", - "title": "NumPy Docs", + "title": "Numpy Docs", "subtitle": "Search through Numpy documentation", "description": "Search NumPy's API reference and preview documentation inside Raycast.", "mode": "view" } ], "preferences": [ + { + "name": "documentationSourceMode", + "type": "dropdown", + "required": true, + "title": "Documentation Source", + "description": "Choose where the extension should load NumPy documentation from", + "default": "online", + "data": [ + { + "title": "Online", + "value": "online" + }, + { + "title": "Local Docs Directory", + "value": "local" + } + ] + }, + { + "name": "localDocsDirectory", + "type": "directory", + "required": false, + "title": "Local Docs Directory", + "description": "Only used when Documentation Source is Local Docs Directory. Select the downloaded NumPy docs folder and the extension will load files from its stable subfolder" + }, { "name": "useShortPrefix", "type": "checkbox", diff --git a/extensions/numpy-documentation-search/src/__tests__/docs-source.test.ts b/extensions/numpy-documentation-search/src/__tests__/docs-source.test.ts new file mode 100644 index 00000000000..220875ac323 --- /dev/null +++ b/extensions/numpy-documentation-search/src/__tests__/docs-source.test.ts @@ -0,0 +1,110 @@ +import { mkdtempSync, mkdirSync, readFileSync, rmSync, writeFileSync } from "node:fs"; +import { tmpdir } from "node:os"; +import path from "node:path"; +import { deflateSync } from "node:zlib"; +import { afterEach, describe, expect, it, vi } from "vitest"; +import { loadDocDetail, loadInventory } from "../lib/docs-source"; +import type { InventoryItem } from "../lib/inventory"; + +const linspaceItem: InventoryItem = { + id: "numpy.linspace", + name: "numpy.linspace", + shortName: "linspace", + role: "py:function", + url: "https://numpy.org/doc/stable/reference/generated/numpy.linspace.html", + docPath: "reference/generated/numpy.linspace.html#numpy.linspace", + displayName: "numpy.linspace", +}; + +const tempDirs: string[] = []; + +afterEach(() => { + for (const dir of tempDirs) { + rmSync(dir, { force: true, recursive: true }); + } + tempDirs.length = 0; +}); + +describe("loadInventory", () => { + it("falls back to local docs in online mode when remote loading fails", async () => { + const localDocsDirectory = createLocalDocsDirectory(); + const stableDir = path.join(localDocsDirectory, "stable"); + mkdirSync(stableDir, { recursive: true }); + writeFileSync(path.join(stableDir, "objects.inv"), buildInventoryFixture()); + + const result = await loadInventory( + { + localDocsDirectory, + mode: "online", + }, + { + fetchImpl: vi.fn(async () => { + throw new Error("certificate failure"); + }) as typeof fetch, + readBinaryFileImpl: async (filePath) => readFileSync(filePath), + readTextFileImpl: async (filePath) => readFileSync(filePath, "utf8"), + }, + ); + + expect(result.source).toBe("local"); + expect(result.remoteError?.message).toBe("certificate failure"); + expect(result.data.some((item) => item.id === "numpy.linspace")).toBe(true); + }); + + it("surfaces the remote error when online mode has no local docs fallback", async () => { + await expect( + loadInventory( + { + mode: "online", + }, + { + fetchImpl: vi.fn(async () => { + throw new Error("certificate failure"); + }) as typeof fetch, + readBinaryFileImpl: async (filePath) => readFileSync(filePath), + readTextFileImpl: async (filePath) => readFileSync(filePath, "utf8"), + }, + ), + ).rejects.toThrow("certificate failure"); + }); +}); + +describe("loadDocDetail", () => { + it("loads documentation detail from a local docs directory", async () => { + const localDocsDirectory = createLocalDocsDirectory(); + const referenceDir = path.join(localDocsDirectory, "stable/reference/generated"); + mkdirSync(referenceDir, { recursive: true }); + writeFileSync( + path.join(referenceDir, "numpy.linspace.html"), + readFileSync(path.join(process.cwd(), "src/__tests__/fixtures/numpy.linspace.html"), "utf8"), + ); + + const result = await loadDocDetail({ + inventorySource: "local", + item: linspaceItem, + localDocsDirectory, + mode: "local", + }); + + expect(result.source).toBe("local"); + expect(result.data?.signature).toContain("numpy.linspace"); + }); +}); + +function createLocalDocsDirectory(): string { + const dir = mkdtempSync(path.join(tmpdir(), "numpy-docs-")); + tempDirs.push(dir); + return dir; +} + +function buildInventoryFixture(): Buffer { + const header = [ + "# Sphinx inventory version 2", + "# Project: NumPy", + "# Version: stable", + "# The remainder of this file is compressed using zlib.", + ].join("\n"); + const body = "numpy.linspace py:function 1 reference/generated/numpy.linspace.html#numpy.linspace -"; + + return Buffer.concat([Buffer.from(`${header}\n`, "utf8"), deflateSync(`${body}\n`)]); +} diff --git a/extensions/numpy-documentation-search/src/hooks/useDocDetail.ts b/extensions/numpy-documentation-search/src/hooks/useDocDetail.ts index b3e280c471b..7d274d2b8b0 100644 --- a/extensions/numpy-documentation-search/src/hooks/useDocDetail.ts +++ b/extensions/numpy-documentation-search/src/hooks/useDocDetail.ts @@ -1,27 +1,94 @@ -import { useFetch } from "@raycast/utils"; +import { useCallback, useEffect, useState } from "react"; +import { loadDocDetail, type DocumentationSourceMode, type ResolvedDocumentationSource } from "../lib/docs-source"; +import type { DocDetail } from "../lib/doc-detail"; import type { InventoryItem } from "../lib/inventory"; -import { parseDocDetail, type DocDetail } from "../lib/doc-detail"; interface UseDocDetailResult { data?: DocDetail; isLoading: boolean; error?: Error; revalidate: () => void; + source?: ResolvedDocumentationSource; } -export function useDocDetail(item: InventoryItem | undefined): UseDocDetailResult { - const { data, isLoading, error, revalidate } = useFetch(item?.url ?? "", { - execute: Boolean(item), - keepPreviousData: true, - parseResponse: async (response) => { - if (!response.ok) { - throw new Error(`Failed to load documentation: ${response.status} ${response.statusText}`); - } - - const html = await response.text(); - return parseDocDetail(html, item!); - }, +interface UseDocDetailOptions { + inventorySource?: ResolvedDocumentationSource; + item: InventoryItem | undefined; + localDocsDirectory?: string; + mode: DocumentationSourceMode; +} + +export function useDocDetail(options: UseDocDetailOptions): UseDocDetailResult { + const [reloadToken, setReloadToken] = useState(0); + + const revalidate = useCallback(() => { + setReloadToken((token) => token + 1); + }, []); + + const [state, setState] = useState({ + isLoading: false, + revalidate, }); - return { data, isLoading, error, revalidate }; + useEffect(() => { + let cancelled = false; + + if (!options.item || !options.inventorySource) { + setState({ + data: undefined, + error: undefined, + isLoading: false, + revalidate, + source: undefined, + }); + return () => { + cancelled = true; + }; + } + + setState((current) => ({ + ...current, + error: undefined, + isLoading: true, + revalidate, + })); + + void loadDocDetail({ + inventorySource: options.inventorySource, + item: options.item, + localDocsDirectory: options.localDocsDirectory, + mode: options.mode, + }) + .then((result) => { + if (cancelled) { + return; + } + + setState({ + data: result.data, + error: undefined, + isLoading: false, + revalidate, + source: result.source, + }); + }) + .catch((error: unknown) => { + if (cancelled) { + return; + } + + setState((current) => ({ + ...current, + error: error instanceof Error ? error : new Error(String(error)), + isLoading: false, + revalidate, + })); + }); + + return () => { + cancelled = true; + }; + }, [options.inventorySource, options.item, options.localDocsDirectory, options.mode, reloadToken, revalidate]); + + return state; } diff --git a/extensions/numpy-documentation-search/src/hooks/useInventory.ts b/extensions/numpy-documentation-search/src/hooks/useInventory.ts index 91b949deee2..40db32d50fb 100644 --- a/extensions/numpy-documentation-search/src/hooks/useInventory.ts +++ b/extensions/numpy-documentation-search/src/hooks/useInventory.ts @@ -1,25 +1,81 @@ -import { useFetch } from "@raycast/utils"; -import { INVENTORY_URL, transformInventoryResponse, type InventoryItem } from "../lib/inventory"; +import { useCallback, useEffect, useState } from "react"; +import { loadInventory, type DocumentationSourceMode, type ResolvedDocumentationSource } from "../lib/docs-source"; +import type { InventoryItem } from "../lib/inventory"; interface UseInventoryResult { data?: InventoryItem[]; isLoading: boolean; error?: Error; + remoteError?: Error; revalidate: () => void; + source?: ResolvedDocumentationSource; } -export function useInventory(): UseInventoryResult { - const { data, isLoading, error, revalidate } = useFetch(INVENTORY_URL, { - keepPreviousData: true, - parseResponse: async (response) => { - if (!response.ok) { - throw new Error(`Failed to load NumPy inventory: ${response.status} ${response.statusText}`); - } - - const buffer = await response.arrayBuffer(); - return transformInventoryResponse(buffer); - }, +interface UseInventoryOptions { + localDocsDirectory?: string; + mode: DocumentationSourceMode; +} + +export function useInventory(options: UseInventoryOptions): UseInventoryResult { + const [reloadToken, setReloadToken] = useState(0); + + const revalidate = useCallback(() => { + setReloadToken((token) => token + 1); + }, []); + + const [state, setState] = useState({ + isLoading: true, + revalidate, }); - return { data, isLoading, error, revalidate }; + useEffect(() => { + let cancelled = false; + + setState((current) => ({ + ...current, + isLoading: true, + error: undefined, + remoteError: undefined, + revalidate, + })); + + void loadInventory({ + localDocsDirectory: options.localDocsDirectory, + mode: options.mode, + }) + .then((result) => { + if (cancelled) { + return; + } + + setState({ + data: result.data, + error: undefined, + isLoading: false, + remoteError: result.remoteError, + revalidate, + source: result.source, + }); + }) + .catch((error: unknown) => { + if (cancelled) { + return; + } + + setState((current) => ({ + ...current, + error: error instanceof Error ? error : new Error(String(error)), + isLoading: false, + remoteError: undefined, + revalidate, + source: undefined, + })); + }); + + return () => { + cancelled = true; + }; + }, [options.localDocsDirectory, options.mode, reloadToken, revalidate]); + + return state; } diff --git a/extensions/numpy-documentation-search/src/lib/docs-source.ts b/extensions/numpy-documentation-search/src/lib/docs-source.ts new file mode 100644 index 00000000000..7f367efc8c1 --- /dev/null +++ b/extensions/numpy-documentation-search/src/lib/docs-source.ts @@ -0,0 +1,175 @@ +import { readFile } from "node:fs/promises"; +import path from "node:path"; +import { parseDocDetail, type DocDetail } from "./doc-detail"; +import { INVENTORY_URL, transformInventoryBuffer, type InventoryItem } from "./inventory"; + +export type DocumentationSourceMode = "online" | "local"; +export type ResolvedDocumentationSource = "remote" | "local"; + +export interface InventoryLoadOptions { + localDocsDirectory?: string; + mode: DocumentationSourceMode; +} + +export interface InventoryLoadResult { + data: InventoryItem[]; + remoteError?: Error; + source: ResolvedDocumentationSource; +} + +export interface DocDetailLoadOptions { + inventorySource: ResolvedDocumentationSource; + item: InventoryItem; + localDocsDirectory?: string; + mode: DocumentationSourceMode; +} + +export interface DocDetailLoadResult { + data?: DocDetail; + remoteError?: Error; + source: ResolvedDocumentationSource; +} + +interface LoaderDeps { + fetchImpl: typeof fetch; + readBinaryFileImpl: (filePath: string) => Promise; + readTextFileImpl: (filePath: string) => Promise; +} + +const defaultDeps: LoaderDeps = { + fetchImpl: fetch, + readBinaryFileImpl: async (filePath) => Buffer.from(await readFile(filePath)), + readTextFileImpl: async (filePath) => readFile(filePath, "utf8"), +}; + +export async function loadInventory( + options: InventoryLoadOptions, + deps: LoaderDeps = defaultDeps, +): Promise { + switch (options.mode) { + case "local": + return { + data: await loadInventoryFromDirectory(options.localDocsDirectory, deps), + source: "local", + }; + case "online": + default: + try { + return { + data: await loadRemoteInventory(deps), + source: "remote", + }; + } catch (remoteError) { + const normalizedRemoteError = toError(remoteError); + + if (options.localDocsDirectory) { + try { + return { + data: await loadInventoryFromDirectory(options.localDocsDirectory, deps), + remoteError: normalizedRemoteError, + source: "local", + }; + } catch (localError) { + throw toError(localError); + } + } + throw normalizedRemoteError; + } + } +} + +export async function loadDocDetail( + options: DocDetailLoadOptions, + deps: LoaderDeps = defaultDeps, +): Promise { + if (options.mode === "local" || options.inventorySource === "local") { + return { + data: await loadLocalDocDetail(options.item, options.localDocsDirectory, deps), + source: "local", + }; + } + + try { + return { + data: await loadRemoteDocDetail(options.item, deps), + source: "remote", + }; + } catch (remoteError) { + const normalizedRemoteError = toError(remoteError); + + if (options.localDocsDirectory) { + try { + return { + data: await loadLocalDocDetail(options.item, options.localDocsDirectory, deps), + remoteError: normalizedRemoteError, + source: "local", + }; + } catch { + // Preserve the remote error below. + } + } + + throw normalizedRemoteError; + } +} + +async function loadRemoteInventory(deps: LoaderDeps): Promise { + const response = await deps.fetchImpl(INVENTORY_URL); + if (!response.ok) { + throw new Error(`Failed to load NumPy inventory: ${response.status} ${response.statusText}`); + } + + return transformInventoryBuffer(Buffer.from(await response.arrayBuffer())); +} + +async function loadInventoryFromDirectory( + localDocsDirectory: string | undefined, + deps: LoaderDeps, +): Promise { + const directory = getStableDocsDirectory(localDocsDirectory); + return loadInventoryFromFile(path.join(directory, "objects.inv"), deps); +} + +async function loadInventoryFromFile(filePath: string, deps: LoaderDeps): Promise { + return transformInventoryBuffer(await deps.readBinaryFileImpl(filePath)); +} + +async function loadRemoteDocDetail(item: InventoryItem, deps: LoaderDeps): Promise { + const response = await deps.fetchImpl(item.url); + if (!response.ok) { + throw new Error(`Failed to load documentation: ${response.status} ${response.statusText}`); + } + + return parseDocDetail(await response.text(), item); +} + +async function loadLocalDocDetail( + item: InventoryItem, + localDocsDirectory: string | undefined, + deps: LoaderDeps, +): Promise { + const directory = getStableDocsDirectory(localDocsDirectory); + const htmlPath = path.join(directory, item.docPath.split("#")[0] ?? item.docPath); + return parseDocDetail(await deps.readTextFileImpl(htmlPath), item); +} + +function getStableDocsDirectory(localDocsDirectory: string | undefined): string { + return path.join(requireLocalDocsDirectory(localDocsDirectory), "stable"); +} + +function requireLocalDocsDirectory(localDocsDirectory: string | undefined): string { + const normalized = localDocsDirectory?.trim(); + if (!normalized) { + throw new Error("Set the Local Docs Directory preference to the downloaded NumPy docs folder."); + } + + return normalized; +} + +function toError(error: unknown): Error { + if (error instanceof Error) { + return error; + } + + return new Error(String(error)); +} diff --git a/extensions/numpy-documentation-search/src/lib/inventory.ts b/extensions/numpy-documentation-search/src/lib/inventory.ts index 9b1f3b1399f..33aab26b56f 100644 --- a/extensions/numpy-documentation-search/src/lib/inventory.ts +++ b/extensions/numpy-documentation-search/src/lib/inventory.ts @@ -127,10 +127,13 @@ function resolveUri(uri: string, name: string): string { return resolved; } -export function transformInventoryResponse(buffer: ArrayBuffer): InventoryItem[] { - const raw = Buffer.from(buffer); - const lines = parseInventory(raw); +export function transformInventoryBuffer(buffer: Buffer): InventoryItem[] { + const lines = parseInventory(buffer); const filtered = dedupeAndFilter(lines); filtered.sort((a, b) => a.shortName.localeCompare(b.shortName)); return filtered; } + +export function transformInventoryResponse(buffer: ArrayBuffer): InventoryItem[] { + return transformInventoryBuffer(Buffer.from(buffer)); +} diff --git a/extensions/numpy-documentation-search/src/numpy-docs.tsx b/extensions/numpy-documentation-search/src/numpy-docs.tsx index 71fb597ee92..bd5e849a7a7 100644 --- a/extensions/numpy-documentation-search/src/numpy-docs.tsx +++ b/extensions/numpy-documentation-search/src/numpy-docs.tsx @@ -1,15 +1,21 @@ -import { Action, ActionPanel, Detail, getPreferenceValues, Icon, List } from "@raycast/api"; +import { + Action, + ActionPanel, + Detail, + getPreferenceValues, + Icon, + List, + openCommandPreferences, + type PreferenceValues, +} from "@raycast/api"; import { useEffect, useMemo, useState } from "react"; -import { useInventory } from "./hooks/useInventory"; import { useDocDetail } from "./hooks/useDocDetail"; +import { useInventory } from "./hooks/useInventory"; +import { type DocDetail, buildMarkdown } from "./lib/doc-detail"; +import { type DocumentationSourceMode } from "./lib/docs-source"; import { type InventoryItem } from "./lib/inventory"; -import { buildMarkdown, type DocDetail } from "./lib/doc-detail"; -import { searchInventory } from "./lib/search"; import { applyPrefixPreference } from "./lib/prefix"; - -interface Preferences { - useShortPrefix: boolean; -} +import { searchInventory } from "./lib/search"; type DetailRenderState = { detail?: DocDetail; @@ -17,16 +23,40 @@ type DetailRenderState = { error?: Error; }; +const RECOVERY_ITEM_ID = "__recovery__"; + export default function Command() { const [searchText, setSearchText] = useState(""); const [selectedId, setSelectedId] = useState(undefined); - const preferences = getPreferenceValues(); + const preferences = getPreferenceValues(); - const { data: inventory = [], isLoading: isLoadingInventory, error: inventoryError } = useInventory(); + const { + data: inventory = [], + isLoading: isLoadingInventory, + error: inventoryError, + remoteError, + revalidate: revalidateInventory, + source: inventorySource, + } = useInventory({ + localDocsDirectory: preferences.localDocsDirectory as string | undefined, + mode: preferences.documentationSourceMode as DocumentationSourceMode, + }); const results = useMemo(() => searchInventory(inventory, searchText), [inventory, searchText]); + const showRecoveryItem = Boolean(remoteError) && searchText.trim().length === 0; useEffect(() => { + if (showRecoveryItem) { + setSelectedId((current) => { + if (current && (current === RECOVERY_ITEM_ID || results.some((item) => item.id === current))) { + return current; + } + + return RECOVERY_ITEM_ID; + }); + return; + } + if (results.length === 0) { setSelectedId(undefined); return; @@ -36,13 +66,23 @@ export default function Command() { if (current && results.some((item) => item.id === current)) { return current; } + return results[0]?.id; }); - }, [results]); + }, [results, showRecoveryItem]); const selectedItem = useMemo(() => results.find((item) => item.id === selectedId), [results, selectedId]); - const { data: selectedDetail, isLoading: isLoadingDetail, error: selectedDetailError } = useDocDetail(selectedItem); + const { + data: selectedDetail, + isLoading: isLoadingDetail, + error: selectedDetailError, + } = useDocDetail({ + inventorySource, + item: selectedItem, + localDocsDirectory: preferences.localDocsDirectory as string | undefined, + mode: preferences.documentationSourceMode as DocumentationSourceMode, + }); const listIsLoading = isLoadingInventory; const noResults = !listIsLoading && results.length === 0; @@ -58,37 +98,64 @@ export default function Command() { onSelectionChange={(id) => setSelectedId(id ?? undefined)} > {inventoryError ? ( - + } + actions={} /> ) : noResults ? ( ) : ( - results.map((item) => { - const renderState: DetailRenderState = - item.id === selectedItem?.id - ? { detail: selectedDetail, isLoading: isLoadingDetail, error: selectedDetailError } - : { detail: undefined, isLoading: false }; - - const detailMarkdown = getDetailMarkdown(item, renderState, preferences.useShortPrefix); - - return ( + <> + {showRecoveryItem && remoteError ? ( } - actions={ - + id={RECOVERY_ITEM_ID} + title="Using local NumPy docs" + subtitle="Live access to numpy.org failed" + icon={Icon.Info} + detail={ + } + actions={} /> - ); - }) + ) : null} + {results.map((item) => { + const renderState: DetailRenderState = + item.id === selectedItem?.id + ? { + detail: selectedDetail, + error: selectedDetailError, + isLoading: isLoadingDetail, + } + : { detail: undefined, isLoading: false }; + + return ( + + } + actions={ + + } + /> + ); + })} + )} ); @@ -100,7 +167,7 @@ function getDetailMarkdown(item: InventoryItem, state: DetailRenderState, useSho } if (state.error) { - return `Failed to load documentation.\\n\\n${state.error.message}`; + return `Failed to load documentation.\n\n${state.error.message}`; } if (!state.detail) { @@ -166,3 +233,35 @@ function FullScreenDocumentation({ /> ); } + +function RecoveryActions({ revalidate }: { revalidate: () => void }) { + return ( + + + + + + ); +} + +function buildRecoveryMarkdown(error: Error, hasLocalDocsDirectory: boolean): string { + return [ + "# Unable to reach live NumPy docs", + "", + "This extension was not able to connect to `https://numpy.org/doc/stable/`.", + "", + "If you can access the internet outside of Raycast, you can download the generated NumPy docs elsewhere and point the extension at that folder.", + "", + "## Setup", + "", + "1. Download the ZIP for the generated NumPy docs from [numpy/doc](https://github.com/numpy/doc) or get it from another machine that can access `numpy.org`.", + "2. Extract the ZIP locally. The extension will look for the documentation version under the `stable` folder inside what you downloaded.", + hasLocalDocsDirectory + ? "3. Open this command's preferences and verify that **Local Docs Directory** points to the downloaded docs folder." + : "3. Open this command's preferences and set **Local Docs Directory** to the downloaded docs folder.", + "", + "## Current error", + "", + `\`${error.message}\``, + ].join("\n"); +}