├── .gitignore ├── .github ├── settings.yml └── workflows │ └── ci.yml ├── test ├── fixtures │ └── tnock.js └── index.js ├── LICENSE ├── package.json ├── index.js ├── CHANGELOG.md └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | .nyc_output 3 | coverage/ 4 | -------------------------------------------------------------------------------- /.github/settings.yml: -------------------------------------------------------------------------------- 1 | --- 2 | _extends: '.github:npm-cli/settings.yml' 3 | -------------------------------------------------------------------------------- /test/fixtures/tnock.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const nock = require('nock') 4 | 5 | module.exports = tnock 6 | function tnock (t, host) { 7 | const server = nock(host) 8 | t.tearDown(function () { 9 | server.done() 10 | }) 11 | return server 12 | } 13 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright npm, Inc 2 | 3 | Permission to use, copy, modify, and/or distribute this software for any 4 | purpose with or without fee is hereby granted, provided that the above 5 | copyright notice and this permission notice appear in all copies. 6 | 7 | THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 8 | WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 9 | MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 10 | ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 11 | WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 12 | ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 13 | OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 14 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "libnpmsearch", 3 | "version": "3.1.2", 4 | "description": "Programmatic API for searching in npm and compatible registries.", 5 | "author": "Kat Marchán ", 6 | "files": [ 7 | "*.js", 8 | "lib" 9 | ], 10 | "keywords": [ 11 | "npm", 12 | "search", 13 | "api", 14 | "libnpm" 15 | ], 16 | "license": "ISC", 17 | "scripts": { 18 | "preversion": "npm test", 19 | "postversion": "npm publish", 20 | "prepublishOnly": "git push origin --follow-tags", 21 | "posttest": "standard", 22 | "test": "tap" 23 | }, 24 | "tap": { 25 | "check-coverage": true 26 | }, 27 | "devDependencies": { 28 | "nock": "^9.6.1", 29 | "standard": "^12.0.0", 30 | "tap": "^14.11.0" 31 | }, 32 | "repository": { 33 | "type": "git", 34 | "url": "https://github.com/npm/libnpmsearch.git" 35 | }, 36 | "bugs": "https://github.com/npm/libnpmsearch/issues", 37 | "homepage": "https://npmjs.com/package/libnpmsearch", 38 | "dependencies": { 39 | "npm-registry-fetch": "^11.0.0" 40 | }, 41 | "engines": { 42 | "node": ">=10" 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const npmFetch = require('npm-registry-fetch') 4 | 5 | module.exports = search 6 | function search (query, opts) { 7 | return search.stream(query, opts).collect() 8 | } 9 | search.stream = searchStream 10 | function searchStream (query, opts = {}) { 11 | opts = { 12 | detailed: false, 13 | limit: 20, 14 | from: 0, 15 | quality: 0.65, 16 | popularity: 0.98, 17 | maintenance: 0.5, 18 | ...opts.opts, // this is to support the cli's --searchopts parameter 19 | ...opts 20 | } 21 | 22 | switch (opts.sortBy) { 23 | case 'optimal': { 24 | opts.quality = 0.65 25 | opts.popularity = 0.98 26 | opts.maintenance = 0.5 27 | break 28 | } 29 | case 'quality': { 30 | opts.quality = 1 31 | opts.popularity = 0 32 | opts.maintenance = 0 33 | break 34 | } 35 | case 'popularity': { 36 | opts.quality = 0 37 | opts.popularity = 1 38 | opts.maintenance = 0 39 | break 40 | } 41 | case 'maintenance': { 42 | opts.quality = 0 43 | opts.popularity = 0 44 | opts.maintenance = 1 45 | break 46 | } 47 | } 48 | return npmFetch.json.stream('/-/v1/search', 'objects.*', 49 | { 50 | ...opts, 51 | query: { 52 | text: Array.isArray(query) ? query.join(' ') : query, 53 | size: opts.limit, 54 | from: opts.from, 55 | quality: opts.quality, 56 | popularity: opts.popularity, 57 | maintenance: opts.maintenance 58 | }, 59 | mapJSON: (obj) => { 60 | if (obj.package.date) { 61 | obj.package.date = new Date(obj.package.date) 62 | } 63 | if (opts.detailed) { 64 | return obj 65 | } else { 66 | return obj.package 67 | } 68 | } 69 | } 70 | ) 71 | } 72 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Change Log 2 | 3 | 4 | # [3.0.0](https://github.com/npm/libnpmhook/compare/v2.0.2...v3.0.0) (2020-02-26) 5 | 6 | ### Breaking Changes 7 | 8 | * [`45f4db1`](https://github.com/npm/libnpmsearch/commit/45f4db1) fix: remove figgy-pudding ([@claudiahdz](https://github.com/claudiahdz)) 9 | 10 | ### Miscellaneuous 11 | 12 | * [`b413aae`](https://github.com/npm/libnpmsearch/commit/b413aae) chore: basic project updates ([@claudiahdz](https://github.com/claudiahdz)) 13 | * [`534983c`](https://github.com/npm/libnpmsearch/commit/534983c) chore: remove pr temmsearch ([@ruyadorno](https://github.com/ruyadorno)) 14 | * [`c503a89`](https://github.com/npm/libnpmsearch/commit/c503a89) chore: cleanup badges + contributing ([@ruyadorno](https://github.com/ruyadorno)) 15 | 16 | --- 17 | 18 | All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines. 19 | 20 | 21 | ## [2.0.2](https://github.com/npm/libnpmsearch/compare/v2.0.1...v2.0.2) (2019-07-16) 22 | 23 | 24 | 25 | 26 | ## [2.0.1](https://github.com/npm/libnpmsearch/compare/v2.0.0...v2.0.1) (2019-06-10) 27 | 28 | 29 | ### Bug Fixes 30 | 31 | * **opts:** support `opts.from` properly ([#2](https://github.com/npm/libnpmsearch/issues/2)) ([da6636c](https://github.com/npm/libnpmsearch/commit/da6636c)) 32 | * **standard:** standard --fix ([beca19c](https://github.com/npm/libnpmsearch/commit/beca19c)) 33 | 34 | 35 | 36 | 37 | # [2.0.0](https://github.com/npm/libnpmsearch/compare/v1.0.0...v2.0.0) (2018-08-28) 38 | 39 | 40 | ### Features 41 | 42 | * **opts:** added options for pagination, details, and sorting weights ([ff97eb5](https://github.com/npm/libnpmsearch/commit/ff97eb5)) 43 | 44 | 45 | ### BREAKING CHANGES 46 | 47 | * **opts:** this changes default requests and makes libnpmsearch return more complete data for individual packages, without null-defaulting 48 | 49 | 50 | 51 | 52 | # 1.0.0 (2018-08-27) 53 | 54 | 55 | ### Features 56 | 57 | * **api:** got API working ([fe90008](https://github.com/npm/libnpmsearch/commit/fe90008)) 58 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | --- 2 | ################################################################################ 3 | # Template - Node CI 4 | # 5 | # Description: 6 | # This contains the basic information to: install dependencies, run tests, 7 | # get coverage, and run linting on a nodejs project. This template will run 8 | # over the MxN matrix of all operating systems, and all current LTS versions 9 | # of NodeJS. 10 | # 11 | # Dependencies: 12 | # This template assumes that your project is using the `tap` module for 13 | # testing. If you're not using this module, then the step that runs your 14 | # coverage will need to be adjusted. 15 | # 16 | ################################################################################ 17 | name: Node CI 18 | 19 | on: [push, pull_request] 20 | 21 | jobs: 22 | build: 23 | strategy: 24 | fail-fast: false 25 | matrix: 26 | node-version: [10.x, 12.x, 13.x] 27 | os: [ubuntu-latest, windows-latest, macOS-latest] 28 | 29 | runs-on: ${{ matrix.os }} 30 | 31 | steps: 32 | # Checkout the repository 33 | - uses: actions/checkout@v2 34 | # Installs the specific version of Node.js 35 | - name: Use Node.js ${{ matrix.node-version }} 36 | uses: actions/setup-node@v1 37 | with: 38 | node-version: ${{ matrix.node-version }} 39 | 40 | ################################################################################ 41 | # Install Dependencies 42 | # 43 | # ASSUMPTIONS: 44 | # - The project has a package-lock.json file 45 | # 46 | # Simply run the tests for the project. 47 | ################################################################################ 48 | - name: Install dependencies 49 | run: npm ci 50 | 51 | ################################################################################ 52 | # Run Testing 53 | # 54 | # ASSUMPTIONS: 55 | # - The project has `tap` as a devDependency 56 | # - There is a script called "test" in the package.json 57 | # 58 | # Simply run the tests for the project. 59 | ################################################################################ 60 | - name: Run tests 61 | run: npm test 62 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # We've Moved! 🚚 2 | The code for this repo is now a workspace in the npm CLI repo. 3 | 4 | [github.com/npm/cli](https://github.com/npm/cli) 5 | 6 | You can find the workspace in /workspaces/libnpmsearch 7 | 8 | Please file bugs and feature requests as issues on the CLI and tag the issue with "ws:libnpmsearch". 9 | 10 | [github.com/npm/cli/issues](https://github.com/npm/cli) 11 | 12 | # libnpmsearch 13 | 14 | [![npm version](https://img.shields.io/npm/v/libnpmsearch.svg)](https://npm.im/libnpmsearch) 15 | [![license](https://img.shields.io/npm/l/libnpmsearch.svg)](https://npm.im/libnpmsearch) 16 | [![Coverage Status](https://coveralls.io/repos/github/npm/libnpmsearch/badge.svg?branch=latest)](https://coveralls.io/github/npm/libnpmsearch?branch=latest) 17 | 18 | [`libnpmsearch`](https://github.com/npm/libnpmsearch) is a Node.js library for 19 | programmatically accessing the npm search endpoint. It does **not** support 20 | legacy search through `/-/all`. 21 | 22 | ## Table of Contents 23 | 24 | * [Example](#example) 25 | * [Install](#install) 26 | * [Contributing](#contributing) 27 | * [API](#api) 28 | * [search opts](#opts) 29 | * [`search()`](#search) 30 | * [`search.stream()`](#search-stream) 31 | 32 | ## Example 33 | 34 | ```js 35 | const search = require('libnpmsearch') 36 | 37 | console.log(await search('libnpm')) 38 | => 39 | [ 40 | { 41 | name: 'libnpm', 42 | description: 'programmatic npm API', 43 | ...etc 44 | }, 45 | { 46 | name: 'libnpmsearch', 47 | description: 'Programmatic API for searching in npm and compatible registries', 48 | ...etc 49 | }, 50 | ...more 51 | ] 52 | ``` 53 | 54 | ## Install 55 | 56 | `$ npm install libnpmsearch` 57 | 58 | ### API 59 | 60 | #### `opts` for `libnpmsearch` commands 61 | 62 | The following opts are used directly by `libnpmsearch` itself: 63 | 64 | * `opts.limit` - Number of results to limit the query to. Default: 20 65 | * `opts.from` - Offset number for results. Used with `opts.limit` for pagination. Default: 0 66 | * `opts.detailed` - If true, returns an object with `package`, `score`, and `searchScore` fields, with `package` being what would usually be returned, and the other two containing details about how that package scored. Useful for UIs. Default: false 67 | * `opts.sortBy` - Used as a shorthand to set `opts.quality`, `opts.maintenance`, and `opts.popularity` with values that prioritize each one. Should be one of `'optimal'`, `'quality'`, `'maintenance'`, or `'popularity'`. Default: `'optimal'` 68 | * `opts.maintenance` - Decimal number between `0` and `1` that defines the weight of `maintenance` metrics when scoring and sorting packages. Default: `0.65` (same as `opts.sortBy: 'optimal'`) 69 | * `opts.popularity` - Decimal number between `0` and `1` that defines the weight of `popularity` metrics when scoring and sorting packages. Default: `0.98` (same as `opts.sortBy: 'optimal'`) 70 | * `opts.quality` - Decimal number between `0` and `1` that defines the weight of `quality` metrics when scoring and sorting packages. Default: `0.5` (same as `opts.sortBy: 'optimal'`) 71 | 72 | `libnpmsearch` uses [`npm-registry-fetch`](https://npm.im/npm-registry-fetch). 73 | Most options are passed through directly to that library, so please refer to 74 | [its own `opts` 75 | documentation](https://www.npmjs.com/package/npm-registry-fetch#fetch-options) 76 | for options that can be passed in. 77 | 78 | A couple of options of note for those in a hurry: 79 | 80 | * `opts.token` - can be passed in and will be used as the authentication token for the registry. For other ways to pass in auth details, see the n-r-f docs. 81 | 82 | #### `> search(query, [opts]) -> Promise` 83 | 84 | `query` must be either a String or an Array of search terms. 85 | 86 | If `opts.limit` is provided, it will be sent to the API to constrain the number 87 | of returned results. You may receive more, or fewer results, at the endpoint's 88 | discretion. 89 | 90 | The returned Promise resolved to an Array of search results with the following 91 | format: 92 | 93 | ```js 94 | { 95 | name: String, 96 | version: SemverString, 97 | description: String || null, 98 | maintainers: [ 99 | { 100 | username: String, 101 | email: String 102 | }, 103 | ...etc 104 | ] || null, 105 | keywords: [String] || null, 106 | date: Date || null 107 | } 108 | ``` 109 | 110 | If `opts.limit` is provided, it will be sent to the API to constrain the number 111 | of returned results. You may receive more, or fewer results, at the endpoint's 112 | discretion. 113 | 114 | For streamed results, see [`search.stream`](#search-stream). 115 | 116 | ##### Example 117 | 118 | ```javascript 119 | await search('libnpm') 120 | => 121 | [ 122 | { 123 | name: 'libnpm', 124 | description: 'programmatic npm API', 125 | ...etc 126 | }, 127 | { 128 | name: 'libnpmsearch', 129 | description: 'Programmatic API for searching in npm and compatible registries', 130 | ...etc 131 | }, 132 | ...more 133 | ] 134 | ``` 135 | 136 | #### `> search.stream(query, [opts]) -> Stream` 137 | 138 | `query` must be either a String or an Array of search terms. 139 | 140 | If `opts.limit` is provided, it will be sent to the API to constrain the number 141 | of returned results. You may receive more, or fewer results, at the endpoint's 142 | discretion. 143 | 144 | The returned Stream emits one entry per search result, with each entry having 145 | the following format: 146 | 147 | ```js 148 | { 149 | name: String, 150 | version: SemverString, 151 | description: String || null, 152 | maintainers: [ 153 | { 154 | username: String, 155 | email: String 156 | }, 157 | ...etc 158 | ] || null, 159 | keywords: [String] || null, 160 | date: Date || null 161 | } 162 | ``` 163 | 164 | For getting results in one chunk, see [`search`](#search-stream). 165 | 166 | ##### Example 167 | 168 | ```javascript 169 | search.stream('libnpm').on('data', console.log) 170 | => 171 | // entry 1 172 | { 173 | name: 'libnpm', 174 | description: 'programmatic npm API', 175 | ...etc 176 | } 177 | // entry 2 178 | { 179 | name: 'libnpmsearch', 180 | description: 'Programmatic API for searching in npm and compatible registries', 181 | ...etc 182 | } 183 | // etc 184 | ``` 185 | -------------------------------------------------------------------------------- /test/index.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const qs = require('querystring') 4 | const test = require('tap').test 5 | const tnock = require('./fixtures/tnock.js') 6 | 7 | const OPTS = { 8 | registry: 'https://mock.reg/' 9 | } 10 | 11 | const REG = OPTS.registry 12 | const NPM_REG = 'https://registry.npmjs.org/' 13 | const search = require('../index.js') 14 | 15 | test('basic test no options', t => { 16 | const query = qs.stringify({ 17 | text: 'oo', 18 | size: 20, 19 | from: 0, 20 | quality: 0.65, 21 | popularity: 0.98, 22 | maintenance: 0.5 23 | }) 24 | tnock(t, NPM_REG).get(`/-/v1/search?${query}`).once().reply(200, { 25 | objects: [ 26 | { package: { name: 'cool', version: '1.0.0' } }, 27 | { package: { name: 'foo', version: '2.0.0' } } 28 | ] 29 | }) 30 | return search('oo').then(results => { 31 | t.similar(results, [{ 32 | name: 'cool', 33 | version: '1.0.0' 34 | }, { 35 | name: 'foo', 36 | version: '2.0.0' 37 | }], 'got back an array of search results') 38 | }) 39 | }) 40 | 41 | test('basic test', t => { 42 | const query = qs.stringify({ 43 | text: 'oo', 44 | size: 20, 45 | from: 0, 46 | quality: 0.65, 47 | popularity: 0.98, 48 | maintenance: 0.5 49 | }) 50 | tnock(t, REG).get(`/-/v1/search?${query}`).once().reply(200, { 51 | objects: [ 52 | { package: { name: 'cool', version: '1.0.0' } }, 53 | { package: { name: 'foo', version: '2.0.0' } } 54 | ] 55 | }) 56 | return search('oo', OPTS).then(results => { 57 | t.similar(results, [{ 58 | name: 'cool', 59 | version: '1.0.0' 60 | }, { 61 | name: 'foo', 62 | version: '2.0.0' 63 | }], 'got back an array of search results') 64 | }) 65 | }) 66 | 67 | test('basic test supports nested options', t => { 68 | const query = qs.stringify({ 69 | text: 'oo', 70 | size: 20, 71 | from: 1, 72 | quality: 0.65, 73 | popularity: 0.98, 74 | maintenance: 0.5 75 | }) 76 | tnock(t, REG).get(`/-/v1/search?${query}`).once().reply(200, { 77 | objects: [ 78 | { package: { name: 'cool', version: '1.0.0' } }, 79 | { package: { name: 'foo', version: '2.0.0' } } 80 | ] 81 | }) 82 | 83 | // this test is to ensure we don't break the nested opts parameter 84 | // that the cli supplies when a user passes --searchopts= 85 | return search('oo', { ...OPTS, opts: { from: 1 } }).then(results => { 86 | t.similar(results, [{ 87 | name: 'cool', 88 | version: '1.0.0' 89 | }, { 90 | name: 'foo', 91 | version: '2.0.0' 92 | }], 'got back an array of search results') 93 | }) 94 | }) 95 | 96 | test('search.stream', t => { 97 | const query = qs.stringify({ 98 | text: 'oo', 99 | size: 20, 100 | from: 0, 101 | quality: 0.65, 102 | popularity: 0.98, 103 | maintenance: 0.5 104 | }) 105 | tnock(t, REG).get(`/-/v1/search?${query}`).once().reply(200, { 106 | objects: [ 107 | { package: { name: 'cool', version: '1.0.0', date: new Date().toISOString() } }, 108 | { package: { name: 'foo', version: '2.0.0' } } 109 | ] 110 | }) 111 | return search.stream('oo', OPTS).collect().then(results => { 112 | t.similar(results, [{ 113 | name: 'cool', 114 | version: '1.0.0' 115 | }, { 116 | name: 'foo', 117 | version: '2.0.0' 118 | }], 'has a stream-based API function with identical results') 119 | }) 120 | }) 121 | 122 | test('accepts a limit option', t => { 123 | const query = qs.stringify({ 124 | text: 'oo', 125 | size: 3, 126 | from: 0, 127 | quality: 0.65, 128 | popularity: 0.98, 129 | maintenance: 0.5 130 | }) 131 | tnock(t, REG).get(`/-/v1/search?${query}`).once().reply(200, { 132 | objects: [ 133 | { package: { name: 'cool', version: '1.0.0' } }, 134 | { package: { name: 'cool', version: '1.0.0' } }, 135 | { package: { name: 'cool', version: '1.0.0' } }, 136 | { package: { name: 'cool', version: '1.0.0' } } 137 | ] 138 | }) 139 | return search('oo', { ...OPTS, limit: 3 }).then(results => { 140 | t.equal(results.length, 4, 'returns more results if endpoint does so') 141 | }) 142 | }) 143 | 144 | test('accepts a from option', t => { 145 | const query = qs.stringify({ 146 | text: 'oo', 147 | size: 20, 148 | from: 1, 149 | quality: 0.65, 150 | popularity: 0.98, 151 | maintenance: 0.5 152 | }) 153 | tnock(t, REG).get(`/-/v1/search?${query}`).once().reply(200, { 154 | objects: [ 155 | { package: { name: 'cool', version: '1.0.0' } }, 156 | { package: { name: 'cool', version: '1.1.0' } }, 157 | { package: { name: 'cool', version: '1.0.0' } }, 158 | { package: { name: 'cool', version: '1.0.0' } } 159 | ] 160 | }) 161 | return search('oo', { ...OPTS, from: 1 }).then(results => { 162 | t.equal(results.length, 4, 'returns more results if endpoint does so') 163 | }) 164 | }) 165 | 166 | test('accepts quality/mainenance/popularity options', t => { 167 | const query = qs.stringify({ 168 | text: 'oo', 169 | size: 20, 170 | from: 0, 171 | quality: 1, 172 | popularity: 2, 173 | maintenance: 3 174 | }) 175 | tnock(t, REG).get(`/-/v1/search?${query}`).once().reply(200, { 176 | objects: [ 177 | { package: { name: 'cool', version: '1.0.0' } }, 178 | { package: { name: 'cool', version: '1.0.0' } }, 179 | { package: { name: 'cool', version: '1.0.0' } }, 180 | { package: { name: 'cool', version: '1.0.0' } } 181 | ] 182 | }) 183 | return search('oo', { 184 | ...OPTS, 185 | quality: 1, 186 | popularity: 2, 187 | maintenance: 3 188 | }).then(results => { 189 | t.equal(results.length, 4, 'returns more results if endpoint does so') 190 | }) 191 | }) 192 | 193 | test('sortBy: quality', t => { 194 | const query = qs.stringify({ 195 | text: 'oo', 196 | size: 20, 197 | from: 0, 198 | quality: 1, 199 | popularity: 0, 200 | maintenance: 0 201 | }) 202 | tnock(t, REG).get(`/-/v1/search?${query}`).once().reply(200, { 203 | objects: [ 204 | { package: { name: 'cool', version: '1.0.0' } }, 205 | { package: { name: 'cool', version: '1.0.0' } }, 206 | { package: { name: 'cool', version: '1.0.0' } }, 207 | { package: { name: 'cool', version: '1.0.0' } } 208 | ] 209 | }) 210 | return search('oo', { 211 | ...OPTS, 212 | sortBy: 'quality' 213 | }).then(results => { 214 | t.equal(results.length, 4, 'returns more results if endpoint does so') 215 | }) 216 | }) 217 | 218 | test('sortBy: popularity', t => { 219 | const query = qs.stringify({ 220 | text: 'oo', 221 | size: 20, 222 | from: 0, 223 | quality: 0, 224 | popularity: 1, 225 | maintenance: 0 226 | }) 227 | tnock(t, REG).get(`/-/v1/search?${query}`).once().reply(200, { 228 | objects: [ 229 | { package: { name: 'cool', version: '1.0.0' } }, 230 | { package: { name: 'cool', version: '1.0.0' } }, 231 | { package: { name: 'cool', version: '1.0.0' } }, 232 | { package: { name: 'cool', version: '1.0.0' } } 233 | ] 234 | }) 235 | return search('oo', { 236 | ...OPTS, 237 | sortBy: 'popularity' 238 | }).then(results => { 239 | t.equal(results.length, 4, 'returns more results if endpoint does so') 240 | }) 241 | }) 242 | 243 | test('sortBy: maintenance', t => { 244 | const query = qs.stringify({ 245 | text: 'oo', 246 | size: 20, 247 | from: 0, 248 | quality: 0, 249 | popularity: 0, 250 | maintenance: 1 251 | }) 252 | tnock(t, REG).get(`/-/v1/search?${query}`).once().reply(200, { 253 | objects: [ 254 | { package: { name: 'cool', version: '1.0.0' } }, 255 | { package: { name: 'cool', version: '1.0.0' } }, 256 | { package: { name: 'cool', version: '1.0.0' } }, 257 | { package: { name: 'cool', version: '1.0.0' } } 258 | ] 259 | }) 260 | return search('oo', { 261 | ...OPTS, 262 | sortBy: 'maintenance' 263 | }).then(results => { 264 | t.equal(results.length, 4, 'returns more results if endpoint does so') 265 | }) 266 | }) 267 | 268 | test('sortBy: optimal', t => { 269 | const query = qs.stringify({ 270 | text: 'oo', 271 | size: 20, 272 | from: 0, 273 | quality: 0.65, 274 | popularity: 0.98, 275 | maintenance: 0.5 276 | }) 277 | tnock(t, REG).get(`/-/v1/search?${query}`).once().reply(200, { 278 | objects: [ 279 | { package: { name: 'cool', version: '1.0.0' } }, 280 | { package: { name: 'cool', version: '1.0.0' } }, 281 | { package: { name: 'cool', version: '1.0.0' } }, 282 | { package: { name: 'cool', version: '1.0.0' } } 283 | ] 284 | }) 285 | return search('oo', { 286 | ...OPTS, 287 | sortBy: 'optimal' 288 | }).then(results => { 289 | t.equal(results.length, 4, 'returns more results if endpoint does so') 290 | }) 291 | }) 292 | 293 | test('detailed format', t => { 294 | const query = qs.stringify({ 295 | text: 'oo', 296 | size: 20, 297 | from: 0, 298 | quality: 0, 299 | popularity: 0, 300 | maintenance: 1 301 | }) 302 | const results = [ 303 | { 304 | package: { name: 'cool', version: '1.0.0' }, 305 | score: { 306 | final: 0.9237841281241451, 307 | detail: { 308 | quality: 0.9270640902288084, 309 | popularity: 0.8484861649808381, 310 | maintenance: 0.9962706951777409 311 | } 312 | }, 313 | searchScore: 100000.914 314 | }, 315 | { 316 | package: { name: 'ok', version: '2.0.0' }, 317 | score: { 318 | final: 0.9237841281451, 319 | detail: { 320 | quality: 0.9270602288084, 321 | popularity: 0.8461649808381, 322 | maintenance: 0.9706951777409 323 | } 324 | }, 325 | searchScore: 1000.91 326 | } 327 | ] 328 | tnock(t, REG).get(`/-/v1/search?${query}`).once().reply(200, { 329 | objects: results 330 | }) 331 | return search('oo', { 332 | ...OPTS, 333 | sortBy: 'maintenance', 334 | detailed: true 335 | }).then(res => { 336 | t.deepEqual(res, results, 'return full-format results with opts.detailed') 337 | }) 338 | }) 339 | 340 | test('space-separates and URI-encodes multiple search params', t => { 341 | const query = qs.stringify({ 342 | text: 'foo bar:baz quux?=', 343 | size: 1, 344 | from: 0, 345 | quality: 1, 346 | popularity: 2, 347 | maintenance: 3 348 | }).replace(/%20/g, '+') 349 | 350 | tnock(t, REG).get(`/-/v1/search?${query}`).reply(200, { objects: [] }) 351 | return search(['foo', 'bar:baz', 'quux?='], { 352 | ...OPTS, 353 | limit: 1, 354 | quality: 1, 355 | popularity: 2, 356 | maintenance: 3 357 | }).then( 358 | () => t.ok(true, 'sent parameters correctly urlencoded') 359 | ) 360 | }) 361 | --------------------------------------------------------------------------------