├── .npmrc ├── .gitattributes ├── .gitignore ├── .editorconfig ├── index.test-d.ts ├── .github └── workflows │ └── main.yml ├── index.d.ts ├── package.json ├── license ├── test.js ├── readme.md └── index.js /.npmrc: -------------------------------------------------------------------------------- 1 | package-lock=false 2 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | * text=auto eol=lf 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | yarn.lock 3 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | indent_style = tab 5 | end_of_line = lf 6 | charset = utf-8 7 | trim_trailing_whitespace = true 8 | insert_final_newline = true 9 | 10 | [*.yml] 11 | indent_style = space 12 | indent_size = 2 13 | -------------------------------------------------------------------------------- /index.test-d.ts: -------------------------------------------------------------------------------- 1 | import {expectType} from 'tsd'; 2 | import npmUser, {type UserInfo} from './index.js'; 3 | 4 | const userInfoPromise = npmUser('sindresorhus'); 5 | expectType>(userInfoPromise); 6 | 7 | const userInfo = await userInfoPromise; 8 | expectType(userInfo.name); 9 | expectType(userInfo.avatar); 10 | expectType(userInfo.email); 11 | expectType(userInfo.github); 12 | expectType(userInfo.twitter); 13 | -------------------------------------------------------------------------------- /.github/workflows/main.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | on: 3 | - push 4 | - pull_request 5 | jobs: 6 | test: 7 | name: Node.js ${{ matrix.node-version }} 8 | runs-on: ubuntu-latest 9 | strategy: 10 | fail-fast: false 11 | matrix: 12 | node-version: 13 | - 21 14 | - 20 15 | - 18 16 | steps: 17 | - uses: actions/checkout@v4 18 | - uses: actions/setup-node@v4 19 | with: 20 | node-version: ${{ matrix.node-version }} 21 | - run: npm install 22 | - run: npm test 23 | -------------------------------------------------------------------------------- /index.d.ts: -------------------------------------------------------------------------------- 1 | export type UserInfo = { 2 | /** 3 | The user's name. 4 | */ 5 | name: string | undefined; 6 | 7 | /** 8 | The URL to user's avatar. 9 | */ 10 | avatar: string | undefined; 11 | 12 | /** 13 | The user's email. 14 | */ 15 | email: string | undefined; 16 | 17 | /** 18 | The user's associated GitHub account. 19 | */ 20 | github: string | undefined; 21 | 22 | /** 23 | The user's associated Twitter account. 24 | */ 25 | twitter: string | undefined; 26 | }; 27 | 28 | /** 29 | Get user info of an npm user. 30 | 31 | @param name - The user's username on npm. 32 | 33 | @example 34 | ``` 35 | import npmUser from 'npm-user'; 36 | 37 | console.log(await npmUser('sindresorhus')); 38 | // { 39 | // name: 'Sindre Sorhus', 40 | // avatar: 'https://www.npmjs.com/npm-avatar/…', 41 | // email: 'sindresorhus@gmail.com', 42 | // github: 'sindresorhus', 43 | // twitter: 'sindresorhus' 44 | // } 45 | ``` 46 | */ 47 | export default function npmUser(name: string): Promise; 48 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "npm-user", 3 | "version": "6.1.1", 4 | "description": "Get user info of an npm user", 5 | "license": "MIT", 6 | "repository": "sindresorhus/npm-user", 7 | "funding": "https://github.com/sponsors/sindresorhus", 8 | "author": { 9 | "name": "Sindre Sorhus", 10 | "email": "sindresorhus@gmail.com", 11 | "url": "https://sindresorhus.com" 12 | }, 13 | "type": "module", 14 | "exports": { 15 | "types": "./index.d.ts", 16 | "default": "./index.js" 17 | }, 18 | "sideEffects": false, 19 | "engines": { 20 | "node": ">=18" 21 | }, 22 | "scripts": { 23 | "test": "xo && ava && tsd" 24 | }, 25 | "files": [ 26 | "index.js", 27 | "index.d.ts" 28 | ], 29 | "keywords": [ 30 | "npm", 31 | "user", 32 | "username", 33 | "homepage", 34 | "email", 35 | "get", 36 | "website", 37 | "scrape", 38 | "info", 39 | "profile" 40 | ], 41 | "dependencies": { 42 | "cheerio": "1.0.0-rc.12", 43 | "ky": "^1.2.1", 44 | "npm-email": "^5.0.0" 45 | }, 46 | "devDependencies": { 47 | "ava": "^6.1.1", 48 | "tsd": "^0.30.7", 49 | "xo": "^0.57.0" 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /license: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) Sindre Sorhus (https://sindresorhus.com) 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 6 | 7 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 8 | 9 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 10 | -------------------------------------------------------------------------------- /test.js: -------------------------------------------------------------------------------- 1 | import test from 'ava'; 2 | import npmUser from './index.js'; 3 | 4 | const avatarRegex = /^https:\/\/www\.npmjs\.com\/npm-avatar\//m; 5 | 6 | test('user: sindresorhus', async t => { 7 | const user = await npmUser('sindresorhus'); 8 | 9 | t.like(user, { 10 | name: 'Sindre Sorhus', 11 | email: 'sindresorhus@gmail.com', 12 | github: 'sindresorhus', 13 | twitter: 'sindresorhus', 14 | }); 15 | 16 | t.regex(user.avatar, avatarRegex); 17 | }); 18 | 19 | test('user: npm', async t => { 20 | const user = await npmUser('npm'); 21 | 22 | t.like(user, { 23 | name: 'No Problem, Meatbag', 24 | email: 'npm@npmjs.com', 25 | }); 26 | 27 | t.regex(user.avatar, avatarRegex); 28 | }); 29 | 30 | test('user: tj', async t => { 31 | const user = await npmUser('tj'); 32 | 33 | t.like(user, { 34 | name: undefined, 35 | email: 'tj@vision-media.ca', 36 | github: undefined, 37 | twitter: undefined, 38 | }); 39 | 40 | t.regex(user.avatar, avatarRegex); 41 | }); 42 | 43 | test('handles non-existent user', async t => { 44 | await t.throwsAsync( 45 | npmUser('nnnope'), 46 | {message: 'User `nnnope` could not be found', code: 'ERR_NO_NPM_USER'}, 47 | ); 48 | }); 49 | -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | # npm-user 2 | 3 | > Get user info of an npm user 4 | 5 | Since npm has no API for this, we are forced to scrape the [profile page](https://www.npmjs.com/~sindresorhus). 6 | 7 | *Use the faster [npm-email](https://github.com/sindresorhus/npm-email) package if you only need the email.* 8 | 9 | ## Install 10 | 11 | ```sh 12 | npm install npm-user 13 | ``` 14 | 15 | ## Usage 16 | 17 | ```js 18 | import npmUser from 'npm-user'; 19 | 20 | console.log(await npmUser('sindresorhus')); 21 | /* 22 | { 23 | name: 'Sindre Sorhus', 24 | avatar: 'https://www.npmjs.com/npm-avatar/…', 25 | email: 'sindresorhus@gmail.com', 26 | github: 'sindresorhus', 27 | twitter: 'sindresorhus' 28 | } 29 | */ 30 | ``` 31 | 32 | *The values will be `undefined` if they're not set in the npm profile.* 33 | 34 | ## Related 35 | 36 | - [npm-user-cli](https://github.com/sindresorhus/npm-user-cli) - CLI for this module 37 | - [npm-email](https://github.com/sindresorhus/npm-email) - Get the email of an npm user 38 | - [npm-keyword](https://github.com/sindresorhus/npm-keyword) - Get a list of npm packages with a certain keyword 39 | - [package-json](https://github.com/sindresorhus/package-json) - Get the package.json of a package from the npm registry 40 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | import ky from 'ky'; 2 | import {load as cheerioLoad} from 'cheerio'; 3 | import npmEmail from 'npm-email'; 4 | 5 | export default async function npmUser(username) { 6 | if (typeof username !== 'string') { 7 | throw new TypeError('Username required'); 8 | } 9 | 10 | const url = `https://www.npmjs.com/~${username}`; 11 | try { 12 | const [profile, email] = await Promise.all([ky(url).text(), npmEmail(username)]); 13 | const $ = cheerioLoad(profile); 14 | 15 | let avatar = $('img[src^="/npm-avatar"]')?.attr('src') || undefined; 16 | avatar &&= `https://www.npmjs.com${avatar}`; 17 | 18 | const $sidebar = $('[class^="_73a8e6f0"]'); 19 | 20 | return { 21 | name: $sidebar.find('.eaac77a6.mv2').text() || undefined, 22 | avatar, 23 | email, 24 | github: $sidebar.find('a[href^="https://github.com/"]').text().slice(1) || undefined, 25 | twitter: $sidebar.find('a[href^="https://twitter.com/"]').text().slice(1) || undefined, 26 | }; 27 | } catch (error) { 28 | if (error?.response?.status === 404) { 29 | const notFoundError = new Error(`User \`${username}\` could not be found`, {cause: error}); 30 | notFoundError.code = 'ERR_NO_NPM_USER'; 31 | throw notFoundError; 32 | } 33 | 34 | throw error; 35 | } 36 | } 37 | --------------------------------------------------------------------------------