├── .gitattributes ├── .eslintignore ├── .gitignore ├── .github ├── dependabot.yml └── workflows │ ├── ci.yml │ └── dependabot-auto-merge.yml ├── devserver.js ├── src ├── utils │ ├── url.js │ ├── log.js │ ├── tracking.js │ ├── payload.js │ ├── url.spec.js │ ├── cache.spec.js │ ├── cache.js │ └── static-cache.json ├── registries │ ├── config.spec.js │ ├── xpath-helper.js │ ├── repository-url.js │ ├── repository-url.spec.js │ ├── xpath-helper.spec.js │ ├── config.json │ └── index.js ├── ping.js ├── java │ └── index.js ├── go.js ├── nuget.js └── handler.js ├── .editorconfig ├── now.json ├── scripts ├── update-cache.js └── update-mapping-files.js ├── .eslintrc.json ├── package.json ├── README.md ├── functional.spec.js └── index.html /.gitattributes: -------------------------------------------------------------------------------- 1 | * text=auto 2 | *.js text eol=lf 3 | -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | /node_modules/ 2 | /api/node_modules/ 3 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | npm-debug.log 3 | .env 4 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: npm 4 | directory: "/" 5 | schedule: 6 | interval: weekly 7 | time: "12:00" 8 | open-pull-requests-limit: 10 9 | -------------------------------------------------------------------------------- /devserver.js: -------------------------------------------------------------------------------- 1 | const http = require('http'); 2 | const handler = require('./src/handler'); 3 | 4 | http.createServer((req, res) => handler(req, res)) 5 | .listen(3000); 6 | 7 | console.log('Dev server is running'); 8 | console.log('Example request: http://localhost:3000/?npm=react'); 9 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: Node CI 2 | on: push 3 | jobs: 4 | build: 5 | runs-on: ubuntu-latest 6 | steps: 7 | - uses: actions/checkout@v1 8 | - uses: actions/setup-node@v1 9 | with: 10 | node-version: '14.x' 11 | - run: npm ci 12 | - run: npm test 13 | env: 14 | CI: true 15 | -------------------------------------------------------------------------------- /src/utils/url.js: -------------------------------------------------------------------------------- 1 | function prioritiesHost(host, urls = []) { 2 | let insertIndex = 0; 3 | return urls.reduce((memo, url) => { 4 | if (url.includes(host)) { 5 | memo.splice(insertIndex++, 0, url); // eslint-disable-line no-plusplus 6 | } else { 7 | memo.push(url); 8 | } 9 | return memo; 10 | }, []); 11 | } 12 | 13 | module.exports = { 14 | prioritiesHost, 15 | }; 16 | -------------------------------------------------------------------------------- /src/utils/log.js: -------------------------------------------------------------------------------- 1 | const name = Math.random() 2 | .toString(36) 3 | .replace(/[^a-z]+/g, '') 4 | .substr(0, 8); 5 | 6 | function log(...rest) { 7 | if (process.env.NODE_ENV === 'test') { 8 | return; 9 | } 10 | 11 | const region = process.env.NOW_REGION || ''; 12 | console.log.apply(this, [`>> ${name} ${region}:`, ...rest]); 13 | } 14 | 15 | log.prefix = name; 16 | 17 | module.exports = log; 18 | -------------------------------------------------------------------------------- /src/registries/config.spec.js: -------------------------------------------------------------------------------- 1 | const config = require('./config.json'); 2 | 3 | describe('config.json', () => { 4 | const props = ['registry', 'xpaths', 'fallback']; 5 | 6 | for (const key of Object.keys(config)) { 7 | describe(`${key}`, () => { 8 | for (const propKey of Object.keys(props)) { 9 | const prop = props[propKey]; 10 | it(`has "${prop}" property`, () => { 11 | expect(config[key][prop]).toBeDefined(); 12 | }); 13 | } 14 | }); 15 | } 16 | }); 17 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # EditorConfig helps developers define and maintain consistent 2 | # coding styles between different editors and IDEs 3 | # editorconfig.org 4 | 5 | root = true 6 | 7 | 8 | [*] 9 | 10 | # Change these settings to your own preference 11 | indent_style = space 12 | indent_size = 2 13 | 14 | # We recommend you to keep these unchanged 15 | end_of_line = lf 16 | charset = utf-8 17 | trim_trailing_whitespace = true 18 | insert_final_newline = true 19 | 20 | [*.md] 21 | trim_trailing_whitespace = false 22 | -------------------------------------------------------------------------------- /src/registries/xpath-helper.js: -------------------------------------------------------------------------------- 1 | const jpath = require('json-path'); 2 | 3 | function xpathResolver(json, selector) { 4 | try { 5 | return jpath.resolve(json, selector); 6 | } catch (err) { 7 | return ''; 8 | } 9 | } 10 | 11 | module.exports = function (json, xpaths) { 12 | return xpaths 13 | .map((selector) => xpathResolver(json, selector)) 14 | .filter( 15 | (result) => result.length && typeof result[0] === 'string' && result[0], 16 | ) 17 | .map((result) => result[0]); 18 | }; 19 | -------------------------------------------------------------------------------- /src/utils/tracking.js: -------------------------------------------------------------------------------- 1 | const Mixpanel = require('mixpanel'); 2 | const log = require('./log'); 3 | 4 | let instance; 5 | 6 | module.exports = { 7 | init: () => { 8 | if (!process.env.MIXPANEL_TOKEN) { 9 | return; 10 | } 11 | 12 | instance = Mixpanel.init(process.env.MIXPANEL_TOKEN, { 13 | protocol: 'https', 14 | }); 15 | }, 16 | 17 | track: (data) => { 18 | if (!instance) { 19 | return log('Track', data); 20 | } 21 | 22 | return new Promise((resolve) => { 23 | instance.track_batch(data, resolve); 24 | }); 25 | }, 26 | }; 27 | -------------------------------------------------------------------------------- /src/utils/payload.js: -------------------------------------------------------------------------------- 1 | const uniqWith = require('lodash.uniqwith'); 2 | const isEqual = require('lodash.isequal'); 3 | 4 | const registries = require('../registries'); 5 | 6 | const supportedTypes = ['ping', 'go', 'java', 'nuget', ...registries.supported]; 7 | 8 | module.exports = function (payload) { 9 | // Remove invalid items which does not follow format {type:'foo', target: 'bar'} 10 | // Filter out types which are not supported 11 | // Remove duplicates 12 | return uniqWith(payload, isEqual).filter( 13 | (item) => item 14 | && item.target 15 | && item.target.length 16 | && supportedTypes.includes(item.type), 17 | ); 18 | }; 19 | -------------------------------------------------------------------------------- /now.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "octolinker-api", 3 | "version": 2, 4 | "builds": [ 5 | { "src": "src/handler.js", "use": "@now/node" }, 6 | { "src": "index.html", "use": "@now/static" } 7 | ], 8 | "routes": [ 9 | { "src": "/?", "dest": "src/handler.js" } 10 | ], 11 | "regions": ["bru1", "gru1", "hnd1", "iad1", "sfo1"], 12 | "alias": ["octolinker-api.now.sh"], 13 | "env": { 14 | "MIXPANEL_TOKEN": "@mixpanel_token", 15 | "REDIS_PASSWORD_BRU1": "@redis_password_bru1", 16 | "REDIS_PASSWORD_GRU1": "@redis_password_gru1", 17 | "REDIS_PASSWORD_HND1": "@redis_password_hnd1", 18 | "REDIS_PASSWORD_IAD1": "@redis_password_iad1", 19 | "REDIS_PASSWORD_SFO1": "@redis_password_sfo1" 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /scripts/update-cache.js: -------------------------------------------------------------------------------- 1 | const fs = require('fs'); 2 | const got = require('got'); 3 | const npmStaticCache = require('../src/utils/static-cache.json'); 4 | 5 | const list = Object.keys(npmStaticCache); 6 | const newCacheFile = {}; 7 | 8 | async function next(item) { 9 | if (list.length === 0) { 10 | fs.writeFileSync('./src/utils/static-cache.json', JSON.stringify(newCacheFile, null, 2)); 11 | return; 12 | } 13 | 14 | const [type, ...target] = item.split('_'); 15 | 16 | const response = await got.post({ 17 | json: true, 18 | url: 'http://localhost:3000/', 19 | body: [{ type, target: target.join('_') }], 20 | }); 21 | 22 | if (response.body.result[0] && response.body.result[0].result) { 23 | newCacheFile[`${type}_${target}`] = response.body.result[0].result; 24 | } 25 | 26 | next(list.shift()); 27 | } 28 | 29 | next(list.shift()); 30 | -------------------------------------------------------------------------------- /src/ping.js: -------------------------------------------------------------------------------- 1 | const got = require('got'); 2 | const cache = require('./utils/cache'); 3 | 4 | const ERR_PING_NOT_FOUND = 'ERR_PING_NOT_FOUND'; 5 | 6 | module.exports = async function (url) { 7 | const cacheKey = `ping_${url}`; 8 | 9 | const cacheValue = await cache.get(cacheKey); 10 | 11 | if (cacheValue) { 12 | if (cacheValue !== ERR_PING_NOT_FOUND) { 13 | return cacheValue; 14 | } 15 | 16 | return undefined; 17 | } 18 | 19 | if (!url.startsWith('https://')) { 20 | return undefined; 21 | } 22 | 23 | return got 24 | .head(url, { 25 | timeout: 500, 26 | retry: 0, 27 | }) 28 | .then(async () => { 29 | await cache.set(cacheKey, url); 30 | 31 | return url; 32 | }) 33 | .catch(async () => { 34 | await cache.set(cacheKey, ERR_PING_NOT_FOUND); 35 | 36 | return undefined; 37 | }); 38 | }; 39 | -------------------------------------------------------------------------------- /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "airbnb-base", 3 | "plugins": ["jest"], 4 | "env": { 5 | "node": true, 6 | "jest": true 7 | }, 8 | "rules": { 9 | "func-names": 0, 10 | "id-length": [1, { "exceptions": ["$"] }], 11 | "new-cap": [2, { "capIsNewExceptions": ["Deferred"] }], 12 | "max-len": 0, 13 | "no-prototype-builtins": 0, 14 | "no-console": 0, 15 | "no-underscore-dangle": 0, 16 | "no-useless-escape": 0, 17 | "class-methods-use-this": 0, 18 | "no-param-reassign": 0, 19 | "no-restricted-syntax": 0, 20 | "consistent-return": 0, 21 | "array-callback-return": 0, 22 | "jest/no-disabled-tests": 2, 23 | "jest/no-focused-tests": 2, 24 | "jest/no-identical-title": 2, 25 | "jest/consistent-test-it": ["error", { "fn": "it" }], 26 | "jest/prefer-to-be": 2, 27 | "jest/prefer-to-have-length": 2, 28 | "jest/valid-expect": 2 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/registries/repository-url.js: -------------------------------------------------------------------------------- 1 | const nodeUrl = require('url'); 2 | const githubUrl = require('github-url-to-object'); 3 | 4 | module.exports = function (url) { 5 | if (typeof url !== 'string') { 6 | return null; 7 | } 8 | 9 | // Remove last trailing slash 10 | if (url.slice(-1) === '/') { 11 | url = url.slice(0, -1); 12 | } 13 | 14 | // Fix multiple forward slashes 15 | url = url.replace(/([^:]\/)\/+/g, '$1'); 16 | 17 | // Resolve shorthand url to a qualified URL 18 | if (url.split('/').length === 2) { 19 | url = `http://github.com/${url}`; 20 | } 21 | 22 | // Replace and fix invalid urls 23 | url = url.replace('https+git://', 'git+https://'); 24 | url = url.replace('://www.github.com', '://github.com'); 25 | 26 | // Ensure there is a protocol (`github-url-to-object` needs it) 27 | if (nodeUrl.parse(url).protocol === null) { 28 | url = `https://${url}`; 29 | } 30 | 31 | const githubInfo = githubUrl(url); 32 | return githubInfo ? githubInfo.https_url : url; 33 | }; 34 | -------------------------------------------------------------------------------- /src/registries/repository-url.spec.js: -------------------------------------------------------------------------------- 1 | const findRepositoryUrl = require('./repository-url'); 2 | 3 | describe('repository url', () => { 4 | const urls = [ 5 | 'github.com/john/doe/', 6 | 'http://github.com/john/doe/', 7 | 'https://github.com/john/doe', 8 | 'https://github.com/john/doe/', 9 | 'https:///github.com/john/doe/', 10 | 'https+git://github.com/john/doe/', 11 | 'https://www.github.com/john/doe/', 12 | 'http://github.com/john/doe/tree/master', 13 | 'https://github.com/john/doe/tree/master', 14 | 'john/doe', 15 | 'john/doe/', 16 | ]; 17 | 18 | urls.forEach((node) => { 19 | let type = node; 20 | if (typeof node !== 'string') { 21 | type = JSON.stringify(node); 22 | } 23 | 24 | it(`resolves ${type}`, () => { 25 | expect(findRepositoryUrl(node)).toBe('https://github.com/john/doe'); 26 | }); 27 | }); 28 | 29 | const detailUrl = 'https://github.com/john/doe/tree/master/foo'; 30 | it(`resolves ${detailUrl}`, () => { 31 | expect(findRepositoryUrl(detailUrl)).toBe( 32 | 'https://github.com/john/doe/tree/master/foo', 33 | ); 34 | }); 35 | }); 36 | -------------------------------------------------------------------------------- /src/utils/url.spec.js: -------------------------------------------------------------------------------- 1 | const { prioritiesHost } = require('./url'); 2 | 3 | const inputUrls = [ 4 | 'https://foo.com', 5 | 'https://github.com/foo', 6 | 'https://bar.com', 7 | 'https://github.com/bar', 8 | ]; 9 | 10 | describe('url', () => { 11 | describe('prioritiesHost', () => { 12 | it('priorities github.com', () => { 13 | expect(prioritiesHost('github.com', inputUrls)).toStrictEqual([ 14 | 'https://github.com/foo', 15 | 'https://github.com/bar', 16 | 'https://foo.com', 17 | 'https://bar.com', 18 | ]); 19 | }); 20 | 21 | it('priorities foo.com', () => { 22 | expect(prioritiesHost('foo.com', inputUrls)).toStrictEqual([ 23 | 'https://foo.com', 24 | 'https://github.com/foo', 25 | 'https://bar.com', 26 | 'https://github.com/bar', 27 | ]); 28 | }); 29 | 30 | it('priorities bar.com', () => { 31 | expect(prioritiesHost('bar.com', inputUrls)).toStrictEqual([ 32 | 'https://bar.com', 33 | 'https://foo.com', 34 | 'https://github.com/foo', 35 | 'https://github.com/bar', 36 | ]); 37 | }); 38 | }); 39 | }); 40 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "octolinker-api", 3 | "version": "2.0.0", 4 | "description": "", 5 | "scripts": { 6 | "pretest": "eslint .", 7 | "dev": "nodemon devserver.js", 8 | "test": "jest" 9 | }, 10 | "dependencies": { 11 | "find-reachable-urls": "^1.1.1", 12 | "github-url-to-object": "^4.0.6", 13 | "got": "^9.5.0", 14 | "ioredis": "^4.28.5", 15 | "is-url": "^1.2.4", 16 | "json-path": "^0.1.3", 17 | "lets-get-meta": "^2.1.1", 18 | "lodash.isequal": "^4.5.0", 19 | "lodash.uniqwith": "^4.5.0", 20 | "micro": "^9.3.3", 21 | "mixpanel": "^0.14.0", 22 | "p-map": "^4.0.0", 23 | "tldjs": "^2.3.1", 24 | "xml-js": "^1.6.11" 25 | }, 26 | "peerDependencies": { 27 | "nodemon": "*" 28 | }, 29 | "devDependencies": { 30 | "jsdom": "^19.0.0", 31 | "eslint": "^8.15.0", 32 | "eslint-config-airbnb-base": "^15.0.0", 33 | "eslint-plugin-import": "^2.26.0", 34 | "eslint-plugin-jest": "^26.2.2", 35 | "got": "^9.5.1", 36 | "jest": "^28.1.0", 37 | "prettier": "^2.6.2" 38 | }, 39 | "engines": { 40 | "node": "14.x" 41 | }, 42 | "keywords": [], 43 | "author": "Stefan Buck", 44 | "license": "MIT" 45 | } 46 | -------------------------------------------------------------------------------- /src/java/index.js: -------------------------------------------------------------------------------- 1 | const findReachableUrls = require('find-reachable-urls'); 2 | const flatMappingList = require('./mapping.json'); 3 | const cache = require('../utils/cache'); 4 | 5 | const SUPPORTED_JAVA_VERSIONS = [9, 8, 7]; 6 | 7 | module.exports = async function (pkg) { 8 | const targetAsPath = pkg.replace(/\./g, '/'); 9 | const isBuildIn = !!pkg.match(/^javax?/); 10 | 11 | if (!isBuildIn) { 12 | const url = flatMappingList[pkg]; 13 | 14 | if (url) { 15 | return url; 16 | } 17 | } 18 | 19 | const cacheKey = `java_${pkg}`; 20 | const cacheValue = await cache.get(cacheKey); 21 | 22 | if (cacheValue) { 23 | return cacheValue; 24 | } 25 | 26 | const urls = SUPPORTED_JAVA_VERSIONS.reduce( 27 | (memo, version) => memo.concat( 28 | `https://docs.oracle.com/javase/${version}/docs/api/${targetAsPath}.html`, 29 | `https://docs.oracle.com/javaee/${version}/api/${targetAsPath}.html`, 30 | ), 31 | [], 32 | ); 33 | 34 | const reachableUrl = await findReachableUrls( 35 | urls, 36 | { firstMatch: true }, 37 | ); 38 | 39 | if (!reachableUrl) { 40 | return undefined; 41 | } 42 | 43 | await cache.set(cacheKey, reachableUrl); 44 | 45 | return reachableUrl; 46 | }; 47 | -------------------------------------------------------------------------------- /src/registries/xpath-helper.spec.js: -------------------------------------------------------------------------------- 1 | const jpath = require('json-path'); 2 | const xpathHelper = require('./xpath-helper'); 3 | 4 | jest.mock('json-path'); 5 | 6 | describe('xpath-helper', () => { 7 | beforeEach(() => { 8 | jpath.resolve.mockReturnValue([]); 9 | }); 10 | 11 | afterEach(() => jpath.resolve.mockClear()); 12 | 13 | describe('jpath', () => { 14 | const json = { 15 | foo: '', 16 | bar: 'blub', 17 | }; 18 | const xpaths = ['/foo', '/bar']; 19 | 20 | it('calls jpath.resolve for each xpath entry', () => { 21 | xpathHelper({}, xpaths); 22 | 23 | expect(jpath.resolve.mock.calls).toHaveLength(xpaths.length); 24 | expect(jpath.resolve.mock.calls[0][1]).toBe(xpaths[0]); 25 | expect(jpath.resolve.mock.calls[1][1]).toBe(xpaths[1]); 26 | }); 27 | 28 | it('calls jpath.resolve with json passed in', () => { 29 | xpathHelper(json, xpaths); 30 | 31 | expect(jpath.resolve.mock.calls[0][0]).toBe(json); 32 | }); 33 | 34 | it('ignores empty values', () => { 35 | jpath.resolve.mockReturnValueOnce(['']); 36 | jpath.resolve.mockReturnValueOnce(['blub']); 37 | 38 | const result = xpathHelper(json, xpaths); 39 | expect(result).toEqual(['blub']); 40 | }); 41 | }); 42 | }); 43 | -------------------------------------------------------------------------------- /.github/workflows/dependabot-auto-merge.yml: -------------------------------------------------------------------------------- 1 | # https://gist.github.com/xt0rted/46475099dc0a70ba63e16e3177407872 2 | name: Dependabot auto-merge 3 | 4 | on: pull_request_target 5 | 6 | permissions: 7 | contents: read 8 | pull-requests: read 9 | 10 | jobs: 11 | dependabot: 12 | runs-on: ubuntu-latest 13 | 14 | if: ${{ github.actor == 'dependabot[bot]' }} 15 | 16 | steps: 17 | - name: Generate token 18 | id: generate_token 19 | uses: tibdex/github-app-token@v1.5.1 20 | with: 21 | app_id: ${{ secrets.BOT_APP_ID }} 22 | private_key: ${{ secrets.BOT_PRIVATE_KEY }} 23 | 24 | - name: Dependabot metadata 25 | id: dependabot_metadata 26 | uses: dependabot/fetch-metadata@v1.1.1 27 | with: 28 | github-token: ${{ secrets.GITHUB_TOKEN }} 29 | 30 | - name: Authenticate cli 31 | run: echo "${{ steps.generate_token.outputs.token }}" | gh auth login --with-token 32 | 33 | - name: Enable auto-merge for Dependabot PRs 34 | if: ${{ steps.dependabot_metadata.outputs.update-type == 'version-update:semver-minor' || steps.dependabot_metadata.outputs.update-type == 'version-update:semver-patch' }} 35 | run: gh pr merge --auto --squash "$PR_URL" 36 | env: 37 | PR_URL: ${{ github.event.pull_request.html_url }} 38 | -------------------------------------------------------------------------------- /src/go.js: -------------------------------------------------------------------------------- 1 | const findReachableUrls = require('find-reachable-urls'); 2 | const readMeta = require('lets-get-meta'); 3 | const got = require('got'); 4 | const { tldExists } = require('tldjs'); 5 | 6 | const cache = require('./utils/cache'); 7 | const log = require('./utils/log'); 8 | 9 | const getGoMeta = async (url) => { 10 | const response = await got.get(url); 11 | const meta = readMeta(response.body); 12 | 13 | if (!meta['go-source']) { 14 | throw new Error('go-source meta is missing'); 15 | } 16 | 17 | const values = meta['go-source'].replace(/\s+/g, ' ').split(' '); 18 | 19 | return { 20 | projectRoot: values[0], 21 | projectUrl: values[1], 22 | dirTemplate: values[2].replace('{/dir}', ''), 23 | }; 24 | }; 25 | 26 | const resolveUrl = async (url) => { 27 | let goMetaConfig; 28 | 29 | const cacheKey = `go_${url}`; 30 | const cacheValue = await cache.get(cacheKey); 31 | if (cacheValue) { 32 | return cacheValue; 33 | } 34 | 35 | if (!tldExists(`http://${url}`)) { 36 | log(`"http://${url}" is not a valid hostname`); 37 | return undefined; 38 | } 39 | 40 | try { 41 | // Preferred with https 42 | goMetaConfig = await getGoMeta(`https://${url}?go-get=1`); 43 | } catch (err) { 44 | // Fallback insecure 45 | goMetaConfig = await getGoMeta(`http://${url}?go-get=1`); 46 | } 47 | 48 | const reachableUrl = await findReachableUrls( 49 | [ 50 | url.replace(goMetaConfig.projectRoot, goMetaConfig.dirTemplate), 51 | goMetaConfig.projectUrl, 52 | ], 53 | { firstMatch: true }, 54 | ); 55 | 56 | if (!reachableUrl) { 57 | throw new Error('No url is reachable'); 58 | } 59 | 60 | await cache.set(cacheKey, reachableUrl); 61 | 62 | return reachableUrl; 63 | }; 64 | 65 | module.exports = async function (pkg) { 66 | try { 67 | return await resolveUrl(pkg); 68 | } catch (err) { 69 | if (err.code === 'ENOTFOUND') { 70 | log('ENOTFOUND', err.url); 71 | } else { 72 | log(err); 73 | } 74 | return undefined; 75 | } 76 | }; 77 | -------------------------------------------------------------------------------- /src/registries/config.json: -------------------------------------------------------------------------------- 1 | { 2 | "bower": { 3 | "registry": "https://registry.bower.io/packages/%s", 4 | "fallback": "https://bower.io/search/?q=%s", 5 | "xpaths": [ 6 | "/url" 7 | ] 8 | }, 9 | "composer": { 10 | "registry": "https://packagist.org/packages/%s.json", 11 | "fallback": "https://packagist.org/packages/%s", 12 | "xpaths": [ 13 | "/package/repository" 14 | ] 15 | }, 16 | "rubygems": { 17 | "registry": "https://rubygems.org/api/v1/gems/%s.json", 18 | "fallback": "https://rubygems.org/search?query=%s", 19 | "xpaths": [ 20 | "/source_code_uri", 21 | "/homepage_uri", 22 | "/project_uri" 23 | ] 24 | }, 25 | "cran": { 26 | "registry": "https://crandb.r-pkg.org/%s", 27 | "fallback": "https://github.com/cran/%s", 28 | "xpaths": [ 29 | "/URL" 30 | ] 31 | }, 32 | "npm": { 33 | "registry": "https://registry.npmjs.org/%s", 34 | "fallback": "https://www.npmjs.com/package/%s", 35 | "xpaths": [ 36 | "/url", 37 | "/repository", 38 | "/repository/url", 39 | "/repository/path", 40 | "/repository/web", 41 | "/repository/git", 42 | "/repositories", 43 | "/repositories/url", 44 | "/repositories/path", 45 | "/repositories/web", 46 | "/repositories/git", 47 | "/homepage" 48 | ] 49 | }, 50 | "pypi": { 51 | "registry": "https://pypi.python.org/pypi/%s/json", 52 | "fallback": "https://pypi.python.org/pypi/%s", 53 | "xpaths": [ 54 | "/info/home_page", 55 | "/info/package_url" 56 | ] 57 | }, 58 | "pub": { 59 | "registry": "https://pub.dev/api/packages/%s", 60 | "fallback": "https://pub.dev/packages/%s", 61 | "xpaths": [ 62 | "/latest/pubspec/homepage" 63 | ] 64 | }, 65 | "crates": { 66 | "registry": "https://crates.io/api/v1/crates/%s", 67 | "fallback": "https://crates.io/crates/%s", 68 | "xpaths": [ 69 | "/crate/repository", 70 | "/crate/homepage" 71 | ] 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # OctoLinker API 2 | 3 | Returns the github repository url for the requested package. 4 | 5 | The supported registries are: 6 | 7 | - [npmjs.com](https://npmjs.com) 8 | - [bower.io](http:/bower.io) 9 | - [getcomposer.org](https://getcomposer.org) 10 | - [rubygems.org](https://rubygems.org) 11 | - [pypi.python.org](https://pypi.python.org) 12 | - [crates.io](https://crates.io) 13 | - [melpa.org](https://melpa.org) 14 | - [pub.dev](https://pub.dev) 15 | - [nuget.org](https://www.nuget.org/) 16 | 17 | Example: 18 | 19 | [POST] https://octolinker-api.now.sh/ 20 | 21 | Body: 22 | 23 | ```json 24 | [ 25 | { "type": "npm", "target": "react" }, 26 | { "type": "composer", "target": "phpunit/phpunit" }, 27 | { "type": "ping", "target": "https://github.com" }, 28 | { "type": "npm", "target": "unknown-package" } 29 | ] 30 | ``` 31 | 32 | or as 33 | 34 | [GET] https://octolinker-api.now.sh/?npm=react,lodash&composer=phpunit/phpunit 35 | 36 | Type must be one of: 37 | 38 | - `npm` 39 | - `bower` 40 | - `composer` 41 | - `rubygems` 42 | - `pypi` 43 | - `crates` 44 | - `java` 45 | - `go` 46 | - `pub` 47 | - `cran` 48 | - `nuget` 49 | - `ping` 50 | 51 | Response: 52 | 53 | ```json 54 | { 55 | "result": [ 56 | { 57 | "type": "npm", 58 | "target": "react", 59 | "result": "https://github.com/facebook/react" 60 | }, 61 | { 62 | "type": "composer", 63 | "target": "phpunit/phpunit", 64 | "result": "https://github.com/sebastianbergmann/phpunit" 65 | }, 66 | { 67 | "type": "ping", 68 | "target": "https://github.com", 69 | "result": "https://github.com" 70 | }, 71 | { 72 | "type": "npm", 73 | "target": "unknown-package" 74 | } 75 | ] 76 | } 77 | ``` 78 | 79 | ## Installation 80 | 81 | Install dependencies: 82 | 83 | `npm install` 84 | 85 | Run server: 86 | 87 | `npm run dev` 88 | 89 | ## Testing 90 | 91 | `npm test` 92 | 93 | ## Privacy Policy 94 | 95 | Our [Privacy Policy](https://octolinker.now.sh/privacy/) describes our practices related to the use, storage and disclosure of information we collect when you're using our service. 96 | 97 | ## License 98 | 99 | Copyright (c) 2015–present [Stefan Buck](https://stefanbuck.com/) and [other contributors](https://github.com/OctoLinker/api/graphs/contributors). 100 | Licensed under the MIT license. 101 | -------------------------------------------------------------------------------- /src/registries/index.js: -------------------------------------------------------------------------------- 1 | const util = require('util'); 2 | const got = require('got'); 3 | const isUrl = require('is-url'); 4 | const findReachableUrls = require('find-reachable-urls'); 5 | const repositoryUrl = require('./repository-url'); 6 | const xpathHelper = require('./xpath-helper'); 7 | const registryConfig = require('./config.json'); 8 | const cache = require('../utils/cache'); 9 | const log = require('../utils/log'); 10 | const { prioritiesHost } = require('../utils/url'); 11 | 12 | const ERR_PACKAGE_NOT_FOUND = 'ERR_PACKAGE_NOT_FOUND'; 13 | 14 | async function resolve(type, packageName) { 15 | const cacheKey = `${type}_${packageName}`; 16 | 17 | const cacheValue = await cache.get(cacheKey); 18 | 19 | if (cacheValue) { 20 | if (cacheValue !== ERR_PACKAGE_NOT_FOUND) { 21 | return cacheValue; 22 | } 23 | 24 | return undefined; 25 | } 26 | 27 | const config = registryConfig[type]; 28 | 29 | if (!config) { 30 | return; 31 | } 32 | 33 | const requestUrl = util.format( 34 | config.registry, 35 | packageName.replace(/\//g, '%2f'), 36 | ); 37 | 38 | let response; 39 | try { 40 | response = await got.get(requestUrl); 41 | } catch (err) { 42 | if (err.statusCode === 404) { 43 | log('Package not found', packageName, type); 44 | await cache.set(cacheKey, ERR_PACKAGE_NOT_FOUND); 45 | return; 46 | } 47 | 48 | return log(err); 49 | } 50 | let json; 51 | 52 | try { 53 | json = JSON.parse(response.body); 54 | } catch (err) { 55 | log('Parsing response failed'); 56 | await cache.set(cacheKey, ERR_PACKAGE_NOT_FOUND); 57 | return; 58 | } 59 | 60 | let urls = xpathHelper(json, config.xpaths); 61 | 62 | if (type === 'npm') { 63 | if (json.repository && json.repository.directory) { 64 | urls.unshift(`${repositoryUrl(json.repository.url)}/tree/master/${json.repository.directory}`); 65 | } 66 | 67 | try { 68 | urls.push( 69 | ...json.maintainers.map(({ name }) => `${name}/${packageName}`), 70 | ); 71 | } catch (err) { 72 | // 73 | } 74 | } 75 | 76 | if (type === 'cran') { 77 | // Some packages export multiple urls seperated by comma. 78 | urls = urls.map((url) => url.split(',').map((str) => str.trim())).flat(); 79 | 80 | urls = prioritiesHost('https://github.com', urls); 81 | } 82 | 83 | const validUrls = urls.map((bestMatchUrl) => { 84 | try { 85 | let url = repositoryUrl(bestMatchUrl); 86 | 87 | if (!url && isUrl(bestMatchUrl)) { 88 | url = bestMatchUrl; 89 | } 90 | 91 | // http://localhost:3000/npm/uiautomatorwd 92 | // returns https:github.com/macacajs/uiautomatorwd.git which cause a timeout 93 | 94 | return url; 95 | } catch (err) { 96 | return false; 97 | } 98 | }); 99 | 100 | const fallbackUrl = util.format(config.fallback, packageName); 101 | const tryUrls = validUrls.concat(fallbackUrl); 102 | const reachableUrl = await findReachableUrls(tryUrls, { firstMatch: true }); 103 | 104 | if (!reachableUrl) { 105 | log('No URL for package found'); 106 | await cache.set(cacheKey, ERR_PACKAGE_NOT_FOUND); 107 | return; 108 | } 109 | 110 | await cache.set(cacheKey, reachableUrl); 111 | 112 | return reachableUrl; 113 | } 114 | 115 | module.exports = { 116 | supported: Object.keys(registryConfig), 117 | resolve, 118 | }; 119 | -------------------------------------------------------------------------------- /src/nuget.js: -------------------------------------------------------------------------------- 1 | const findReachableUrls = require('find-reachable-urls'); 2 | const got = require('got'); 3 | const isUrl = require('is-url'); 4 | const { xml2js } = require('xml-js'); 5 | 6 | const cache = require('./utils/cache'); 7 | const log = require('./utils/log'); 8 | const repositoryUrl = require('./registries/repository-url'); 9 | 10 | const getLatestVersion = async (pkg) => { 11 | let response; 12 | try { 13 | response = await got.get(`https://api.nuget.org/v3-flatcontainer/${pkg}/index.json`); 14 | } catch (err) { 15 | if (err.statusCode === 404) { 16 | log('Package not found', pkg); 17 | return; 18 | } 19 | 20 | log(err); 21 | return; 22 | } 23 | 24 | let json; 25 | try { 26 | json = JSON.parse(response.body); 27 | } catch (err) { 28 | log('Parsing response failed'); 29 | return; 30 | } 31 | 32 | // The version list is in ascending order so we take the last one and hope it has the best url data 33 | const [version] = json.versions.slice(-1); 34 | 35 | return version; 36 | }; 37 | 38 | const getProjectUrls = async (pkg, version) => { 39 | let response; 40 | try { 41 | response = await got.get(`https://api.nuget.org/v3-flatcontainer/${pkg}/${version}/${pkg}.nuspec`); 42 | } catch (err) { 43 | if (err.statusCode === 404) { 44 | log('Package not found', pkg); 45 | return; 46 | } 47 | 48 | log(err); 49 | return; 50 | } 51 | 52 | let json; 53 | try { 54 | json = xml2js(response.body, { compact: true }); 55 | } catch (err) { 56 | log('Parsing response failed'); 57 | return; 58 | } 59 | 60 | // Parsing is based on this version of the schema 61 | // https://github.com/NuGet/NuGet.Client/blob/b82834821e19fd2a0489ef66f786939a38d435b5/src/NuGet.Core/NuGet.Packaging/compiler/resources/nuspec.xsd 62 | const { metadata: { repository, projectUrl: project } } = json.package; 63 | 64 | const urls = []; 65 | 66 | if (repository && repository._attributes) { 67 | const { url } = repository._attributes; 68 | urls.push(url); 69 | } 70 | 71 | if (project) { 72 | const url = project._text; 73 | 74 | // We only want to use the project url if it points to github because a lot of the times these go to corporate, project, or personal websites and aren't useful for us 75 | if (url && url.startsWith('https://github.com')) { 76 | urls.push(url); 77 | } 78 | } 79 | 80 | return urls; 81 | }; 82 | 83 | module.exports = async (pkg) => { 84 | pkg = pkg.toLowerCase(); 85 | 86 | const cacheKey = `nuget_${pkg}`; 87 | const cacheValue = await cache.get(cacheKey); 88 | 89 | if (cacheValue) { 90 | return cacheValue; 91 | } 92 | 93 | const packageVersion = await getLatestVersion(pkg); 94 | 95 | if (!packageVersion) { 96 | return undefined; 97 | } 98 | 99 | const urls = await getProjectUrls(pkg, packageVersion); 100 | 101 | const validUrls = urls.map((bestMatchUrl) => { 102 | try { 103 | let url = repositoryUrl(bestMatchUrl); 104 | 105 | if (!url && isUrl(bestMatchUrl)) { 106 | url = bestMatchUrl; 107 | } 108 | 109 | return url; 110 | } catch (err) { 111 | return false; 112 | } 113 | }); 114 | 115 | const reachableUrl = await findReachableUrls(validUrls, { firstMatch: true }); 116 | 117 | if (!reachableUrl) { 118 | log('No URL for package found, falling back to nuget.org'); 119 | return `https://www.nuget.org/packages/${pkg}/`; 120 | } 121 | 122 | await cache.set(cacheKey, reachableUrl); 123 | 124 | return reachableUrl; 125 | }; 126 | -------------------------------------------------------------------------------- /src/utils/cache.spec.js: -------------------------------------------------------------------------------- 1 | const redis = require('ioredis'); 2 | const cache = require('./cache'); 3 | require('./log'); 4 | 5 | jest.mock('./log'); 6 | jest.mock('ioredis'); 7 | 8 | describe('cache', () => { 9 | let redisInstance; 10 | 11 | function invokeEventHandler(calls, eventName) { 12 | const item = calls.find(([name]) => name === eventName); 13 | if (item && item[1]) { 14 | return item[1](); 15 | } 16 | 17 | throw new Error(`No handler for ${eventName} found`); 18 | } 19 | 20 | function instantiateAuth(eventName, status) { 21 | const promise = cache.auth(); 22 | [redisInstance] = redis.mock.instances; 23 | invokeEventHandler(redisInstance.on.mock.calls, eventName); 24 | redisInstance.status = status; 25 | return promise; 26 | } 27 | 28 | afterEach(() => { 29 | // Force new redis instance creation 30 | redis.mock.instances[0].status = null; 31 | 32 | // Clear memory cache 33 | cache._memoryCache.clear(); 34 | 35 | jest.clearAllMocks(); 36 | }); 37 | 38 | describe('auth', () => { 39 | it('resolves when redis emits "ready" event', () => { 40 | instantiateAuth('ready', 'ready'); 41 | }); 42 | 43 | it('resolves when redis emits "error" event', () => { 44 | const promise = instantiateAuth('error', null); 45 | expect(redisInstance.disconnect).toHaveBeenCalledTimes(1); 46 | return promise; 47 | }); 48 | }); 49 | 50 | describe('get', () => { 51 | describe('when not connected to redis', () => { 52 | it('reads value from memory cache', async () => { 53 | instantiateAuth('error', null); 54 | cache._memoryCache.set('memory-foo', 'memory-bar'); 55 | const result = await cache.get('memory-foo'); 56 | expect(result).toBe('memory-bar'); 57 | }); 58 | }); 59 | 60 | describe('when connected to redis', () => { 61 | it('reads value form redis cache', async () => { 62 | instantiateAuth('ready', 'ready'); 63 | 64 | redisInstance.get.mockReturnValue('redis-bar'); 65 | const result = await cache.get('redis-foo'); 66 | expect(result).toBe('redis-bar'); 67 | }); 68 | }); 69 | }); 70 | 71 | describe('set', () => { 72 | describe('when not connected to redis', () => { 73 | it('writes value to memory cache', async () => { 74 | instantiateAuth('error', null); 75 | 76 | cache.set('memory-foo', 'memory-bar'); 77 | const result = await cache.get('memory-foo'); 78 | expect(result).toBe('memory-bar'); 79 | }); 80 | }); 81 | 82 | describe('when connected to redis', () => { 83 | it('writes value to redis cache', async () => { 84 | instantiateAuth('ready', 'ready'); 85 | 86 | cache.set('redis-foo', 'redis-bar'); 87 | expect(redisInstance.set).toBeCalledWith( 88 | 'redis-foo', 89 | 'redis-bar', 90 | 'EX', 91 | 3600 * 48, 92 | ); 93 | }); 94 | }); 95 | }); 96 | 97 | describe('getRedisStatus', () => { 98 | it('returns ready status', async () => { 99 | instantiateAuth('ready', 'ready'); 100 | expect(cache.getRedisStatus()).toBe('ready'); 101 | }); 102 | 103 | it('returns reconneting status', async () => { 104 | instantiateAuth('ready', 'reconneting'); 105 | expect(cache.getRedisStatus()).toBe('reconneting'); 106 | }); 107 | }); 108 | 109 | describe('simpleCacheSize', () => { 110 | it('returns the size of the memory cache', async () => { 111 | instantiateAuth('ready', 'ready'); 112 | cache._memoryCache.set('foo'); 113 | cache._memoryCache.set('bar'); 114 | expect(cache.simpleCacheSize()).toBe(2); 115 | }); 116 | }); 117 | }); 118 | -------------------------------------------------------------------------------- /src/handler.js: -------------------------------------------------------------------------------- 1 | const { parse } = require('url'); 2 | const { json } = require('micro'); 3 | const pMap = require('p-map'); 4 | 5 | const go = require('./go'); 6 | const java = require('./java'); 7 | const nuget = require('./nuget'); 8 | const ping = require('./ping'); 9 | const registries = require('./registries'); 10 | 11 | const log = require('./utils/log'); 12 | const cache = require('./utils/cache'); 13 | const tracking = require('./utils/tracking'); 14 | const preparePayload = require('./utils/payload'); 15 | 16 | const logPrefix = log.prefix; 17 | 18 | const mapper = async (item) => { 19 | let result; 20 | 21 | if (registries.supported.includes(item.type)) { 22 | result = await registries.resolve(item.type, item.target); 23 | } else if (item.type === 'go') { 24 | result = await go(item.target); 25 | } else if (item.type === 'java') { 26 | result = await java(item.target); 27 | } else if (item.type === 'ping') { 28 | result = await ping(item.target); 29 | } else if (item.type === 'nuget') { 30 | result = await nuget(item.target); 31 | } else { 32 | return; 33 | } 34 | 35 | return { 36 | ...item, 37 | result, 38 | }; 39 | }; 40 | 41 | async function requestHandler(payload) { 42 | return pMap(payload, mapper, { concurrency: 5 }); 43 | } 44 | 45 | function errorHandler(error, res) { 46 | log(error); 47 | res.statusCode = 500; 48 | res.end('Internal server error'); 49 | } 50 | 51 | tracking.init(); 52 | 53 | module.exports = async (req, res) => { 54 | if (['POST', 'GET'].includes(req.method)) { 55 | const timingTotalStart = Date.now(); 56 | 57 | let body; 58 | if (req.method === 'POST') { 59 | body = await json(req); 60 | } else { 61 | const { query } = parse(req.url, true); 62 | body = [].concat( 63 | ...Object.entries(query).map(([type, values]) => values.split(',').map((target) => ({ 64 | type, 65 | target, 66 | }))), 67 | ); 68 | } 69 | 70 | const timingCacheAuthStart = Date.now(); 71 | await cache.auth(); 72 | const timingCacheAuth = Date.now() - timingCacheAuthStart; 73 | 74 | let result = []; 75 | let timingTotalEnd; 76 | let completed = false; 77 | const payload = preparePayload(body); 78 | 79 | try { 80 | result = await requestHandler(payload); 81 | completed = true; 82 | } catch (error) { 83 | return errorHandler(error, res); 84 | } finally { 85 | timingTotalEnd = Date.now(); 86 | 87 | log('Request completed', completed); 88 | log('Instance Name', logPrefix); 89 | log('Redis Status', cache.getRedisStatus()); 90 | log('Redis Auth Duration', timingCacheAuth); 91 | log('Simple Cache Size', cache.simpleCacheSize()); 92 | log('Result Length', (result && result.length) || 0); 93 | log('Duration', timingTotalEnd - timingTotalStart); 94 | 95 | const meta = { 96 | event: 'meta', 97 | properties: { 98 | Region: process.env.NOW_REGION || 'unknown', 99 | 'Request completed': completed, 100 | 'Instance Name': logPrefix, 101 | 'Redis Status': cache.getRedisStatus(), 102 | 'Redis Auth Duration': timingCacheAuth, 103 | 'Simple Cache Size': cache.simpleCacheSize(), 104 | 'Result Length': (result && result.length) || 0, 105 | Duration: timingTotalEnd - timingTotalStart, 106 | }, 107 | }; 108 | 109 | const bulkData = [ 110 | meta, 111 | ...result.map((item) => ({ 112 | event: 'packages', 113 | properties: { 114 | Type: item.type, 115 | Target: item.target, 116 | Result: item.result, 117 | }, 118 | })), 119 | ]; 120 | 121 | if (process.env.NODE_ENV !== 'test') { 122 | console.time('Mixpanel'); 123 | await tracking.track(bulkData); 124 | console.timeEnd('Mixpanel'); 125 | } 126 | } 127 | 128 | res.setHeader('Access-Control-Allow-Origin', '*'); 129 | 130 | if (req.method === 'GET') { 131 | res.setHeader('Cache-Control', 'public, max-age=300, s-maxage=300, stale-while-revalidate'); 132 | } 133 | 134 | return res.end( 135 | JSON.stringify({ 136 | result, 137 | }), 138 | ); 139 | } 140 | 141 | res.setHeader('Access-Control-Allow-Origin', '*'); 142 | res.setHeader('Access-Control-Allow-Headers', 'content-type'); 143 | res.end(); 144 | }; 145 | -------------------------------------------------------------------------------- /functional.spec.js: -------------------------------------------------------------------------------- 1 | const http = require('http'); 2 | const got = require('got'); 3 | 4 | const handler = require('./src/handler'); 5 | 6 | describe('functional', () => { 7 | let server; 8 | 9 | beforeAll( 10 | () => new Promise((resolve) => { 11 | server = http 12 | .createServer((req, res) => handler(req, res)) 13 | .listen(3000); 14 | 15 | resolve(); 16 | }), 17 | ); 18 | 19 | afterAll((done) => { server.close(done); }); 20 | 21 | function testBulk(type, target, result) { 22 | it(`resolves ${target} from ${type} to ${result}`, async () => { 23 | const response = await got.post({ 24 | json: true, 25 | url: 'http://localhost:3000/', 26 | body: [{ type, target }], 27 | }); 28 | expect(response.body).toEqual({ 29 | result: [ 30 | { 31 | result, 32 | target, 33 | type, 34 | }, 35 | ], 36 | }); 37 | }); 38 | } 39 | 40 | it('supports GET', async () => { 41 | const response = await got.get({ 42 | json: true, 43 | url: 44 | 'http://localhost:3000/?npm=jquery,request&go=k8s.io/kubernetes/pkg/api', 45 | }); 46 | expect(response.body).toEqual({ 47 | result: [ 48 | { 49 | result: 'https://github.com/jquery/jquery', 50 | target: 'jquery', 51 | type: 'npm', 52 | }, 53 | { 54 | result: 'https://github.com/request/request', 55 | target: 'request', 56 | type: 'npm', 57 | }, 58 | { 59 | result: 60 | 'https://github.com/kubernetes/kubernetes/tree/master/pkg/api', 61 | target: 'k8s.io/kubernetes/pkg/api', 62 | type: 'go', 63 | }, 64 | ], 65 | }); 66 | }); 67 | 68 | testBulk('bower', 'jquery', 'https://github.com/jquery/jquery-dist'); 69 | testBulk( 70 | 'composer', 71 | 'phpunit/phpunit', 72 | 'https://github.com/sebastianbergmann/phpunit', 73 | ); 74 | testBulk('rubygems', 'nokogiri', 'https://github.com/sparklemotion/nokogiri'); 75 | testBulk('npm', 'eslint', 'https://github.com/eslint/eslint'); 76 | testBulk('npm', '@jest/core', 'https://github.com/facebook/jest/tree/master/packages/jest-core'); 77 | testBulk('npm', '@lerna/command', 'https://github.com/lerna/lerna/tree/master/core/command'); 78 | testBulk('npm', 'request', 'https://github.com/request/request'); 79 | testBulk( 80 | 'npm', 81 | 'babel-helper-regex', 82 | 'https://github.com/babel/babel/tree/master/packages/babel-helper-regex', 83 | ); 84 | testBulk( 85 | 'npm', 86 | 'audio-context-polyfill', 87 | 'https://www.npmjs.com/package/audio-context-polyfill', 88 | ); 89 | testBulk( 90 | 'npm', 91 | 'github-url-from-username-repo', 92 | 'https://github.com/robertkowalski/github-url-from-username-repo', 93 | ); 94 | testBulk( 95 | 'npm', 96 | 'find-project-root', 97 | 'https://github.com/kirstein/find-project-root', 98 | ); 99 | testBulk('pypi', 'simplejson', 'https://github.com/simplejson/simplejson'); 100 | testBulk('crates', 'libc', 'https://github.com/rust-lang/libc'); 101 | testBulk( 102 | 'go', 103 | 'k8s.io/kubernetes/pkg/api', 104 | 'https://github.com/kubernetes/kubernetes/tree/master/pkg/api', 105 | ); 106 | // testBulk('melpa', 'zzz-to-char', 'https://github.com/mrkkrp/zzz-to-char'); 107 | testBulk( 108 | 'java', 109 | 'org.apache.log4j.Appender', 110 | 'https://www.slf4j.org/api/org/apache/log4j/Appender.html', 111 | ); 112 | testBulk( 113 | 'java', 114 | 'java.util.List', 115 | 'https://docs.oracle.com/javase/9/docs/api/java/util/List.html', 116 | ); 117 | testBulk( 118 | 'ping', 119 | 'https://nodejs.org/api/path.html', 120 | 'https://nodejs.org/api/path.html', 121 | ); 122 | testBulk( 123 | 'pub', 124 | 'path', 125 | 'https://github.com/dart-lang/path', 126 | ); 127 | testBulk( 128 | 'cran', 129 | 'fracdiff', 130 | 'https://github.com/mmaechler/fracdiff', 131 | ); 132 | testBulk( 133 | 'cran', 134 | 'usethis', 135 | 'https://github.com/r-lib/usethis', 136 | ); 137 | testBulk( 138 | 'cran', 139 | 'boot', 140 | 'https://github.com/cran/boot', 141 | ); 142 | testBulk( 143 | 'nuget', 144 | 'Notify', 145 | 'https://www.nuget.org/packages/notify/', 146 | ); 147 | testBulk( 148 | 'nuget', 149 | 'QRCoder', 150 | 'https://github.com/codebude/QRCoder', 151 | ); 152 | }); 153 | -------------------------------------------------------------------------------- /src/utils/cache.js: -------------------------------------------------------------------------------- 1 | const Redis = require('ioredis'); 2 | const log = require('./log'); 3 | const staticCache = require('./static-cache.json'); 4 | 5 | // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 6 | // ~~ Thanks to RedisGreen and ZEIT for sponsoring ~~ 7 | // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 8 | 9 | // https://zeit.co/docs/v2/platform/regions-and-providers/ 10 | // bru1 - Brussels, Belgium, Europe 11 | // gru1 - São Paulo, Brazil 12 | // hnd1 - Tokyo, Japan 13 | // iad1 - Washington DC, USA 14 | // sfo1 - San Francisco, CA, USA 15 | 16 | const redisUrls = { 17 | dev1: 'sprightly-lavender-4454.redisgreen.net', 18 | bru1: 'sprightly-lavender-4454.redisgreen.net', 19 | gru1: 'handsome-turtle-8352.redisgreen.net', 20 | hnd1: 'inventive-willow-2140.redisgreen.net', 21 | iad1: 'content-pumpkin-3522.redisgreen.net', 22 | sfo1: 'graceful-turnip-982.redisgreen.net', 23 | }; 24 | 25 | const availableRegions = ['dev1', 'bru1', 'gru1', 'hnd1', 'iad1', 'sfo1']; 26 | const defaultExpireValue = 3600 * 48; 27 | 28 | let redis; 29 | const simpleCache = new Map(); 30 | 31 | const nowRegion = process.env.NOW_REGION || ''; 32 | let redisRegion = nowRegion; 33 | 34 | function getRedisConfig() { 35 | if (!availableRegions.includes(nowRegion)) { 36 | log(`Region "${nowRegion}" not supported. Fallback to a random region`); 37 | // TODO Fallback to closest region 38 | redisRegion = availableRegions[Math.floor(Math.random() * availableRegions.length)]; 39 | } 40 | 41 | const password = process.env[`REDIS_PASSWORD_${redisRegion.toUpperCase()}`]; 42 | const host = redisUrls[redisRegion]; 43 | 44 | log( 45 | `Cache connect to redis ${host} (${redisRegion}) from NOW region ${nowRegion}`, 46 | ); 47 | 48 | return { 49 | host, 50 | password, 51 | port: 11042, 52 | }; 53 | } 54 | 55 | function auth() { 56 | log('Cache auth'); 57 | 58 | return new Promise((resolve) => { 59 | if (redis && redis.status === 'ready') { 60 | log( 61 | `Cache re-use redis ${ 62 | redisUrls[redisRegion] 63 | } (${redisRegion}) from NOW region ${nowRegion}`, 64 | ); 65 | resolve(); 66 | return; 67 | } 68 | 69 | const { host, port, password } = getRedisConfig(); 70 | 71 | if (redis) { 72 | log('Cache Redis disconnect'); 73 | redis.disconnect(); 74 | } 75 | 76 | redis = new Redis({ 77 | port, 78 | host, 79 | password, 80 | }); 81 | 82 | ['connect', 'close', 'reconnecting', 'end'].forEach((eventName) => { 83 | redis.on(eventName, () => { 84 | log(`Cache Redis event: ${eventName}`); 85 | }); 86 | }); 87 | 88 | redis.on('error', async (error) => { 89 | log('Cache Redis event: error', error); 90 | if (redis) { 91 | log('Cache Redis status', redis.status); 92 | log('Cache Redis disconnect'); 93 | redis.disconnect(); 94 | redis = null; 95 | } 96 | resolve(); 97 | }); 98 | 99 | redis.on('ready', () => { 100 | log('Cache Redis event: ready'); 101 | resolve(); 102 | }); 103 | }); 104 | } 105 | 106 | async function set(key, value, expire = defaultExpireValue) { 107 | if (!redis || redis.status !== 'ready') { 108 | log('Cache SET simple-cache', key, value); 109 | simpleCache.set(key, value); 110 | return; 111 | } 112 | 113 | try { 114 | log('Cache SET redis-cache', key, value, expire); 115 | const timingStart = Date.now(); 116 | await redis.set(key, value, 'EX', expire); 117 | log('Cache SET redis-cache timing', Date.now() - timingStart); 118 | } catch (error) { 119 | log('Cache SET redis-cache error', error); 120 | } 121 | } 122 | 123 | async function get(key) { 124 | if (staticCache[key]) { 125 | log('Cache GET static-cache', key); 126 | return staticCache[key]; 127 | } 128 | 129 | if (!redis || redis.status !== 'ready') { 130 | log('Cache GET simple-cache', key); 131 | return simpleCache.get(key); 132 | } 133 | 134 | try { 135 | log('Cache GET redis-cache', key); 136 | const timingStart = Date.now(); 137 | const value = await redis.get(key); 138 | log('Cache GET redis-cache timing', Date.now() - timingStart); 139 | return value; 140 | } catch (error) { 141 | log('Cache GET redis-cache error', error); 142 | } 143 | } 144 | 145 | module.exports = { 146 | auth, 147 | set, 148 | get, 149 | getRedisStatus: () => redis && redis.status, 150 | simpleCacheSize: () => simpleCache.size, 151 | _memoryCache: simpleCache, 152 | }; 153 | -------------------------------------------------------------------------------- /scripts/update-mapping-files.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | const fs = require('fs'); 4 | const path = require('path'); 5 | const got = require('got'); 6 | const jsdom = require('jsdom'); // eslint-disable-line import/no-extraneous-dependencies 7 | 8 | const { JSDOM } = jsdom; 9 | 10 | const config = [ 11 | 'https://junit.org/junit5/docs/current/api/allclasses-index.html', 12 | 'https://www.javadoc.io/static/org.mockito/mockito-core/3.12.4/allclasses-frame.html', 13 | 'https://www.javadoc.io/static/org.mockito/mockito-core/2.12.0/allclasses-frame.html', // The original link 'http://static.javadoc.io/org.mockito/mockito-core/2.12.0/allclasses-frame.html' does not work correctly 14 | 'https://www.slf4j.org/api/allclasses-frame.html', 15 | 'http://hamcrest.org/JavaHamcrest/javadoc/2.0.0.0/allclasses-frame.html', 16 | 'http://fasterxml.github.io/jackson-core/javadoc/2.9/allclasses-frame.html', 17 | 'http://fasterxml.github.io/jackson-databind/javadoc/2.9/allclasses-frame.html', 18 | 'http://fasterxml.github.io/jackson-annotations/javadoc/2.9/allclasses-frame.html', 19 | 'http://hadoop.apache.org/docs/stable/api/allclasses-frame.html', 20 | 'http://projectlombok.org/api/allclasses-frame.html', 21 | 'http://www.atteo.org/static/classindex/apidocs/allclasses-frame.html', 22 | 'https://asm.ow2.io/javadoc/allclasses-index.html', 23 | 'http://hamcrest.org/JavaHamcrest/javadoc/2.2/allclasses-frame.html', 24 | 'http://logback.qos.ch/apidocs/allclasses-frame.html', 25 | 'http://yoyosource.github.io/YAPION/javadoc/v0.25.3/allclasses-index.html', 26 | ]; 27 | 28 | async function loadPage(url) { 29 | try { 30 | const { body } = await got(url); 31 | return new JSDOM(body).window.document; 32 | } catch (err) { 33 | console.log(`Could not fetch: ${url}`); 34 | return new JSDOM('').window.document; 35 | } 36 | } 37 | 38 | async function getSpringDocumentationUrls() { 39 | const urlsToFetch = []; 40 | 41 | const document = await loadPage('https://spring.io/projects'); 42 | const linkNodes = document.querySelector('#filters-and-proj').querySelectorAll('a'); 43 | const allLinks = []; 44 | linkNodes.forEach((el) => { 45 | allLinks.push(el.href); 46 | }); 47 | 48 | async function getLinks(link) { 49 | function next() { 50 | if (allLinks.length === 0) { 51 | return Promise.resolve(); 52 | } 53 | const current = allLinks.shift(); 54 | return getLinks(current); 55 | } 56 | 57 | const currentDocument = await loadPage(`${link}#learn`); 58 | if (!currentDocument) { 59 | return next(); 60 | } 61 | const learnNode = currentDocument.querySelector('#learn'); 62 | if (!learnNode) { 63 | return next(); 64 | } 65 | const currentNode = learnNode.querySelectorAll('a'); 66 | const apiLinks = []; 67 | currentNode.forEach((el) => { 68 | const { href } = el; 69 | if (href.includes('api')) { 70 | apiLinks.push(href); 71 | } 72 | }); 73 | if (apiLinks.length === 0) { 74 | return next(); 75 | } 76 | apiLinks.forEach((el) => { 77 | // Example link: https://docs.spring.io/spring-boot/docs/current/api/allclasses-frame.html 78 | urlsToFetch.push(`${el.replace('index.html', '')}allclasses-frame.html`); 79 | urlsToFetch.push(`${el.replace('index.html', '')}allclasses-index.html`); 80 | }); 81 | 82 | return next(); 83 | } 84 | 85 | await getLinks(allLinks); 86 | 87 | return urlsToFetch.reverse(); 88 | } 89 | 90 | async function getClassesUrl(results, url) { 91 | console.log(url); 92 | const document = await loadPage(url); 93 | const baseUrl = url.replace('allclasses-frame.html', '').replace('allclasses-index.html', ''); 94 | 95 | let nodes; 96 | const node = document.querySelector('.allClassesContainer'); 97 | if (node) { 98 | nodes = []; 99 | let index = 0; 100 | while (true) { 101 | const current = node.querySelector(`#i${index}`); 102 | if (!current) { 103 | break; 104 | } 105 | nodes.push(current.querySelector('a')); 106 | index += 1; 107 | } 108 | } else { 109 | nodes = document.querySelectorAll('a'); 110 | } 111 | 112 | if (url === 'https://junit.org/junit5/docs/current/api/allclasses-index.html') { 113 | nodes = document.querySelector('div.summary-table.two-column-summary').querySelectorAll('a'); 114 | } 115 | 116 | if (nodes) { 117 | nodes.forEach((el) => { 118 | const link = el.href; 119 | if (link.startsWith('http')) { 120 | return; 121 | } 122 | let library = link.replace(/\//g, '.'); 123 | 124 | if (library.endsWith('.html')) { 125 | library = library.slice(0, -5); 126 | } 127 | 128 | results[library] = `${baseUrl}${link}`; 129 | }); 130 | } 131 | } 132 | 133 | (async () => { 134 | const fullconfig = config.concat(await getSpringDocumentationUrls()); 135 | 136 | const dir = path.join(__dirname, '../src/java/mapping.json'); 137 | 138 | const content = {}; 139 | for (const url of fullconfig) { 140 | await getClassesUrl(content, url); // eslint-disable-line no-await-in-loop 141 | } 142 | 143 | fs.writeFileSync(dir, JSON.stringify(content, null, ' ')); 144 | })(); 145 | -------------------------------------------------------------------------------- /src/utils/static-cache.json: -------------------------------------------------------------------------------- 1 | { 2 | "npm_react": "https://github.com/facebook/react/tree/master/packages/react", 3 | "npm_lodash": "https://github.com/lodash/lodash", 4 | "npm_react-dom": "https://github.com/facebook/react/tree/master/packages/react-dom", 5 | "npm_next": "https://github.com/vercel/next.js", 6 | "npm_typescript": "https://github.com/Microsoft/TypeScript", 7 | "npm_express": "https://github.com/expressjs/express", 8 | "npm_eslint": "https://github.com/eslint/eslint", 9 | "npm_vue": "https://github.com/vuejs/core", 10 | "npm_react-router-dom": "https://github.com/remix-run/react-router/tree/master/packages/react-router-dom", 11 | "npm_axios": "https://github.com/axios/axios", 12 | "npm_prettier": "https://github.com/prettier/prettier", 13 | "npm_prop-types": "https://github.com/facebook/prop-types", 14 | "npm_styled-components": "https://github.com/styled-components/styled-components", 15 | "npm_react-redux": "https://github.com/reduxjs/react-redux", 16 | "npm_react-native": "https://github.com/facebook/react-native", 17 | "npm_classnames": "https://github.com/JedWatson/classnames", 18 | "npm_@types/node": "https://github.com/DefinitelyTyped/DefinitelyTyped/tree/master/types/node", 19 | "npm_@testing-library/react": "https://github.com/testing-library/react-testing-library", 20 | "npm_@nestjs/common": "https://github.com/nestjs/nest", 21 | "npm_webpack": "https://github.com/webpack/webpack", 22 | "npm_components": "https://github.com/upstage/components", 23 | "npm_jest": "https://github.com/facebook/jest", 24 | "npm_moment": "https://github.com/moment/moment", 25 | "npm_dotenv": "https://github.com/motdotla/dotenv", 26 | "npm_@typescript-eslint/parser": "https://github.com/typescript-eslint/typescript-eslint/tree/master/packages/parser", 27 | "npm_@typescript-eslint/eslint-plugin": "https://github.com/typescript-eslint/typescript-eslint/tree/master/packages/eslint-plugin", 28 | "npm_@babel/core": "https://github.com/babel/babel/tree/master/packages/babel-core", 29 | "npm_eslint-config-prettier": "https://github.com/prettier/eslint-config-prettier", 30 | "npm_husky": "https://github.com/typicode/husky", 31 | "npm_@types/jest": "https://github.com/DefinitelyTyped/DefinitelyTyped/tree/master/types/jest", 32 | "npm_uuid": "https://github.com/uuidjs/uuid", 33 | "npm_vite": "https://github.com/vitejs/vite/tree/master/packages/vite", 34 | "npm_@types/react": "https://github.com/DefinitelyTyped/DefinitelyTyped/tree/master/types/react", 35 | "npm_fs-extra": "https://github.com/jprichardson/node-fs-extra", 36 | "npm_rxjs": "https://github.com/reactivex/rxjs", 37 | "npm_eslint-plugin-import": "https://github.com/import-js/eslint-plugin-import", 38 | "npm_src": "https://github.com/wlaurance/src", 39 | "npm_date-fns": "https://github.com/date-fns/date-fns", 40 | "npm_chalk": "https://github.com/chalk/chalk", 41 | "npm_utils": "https://github.com/jonschlinkert/utils", 42 | "npm_lint-staged": "https://github.com/okonet/lint-staged", 43 | "npm_@angular/core": "https://github.com/angular/angular/tree/master/packages/core", 44 | "npm_chai": "https://github.com/chaijs/chai", 45 | "npm_@material-ui/core": "https://github.com/mui-org/material-ui", 46 | "npm_react-i18next": "https://github.com/i18next/react-i18next", 47 | "npm_rimraf": "https://github.com/isaacs/rimraf", 48 | "npm_postcss": "https://github.com/postcss/postcss", 49 | "npm_redux": "https://github.com/reduxjs/redux", 50 | "npm_@apollo/client": "https://github.com/apollographql/apollo-client", 51 | "npm_node-fetch": "https://github.com/node-fetch/node-fetch", 52 | "npm_@storybook/react": "https://github.com/storybookjs/storybook/tree/master/app/react", 53 | "npm_eslint-plugin-prettier": "https://github.com/prettier/eslint-plugin-prettier", 54 | "npm_graphql": "https://github.com/graphql/graphql-js", 55 | "npm_autoprefixer": "https://github.com/postcss/autoprefixer", 56 | "npm_@testing-library/jest-dom": "https://github.com/testing-library/jest-dom", 57 | "npm_@mui/material": "https://github.com/mui/material-ui/tree/master/packages/mui-material", 58 | "npm_ts-node": "https://github.com/TypeStrong/ts-node", 59 | "npm_ts-jest": "https://github.com/kulshekhar/ts-jest", 60 | "npm_@types/react-dom": "https://github.com/DefinitelyTyped/DefinitelyTyped/tree/master/types/react-dom", 61 | "npm_@babel/preset-env": "https://github.com/babel/babel/tree/master/packages/babel-preset-env", 62 | "npm_@testing-library/user-event": "https://github.com/testing-library/user-event", 63 | "npm_eslint-plugin-react": "https://github.com/yannickcr/eslint-plugin-react", 64 | "npm_typeorm": "https://github.com/typeorm/typeorm", 65 | "npm_babel-loader": "https://github.com/babel/babel-loader", 66 | "npm_antd": "https://github.com/ant-design/ant-design", 67 | "npm_cross-env": "https://github.com/kentcdodds/cross-env", 68 | "npm_sinon": "https://github.com/sinonjs/sinon", 69 | "npm_react-query": "https://github.com/tannerlinsley/react-query", 70 | "npm_core-js": "https://github.com/zloirock/core-js", 71 | "npm_mocha": "https://github.com/mochajs/mocha", 72 | "npm_rollup": "https://github.com/rollup/rollup", 73 | "npm_esbuild": "https://github.com/evanw/esbuild", 74 | "npm_@chakra-ui/react": "https://github.com/chakra-ui/chakra-ui/tree/master/packages/react", 75 | "npm_semver": "https://github.com/npm/node-semver", 76 | "npm_dayjs": "https://github.com/iamkun/dayjs", 77 | "npm_mongoose": "https://github.com/Automattic/mongoose", 78 | "npm_tailwindcss": "https://github.com/tailwindlabs/tailwindcss", 79 | "npm_enzyme": "https://github.com/airbnb/enzyme/tree/master/packages/enzyme", 80 | "npm_electron": "https://github.com/electron/electron", 81 | "npm_html-webpack-plugin": "https://github.com/jantimon/html-webpack-plugin", 82 | "npm_react-router": "https://github.com/remix-run/react-router/tree/master/packages/react-router", 83 | "npm_vue-router": "https://github.com/vuejs/router", 84 | "npm_@emotion/react": "https://github.com/emotion-js/emotion/blob/main/packages", 85 | "npm_react-hook-form": "https://github.com/react-hook-form/react-hook-form", 86 | "npm_debug": "https://github.com/debug-js/debug", 87 | "npm_lib": "https://github.com/stdlib/lib-node", 88 | "npm_glob": "https://github.com/isaacs/node-glob", 89 | "npm_aws-sdk": "https://github.com/aws/aws-sdk-js", 90 | "npm_body-parser": "https://github.com/expressjs/body-parser", 91 | "npm_@emotion/styled": "https://github.com/emotion-js/emotion/blob/main/packages", 92 | "npm_css-loader": "https://github.com/webpack-contrib/css-loader", 93 | "npm_npm-run-all": "https://github.com/mysticatea/npm-run-all", 94 | "npm_eslint-plugin-react-hooks": "https://github.com/facebook/react/tree/master/packages/eslint-plugin-react-hooks", 95 | "npm_puppeteer": "https://github.com/puppeteer/puppeteer", 96 | "npm_@reduxjs/toolkit": "https://github.com/reduxjs/redux-toolkit", 97 | "npm_hooks": "https://github.com/bnoguchi/hooks-js", 98 | "npm_webpack-cli": "https://github.com/webpack/webpack-cli", 99 | "npm_babel-eslint": "https://github.com/babel/babel-eslint", 100 | "npm_ramda": "https://github.com/ramda/ramda", 101 | "npm_@prisma/client": "https://github.com/prisma/prisma/tree/master/packages/client", 102 | "npm_mini-css-extract-plugin": "https://github.com/webpack-contrib/mini-css-extract-plugin", 103 | "npm_clsx": "https://github.com/lukeed/clsx", 104 | "npm_vuex": "https://github.com/vuejs/vuex", 105 | "npm_yup": "https://github.com/jquense/yup", 106 | "npm_cors": "https://github.com/expressjs/cors", 107 | "npm_sass": "https://github.com/sass/dart-sass", 108 | "npm_react-intl": "https://github.com/formatjs/formatjs", 109 | "npm_jsonwebtoken": "https://github.com/auth0/node-jsonwebtoken", 110 | "npm_types": "https://github.com/nodeca/types", 111 | "npm_react-icons": "https://github.com/react-icons/react-icons", 112 | "npm_babel-jest": "https://github.com/facebook/jest/tree/master/packages/babel-jest", 113 | "npm_@babel/preset-react": "https://github.com/babel/babel/tree/master/packages/babel-preset-react", 114 | "npm_qs": "https://github.com/ljharb/qs", 115 | "npm_shared": "https://github.com/hutkev/shared", 116 | "npm_app": "https://www.npmjs.com/package/app", 117 | "npm_tslib": "https://github.com/Microsoft/tslib", 118 | "npm_@vitejs/plugin-vue": "https://github.com/vitejs/vite/tree/master/packages/plugin-vue", 119 | "npm_fastify": "https://github.com/fastify/fastify", 120 | "npm_formik": "https://github.com/formium/formik", 121 | "npm_execa": "https://github.com/sindresorhus/execa", 122 | "npm_webpack-dev-server": "https://github.com/webpack/webpack-dev-server", 123 | "npm_reflect-metadata": "https://github.com/rbuckton/reflect-metadata", 124 | "npm_@babel/runtime": "https://github.com/babel/babel/tree/master/packages/babel-runtime", 125 | "npm_request": "https://github.com/request/request", 126 | "npm_@rollup/plugin-node-resolve": "https://github.com/rollup/plugins/tree/master/packages/node-resolve", 127 | "npm_react-test-renderer": "https://github.com/facebook/react/tree/master/packages/react-test-renderer", 128 | "npm_eslint-plugin-jsx-a11y": "https://github.com/jsx-eslint/eslint-plugin-jsx-a11y", 129 | "npm_class-validator": "https://github.com/typestack/class-validator", 130 | "npm_bluebird": "https://github.com/petkaantonov/bluebird", 131 | "npm_remix": "https://github.com/remix-run/remix/tree/master/packages/remix", 132 | "npm_lodash-es": "https://github.com/lodash/lodash", 133 | "npm_style-loader": "https://github.com/webpack-contrib/style-loader", 134 | "npm_history": "https://github.com/remix-run/history", 135 | "npm_react-use": "https://github.com/streamich/react-use", 136 | "npm_@material-ui/icons": "https://github.com/mui-org/material-ui", 137 | "npm_@testing-library/react-hooks": "https://github.com/testing-library/react-hooks-testing-library", 138 | "npm_config": "https://github.com/lorenwest/node-config", 139 | "npm_got": "https://github.com/sindresorhus/got", 140 | "npm_@angular/common": "https://github.com/angular/angular/tree/master/packages/common", 141 | "npm_supertest": "https://github.com/visionmedia/supertest", 142 | "npm_@rollup/plugin-commonjs": "https://github.com/rollup/plugins/tree/master/packages/commonjs", 143 | "npm_@types/lodash": "https://github.com/DefinitelyTyped/DefinitelyTyped/tree/master/types/lodash", 144 | "npm_regenerator-runtime": "https://www.npmjs.com/package/regenerator-runtime", 145 | "npm_@react-navigation/native": "https://github.com/react-navigation/react-navigation/tree/master/packages/native", 146 | "npm_swr": "https://github.com/vercel/swr", 147 | "npm_@storybook/addon-actions": "https://github.com/storybookjs/storybook/tree/master/addons/actions", 148 | "npm_ts-loader": "https://github.com/TypeStrong/ts-loader", 149 | "npm_faker": "https://www.npmjs.com/package/faker", 150 | "npm_@babel/preset-typescript": "https://github.com/babel/babel/tree/master/packages/babel-preset-typescript", 151 | "npm_@nestjs/core": "https://github.com/nestjs/nest", 152 | "npm_firebase": "https://github.com/firebase/firebase-js-sdk", 153 | "npm_eslint-plugin-jest": "https://github.com/jest-community/eslint-plugin-jest", 154 | "npm_eslint-plugin-node": "https://github.com/mysticatea/eslint-plugin-node", 155 | "npm_yargs": "https://github.com/yargs/yargs", 156 | "npm_graphql-tag": "https://github.com/apollographql/graphql-tag", 157 | "npm_copy-webpack-plugin": "https://github.com/webpack-contrib/copy-webpack-plugin", 158 | "npm_@angular/router": "https://github.com/angular/angular/tree/master/packages/router", 159 | "npm_jquery": "https://github.com/jquery/jquery", 160 | "npm_common": "https://github.com/gett/common", 161 | "npm_ethers": "https://github.com/ethers-io/ethers.js/tree/master/packages/ethers", 162 | "npm_sass-loader": "https://github.com/webpack-contrib/sass-loader", 163 | "npm_svelte": "https://github.com/sveltejs/svelte", 164 | "npm_@commitlint/cli": "https://github.com/conventional-changelog/commitlint/tree/master/@commitlint/cli", 165 | "npm_file-loader": "https://github.com/webpack-contrib/file-loader", 166 | "npm_koa": "https://github.com/koajs/koa", 167 | "npm_react-scripts": "https://github.com/facebook/create-react-app/tree/master/packages/react-scripts", 168 | "npm_ava": "https://github.com/avajs/ava", 169 | "npm_@nestjs/swagger": "https://github.com/nestjs/swagger", 170 | "npm_stylelint": "https://github.com/stylelint/stylelint", 171 | "npm_minimist": "https://github.com/substack/minimist", 172 | "npm_framer-motion": "https://github.com/framer/motion", 173 | "npm_i18next": "https://github.com/i18next/i18next", 174 | "npm_gulp": "https://github.com/gulpjs/gulp", 175 | "npm_morgan": "https://github.com/expressjs/morgan", 176 | "npm_@babel/cli": "https://github.com/babel/babel/tree/master/packages/babel-cli", 177 | "npm_winston": "https://github.com/winstonjs/winston", 178 | "npm_query-string": "https://github.com/sindresorhus/query-string", 179 | "npm_@commitlint/config-conventional": "https://github.com/conventional-changelog/commitlint/tree/master/@commitlint/config-conventional", 180 | "npm_jsdom": "https://github.com/jsdom/jsdom", 181 | "npm_@actions/core": "https://github.com/actions/toolkit/tree/master/packages/core", 182 | "npm_@babel/plugin-proposal-class-properties": "https://github.com/babel/babel/tree/master/packages/babel-plugin-proposal-class-properties", 183 | "npm_msw": "https://github.com/mswjs/msw", 184 | "npm_immer": "https://github.com/immerjs/immer", 185 | "npm_services": "https://www.npmjs.com/package/services", 186 | "npm_commander": "https://github.com/tj/commander.js", 187 | "npm_react-bootstrap": "https://github.com/react-bootstrap/react-bootstrap", 188 | "npm_mobx": "https://github.com/mobxjs/mobx", 189 | "npm_foo": "https://github.com/jasonLaster/foo", 190 | "npm_terser-webpack-plugin": "https://github.com/webpack-contrib/terser-webpack-plugin", 191 | "npm_mongodb": "https://github.com/mongodb/node-mongodb-native", 192 | "npm_node-sass": "https://github.com/sass/node-sass", 193 | "npm_zod": "https://github.com/colinhacks/zod", 194 | "npm_class-transformer": "https://github.com/typestack/class-transformer", 195 | "npm_@angular/forms": "https://github.com/angular/angular/tree/master/packages/forms", 196 | "npm_rollup-plugin-terser": "https://github.com/TrySound/rollup-plugin-terser", 197 | "npm_@nestjs/testing": "https://github.com/nestjs/nest", 198 | "npm_nodemon": "https://github.com/remy/nodemon", 199 | "npm_discord.js": "https://github.com/discordjs/discord.js", 200 | "npm_lerna": "https://github.com/lerna/lerna/tree/master/core/lerna", 201 | "npm_concurrently": "https://github.com/open-cli-tools/concurrently", 202 | "npm_@mui/icons-material": "https://github.com/mui/material-ui/tree/master/packages/mui-icons-material", 203 | "npm_cookie-parser": "https://github.com/expressjs/cookie-parser", 204 | "npm_pages": "https://www.npmjs.com/package/pages", 205 | "npm_webpack-merge": "https://github.com/survivejs/webpack-merge", 206 | "npm_js-yaml": "https://github.com/nodeca/js-yaml", 207 | "npm_chokidar": "https://github.com/paulmillr/chokidar", 208 | "npm_@babel/plugin-transform-runtime": "https://github.com/babel/babel/tree/master/packages/babel-plugin-transform-runtime", 209 | "npm_inquirer": "https://github.com/SBoudrias/Inquirer.js", 210 | "npm_@types/express": "https://github.com/DefinitelyTyped/DefinitelyTyped/tree/master/types/express", 211 | "npm_postcss-loader": "https://github.com/webpack-contrib/postcss-loader", 212 | "npm_gatsby": "https://github.com/gatsbyjs/gatsby", 213 | "npm_bootstrap": "https://github.com/twbs/bootstrap", 214 | "npm_cypress": "https://github.com/cypress-io/cypress", 215 | "npm_form-data": "https://github.com/form-data/form-data", 216 | "npm_moment-timezone": "https://github.com/moment/moment-timezone", 217 | "npm_nyc": "https://github.com/istanbuljs/nyc", 218 | "npm_webpack-bundle-analyzer": "https://github.com/webpack-contrib/webpack-bundle-analyzer", 219 | "npm_globby": "https://github.com/sindresorhus/globby", 220 | "npm_react-helmet": "https://github.com/nfl/react-helmet", 221 | "npm_reselect": "https://github.com/reduxjs/reselect", 222 | "npm_constants": "https://github.com/dominicbarnes/node-constants", 223 | "npm_joi": "https://github.com/sideway/joi", 224 | "npm_nanoid": "https://github.com/ai/nanoid", 225 | "npm_@nestjs/graphql": "https://github.com/nestjs/graphql", 226 | "npm_next-auth": "https://github.com/nextauthjs/next-auth", 227 | "npm_babel-core": "https://github.com/babel/babel/tree/master/packages/babel-core", 228 | "npm_@angular/platform-browser": "https://github.com/angular/angular/tree/master/packages/platform-browser", 229 | "npm_@nestjs/typeorm": "https://github.com/nestjs/typeorm", 230 | "npm_redux-thunk": "https://github.com/reduxjs/redux-thunk", 231 | "npm_zustand": "https://github.com/pmndrs/zustand", 232 | "npm_vitest": "https://github.com/vitest-dev/vitest", 233 | "npm_wix-style-react": "https://github.com/netanelgilad/wix-style-react", 234 | "npm_@types/fs-extra": "https://github.com/DefinitelyTyped/DefinitelyTyped/tree/master/types/fs-extra", 235 | "npm_eslint-plugin-promise": "https://github.com/xjamundx/eslint-plugin-promise", 236 | "npm_eslint-config-airbnb": "https://github.com/airbnb/javascript", 237 | "npm_fs": "https://github.com/npm/security-holder", 238 | "npm_@types/mocha": "https://github.com/DefinitelyTyped/DefinitelyTyped/tree/master/types/mocha", 239 | "npm_nock": "https://github.com/nock/nock", 240 | "npm_type-fest": "https://github.com/sindresorhus/type-fest", 241 | "npm_preact": "https://github.com/preactjs/preact", 242 | "npm_compression": "https://github.com/expressjs/compression", 243 | "npm_pino": "https://github.com/pinojs/pino", 244 | "npm_mobx-react": "https://github.com/mobxjs/mobx", 245 | "npm_@openzeppelin/contracts": "https://github.com/OpenZeppelin/openzeppelin-contracts", 246 | "npm_@ant-design/icons": "https://github.com/ant-design/ant-design-icons/tree/master/packages/icons-react", 247 | "npm_modules": "https://github.com/thetalecrafter/modules", 248 | "npm_@nestjs/config": "https://github.com/nestjs/config", 249 | "npm_@types/uuid": "https://github.com/DefinitelyTyped/DefinitelyTyped/tree/master/types/uuid", 250 | "npm_recoil": "https://github.com/facebookexperimental/Recoil", 251 | "npm_eslint-plugin-vue": "https://github.com/vuejs/eslint-plugin-vue", 252 | "npm_ws": "https://github.com/websockets/ws", 253 | "npm_luxon": "https://github.com/moment/luxon", 254 | "npm_@vueuse/core": "https://github.com/vueuse/vueuse/tree/master/packages/core", 255 | "npm_source-map-support": "https://github.com/evanw/node-source-map-support", 256 | "npm_ajv": "https://github.com/ajv-validator/ajv", 257 | "npm_async": "https://github.com/caolan/async", 258 | "npm_ioredis": "https://github.com/luin/ioredis", 259 | "npm_sequelize": "https://github.com/sequelize/sequelize", 260 | "npm_enzyme-adapter-react-16": "https://github.com/enzymejs/enzyme/tree/master/packages/enzyme-adapter-react-16", 261 | "npm_js-cookie": "https://github.com/js-cookie/js-cookie", 262 | "npm_hardhat": "https://github.com/nomiclabs/hardhat", 263 | "npm_url-loader": "https://github.com/webpack-contrib/url-loader", 264 | "npm_pinia": "https://github.com/vuejs/pinia", 265 | "npm_@sentry/browser": "https://github.com/getsentry/sentry-javascript", 266 | "npm_unified": "https://github.com/unifiedjs/unified", 267 | "npm_helmet": "https://github.com/helmetjs/helmet", 268 | "npm_react-native-gesture-handler": "https://github.com/software-mansion/react-native-gesture-handler", 269 | "npm_react-select": "https://github.com/JedWatson/react-select/tree/master/packages/react-select", 270 | "npm_cheerio": "https://github.com/cheeriojs/cheerio", 271 | "npm_bignumber.js": "https://github.com/MikeMcl/bignumber.js", 272 | "npm_web-vitals": "https://github.com/GoogleChrome/web-vitals", 273 | "npm_fast-glob": "https://github.com/mrmlnc/fast-glob", 274 | "npm_eslint-config-next": "https://github.com/vercel/next.js/tree/master/packages/eslint-config-next", 275 | "npm_@rollup/plugin-babel": "https://github.com/rollup/plugins/tree/master/packages/babel", 276 | "npm_postcss-import": "https://github.com/postcss/postcss-import", 277 | "npm_knex": "https://github.com/knex/knex", 278 | "npm_@heroicons/react": "https://github.com/tailwindlabs/heroicons", 279 | "npm_mkdirp": "https://github.com/isaacs/node-mkdirp", 280 | "npm_@storybook/addon-links": "https://github.com/storybookjs/storybook/tree/master/addons/links", 281 | "npm_@vue/composition-api": "https://github.com/vuejs/composition-api", 282 | "npm_shelljs": "https://github.com/shelljs/shelljs", 283 | "npm_tslint": "https://github.com/palantir/tslint", 284 | "npm_@sentry/node": "https://github.com/getsentry/sentry-javascript", 285 | "npm_@vitejs/plugin-react": "https://github.com/vitejs/vite/tree/master/packages/plugin-react", 286 | "npm_@vue/test-utils": "https://github.com/vuejs/test-utils", 287 | "npm_redis": "https://github.com/redis/node-redis", 288 | "npm_redux-saga": "https://github.com/redux-saga/redux-saga/tree/master/packages/core", 289 | "npm_@babel/register": "https://github.com/babel/babel/tree/master/packages/babel-register", 290 | "npm_unocss": "https://github.com/unocss/unocss", 291 | "npm_svelte-preprocess": "https://github.com/sveltejs/svelte-preprocess", 292 | "npm_react-native-safe-area-context": "https://github.com/th3rdwave/react-native-safe-area-context", 293 | "npm_three": "https://github.com/mrdoob/three.js", 294 | "npm_api": "https://github.com/readmeio/api/tree/master/packages/api", 295 | "npm_io-ts": "https://github.com/gcanti/io-ts", 296 | "npm_cssnano": "https://github.com/cssnano/cssnano", 297 | "npm_cookie": "https://github.com/jshttp/cookie", 298 | "npm_@types/react-router-dom": "https://github.com/DefinitelyTyped/DefinitelyTyped/tree/master/types/react-router-dom", 299 | "npm_react-transition-group": "https://github.com/reactjs/react-transition-group", 300 | "npm_postcss-preset-env": "https://github.com/csstools/postcss-plugins/tree/master/plugin-packs/postcss-preset-env", 301 | "npm_@rollup/plugin-replace": "https://github.com/rollup/plugins/tree/master/packages/replace", 302 | "npm_immutable": "https://github.com/immutable-js/immutable-js", 303 | "npm_apollo-server-express": "https://github.com/apollographql/apollo-server/tree/master/packages/apollo-server-express", 304 | "npm_react-toastify": "https://github.com/fkhadra/react-toastify", 305 | "npm_polished": "https://github.com/styled-components/polished", 306 | "npm_@rollup/plugin-json": "https://github.com/rollup/plugins", 307 | "npm_assets": "https://github.com/assetsjs/assets", 308 | "npm_models": "https://github.com/Gozala/models", 309 | "npm_resolve": "https://github.com/browserify/resolve", 310 | "npm_fp-ts": "https://github.com/gcanti/fp-ts", 311 | "npm_karma": "https://github.com/karma-runner/karma", 312 | "npm_@babel/eslint-parser": "https://github.com/babel/babel/tree/master/eslint/babel-eslint-parser", 313 | "npm_@testing-library/dom": "https://github.com/testing-library/dom-testing-library", 314 | "npm_socket.io": "https://github.com/socketio/socket.io", 315 | "npm_next-i18next": "https://github.com/isaachinman/next-i18next", 316 | "npm_eslint-config-standard": "https://github.com/standard/eslint-config-standard", 317 | "npm_clean-webpack-plugin": "https://github.com/johnagan/clean-webpack-plugin", 318 | "npm_@tailwindcss/forms": "https://github.com/tailwindlabs/tailwindcss-forms", 319 | "npm_@faker-js/faker": "https://github.com/faker-js/faker", 320 | "npm_@storybook/addon-knobs": "https://github.com/storybookjs/addon-knobs", 321 | "npm_vue-template-compiler": "https://github.com/vuejs/vue", 322 | "npm_marked": "https://github.com/markedjs/marked", 323 | "npm_eslint-config-airbnb-base": "https://github.com/airbnb/javascript", 324 | "npm_@playwright/test": "https://github.com/Microsoft/playwright", 325 | "npm_pg": "https://github.com/brianc/node-postgres/tree/master/packages/pg", 326 | "npm_@fortawesome/free-solid-svg-icons": "https://github.com/FortAwesome/Font-Awesome", 327 | "npm_vscode": "https://github.com/Microsoft/vscode-extension-vscode", 328 | "npm_@types/semver": "https://github.com/DefinitelyTyped/DefinitelyTyped/tree/master/types/semver", 329 | "npm_@angular/material": "https://github.com/angular/components", 330 | "npm_prismjs": "https://github.com/PrismJS/prism", 331 | "npm_web3": "https://github.com/ethereum/web3.js", 332 | "npm_bcryptjs": "https://github.com/dcodeIO/bcrypt.js", 333 | "npm_ui": "https://github.com/marcuswestin/ui.js", 334 | "npm_element-ui": "https://github.com/ElemeFE/element", 335 | "npm_@glimmer/component": "https://github.com/glimmerjs/glimmer.js", 336 | "npm_store": "https://github.com/marcuswestin/store.js", 337 | "npm_@storybook/addons": "https://github.com/storybookjs/storybook/tree/master/lib/addons", 338 | "npm_less": "https://github.com/less/less.js", 339 | "npm_bcrypt": "https://github.com/kelektiv/node.bcrypt.js", 340 | "npm_deepmerge": "https://github.com/TehShrike/deepmerge", 341 | "npm_rollup-plugin-typescript2": "https://github.com/ezolenko/rollup-plugin-typescript2", 342 | "npm_browserslist": "https://github.com/browserslist/browserslist", 343 | "npm_helpers": "https://github.com/fshost/helpers", 344 | "npm_xstate": "https://github.com/statelyai/xstate", 345 | "npm_prompts": "https://github.com/terkelg/prompts", 346 | "npm_@storybook/addon-essentials": "https://github.com/storybookjs/storybook/tree/master/addons/essentials", 347 | "npm_passport": "https://github.com/jaredhanson/passport", 348 | "npm_@types/chai": "https://github.com/DefinitelyTyped/DefinitelyTyped/tree/master/types/chai", 349 | "npm_@react-navigation/stack": "https://github.com/react-navigation/react-navigation/tree/master/packages/stack", 350 | "npm_@fortawesome/react-fontawesome": "https://github.com/FortAwesome/react-fontawesome", 351 | "npm_express-session": "https://github.com/expressjs/session", 352 | "npm_pretty-quick": "https://github.com/azz/pretty-quick", 353 | "npm_markdown-it": "https://github.com/markdown-it/markdown-it", 354 | "npm_karma-chrome-launcher": "https://github.com/karma-runner/karma-chrome-launcher", 355 | "npm_react-table": "https://github.com/tannerlinsley/react-table", 356 | "npm_core": "https://www.npmjs.com/package/core", 357 | "npm_@headlessui/react": "https://github.com/tailwindlabs/headlessui/tree/master/packages", 358 | "npm_identity-obj-proxy": "https://github.com/keyanzhang/identity-obj-proxy", 359 | "npm_@react-native-async-storage/async-storage": "https://github.com/react-native-async-storage/async-storage", 360 | "npm_core-js-pure": "https://github.com/zloirock/core-js", 361 | "npm_commitizen": "https://github.com/commitizen/cz-cli", 362 | "npm_vue-i18n": "https://github.com/kazupon/vue-i18n", 363 | "npm_zx": "https://github.com/google/zx", 364 | "npm_micromatch": "https://github.com/micromatch/micromatch", 365 | "npm_tsconfig-paths": "https://github.com/dividab/tsconfig-paths", 366 | "npm_rollup-plugin-node-resolve": "https://github.com/rollup/rollup-plugin-node-resolve", 367 | "npm_@sentry/react": "https://github.com/getsentry/sentry-javascript", 368 | "npm_@svgr/webpack": "https://github.com/gregberge/svgr", 369 | "npm_styles": "https://www.npmjs.com/package/styles", 370 | "npm_del": "https://github.com/sindresorhus/del", 371 | "npm_terser": "https://github.com/terser/terser", 372 | "npm_vue-loader": "https://www.npmjs.com/package/vue-loader", 373 | "npm_firebase-admin": "https://github.com/firebase/firebase-admin-node", 374 | "npm_ora": "https://github.com/sindresorhus/ora", 375 | "npm_@babel/parser": "https://github.com/babel/babel/tree/master/packages/babel-parser", 376 | "npm_cross-spawn": "https://github.com/moxystudio/node-cross-spawn", 377 | "npm_strip-ansi": "https://github.com/chalk/strip-ansi", 378 | "npm_multer": "https://github.com/expressjs/multer", 379 | "npm_@octokit/rest": "https://github.com/octokit/rest.js", 380 | "npm_remark-gfm": "https://github.com/remarkjs/remark-gfm", 381 | "npm_@rollup/plugin-typescript": "https://github.com/rollup/plugins/tree/master/packages/typescript", 382 | "npm_next-transpile-modules": "https://github.com/martpie/next-transpile-modules", 383 | "npm_remark-parse": "https://github.com/remarkjs/remark", 384 | "npm_apollo-server": "https://github.com/apollographql/apollo-server/tree/master/packages/apollo-server", 385 | "npm_next-seo": "https://github.com/garmeeh/next-seo", 386 | "npm_@unocss/reset": "https://github.com/unocss/unocss", 387 | "npm_socket.io-client": "https://github.com/socketio/socket.io-client", 388 | "npm_highlight.js": "https://github.com/highlightjs/highlight.js", 389 | "npm_react-is": "https://github.com/facebook/react/tree/master/packages/react-is", 390 | "npm_aws-lambda": "https://github.com/awspilot/cli-lambda-deploy", 391 | "npm_@hookform/resolvers": "https://github.com/react-hook-form/resolvers", 392 | "npm_fork-ts-checker-webpack-plugin": "https://github.com/TypeStrong/fork-ts-checker-webpack-plugin", 393 | "npm_crypto-js": "https://github.com/brix/crypto-js", 394 | "npm_@types/styled-components": "https://github.com/DefinitelyTyped/DefinitelyTyped/tree/master/types/styled-components", 395 | "npm_underscore": "https://github.com/jashkenas/underscore", 396 | "npm_cz-conventional-changelog": "https://github.com/commitizen/cz-conventional-changelog", 397 | "npm_coveralls": "https://github.com/nickmerwin/node-coveralls", 398 | "npm_postcss-nested": "https://github.com/postcss/postcss-nested", 399 | "npm_@tailwindcss/typography": "https://github.com/tailwindcss/typography", 400 | "npm_stylelint-config-standard": "https://github.com/stylelint/stylelint-config-standard", 401 | "npm_features": "https://github.com/thejh/node-features", 402 | "npm_nprogress": "https://github.com/rstacruz/nprogress", 403 | "npm_eslint-import-resolver-typescript": "https://github.com/alexgorbatchev/eslint-import-resolver-typescript", 404 | "npm_react-native-svg": "https://github.com/react-native-community/react-native-svg", 405 | "npm_css-minimizer-webpack-plugin": "https://github.com/webpack-contrib/css-minimizer-webpack-plugin", 406 | "npm_some-module": "https://www.npmjs.com/package/some-module", 407 | "npm_tape": "https://github.com/substack/tape", 408 | "npm_chai-as-promised": "https://github.com/domenic/chai-as-promised", 409 | "npm_http-errors": "https://github.com/jshttp/http-errors", 410 | "npm_@sveltejs/kit": "https://www.npmjs.com/package/@sveltejs/kit", 411 | "npm_gulp-postcss": "https://github.com/postcss/gulp-postcss", 412 | "npm_abort-controller": "https://github.com/mysticatea/abort-controller", 413 | "npm_@babel/plugin-syntax-dynamic-import": "https://www.npmjs.com/package/@babel/plugin-syntax-dynamic-import", 414 | "npm_rc-util": "https://github.com/react-component/util", 415 | "npm_graphql-request": "https://github.com/prisma/graphql-request", 416 | "npm_@angular/platform-browser-dynamic": "https://github.com/angular/angular/tree/master/packages/platform-browser-dynamic", 417 | "npm_@glimmer/tracking": "https://github.com/glimmerjs/glimmer.js", 418 | "npm_react-hot-toast": "https://github.com/timolins/react-hot-toast", 419 | "npm_zone.js": "https://github.com/angular/angular/tree/master/packages/zone.js", 420 | "npm_tsyringe": "https://github.com/Microsoft/tsyringe", 421 | "npm_eslint-plugin-flowtype": "https://github.com/gajus/eslint-plugin-flowtype", 422 | "npm_minimatch": "https://github.com/isaacs/minimatch", 423 | "npm_eslint-plugin-cypress": "https://github.com/cypress-io/eslint-plugin-cypress", 424 | "npm_solid-js": "https://github.com/solidjs/solid", 425 | "npm_wix-ui-icons-common": "https://github.com/wix/wix-ui/tree/master/packages/wix-ui-icons-common", 426 | "npm_bar": "https://github.com/indutny/bar", 427 | "npm_schema": "https://github.com/akidee/schema.js", 428 | "npm_@babel/types": "https://github.com/babel/babel/tree/master/packages/babel-types", 429 | "npm_standard-version": "https://github.com/conventional-changelog/standard-version", 430 | "npm_@types/node-fetch": "https://github.com/DefinitelyTyped/DefinitelyTyped/tree/master/types/node-fetch", 431 | "npm_react-refresh": "https://github.com/facebook/react/tree/master/packages/react", 432 | "npm_rollup-plugin-babel": "https://github.com/rollup/rollup-plugin-babel", 433 | "npm_typedi": "https://github.com/pleerock/typedi", 434 | "npm_@vue/compiler-sfc": "https://github.com/vuejs/core/tree/master/packages/compiler-sfc", 435 | "npm_redux-persist": "https://github.com/rt2zz/redux-persist", 436 | "npm_remark": "https://github.com/remarkjs/remark", 437 | "npm_sharp": "https://github.com/lovell/sharp", 438 | "npm_gulp-sourcemaps": "https://github.com/gulp-sourcemaps/gulp-sourcemaps", 439 | "npm_discord-api-types": "https://github.com/discordjs/discord-api-types", 440 | "npm_source-map": "https://github.com/mozilla/source-map", 441 | "npm_state": "https://github.com/nickfargo/state", 442 | "npm_dotenv-expand": "https://github.com/motdotla/dotenv-expand", 443 | "npm_@storybook/addon-docs": "https://github.com/storybookjs/storybook/tree/master/addons/docs", 444 | "npm_@solana/web3.js": "https://github.com/solana-labs/solana-web3.js", 445 | "npm_mobx-react-lite": "https://github.com/mobxjs/mobx", 446 | "npm_@babel/plugin-proposal-object-rest-spread": "https://github.com/babel/babel/tree/master/packages/babel-plugin-proposal-object-rest-spread", 447 | "npm_rollup-plugin-commonjs": "https://github.com/rollup/rollup-plugin-commonjs", 448 | "npm_lru-cache": "https://github.com/isaacs/node-lru-cache", 449 | "npm_delay": "https://github.com/sindresorhus/delay", 450 | "npm_eslint-config-react-app": "https://github.com/facebook/create-react-app/tree/master/packages/eslint-config-react-app", 451 | "npm_react-error-boundary": "https://github.com/bvaughn/react-error-boundary", 452 | "npm_optimize-css-assets-webpack-plugin": "https://github.com/NMFR/optimize-css-assets-webpack-plugin", 453 | "npm_react-dev-utils": "https://github.com/facebook/create-react-app/tree/master/packages/react-dev-utils", 454 | "npm_@pmmmwh/react-refresh-webpack-plugin": "https://github.com/pmmmwh/react-refresh-webpack-plugin", 455 | "npm_colors": "https://github.com/Marak/colors.js", 456 | "ping_https://k8s.io/api/core/v1": "https://github.com/kubernetes/api/tree/master/core/v1", 457 | "ping_https://pkg.go.dev/pkg/fmt": "https://pkg.go.dev/pkg/fmt", 458 | "ping_https://pkg.go.dev/pkg/time": "https://pkg.go.dev/pkg/time", 459 | "ping_https://pkg.go.dev/pkg/context": "https://pkg.go.dev/pkg/context", 460 | "ping_https://docs.python.org/3/library/os.html": "https://docs.python.org/3/library/os.html", 461 | "ping_https://pkg.go.dev/pkg/os": "https://pkg.go.dev/pkg/os", 462 | "ping_https://docs.python.org/3/library/typing.html": "https://docs.python.org/3/library/typing.html", 463 | "ping_https://pkg.go.dev/pkg/strings": "https://pkg.go.dev/pkg/strings", 464 | "ping_https://pkg.go.dev/pkg/net/http": "https://pkg.go.dev/pkg/net/http", 465 | "ping_https://pkg.go.dev/pkg/log": "https://pkg.go.dev/pkg/log", 466 | "ping_https://docs.python.org/3/library/datetime.html": "https://docs.python.org/3/library/datetime.html", 467 | "ping_https://github.com/actions/setup-node": "https://github.com/actions/setup-node", 468 | "ping_https://docs.python.org/3/library/json.html": "https://docs.python.org/3/library/json.html", 469 | "ping_https://pkg.go.dev/pkg/errors": "https://pkg.go.dev/pkg/errors", 470 | "ping_https://docs.python.org/3/library/sys.html": "https://docs.python.org/3/library/sys.html", 471 | "ping_https://docs.python.org/3/library/logging.html": "https://docs.python.org/3/library/logging.html", 472 | "ping_https://pkg.go.dev/pkg/encoding/json": "https://pkg.go.dev/pkg/encoding/json", 473 | "ping_https://docs.python.org/3/library/time.html": "https://docs.python.org/3/library/time.html", 474 | "ping_https://pkg.go.dev/pkg/strconv": "https://pkg.go.dev/pkg/strconv", 475 | "ping_https://docs.python.org/3/library/,,future,,.html": "https://docs.python.org/3/library/__future__.html", 476 | "ping_https://pkg.go.dev/pkg/sync": "https://pkg.go.dev/pkg/sync", 477 | "ping_https://pkg.go.dev/pkg/io": "https://pkg.go.dev/pkg/io", 478 | "ping_https://pkg.go.dev/pkg/bytes": "https://pkg.go.dev/pkg/bytes", 479 | "ping_https://pkg.go.dev/pkg/testing": "https://pkg.go.dev/pkg/testing", 480 | "ping_https://docs.python.org/3/library/argparse.html": "https://docs.python.org/3/library/argparse.html", 481 | "ping_https://pkg.go.dev/pkg/io/ioutil": "https://pkg.go.dev/pkg/io/ioutil", 482 | "ping_https://docs.python.org/3/library/collections.html": "https://docs.python.org/3/library/collections.html", 483 | "ping_https://github.com/actions/cache": "https://github.com/actions/cache", 484 | "ping_https://docs.python.org/3/library/re.html": "https://docs.python.org/3/library/re.html", 485 | "ping_https://github.com/stretchr/testify": "https://github.com/stretchr/testify", 486 | "ping_https://hub.docker.com/,/node": "https://hub.docker.com/_/node", 487 | "ping_https://docs.python.org/3/library/pathlib.html": "https://docs.python.org/3/library/pathlib.html", 488 | "ping_https://pkg.go.dev/pkg/net": "https://pkg.go.dev/pkg/net", 489 | "ping_https://github.com/pkg/errors": "https://github.com/pkg/errors", 490 | "ping_https://docs.python.org/3/library/random.html": "https://docs.python.org/3/library/random.html", 491 | "ping_https://pkg.go.dev/pkg/path/filepath": "https://pkg.go.dev/pkg/path/filepath", 492 | "ping_https://github.com/actions/upload-artifact": "https://github.com/actions/upload-artifact", 493 | "ping_https://github.com/sirupsen/logrus": "https://github.com/sirupsen/logrus", 494 | "ping_https://pkg.go.dev/pkg/flag": "https://pkg.go.dev/pkg/flag", 495 | "ping_https://docs.python.org/3/library/functools.html": "https://docs.python.org/3/library/functools.html", 496 | "ping_https://docs.python.org/3/library/math.html": "https://docs.python.org/3/library/math.html", 497 | "ping_https://docs.python.org/3/library/asyncio.html": "https://docs.python.org/3/library/asyncio.html", 498 | "ping_https://pkg.go.dev/pkg/net/url": "https://pkg.go.dev/pkg/net/url" 499 | } 500 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | OctoLinker API 6 | 7 | 12 | 34 | 35 | 36 |
37 |
38 | 44 |

45 | This API is part of the 46 | OctoLinker project. 47 |

48 |

49 |

50 | Learn 51 | more about 52 | this service. 53 |

54 |
55 |
56 | 57 | 58 | --------------------------------------------------------------------------------