├── .prettierrc ├── .editorconfig ├── .gitignore ├── tsconfig.json ├── package.json ├── yarn.lock ├── LICENSE ├── README.md ├── docs └── npm-package-support-ts.user.js └── npm-package-support-ts.user.ts /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "singleQuote": true, 3 | "semi": false 4 | } 5 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # http://editorconfig.org 2 | root = true 3 | 4 | [*] 5 | indent_style = space 6 | indent_size = 2 7 | end_of_line = lf 8 | charset = utf-8 9 | trim_trailing_whitespace = true 10 | insert_final_newline = true 11 | 12 | [*.md] 13 | trim_trailing_whitespace = false 14 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/ignore-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | node_modules 5 | 6 | # misc 7 | .back 8 | .cache 9 | .DS_Store 10 | .env.local 11 | .env.development.local 12 | .env.test.local 13 | .env.production.local 14 | 15 | npm-debug.log*a 16 | yarn-debug.log* 17 | yarn-error.log* 18 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es2017", 4 | "module": "esnext", 5 | "outDir": "docs", 6 | "removeComments": true, 7 | "strict": true, 8 | "alwaysStrict": false, 9 | "noUnusedLocals": true, 10 | "noUnusedParameters": true, 11 | "noImplicitReturns": true, 12 | "noFallthroughCasesInSwitch": true, 13 | "moduleResolution": "node", 14 | "esModuleInterop": true 15 | }, 16 | "files": [ 17 | "npm-package-support-ts.user.ts" 18 | ] 19 | } 20 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "npm-package-support", 3 | "author": "8th713", 4 | "license": "MIT", 5 | "bugs": { 6 | "url": "https://github.com/8th713/npm-package-support/issues" 7 | }, 8 | "homepage": "https://github.com/8th713/npm-package-support", 9 | "private": true, 10 | "readmeFilename": "README.md", 11 | "scripts": { 12 | "build": "tsc", 13 | "watch": "tsc -w" 14 | }, 15 | "dependencies": {}, 16 | "devDependencies": { 17 | "@types/tampermonkey": "^4.0.0", 18 | "typescript": "^3.6.2" 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /yarn.lock: -------------------------------------------------------------------------------- 1 | # THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. 2 | # yarn lockfile v1 3 | 4 | 5 | "@types/tampermonkey@^4.0.0": 6 | version "4.0.0" 7 | resolved "https://registry.yarnpkg.com/@types/tampermonkey/-/tampermonkey-4.0.0.tgz#fd654ea1191f6ae7acd7e9ddc19c67ed062a1495" 8 | integrity sha512-StJoz2Cw5bM5Eqp/68O/X1rjI5aw7hGfbji6eXiY+ERN4o8lyfLvJKD91zXmdhUqK157QBjcCtzJUtU+qmAOFA== 9 | 10 | typescript@^3.6.2: 11 | version "3.6.2" 12 | resolved "https://registry.yarnpkg.com/typescript/-/typescript-3.6.2.tgz#105b0f1934119dde543ac8eb71af3a91009efe54" 13 | integrity sha512-lmQ4L+J6mnu3xweP8+rOrUwzmN+MRAj7TgtJtDaXE5PMyX2kCrklhg3rvOsOIfNeAWMQWO2F1GPc1kMD2vLAfw== 14 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License 2 | 3 | Copyright (c) 2014-2019 8th713 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # npm package support TypeScript 2 | 3 | UserScript that detects TypeScript support of npm package. 4 | 5 | Inspired by [npm: package support types](https://gist.github.com/azu/ec22f3def09563ece54f8dc32523b152) 6 | 7 | ## Install 8 | 9 | Install from [npm-package-support-ts.user.js](https://8th713.github.io/npm-package-support/npm-package-support-ts.user.js) 10 | 11 | **Dependencies:** 12 | 13 | You need to install Greasemonkey extension before installing this script. 14 | 15 | - [Greasemonkey – Firefox](https://addons.mozilla.org/ja/firefox/addon/greasemonkey/) 16 | - [Tampermonkey - Chrome](https://chrome.google.com/webstore/detail/tampermonkey/dhdgffkkebhmkfjojejmpbldmpobfkfo?hl=ja) 17 | 18 | ## Usage 19 | 20 | Visit any npm package page. 21 | 22 | **Example:** 23 | 24 | * [React](https://www.npmjs.com/package/react) - In DefinitelyTyped. 25 | * [Redux](https://www.npmjs.com/package/redux) - Included in the package. 26 | * [@testing-library/react](https://www.npmjs.com/package/@testing-library/react) - Depends on external. 27 | 28 | ## Contributing 29 | 30 | 1. Fork it! 31 | 2. Create your feature branch: `git checkout -b my-new-feature` 32 | 3. Commit your changes: `git commit -am 'Add some feature'` 33 | 4. Push to the branch: `git push origin my-new-feature` 34 | 5. Submit a pull request 35 | 36 | ## Development 37 | ```sh 38 | # Setup 39 | npm install 40 | # or 41 | yarn install 42 | 43 | # Build 44 | npm run build 45 | # or 46 | yarn build 47 | 48 | # Watch 49 | npm run watch 50 | # or 51 | yarn watch 52 | ``` 53 | 54 | ## License 55 | 56 | [MIT license](./LICENSE) 57 | -------------------------------------------------------------------------------- /docs/npm-package-support-ts.user.js: -------------------------------------------------------------------------------- 1 | /*! 2 | // ==UserScript== 3 | // @name npm package support TypeScript 4 | // @description Detects TypeScript support of npm package. 5 | // @version 1.3.0 6 | // @author 8th713 7 | // @license MIT 8 | // @homepage https://github.com/8th713/npm-package-support 9 | // @namespace http://github.com/8th713 10 | // @match https://www.npmjs.com/package/* 11 | // @grant GM_xmlhttpRequest 12 | // @connect registry.npmjs.com 13 | // ==/UserScript== 14 | 15 | Inspired by npm: package support types 16 | https://gist.github.com/azu/ec22f3def09563ece54f8dc32523b152 17 | */ 18 | const getPackageName = (pathname) => { 19 | const [path, version] = pathname.split('/v/'); 20 | const name = path 21 | .split('/') 22 | .slice(2) 23 | .join('/'); 24 | return [name, version]; 25 | }; 26 | const isDTPackage = (name) => name.startsWith('@types/'); 27 | const escapeSlash = (str) => str.replace(/\//g, '%2F'); 28 | const createTypePackageName = (pkgName) => { 29 | if (pkgName.startsWith('@')) { 30 | return `@types/${pkgName.slice(1).replace('/', '__')}`; 31 | } 32 | return `@types/${pkgName}`; 33 | }; 34 | const isPackument = (metadata) => Object.hasOwnProperty.call(metadata, 'dist-tags'); 35 | const getPackage = (pkg, version = pkg['dist-tags'].latest) => pkg.versions[version]; 36 | const hasTypeField = (pkg) => !!(pkg.types || pkg.typings); 37 | const hasDependency = (pkg, pkgName) => !!(pkg.dependencies && pkg.dependencies[pkgName]); 38 | const CACHE = {}; 39 | const fetchPackument = (packageName) => new Promise((resolve, reject) => { 40 | if (CACHE[packageName]) { 41 | return resolve(CACHE[packageName]); 42 | } 43 | const path = escapeSlash(packageName); 44 | GM_xmlhttpRequest({ 45 | method: 'GET', 46 | url: `https://registry.npmjs.com/${path}`, 47 | onload(response) { 48 | try { 49 | const json = JSON.parse(response.responseText); 50 | if (isPackument(json)) { 51 | CACHE[packageName] = json; 52 | resolve(json); 53 | } 54 | else { 55 | reject(json); 56 | } 57 | } 58 | catch (error) { 59 | reject(error); 60 | } 61 | }, 62 | onerror(response) { 63 | reject(new Error(response.responseText)); 64 | } 65 | }); 66 | }); 67 | const ID = 'npm-package-support'; 68 | const createLink = (text, href) => { 69 | const link = document.createElement('a'); 70 | link.href = href; 71 | link.textContent = text; 72 | return link; 73 | }; 74 | const insertToTop = (...nodes) => { 75 | const top = document.querySelector('#top'); 76 | if (!top) { 77 | throw new Error('Not found #top'); 78 | } 79 | const p = document.createElement('p'); 80 | const span = document.createElement('span'); 81 | p.id = ID; 82 | span.setAttribute('style', 'color:green'); 83 | span.textContent = 'TYPE: '; 84 | p.append(span); 85 | p.append(...nodes); 86 | top.firstElementChild.insertAdjacentElement('beforebegin', p); 87 | }; 88 | const cleanup = () => { 89 | const p = document.getElementById(ID); 90 | if (p) 91 | p.remove(); 92 | }; 93 | const patchHistoryEvents = () => { 94 | const events = ['pushState', 'replaceState']; 95 | events.forEach(type => { 96 | const original = history[type]; 97 | history[type] = (...args) => { 98 | original.apply(history, args); 99 | dispatchEvent(new Event(type)); 100 | }; 101 | }); 102 | }; 103 | const main = async () => { 104 | cleanup(); 105 | try { 106 | const [name, version] = getPackageName(location.pathname); 107 | if (!name) 108 | throw new Error('Not found package name'); 109 | if (isDTPackage(name)) 110 | throw new Error('Package is type definitions'); 111 | const packument = await fetchPackument(name).catch(() => Promise.reject(new Error('Failed to get package'))); 112 | const pkg = getPackage(packument, version); 113 | if (hasTypeField(pkg)) 114 | return insertToTop('Package contains type definitions'); 115 | const typeName = createTypePackageName(name); 116 | if (hasDependency(pkg, typeName)) 117 | return insertToTop(`Package depends on`, createLink(typeName, `/package/${typeName}`)); 118 | const typesPackument = await fetchPackument(typeName).catch(() => Promise.reject(new Error('Does not support types'))); 119 | const typesPkg = getPackage(typesPackument); 120 | return insertToTop(createLink(typesPkg._id, `/package/${typeName}`)); 121 | } 122 | catch (error) { 123 | insertToTop(error.message); 124 | } 125 | }; 126 | patchHistoryEvents(); 127 | const events = ['pushState', 'replaceState']; 128 | events.forEach(eventName => addEventListener(eventName, main)); 129 | main(); 130 | -------------------------------------------------------------------------------- /npm-package-support-ts.user.ts: -------------------------------------------------------------------------------- 1 | /*! 2 | // ==UserScript== 3 | // @name npm package support TypeScript 4 | // @description Detects TypeScript support of npm package. 5 | // @version 1.3.0 6 | // @author 8th713 7 | // @license MIT 8 | // @homepage https://github.com/8th713/npm-package-support 9 | // @namespace http://github.com/8th713 10 | // @match https://www.npmjs.com/package/* 11 | // @grant GM_xmlhttpRequest 12 | // @connect registry.npmjs.com 13 | // ==/UserScript== 14 | 15 | Inspired by npm: package support types 16 | https://gist.github.com/azu/ec22f3def09563ece54f8dc32523b152 17 | */ 18 | 19 | /** https://github.com/npm/registry/blob/master/docs/REGISTRY-API.md#version */ 20 | interface Package { 21 | /** @ */ 22 | _id: string 23 | name: string 24 | version: string 25 | types?: string 26 | typings?: string 27 | dependencies?: { [key: string]: string } 28 | } 29 | 30 | /** https://github.com/npm/registry/blob/master/docs/REGISTRY-API.md#package */ 31 | interface Packument { 32 | name: string 33 | 'dist-tags': { [tag: string]: string } 34 | versions: { [version: string]: Package } 35 | } 36 | 37 | /** 38 | * - /package/foo -> ['foo'] 39 | * - /package/@scope/foo -> ['@scope/foo'] 40 | * - /package/foo/v/1.0.0 -> ['foo','1.0.0'] 41 | * - /package/@scope/foo/v/1.0.0 -> ['@scope/foo','1.0.0'] 42 | */ 43 | const getPackageName = (pathname: string) => { 44 | const [path, version] = pathname.split('/v/') 45 | const name = path 46 | .split('/') 47 | .slice(2) 48 | .join('/') 49 | 50 | return [name, version] 51 | } 52 | 53 | const isDTPackage = (name: string) => name.startsWith('@types/') 54 | 55 | const escapeSlash = (str: string) => str.replace(/\//g, '%2F') 56 | 57 | /** 58 | * - foo -> @types/foo 59 | * - @scope/foo -> @types/scope__foo 60 | */ 61 | const createTypePackageName = (pkgName: string) => { 62 | if (pkgName.startsWith('@')) { 63 | return `@types/${pkgName.slice(1).replace('/', '__')}` 64 | } 65 | return `@types/${pkgName}` 66 | } 67 | 68 | const isPackument = (metadata: unknown): metadata is Packument => 69 | Object.hasOwnProperty.call(metadata, 'dist-tags') 70 | 71 | /** Gets the specified `Package` of `Packument`. */ 72 | const getPackage = ( 73 | pkg: Packument, 74 | version: string = pkg['dist-tags'].latest 75 | ) => pkg.versions[version] 76 | 77 | /** Checks if the `types` field or `typings` field exists in the `Package`. */ 78 | const hasTypeField = (pkg: Package) => !!(pkg.types || pkg.typings) 79 | 80 | /** Checks if the specified package exists in the `Package`'s dependency. */ 81 | const hasDependency = (pkg: Package, pkgName: string) => 82 | !!(pkg.dependencies && pkg.dependencies[pkgName]) 83 | 84 | const CACHE: { [key: string]: Packument } = {} 85 | 86 | const fetchPackument = (packageName: string) => 87 | new Promise((resolve, reject) => { 88 | if (CACHE[packageName]) { 89 | return resolve(CACHE[packageName]) 90 | } 91 | const path = escapeSlash(packageName) 92 | GM_xmlhttpRequest({ 93 | method: 'GET', 94 | url: `https://registry.npmjs.com/${path}`, 95 | onload(response) { 96 | try { 97 | const json = JSON.parse(response.responseText) 98 | if (isPackument(json)) { 99 | CACHE[packageName] = json 100 | resolve(json) 101 | } else { 102 | reject(json) 103 | } 104 | } catch (error) { 105 | reject(error) 106 | } 107 | }, 108 | onerror(response) { 109 | reject(new Error(response.responseText)) 110 | } 111 | }) 112 | }) 113 | 114 | const ID = 'npm-package-support' 115 | 116 | const createLink = (text: string, href: string) => { 117 | const link = document.createElement('a') 118 | link.href = href 119 | link.textContent = text 120 | return link 121 | } 122 | 123 | const insertToTop = (...nodes: (string | Node)[]) => { 124 | const top = document.querySelector('#top') 125 | if (!top) { 126 | throw new Error('Not found #top') 127 | } 128 | const p = document.createElement('p') 129 | const span = document.createElement('span') 130 | p.id = ID 131 | span.setAttribute('style', 'color:green') 132 | span.textContent = 'TYPE: ' 133 | p.append(span) 134 | p.append(...nodes) 135 | top.firstElementChild!.insertAdjacentElement('beforebegin', p) 136 | } 137 | 138 | const cleanup = () => { 139 | const p = document.getElementById(ID) 140 | if (p) p.remove() 141 | } 142 | 143 | const patchHistoryEvents = () => { 144 | const events = ['pushState', 'replaceState'] as const 145 | 146 | events.forEach(type => { 147 | const original = history[type] 148 | history[type] = (...args) => { 149 | original.apply(history, args) 150 | dispatchEvent(new Event(type)) 151 | } 152 | }) 153 | } 154 | 155 | const main = async () => { 156 | cleanup() 157 | try { 158 | const [name, version] = getPackageName(location.pathname) 159 | if (!name) throw new Error('Not found package name') 160 | if (isDTPackage(name)) throw new Error('Package is type definitions') 161 | 162 | const packument = await fetchPackument(name).catch(() => 163 | Promise.reject(new Error('Failed to get package')) 164 | ) 165 | const pkg = getPackage(packument, version) 166 | if (hasTypeField(pkg)) 167 | return insertToTop('Package contains type definitions') 168 | 169 | const typeName = createTypePackageName(name) 170 | if (hasDependency(pkg, typeName)) 171 | return insertToTop( 172 | `Package depends on`, 173 | createLink(typeName, `/package/${typeName}`) 174 | ) 175 | 176 | const typesPackument = await fetchPackument(typeName).catch(() => 177 | Promise.reject(new Error('Does not support types')) 178 | ) 179 | const typesPkg = getPackage(typesPackument) 180 | return insertToTop(createLink(typesPkg._id, `/package/${typeName}`)) 181 | } catch (error) { 182 | insertToTop(error.message) 183 | } 184 | } 185 | 186 | patchHistoryEvents() 187 | 188 | const events = ['pushState', 'replaceState'] 189 | 190 | events.forEach(eventName => addEventListener(eventName, main)) 191 | main() 192 | --------------------------------------------------------------------------------