├── .eslintrc.js ├── .github └── workflows │ └── tests.yml ├── .gitignore ├── LICENSE ├── README.md ├── docs └── screenshot.png ├── e2e ├── fetch-package-options │ └── test.spec.js └── runApp.js ├── package-lock.json ├── package.json └── src ├── __specs__ ├── createFixture.js ├── fixtures │ ├── builtins-1.0.3.tgz.json │ ├── builtins.json │ ├── fsevents.json │ ├── hosted-git-info.json │ ├── lighter-config-1.1.0.tgz.json │ ├── lighter-config.json │ ├── lighter-run-1.2.1.tgz.json │ ├── lighter-run.json │ ├── mamacro.json │ ├── npm-package-arg.json │ ├── os-homedir-1.0.2.tgz.json │ ├── os-homedir.json │ ├── os-tmpdir-1.0.2.tgz.json │ ├── os-tmpdir.json │ ├── osenv.json │ ├── semver.json │ ├── validate-npm-package-name-3.0.0.tgz.json │ ├── validate-npm-package-name.json │ ├── webassemblyjs_ast.json │ ├── webassemblyjs_floating-point-hex-parser.json │ ├── webassemblyjs_helper-api-error.json │ ├── webassemblyjs_helper-code-frame.json │ ├── webassemblyjs_helper-fsm.json │ ├── webassemblyjs_helper-module-context.json │ ├── webassemblyjs_helper-wasm-bytecode.json │ ├── webassemblyjs_wast-parser.json │ ├── webassemblyjs_wast-printer.json │ └── xtuc_long.json ├── index.js └── projects │ ├── local-dependencies │ ├── README.md │ └── package.json │ ├── local1 │ ├── README.md │ └── package.json │ └── local2 │ ├── README.md │ └── package.json ├── dependency ├── Dependency.js ├── DependencyFactory.js ├── DuplicateDependency.js ├── GroupDependency.js ├── ProjectDependency.js ├── RealDependency.js ├── UnmetDependency.js ├── __specs__ │ ├── Dependency.spec.js │ ├── GroupDependency.spec.js │ └── graph.spec.js ├── graph.js └── index.js ├── index.d.ts ├── index.js ├── package ├── Package.js ├── PackageFactory.js ├── __specs__ │ └── Package.spec.js └── fetchers │ ├── DirectoryFetcher.js │ ├── Fetcher.js │ ├── GitFetcher.js │ ├── GithubFetcher.js │ ├── HttpFetcher.js │ ├── NpmFetcher.js │ └── __specs__ │ └── DirectoryFetcher.spec.js ├── reporters ├── BaseReporter.js ├── Json.js ├── Simple.js ├── Table.js ├── Tree.js ├── __specs__ │ ├── Default.spec.js │ ├── Json.spec.js │ ├── Tree.spec.js │ └── helpers.spec.js ├── helpers.js └── index.js ├── resolver ├── DependencyCache.js ├── DependencyResolver.js ├── __specs__ │ ├── DependencyCache.spec.js │ └── DependencyResolver.spec.js └── index.js └── utils ├── __specs__ └── spec.spec.js ├── config.js ├── http ├── HttpClient.js ├── interceptors │ ├── axiosCachePlugin.js │ ├── axiosQueuePlugin.js │ └── axiosRetryPlugin.js └── progress │ ├── ProgressIndicator.js │ ├── StatProgressIndicator.js │ ├── UrlProgressIndicator.js │ └── index.js ├── index.js ├── spec.js └── tarball ├── TarballReader.js ├── TarballStat.js └── __specs__ ├── TarballReader.spec.js └── tarball.fixture.tgz /.eslintrc.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = { 4 | extends: 'airbnb-base', 5 | env: { 6 | es6: true, 7 | jasmine: true, 8 | }, 9 | 10 | parserOptions: { 11 | ecmaVersion: 'latest', 12 | sourceType: 'script', 13 | }, 14 | 15 | rules: { 16 | 'arrow-body-style': 'off', 17 | 'class-methods-use-this': 'off', 18 | 'lines-between-class-members': ['error', 'always', { 19 | exceptAfterSingleLine: true, 20 | }], 21 | 'max-classes-per-file': 'off', 22 | 'max-len': ['error', { code: 80 }], 23 | 'no-continue': 'off', 24 | 'no-param-reassign': 'off', 25 | 'no-plusplus': 'off', 26 | 'no-restricted-syntax': 'off', 27 | 'no-unused-vars': ['warn', { 28 | argsIgnorePattern: '^_', 29 | varsIgnorePattern: '^_', 30 | vars: 'all', 31 | args: 'after-used', 32 | ignoreRestSiblings: true, 33 | }], 34 | 'no-use-before-define': 'off', 35 | 'object-curly-newline': 'off', 36 | 'prefer-destructuring': 'off', 37 | 'prefer-template': 'off', 38 | 'quote-props': ['error', 'consistent-as-needed'], 39 | 'strict': ['error', 'global'], 40 | }, 41 | }; 42 | -------------------------------------------------------------------------------- /.github/workflows/tests.yml: -------------------------------------------------------------------------------- 1 | name: Tests 2 | 3 | on: 4 | - pull_request 5 | - push 6 | 7 | env: 8 | CI: 1 9 | 10 | jobs: 11 | main: 12 | defaults: 13 | run: 14 | shell: bash 15 | 16 | strategy: 17 | matrix: 18 | include: 19 | - node_version: latest 20 | - node_version: 14 21 | 22 | runs-on: ubuntu-latest 23 | steps: 24 | - uses: actions/checkout@v3 25 | - uses: actions/setup-node@v3 26 | with: 27 | node-version: ${{ matrix.node_version }} 28 | 29 | - run: npm install 30 | - run: npm run test:full 31 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .idea 2 | node_modules 3 | todo.md 4 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2019 Alexey Prokhorov 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # howfat 2 | [![Tests](https://github.com/megahertz/howfat/actions/workflows/tests.yml/badge.svg)](https://github.com/megahertz/howfat/actions/workflows/tests.yml) 3 | [![NPM version](https://badge.fury.io/js/howfat.svg)](https://badge.fury.io/js/howfat) 4 | 5 | Shows how fat is a package together with its dependencies 6 | 7 | ![howfat](docs/screenshot.png) 8 | 9 | ## Usage 10 | 11 | ### Simple 12 | 13 | `npx howfat mkdirp` 14 | 15 | Specified version or version range: 16 | 17 | `npx howfat mkdirp@^0.5.0` 18 | 19 | Local packages 20 | 21 | ```bash 22 | cd my-project 23 | npx howfat 24 | ``` 25 | 26 | `npx howfat ../my-other-package` 27 | 28 | Git or github 29 | 30 | `npx howfat https://github.com/substack/node-mkdirp` 31 | 32 | `npx howfat ssh://git@github.com:substack/node-mkdirp.git#0.3.4` 33 | 34 | ### Different reporters 35 | 36 | Just show a simple stats: 37 | 38 | ``` 39 | $ npx howfat -r simple mkdirp@0.5.1 40 | Dependencies: 1 41 | Size: 41.49kb 42 | Files: 37 43 | ``` 44 | 45 | as a table: 46 | 47 | ``` 48 | $ npx howfat -r table mkdirp@0.5.1 49 | mkdirp@0.5.1 (1 dep, 41.49kb, 37 files) 50 | ╭────────────────┬──────────────┬─────────┬───────╮ 51 | │ Name │ Dependencies │ Size │ Files │ 52 | ├────────────────┼──────────────┼─────────┼───────┤ 53 | │ minimist@0.0.8 │ 0 │ 20.78kb │ 14 │ 54 | ╰────────────────┴──────────────┴─────────┴───────╯ 55 | ``` 56 | 57 | as a json: 58 | 59 | `$ npx howfat -r json mkdirp@0.5.1 --space 2` 60 | ```json 61 | { 62 | "package": "mkdirp@0.5.1", 63 | "dependencyCount": 1, 64 | "fileCount": 37, 65 | "unpackedSize": 42486, 66 | "duplicate": false, 67 | "error": false, 68 | "unmet": false, 69 | "author": "{name:James Halliday,email:mail@substack.net,url:http://substack.net}", 70 | "deprecated": "Legacy versions of mkdirp are no longer supported. Please update to mkdirp 1.x. (Note that the API surface has changed to use Promises in 1.x.)", 71 | "description": "Recursively mkdir, like `mkdir -p`", 72 | "license": "MIT", 73 | "maintainers": "[{name:isaacs,email:i@izs.me}]", 74 | "ownStats": { 75 | "dependencyCount": 1, 76 | "fileCount": 23, 77 | "unpackedSize": 21212 78 | }, 79 | "children": [ 80 | { 81 | "package": "minimist@0.0.8", 82 | "dependencyCount": 0, 83 | "fileCount": 14, 84 | "unpackedSize": 21274, 85 | "duplicate": false, 86 | "error": false, 87 | "unmet": false, 88 | "author": "{name:James Halliday,email:mail@substack.net,url:http://substack.net}", 89 | "deprecated": "", 90 | "description": "parse argument options", 91 | "license": "MIT", 92 | "maintainers": "[{email:ljharb@gmail.com,name:ljharb},{email:github@tixz.dk,name:emilbayes}]", 93 | "ownStats": { 94 | "dependencyCount": 1, 95 | "fileCount": 14, 96 | "unpackedSize": 21274 97 | }, 98 | "children": [] 99 | } 100 | ] 101 | } 102 | ``` 103 | 104 | ### Other options 105 | 106 | ``` 107 | -d, --dev-dependencies BOOLEAN Fetch dev dependencies, default false 108 | -p, --peer-dependencies BOOLEAN Fetch peer dependencies, default false 109 | 110 | -r, --reporter STRING 'json', 'simple', 'table', 'tree', default tree 111 | --fields STRING Displayed fields separated by a comma: 112 | dependencies,size,files,license, 113 | author,description,maintainers,deprec, 114 | deprecated,node,os,platform 115 | --sort STRING Sort field. Add minus sign for 116 | desc order, like size-. Default to 'name' 117 | --space NUMBER Use spaces in json output, default null 118 | 119 | -v, --verbose BOOLEAN Show additional logs 120 | --no-colors BOOLEAN Prevent color output 121 | --no-human-readable BOOLEAN Show size in bytes 122 | 123 | --registry-url STRING Default to https://registry.npmjs.org/ 124 | 125 | --http Node.js RequestOptions, like: 126 | --http.timeout NUMBER Request timeout in ms, default 10000 127 | --http.connection-limit NUMBER Max simultaneous connections, default 10 128 | --http.retry-count NUMBER Try to fetch again of failure, default 5 129 | --http.proxy STRING A proxy server url 130 | 131 | --show-config Show the current configuration 132 | --version Show howfat version 133 | --help Show this help 134 | ``` 135 | 136 | ## Accuracy 137 | 138 | Different package managers use different dependency resolution algorithms. Even 139 | different versions of the same manager will resolve different dependency tree. 140 | So, this package tries to calculate stats similar to `npm`, but keep in mind that 141 | it provides approximate results. 142 | 143 | ## Why should I care about my package size? 144 | 145 | - Small package is installed much faster on CI 146 | - Runs faster via `npx` 147 | - Less dependencies = less troubles 148 | -------------------------------------------------------------------------------- /docs/screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/megahertz/howfat/c0d0dd7411de9b21de8034c58b2bcb1f162696ad/docs/screenshot.png -------------------------------------------------------------------------------- /e2e/fetch-package-options/test.spec.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const { test, expect } = require('humile'); 4 | const { runApp } = require('../runApp'); 5 | 6 | test('fetch-package-options', async () => { 7 | expect(await runApp({ args: 'package-options@0.1.4' })).toEqual([ 8 | 'package-options@0.1.4 (27.2kb, 12 files, ©MIT)', 9 | ]); 10 | }); 11 | -------------------------------------------------------------------------------- /e2e/runApp.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const { exec } = require('child_process'); 4 | const path = require('path'); 5 | 6 | module.exports = { runApp }; 7 | 8 | function runApp({ args, colors = false }) { 9 | const cmd = [ 10 | 'node', 11 | path.join(__dirname, '../src/index.js'), 12 | args, 13 | colors ? '' : '--no-colors', 14 | ].filter(Boolean).join(' '); 15 | 16 | return new Promise((resolve, reject) => { 17 | exec(cmd, (error, stdout) => { 18 | if (error) { 19 | reject(error); 20 | return; 21 | } 22 | 23 | const outputLines = stdout 24 | .split('\n') 25 | .map((line) => line.trim()) 26 | .filter(Boolean); 27 | 28 | resolve(outputLines); 29 | }); 30 | }); 31 | } 32 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "howfat", 3 | "version": "0.3.8", 4 | "description": "Shows how fat is a package", 5 | "bin": "src/index.js", 6 | "main": "src/index.js", 7 | "scripts": { 8 | "test": "humile src", 9 | "test:e2e": "humile e2e", 10 | "test:full": "npm test && npm run lint && npm run test:e2e", 11 | "lint": "eslint src", 12 | "postversion": "npm run test:full && git push && git push --tags", 13 | "prepack": "npm run test:full" 14 | }, 15 | "typings": "src/index.d.ts", 16 | "repository": "megahertz/howfat", 17 | "files": [ 18 | "src/**/*.js", 19 | "src/**/*.ts", 20 | "!**/__specs__/*" 21 | ], 22 | "keywords": [ 23 | "npm", 24 | "package", 25 | "size", 26 | "dependencies", 27 | "fat" 28 | ], 29 | "author": "Alexey Prokhorov", 30 | "license": "MIT", 31 | "bugs": "https://github.com/megahertz/howfat/issues", 32 | "homepage": "https://github.com/megahertz/howfat#readme", 33 | "engines": { 34 | "node": ">=14" 35 | }, 36 | "devDependencies": { 37 | "eslint": "^8.28.0", 38 | "eslint-config-airbnb-base": "^15.0.0", 39 | "eslint-plugin-import": "^2.26.0", 40 | "humile": "^0.5.0" 41 | }, 42 | "dependencies": { 43 | "axios": "^0.26.1", 44 | "npm-package-arg": "^10.0.0", 45 | "package-options": "^0.1.4", 46 | "semver": "^7.3.8", 47 | "tar": "^6.1.12" 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /src/__specs__/createFixture.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | 'use strict'; 4 | 5 | /* eslint-disable no-console */ 6 | 7 | const fs = require('fs'); 8 | const path = require('path'); 9 | const { createHttpClient } = require('../utils'); 10 | const { createDependencyResolver } = require('../resolver'); 11 | const { createDependencyFactory } = require('../dependency'); 12 | const { getFixtureName } = require('.'); 13 | const PackageFactory = require('../package/PackageFactory'); 14 | const TarballReader = require('../utils/tarball/TarballReader'); 15 | 16 | main(process.argv.slice(2)).catch(console.error); 17 | 18 | function createFixture(packageName) { 19 | const factory = createDependencyFactory(); 20 | const httpClient = createHttpClient(); 21 | const tarballReader = new TarballReader({ httpClient }); 22 | const resolver = createDependencyResolver( 23 | new PackageFactory({ httpClient, tarballReader }), 24 | factory, 25 | ); 26 | 27 | httpClient 28 | .on('finish', serializeTaskResponse) 29 | .on('start', (task) => console.error('get', task.url)); 30 | 31 | return resolver.resolve(factory.createGroup([packageName])); 32 | } 33 | 34 | async function main([packageName]) { 35 | if (!packageName) { 36 | console.info('Usage: ./createFixture.js PACKAGE[@version]'); 37 | return; 38 | } 39 | 40 | await createFixture(packageName); 41 | process.exit(0); 42 | } 43 | 44 | function serializeTaskResponse(task) { 45 | if (task.status !== 'resolved') return; 46 | 47 | const fixtureName = getFixtureName(path.basename(task.url)); 48 | const jsonPath = path.resolve(__dirname, 'fixtures', fixtureName + '.json'); 49 | 50 | if (!fs.existsSync(jsonPath)) { 51 | fs.writeFileSync(jsonPath, JSON.stringify(task.response)); 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /src/__specs__/fixtures/builtins-1.0.3.tgz.json: -------------------------------------------------------------------------------- 1 | {"fileCount":7,"unpackedSize":2696,"packageJson":{"name":"builtins","version":"1.0.3","description":"List of node.js builtin modules","repository":"juliangruber/builtins","license":"MIT","main":"builtins.json","publishConfig":{"registry":"https://registry.npmjs.org"},"scripts":{"test":"node test.js"}}} -------------------------------------------------------------------------------- /src/__specs__/fixtures/builtins.json: -------------------------------------------------------------------------------- 1 | {"_id":"builtins","_rev":"28-ea4624435c736e507be2ff809cddca4c","name":"builtins","description":"List of node.js builtin modules","dist-tags":{"latest":"2.0.1"},"versions":{"0.0.0":{"name":"builtins","version":"0.0.0","description":"List of node.js builtin modules","repository":{"type":"git","url":"git://github.com/segmentio/builtins"},"license":"MIT","bugs":{"url":"https://github.com/segmentio/builtins/issues"},"homepage":"https://github.com/segmentio/builtins","_id":"builtins@0.0.0","dist":{"shasum":"639d35f88b2db7d50e25de240cf4e025d215d46a","tarball":"https://registry.npmjs.org/builtins/-/builtins-0.0.0.tgz"},"_from":".","_npmVersion":"1.3.24","_npmUser":{"name":"juliangruber","email":"julian@juliangruber.com"},"maintainers":[{"name":"juliangruber","email":"julian@juliangruber.com"}],"directories":{}},"0.0.1":{"name":"builtins","version":"0.0.1","description":"List of node.js builtin modules","repository":{"type":"git","url":"git://github.com/segmentio/builtins"},"license":"MIT","main":"builtins.json","bugs":{"url":"https://github.com/segmentio/builtins/issues"},"homepage":"https://github.com/segmentio/builtins","_id":"builtins@0.0.1","dist":{"shasum":"732eb7e0ee53df2ba9c6b131cb94d31bab5b61b9","tarball":"https://registry.npmjs.org/builtins/-/builtins-0.0.1.tgz"},"_from":".","_npmVersion":"1.3.24","_npmUser":{"name":"juliangruber","email":"julian@juliangruber.com"},"maintainers":[{"name":"juliangruber","email":"julian@juliangruber.com"}],"directories":{}},"0.0.2":{"name":"builtins","version":"0.0.2","description":"List of node.js builtin modules","repository":{"type":"git","url":"git://github.com/segmentio/builtins"},"license":"MIT","main":"builtins.json","bugs":{"url":"https://github.com/segmentio/builtins/issues"},"homepage":"https://github.com/segmentio/builtins","_id":"builtins@0.0.2","dist":{"shasum":"b738db1ea166b752f7e0144c76f6ad5289448e51","tarball":"https://registry.npmjs.org/builtins/-/builtins-0.0.2.tgz"},"_from":".","_npmVersion":"1.3.24","_npmUser":{"name":"juliangruber","email":"julian@juliangruber.com"},"maintainers":[{"name":"juliangruber","email":"julian@juliangruber.com"}],"directories":{}},"0.0.3":{"name":"builtins","version":"0.0.3","description":"List of node.js builtin modules","repository":{"type":"git","url":"git://github.com/segmentio/builtins"},"license":"MIT","main":"builtins.json","bugs":{"url":"https://github.com/segmentio/builtins/issues"},"homepage":"https://github.com/segmentio/builtins","_id":"builtins@0.0.3","dist":{"shasum":"5d006166da71610bc2bcf73019f0f0cc43309755","tarball":"https://registry.npmjs.org/builtins/-/builtins-0.0.3.tgz"},"_from":".","_npmVersion":"1.4.3","_npmUser":{"name":"juliangruber","email":"julian@juliangruber.com"},"maintainers":[{"name":"juliangruber","email":"julian@juliangruber.com"}],"directories":{}},"0.0.4":{"name":"builtins","version":"0.0.4","description":"List of node.js builtin modules","repository":{"type":"git","url":"git://github.com/segmentio/builtins"},"license":"MIT","main":"builtins.json","bugs":{"url":"https://github.com/segmentio/builtins/issues"},"homepage":"https://github.com/segmentio/builtins","_id":"builtins@0.0.4","dist":{"shasum":"ec6d4ca4ba5a3a173749f10146bdcda28ea6d65d","tarball":"https://registry.npmjs.org/builtins/-/builtins-0.0.4.tgz"},"_from":".","_npmVersion":"1.3.22","_npmUser":{"name":"segment","email":"tj@segment.io"},"maintainers":[{"name":"juliangruber","email":"julian@juliangruber.com"},{"name":"segment","email":"tj@segment.io"}],"directories":{}},"0.0.5":{"name":"builtins","version":"0.0.5","description":"List of node.js builtin modules","repository":{"type":"git","url":"git://github.com/segmentio/builtins"},"license":"MIT","main":"builtins.json","publishConfig":{"registry":"https://registry.npmjs.org"},"gitHead":"6fdb09e119dfdd35ddcac1821a5cb58fd5ca8a8f","bugs":{"url":"https://github.com/segmentio/builtins/issues"},"homepage":"https://github.com/segmentio/builtins","_id":"builtins@0.0.5","scripts":{},"_shasum":"86dd881f9862856e62fd7ed7767b438c4d79b7ab","_from":".","_npmVersion":"1.4.14","_npmUser":{"name":"segment","email":"tj@segment.io"},"maintainers":[{"name":"juliangruber","email":"julian@juliangruber.com"},{"name":"segment","email":"tj@segment.io"}],"dist":{"shasum":"86dd881f9862856e62fd7ed7767b438c4d79b7ab","tarball":"https://registry.npmjs.org/builtins/-/builtins-0.0.5.tgz"},"directories":{}},"0.0.6":{"name":"builtins","version":"0.0.6","description":"List of node.js builtin modules","repository":{"type":"git","url":"git://github.com/segmentio/builtins"},"license":"MIT","main":"builtins.json","publishConfig":{"registry":"https://registry.npmjs.org"},"scripts":{"test":"node -e \"require('./builtins.json')\""},"bugs":{"url":"https://github.com/segmentio/builtins/issues"},"homepage":"https://github.com/segmentio/builtins","_id":"builtins@0.0.6","dist":{"shasum":"14841048b8bc745c87c18f5dfeb4f783311a5a37","tarball":"https://registry.npmjs.org/builtins/-/builtins-0.0.6.tgz"},"_from":".","_npmVersion":"1.3.22","_npmUser":{"name":"juliangruber","email":"julian@juliangruber.com"},"maintainers":[{"name":"juliangruber","email":"julian@juliangruber.com"},{"name":"segment","email":"tj@segment.io"}],"directories":{}},"0.0.7":{"name":"builtins","version":"0.0.7","description":"List of node.js builtin modules","repository":{"type":"git","url":"git://github.com/juliangruber/builtins"},"license":"MIT","main":"builtins.json","publishConfig":{"registry":"https://registry.npmjs.org"},"scripts":{"test":"node -e \"require('./builtins.json')\""},"bugs":{"url":"https://github.com/juliangruber/builtins/issues"},"homepage":"https://github.com/juliangruber/builtins","_id":"builtins@0.0.7","dist":{"shasum":"355219cd6cf18dbe7c01cc7fd2dce765cfdc549a","tarball":"https://registry.npmjs.org/builtins/-/builtins-0.0.7.tgz"},"_from":".","_npmVersion":"1.3.22","_npmUser":{"name":"juliangruber","email":"julian@juliangruber.com"},"maintainers":[{"name":"juliangruber","email":"julian@juliangruber.com"},{"name":"segment","email":"tj@segment.io"}],"directories":{}},"1.0.0":{"name":"builtins","version":"1.0.0","description":"List of node.js builtin modules","repository":{"type":"git","url":"https://github.com/juliangruber/builtins"},"license":"MIT","main":"builtins.json","publishConfig":{"registry":"https://registry.npmjs.org"},"scripts":{"test":"node -e \"require('./builtins.json')\""},"gitHead":"2d513ebb6b2556e0e6cacf3aac709d48070a461e","bugs":{"url":"https://github.com/juliangruber/builtins/issues"},"homepage":"https://github.com/juliangruber/builtins","_id":"builtins@1.0.0","_shasum":"5bbb1cf059da03c16cfb5b8999e77c2b9e4998cc","_from":".","_npmVersion":"2.6.1","_nodeVersion":"0.10.36","_npmUser":{"name":"juliangruber","email":"julian@juliangruber.com"},"maintainers":[{"name":"juliangruber","email":"julian@juliangruber.com"},{"name":"segment","email":"tj@segment.io"}],"dist":{"shasum":"5bbb1cf059da03c16cfb5b8999e77c2b9e4998cc","tarball":"https://registry.npmjs.org/builtins/-/builtins-1.0.0.tgz"},"directories":{}},"1.0.1":{"name":"builtins","version":"1.0.1","description":"List of node.js builtin modules","repository":{"type":"git","url":"https://github.com/juliangruber/builtins"},"license":"MIT","main":"builtins.json","publishConfig":{"registry":"https://registry.npmjs.org"},"scripts":{"test":"node -e \"require('./builtins.json')\""},"gitHead":"1b57ac1f0d3fdcde800967336f61f552615c5f96","bugs":{"url":"https://github.com/juliangruber/builtins/issues"},"homepage":"https://github.com/juliangruber/builtins","_id":"builtins@1.0.1","_shasum":"4cc7f9635eb07653d1daa63e475c6a0ac45a8042","_from":".","_npmVersion":"2.6.1","_nodeVersion":"0.10.36","_npmUser":{"name":"juliangruber","email":"julian@juliangruber.com"},"maintainers":[{"name":"juliangruber","email":"julian@juliangruber.com"},{"name":"segment","email":"tj@segment.io"}],"dist":{"shasum":"4cc7f9635eb07653d1daa63e475c6a0ac45a8042","tarball":"https://registry.npmjs.org/builtins/-/builtins-1.0.1.tgz"},"directories":{}},"1.0.2":{"name":"builtins","version":"1.0.2","description":"List of node.js builtin modules","repository":{"type":"git","url":"https://github.com/juliangruber/builtins"},"license":"MIT","main":"builtins.json","publishConfig":{"registry":"https://registry.npmjs.org"},"scripts":{"test":"node test.js"},"gitHead":"0d8fb1463330fad2df99605f29b934cd77f84b46","bugs":{"url":"https://github.com/juliangruber/builtins/issues"},"homepage":"https://github.com/juliangruber/builtins","_id":"builtins@1.0.2","_shasum":"db1e3693b71a56ff4113815a81b6294522654e0b","_from":".","_npmVersion":"2.6.1","_nodeVersion":"0.10.36","_npmUser":{"name":"juliangruber","email":"julian@juliangruber.com"},"maintainers":[{"name":"juliangruber","email":"julian@juliangruber.com"},{"name":"segment","email":"tj@segment.io"}],"dist":{"shasum":"db1e3693b71a56ff4113815a81b6294522654e0b","tarball":"https://registry.npmjs.org/builtins/-/builtins-1.0.2.tgz"},"directories":{}},"1.0.3":{"name":"builtins","version":"1.0.3","description":"List of node.js builtin modules","repository":{"type":"git","url":"git+https://github.com/juliangruber/builtins.git"},"license":"MIT","main":"builtins.json","publishConfig":{"registry":"https://registry.npmjs.org"},"scripts":{"test":"node test.js"},"gitHead":"be046dcd4e338cadff24d3330ad43d65dfd80bb3","bugs":{"url":"https://github.com/juliangruber/builtins/issues"},"homepage":"https://github.com/juliangruber/builtins#readme","_id":"builtins@1.0.3","_shasum":"cb94faeb61c8696451db36534e1422f94f0aee88","_from":".","_npmVersion":"2.14.7","_nodeVersion":"4.2.1","_npmUser":{"name":"juliangruber","email":"julian@juliangruber.com"},"dist":{"shasum":"cb94faeb61c8696451db36534e1422f94f0aee88","tarball":"https://registry.npmjs.org/builtins/-/builtins-1.0.3.tgz"},"maintainers":[{"name":"juliangruber","email":"julian@juliangruber.com"},{"name":"segment","email":"tj@segment.io"}],"directories":{}},"2.0.0":{"name":"builtins","version":"2.0.0","description":"List of node.js builtin modules","repository":{"type":"git","url":"git+https://github.com/juliangruber/builtins.git"},"license":"MIT","main":"index.js","scripts":{"test":"node test.js"},"dependencies":{"semver":"^5.4.1"},"devDependencies":{"prettier-standard":"^7.0.3","standard":"^10.0.3","tape":"^4.8.0"},"publishConfig":{"registry":"https://registry.npmjs.org"},"gitHead":"84d9861f14e3a6585a71627881f6516df611b99b","bugs":{"url":"https://github.com/juliangruber/builtins/issues"},"homepage":"https://github.com/juliangruber/builtins#readme","_id":"builtins@2.0.0","_npmVersion":"5.5.1","_nodeVersion":"9.0.0","_npmUser":{"name":"juliangruber","email":"julian@juliangruber.com"},"dist":{"integrity":"sha512-8srrxpDx3a950BHYcbse+xMjupHHECvQYnShkoPz2ZLhTBrk/HQO6nWMh4o4ui8YYp2ourGVYXlGqFm+UYQwmA==","shasum":"018999641e11252188652dbb2db01ad386fcdc46","tarball":"https://registry.npmjs.org/builtins/-/builtins-2.0.0.tgz"},"maintainers":[{"email":"tools+npm@segment.com","name":"segment-admin"},{"email":"julian@juliangruber.com","name":"juliangruber"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages","tmp":"tmp/builtins-2.0.0.tgz_1513525346292_0.8955418218392879"},"directories":{}},"2.0.1":{"name":"builtins","version":"2.0.1","description":"List of node.js builtin modules","repository":{"type":"git","url":"git+https://github.com/juliangruber/builtins.git"},"license":"MIT","main":"index.js","scripts":{"test":"node test.js"},"dependencies":{"semver":"^6.0.0"},"devDependencies":{"prettier-standard":"^9.0.0","standard":"^12.0.0","tape":"^4.8.0"},"gitHead":"df4a0c37bdf50cd5215f71fd5e740f5ba7dc4b05","bugs":{"url":"https://github.com/juliangruber/builtins/issues"},"homepage":"https://github.com/juliangruber/builtins#readme","_id":"builtins@2.0.1","_nodeVersion":"12.0.0","_npmVersion":"6.9.0","dist":{"integrity":"sha512-XkkVe5QAb6guWPXTzpSrYpSlN3nqEmrrE2TkAr/tp7idSF6+MONh9WvKrAuR3HiKLvoSgmbs8l1U9IPmMrIoLw==","shasum":"42a4d6fe38973a7c185b435970d13e5e70f70f3c","tarball":"https://registry.npmjs.org/builtins/-/builtins-2.0.1.tgz","fileCount":7,"unpackedSize":4921,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.4\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJc94esCRA9TVsSAnZWagAAkyMP/25npMLc2JQBOzzQtEP1\nQQxj+pMfConr2SybLt6gQY+Wtvo1pdrTMiLCvAIxzPjshFr87QeAKwECJ94+\nF//uZj5gOyS/MiOzJTTj4vv/VTDm9UiuuOoYYrqtp5KHfLkblKuizL17SMsG\nYTBIrGGb/uiU53cZDh5sEmKDCmkBdolyYuLtPa08Uh5XbfYu4U3ZWAfe6KVP\nl3EVpxUsVlFF92ZVmYLYYkpN7sRJqZOOc2G8VAM2ExFemKMGY0AmEYq2HUnu\nNS3Nx0wFkiI65DhkSs1MuKQ0QnBLgZ0iiWgo0aOOr/WBnTtN9Gqm4BjdimUA\nxtbI2qolAeCwwkRUTB0xML+FGu07FkuPEt42ZFolJ2rKa7F7tVK2Zy6XcI7H\ndeFuedgxzNUBLoDYGV6ng1LEHluywEw7jr/NKwmiFlu2NnGxIr0TRk9cjsOw\n3LCI6zR9UDl5abbdud9Lcwh6u+2ozX/DSljcSDJPn21wlbmo7bWQ7gdvtx3V\naWZND24OoEIAtQ4go2pcEzXY5NYshOmmPGcwFuiPh0cY83pdEXlPB2mE8OOT\nRs/8faOrKWccJiquha3yvKSgzYIndLeoCYoIaBAByxa4byfCNTkLcFf5o8bM\ngsMd+I+v6IeyH+DQ/oVf/3x2X0BuuRfYn08iKbfct+B7cvssfVlEQxlkr5q0\nDZtW\r\n=oZR8\r\n-----END PGP SIGNATURE-----\r\n"},"maintainers":[{"email":"julian@juliangruber.com","name":"juliangruber"},{"email":"tools+npm@segment.com","name":"segment-admin"}],"_npmUser":{"name":"juliangruber","email":"julian@juliangruber.com"},"directories":{},"_npmOperationalInternal":{"host":"s3://npm-registry-packages","tmp":"tmp/builtins_2.0.1_1559725995856_0.5507155920380598"},"_hasShrinkwrap":false}},"readme":"\n# builtins\n\n List of node.js [builtin modules](http://nodejs.org/api/).\n\n [![build status](https://secure.travis-ci.org/juliangruber/builtins.svg)](http://travis-ci.org/juliangruber/builtins) [![Greenkeeper badge](https://badges.greenkeeper.io/juliangruber/builtins.svg)](https://greenkeeper.io/)\n\n## Example\n\nGet list of core modules for current Node.js version:\n\n```js\nvar builtins = require('builtins')()\n\nassert(builtins.indexOf('http') > -1)\n```\n\nGet list of core modules for specific Node.js version:\n\n```js\nvar builtins = require('builtins')('6.0.0')\n\nassert(builtins.indexOf('http') > -1)\n```\n\n## License\n\n MIT\n","maintainers":[{"email":"julian@juliangruber.com","name":"juliangruber"},{"email":"tools+npm@segment.com","name":"segment-admin"}],"time":{"modified":"2019-06-05T09:13:18.692Z","created":"2014-02-11T08:35:44.864Z","0.0.0":"2014-02-11T08:35:44.864Z","0.0.1":"2014-02-11T08:37:17.448Z","0.0.2":"2014-02-11T09:54:02.820Z","0.0.3":"2014-02-22T10:42:01.107Z","0.0.4":"2014-04-26T06:11:50.887Z","0.0.5":"2014-06-27T08:45:36.181Z","0.0.6":"2014-09-01T10:06:00.540Z","0.0.7":"2014-09-01T10:17:05.993Z","1.0.0":"2015-03-23T11:49:17.230Z","1.0.1":"2015-04-13T10:15:53.553Z","1.0.2":"2015-06-29T12:38:33.263Z","1.0.3":"2015-10-25T10:39:36.182Z","2.0.0":"2017-12-17T15:42:27.181Z","2.0.1":"2019-06-05T09:13:16.040Z"},"readmeFilename":"Readme.md","homepage":"https://github.com/juliangruber/builtins#readme","repository":{"type":"git","url":"git+https://github.com/juliangruber/builtins.git"},"bugs":{"url":"https://github.com/juliangruber/builtins/issues"},"license":"MIT","users":{"simplyianm":true,"voxpelli":true}} -------------------------------------------------------------------------------- /src/__specs__/fixtures/lighter-config-1.1.0.tgz.json: -------------------------------------------------------------------------------- 1 | {"fileCount":8,"unpackedSize":22354,"packageJson":{"name":"lighter-config","version":"1.1.0","description":"A lightweight JavaScript configuration utility.","dependencies":{},"devDependencies":{"exam":">=1.3.0 <2"},"keywords":["lighter","config","configuration","development","staging","production","base","common","shared","sandbox","alpha","local","qa","test","beta","ci","jenkins","gamma","release","one","canary","conf","cnf","json","load","get","loader","lite","light","lightweight","fast","quick","simple","basic","optimized","optimize","sync","synchronous"],"main":"lighter-config.js","engines":{"node":">=0.10.40"},"scripts":{"test":"./node_modules/exam/exam.js","cover":"./node_modules/exam/exam-cover.js","report":"open coverage/lcov-report/index.html","coveralls":"./node_modules/exam/exam-coveralls.js"},"repository":"https://github.com/lighterio/lighter-config.git","bugs":"https://github.com/lighterio/lighter-config/issues","homepage":"https://github.com/lighterio/lighter-config","author":"Sam Eubank ","contributors":["Sam Eubank "],"license":"ISC"}} -------------------------------------------------------------------------------- /src/__specs__/fixtures/lighter-config.json: -------------------------------------------------------------------------------- 1 | {"_id":"lighter-config","_rev":"3-f4586400ff1517b4fb59bbd66f8e5c3c","name":"lighter-config","description":"A lightweight JavaScript configuration utility.","dist-tags":{"latest":"1.1.0"},"versions":{"1.0.0":{"name":"lighter-config","version":"1.0.0","description":"A lightweight JavaScript configuration utility.","dependencies":{},"devDependencies":{"exam":">=1.3.0 <2"},"keywords":["lighter","config","configuration","development","staging","production","base","common","shared","sandbox","alpha","local","qa","test","beta","ci","jenkins","gamma","release","one","canary","conf","cnf","json","load","get","loader","lite","light","lightweight","fast","quick","simple","basic","optimized","optimize","sync","synchronous"],"main":"lighter-config.js","engines":{"node":">=0.10.40"},"scripts":{"test":"./node_modules/exam/exam.js","cover":"./node_modules/exam/exam-cover.js","report":"open coverage/lcov-report/index.html","coveralls":"./node_modules/exam/exam-coveralls.js"},"repository":{"type":"git","url":"git+https://github.com/lighterio/lighter-config.git"},"bugs":{"url":"https://github.com/lighterio/lighter-config/issues"},"homepage":"https://github.com/lighterio/lighter-config","author":{"name":"Sam Eubank","email":"sameubank@gmail.com"},"contributors":[{"name":"Sam Eubank","email":"sameubank@gmail.com"}],"license":"ISC","gitHead":"5289808b5b81f5cdaa5b3e35c4688c0c0dc4d9c1","_id":"lighter-config@1.0.0","_shasum":"0ae54d8d021b55e4279f11e71f58ef345dde08d0","_from":".","_npmVersion":"3.3.8","_nodeVersion":"4.1.1","_npmUser":{"name":"zerious","email":"sameubank@gmail.com"},"maintainers":[{"name":"zerious","email":"sameubank@gmail.com"}],"dist":{"shasum":"0ae54d8d021b55e4279f11e71f58ef345dde08d0","tarball":"https://registry.npmjs.org/lighter-config/-/lighter-config-1.0.0.tgz"},"directories":{}},"1.1.0":{"name":"lighter-config","version":"1.1.0","description":"A lightweight JavaScript configuration utility.","dependencies":{},"devDependencies":{"exam":">=1.3.0 <2"},"keywords":["lighter","config","configuration","development","staging","production","base","common","shared","sandbox","alpha","local","qa","test","beta","ci","jenkins","gamma","release","one","canary","conf","cnf","json","load","get","loader","lite","light","lightweight","fast","quick","simple","basic","optimized","optimize","sync","synchronous"],"main":"lighter-config.js","engines":{"node":">=0.10.40"},"scripts":{"test":"./node_modules/exam/exam.js","cover":"./node_modules/exam/exam-cover.js","report":"open coverage/lcov-report/index.html","coveralls":"./node_modules/exam/exam-coveralls.js"},"repository":{"type":"git","url":"git+https://github.com/lighterio/lighter-config.git"},"bugs":{"url":"https://github.com/lighterio/lighter-config/issues"},"homepage":"https://github.com/lighterio/lighter-config","author":{"name":"Sam Eubank","email":"sameubank@gmail.com"},"contributors":[{"name":"Sam Eubank","email":"sameubank@gmail.com"}],"license":"ISC","gitHead":"ffc7f58b3076091b20867805c2e478f5445d0c78","_id":"lighter-config@1.1.0","_shasum":"732c7cf6de03bd73d8c3642466d8155a4d0acdbe","_from":".","_npmVersion":"2.11.3","_nodeVersion":"0.12.7","_npmUser":{"name":"zerious","email":"sameubank@gmail.com"},"dist":{"shasum":"732c7cf6de03bd73d8c3642466d8155a4d0acdbe","tarball":"https://registry.npmjs.org/lighter-config/-/lighter-config-1.1.0.tgz"},"maintainers":[{"name":"zerious","email":"sameubank@gmail.com"}],"_npmOperationalInternal":{"host":"packages-9-west.internal.npmjs.com","tmp":"tmp/lighter-config-1.1.0.tgz_1454966403648_0.462170728482306"},"directories":{}}},"readme":"# lighter-config\n[![Chat](https://badges.gitter.im/chat.svg)](//gitter.im/lighterio/public)\n[![Version](https://img.shields.io/npm/v/lighter-config.svg)](//www.npmjs.com/package/lighter-config)\n[![Downloads](https://img.shields.io/npm/dm/lighter-config.svg)](//www.npmjs.com/package/lighter-config)\n[![Build](https://img.shields.io/travis/lighterio/lighter-config.svg)](//travis-ci.org/lighterio/lighter-config)\n[![Coverage](https://img.shields.io/coveralls/lighterio/lighter-config/master.svg)](//coveralls.io/r/lighterio/lighter-config)\n[![Style](https://img.shields.io/badge/code%20style-standard-brightgreen.svg)](//www.npmjs.com/package/standard)\n\nThe `lighter-config` module loads configuration files and allows JSON to be\ndecorated with environment variables for `\"development\"`, `\"staging\"` and\n`\"production\"` environments, according to the `NODE_ENV`, `LIGHTER_ENV`,\n`DEPLOY_ENV` or `ENV` environment variables.\n\n## Installation\nFrom your project directory, install and save as a dependency:\n```bash\nnpm install --save lighter-config\n```\n\n## Usage\nCreate the following files in your project directory, with some JSON content:\n* `/config/base.json`\n* `/config/development.json`\n* `/config/staging.json`\n* `/config/production.json`\n\nThen in your project, from a file such as `/index.js`, require the\n`lighter-config` module, and use the result as your configuration.\n\n```js\nvar config = require('lighter-config')\n\n// Example usage of config:\nif (config.isDevelopment) {\n console.log('Currently running in development mode.')\n\n // And if base.json or development.json has a \"host\" and \"port\" property:\n console.log('Listening at http://' + config.host + ':' + config.port)\n}\n```\n\n## Configuration Result\nThe result of requiring `lighter-config` is an object with the following\nbase properties, as well as any properties found in configuration files:\n\n* (String) `config.env`\n* (String) `config.environment`\n* (Boolean) `config.isDebug`\n* (Boolean) `config.isDevelopment`\n* (Boolean) `config.isStaging`\n* (Boolean) `config.isProduction`\n* (Function) `config.get(options)`\n* (Function) `config.load(dir, name)`\n\n### `config.env`\nThe `env` is found from an environment variable, as described below, or from\nthe `env` property of the options argument to the `get` method as described\nbelow. If omitted, it defaults to \"staging\".\n\n### `config.environment`\nThe `environment` is based on the `env`, and is coerced to be either\n\"development\", \"staging\" or \"production\".\n\n### `config.isDebug`\nThe `isDebug` property is a special property which is true if the `env` is\nsomething like \"debug\" or \"dbg\".\n\n### `config.isDevelopment`, `config.isStaging` and `config.isProduction`\nThe `isDevelopment`, `isStaging` and `isProduction` properties are based on the\n`environment`. Exactly one of them is `true`.\n\n### `config.get(options)`\nThe `lighter-config` library returns an object which can be used to load other\nconfigurations using the `get` method. The options object can have the\nfollowing properties:\n* `env` determines the `config.env` value (with the default being \"staging\").\n* `dir` determines which directory JSON files will be loaded from (with the\n default being \"config\"). The value is used to prefix calls to\n `fs.readFileSync`, so it can be an absolute path.\n* `base` determines the name of the base configuration, such as `base.json`,\n which is loaded prior to the environment-specific configuration, such as\n `staging.json`. It defaults to \"base\", making the base configuration file\n `base.json`.\n\nExample:\n```js\nvar config = require('lighter-config')\nvar prodConfig = config.get({env: 'production'})\n```\n\n### `config.load(dir, name)`\nThe `load` method can load a named configuration from a directory, and use it\nto override a configuration object's properties.\n\n### `config.subEnvironments`\nIf a configuration file contains a key called `subEnvironments` with an array\nof sub-environment names, then those environment names are whitelisted as\noverride files.\n\nExample `production.json`:\n```js\n{\n \"key\": \"p\",\n \"subEnvironments\": [\"pre-production\"]\n}\n```\n\nExample `pre-production.json`:\n```js\n{\n \"key\": \"pp\"\n}\n```\nIf the `NODE_ENV` is set to \"pre-production\", then the \"pre-production.json\"\nfile will be loaded and used to override the \"production.json\" configuration.\n\n\n## Environment Variables\nYou can affect the outcome of `lighter-config` by running your application with\nspecific environment variable values, or modifying `process.env` prior to\nrequiring `lighter-config`.\n\n### `CONFIG_DIR`\nDetermines where `lighter-config` will look for configuration JSON files.\nDefaults to \"config\" if not found.\n\n### `CONFIG_BASE`\nDetermines the name of the base configuration. Defaults to \"base\".\n\n### `NODE_ENV`, `LIGHTER_ENV`, `DEPLOY_ENV` or `ENV`\nDetermines the value of `config.env` directly, and `config.environment`\nindirectly. Defaults to \"staging\" if not found.\n\n## Replacements\nConfiguration files can include replacement variables, for which substitutions\nwill be made. For example, if you want to expect a host and port to be in the\n`HOST` and `PORT` environment variables, you can provide the following JSON:\n```json\n{\n \"host\": \"$HOST\",\n \"port\": \"$PORT\"\n}\n```\n\nYou can also include default values:\n```json\n{\n \"host\": \"${HOST-localhost}\",\n \"port\": \"${PORT-1337}\"\n}\n```\n\n## More on lighter-config...\n* [Contributing](//github.com/lighterio/lighter-config/blob/master/CONTRIBUTING.md)\n* [License (ISC)](//github.com/lighterio/lighter-config/blob/master/LICENSE.md)\n* [Change Log](//github.com/lighterio/lighter-config/blob/master/CHANGELOG.md)\n* [Roadmap](//github.com/lighterio/lighter-config/blob/master/ROADMAP.md)\n","maintainers":[{"name":"zerious","email":"sameubank@gmail.com"}],"time":{"modified":"2016-02-08T21:20:06.689Z","created":"2015-11-17T22:20:20.678Z","1.0.0":"2015-11-17T22:20:20.678Z","1.1.0":"2016-02-08T21:20:06.689Z"},"homepage":"https://github.com/lighterio/lighter-config","keywords":["lighter","config","configuration","development","staging","production","base","common","shared","sandbox","alpha","local","qa","test","beta","ci","jenkins","gamma","release","one","canary","conf","cnf","json","load","get","loader","lite","light","lightweight","fast","quick","simple","basic","optimized","optimize","sync","synchronous"],"repository":{"type":"git","url":"git+https://github.com/lighterio/lighter-config.git"},"contributors":[{"name":"Sam Eubank","email":"sameubank@gmail.com"}],"author":{"name":"Sam Eubank","email":"sameubank@gmail.com"},"bugs":{"url":"https://github.com/lighterio/lighter-config/issues"},"license":"ISC","readmeFilename":"README.md","users":{"zerious":true},"_attachments":{}} -------------------------------------------------------------------------------- /src/__specs__/fixtures/lighter-run-1.2.1.tgz.json: -------------------------------------------------------------------------------- 1 | {"fileCount":11,"unpackedSize":27695,"packageJson":{"name":"lighter-run","version":"1.2.1","description":"A process runner with refresh capabilities.","dependencies":{"lighter-config":">=1.1.0 <2"},"optionalDependencies":{"fsevents":">=1.0.14 <2"},"devDependencies":{"exam":"^1.4.5"},"keywords":["lighter","app","application","run","runner","spawn","watch","watcher","reload","refresh","utility","fast","lightweight"],"bin":{"lighter-run":"run.js"},"main":"run.js","engines":{"node":">=0.8.28"},"scripts":{"test":"./node_modules/exam/exam.js","cover":"./node_modules/exam/exam-cover.js","report":"open coverage/lcov-report/index.html","bench":"./node_modules/exam/exam.js test/bench"},"repository":"https://github.com/lighterio/lighter-run.git","bugs":"https://github.com/lighterio/lighter-run/issues","homepage":"https://github.com/lighterio/lighter-run","author":"Sam Eubank ","contributors":["Sam Eubank "],"license":"MIT"}} -------------------------------------------------------------------------------- /src/__specs__/fixtures/lighter-run.json: -------------------------------------------------------------------------------- 1 | {"_id":"lighter-run","_rev":"4-d4d204a04ddb97b213ad4c8430bb97c4","name":"lighter-run","description":"A process runner with refresh capabilities.","dist-tags":{"latest":"1.2.1"},"versions":{"1.0.0":{"name":"lighter-run","version":"1.0.0","description":"A process runner with refresh capabilities.","dependencies":{"lighter-config":">=1.1.0 <2","fsevents":">=1.0.14 <2"},"optionalDependencies":{"fsevents":">=1.0.14 <2"},"devDependencies":{"exam":">=1.4.5 <2"},"keywords":["lighter","app","application","run","runner","spawn","watch","watcher","reload","refresh","utility","fast","lightweight"],"bin":{"lighter-run":"run.js"},"main":"run.js","engines":{"node":">=0.8.28"},"scripts":{"test":"./node_modules/exam/exam.js","cover":"./node_modules/exam/exam-cover.js","report":"open coverage/lcov-report/index.html","coveralls":"./node_modules/exam/exam-coveralls.js","bench":"./node_modules/exam/exam.js test/bench"},"repository":{"type":"git","url":"git+https://github.com/lighterio/lighter-run.git"},"bugs":{"url":"https://github.com/lighterio/lighter-run/issues"},"homepage":"https://github.com/lighterio/lighter-run","author":{"name":"Sam Eubank","email":"sameubank@gmail.com"},"contributors":[{"name":"Sam Eubank","email":"sameubank@gmail.com"}],"license":"ISC","gitHead":"67f00f6d0058566019b81d5ff00ce23269169cac","_id":"lighter-run@1.0.0","_shasum":"c6f3493be6a7d316c29ccc73b9398f112c0f05fc","_from":".","_npmVersion":"3.3.8","_nodeVersion":"4.1.1","_npmUser":{"name":"zerious","email":"sameubank@gmail.com"},"maintainers":[{"name":"zerious","email":"sameubank@gmail.com"}],"dist":{"shasum":"c6f3493be6a7d316c29ccc73b9398f112c0f05fc","tarball":"https://registry.npmjs.org/lighter-run/-/lighter-run-1.0.0.tgz"},"_npmOperationalInternal":{"host":"packages-12-west.internal.npmjs.com","tmp":"tmp/lighter-run-1.0.0.tgz_1477452758857_0.29853357444517314"},"directories":{}},"1.1.0":{"name":"lighter-run","version":"1.1.0","description":"A process runner with refresh capabilities.","dependencies":{"lighter-config":">=1.1.0 <2","fsevents":">=1.0.14 <2"},"optionalDependencies":{"fsevents":">=1.0.14 <2"},"devDependencies":{"exam":">=1.4.5 <2"},"keywords":["lighter","app","application","run","runner","spawn","watch","watcher","reload","refresh","utility","fast","lightweight"],"bin":{"lighter-run":"run.js"},"main":"run.js","engines":{"node":">=0.8.28"},"scripts":{"test":"./node_modules/exam/exam.js","cover":"./node_modules/exam/exam-cover.js","report":"open coverage/lcov-report/index.html","coveralls":"./node_modules/exam/exam-coveralls.js","bench":"./node_modules/exam/exam.js test/bench"},"repository":{"type":"git","url":"git+https://github.com/lighterio/lighter-run.git"},"bugs":{"url":"https://github.com/lighterio/lighter-run/issues"},"homepage":"https://github.com/lighterio/lighter-run","author":{"name":"Sam Eubank","email":"sameubank@gmail.com"},"contributors":[{"name":"Sam Eubank","email":"sameubank@gmail.com"}],"license":"ISC","gitHead":"5c1f5f8a61675cd477ba0847a9475c2530d13b8f","_id":"lighter-run@1.1.0","_shasum":"f86ccccc90cf031e17390f409f3d7e578f1eb116","_from":".","_npmVersion":"3.10.10","_nodeVersion":"6.9.4","_npmUser":{"name":"zerious","email":"sameubank@gmail.com"},"maintainers":[{"name":"zerious","email":"sameubank@gmail.com"}],"dist":{"shasum":"f86ccccc90cf031e17390f409f3d7e578f1eb116","tarball":"https://registry.npmjs.org/lighter-run/-/lighter-run-1.1.0.tgz"},"_npmOperationalInternal":{"host":"packages-18-east.internal.npmjs.com","tmp":"tmp/lighter-run-1.1.0.tgz_1483687509747_0.35493733710609376"},"directories":{}},"1.2.0":{"name":"lighter-run","version":"1.2.0","description":"A process runner with refresh capabilities.","dependencies":{"lighter-config":">=1.1.0 <2","fsevents":">=1.0.14 <2"},"optionalDependencies":{"fsevents":">=1.0.14 <2"},"devDependencies":{"exam":"^1.4.5"},"keywords":["lighter","app","application","run","runner","spawn","watch","watcher","reload","refresh","utility","fast","lightweight"],"bin":{"lighter-run":"run.js"},"main":"run.js","engines":{"node":">=0.8.28"},"scripts":{"test":"./node_modules/exam/exam.js","cover":"./node_modules/exam/exam-cover.js","report":"open coverage/lcov-report/index.html","bench":"./node_modules/exam/exam.js test/bench"},"repository":{"type":"git","url":"git+https://github.com/lighterio/lighter-run.git"},"bugs":{"url":"https://github.com/lighterio/lighter-run/issues"},"homepage":"https://github.com/lighterio/lighter-run","author":{"name":"Sam Eubank","email":"sameubank@gmail.com"},"contributors":[{"name":"Sam Eubank","email":"sameubank@gmail.com"}],"license":"MIT","gitHead":"13c63d5b70d241de17c80eb33cf23925e1ad13ff","_id":"lighter-run@1.2.0","_shasum":"36f0a86eb2637a0b5f68aabbf5d73655f21619d6","_from":".","_npmVersion":"3.10.10","_nodeVersion":"6.9.5","_npmUser":{"name":"zerious","email":"sameubank@gmail.com"},"dist":{"shasum":"36f0a86eb2637a0b5f68aabbf5d73655f21619d6","tarball":"https://registry.npmjs.org/lighter-run/-/lighter-run-1.2.0.tgz"},"maintainers":[{"name":"zerious","email":"sameubank@gmail.com"}],"_npmOperationalInternal":{"host":"packages-12-west.internal.npmjs.com","tmp":"tmp/lighter-run-1.2.0.tgz_1488860646356_0.44512079237028956"},"directories":{}},"1.2.1":{"name":"lighter-run","version":"1.2.1","description":"A process runner with refresh capabilities.","dependencies":{"lighter-config":">=1.1.0 <2","fsevents":">=1.0.14 <2"},"optionalDependencies":{"fsevents":">=1.0.14 <2"},"devDependencies":{"exam":"^1.4.5"},"keywords":["lighter","app","application","run","runner","spawn","watch","watcher","reload","refresh","utility","fast","lightweight"],"bin":{"lighter-run":"run.js"},"main":"run.js","engines":{"node":">=0.8.28"},"scripts":{"test":"./node_modules/exam/exam.js","cover":"./node_modules/exam/exam-cover.js","report":"open coverage/lcov-report/index.html","bench":"./node_modules/exam/exam.js test/bench"},"repository":{"type":"git","url":"git+https://github.com/lighterio/lighter-run.git"},"bugs":{"url":"https://github.com/lighterio/lighter-run/issues"},"homepage":"https://github.com/lighterio/lighter-run","author":{"name":"Sam Eubank","email":"sameubank@gmail.com"},"contributors":[{"name":"Sam Eubank","email":"sameubank@gmail.com"}],"license":"MIT","gitHead":"db9226ccb839dbdb0be24afd4fd0c0e2357bfb4a","_id":"lighter-run@1.2.1","_shasum":"ba1cb5cbb264c9411fbc0f4fc122962a30a50767","_from":".","_npmVersion":"3.10.10","_nodeVersion":"6.9.5","_npmUser":{"name":"zerious","email":"sameubank@gmail.com"},"dist":{"shasum":"ba1cb5cbb264c9411fbc0f4fc122962a30a50767","tarball":"https://registry.npmjs.org/lighter-run/-/lighter-run-1.2.1.tgz"},"maintainers":[{"name":"zerious","email":"sameubank@gmail.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages","tmp":"tmp/lighter-run-1.2.1.tgz_1495859291627_0.2789006168022752"},"directories":{}}},"readme":"# lighter-run\n[![Chat](https://badges.gitter.im/chat.svg)](//gitter.im/lighterio/public)\n[![Version](https://img.shields.io/npm/v/lighter-run.svg)](//www.npmjs.com/package/lighter-run)\n[![Downloads](https://img.shields.io/npm/dm/lighter-run.svg)](//www.npmjs.com/package/lighter-run)\n[![Build](https://img.shields.io/travis/lighterio/lighter-run.svg)](//travis-ci.org/lighterio/lighter-run)\n[![Coverage](https://img.shields.io/codecov/c/github/lighterio/lighter-run/master.svg)](//codecov.io/gh/lighterio/lighter-run)\n[![Style](https://img.shields.io/badge/code%20style-standard-brightgreen.svg)](//www.npmjs.com/package/standard)\n\nWith `lighter-run`, you can run the node application that's in your current\ndirectory, and it will restart when files change.\n\n## Quick Start\n1. Install globally (using sudo if necessary).\n```bash\nnpm install --global lighter-run\n```\n2. Ensure that the `\"main\"` property in your `package.json` points to your\napplication's entry point file.\n\n3. Run!\n```bash\nlighter-run\n```\n\n## Passing Arguments\n\nTo pass arguments to your entry point, just list them after a double-dash:\n\n```bash\nlighter-run -- ARG1 ARG2 ARG3\n```\n\n## Running Other Files\n\nIf you'd like to run a file other than the one that's specified as `\"main\"` in\nyour `package.json`, then you can specify it as an argument to `lighter-run`:\n\n```bash\nlighter-run index\n```\n\n**NOTE:** You can omit the \".js\" extension just as you would with the `node`\nexecutable.\n\n\n## Configuration\n`lighter-run` uses `lighter-config` for its configuration. Just create a\n`\"config/base.json\"` file in your project, and add some options under a\nproperty called `\"lighterRun\"`. The following is an example of a configuration\nwhich uses the default values:\n```json\n{\n \"lighterRun\": {\n \"minRestartDelay\": 500,\n \"maxRestartDelay\": 5000,\n \"restartDelayBackoff\": 2,\n \"cleanTime\": 2000,\n \"ignore\": [\n \".DS_Store\",\n \".cache\",\n \".git\",\n \".idea\",\n \".project\",\n \"coverage\",\n \"data\",\n \"log\"\n ],\n \"live\": [\n \"public\",\n \"scripts\",\n \"styles\",\n \"views\"\n ],\n \"watchDirs\": null\n }\n}\n```\n\n**minRestartDelay**
\nAmount of time to wait before trying to restart (the first time).\n\n**maxRestartDelay**
\nMaximum amount of time to wait before trying to restart, after failing many times.\n\n**restartDelayBackoff**
\nMultiplier to be applied to the restart delay time after each failure.\n\n**cleanTime**
\nLength of time that a process must be running before it's considered to have started cleanly.\n\n**ignore**
\nFile patterns in which changes should be ignored.\n\n**live**
\nFile patterns that can be live-reloaded instead of restarting the process.\n\n**watchDirs**
\nAbsolute paths for directories that should be watched. (Defaults to the current\nworking directory).\n\n\n## More on lighter-run...\n* [Contributing](//github.com/lighterio/lighter-run/blob/master/CONTRIBUTING.md)\n* [License (ISC)](//github.com/lighterio/lighter-run/blob/master/LICENSE.md)\n* [Change Log](//github.com/lighterio/lighter-run/blob/master/CHANGELOG.md)\n* [Roadmap](//github.com/lighterio/lighter-run/blob/master/ROADMAP.md)\n","maintainers":[{"name":"zerious","email":"sameubank@gmail.com"}],"time":{"modified":"2017-05-27T04:28:11.757Z","created":"2016-10-26T03:32:39.100Z","1.0.0":"2016-10-26T03:32:39.100Z","1.1.0":"2017-01-06T07:25:11.777Z","1.2.0":"2017-03-07T04:24:06.740Z","1.2.1":"2017-05-27T04:28:11.757Z"},"homepage":"https://github.com/lighterio/lighter-run","keywords":["lighter","app","application","run","runner","spawn","watch","watcher","reload","refresh","utility","fast","lightweight"],"repository":{"type":"git","url":"git+https://github.com/lighterio/lighter-run.git"},"contributors":[{"name":"Sam Eubank","email":"sameubank@gmail.com"}],"author":{"name":"Sam Eubank","email":"sameubank@gmail.com"},"bugs":{"url":"https://github.com/lighterio/lighter-run/issues"},"license":"MIT","readmeFilename":"README.md","_attachments":{}} -------------------------------------------------------------------------------- /src/__specs__/fixtures/mamacro.json: -------------------------------------------------------------------------------- 1 | {"_id":"mamacro","_rev":"3-1379a6dec7d3142018ddee5558f1b71b","name":"mamacro","dist-tags":{"latest":"0.0.7"},"versions":{"0.0.1":{"name":"mamacro","version":"0.0.1","description":"","main":"index.js","scripts":{"test":"echo \"Error: no test specified\" && exit 1"},"author":{"name":"Sven Sauleau"},"license":"MIT","_id":"mamacro@0.0.1","_npmVersion":"5.4.2","_nodeVersion":"6.11.4","_npmUser":{"name":"xtuc","email":"contact@xtuc.fr"},"dist":{"integrity":"sha512-B1Mn8Ac9HvqNwztIEShlGlXpy1/lTvxCOsQVpUr2bQjj89WNkEiob+ttK7acHbP1FiHlpfHY3jm14NrU4u4/EQ==","shasum":"4b0b54e4fc30ffff52a97eb3b4a66a01b4dd18b0","tarball":"https://registry.npmjs.org/mamacro/-/mamacro-0.0.1.tgz","fileCount":2,"unpackedSize":324,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.4\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJbBS6sCRA9TVsSAnZWagAA7EEP/3jV13RBpT3xs4iXjgZ5\n5uFmQsIse89NmgqtzI7/1FgmqBmMLpU3TiywEnddVNdhUhP541xdxdSMBk6V\nsFjrLnnutKP5u+Kkdg++/mA6mmiaut6B1lfbtsenje2bOpHcAyMVr1GGnWhM\nKcxIvNvAMmvVO30Anj5YmJgeidq8gvHTH27Qlucea+9/8hCCdU9jYaUv4zjV\n2R0ZA+Ew4QfGXHN8UG63sAv+clantmZE+fMT0fSTyW7zWoSzSUQ9iE29WFvt\naO1OhxOlRHzzEGjGfnlfD8NFZzRUjO8zmnRQfW8k9foDIjNhoE1E3ltGRwck\nIxs0DzvnHjTvEtbYVNJ56ZZHBheFOz0nM96vblb9nAD3B6O69C9SVtq/SHc2\nyTucmCU+QCHTAF7lq2TE0xAPSlVs2kSgXU4vY9l1d6/G870yGtg4gcwEuf3p\nmuHchK1P07GvW8KhX+2beqzqgm+SqHVowS6YGQQuHtGQOkZKn7M+24LKlUXB\nLakCZorVF29g8kn0votnHN9EtLvDFZALvjB6mefYyi9U5Y/PWrweD1dY8JrT\n7eLTtiqKk8lRPbDFctjZyr17cTXNzFFlkbCvxnmAmxs55OE6iewASoXfBcV2\ncZa5r6fnDgFhMVBjtlHSfKAeRzreQcjmu+AUrQP8UPuPRV7QvfdvRrx7Sfmg\n8kH2\r\n=X4Sq\r\n-----END PGP SIGNATURE-----\r\n"},"maintainers":[{"name":"xtuc","email":"contact@xtuc.fr"}],"directories":{},"_npmOperationalInternal":{"host":"s3://npm-registry-packages","tmp":"tmp/mamacro_0.0.1_1527066283980_0.10923689989592344"},"_hasShrinkwrap":false},"0.0.2":{"name":"mamacro","version":"0.0.2","description":"","main":"index.js","scripts":{"test":"echo \"Error: no test specified\" && exit 1"},"author":{"name":"Sven Sauleau"},"license":"MIT","_id":"mamacro@0.0.2","_npmVersion":"5.4.2","_nodeVersion":"6.11.4","_npmUser":{"name":"xtuc","email":"contact@xtuc.fr"},"dist":{"integrity":"sha512-prky5FWx51AFEdn4SkN4i7QYq7ym6H+prYRte6bXBNNvf4f7fBWoioT0o+SaSbEMXqZviinaR6pevGrBLisZDQ==","shasum":"2abe649f9236fcb75045a110f8bd0287d6d3253d","tarball":"https://registry.npmjs.org/mamacro/-/mamacro-0.0.2.tgz","fileCount":2,"unpackedSize":340,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.4\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJbBTAICRA9TVsSAnZWagAAWMgP/RQ4XQQ+hrnFfC2EPwDu\nsSehOiEe0z6siH41eQyw6cPWSV8vzlKsnGCB4mxjBJVI1pM2cvW7x1VK0dj3\nZfvtfNvGq2WNUN5temH6cVBSxq+dpgnRpr9JUb4kgHm0IO4Kon7IsKWqFc+0\nHnlVxpc0BR0pbnZdCRwjN5EQzMEYnqrtLhUmYxgSIpQSCpGTHLFurIili8nw\nCBC/PeEcZF7WTns+Fz/hmguEBuqAObakk6lqrJkMe1Cf8hQHj4vSVOJdXS0T\n0hDgqTRw+UcrnAkrCcUN9U/jj8Q74kF+8PxLPAHPyx+N3zmnk6jm4YZrRs5u\n4xhG+4hsT1a7VQpjbpQdz51kkR+7bMAfqFNYGqTlp0xhirXCFwHq2wPTmN4G\nHPg666SQGIKJhqZalFiz1ov2CBY93XceufQqhBNxsi5Bl0RkoHrCWYsnhQUo\njT9Uvw7Uez+VqcAOtYdQWx6AuHW4Eey6cmFFKhbPCBGYeoGdDRmHy1prIARB\nztu415kmpWwFpxlusyUZKqgWTemVPNrZ2k0qIsNCQN9ibqV+Wo7r70NKk3Hr\naZvhoO2rbqwqgKu+jsFyqfsYdMaefUKnaNFbhuzPkOQHOoEwEeQUFJrwfaEI\nMfJaEEfr2QvE5rDhM1nJmFvH773EPikNEyfOBTSdt0n90VrayiqfO0nx69Ab\nnRoD\r\n=GL3v\r\n-----END PGP SIGNATURE-----\r\n"},"maintainers":[{"name":"xtuc","email":"contact@xtuc.fr"}],"directories":{},"_npmOperationalInternal":{"host":"s3://npm-registry-packages","tmp":"tmp/mamacro_0.0.2_1527066632103_0.9659196721529155"},"_hasShrinkwrap":false},"0.0.3":{"name":"mamacro","version":"0.0.3","description":"","main":"index.js","scripts":{"test":"echo \"Error: no test specified\" && exit 1"},"author":{"name":"Sven Sauleau"},"license":"MIT","_id":"mamacro@0.0.3","_npmVersion":"5.4.2","_nodeVersion":"6.11.4","_npmUser":{"name":"xtuc","email":"contact@xtuc.fr"},"dist":{"integrity":"sha512-qMEwh+UujcQ+kbz3T6V+wAmO2U8veoq2w+3wY8MquqwVA3jChfwY+Tk52GZKDfACEPjuZ7r2oJLejwpt8jtwTA==","shasum":"ad2c9576197c9f1abf308d0787865bd975a3f3e4","tarball":"https://registry.npmjs.org/mamacro/-/mamacro-0.0.3.tgz","fileCount":2,"unpackedSize":369,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.4\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJbBTWVCRA9TVsSAnZWagAAzaMP/iY/tyGf3Els8/qVgN82\n7bvwvIwB9RacBZRL87AT/rxlznTdEi/WwAL0UXHpLK2hizZxpxGCL6r6+3Ls\nUXh+k2ylXCPTg6OAayCue8OPu0USugz00Mz14lWvOqM17h+dx+W5d+MlnDTq\nlKmNLkzMqpbVT2+7f+4LLq7OdyIyS8dVNt60EhoMThz+UIF7xEHJCHvUpjzY\nFF2uEjT7LYORX9Stf1hAv4tp3at0KnG6FKxknviza1xCcEroB26m6RExa7CB\nREeByWBo6i8b6RzPZi0VL9ylFgtOIld43gGA3x2wytAbMmdadbRWdePqd1SH\nOaAJB1hqHxwH4RryxtcITwK1lZn+lb7R5sHv85CatVftvp4/qtr6auqXnh5s\nvRwEr0fkNowxHbIDvmTafVCr3i5g7JlhOp9wDDrIXiV0rSJIt8+Uh8wVK1MC\nb3zxGrLrBp1tufQR2LIeNyHxeQqudOOOt7Ll+LA88kIjM0EShcKUe7IC1AHK\nIlabzi58CLp/mgV5XNFb/8/JUdsAPtNocc1/pSsCSRpss1sTZzqxmgzdSF2c\nV9AcbBUdjuuumFHBYzPEb2WZpaGQ1MfDI1mrIBlb/VQMeWJ/DGmR6QVxSK7/\nH7lXxwOQ6Z1XW+E3a7vVYESkala1hbUC4oc4zA5PB/Kl/PSE9QRHoYpFd0uL\ntbRe\r\n=s65C\r\n-----END PGP SIGNATURE-----\r\n"},"maintainers":[{"name":"xtuc","email":"contact@xtuc.fr"}],"directories":{},"_npmOperationalInternal":{"host":"s3://npm-registry-packages","tmp":"tmp/mamacro_0.0.3_1527068052861_0.5296568298276598"},"_hasShrinkwrap":false},"0.0.7":{"name":"mamacro","version":"0.0.7","description":"","main":"index.js","scripts":{"test":"echo \"Error: no test specified\" && exit 1"},"author":{"name":"Sven Sauleau"},"license":"MIT","_id":"mamacro@0.0.7","_npmVersion":"6.4.1","_nodeVersion":"10.11.0","_npmUser":{"name":"xtuc","email":"contact@xtuc.fr"},"dist":{"integrity":"sha512-U3Mgo/yOu5KO/fI4a3NC5Dh/hnELsjUpMfjGQDgQfRhEvqQT+UOXoPtkmQrIBwr0HAu6P6GKdeKsKw70cLdcGw==","shasum":"d734b7b9d124a3cb3ce98bfc5f34244285aefb16","tarball":"https://registry.npmjs.org/mamacro/-/mamacro-0.0.7.tgz","fileCount":3,"unpackedSize":1442,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.4\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJcUE8uCRA9TVsSAnZWagAAaZEP/126YdsVyzmF8AxeJtBo\nZywIBsZzFTDvZTl08gt5c1ce8qv+uKcoamxwWiiUuWjAw62gyLxX5Zdn98bM\nWgnjUggMka4hkZLrPwF3BltiADXahevm2mQ8lzlPulmOpg6N40LjtaBozncN\n/ILsgmvrT8yyDIA1nNKUycUhSjdZEfwmDyJOIv4mYk+X55HvR8G2N5BbnR7R\nlhwrcQ5SA2SvVcKNPqoc3zZxu+avQ87hrTUqQDZGBLKKN1bsnEivh3TCfQqU\nbhONGqdkthEywW2bfGndi1gsM3l9ddWVlrhITPI4pLH+8/uWrp5zdgXo/yda\nEVQbwSCpwMNrVblTN943HuhMnIF6DKqo7rKZ5ttXtdeOW5hALVSbc0FUUESy\nzOww3qk8P+lsF0CbA6Ed30/LkKzgZefyl4MDJtpoflrG9rddJFeTloKISDZJ\n7u7BJPfK7I4bIFVgG98zkd/FZe30lXCqUe14bzm7xbAB0exg6S1LkzbBGGeU\n5BXVN6Sl+Fu/9xIkJWcNwKTQexBpB1Yt0FX4BBnJI5DLn9aTB3AAGBlLWaf2\namubrx6Bv3oe1nH8vuME8z7e+J2JCYIYsXW+GPqwWC4ljhiOKJ9Ux0vetPQA\nUvHskupL9YyS5DD6gh7NoJAenyuaDdu4vctTYBbCinTBc8bZak9gwdYpc1Sr\n1Wwc\r\n=6Lxh\r\n-----END PGP SIGNATURE-----\r\n"},"maintainers":[{"name":"xtuc","email":"contact@xtuc.fr"}],"directories":{},"_npmOperationalInternal":{"host":"s3://npm-registry-packages","tmp":"tmp/mamacro_0.0.7_1548767021991_0.19328100895865585"},"_hasShrinkwrap":false}},"time":{"created":"2018-05-23T09:04:43.980Z","0.0.1":"2018-05-23T09:04:44.039Z","modified":"2019-01-29T13:03:45.969Z","0.0.2":"2018-05-23T09:10:32.216Z","0.0.3":"2018-05-23T09:34:12.946Z","0.0.7":"2019-01-29T13:03:42.066Z"},"maintainers":[{"name":"xtuc","email":"contact@xtuc.fr"}],"author":{"name":"Sven Sauleau"},"license":"MIT","readme":"ERROR: No README data found!","readmeFilename":""} -------------------------------------------------------------------------------- /src/__specs__/fixtures/os-homedir-1.0.2.tgz.json: -------------------------------------------------------------------------------- 1 | {"fileCount":4,"unpackedSize":3152,"packageJson":{"name":"os-homedir","version":"1.0.2","description":"Node.js 4 `os.homedir()` ponyfill","license":"MIT","repository":"sindresorhus/os-homedir","author":{"name":"Sindre Sorhus","email":"sindresorhus@gmail.com","url":"sindresorhus.com"},"engines":{"node":">=0.10.0"},"scripts":{"test":"xo && ava"},"files":["index.js"],"keywords":["builtin","core","ponyfill","polyfill","shim","os","homedir","home","dir","directory","folder","user","path"],"devDependencies":{"ava":"*","path-exists":"^2.0.0","xo":"^0.16.0"}}} -------------------------------------------------------------------------------- /src/__specs__/fixtures/os-homedir.json: -------------------------------------------------------------------------------- 1 | {"_id":"os-homedir","_rev":"15-19a80317822c2a105289f267d2151336","name":"os-homedir","description":"Node.js 4 `os.homedir()` ponyfill","dist-tags":{"latest":"2.0.0"},"versions":{"0.1.0":{"name":"os-homedir","version":"0.1.0","description":"Node.js os.homedir() ponyfill","license":"MIT","repository":{"type":"git","url":"https://github.com/sindresorhus/os-homedir"},"author":{"name":"Sindre Sorhus","email":"sindresorhus@gmail.com","url":"sindresorhus.com"},"engines":{"node":">=0.10.0"},"scripts":{"test":"node test.js"},"files":["index.js"],"keywords":["built-in","core","ponyfill","polyfill","shim","os","homedir","home","dir","directory","folder","user","path"],"devDependencies":{"ava":"0.0.4","path-exists":"^1.0.0"},"gitHead":"dd2c434e1f540dd208cab20824620ba93eca94a0","bugs":{"url":"https://github.com/sindresorhus/os-homedir/issues"},"homepage":"https://github.com/sindresorhus/os-homedir","_id":"os-homedir@0.1.0","_shasum":"da4fa4c5fcf09bc4eceacb38b0117a88ae9f6224","_from":".","_npmVersion":"2.7.4","_nodeVersion":"0.12.2","_npmUser":{"name":"sindresorhus","email":"sindresorhus@gmail.com"},"dist":{"shasum":"da4fa4c5fcf09bc4eceacb38b0117a88ae9f6224","tarball":"https://registry.npmjs.org/os-homedir/-/os-homedir-0.1.0.tgz"},"maintainers":[{"name":"sindresorhus","email":"sindresorhus@gmail.com"}],"directories":{}},"1.0.0":{"name":"os-homedir","version":"1.0.0","description":"io.js 2.3.0 os.homedir() ponyfill","license":"MIT","repository":{"type":"git","url":"https://github.com/sindresorhus/os-homedir"},"author":{"name":"Sindre Sorhus","email":"sindresorhus@gmail.com","url":"sindresorhus.com"},"engines":{"node":">=0.10.0"},"scripts":{"test":"node test.js"},"files":["index.js"],"keywords":["built-in","core","ponyfill","polyfill","shim","os","homedir","home","dir","directory","folder","user","path"],"devDependencies":{"ava":"0.0.4","path-exists":"^1.0.0"},"gitHead":"7e39e2e049de404f06233fa617ecf46fed997a78","bugs":{"url":"https://github.com/sindresorhus/os-homedir/issues"},"homepage":"https://github.com/sindresorhus/os-homedir","_id":"os-homedir@1.0.0","_shasum":"e37078bc61b5869063053897257e39ec1261b702","_from":".","_npmVersion":"2.11.1","_nodeVersion":"2.3.0","_npmUser":{"name":"sindresorhus","email":"sindresorhus@gmail.com"},"dist":{"shasum":"e37078bc61b5869063053897257e39ec1261b702","tarball":"https://registry.npmjs.org/os-homedir/-/os-homedir-1.0.0.tgz"},"maintainers":[{"name":"sindresorhus","email":"sindresorhus@gmail.com"}],"directories":{}},"1.0.1":{"name":"os-homedir","version":"1.0.1","description":"io.js 2.3.0 os.homedir() ponyfill","license":"MIT","repository":{"type":"git","url":"https://github.com/sindresorhus/os-homedir"},"author":{"name":"Sindre Sorhus","email":"sindresorhus@gmail.com","url":"sindresorhus.com"},"engines":{"node":">=0.10.0"},"scripts":{"test":"node test.js"},"files":["index.js"],"keywords":["built-in","core","ponyfill","polyfill","shim","os","homedir","home","dir","directory","folder","user","path"],"devDependencies":{"ava":"0.0.4","path-exists":"^1.0.0"},"gitHead":"13ff83fbd13ebe286a6092286b2c634ab4534c5f","bugs":{"url":"https://github.com/sindresorhus/os-homedir/issues"},"homepage":"https://github.com/sindresorhus/os-homedir","_id":"os-homedir@1.0.1","_shasum":"0d62bdf44b916fd3bbdcf2cab191948fb094f007","_from":".","_npmVersion":"2.11.2","_nodeVersion":"0.12.5","_npmUser":{"name":"sindresorhus","email":"sindresorhus@gmail.com"},"dist":{"shasum":"0d62bdf44b916fd3bbdcf2cab191948fb094f007","tarball":"https://registry.npmjs.org/os-homedir/-/os-homedir-1.0.1.tgz"},"maintainers":[{"name":"sindresorhus","email":"sindresorhus@gmail.com"}],"directories":{}},"1.0.2":{"name":"os-homedir","version":"1.0.2","description":"Node.js 4 `os.homedir()` ponyfill","license":"MIT","repository":{"type":"git","url":"git+https://github.com/sindresorhus/os-homedir.git"},"author":{"name":"Sindre Sorhus","email":"sindresorhus@gmail.com","url":"sindresorhus.com"},"engines":{"node":">=0.10.0"},"scripts":{"test":"xo && ava"},"files":["index.js"],"keywords":["builtin","core","ponyfill","polyfill","shim","os","homedir","home","dir","directory","folder","user","path"],"devDependencies":{"ava":"*","path-exists":"^2.0.0","xo":"^0.16.0"},"gitHead":"b1b0ae70a5965fef7005ff6509a5dd1a78c95e36","bugs":{"url":"https://github.com/sindresorhus/os-homedir/issues"},"homepage":"https://github.com/sindresorhus/os-homedir#readme","_id":"os-homedir@1.0.2","_shasum":"ffbc4988336e0e833de0c168c7ef152121aa7fb3","_from":".","_npmVersion":"3.10.3","_nodeVersion":"6.6.0","_npmUser":{"name":"sindresorhus","email":"sindresorhus@gmail.com"},"dist":{"shasum":"ffbc4988336e0e833de0c168c7ef152121aa7fb3","tarball":"https://registry.npmjs.org/os-homedir/-/os-homedir-1.0.2.tgz"},"maintainers":[{"name":"sindresorhus","email":"sindresorhus@gmail.com"}],"_npmOperationalInternal":{"host":"packages-16-east.internal.npmjs.com","tmp":"tmp/os-homedir-1.0.2.tgz_1475211519628_0.7873868853785098"},"directories":{}},"2.0.0":{"name":"os-homedir","version":"2.0.0","description":"Node.js 4 `os.homedir()` ponyfill","license":"MIT","repository":{"type":"git","url":"git+https://github.com/sindresorhus/os-homedir.git"},"author":{"name":"Sindre Sorhus","email":"sindresorhus@gmail.com","url":"sindresorhus.com"},"engines":{"node":">=0.10.0"},"scripts":{"test":"xo && ava"},"keywords":["builtin","core","ponyfill","polyfill","shim","os","homedir","home","dir","directory","folder","user","path"],"devDependencies":{"ava":"*","path-exists":"^2.0.0","xo":"^0.16.0"},"gitHead":"e7277f6db9df0b8a5b30aad9fb16429dd3c057b1","bugs":{"url":"https://github.com/sindresorhus/os-homedir/issues"},"homepage":"https://github.com/sindresorhus/os-homedir#readme","_id":"os-homedir@2.0.0","_npmVersion":"6.4.1","_nodeVersion":"8.12.0","_npmUser":{"name":"sindresorhus","email":"sindresorhus@gmail.com"},"dist":{"integrity":"sha512-saRNz0DSC5C/I++gFIaJTXoFJMRwiP5zHar5vV3xQ2TkgEw6hDCcU5F272JjUylpiVgBrZNQHnfjkLabTfb92Q==","shasum":"a0c76bb001a8392a503cbd46e7e650b3423a923c","tarball":"https://registry.npmjs.org/os-homedir/-/os-homedir-2.0.0.tgz","fileCount":4,"unpackedSize":3240,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.4\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJb5BuOCRA9TVsSAnZWagAAEB4P/16c8lUgo2W6xp1qLygI\nHAkwzie45zF8JGEfejH2mXv3AmRVwRhqYnN7MVEYdWmz66xv0G/3BsF0DXrk\nSsDL03hea6wFw3UBwvyeJG33LL8H+s6lDJogyEHbCz5ALwYcTvT/kz3gN2AB\n2mnwQ85WaCIP7wkAH6uZ9ykR8/hjMF0Enl5See8XhSLkBy6Z9go2szuvEZ2R\ngO3hqLzzJDrsqk90wP1nky7GvQKB2aTSOqAtA3+N19p/nJTBJuhmgUq4kz6F\n8qyhigpSF8q0N6/7YWYfUqW0Pce/X3Wy7gx8LWW6ZN9er2afZHMHmvgG8pgd\nz4v6Gi+57Nq9fvH9IpOSlD1biJRA1Ud2FqY4QM43iONFcbHRGgJA/F01w/eQ\nlXmWFvJjbKQSKSUrKhU4gD1qDBVDzlN9W/+UZ0fDjey8Bm51FGyq9f847Hy3\nXni9/tK70Z2xLEqwaoiCWUCHr5kJjUDSQHm8x2l0EaC+Zj0yDdqsgQrlgAYW\nflg5VfpuqJeFrpAMnF2a19MJ2tJ8yGhZ3AP22tsc2EswHZRKBYcVyzAPq6zD\nR8lLWkjRpSnmKIvcbQt9AMsVWKKwUetpIuTvsRZVD2Fkph1/61Cax90MWYv+\ncIKL/N53wN326LbMHrZ2cFf44jC2ZpZ01ysS2/eO0Zp3PZljCV6o8xpVvFkz\nMzDl\r\n=Kx5M\r\n-----END PGP SIGNATURE-----\r\n"},"maintainers":[{"name":"sindresorhus","email":"sindresorhus@gmail.com"}],"directories":{},"_npmOperationalInternal":{"host":"s3://npm-registry-packages","tmp":"tmp/os-homedir_2.0.0_1541675917957_0.1817126090071035"},"_hasShrinkwrap":false,"deprecated":"This is not needed anymore. Use `require('os').homedir()` instead."}},"readme":"# Deprecated\n\nThis is not needed anymore. Use `require('os').homedir()` instead.\n\n---\n\n\n# os-homedir [![Build Status](https://travis-ci.org/sindresorhus/os-homedir.svg?branch=master)](https://travis-ci.org/sindresorhus/os-homedir)\n\n> Node.js 4 [`os.homedir()`](https://nodejs.org/api/os.html#os_os_homedir) [ponyfill](https://ponyfill.com)\n\n\n## Install\n\n```\n$ npm install --save os-homedir\n```\n\n\n## Usage\n\n```js\nconst osHomedir = require('os-homedir');\n\nconsole.log(osHomedir());\n//=> '/Users/sindresorhus'\n```\n\n\n## Related\n\n- [user-home](https://github.com/sindresorhus/user-home) - Same as this module but caches the result\n- [home-or-tmp](https://github.com/sindresorhus/home-or-tmp) - Get the user home directory with fallback to the system temp directory\n\n\n## License\n\nMIT © [Sindre Sorhus](https://sindresorhus.com)\n","maintainers":[{"name":"sindresorhus","email":"sindresorhus@gmail.com"}],"time":{"modified":"2019-01-05T01:41:52.823Z","created":"2015-05-11T12:15:42.136Z","0.1.0":"2015-05-11T12:15:42.136Z","1.0.0":"2015-06-13T12:25:40.507Z","1.0.1":"2015-07-20T23:26:36.048Z","1.0.2":"2016-09-30T04:58:41.739Z","2.0.0":"2018-11-08T11:18:38.141Z"},"homepage":"https://github.com/sindresorhus/os-homedir#readme","keywords":["builtin","core","ponyfill","polyfill","shim","os","homedir","home","dir","directory","folder","user","path"],"repository":{"type":"git","url":"git+https://github.com/sindresorhus/os-homedir.git"},"author":{"name":"Sindre Sorhus","email":"sindresorhus@gmail.com","url":"sindresorhus.com"},"bugs":{"url":"https://github.com/sindresorhus/os-homedir/issues"},"license":"MIT","readmeFilename":"readme.md","users":{"demoive":true,"gochomugo":true,"akiva":true,"kvz":true,"rbecheras":true,"kerwyn":true,"malloryerik":true}} -------------------------------------------------------------------------------- /src/__specs__/fixtures/os-tmpdir-1.0.2.tgz.json: -------------------------------------------------------------------------------- 1 | {"fileCount":4,"unpackedSize":3056,"packageJson":{"name":"os-tmpdir","version":"1.0.2","description":"Node.js os.tmpdir() ponyfill","license":"MIT","repository":"sindresorhus/os-tmpdir","author":{"name":"Sindre Sorhus","email":"sindresorhus@gmail.com","url":"sindresorhus.com"},"engines":{"node":">=0.10.0"},"scripts":{"test":"xo && ava"},"files":["index.js"],"keywords":["built-in","core","ponyfill","polyfill","shim","os","tmpdir","tempdir","tmp","temp","dir","directory","env","environment"],"devDependencies":{"ava":"*","xo":"^0.16.0"}}} -------------------------------------------------------------------------------- /src/__specs__/fixtures/os-tmpdir.json: -------------------------------------------------------------------------------- 1 | {"_id":"os-tmpdir","_rev":"6-c4ded03eb6d8bec1f3f0d2e3d52206b7","name":"os-tmpdir","description":"Node.js os.tmpdir() ponyfill","dist-tags":{"latest":"2.0.0"},"versions":{"1.0.0":{"name":"os-tmpdir","version":"1.0.0","description":"Node.js os.tmpdir() ponyfill","license":"MIT","repository":{"type":"git","url":"git+https://github.com/sindresorhus/os-tmpdir.git"},"author":{"name":"Sindre Sorhus","email":"sindresorhus@gmail.com","url":"sindresorhus.com"},"engines":{"node":">=0.10.0"},"scripts":{"test":"node test.js"},"files":["index.js"],"keywords":["built-in","core","ponyfill","polyfill","shim","os","tmpdir","tempdir","tmp","temp","dir","directory","env","environment"],"devDependencies":{"ava":"0.0.4"},"gitHead":"0cba129ac9a79ecd7c2f18786a41ab228ac37c6d","bugs":{"url":"https://github.com/sindresorhus/os-tmpdir/issues"},"homepage":"https://github.com/sindresorhus/os-tmpdir#readme","_id":"os-tmpdir@1.0.0","_shasum":"698cf46067e8843fb930eb600cbf80a6c1b92066","_from":".","_npmVersion":"2.9.0","_nodeVersion":"0.12.2","_npmUser":{"name":"sindresorhus","email":"sindresorhus@gmail.com"},"dist":{"shasum":"698cf46067e8843fb930eb600cbf80a6c1b92066","tarball":"https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.0.tgz"},"maintainers":[{"name":"sindresorhus","email":"sindresorhus@gmail.com"}],"directories":{}},"1.0.1":{"name":"os-tmpdir","version":"1.0.1","description":"Node.js os.tmpdir() ponyfill","license":"MIT","repository":{"type":"git","url":"https://github.com/sindresorhus/os-tmpdir"},"author":{"name":"Sindre Sorhus","email":"sindresorhus@gmail.com","url":"sindresorhus.com"},"engines":{"node":">=0.10.0"},"scripts":{"test":"node test.js"},"files":["index.js"],"keywords":["built-in","core","ponyfill","polyfill","shim","os","tmpdir","tempdir","tmp","temp","dir","directory","env","environment"],"devDependencies":{"ava":"0.0.4"},"gitHead":"5c5d355f81378980db629d60128ad03e02b1c1e5","bugs":{"url":"https://github.com/sindresorhus/os-tmpdir/issues"},"homepage":"https://github.com/sindresorhus/os-tmpdir","_id":"os-tmpdir@1.0.1","_shasum":"e9b423a1edaf479882562e92ed71d7743a071b6e","_from":".","_npmVersion":"2.9.1","_nodeVersion":"0.12.3","_npmUser":{"name":"sindresorhus","email":"sindresorhus@gmail.com"},"dist":{"shasum":"e9b423a1edaf479882562e92ed71d7743a071b6e","tarball":"https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.1.tgz"},"maintainers":[{"name":"sindresorhus","email":"sindresorhus@gmail.com"}],"directories":{}},"1.0.2":{"name":"os-tmpdir","version":"1.0.2","description":"Node.js os.tmpdir() ponyfill","license":"MIT","repository":{"type":"git","url":"git+https://github.com/sindresorhus/os-tmpdir.git"},"author":{"name":"Sindre Sorhus","email":"sindresorhus@gmail.com","url":"sindresorhus.com"},"engines":{"node":">=0.10.0"},"scripts":{"test":"xo && ava"},"files":["index.js"],"keywords":["built-in","core","ponyfill","polyfill","shim","os","tmpdir","tempdir","tmp","temp","dir","directory","env","environment"],"devDependencies":{"ava":"*","xo":"^0.16.0"},"gitHead":"1abf9cf5611b4be7377060ea67054b45cbf6813c","bugs":{"url":"https://github.com/sindresorhus/os-tmpdir/issues"},"homepage":"https://github.com/sindresorhus/os-tmpdir#readme","_id":"os-tmpdir@1.0.2","_shasum":"bbe67406c79aa85c5cfec766fe5734555dfa1274","_from":".","_npmVersion":"3.10.3","_nodeVersion":"6.6.0","_npmUser":{"name":"sindresorhus","email":"sindresorhus@gmail.com"},"dist":{"shasum":"bbe67406c79aa85c5cfec766fe5734555dfa1274","tarball":"https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz"},"maintainers":[{"name":"sindresorhus","email":"sindresorhus@gmail.com"}],"_npmOperationalInternal":{"host":"packages-12-west.internal.npmjs.com","tmp":"tmp/os-tmpdir-1.0.2.tgz_1475211274587_0.14931037812493742"},"directories":{}},"2.0.0":{"name":"os-tmpdir","version":"2.0.0","description":"Node.js os.tmpdir() ponyfill","license":"MIT","repository":{"type":"git","url":"git+https://github.com/sindresorhus/os-tmpdir.git"},"author":{"name":"Sindre Sorhus","email":"sindresorhus@gmail.com","url":"sindresorhus.com"},"engines":{"node":">=0.10.0"},"scripts":{"test":"xo && ava"},"keywords":["built-in","core","ponyfill","polyfill","shim","os","tmpdir","tempdir","tmp","temp","dir","directory","env","environment"],"devDependencies":{"ava":"*","xo":"^0.16.0"},"gitHead":"8e6b12fd39910ca62c642b4f2ba458defb91dbfa","bugs":{"url":"https://github.com/sindresorhus/os-tmpdir/issues"},"homepage":"https://github.com/sindresorhus/os-tmpdir#readme","_id":"os-tmpdir@2.0.0","_npmVersion":"6.4.1","_nodeVersion":"8.12.0","_npmUser":{"name":"sindresorhus","email":"sindresorhus@gmail.com"},"dist":{"integrity":"sha512-wZojVQP1fR3zW/Z9YLGJz4etXiu5d2tm4D6R4w2FTx1BAx9F1T+tEBb4+0deWQoskF1xe9zgBr/9mYwE+KY3xw==","shasum":"610a70c5c2a986d5fef3830c429a837c4c4b2968","tarball":"https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-2.0.0.tgz","fileCount":4,"unpackedSize":3160,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.4\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJb5BqICRA9TVsSAnZWagAA/IoP/iC6GgS1jg1aI3RgEshH\nH/c8v2Qrdv3fXkaSX92c24115q5XoaSGI2hYynW/4g9cORUyjSGiKq8nSnMX\n/w4Oth9pHj8BgLVLqNUgythS7d1tMd4dveFBOgW2J/Hsvksd0uxAq7wKF+i7\nLYbjvjGklE4s7GOKLYm+C8OPuSf4kQJEn60qphmYvV63KtMesqpBWSQKjhRq\nV0Upu06/sEZQaYP4zSInn2dAnESEfuYg/3/kR1Aqq0Im9JAMJqy/6FpHFVP6\nUb8aouz+D9xezjvd1JF36FBiQFQZrgWtd8dp+SDMglECjBaG4QwpZDN8HPm2\nfTBKBh/JOjejc6/ILtFwsrIAYY5AskKYF7YX3RR3DDTIRU9x/jGbu7kvRyt5\nPgKxu1coLCan9D7+rNuw/mxevP8XmBd3N6pGdcWy30RIOEfd6brr7UTyyH61\ndt9ayOLx9RBcc8yeekGey6d7Ktmvu66cxed5OVK1kOZNvWm4X+dQUP4osdM/\n6KgnuVOlY5tns1NczriuJQUy+XzErrQco2cIdkNvKli0DRhUXitCvZakOl7Q\nJ9kQWcgImZZ9GPLSBNuoecWE4biK4yNLbNNOb9Cq5yaa2VDBkvsO1NZuLL6Q\n/PdAi1l0a7jEfIvrRw88ElyaY7UyOmfjG4k5kde334ys9C81eCDGKaRadsOm\nYS+2\r\n=3NL3\r\n-----END PGP SIGNATURE-----\r\n"},"maintainers":[{"name":"sindresorhus","email":"sindresorhus@gmail.com"}],"directories":{},"_npmOperationalInternal":{"host":"s3://npm-registry-packages","tmp":"tmp/os-tmpdir_2.0.0_1541675655412_0.8308333488963193"},"_hasShrinkwrap":false,"deprecated":"This is not needed anymore. `require('os').tmpdir()` in Node.js 4 and up is good."}},"readme":"**Deprecated:** This is not needed anymore. `require('os').tmpdir()` in Node.js 4 and up is good.\n\n---\n\n# os-tmpdir [![Build Status](https://travis-ci.org/sindresorhus/os-tmpdir.svg?branch=master)](https://travis-ci.org/sindresorhus/os-tmpdir)\n\n> Node.js [`os.tmpdir()`](https://nodejs.org/api/os.html#os_os_tmpdir) [ponyfill](https://ponyfill.com)\n\nUse this instead of `require('os').tmpdir()` to get a consistent behavior on different Node.js versions (even 0.8).\n\n\n## Install\n\n```\n$ npm install --save os-tmpdir\n```\n\n\n## Usage\n\n```js\nconst osTmpdir = require('os-tmpdir');\n\nosTmpdir();\n//=> '/var/folders/m3/5574nnhn0yj488ccryqr7tc80000gn/T'\n```\n\n\n## API\n\nSee the [`os.tmpdir()` docs](https://nodejs.org/api/os.html#os_os_tmpdir).\n\n\n## License\n\nMIT © [Sindre Sorhus](https://sindresorhus.com)\n","maintainers":[{"name":"sindresorhus","email":"sindresorhus@gmail.com"}],"time":{"modified":"2019-01-05T01:42:02.561Z","created":"2015-05-07T19:12:57.265Z","1.0.0":"2015-05-07T19:12:57.265Z","1.0.1":"2015-05-16T00:01:21.701Z","1.0.2":"2016-09-30T04:54:34.812Z","2.0.0":"2018-11-08T11:14:15.596Z"},"homepage":"https://github.com/sindresorhus/os-tmpdir#readme","keywords":["built-in","core","ponyfill","polyfill","shim","os","tmpdir","tempdir","tmp","temp","dir","directory","env","environment"],"repository":{"type":"git","url":"git+https://github.com/sindresorhus/os-tmpdir.git"},"author":{"name":"Sindre Sorhus","email":"sindresorhus@gmail.com","url":"sindresorhus.com"},"bugs":{"url":"https://github.com/sindresorhus/os-tmpdir/issues"},"license":"MIT","readmeFilename":"readme.md","users":{"jacopkane":true}} -------------------------------------------------------------------------------- /src/__specs__/fixtures/osenv.json: -------------------------------------------------------------------------------- 1 | {"_id":"osenv","_rev":"33-22304e717cf5100d225db7531db1805e","name":"osenv","description":"Look up environment settings specific to different operating systems","dist-tags":{"latest":"0.1.5"},"versions":{"0.0.1":{"name":"osenv","version":"0.0.1","main":"osenv.js","directories":{"test":"test"},"dependencies":{},"devDependencies":{"tap":"~0.2.5"},"scripts":{"test":"tap test/*.js"},"repository":{"type":"git","url":"git://github.com/isaacs/osenv"},"keywords":["environment","variable","home","tmpdir","path","prompt","ps1"],"author":{"name":"Isaac Z. Schlueter","email":"i@izs.me","url":"http://blog.izs.me/"},"license":"BSD","description":"Look up environment settings specific to different operating systems","_id":"osenv@0.0.1","dist":{"shasum":"cf33b1fb1b34778cc88d440890e95027b841fad5","tarball":"https://registry.npmjs.org/osenv/-/osenv-0.0.1.tgz"},"maintainers":[{"name":"isaacs","email":"i@izs.me"}]},"0.0.2":{"name":"osenv","version":"0.0.2","main":"osenv.js","directories":{"test":"test"},"dependencies":{},"devDependencies":{"tap":"~0.2.5"},"scripts":{"test":"tap test/*.js"},"repository":{"type":"git","url":"git://github.com/isaacs/osenv"},"keywords":["environment","variable","home","tmpdir","path","prompt","ps1"],"author":{"name":"Isaac Z. Schlueter","email":"i@izs.me","url":"http://blog.izs.me/"},"license":"BSD","description":"Look up environment settings specific to different operating systems","_id":"osenv@0.0.2","dist":{"shasum":"1af1612efd71e1e3d1a72e17a33b869f95f58abe","tarball":"https://registry.npmjs.org/osenv/-/osenv-0.0.2.tgz"},"maintainers":[{"name":"isaacs","email":"i@izs.me"}]},"0.0.3":{"name":"osenv","version":"0.0.3","main":"osenv.js","directories":{"test":"test"},"dependencies":{},"devDependencies":{"tap":"~0.2.5"},"scripts":{"test":"tap test/*.js"},"repository":{"type":"git","url":"git://github.com/isaacs/osenv"},"keywords":["environment","variable","home","tmpdir","path","prompt","ps1"],"author":{"name":"Isaac Z. Schlueter","email":"i@izs.me","url":"http://blog.izs.me/"},"license":"BSD","description":"Look up environment settings specific to different operating systems","_id":"osenv@0.0.3","dist":{"shasum":"cd6ad8ddb290915ad9e22765576025d411f29cb6","tarball":"https://registry.npmjs.org/osenv/-/osenv-0.0.3.tgz"},"maintainers":[{"name":"isaacs","email":"i@izs.me"}]},"0.1.0":{"name":"osenv","version":"0.1.0","main":"osenv.js","directories":{"test":"test"},"dependencies":{},"devDependencies":{"tap":"~0.4.9"},"scripts":{"test":"tap test/*.js"},"repository":{"type":"git","url":"git://github.com/isaacs/osenv"},"keywords":["environment","variable","home","tmpdir","path","prompt","ps1"],"author":{"name":"Isaac Z. Schlueter","email":"i@izs.me","url":"http://blog.izs.me/"},"license":"BSD","description":"Look up environment settings specific to different operating systems","bugs":{"url":"https://github.com/isaacs/osenv/issues"},"homepage":"https://github.com/isaacs/osenv","_id":"osenv@0.1.0","_shasum":"61668121eec584955030b9f470b1d2309504bfcb","_from":".","_npmVersion":"1.4.9","_npmUser":{"name":"robertkowalski","email":"rok@kowalski.gd"},"maintainers":[{"name":"isaacs","email":"i@izs.me"},{"name":"robertkowalski","email":"rok@kowalski.gd"}],"dist":{"shasum":"61668121eec584955030b9f470b1d2309504bfcb","tarball":"https://registry.npmjs.org/osenv/-/osenv-0.1.0.tgz"}},"0.1.1":{"name":"osenv","version":"0.1.1","main":"osenv.js","directories":{"test":"test"},"dependencies":{},"devDependencies":{"tap":"~0.4.9"},"scripts":{"test":"tap test/*.js"},"repository":{"type":"git","url":"git+https://github.com/npm/osenv.git"},"keywords":["environment","variable","home","tmpdir","path","prompt","ps1"],"author":{"name":"Isaac Z. Schlueter","email":"i@izs.me","url":"http://blog.izs.me/"},"license":"ISC","description":"Look up environment settings specific to different operating systems","gitHead":"769ada6737026254372e3013b702c450a9b781e9","bugs":{"url":"https://github.com/npm/osenv/issues"},"homepage":"https://github.com/npm/osenv#readme","_id":"osenv@0.1.1","_shasum":"ddc7c4bb86c64a3022e95f030ee028e9a5996c07","_from":".","_npmVersion":"2.10.0","_nodeVersion":"2.0.1","_npmUser":{"name":"isaacs","email":"isaacs@npmjs.com"},"dist":{"shasum":"ddc7c4bb86c64a3022e95f030ee028e9a5996c07","tarball":"https://registry.npmjs.org/osenv/-/osenv-0.1.1.tgz"},"maintainers":[{"name":"isaacs","email":"i@izs.me"},{"name":"robertkowalski","email":"rok@kowalski.gd"},{"name":"othiym23","email":"ogd@aoaioxxysz.net"},{"name":"iarna","email":"me@re-becca.org"}]},"0.1.2":{"name":"osenv","version":"0.1.2","main":"osenv.js","directories":{"test":"test"},"dependencies":{"os-tmpdir":"^1.0.0"},"devDependencies":{"tap":"^1.2.0"},"scripts":{"test":"tap test/*.js"},"repository":{"type":"git","url":"git+https://github.com/npm/osenv.git"},"keywords":["environment","variable","home","tmpdir","path","prompt","ps1"],"author":{"name":"Isaac Z. Schlueter","email":"i@izs.me","url":"http://blog.izs.me/"},"license":"ISC","description":"Look up environment settings specific to different operating systems","gitHead":"88a154d6d8ad39fefb9af2fe1b306cd12fb6d6d0","bugs":{"url":"https://github.com/npm/osenv/issues"},"homepage":"https://github.com/npm/osenv#readme","_id":"osenv@0.1.2","_shasum":"f4d23ebeceaef078600fb78c0ea58fac5996a02d","_from":".","_npmVersion":"3.0.0-pre.11","_nodeVersion":"2.2.1","_npmUser":{"name":"iarna","email":"me@re-becca.org"},"dist":{"shasum":"f4d23ebeceaef078600fb78c0ea58fac5996a02d","tarball":"https://registry.npmjs.org/osenv/-/osenv-0.1.2.tgz"},"maintainers":[{"name":"isaacs","email":"i@izs.me"},{"name":"robertkowalski","email":"rok@kowalski.gd"},{"name":"othiym23","email":"ogd@aoaioxxysz.net"},{"name":"iarna","email":"me@re-becca.org"}]},"0.1.3":{"name":"osenv","version":"0.1.3","main":"osenv.js","directories":{"test":"test"},"dependencies":{"os-homedir":"^1.0.0","os-tmpdir":"^1.0.0"},"devDependencies":{"tap":"^1.2.0"},"scripts":{"test":"tap test/*.js"},"repository":{"type":"git","url":"git+https://github.com/npm/osenv.git"},"keywords":["environment","variable","home","tmpdir","path","prompt","ps1"],"author":{"name":"Isaac Z. Schlueter","email":"i@izs.me","url":"http://blog.izs.me/"},"license":"ISC","description":"Look up environment settings specific to different operating systems","gitHead":"f746b3405d8f9e28054d11b97e1436f6a15016c4","bugs":{"url":"https://github.com/npm/osenv/issues"},"homepage":"https://github.com/npm/osenv#readme","_id":"osenv@0.1.3","_shasum":"83cf05c6d6458fc4d5ac6362ea325d92f2754217","_from":".","_npmVersion":"3.0.0","_nodeVersion":"2.2.1","_npmUser":{"name":"isaacs","email":"isaacs@npmjs.com"},"dist":{"shasum":"83cf05c6d6458fc4d5ac6362ea325d92f2754217","tarball":"https://registry.npmjs.org/osenv/-/osenv-0.1.3.tgz"},"maintainers":[{"name":"isaacs","email":"i@izs.me"},{"name":"robertkowalski","email":"rok@kowalski.gd"},{"name":"othiym23","email":"ogd@aoaioxxysz.net"},{"name":"iarna","email":"me@re-becca.org"}]},"0.1.4":{"name":"osenv","version":"0.1.4","main":"osenv.js","directories":{"test":"test"},"dependencies":{"os-homedir":"^1.0.0","os-tmpdir":"^1.0.0"},"devDependencies":{"tap":"^8.0.1"},"scripts":{"test":"tap test/*.js"},"repository":{"type":"git","url":"git+https://github.com/npm/osenv.git"},"keywords":["environment","variable","home","tmpdir","path","prompt","ps1"],"author":{"name":"Isaac Z. Schlueter","email":"i@izs.me","url":"http://blog.izs.me/"},"license":"ISC","description":"Look up environment settings specific to different operating systems","gitHead":"ef718f0d20e38d45ec452b7faeefc692d3cd1062","bugs":{"url":"https://github.com/npm/osenv/issues"},"homepage":"https://github.com/npm/osenv#readme","_id":"osenv@0.1.4","_shasum":"42fe6d5953df06c8064be6f176c3d05aaaa34644","_from":".","_npmVersion":"3.10.9","_nodeVersion":"6.5.0","_npmUser":{"name":"isaacs","email":"i@izs.me"},"dist":{"shasum":"42fe6d5953df06c8064be6f176c3d05aaaa34644","tarball":"https://registry.npmjs.org/osenv/-/osenv-0.1.4.tgz"},"maintainers":[{"name":"isaacs","email":"i@izs.me"},{"name":"robertkowalski","email":"rok@kowalski.gd"},{"name":"othiym23","email":"ogd@aoaioxxysz.net"},{"name":"iarna","email":"me@re-becca.org"}],"_npmOperationalInternal":{"host":"packages-18-east.internal.npmjs.com","tmp":"tmp/osenv-0.1.4.tgz_1481655889868_0.3980878754518926"}},"0.1.5":{"name":"osenv","version":"0.1.5","main":"osenv.js","directories":{"test":"test"},"dependencies":{"os-homedir":"^1.0.0","os-tmpdir":"^1.0.0"},"devDependencies":{"tap":"^11.1.0"},"scripts":{"test":"tap test/*.js","preversion":"npm test","postversion":"npm publish","postpublish":"git push origin --all; git push origin --tags"},"repository":{"type":"git","url":"git+https://github.com/npm/osenv.git"},"keywords":["environment","variable","home","tmpdir","path","prompt","ps1"],"author":{"name":"Isaac Z. Schlueter","email":"i@izs.me","url":"http://blog.izs.me/"},"license":"ISC","description":"Look up environment settings specific to different operating systems","files":["osenv.js"],"gitHead":"1c642b8f5ddb1f99671a300a466bf42ffb9f5ea2","bugs":{"url":"https://github.com/npm/osenv/issues"},"homepage":"https://github.com/npm/osenv#readme","_id":"osenv@0.1.5","_npmVersion":"5.6.0-canary.11","_nodeVersion":"8.9.1","_npmUser":{"name":"isaacs","email":"i@izs.me"},"dist":{"integrity":"sha512-0CWcCECdMVc2Rw3U5w9ZjqX6ga6ubk1xDVKxtBQPK7wis/0F2r9T6k4ydGYhecl7YUBxBVxhL5oisPsNxAPe2g==","shasum":"85cdfafaeb28e8677f416e287592b5f3f49ea410","tarball":"https://registry.npmjs.org/osenv/-/osenv-0.1.5.tgz","fileCount":4,"unpackedSize":4889},"maintainers":[{"email":"me@re-becca.org","name":"iarna"},{"email":"i@izs.me","name":"isaacs"},{"email":"ogd@aoaioxxysz.net","name":"othiym23"},{"email":"rok@kowalski.gd","name":"robertkowalski"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages","tmp":"tmp/osenv_0.1.5_1518743366771_0.010286411112286009"},"_hasShrinkwrap":false}},"readme":"# osenv\n\nLook up environment settings specific to different operating systems.\n\n## Usage\n\n```javascript\nvar osenv = require('osenv')\nvar path = osenv.path()\nvar user = osenv.user()\n// etc.\n\n// Some things are not reliably in the env, and have a fallback command:\nvar h = osenv.hostname(function (er, hostname) {\n h = hostname\n})\n// This will still cause it to be memoized, so calling osenv.hostname()\n// is now an immediate operation.\n\n// You can always send a cb, which will get called in the nextTick\n// if it's been memoized, or wait for the fallback data if it wasn't\n// found in the environment.\nosenv.hostname(function (er, hostname) {\n if (er) console.error('error looking up hostname')\n else console.log('this machine calls itself %s', hostname)\n})\n```\n\n## osenv.hostname()\n\nThe machine name. Calls `hostname` if not found.\n\n## osenv.user()\n\nThe currently logged-in user. Calls `whoami` if not found.\n\n## osenv.prompt()\n\nEither PS1 on unix, or PROMPT on Windows.\n\n## osenv.tmpdir()\n\nThe place where temporary files should be created.\n\n## osenv.home()\n\nNo place like it.\n\n## osenv.path()\n\nAn array of the places that the operating system will search for\nexecutables.\n\n## osenv.editor() \n\nReturn the executable name of the editor program. This uses the EDITOR\nand VISUAL environment variables, and falls back to `vi` on Unix, or\n`notepad.exe` on Windows.\n\n## osenv.shell()\n\nThe SHELL on Unix, which Windows calls the ComSpec. Defaults to 'bash'\nor 'cmd'.\n","maintainers":[{"email":"ruyadorno@hotmail.com","name":"ruyadorno"},{"email":"mike@mikecorp.ca","name":"mikemimik"},{"email":"billatnpm@gmail.com","name":"billatnpm"},{"email":"anne@npmjs.com","name":"annekimsey"},{"email":"cghr1990@gmail.com","name":"claudiahdz"},{"email":"darcy@darcyclarke.me","name":"darcyclarke"},{"email":"evilpacket@gmail.com","name":"adam_baldwin"},{"email":"ahmad@ahmadnassri.com","name":"ahmadnassri"},{"email":"i@izs.me","name":"isaacs"}],"time":{"modified":"2019-09-10T19:04:59.297Z","created":"2012-06-18T00:09:12.503Z","0.0.1":"2012-06-18T00:09:13.936Z","0.0.2":"2012-06-18T00:43:30.797Z","0.0.3":"2012-06-18T00:50:55.051Z","0.1.0":"2014-05-26T17:58:12.322Z","0.1.1":"2015-05-20T07:15:31.410Z","0.1.2":"2015-06-12T01:44:07.722Z","0.1.3":"2015-06-29T22:56:01.295Z","0.1.4":"2016-12-13T19:04:51.765Z","0.1.5":"2018-02-16T01:09:26.829Z"},"author":{"name":"Isaac Z. Schlueter","email":"i@izs.me","url":"http://blog.izs.me/"},"repository":{"type":"git","url":"git+https://github.com/npm/osenv.git"},"users":{"wangnan0610":true,"okmogwai":true,"jian263994241":true,"scottfreecode":true,"shanewholloway":true,"zhenguo.zhao":true},"keywords":["environment","variable","home","tmpdir","path","prompt","ps1"],"license":"ISC","readmeFilename":"README.md","homepage":"https://github.com/npm/osenv#readme","bugs":{"url":"https://github.com/npm/osenv/issues"}} -------------------------------------------------------------------------------- /src/__specs__/fixtures/validate-npm-package-name-3.0.0.tgz.json: -------------------------------------------------------------------------------- 1 | {"fileCount":9,"unpackedSize":20998,"packageJson":{"name":"validate-npm-package-name","version":"3.0.0","description":"Give me a string and I'll tell you if it's a valid npm package name","main":"index.js","directories":{"test":"test"},"dependencies":{"builtins":"^1.0.3"},"devDependencies":{"standard":"^8.6.0","tap":"^10.0.0"},"scripts":{"cov:test":"TAP_FLAGS='--cov' npm run test:code","test:code":"tap ${TAP_FLAGS:-'--'} test/*.js","test:style":"standard","test":"npm run test:code && npm run test:style"},"repository":{"type":"git","url":"https://github.com/npm/validate-npm-package-name"},"keywords":["npm","package","names","validation"],"author":"zeke","license":"ISC","bugs":{"url":"https://github.com/npm/validate-npm-package-name/issues"},"homepage":"https://github.com/npm/validate-npm-package-name"}} -------------------------------------------------------------------------------- /src/__specs__/fixtures/validate-npm-package-name.json: -------------------------------------------------------------------------------- 1 | {"_id":"validate-npm-package-name","_rev":"38-843a5c9abac7c17f38a480a8158702f8","name":"validate-npm-package-name","description":"Give me a string and I'll tell you if it's a valid npm package name","dist-tags":{"latest":"3.0.0"},"versions":{"1.0.0":{"name":"validate-npm-package-name","version":"1.0.0","description":"Give me a string and I'll tell you if it's a valid npm package name","main":"index.js","directories":{"test":"test"},"dependencies":{},"devDependencies":{"tap":"^0.4.13"},"scripts":{"test":"tap test/*.js"},"repository":{"type":"git","url":"https://github.com/zeke/validate-npm-package-name"},"keywords":["npm","package","names","validation"],"author":{"name":"zeke"},"license":"ISC","bugs":{"url":"https://github.com/zeke/validate-npm-package-name/issues"},"homepage":"https://github.com/zeke/validate-npm-package-name","gitHead":"3cef9813cf63bdfb287adad51d98b05661814eb0","_id":"validate-npm-package-name@1.0.0","_shasum":"1d9079f1b3462366460376e4d8aa05c1c6b7c407","_from":".","_npmVersion":"2.1.4","_nodeVersion":"0.10.31","_npmUser":{"name":"zeke","email":"zeke@sikelianos.com"},"maintainers":[{"name":"zeke","email":"zeke@sikelianos.com"}],"dist":{"shasum":"1d9079f1b3462366460376e4d8aa05c1c6b7c407","tarball":"https://registry.npmjs.org/validate-npm-package-name/-/validate-npm-package-name-1.0.0.tgz"}},"1.0.1":{"name":"validate-npm-package-name","version":"1.0.1","description":"Give me a string and I'll tell you if it's a valid npm package name","main":"index.js","directories":{"test":"test"},"dependencies":{},"devDependencies":{"tap":"^0.4.13"},"scripts":{"test":"tap test/*.js"},"repository":{"type":"git","url":"https://github.com/npm/validate-npm-package-name"},"keywords":["npm","package","names","validation"],"author":{"name":"zeke"},"license":"ISC","bugs":{"url":"https://github.com/npm/validate-npm-package-name/issues"},"homepage":"https://github.com/npm/validate-npm-package-name","gitHead":"d63552945df873f81666b84ad8f8358ef950700a","_id":"validate-npm-package-name@1.0.1","_shasum":"07cc7694759ea445d3a401c3c7079468d768d137","_from":".","_npmVersion":"2.1.4","_nodeVersion":"0.10.31","_npmUser":{"name":"zeke","email":"zeke@sikelianos.com"},"maintainers":[{"name":"zeke","email":"zeke@sikelianos.com"}],"dist":{"shasum":"07cc7694759ea445d3a401c3c7079468d768d137","tarball":"https://registry.npmjs.org/validate-npm-package-name/-/validate-npm-package-name-1.0.1.tgz"}},"1.1.0":{"name":"validate-npm-package-name","version":"1.1.0","description":"Give me a string and I'll tell you if it's a valid npm package name","main":"index.js","directories":{"test":"test"},"dependencies":{},"devDependencies":{"tap":"^0.4.13"},"scripts":{"test":"tap test/*.js"},"repository":{"type":"git","url":"https://github.com/npm/validate-npm-package-name"},"keywords":["npm","package","names","validation"],"author":{"name":"zeke"},"license":"ISC","bugs":{"url":"https://github.com/npm/validate-npm-package-name/issues"},"homepage":"https://github.com/npm/validate-npm-package-name","gitHead":"14d82e165fda7371d24964ab3a12a2820900118a","_id":"validate-npm-package-name@1.1.0","_shasum":"900d1c1556efce21581a8e09e857b003836badd9","_from":".","_npmVersion":"2.1.4","_nodeVersion":"0.10.31","_npmUser":{"name":"zeke","email":"zeke@sikelianos.com"},"maintainers":[{"name":"zeke","email":"zeke@sikelianos.com"}],"dist":{"shasum":"900d1c1556efce21581a8e09e857b003836badd9","tarball":"https://registry.npmjs.org/validate-npm-package-name/-/validate-npm-package-name-1.1.0.tgz"}},"1.1.1":{"name":"validate-npm-package-name","version":"1.1.1","description":"Give me a string and I'll tell you if it's a valid npm package name","main":"index.js","directories":{"test":"test"},"dependencies":{},"devDependencies":{"tap":"^0.4.13"},"scripts":{"test":"tap test/*.js"},"repository":{"type":"git","url":"https://github.com/npm/validate-npm-package-name"},"keywords":["npm","package","names","validation"],"author":{"name":"zeke"},"license":"ISC","bugs":{"url":"https://github.com/npm/validate-npm-package-name/issues"},"homepage":"https://github.com/npm/validate-npm-package-name","gitHead":"27e7422a24d83513450bfd2d7e3974edbf294862","_id":"validate-npm-package-name@1.1.1","_shasum":"0797dce7495881b2d1e1ddbb52262a7dcac0fc0c","_from":".","_npmVersion":"2.1.4","_nodeVersion":"0.10.31","_npmUser":{"name":"zeke","email":"zeke@sikelianos.com"},"maintainers":[{"name":"zeke","email":"zeke@sikelianos.com"}],"dist":{"shasum":"0797dce7495881b2d1e1ddbb52262a7dcac0fc0c","tarball":"https://registry.npmjs.org/validate-npm-package-name/-/validate-npm-package-name-1.1.1.tgz"}},"1.2.0":{"name":"validate-npm-package-name","version":"1.2.0","description":"Give me a string and I'll tell you if it's a valid npm package name","main":"index.js","directories":{"test":"test"},"dependencies":{},"devDependencies":{"tap":"^0.4.13"},"scripts":{"test":"tap test/*.js"},"repository":{"type":"git","url":"https://github.com/npm/validate-npm-package-name"},"keywords":["npm","package","names","validation"],"author":{"name":"zeke"},"license":"ISC","bugs":{"url":"https://github.com/npm/validate-npm-package-name/issues"},"homepage":"https://github.com/npm/validate-npm-package-name","gitHead":"7bc42600dd83f548c7024cebcdce2631f1daadf5","_id":"validate-npm-package-name@1.2.0","_shasum":"2115748ff89aafce82b60f90a7d5a54fc01bca28","_from":".","_npmVersion":"2.1.4","_nodeVersion":"0.10.31","_npmUser":{"name":"zeke","email":"zeke@sikelianos.com"},"maintainers":[{"name":"zeke","email":"zeke@sikelianos.com"}],"dist":{"shasum":"2115748ff89aafce82b60f90a7d5a54fc01bca28","tarball":"https://registry.npmjs.org/validate-npm-package-name/-/validate-npm-package-name-1.2.0.tgz"}},"2.0.0":{"name":"validate-npm-package-name","version":"2.0.0","description":"Give me a string and I'll tell you if it's a valid npm package name","main":"index.js","directories":{"test":"test"},"dependencies":{"builtins":"0.0.7"},"devDependencies":{"tap":"^0.4.13"},"scripts":{"test":"tap test/*.js"},"repository":{"type":"git","url":"https://github.com/npm/validate-npm-package-name"},"keywords":["npm","package","names","validation"],"author":{"name":"zeke"},"license":"ISC","bugs":{"url":"https://github.com/npm/validate-npm-package-name/issues"},"homepage":"https://github.com/npm/validate-npm-package-name","gitHead":"34c168ed0175a3e20a06c910b06b26cbe81a18a8","_id":"validate-npm-package-name@2.0.0","_shasum":"6dfa0e63971c994d7a49d082bc5847c0d570358d","_from":".","_npmVersion":"2.2.0","_nodeVersion":"0.10.31","_npmUser":{"name":"zeke","email":"zeke@npmjs.com"},"maintainers":[{"name":"zeke","email":"zeke@sikelianos.com"}],"dist":{"shasum":"6dfa0e63971c994d7a49d082bc5847c0d570358d","tarball":"https://registry.npmjs.org/validate-npm-package-name/-/validate-npm-package-name-2.0.0.tgz"}},"2.0.1":{"name":"validate-npm-package-name","version":"2.0.1","description":"Give me a string and I'll tell you if it's a valid npm package name","main":"index.js","directories":{"test":"test"},"dependencies":{"builtins":"0.0.7"},"devDependencies":{"tap":"^0.4.13"},"scripts":{"test":"tap test/*.js"},"repository":{"type":"git","url":"https://github.com/npm/validate-npm-package-name"},"keywords":["npm","package","names","validation"],"author":{"name":"zeke"},"license":"ISC","bugs":{"url":"https://github.com/npm/validate-npm-package-name/issues"},"homepage":"https://github.com/npm/validate-npm-package-name","gitHead":"69f5c629525feeccfc941a6f1b9280ceed8a4b3a","_id":"validate-npm-package-name@2.0.1","_shasum":"ca006761b2b325f107fab172fb0cfcfc5e412c58","_from":".","_npmVersion":"2.2.0","_nodeVersion":"0.10.31","_npmUser":{"name":"zeke","email":"zeke@npmjs.com"},"maintainers":[{"name":"zeke","email":"zeke@sikelianos.com"}],"dist":{"shasum":"ca006761b2b325f107fab172fb0cfcfc5e412c58","tarball":"https://registry.npmjs.org/validate-npm-package-name/-/validate-npm-package-name-2.0.1.tgz"}},"2.1.0":{"name":"validate-npm-package-name","version":"2.1.0","description":"Give me a string and I'll tell you if it's a valid npm package name","main":"index.js","directories":{"test":"test"},"dependencies":{"builtins":"0.0.7"},"devDependencies":{"tap":"^0.4.13"},"scripts":{"test":"tap test/*.js"},"repository":{"type":"git","url":"git+https://github.com/npm/validate-npm-package-name.git"},"keywords":["npm","package","names","validation"],"author":{"name":"zeke"},"license":"ISC","bugs":{"url":"https://github.com/npm/validate-npm-package-name/issues"},"homepage":"https://github.com/npm/validate-npm-package-name","gitHead":"015cffe92eacf7432b22b3b68a37c507d03733c8","_id":"validate-npm-package-name@2.1.0","_shasum":"b7c643ba6cec2922c0f7b3b0f69519cbdc44653a","_from":".","_npmVersion":"2.8.2","_nodeVersion":"1.5.1","_npmUser":{"name":"zeke","email":"zeke@npmjs.com"},"maintainers":[{"name":"zeke","email":"zeke@sikelianos.com"}],"dist":{"shasum":"b7c643ba6cec2922c0f7b3b0f69519cbdc44653a","tarball":"https://registry.npmjs.org/validate-npm-package-name/-/validate-npm-package-name-2.1.0.tgz"}},"2.2.0":{"name":"validate-npm-package-name","version":"2.2.0","description":"Give me a string and I'll tell you if it's a valid npm package name","main":"index.js","directories":{"test":"test"},"dependencies":{"builtins":"0.0.7"},"devDependencies":{"tap":"^0.4.13"},"scripts":{"test":"tap test/*.js"},"repository":{"type":"git","url":"https://github.com/npm/validate-npm-package-name"},"keywords":["npm","package","names","validation"],"author":{"name":"zeke"},"license":"ISC","bugs":{"url":"https://github.com/npm/validate-npm-package-name/issues"},"homepage":"https://github.com/npm/validate-npm-package-name","gitHead":"acef1219c13a0cf4cf6b8706d65f606d82a7d472","_id":"validate-npm-package-name@2.2.0","_shasum":"4cb6ff120bd7afb0b5681406cfaea8df2d763477","_from":".","_npmVersion":"2.7.6","_nodeVersion":"1.6.2","_npmUser":{"name":"bcoe","email":"ben@npmjs.com"},"dist":{"shasum":"4cb6ff120bd7afb0b5681406cfaea8df2d763477","tarball":"https://registry.npmjs.org/validate-npm-package-name/-/validate-npm-package-name-2.2.0.tgz"},"maintainers":[{"name":"zeke","email":"zeke@npmjs.com"},{"name":"bcoe","email":"ben@npmjs.com"}]},"2.2.1":{"name":"validate-npm-package-name","version":"2.2.1","description":"Give me a string and I'll tell you if it's a valid npm package name","main":"index.js","directories":{"test":"test"},"dependencies":{"builtins":"0.0.7"},"devDependencies":{"tap":"^0.4.13"},"scripts":{"test":"tap test/*.js"},"repository":{"type":"git","url":"git+https://github.com/npm/validate-npm-package-name.git"},"keywords":["npm","package","names","validation"],"author":{"name":"zeke"},"license":"ISC","bugs":{"url":"https://github.com/npm/validate-npm-package-name/issues"},"homepage":"https://github.com/npm/validate-npm-package-name","gitHead":"b25e4f4a927bee4130058c6800b0352711faeacb","_id":"validate-npm-package-name@2.2.1","_shasum":"f7c0ddbec401e0308adcc4451aeeb141505060ee","_from":".","_npmVersion":"3.0.0","_nodeVersion":"0.12.5","_npmUser":{"name":"zkat","email":"kat@sykosomatic.org"},"dist":{"shasum":"f7c0ddbec401e0308adcc4451aeeb141505060ee","tarball":"https://registry.npmjs.org/validate-npm-package-name/-/validate-npm-package-name-2.2.1.tgz"},"maintainers":[{"name":"zeke","email":"zeke@sikelianos.com"},{"name":"bcoe","email":"ben@npmjs.com"},{"name":"zkat","email":"kat@sykosomatic.org"}]},"2.2.2":{"name":"validate-npm-package-name","version":"2.2.2","description":"Give me a string and I'll tell you if it's a valid npm package name","main":"index.js","directories":{"test":"test"},"dependencies":{"builtins":"0.0.7"},"devDependencies":{"tap":"^0.4.13"},"scripts":{"test":"tap test/*.js"},"repository":{"type":"git","url":"git+https://github.com/npm/validate-npm-package-name.git"},"keywords":["npm","package","names","validation"],"author":{"name":"zeke"},"license":"ISC","bugs":{"url":"https://github.com/npm/validate-npm-package-name/issues"},"homepage":"https://github.com/npm/validate-npm-package-name","gitHead":"3af92c881549f1b96f05ab6bfb5768bba94ad72d","_id":"validate-npm-package-name@2.2.2","_shasum":"f65695b22f7324442019a3c7fa39a6e7fd299085","_from":".","_npmVersion":"3.0.0","_nodeVersion":"0.12.5","_npmUser":{"name":"zkat","email":"kat@sykosomatic.org"},"dist":{"shasum":"f65695b22f7324442019a3c7fa39a6e7fd299085","tarball":"https://registry.npmjs.org/validate-npm-package-name/-/validate-npm-package-name-2.2.2.tgz"},"maintainers":[{"name":"zeke","email":"zeke@sikelianos.com"},{"name":"bcoe","email":"ben@npmjs.com"},{"name":"zkat","email":"kat@sykosomatic.org"}]},"3.0.0":{"name":"validate-npm-package-name","version":"3.0.0","description":"Give me a string and I'll tell you if it's a valid npm package name","main":"index.js","directories":{"test":"test"},"dependencies":{"builtins":"^1.0.3"},"devDependencies":{"standard":"^8.6.0","tap":"^10.0.0"},"scripts":{"cov:test":"TAP_FLAGS='--cov' npm run test:code","test:code":"tap ${TAP_FLAGS:-'--'} test/*.js","test:style":"standard","test":"npm run test:code && npm run test:style"},"repository":{"type":"git","url":"git+https://github.com/npm/validate-npm-package-name.git"},"keywords":["npm","package","names","validation"],"author":{"name":"zeke"},"license":"ISC","bugs":{"url":"https://github.com/npm/validate-npm-package-name/issues"},"homepage":"https://github.com/npm/validate-npm-package-name","gitHead":"ddf73c79e920b59413485ca2ae50a38552156d62","_id":"validate-npm-package-name@3.0.0","_shasum":"5fa912d81eb7d0c74afc140de7317f0ca7df437e","_from":".","_npmVersion":"4.1.1","_nodeVersion":"6.9.4","_npmUser":{"name":"chrisdickinson","email":"chris@neversaw.us"},"dist":{"shasum":"5fa912d81eb7d0c74afc140de7317f0ca7df437e","tarball":"https://registry.npmjs.org/validate-npm-package-name/-/validate-npm-package-name-3.0.0.tgz"},"maintainers":[{"name":"chrisdickinson","email":"chris@neversaw.us"}],"_npmOperationalInternal":{"host":"packages-18-east.internal.npmjs.com","tmp":"tmp/validate-npm-package-name-3.0.0.tgz_1487026281960_0.640724653378129"}}},"readme":"# validate-npm-package-name\n\nGive me a string and I'll tell you if it's a valid `npm` package name.\n\nThis package exports a single synchronous function that takes a `string` as\ninput and returns an object with two properties:\n\n- `validForNewPackages` :: `Boolean`\n- `validForOldPackages` :: `Boolean`\n\n## Contents\n\n- [Naming rules](#naming-rules)\n- [Examples](#examples)\n + [Valid Names](#valid-names)\n + [Invalid Names](#invalid-names)\n- [Legacy Names](#legacy-names)\n- [Tests](#tests)\n- [License](#license)\n\n## Naming Rules\n\nBelow is a list of rules that valid `npm` package name should conform to.\n\n- package name length should be greater than zero\n- all the characters in the package name must be lowercase i.e., no uppercase or mixed case names are allowed\n- package name *can* consist of hyphens\n- package name must *not* contain any non-url-safe characters (since name ends up being part of a URL)\n- package name should not start with `.` or `_`\n- package name should *not* contain any leading or trailing spaces\n- package name should *not* contain any of the following characters: `~)('!*`\n- package name *cannot* be the same as a node.js/io.js core module nor a reserved/blacklisted name. For example, the following names are invalid:\n + http\n + stream\n + node_modules\n + favicon.ico\n- package name length cannot exceed 214\n\n\n## Examples\n\n### Valid Names\n\n```js\nvar validate = require(\"validate-npm-package-name\")\n\nvalidate(\"some-package\")\nvalidate(\"example.com\")\nvalidate(\"under_score\")\nvalidate(\"123numeric\")\nvalidate(\"excited!\")\nvalidate(\"@npm/thingy\")\nvalidate(\"@jane/foo.js\")\n```\n\nAll of the above names are valid, so you'll get this object back:\n\n```js\n{\n validForNewPackages: true,\n validForOldPackages: true\n}\n```\n\n### Invalid Names\n\n```js\nvalidate(\" leading-space:and:weirdchars\")\n```\n\nThat was never a valid package name, so you get this:\n\n```js\n{\n validForNewPackages: false,\n validForOldPackages: false,\n errors: [\n 'name cannot contain leading or trailing spaces',\n 'name can only contain URL-friendly characters'\n ]\n}\n```\n\n## Legacy Names\n\nIn the old days of npm, package names were wild. They could have capital\nletters in them. They could be really long. They could be the name of an\nexisting module in node core.\n\nIf you give this function a package name that **used to be valid**, you'll see\na change in the value of `validForNewPackages` property, and a warnings array\nwill be present:\n\n```js\nvalidate(\"eLaBorAtE-paCkAgE-with-mixed-case-and-more-than-214-characters-----------------------------------------------------------------------------------------------------------------------------------------------------------\")\n```\n\nreturns:\n\n```js\n{\n validForNewPackages: false,\n validForOldPackages: true,\n warnings: [\n \"name can no longer contain capital letters\",\n \"name can no longer contain more than 214 characters\"\n ]\n}\n```\n\n## Tests\n\n```sh\nnpm install\nnpm test\n```\n\n## License\n\nISC\n","maintainers":[{"email":"ruyadorno@hotmail.com","name":"ruyadorno"},{"email":"mike@mikecorp.ca","name":"mikemimik"},{"email":"billatnpm@gmail.com","name":"billatnpm"},{"email":"anne@npmjs.com","name":"annekimsey"},{"email":"cghr1990@gmail.com","name":"claudiahdz"},{"email":"darcy@darcyclarke.me","name":"darcyclarke"},{"email":"evilpacket@gmail.com","name":"adam_baldwin"},{"email":"ahmad@ahmadnassri.com","name":"ahmadnassri"},{"email":"i@izs.me","name":"isaacs"}],"time":{"modified":"2019-09-10T19:05:13.338Z","created":"2014-11-12T22:55:16.098Z","1.0.0":"2014-11-12T22:55:16.098Z","1.0.1":"2014-11-12T23:01:35.134Z","1.1.0":"2014-11-13T00:48:37.352Z","1.1.1":"2014-11-13T00:49:44.391Z","1.2.0":"2014-11-13T01:01:20.934Z","2.0.0":"2015-01-20T01:22:19.897Z","2.0.1":"2015-01-20T01:23:19.782Z","2.1.0":"2015-04-28T19:20:00.232Z","2.2.0":"2015-04-28T22:41:50.974Z","2.2.1":"2015-06-30T18:33:46.152Z","2.2.2":"2015-06-30T18:33:59.558Z","3.0.0":"2017-02-13T22:51:23.906Z"},"homepage":"https://github.com/npm/validate-npm-package-name","keywords":["npm","package","names","validation"],"repository":{"type":"git","url":"git+https://github.com/npm/validate-npm-package-name.git"},"author":{"name":"zeke"},"bugs":{"url":"https://github.com/npm/validate-npm-package-name/issues"},"license":"ISC","readmeFilename":"README.md","users":{"akiva":true,"mackenza":true,"alshamiri2":true,"akabeko":true,"pftom":true,"daizch":true}} -------------------------------------------------------------------------------- /src/__specs__/index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const fs = require('fs'); 4 | const path = require('path'); 5 | const { createDependencyFactory } = require('../dependency'); 6 | const PackageFactory = require('../package/PackageFactory'); 7 | const Tree = require('../reporters/Tree'); 8 | const { createDependencyResolver } = require('../resolver'); 9 | const HttpClient = require('../utils/http/HttpClient'); 10 | const TarballReader = require('../utils/tarball/TarballReader'); 11 | 12 | const dependencyFactory = createDependencyFactory(); 13 | const packageFactory = new PackageFactory({ 14 | httpClient: null, 15 | tarballReader: null, 16 | }); 17 | const fixtureCache = {}; 18 | 19 | // noinspection JSUnusedGlobalSymbols 20 | module.exports = { 21 | createDependency, 22 | createSimpleGraph, 23 | dependencyFactory, 24 | getFixtureName, 25 | loadProject, 26 | loadFixture, 27 | printDependencyGraph, 28 | }; 29 | 30 | /** 31 | * @param {string} name 32 | * @param {string} versionSpec 33 | * @param {(string | null)} version 34 | * @return {Dependency} 35 | */ 36 | function createDependency(name, versionSpec, { version = null } = {}) { 37 | const dependency = dependencyFactory.create(name, versionSpec); 38 | dependency.package = packageFactory.createUnresolved( 39 | dependency.spec, 40 | ); 41 | dependency.package.version = version || versionSpec; 42 | return dependency; 43 | } 44 | 45 | /** 46 | * @return {{ root: Dependency, a: Dependency, b: Dependency }} 47 | */ 48 | function createSimpleGraph() { 49 | const root = createDependency('test', '1.0.0'); 50 | const a = createDependency('a', '1.0.0'); 51 | const b = createDependency('b', '1.0.0'); 52 | 53 | root.package.stats = { fileCount: 1, unpackedSize: 10 }; 54 | root.children.push(a); 55 | 56 | a.package.stats = { fileCount: 10, unpackedSize: 100 }; 57 | a.children.push(b); 58 | 59 | b.package.stats = { fileCount: 100, unpackedSize: 1000 }; 60 | 61 | return { root, a, b }; 62 | } 63 | 64 | /** 65 | * Transform package name to fixture name 66 | * @param {string} packageName 67 | * @return {string} 68 | */ 69 | function getFixtureName(packageName) { 70 | return decodeURIComponent(packageName) 71 | .replace('@', '') 72 | .replace('/', '_'); 73 | } 74 | 75 | /** 76 | * Create dependencies graph from the fixture (cached version) 77 | * @param {string} packageName 78 | * @return {Promise} 79 | */ 80 | async function loadFixture(packageName) { 81 | if (fixtureCache[packageName]) { 82 | return fixtureCache[packageName]; 83 | } 84 | 85 | const fixture = await loadFixtureWithoutCache(packageName); 86 | fixtureCache[packageName] = fixture; 87 | return fixture; 88 | } 89 | 90 | /** 91 | * Create dependencies graph from the fixture 92 | * @param {string} packageName 93 | * @return {Promise} 94 | */ 95 | async function loadFixtureWithoutCache(packageName) { 96 | const resolver = createDependencyResolver( 97 | createMockedPackageFactory(), 98 | dependencyFactory, 99 | ); 100 | 101 | const root = dependencyFactory.createGroup([packageName]); 102 | return new FixtureGraph(await resolver.resolve(root)); 103 | } 104 | 105 | /** 106 | * Create dependencies graph from the test project 107 | * @param {string} projectName 108 | * @return {Promise} 109 | */ 110 | async function loadProject(projectName) { 111 | const resolver = createDependencyResolver( 112 | createMockedPackageFactory(), 113 | dependencyFactory, 114 | ); 115 | 116 | const root = dependencyFactory.createProject( 117 | path.resolve(__dirname, 'projects', projectName), 118 | ); 119 | return new FixtureGraph(await resolver.resolve(root)); 120 | } 121 | 122 | /** 123 | * @param {Dependency} dependency 124 | */ 125 | function printDependencyGraph(dependency) { 126 | // eslint-disable-next-line no-console 127 | const reporter = new Tree({ printer: console.info }); 128 | reporter.print(dependency); 129 | } 130 | 131 | class HttpClientMock extends HttpClient { 132 | constructor() { 133 | super({}); 134 | } 135 | 136 | async request(config) { 137 | const { url } = config; 138 | 139 | return { 140 | data: this.getFixtureFromUrl(url), 141 | headers: {}, 142 | status: 200, 143 | }; 144 | } 145 | 146 | async getFixtureFromUrl(url) { 147 | const fixturePath = path.resolve( 148 | __dirname, 149 | 'fixtures', 150 | getFixtureName(path.basename(url)) + '.json', 151 | ); 152 | 153 | return fs.promises.readFile(fixturePath, 'utf8').then(JSON.parse); 154 | } 155 | } 156 | 157 | class FixtureGraph { 158 | constructor(root) { 159 | this.root = root; 160 | } 161 | 162 | /** 163 | * 164 | * @param {string[]} paths 165 | * @return {Dependency} 166 | */ 167 | getNode(paths) { 168 | let node = this.getFixtureRoot(); 169 | 170 | paths.forEach((nodeName) => { 171 | node = node && node.children.find((dep) => dep.name === nodeName); 172 | }); 173 | 174 | return node; 175 | } 176 | 177 | /** 178 | * @return {Dependency} 179 | */ 180 | getRoot() { 181 | return this.root; 182 | } 183 | 184 | /** 185 | * @return {Dependency} 186 | */ 187 | getFixtureRoot() { 188 | if (this.getRoot().isReal()) { 189 | return this.getRoot(); 190 | } 191 | 192 | return this.getRoot().children[0]; 193 | } 194 | } 195 | 196 | function createMockedPackageFactory() { 197 | const httpClient = new HttpClientMock(); 198 | const tarballReader = new TarballReader({ httpClient }); 199 | 200 | tarballReader.readStream = (stream) => stream; 201 | 202 | return new PackageFactory({ httpClient, tarballReader }); 203 | } 204 | -------------------------------------------------------------------------------- /src/__specs__/projects/local-dependencies/README.md: -------------------------------------------------------------------------------- 1 | Test project which includes only local dependencies 2 | -------------------------------------------------------------------------------- /src/__specs__/projects/local-dependencies/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "local-dependencies", 3 | "version": "0.0.1", 4 | "private": true, 5 | "dependencies": { 6 | "local1": "file:../local1", 7 | "local2": "../local2" 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /src/__specs__/projects/local1/README.md: -------------------------------------------------------------------------------- 1 | A simple local package without dependencies 2 | -------------------------------------------------------------------------------- /src/__specs__/projects/local1/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "local1", 3 | "version": "0.0.1", 4 | "private": true 5 | } 6 | -------------------------------------------------------------------------------- /src/__specs__/projects/local2/README.md: -------------------------------------------------------------------------------- 1 | A simple local package without dependencies 2 | -------------------------------------------------------------------------------- /src/__specs__/projects/local2/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "local2", 3 | "version": "0.0.2", 4 | "private": true 5 | } 6 | -------------------------------------------------------------------------------- /src/dependency/Dependency.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const graph = require('./graph'); 4 | 5 | class Dependency { 6 | constructor() { 7 | /** 8 | * @type {Dependency[]} 9 | */ 10 | this.children = []; 11 | 12 | /** 13 | * @private 14 | */ 15 | this.onLoadDependencies = { 16 | handler: () => {}, 17 | }; 18 | this.onLoadDependencies.promise = new Promise((resolve) => { 19 | this.onLoadDependencies.handler = resolve; 20 | }); 21 | } 22 | 23 | canIncludeDevDependencies() { 24 | return false; 25 | } 26 | 27 | /** 28 | * @return {Dependency[]} 29 | */ 30 | flatChildren() { 31 | return Array.from(graph.flat(this)); 32 | } 33 | 34 | getError() { 35 | return { 36 | error: null, 37 | message: '', 38 | reason: 'none', 39 | }; 40 | } 41 | 42 | getLabel() { 43 | return ''; 44 | } 45 | 46 | getField(name) { 47 | return this.getFields()[name]; 48 | } 49 | 50 | getFields() { 51 | return {}; 52 | } 53 | 54 | getPackageJson() { 55 | return {}; 56 | } 57 | 58 | getOriginal() { 59 | return this; 60 | } 61 | 62 | /** 63 | * @return {Stats} 64 | */ 65 | getStats() { 66 | const stats = this.package ? this.package.stats : {}; 67 | 68 | return { 69 | dependencyCount: 1, 70 | fileCount: Math.max(0, stats.fileCount || 0), 71 | unpackedSize: Math.max(0, stats.unpackedSize || 0), 72 | }; 73 | } 74 | 75 | /** 76 | * @return {Stats} 77 | */ 78 | getStatsRecursive() { 79 | const dependencies = this.flatChildren(); 80 | 81 | if (!dependencies.includes(this)) { 82 | // If there is no cyclic dependencies, add itself 83 | dependencies.push(this); 84 | } 85 | 86 | const stats = { 87 | dependencyCount: -1, // don't count itself 88 | fileCount: 0, 89 | unpackedSize: 0, 90 | }; 91 | 92 | dependencies.forEach((dep) => { 93 | const depStats = dep.getStats(); 94 | stats.dependencyCount += depStats.dependencyCount; 95 | stats.fileCount += depStats.fileCount; 96 | stats.unpackedSize += depStats.unpackedSize; 97 | }); 98 | 99 | return { 100 | dependencyCount: Math.max(0, stats.dependencyCount), 101 | fileCount: Math.max(0, stats.fileCount), 102 | unpackedSize: Math.max(0, stats.unpackedSize), 103 | }; 104 | } 105 | 106 | isReal() { 107 | return false; 108 | } 109 | 110 | /** 111 | * @param {Dependency[]} children 112 | */ 113 | loadChildren(children) { 114 | this.children = children; 115 | } 116 | 117 | /** 118 | * @return {Promise} 119 | */ 120 | async waitForResolve() { 121 | return null; 122 | } 123 | } 124 | 125 | Dependency.TYPES = ['normal', 'optional', 'peer', 'dev']; 126 | 127 | module.exports = Dependency; 128 | -------------------------------------------------------------------------------- /src/dependency/DependencyFactory.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const { parseSpec } = require('../utils/spec'); 4 | const Dependency = require('./Dependency'); 5 | const DuplicateDependency = require('./DuplicateDependency'); 6 | const GroupDependency = require('./GroupDependency'); 7 | const ProjectDependency = require('./ProjectDependency'); 8 | const RealDependency = require('./RealDependency'); 9 | const UnmetDependency = require('./UnmetDependency'); 10 | 11 | class DependencyFactory { 12 | /** 13 | * @param {string} name 14 | * @param {string} versionSpec 15 | * @param {DependencyType} type 16 | * @param {string} localPath 17 | * @return {RealDependency} 18 | */ 19 | create( 20 | name, 21 | versionSpec = undefined, 22 | type = 'normal', 23 | localPath = undefined, 24 | ) { 25 | const dependencySpec = parseSpec(name, versionSpec, localPath); 26 | return new RealDependency(dependencySpec, type); 27 | } 28 | 29 | /** 30 | * @param {RealDependency} original 31 | * @param {RealDependency} duplicate 32 | * @return {DuplicateDependency} 33 | */ 34 | createDuplicate(original, duplicate = null) { 35 | const type = duplicate && duplicate.type; 36 | return new DuplicateDependency(original.spec, type) 37 | .setOriginal(original); 38 | } 39 | 40 | /** 41 | * Is used when we need to fetch stats of one or many independent packages 42 | * @param {string[]} dependencies 43 | * @return {GroupDependency} 44 | */ 45 | createGroup(dependencies = []) { 46 | const group = new GroupDependency(); 47 | 48 | dependencies.forEach((depSpec) => { 49 | const dependency = this.create(depSpec); 50 | dependency.setIncludesDevDependencies(true); 51 | group.addDependency(dependency); 52 | }); 53 | 54 | return group; 55 | } 56 | 57 | /** 58 | * @param {RealDependency} original 59 | * @return {RealDependency} 60 | */ 61 | createUnmet(original) { 62 | return new UnmetDependency(original.spec, original.type) 63 | .setOriginal(original); 64 | } 65 | 66 | /** 67 | * @param {Package} pkg 68 | * @param {DependencyTypeFilter} typeFilter 69 | * @return {RealDependency[]} 70 | */ 71 | createDependenciesOfPackage(pkg, typeFilter = {}) { 72 | const result = []; 73 | const specs = pkg.getDependencies(typeFilter); 74 | 75 | for (const type of Dependency.TYPES) { 76 | for (const [name, versionSpec] of Object.entries(specs[type])) { 77 | result.push(this.create(name, versionSpec, type, pkg.localPath)); 78 | } 79 | } 80 | 81 | return result; 82 | } 83 | 84 | createProject(projectPath) { 85 | const dependencySpec = parseSpec(null, projectPath); 86 | return new ProjectDependency(dependencySpec); 87 | } 88 | } 89 | 90 | module.exports = DependencyFactory; 91 | -------------------------------------------------------------------------------- /src/dependency/DuplicateDependency.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const RealDependency = require('./RealDependency'); 4 | 5 | class DuplicateDependency extends RealDependency { 6 | get version() { 7 | return this.original ? this.original.version : null; 8 | } 9 | 10 | getError() { 11 | return this.original ? this.original.getError() : super.getError(); 12 | } 13 | 14 | getLabel() { 15 | return 'duplicate'; 16 | } 17 | 18 | getFields() { 19 | return this.original ? this.original.getFields() : super.getFields(); 20 | } 21 | 22 | getPackageJson() { 23 | return this.original 24 | ? this.original.getPackageJson() 25 | : super.getPackageJson(); 26 | } 27 | 28 | /** 29 | * @param {Dependency} dependency 30 | * @return {this} 31 | */ 32 | setOriginal(dependency) { 33 | this.original = dependency; 34 | return this; 35 | } 36 | 37 | getOriginal() { 38 | return this.original || this; 39 | } 40 | 41 | getStats() { 42 | return this.original ? this.original.getStats() : super.getStats(); 43 | } 44 | 45 | getStatsRecursive() { 46 | const original = this.original; 47 | return original ? original.getStatsRecursive() : super.getStatsRecursive(); 48 | } 49 | } 50 | 51 | module.exports = DuplicateDependency; 52 | -------------------------------------------------------------------------------- /src/dependency/GroupDependency.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const Dependency = require('./Dependency'); 4 | 5 | class GroupDependency extends Dependency { 6 | /** 7 | * @param {Dependency} dependency 8 | * @return {this} 9 | */ 10 | addDependency(dependency) { 11 | this.children.push(dependency); 12 | } 13 | 14 | getStats() { 15 | return { 16 | dependencyCount: 1 - this.children.length, 17 | fileCount: 0, 18 | unpackedSize: 0, 19 | }; 20 | } 21 | } 22 | 23 | module.exports = GroupDependency; 24 | -------------------------------------------------------------------------------- /src/dependency/ProjectDependency.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const RealDependency = require('./RealDependency'); 4 | 5 | class ProjectDependency extends RealDependency { 6 | canIncludeDevDependencies() { 7 | return true; 8 | } 9 | 10 | setPackage(pkg) { 11 | this.package = pkg; 12 | } 13 | } 14 | 15 | module.exports = ProjectDependency; 16 | -------------------------------------------------------------------------------- /src/dependency/RealDependency.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const Dependency = require('./Dependency'); 4 | 5 | class RealDependency extends Dependency { 6 | /** 7 | * @param {DependencySpec} spec 8 | * @param {DependencyType} type 9 | */ 10 | constructor(spec, type = 'normal') { 11 | super(); 12 | 13 | this.spec = spec; 14 | this.type = type; 15 | 16 | /** 17 | * @type {Package} 18 | */ 19 | this.package = null; 20 | 21 | this.includesDev = false; 22 | 23 | /** 24 | * @private 25 | */ 26 | this.onLoadDependencies = { 27 | handler: () => {}, 28 | }; 29 | this.onLoadDependencies.promise = new Promise((resolve) => { 30 | this.onLoadDependencies.handler = resolve; 31 | }); 32 | } 33 | 34 | get name() { 35 | if (this.package) { 36 | return this.package.name; 37 | } 38 | 39 | return this.spec.name; 40 | } 41 | 42 | get version() { 43 | if (this.package) { 44 | return this.package.version; 45 | } 46 | 47 | return null; 48 | } 49 | 50 | canIncludeDevDependencies() { 51 | return this.includesDev; 52 | } 53 | 54 | /** 55 | * @param {boolean} value 56 | */ 57 | setIncludesDevDependencies(value) { 58 | this.includesDev = value; 59 | } 60 | 61 | isReal() { 62 | return true; 63 | } 64 | 65 | /** 66 | * @param {Dependency[]} children 67 | */ 68 | loadChildren(children) { 69 | super.loadChildren(children); 70 | this.onLoadDependencies.handler(); 71 | } 72 | 73 | getError() { 74 | return this.package.error; 75 | } 76 | 77 | getFields() { 78 | return { 79 | ...this.package?.requirements, 80 | ...this.package?.fields, 81 | }; 82 | } 83 | 84 | getPackageJson() { 85 | return this.package?.packageJson || {}; 86 | } 87 | 88 | /** 89 | * @param {Package} pkg 90 | */ 91 | setPackage(pkg) { 92 | this.package = pkg; 93 | } 94 | 95 | toString() { 96 | return this.name + '@' + (this.version || this.spec.versionSpec); 97 | } 98 | 99 | /** 100 | * @return {Promise} 101 | */ 102 | async waitForResolve() { 103 | return this.onLoadDependencies.promise; 104 | } 105 | } 106 | 107 | module.exports = RealDependency; 108 | -------------------------------------------------------------------------------- /src/dependency/UnmetDependency.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const DuplicateDependency = require('./DuplicateDependency'); 4 | 5 | class UnmetDependency extends DuplicateDependency { 6 | getLabel() { 7 | return 'unmet'; 8 | } 9 | 10 | getStats() { 11 | return { 12 | dependencyCount: 0, 13 | fileCount: 0, 14 | unpackedSize: 0, 15 | }; 16 | } 17 | 18 | getOriginal() { 19 | return this; 20 | } 21 | } 22 | 23 | module.exports = UnmetDependency; 24 | -------------------------------------------------------------------------------- /src/dependency/__specs__/Dependency.spec.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const { describe, expect, expectAsync, it } = require('humile'); 4 | const { createSimpleGraph } = require('../../__specs__'); 5 | 6 | describe('Dependency', () => { 7 | it('flatChildren', () => { 8 | const { root, a, b } = createSimpleGraph(); 9 | expect(root.flatChildren()).toEqual([a, b]); 10 | }); 11 | 12 | it('getStats', () => { 13 | const { root } = createSimpleGraph(); 14 | 15 | expect(root.getStats()).toEqual({ 16 | dependencyCount: 1, 17 | fileCount: 1, 18 | unpackedSize: 10, 19 | }); 20 | }); 21 | 22 | it('getStatsRecursive', () => { 23 | const { root, a, b } = createSimpleGraph(); 24 | 25 | expect(a.getStatsRecursive()).toEqual({ 26 | dependencyCount: 1, 27 | fileCount: 110, 28 | unpackedSize: 1100, 29 | }); 30 | 31 | expect(b.getStatsRecursive()).toEqual({ 32 | dependencyCount: 0, 33 | fileCount: 100, 34 | unpackedSize: 1000, 35 | }); 36 | 37 | expect(root.getStatsRecursive()).toEqual({ 38 | dependencyCount: 2, 39 | fileCount: 111, 40 | unpackedSize: 1110, 41 | }); 42 | }); 43 | 44 | it('waitForResolve', async () => { 45 | const { root, a } = createSimpleGraph(); 46 | 47 | const promise = root.waitForResolve(); 48 | 49 | root.loadChildren([a]); 50 | 51 | return expectAsync(promise).toBeResolved(); 52 | }); 53 | }); 54 | -------------------------------------------------------------------------------- /src/dependency/__specs__/GroupDependency.spec.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const { describe, expect, it } = require('humile'); 4 | const { dependencyFactory, loadFixture } = require('../../__specs__'); 5 | 6 | describe('GroupDependency', () => { 7 | describe('getStatsRecursive', () => { 8 | it('should return 0 dependencies for itself', () => { 9 | const root = dependencyFactory.createGroup(); 10 | 11 | expect(root.getStatsRecursive()).toEqual({ 12 | dependencyCount: 0, 13 | fileCount: 0, 14 | unpackedSize: 0, 15 | }); 16 | }); 17 | 18 | it('should not count direct descendants', () => { 19 | const root = dependencyFactory.createGroup(); 20 | const a = dependencyFactory.create('a'); 21 | const b = dependencyFactory.create('b'); 22 | root.addDependency(a); 23 | root.addDependency(b); 24 | 25 | expect(root.getStatsRecursive()).toEqual({ 26 | dependencyCount: 0, 27 | fileCount: 0, 28 | unpackedSize: 0, 29 | }); 30 | }); 31 | 32 | it('should count only indirect descendants', () => { 33 | const root = dependencyFactory.createGroup(); 34 | const a = dependencyFactory.create('a'); 35 | const b = dependencyFactory.create('b'); 36 | root.addDependency(a); 37 | a.children.push(b); 38 | 39 | expect(root.getStatsRecursive()).toEqual({ 40 | dependencyCount: 1, 41 | fileCount: 0, 42 | unpackedSize: 0, 43 | }); 44 | }); 45 | 46 | it('should return stats for a complex graph correctly', async () => { 47 | const graph = await loadFixture('@webassemblyjs/helper-code-frame'); 48 | expect(graph.getRoot().getStatsRecursive()).toEqual({ 49 | dependencyCount: 10, 50 | unpackedSize: 691071, 51 | fileCount: 96, 52 | }); 53 | }); 54 | }); 55 | }); 56 | -------------------------------------------------------------------------------- /src/dependency/__specs__/graph.spec.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const { describe, expect, it } = require('humile'); 4 | const { createSimpleGraph, dependencyFactory } = require('../../__specs__'); 5 | const DuplicateDependency = require('../DuplicateDependency'); 6 | const graph = require('../graph'); 7 | 8 | describe('graph', () => { 9 | it('flat', () => { 10 | const { root, a, b } = createSimpleGraph(); 11 | 12 | expect(graph.flat(root)).toEqual(new Set([a, b])); 13 | }); 14 | 15 | it('mapAsync', async () => { 16 | let { root } = createSimpleGraph(); 17 | 18 | root = await graph.mapAsync(root, async (dep) => { 19 | if (dep.spec.name === 'test') { 20 | return dep; 21 | } 22 | 23 | return dependencyFactory.createDuplicate(dep); 24 | }); 25 | 26 | expect(root.children[0].constructor).toBe(DuplicateDependency); 27 | expect(root.children[0].getOriginal().children[0].constructor) 28 | .toBe(DuplicateDependency); 29 | }); 30 | }); 31 | -------------------------------------------------------------------------------- /src/dependency/graph.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = { 4 | flat, 5 | mapAsync, 6 | }; 7 | 8 | /** 9 | * @param {Dependency} dependency 10 | * @param {Set} flattenItems For internal usage 11 | * @return {Set} 12 | */ 13 | function flat(dependency, flattenItems = new Set()) { 14 | dependency.children.forEach((dep) => { 15 | dep = dep.getOriginal(); 16 | if (!flattenItems.has(dep.getOriginal())) { 17 | flattenItems.add(dep); 18 | flat(dep, flattenItems); 19 | } 20 | }); 21 | 22 | return flattenItems; 23 | } 24 | 25 | /** 26 | * @param {Dependency} dependency 27 | * @param {MapDependencyCallback} callback 28 | * @return {Promise} 29 | */ 30 | async function mapAsync(dependency, callback) { 31 | dependency = await callback(dependency, 0, null); 32 | 33 | let list = dependency.children 34 | .map((child, index) => ({ parent: dependency, child, index })); 35 | 36 | while (list.length > 0) { 37 | const nextList = []; 38 | 39 | const promises = list.map(({ child, index, parent }) => { 40 | return callback(child, index, parent) 41 | .then((newChild) => { 42 | parent.children[index] = newChild; 43 | child.children.forEach((c, i) => { 44 | nextList.push({ child: c, index: i, parent: child }); 45 | }); 46 | }); 47 | }); 48 | 49 | await Promise.all(promises); // eslint-disable-line no-await-in-loop 50 | list = nextList; 51 | } 52 | 53 | return dependency; 54 | } 55 | -------------------------------------------------------------------------------- /src/dependency/index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const DependencyFactory = require('./DependencyFactory'); 4 | 5 | module.exports = { 6 | createDependencyFactory, 7 | }; 8 | 9 | /** 10 | * @return {DependencyFactory} 11 | */ 12 | function createDependencyFactory() { 13 | return new DependencyFactory(); 14 | } 15 | -------------------------------------------------------------------------------- /src/index.d.ts: -------------------------------------------------------------------------------- 1 | import Dependency = require('./dependency/Dependency'); 2 | import HttpClient = require('./utils/http/HttpClient'); 3 | import { TarballStat } from './utils/tarball'; 4 | 5 | export type DependencyType = 'dev' | 'normal' | 'optional' | 'peer'; 6 | 7 | export type DependencyTypeFilter = { 8 | [P in DependencyType]?: boolean; 9 | } 10 | 11 | export type DependencySpec = { 12 | name: string; 13 | versionSpec: string; 14 | escapedName: string; 15 | source: 'npm' | 'directory' | 'github' | 'http'; 16 | } 17 | 18 | export type GetTarballStats = ( 19 | url: string, 20 | httpClient: HttpClient, 21 | ) => Promise; 22 | 23 | export type HttpProgressEvent = { 24 | finishedCount: number; 25 | queuedCount: number; 26 | } 27 | 28 | export type MapDependencyCallback = ( 29 | dependency: Dependency, 30 | index?: number, 31 | parent?: Dependency, 32 | ) => Promise; 33 | 34 | export type ProgressIndicatorType = 'url' | 'stat' 35 | 36 | export interface ReporterOptions { 37 | name?: string; 38 | fields?: string[]; 39 | shortSize?: boolean; 40 | sort?: string; 41 | sortDesc?: boolean; 42 | space?: number | string, 43 | useColors?: boolean; 44 | printer?(...args: string[]); 45 | } 46 | 47 | export interface Stats { 48 | dependencyCount?: number; 49 | fileCount: number; 50 | unpackedSize: number; 51 | } 52 | 53 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | /* eslint-disable no-console */ 4 | 5 | 'use strict'; 6 | 7 | const { createDependencyFactory } = require('./dependency'); 8 | const { createReporter } = require('./reporters'); 9 | const { createDependencyResolver } = require('./resolver'); 10 | const { createProgressIndicator, getConfig } = require('./utils'); 11 | const TarballReader = require('./utils/tarball/TarballReader'); 12 | const PackageFactory = require('./package/PackageFactory'); 13 | const HttpClient = require('./utils/http/HttpClient'); 14 | 15 | const config = getConfig(); 16 | 17 | main().catch((error) => { 18 | console.error('Error:', config.isVerbose ? error : error.message); 19 | process.exit(1); 20 | }); 21 | 22 | async function main() { 23 | const { 24 | dependencyFactory, 25 | progressIndicator, 26 | reporter, 27 | resolver, 28 | } = createModules(); 29 | 30 | let rootDependency; 31 | if (config.projectPath) { 32 | rootDependency = dependencyFactory.createProject(config.projectPath); 33 | } else if (config.fetchedPackages.length > 0) { 34 | rootDependency = dependencyFactory.createGroup(config.fetchedPackages); 35 | } else { 36 | console.error(config.helpText); 37 | process.exit(1); 38 | } 39 | 40 | rootDependency = await resolver.resolve(rootDependency); 41 | progressIndicator.finish(); 42 | 43 | reporter.print(rootDependency); 44 | process.exit(); 45 | } 46 | 47 | function createModules() { 48 | const httpClient = new HttpClient(config.httpOptions); 49 | httpClient.on('error', ({ error, url }) => { 50 | console.error('Warning:', error.message, 'for', url); 51 | }); 52 | 53 | const dependencyFactory = createDependencyFactory(); 54 | const progressIndicator = createProgressIndicator( 55 | httpClient, 56 | config.isVerbose ? 'url' : 'stat', 57 | ); 58 | 59 | const tarballReader = new TarballReader({ httpClient }); 60 | 61 | const resolver = createDependencyResolver( 62 | new PackageFactory({ 63 | httpClient, 64 | registryUrl: config.registryUrl, 65 | tarballReader, 66 | }), 67 | dependencyFactory, 68 | config.dependencyTypeFilter, 69 | ); 70 | 71 | return { 72 | dependencyFactory, 73 | httpClient, 74 | progressIndicator, 75 | reporter: createReporter(config.reporterOptions), 76 | resolver, 77 | }; 78 | } 79 | -------------------------------------------------------------------------------- /src/package/Package.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | class Package { 4 | /** @type {string} */ 5 | name; 6 | 7 | /** @type {string} */ 8 | version; 9 | 10 | /** @type {string} */ 11 | versionSpec; 12 | 13 | /** @type {Record} */ 14 | fields = {}; 15 | 16 | /** @type {string} */ 17 | localPath; 18 | 19 | dependencies = { 20 | dev: {}, 21 | normal: {}, 22 | optional: {}, 23 | regular: {}, 24 | }; 25 | 26 | stats = { 27 | fileCount: -1, 28 | unpackedSize: -1, 29 | }; 30 | 31 | packageJson = {}; 32 | 33 | requirements = { 34 | arch: undefined, 35 | node: undefined, 36 | platform: undefined, 37 | }; 38 | 39 | error = { 40 | error: null, 41 | message: '', 42 | 43 | /** @type {'none' | 'not-found'} */ 44 | reason: 'none', 45 | }; 46 | 47 | /** 48 | * @param {string} name 49 | * @param {string} versionSpec 50 | */ 51 | constructor(name, versionSpec) { 52 | this.name = name; 53 | this.versionSpec = versionSpec; 54 | } 55 | 56 | /** 57 | * @param {DependencyTypeFilter} typeFilter 58 | * @return {{ normal: object, dev: object, peer: object, optional: object }} 59 | */ 60 | getDependencies(typeFilter = {}) { 61 | const result = { normal: {}, optional: {}, peer: {}, dev: {} }; 62 | const duplicates = {}; 63 | 64 | typeFilter = typeFilter || {}; 65 | typeFilter.dev = Boolean(typeFilter.dev); 66 | typeFilter.normal = typeFilter.normal !== false; 67 | typeFilter.optional = typeFilter.optional !== false; 68 | typeFilter.peer = Boolean(typeFilter.peer); 69 | 70 | for (const type of ['normal', 'optional', 'peer', 'dev']) { 71 | if (!typeFilter[type] || typeof this.dependencies[type] !== 'object') { 72 | continue; 73 | } 74 | 75 | for (const [name, spec] of Object.entries(this.dependencies[type])) { 76 | if (!duplicates[name]) { 77 | result[type][name] = spec; 78 | duplicates[name] = true; 79 | } 80 | } 81 | } 82 | 83 | return result; 84 | } 85 | 86 | hasStats() { 87 | return this.stats.fileCount > -1 && this.stats.unpackedSize > -1; 88 | } 89 | 90 | setError({ error, message, reason }) { 91 | this.error = { error, message, reason }; 92 | } 93 | 94 | toString() { 95 | return this.name + '@' + (this.version || this.versionSpec); 96 | } 97 | } 98 | 99 | module.exports = Package; 100 | -------------------------------------------------------------------------------- /src/package/PackageFactory.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const DirectoryFetcher = require('./fetchers/DirectoryFetcher'); 4 | const GitFetcher = require('./fetchers/GitFetcher'); 5 | const GithubFetcher = require('./fetchers/GithubFetcher'); 6 | const HttpFetcher = require('./fetchers/HttpFetcher'); 7 | const NpmFetcher = require('./fetchers/NpmFetcher'); 8 | const Package = require('./Package'); 9 | 10 | class PackageFactory { 11 | #fetchers = {}; 12 | 13 | /** 14 | * @param {object} options 15 | * @param {HttpClient} options.httpClient 16 | * @param {string} [options.registryUrl] 17 | * @param {TarballReader} options.tarballReader 18 | */ 19 | constructor({ httpClient, registryUrl, tarballReader }) { 20 | this.#fetchers = { 21 | directory: new DirectoryFetcher(), 22 | git: new GitFetcher(), 23 | github: new GithubFetcher({ tarballReader }), 24 | http: new HttpFetcher({ tarballReader }), 25 | npm: new NpmFetcher({ httpClient, registryUrl, tarballReader }), 26 | }; 27 | } 28 | 29 | /** 30 | * @param {DependencySpec} dependencySpec 31 | * @return {Promise} 32 | */ 33 | async create(dependencySpec) { 34 | const pkg = new Package(dependencySpec.name, dependencySpec.versionSpec); 35 | const source = dependencySpec.source; 36 | 37 | if (this.#fetchers[source]) { 38 | return this.#fetchers[source].fetch(pkg, dependencySpec); 39 | } 40 | 41 | throw new Error( 42 | `PackageFactory doesn't support ${source} source (${pkg})`, 43 | ); 44 | } 45 | 46 | /** 47 | * @param {DependencySpec} dependencySpec 48 | * @return {Package} 49 | */ 50 | createUnresolved(dependencySpec) { 51 | return new Package(dependencySpec.name, dependencySpec.versionSpec); 52 | } 53 | } 54 | 55 | module.exports = PackageFactory; 56 | -------------------------------------------------------------------------------- /src/package/__specs__/Package.spec.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const { describe, expect, it } = require('humile'); 4 | const Package = require('../Package'); 5 | 6 | describe('Package', () => { 7 | describe('getDependencies', () => { 8 | const pkg = new Package('test', '^1.0.0'); 9 | 10 | pkg.dependencies = { 11 | normal: { 12 | 'lighter-config': '>=1.1.0 <2', 13 | 'fsevents': '>=1.0.14 <2', 14 | }, 15 | optional: { 16 | debug: '^1.0.0', 17 | fsevents: '>=1.0.14 <2', 18 | }, 19 | }; 20 | 21 | it('removes duplicates', () => { 22 | expect(pkg.getDependencies()).toEqual({ 23 | dev: {}, 24 | normal: { 25 | 'lighter-config': '>=1.1.0 <2', 26 | 'fsevents': '>=1.0.14 <2', 27 | }, 28 | optional: { 29 | debug: '^1.0.0', 30 | }, 31 | peer: {}, 32 | }); 33 | }); 34 | 35 | it('filter dependencies', () => { 36 | expect(pkg.getDependencies({ optional: false })).toEqual({ 37 | dev: {}, 38 | normal: { 39 | 'lighter-config': '>=1.1.0 <2', 40 | 'fsevents': '>=1.0.14 <2', 41 | }, 42 | optional: {}, 43 | peer: {}, 44 | }); 45 | }); 46 | }); 47 | }); 48 | -------------------------------------------------------------------------------- /src/package/fetchers/DirectoryFetcher.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const fs = require('fs'); 4 | const path = require('path'); 5 | const Fetcher = require('./Fetcher'); 6 | 7 | class DirectoryFetcher extends Fetcher { 8 | /** 9 | * @param {Package} pkg 10 | * @param {DependencySpec} dependencySpec 11 | * @return {Promise} 12 | */ 13 | async fetch(pkg, { versionSpec }) { 14 | await this.updatePackageFromDirectory(pkg, versionSpec); 15 | return pkg; 16 | } 17 | 18 | /** 19 | * 20 | * @param {Package} pkg 21 | * @param {string} dirPath 22 | * @return {Promise} 23 | * @protected 24 | */ 25 | async updatePackageFromDirectory(pkg, dirPath) { 26 | const content = await fs.promises.readFile( 27 | path.join(dirPath, 'package.json'), 28 | 'utf8', 29 | ); 30 | 31 | const packageJson = JSON.parse(content); 32 | 33 | if (!pkg.name) { 34 | pkg.name = packageJson.name; 35 | } 36 | 37 | pkg.localPath = dirPath; 38 | pkg.version = packageJson.version; 39 | pkg.dependencies = this.extractDependencies(packageJson); 40 | pkg.stats = await this.getDirStats(dirPath); 41 | pkg.requirements = this.extractRequirements(packageJson); 42 | } 43 | 44 | /** 45 | * @param {string} directory 46 | * @param {object} initial 47 | * @return {Promise<{ unpackedSize: number, fileCount: number }>} 48 | * @private 49 | */ 50 | async getDirStats(directory, initial = { fileCount: 0, unpackedSize: 0 }) { 51 | const files = await fs.promises.readdir(directory); 52 | 53 | const promises = files 54 | .filter((file) => file !== 'node_modules') 55 | .map(async (file) => { 56 | const filePath = path.join(directory, file); 57 | const stat = await fs.promises.stat(filePath); 58 | if (stat.isDirectory()) { 59 | await this.getDirStats(filePath, initial); 60 | return; 61 | } 62 | 63 | initial.fileCount += 1; 64 | initial.unpackedSize += stat.size; 65 | }); 66 | 67 | await Promise.all(promises); 68 | 69 | return initial; 70 | } 71 | } 72 | 73 | module.exports = DirectoryFetcher; 74 | -------------------------------------------------------------------------------- /src/package/fetchers/Fetcher.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | class Fetcher { 4 | /** 5 | * @param {Package} _pkg 6 | * @param {DependencySpec} _dependencySpec 7 | * @return {Promise} 8 | */ 9 | async fetch(_pkg, _dependencySpec) { 10 | throw new Error('Not implemented in base Fetcher'); 11 | } 12 | 13 | /** 14 | * @param {object} packageJson 15 | * @return {{ dev: {}, normal: {}, peer: {}, optional: {} }} 16 | * @protected 17 | */ 18 | extractDependencies(packageJson) { 19 | const defaults = { dev: {}, normal: {}, optional: {}, peer: {} }; 20 | 21 | if (!packageJson) { 22 | return defaults; 23 | } 24 | 25 | return Object.keys(defaults).reduce((result, type) => { 26 | const key = type === 'normal' ? 'dependencies' : type + 'Dependencies'; 27 | const deps = packageJson[key]; 28 | if (typeof deps !== 'object') { 29 | return result; 30 | } 31 | 32 | result[type] = deps; 33 | 34 | return result; 35 | }, defaults); 36 | } 37 | 38 | /** 39 | * @param {object} packageJson 40 | * @return {{ arch: ?string, node: ?string, platform: ?string }} 41 | * @protected 42 | */ 43 | extractRequirements(packageJson) { 44 | return { 45 | arch: packageJson?.cpu, 46 | node: packageJson?.engines?.node, 47 | platform: packageJson.os, 48 | }; 49 | } 50 | } 51 | 52 | module.exports = Fetcher; 53 | -------------------------------------------------------------------------------- /src/package/fetchers/GitFetcher.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const { spawn } = require('child_process'); 4 | const fs = require('fs'); 5 | const os = require('os'); 6 | const path = require('path'); 7 | const DirectoryFetcher = require('./DirectoryFetcher'); 8 | 9 | class GitFetcher extends DirectoryFetcher { 10 | /** 11 | * @param {Package} pkg 12 | * @param {DependencySpec} dependencySpec 13 | * @return {Promise} 14 | */ 15 | async fetch(pkg, { escapedName, versionSpec }) { 16 | await this.checkGitInstalled(); 17 | 18 | const tmpDirName = 'howfat-' + Math.random().toString(36).substring(7); 19 | const clonePath = path.join(os.tmpdir(), tmpDirName); 20 | 21 | try { 22 | await this.cloneRepo(escapedName, tmpDirName); 23 | 24 | if (versionSpec) { 25 | await this.checkout(versionSpec); 26 | } 27 | 28 | await this.updatePackageFromDirectory(pkg, clonePath); 29 | } finally { 30 | await this.rmDir(clonePath); 31 | } 32 | 33 | return pkg; 34 | } 35 | 36 | async checkGitInstalled() { 37 | try { 38 | return this.gitCmd(['--version']); 39 | } catch (e) { 40 | throw new Error('git command not found. ' + e.message); 41 | } 42 | } 43 | 44 | async checkout(commit) { 45 | return this.gitCmd('checkout', commit); 46 | } 47 | 48 | async cloneRepo(repo, dir) { 49 | return this.gitCmd(['clone', '--depth', '1', repo, dir]); 50 | } 51 | 52 | async gitCmd(args) { 53 | return new Promise((resolve, reject) => { 54 | const git = spawn('git', args, { 55 | cwd: os.tmpdir(), 56 | env: { 57 | GIT_TERMINAL_PROMPT: 0, 58 | // Prevent ssh fingerprint prompt, it's ok for just getting stats 59 | GIT_SSH_COMMAND: 60 | 'ssh -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no', 61 | }, 62 | }); 63 | 64 | const stdout = []; 65 | const stderr = []; 66 | 67 | git.on('close', (code) => { 68 | if (!code) { 69 | resolve(stdout.join('')); 70 | } else { 71 | reject(stderr.join('')); 72 | } 73 | }); 74 | 75 | git.stdout.on('data', (buffer) => stdout.push(buffer.toString())); 76 | git.stderr.on('data', (buffer) => stderr.push(buffer.toString())); 77 | }); 78 | } 79 | 80 | async rmDir(dirPath) { 81 | try { 82 | const files = await fs.promises.readdir(dirPath); 83 | 84 | const promises = files 85 | .map(async (file) => { 86 | const filePath = path.join(dirPath, file); 87 | const stat = await fs.promises.stat(filePath); 88 | if (stat.isDirectory()) { 89 | return this.rmDir(filePath); 90 | } 91 | 92 | return fs.promises.unlink(filePath); 93 | }); 94 | 95 | await Promise.all(promises); 96 | await fs.promises.rmdir(dirPath); 97 | } catch (e) { 98 | // Can't delete some temp files, skip 99 | } 100 | } 101 | } 102 | 103 | module.exports = GitFetcher; 104 | -------------------------------------------------------------------------------- /src/package/fetchers/GithubFetcher.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const HttpFetcher = require('./HttpFetcher'); 4 | 5 | class GithubFetcher extends HttpFetcher { 6 | /** 7 | * @param {Package} pkg 8 | * @param {DependencySpec} dependencySpec 9 | * @return {Promise} 10 | */ 11 | async fetch(pkg, { escapedName, versionSpec }) { 12 | const stats = await this.tarballReader.readUrl( 13 | `https://codeload.github.com/${escapedName}/tar.gz/${versionSpec}`, 14 | ); 15 | 16 | this.updatePackageByStats(pkg, stats); 17 | 18 | return pkg; 19 | } 20 | } 21 | 22 | module.exports = GithubFetcher; 23 | -------------------------------------------------------------------------------- /src/package/fetchers/HttpFetcher.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const Fetcher = require('./Fetcher'); 4 | 5 | class HttpFetcher extends Fetcher { 6 | /** @type {TarballReader} */ 7 | tarballReader; 8 | 9 | /** 10 | * @param {object} options 11 | * @param {TarballReader} options.tarballReader 12 | */ 13 | constructor({ tarballReader }) { 14 | super(); 15 | this.tarballReader = tarballReader; 16 | } 17 | 18 | /** 19 | * @param {Package} pkg 20 | * @param {DependencySpec} dependencySpec 21 | * @return {Promise} 22 | */ 23 | async fetch(pkg, { versionSpec }) { 24 | const stats = await this.tarballReader.readUrl(versionSpec); 25 | this.updatePackageByStats(pkg, stats); 26 | return pkg; 27 | } 28 | 29 | /** 30 | * @param {Package} pkg 31 | * @param {TarballStat} stats 32 | * @protected 33 | */ 34 | updatePackageByStats(pkg, stats) { 35 | const packageJson = stats.packageJson; 36 | 37 | if (!pkg.name) { 38 | pkg.name = packageJson.name; 39 | } 40 | 41 | pkg.version = packageJson.version; 42 | pkg.dependencies = this.extractDependencies(packageJson); 43 | pkg.stats = stats; 44 | pkg.requirements = this.extractRequirements(packageJson); 45 | } 46 | } 47 | 48 | module.exports = HttpFetcher; 49 | -------------------------------------------------------------------------------- /src/package/fetchers/NpmFetcher.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const { 4 | filterReleases, 5 | getLatestVersion, 6 | } = require('../../utils/spec'); 7 | const Fetcher = require('./Fetcher'); 8 | 9 | const META_FIELDS = [ 10 | 'author', 11 | 'deprecated', 12 | 'description', 13 | 'license', 14 | 'maintainers', 15 | ]; 16 | 17 | class NpmFetcher extends Fetcher { 18 | /** @type {HttpClient} */ 19 | #httpClient; 20 | 21 | /** @type {string} */ 22 | #registryUrl; 23 | 24 | /** @type {TarballReader} */ 25 | #tarballReader; 26 | 27 | /** 28 | * @param {object} options 29 | * @param {HttpClient} options.httpClient 30 | * @param {string} options.registryUrl 31 | * @param {TarballReader} options.tarballReader 32 | */ 33 | constructor({ 34 | httpClient, 35 | registryUrl = 'https://registry.npmjs.org/', 36 | tarballReader, 37 | }) { 38 | super(); 39 | this.#httpClient = httpClient; 40 | this.#registryUrl = registryUrl; 41 | this.#tarballReader = tarballReader; 42 | } 43 | 44 | /** 45 | * @param {Package} pkg 46 | * @param {DependencySpec} dependencySpec 47 | * @return {Promise} 48 | */ 49 | async fetch(pkg, { escapedName }) { 50 | const packageMetaUrl = this.#registryUrl + escapedName; 51 | let packageFullMeta; 52 | 53 | try { 54 | packageFullMeta = await this.#httpClient.get(packageMetaUrl); 55 | } catch (error) { 56 | if (error.response?.status === 404) { 57 | pkg.setError({ 58 | error, 59 | reason: 'not-found', 60 | message: 'Package not found', 61 | }); 62 | 63 | return pkg; 64 | } 65 | 66 | throw error; 67 | } 68 | 69 | pkg.version = this.extractVersion(pkg, packageFullMeta); 70 | 71 | const packageJson = packageFullMeta.versions[pkg.version]; 72 | 73 | pkg.dependencies = this.extractDependencies(packageJson); 74 | pkg.stats = this.extractStats(packageJson); 75 | pkg.requirements = this.extractRequirements(packageJson); 76 | pkg.packageJson = packageJson; 77 | 78 | for (const f of META_FIELDS) { 79 | const value = JSON.stringify(packageJson[f] || packageFullMeta[f]); 80 | pkg.fields[f] = (value || '').replace(/"/g, ''); 81 | } 82 | 83 | if (!pkg.hasStats()) { 84 | pkg.stats = await this.fetchStats(packageJson.dist.tarball); 85 | } 86 | 87 | return pkg; 88 | } 89 | 90 | /** 91 | * @param {string} url 92 | * @return {Promise} 93 | * @protected 94 | */ 95 | async fetchStats(url) { 96 | if (url) { 97 | return this.#tarballReader.readUrl(url); 98 | } 99 | 100 | return { fileCount: 0, unpackedSize: 0 }; 101 | } 102 | 103 | /** 104 | * @param {object} packageJson 105 | * @param {object} packageJson.dist 106 | * @param {string} packageJson.dist.tarball 107 | * @param {number} packageJson.dist.fileCount 108 | * @param {number} packageJson.dist.unpackedSize 109 | * @return {Stats} 110 | * @private 111 | */ 112 | extractStats(packageJson) { 113 | let stats = { 114 | fileCount: -1, 115 | unpackedSize: -1, 116 | }; 117 | 118 | const dist = packageJson.dist || {}; 119 | 120 | if (dist.fileCount !== undefined && dist.unpackedSize !== undefined) { 121 | stats = { 122 | fileCount: dist.fileCount, 123 | unpackedSize: dist.unpackedSize, 124 | }; 125 | } 126 | 127 | return stats; 128 | } 129 | 130 | /** 131 | * Return the exact version based on versionSpec and available versions 132 | * @param {Package} pkg 133 | * @param {any} packageMeta 134 | * @param {any} packageMeta.versions 135 | * @param {any} packageMeta['dist-tags'] 136 | * @return {string} 137 | * @private 138 | */ 139 | extractVersion(pkg, packageMeta) { 140 | let versionSpec = pkg.versionSpec || '*'; 141 | if (versionSpec === 'latest') { 142 | versionSpec = '*'; 143 | } 144 | 145 | const availableVersions = Object.keys(packageMeta.versions || {}); 146 | let version = getLatestVersion(availableVersions, versionSpec); 147 | if (version) { 148 | return version; 149 | } 150 | 151 | const hasReleases = filterReleases(availableVersions).length > 0; 152 | 153 | if (versionSpec === '*' && !hasReleases && packageMeta['dist-tags']) { 154 | version = packageMeta['dist-tags'] && packageMeta['dist-tags'].latest; 155 | } 156 | 157 | if (version) { 158 | return version; 159 | } 160 | 161 | throw Error(`Could not find a satisfactory version for ${pkg}`); 162 | } 163 | } 164 | 165 | module.exports = NpmFetcher; 166 | -------------------------------------------------------------------------------- /src/package/fetchers/__specs__/DirectoryFetcher.spec.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const { describe, expect, it } = require('humile'); 4 | const path = require('path'); 5 | const { loadProject } = require('../../../__specs__'); 6 | 7 | describe('DirectoryFetcher', () => { 8 | it('should resolve local dependencies correctly', async () => { 9 | const graph = await loadProject('local-dependencies'); 10 | 11 | const project = graph.getFixtureRoot(); 12 | const firstLocal = graph.getNode(['local1']); 13 | 14 | expect(firstLocal.name).toBe('local1'); 15 | expect(firstLocal.spec.versionSpec) 16 | .toBe(path.join(project.spec.versionSpec, '../local1')); 17 | 18 | expect(project.getStatsRecursive()).toEqual({ 19 | dependencyCount: 2, 20 | fileCount: 6, 21 | unpackedSize: 429, 22 | }); 23 | }); 24 | }); 25 | -------------------------------------------------------------------------------- /src/reporters/BaseReporter.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const { getFieldNameByAlias } = require('./helpers'); 4 | 5 | class BaseReporter { 6 | /** @type {Required & { sortAsc: boolean }} */ 7 | options; 8 | 9 | /** 10 | * @param {ReporterOptions} options 11 | */ 12 | constructor(options) { 13 | this.options = this.normalizeOptions(options); 14 | this.sortDependencies = this.sortDependencies.bind(this); 15 | } 16 | 17 | /** 18 | * @param {Dependency} _dependency 19 | * @abstract 20 | */ 21 | print(_dependency) { 22 | // Not implemented 23 | } 24 | 25 | /** 26 | * @param {ReporterOptions} options 27 | * @return {Required & { sortAsc: boolean }}} 28 | */ 29 | normalizeOptions(options) { 30 | const sort = options.sort || 'name'; 31 | 32 | const fields = (options.fields || 'dependencies,size,files,license') 33 | .split(',') 34 | .map((f) => f?.trim()) 35 | .filter(Boolean) 36 | .map((f) => getFieldNameByAlias(f)); 37 | 38 | return { 39 | fields, 40 | printer: options.printer, 41 | shortSize: options.shortSize !== false, 42 | sort: getFieldNameByAlias(sort.replace(/[-+]$/, '')), 43 | sortDesc: sort.endsWith('-'), 44 | space: options.space, 45 | useColors: options.useColors, 46 | }; 47 | } 48 | 49 | /** 50 | * @param {object} a 51 | * @param {object} b 52 | */ 53 | sortDependencies(a, b) { 54 | const sort = this.options.sort; 55 | 56 | const aVal = a[sort]; 57 | const bVal = b[sort]; 58 | 59 | // eslint-disable-next-line no-restricted-globals 60 | const isNumeric = !isNaN(aVal) && !isNaN(bVal); 61 | 62 | let diff = 0; 63 | if (isNumeric) { 64 | diff = aVal - bVal; 65 | } if (typeof aVal === 'string' && typeof bVal === 'string') { 66 | diff = aVal.localeCompare(bVal); 67 | } 68 | 69 | return this.options.sortDesc ? -diff : diff; 70 | } 71 | } 72 | 73 | module.exports = BaseReporter; 74 | -------------------------------------------------------------------------------- /src/reporters/Json.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const BaseReporter = require('./BaseReporter'); 4 | 5 | class Json extends BaseReporter { 6 | /** 7 | * @param {Dependency} dependency 8 | */ 9 | print(dependency) { 10 | if (dependency.isReal()) { 11 | this.printJson(this.serializeDependency(dependency)); 12 | return; 13 | } 14 | 15 | const children = dependency.children; 16 | if (children.length === 1) { 17 | this.printJson(this.serializeDependency(children[0])); 18 | return; 19 | } 20 | 21 | this.printJson( 22 | children.map((c) => this.serializeDependency(c)), 23 | ); 24 | } 25 | 26 | /** 27 | * @param {object} data 28 | * @private 29 | */ 30 | printJson(data) { 31 | this.options.printer(JSON.stringify(data, null, this.options.space)); 32 | } 33 | 34 | /** 35 | * @param {Dependency} dependency 36 | * @return {object} 37 | * @private 38 | */ 39 | serializeDependency(dependency) { 40 | const stats = dependency.getStatsRecursive(); 41 | const label = dependency.getLabel(); 42 | const error = dependency.getError(); 43 | 44 | return { 45 | package: dependency.toString(), 46 | dependencyCount: stats.dependencyCount, 47 | fileCount: stats.fileCount, 48 | unpackedSize: stats.unpackedSize, 49 | duplicate: label === 'duplicate', 50 | error: error.reason !== 'none' && error.message, 51 | unmet: label === 'unmet', 52 | ...dependency.getFields(), 53 | ownStats: dependency.getStats(), 54 | children: dependency.children.map( 55 | (child) => this.serializeDependency(child), 56 | ), 57 | }; 58 | } 59 | } 60 | 61 | module.exports = Json; 62 | -------------------------------------------------------------------------------- /src/reporters/Simple.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const { formatSize } = require('./helpers'); 4 | const BaseReporter = require('./BaseReporter'); 5 | 6 | class Simple extends BaseReporter { 7 | /** 8 | * @param {Dependency} dependency 9 | */ 10 | print(dependency) { 11 | const stats = dependency.getStatsRecursive(); 12 | 13 | let size = stats.unpackedSize; 14 | if (this.options.shortSize) { 15 | size = formatSize(size); 16 | } 17 | 18 | this.options.printer('Dependencies:', stats.dependencyCount); 19 | this.options.printer('Size:', size); 20 | this.options.printer('Files:', stats.fileCount); 21 | } 22 | } 23 | 24 | module.exports = Simple; 25 | -------------------------------------------------------------------------------- /src/reporters/Table.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const BaseReporter = require('./BaseReporter'); 4 | const { colorGray, formatSize, formatStats } = require('./helpers'); 5 | 6 | const COLUMNS = [ 7 | { id: 'name', title: 'Name' }, 8 | { 9 | id: 'dependencyCount', 10 | alias: 'dependencies', 11 | title: 'Dependencies', 12 | alignRight: true, 13 | }, 14 | { id: 'unpackedSize', alias: 'size', title: 'Size', alignRight: true }, 15 | { id: 'fileCount', alias: 'files', title: 'Files', alignRight: true }, 16 | ]; 17 | 18 | class Table extends BaseReporter { 19 | /** 20 | * @param {Dependency} dependency 21 | */ 22 | print(dependency) { 23 | if (dependency.isReal()) { 24 | this.draw(dependency); 25 | return; 26 | } 27 | 28 | dependency.children.forEach((dep) => { 29 | this.draw(dep); 30 | }); 31 | } 32 | 33 | /** 34 | * @param {Dependency} dependency 35 | * @private 36 | */ 37 | draw(dependency) { 38 | this.options.printer( 39 | dependency.toString() + formatStats(dependency, this.options), 40 | ); 41 | 42 | const rows = dependency.children 43 | .map((child) => { 44 | return { 45 | ...child.getFields(), 46 | ...child.getStatsRecursive(), 47 | name: child.toString(), 48 | }; 49 | }) 50 | .sort(this.sortDependencies) 51 | .map((row) => { 52 | if (this.options.shortSize) { 53 | row.unpackedSize = formatSize(row.unpackedSize); 54 | } 55 | return row; 56 | }); 57 | 58 | if (rows.length < 1) { 59 | this.options.printer(colorGray('no dependencies', this.colors)); 60 | return; 61 | } 62 | 63 | const columnNames = [...this.options.fields]; 64 | if (!columnNames.includes('name')) { 65 | columnNames.unshift('name'); 66 | } 67 | 68 | const columns = columnNames.map((name) => { 69 | const column = COLUMNS.find((c) => c.id === name); 70 | return column || { 71 | id: name, 72 | title: name[0].toUpperCase() + name.slice(1), 73 | }; 74 | }); 75 | 76 | printTable(columns, rows, this.options.printer); 77 | } 78 | } 79 | 80 | module.exports = Table; 81 | 82 | function printTable(columns, rows, printer) { 83 | const chars = { 84 | first: { first: '╭', middle: '┬', last: '╮' }, 85 | middle: { first: '├', middle: '┼', last: '┤' }, 86 | last: { first: '╰', middle: '┴', last: '╯' }, 87 | line: '─', 88 | separator: '│', 89 | }; 90 | 91 | columns = columns.map((column) => { 92 | return rows.reduce((c, row) => { 93 | c.size = Math.max(c.size, String(row[c.id]).length); 94 | return c; 95 | }, { ...column, size: column.title.length }); 96 | }); 97 | 98 | print(); 99 | 100 | function print() { 101 | const headerRow = columns.reduce((row, column) => { 102 | return { ...row, [column.id]: column.title }; 103 | }, {}); 104 | printRowLine(chars.first); 105 | printRow(headerRow); 106 | 107 | rows.forEach((row) => { 108 | printRowLine(chars.middle); 109 | printRow(row); 110 | }); 111 | 112 | printRowLine(chars.last); 113 | } 114 | 115 | function printRow(row) { 116 | const text = columns.reduce((line, column) => { 117 | let content = (row[column.id] || '').toString(); 118 | if (column.alignRight) { 119 | content = content.padStart(column.size); 120 | } else { 121 | content = content.padEnd(column.size); 122 | } 123 | 124 | return line + chars.separator + ' ' + content + ' '; 125 | }, ''); 126 | 127 | printer(text + chars.separator); 128 | } 129 | 130 | function printRowLine(charSet) { 131 | const text = columns.reduce((line, column, i) => { 132 | return line + (i === 0 ? charSet.first : charSet.middle) 133 | + chars.line.repeat(column.size + 2); 134 | }, ''); 135 | 136 | printer(text + charSet.last); 137 | } 138 | } 139 | -------------------------------------------------------------------------------- /src/reporters/Tree.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const BaseReporter = require('./BaseReporter'); 4 | const { formatStats } = require('./helpers'); 5 | 6 | const FIRST = 0; 7 | const NORMAL = 1; 8 | const LAST = 2; 9 | 10 | class Tree extends BaseReporter { 11 | /** 12 | * @param {Dependency} dependency 13 | */ 14 | print(dependency) { 15 | if (dependency.isReal()) { 16 | this.draw(dependency, '', FIRST); 17 | return; 18 | } 19 | 20 | dependency.children.sort(this.sortDependencies).forEach((dep, i, deps) => { 21 | this.draw(dep, '', FIRST); 22 | if (i < deps.length - 1) { 23 | this.options.printer(''); 24 | } 25 | }); 26 | } 27 | 28 | /** 29 | * @param {Dependency} dependency 30 | * @param {string} prefix 31 | * @param {number} state 32 | * @private 33 | */ 34 | draw(dependency, prefix, state) { 35 | const dependencies = dependency.children; 36 | 37 | const nameChar = dependencies.length > 0 ? '┬' : '─'; 38 | let selfPrefix = prefix + (state === LAST ? '╰─' : '├─') + nameChar + ' '; 39 | let childPrefix = prefix + (state === LAST ? ' ' : '│ '); 40 | 41 | if (state === FIRST) { 42 | selfPrefix = ''; 43 | childPrefix = ''; 44 | } 45 | 46 | this.options.printer( 47 | selfPrefix + dependency + formatStats(dependency, this.options), 48 | ); 49 | 50 | dependencies.sort(this.sortDependencies).forEach((dep, i, deps) => { 51 | this.draw(dep, childPrefix, i >= deps.length - 1 ? LAST : NORMAL); 52 | }); 53 | } 54 | 55 | /** 56 | * @param {Dependency} a 57 | * @param {Dependency} b 58 | */ 59 | sortDependencies(a, b) { 60 | const sort = this.options.sort; 61 | 62 | if (['dependencyCount', 'fileCount', 'unpackedSize'].includes(sort)) { 63 | return super.sortDependencies( 64 | a.getStatsRecursive(), 65 | b.getStatsRecursive(), 66 | ); 67 | } 68 | 69 | return super.sortDependencies(a, b); 70 | } 71 | } 72 | 73 | module.exports = Tree; 74 | -------------------------------------------------------------------------------- /src/reporters/__specs__/Default.spec.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const { describe, expect, it } = require('humile'); 4 | const Default = require('../Simple'); 5 | const { loadFixture } = require('../../__specs__'); 6 | 7 | describe('reporters/Default', () => { 8 | it('should print simple stats', async () => { 9 | const graph = await loadFixture('npm-package-arg'); 10 | const lines = []; 11 | const tree = new Default({ 12 | printer: (...text) => lines.push(text.join(' ')), 13 | useColors: false, 14 | }); 15 | 16 | tree.print(graph.getRoot()); 17 | 18 | expect(lines).toEqual([ 19 | 'Dependencies: 7', 20 | 'Size: 132.23kb', 21 | 'Files: 47', 22 | ]); 23 | }); 24 | }); 25 | -------------------------------------------------------------------------------- /src/reporters/__specs__/Json.spec.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /* eslint-disable max-len */ 4 | 5 | const { describe, expect, it } = require('humile'); 6 | const Json = require('../Json'); 7 | const { loadFixture } = require('../../__specs__'); 8 | 9 | describe('reporters/Json', () => { 10 | it('should print a complete json', async () => { 11 | const graph = await loadFixture('npm-package-arg'); 12 | 13 | const jsonOutput = []; 14 | 15 | const json = new Json({ 16 | printer: (text) => jsonOutput.push(text), 17 | }); 18 | 19 | json.print(graph.getRoot()); 20 | 21 | const outputJson = JSON.parse(jsonOutput[0]); 22 | 23 | expect(outputJson).toEqual({ 24 | package: 'npm-package-arg@6.1.1', 25 | dependencyCount: 7, 26 | fileCount: 47, 27 | unpackedSize: 135406, 28 | duplicate: false, 29 | error: false, 30 | ownStats: { 31 | dependencyCount: 1, 32 | fileCount: 5, 33 | unpackedSize: 15759, 34 | }, 35 | unmet: false, 36 | author: '{name:Isaac Z. Schlueter,email:i@izs.me,url:http://blog.izs.me/}', 37 | deprecated: '', 38 | description: 'Parse the things that can be arguments to `npm install`', 39 | license: 'ISC', 40 | maintainers: '[{email:evilpacket@gmail.com,name:adam_baldwin},{email:ahmad@ahmadnassri.com,name:ahmadnassri},{email:anne@npmjs.com,name:annekimsey},{email:billatnpm@gmail.com,name:billatnpm},{email:cghr1990@gmail.com,name:claudiahdz},{email:darcy@darcyclarke.me,name:darcyclarke},{email:i@izs.me,name:isaacs}]', 41 | children: [ 42 | { 43 | package: 'hosted-git-info@2.8.5', 44 | dependencyCount: 0, 45 | fileCount: 7, 46 | unpackedSize: 23278, 47 | duplicate: false, 48 | error: false, 49 | ownStats: { 50 | dependencyCount: 1, 51 | fileCount: 7, 52 | unpackedSize: 23278, 53 | }, 54 | unmet: false, 55 | author: '{name:Rebecca Turner,email:me@re-becca.org,url:http://re-becca.org}', 56 | deprecated: '', 57 | description: 'Provides metadata and conversions from repository urls for Github, Bitbucket and Gitlab', 58 | license: 'ISC', 59 | maintainers: '[{email:evilpacket@gmail.com,name:adam_baldwin},{email:ahmad@ahmadnassri.com,name:ahmadnassri},{email:anne@npmjs.com,name:annekimsey},{email:billatnpm@gmail.com,name:billatnpm},{email:cghr1990@gmail.com,name:claudiahdz},{email:darcy@darcyclarke.me,name:darcyclarke},{email:i@izs.me,name:isaacs},{email:mike@mikecorp.ca,name:mikemimik},{email:ruyadorno@hotmail.com,name:ruyadorno}]', 60 | children: [], 61 | }, 62 | { 63 | package: 'osenv@0.1.5', 64 | dependencyCount: 2, 65 | fileCount: 12, 66 | unpackedSize: 11097, 67 | duplicate: false, 68 | error: false, 69 | ownStats: { 70 | dependencyCount: 1, 71 | fileCount: 4, 72 | unpackedSize: 4889, 73 | }, 74 | unmet: false, 75 | author: '{name:Isaac Z. Schlueter,email:i@izs.me,url:http://blog.izs.me/}', 76 | deprecated: '', 77 | description: 'Look up environment settings specific to different operating systems', 78 | license: 'ISC', 79 | maintainers: '[{email:me@re-becca.org,name:iarna},{email:i@izs.me,name:isaacs},{email:ogd@aoaioxxysz.net,name:othiym23},{email:rok@kowalski.gd,name:robertkowalski}]', 80 | children: [ 81 | { 82 | package: 'os-homedir@1.0.2', 83 | dependencyCount: 0, 84 | fileCount: 4, 85 | unpackedSize: 3152, 86 | duplicate: false, 87 | error: false, 88 | ownStats: { 89 | dependencyCount: 1, 90 | fileCount: 4, 91 | unpackedSize: 3152, 92 | }, 93 | unmet: false, 94 | node: '>=0.10.0', 95 | author: '{name:Sindre Sorhus,email:sindresorhus@gmail.com,url:sindresorhus.com}', 96 | deprecated: '', 97 | description: 'Node.js 4 `os.homedir()` ponyfill', 98 | license: 'MIT', 99 | maintainers: '[{name:sindresorhus,email:sindresorhus@gmail.com}]', 100 | children: [], 101 | }, 102 | { 103 | package: 'os-tmpdir@1.0.2', 104 | dependencyCount: 0, 105 | fileCount: 4, 106 | unpackedSize: 3056, 107 | duplicate: false, 108 | error: false, 109 | ownStats: { 110 | dependencyCount: 1, 111 | fileCount: 4, 112 | unpackedSize: 3056, 113 | }, 114 | unmet: false, 115 | node: '>=0.10.0', 116 | author: '{name:Sindre Sorhus,email:sindresorhus@gmail.com,url:sindresorhus.com}', 117 | deprecated: '', 118 | description: 'Node.js os.tmpdir() ponyfill', 119 | license: 'MIT', 120 | maintainers: '[{name:sindresorhus,email:sindresorhus@gmail.com}]', 121 | children: [], 122 | }, 123 | ], 124 | }, 125 | { 126 | package: 'semver@5.7.1', 127 | dependencyCount: 0, 128 | fileCount: 7, 129 | unpackedSize: 61578, 130 | duplicate: false, 131 | error: false, 132 | ownStats: { 133 | dependencyCount: 1, 134 | fileCount: 7, 135 | unpackedSize: 61578, 136 | }, 137 | unmet: false, 138 | author: '', 139 | deprecated: '', 140 | description: 'The semantic version parser used by npm.', 141 | license: 'ISC', 142 | maintainers: '[{email:evilpacket@gmail.com,name:adam_baldwin},{email:ahmad@ahmadnassri.com,name:ahmadnassri},{email:anne@npmjs.com,name:annekimsey},{email:cghr1990@gmail.com,name:claudiahdz},{email:darcy@darcyclarke.me,name:darcyclarke},{email:i@izs.me,name:isaacs}]', 143 | children: [], 144 | }, 145 | { 146 | package: 'validate-npm-package-name@3.0.0', 147 | dependencyCount: 1, 148 | fileCount: 16, 149 | unpackedSize: 23694, 150 | duplicate: false, 151 | error: false, 152 | ownStats: { 153 | dependencyCount: 1, 154 | fileCount: 9, 155 | unpackedSize: 20998, 156 | }, 157 | unmet: false, 158 | author: '{name:zeke}', 159 | deprecated: '', 160 | description: "Give me a string and I'll tell you if it's a valid npm package name", 161 | license: 'ISC', 162 | maintainers: '[{name:chrisdickinson,email:chris@neversaw.us}]', 163 | children: [ 164 | { 165 | package: 'builtins@1.0.3', 166 | dependencyCount: 0, 167 | fileCount: 7, 168 | unpackedSize: 2696, 169 | duplicate: false, 170 | error: false, 171 | ownStats: { 172 | dependencyCount: 1, 173 | fileCount: 7, 174 | unpackedSize: 2696, 175 | }, 176 | unmet: false, 177 | author: '', 178 | deprecated: '', 179 | description: 'List of node.js builtin modules', 180 | license: 'MIT', 181 | maintainers: '[{name:juliangruber,email:julian@juliangruber.com},{name:segment,email:tj@segment.io}]', 182 | children: [], 183 | }, 184 | ], 185 | }, 186 | ], 187 | }); 188 | }); 189 | }); 190 | -------------------------------------------------------------------------------- /src/reporters/__specs__/Tree.spec.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const { describe, expect, it } = require('humile'); 4 | const Tree = require('../Tree'); 5 | const { loadFixture } = require('../../__specs__'); 6 | 7 | describe('reporters/Tree', () => { 8 | it('should print a simple tree', async () => { 9 | const graph = await loadFixture('npm-package-arg'); 10 | 11 | const lines = []; 12 | 13 | const tree = new Tree({ 14 | printer: (text) => lines.push(text), 15 | useColors: false, 16 | shortSize: true, 17 | }); 18 | tree.print(graph.getRoot()); 19 | 20 | expect(lines).toEqual([ 21 | 'npm-package-arg@6.1.1 (7 deps, 132.23kb, 47 files, ©ISC)', 22 | '├── hosted-git-info@2.8.5 (22.73kb, 7 files, ©ISC)', 23 | '├─┬ osenv@0.1.5 (2 deps, 10.84kb, 12 files, ©ISC)', 24 | '│ ├── os-homedir@1.0.2 (3.08kb, 4 files, ©MIT)', 25 | '│ ╰── os-tmpdir@1.0.2 (2.98kb, 4 files, ©MIT)', 26 | '├── semver@5.7.1 (60.13kb, 7 files, ©ISC)', 27 | '╰─┬ validate-npm-package-name@3.0.0 (1 dep, 23.14kb, 16 files, ©ISC)', 28 | ' ╰── builtins@1.0.3 (2.63kb, 7 files, ©MIT)', 29 | ]); 30 | }); 31 | }); 32 | -------------------------------------------------------------------------------- /src/reporters/__specs__/helpers.spec.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const { describe, expect, it } = require('humile'); 4 | const helpers = require('../helpers'); 5 | 6 | describe('utils/helpers', () => { 7 | it('formatSize', () => { 8 | expect(helpers.formatSize(250)).toBe('250b'); 9 | expect(helpers.formatSize(1024)).toBe('1kb'); 10 | expect(helpers.formatSize(1500)).toBe('1.46kb'); 11 | expect(helpers.formatSize(1500000)).toBe('1.43mb'); 12 | }); 13 | }); 14 | -------------------------------------------------------------------------------- /src/reporters/helpers.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = { 4 | colorGray, 5 | formatSize, 6 | formatStats, 7 | 8 | getFieldNameByAlias(alias) { 9 | return FIELD_ALIASES[alias] || alias; 10 | }, 11 | }; 12 | 13 | const FIELD_ALIASES = { 14 | dependencies: 'dependencyCount', 15 | files: 'fileCount', 16 | size: 'unpackedSize', 17 | }; 18 | 19 | const LABEL_MAP = { 20 | duplicate: '🔗', 21 | unmet: 'UNMET', 22 | }; 23 | 24 | const COLORS = { 25 | gray: '\x1b[90m', 26 | red: '\x1b[31m', 27 | reset: '\x1b[0m', 28 | }; 29 | 30 | function formatSize(bytes) { 31 | if (bytes === 0) return '0b'; 32 | const e = Math.floor(Math.log(bytes) / Math.log(1024)); 33 | // eslint-disable-next-line no-restricted-properties 34 | return +(bytes / (1024 ** e)).toFixed(2) 35 | + 'bkmgtp'.charAt(e).replace('b', '') + 'b'; 36 | } 37 | 38 | /** 39 | * @param {Dependency} dependency 40 | * @param {Required} options 41 | * @return {string} 42 | */ 43 | function formatStats(dependency, options) { 44 | const stats = dependency.getStatsRecursive(); 45 | const results = []; 46 | 47 | const error = dependency.getError(); 48 | if (error.reason !== 'none') { 49 | results.push(colorRed(error.message, options.useColors)); 50 | } 51 | 52 | const label = dependency.getLabel(); 53 | if (label && LABEL_MAP[label]) { 54 | results.push(LABEL_MAP[label]); 55 | } 56 | 57 | options.fields 58 | .forEach((field) => { 59 | switch (field) { 60 | case 'dependencyCount': { 61 | if (stats.dependencyCount > 0) { 62 | const count = stats.dependencyCount; 63 | results.push(`${count} dep${count === 1 ? '' : 's'}`); 64 | } 65 | break; 66 | } 67 | 68 | case 'deprec': { 69 | const deprecated = dependency.getField('deprecated'); 70 | if (deprecated) { 71 | let short = deprecated.slice(0, 35); 72 | if (short.length < deprecated.length) { 73 | short += '…'; 74 | } 75 | results.push( 76 | colorRed(`⛔ ${short}`, options.useColors), 77 | ); 78 | } 79 | break; 80 | } 81 | 82 | case 'deprecated': { 83 | const deprecated = dependency.getField('deprecated'); 84 | if (deprecated) { 85 | results.push( 86 | colorRed(`Deprecated: ${deprecated}`, options.useColors), 87 | ); 88 | } 89 | break; 90 | } 91 | 92 | case 'fileCount': { 93 | if (stats.fileCount > 0) { 94 | const count = stats.fileCount; 95 | results.push(`${count} file${count === 1 ? '' : 's'}`); 96 | } 97 | break; 98 | } 99 | 100 | case 'license': { 101 | if (dependency.getField('license') !== 'Unknown') { 102 | results.push(`©${dependency.getField('license')}`); 103 | } 104 | break; 105 | } 106 | 107 | case 'native': { 108 | if (isNativeDependency(dependency.getPackageJson())) { 109 | results.push(colorRed('NATIVE', options.useColors)); 110 | } 111 | break; 112 | } 113 | 114 | case 'unpackedSize': { 115 | if (stats.unpackedSize > 0) { 116 | if (options.shortSize) { 117 | results.push(formatSize(stats.unpackedSize)); 118 | } else { 119 | results.push(stats.unpackedSize); 120 | } 121 | } 122 | break; 123 | } 124 | 125 | default: { 126 | results.push(dependency.getField(field)); 127 | } 128 | } 129 | }); 130 | 131 | if (results.length < 1) { 132 | return ''; 133 | } 134 | 135 | const resultsString = results 136 | .map((f) => f?.toString().trim()) 137 | .filter(Boolean) 138 | .join(', '); 139 | 140 | return colorGray(` (${resultsString})`, options.useColors); 141 | } 142 | 143 | function colorGray(text, useColors) { 144 | if (!useColors) { 145 | return text; 146 | } 147 | 148 | return COLORS.gray + text + COLORS.reset; 149 | } 150 | 151 | function colorRed(text, useColors, endColor = COLORS.gray) { 152 | if (!useColors) { 153 | return text; 154 | } 155 | 156 | return COLORS.red + text + endColor; 157 | } 158 | 159 | /** 160 | * Return true if the package looks like a native dependency. 161 | * 162 | * Based on is-native-module and @electron/rebuild 163 | * 164 | * @param {object} packageJson 165 | * @return {boolean} 166 | */ 167 | function isNativeDependency(packageJson) { 168 | if (typeof packageJson !== 'object' || !packageJson) { 169 | return false; 170 | } 171 | 172 | const { dependencies = {}, devDependencies = {} } = packageJson; 173 | 174 | const checks = [ 175 | dependencies.bindings, 176 | dependencies.nan, 177 | dependencies['node-gyp-build'], 178 | dependencies['node-pre-gyp'], 179 | dependencies.prebuild, 180 | dependencies['prebuild-install'], 181 | 182 | devDependencies.prebuildify, 183 | 184 | packageJson.binary, 185 | packageJson.gypfile, 186 | ]; 187 | 188 | return checks.some(Boolean); 189 | } 190 | -------------------------------------------------------------------------------- /src/reporters/index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const Simple = require('./Simple'); 4 | const Table = require('./Table'); 5 | const Tree = require('./Tree'); 6 | const Json = require('./Json'); 7 | 8 | module.exports = { 9 | createReporter, 10 | }; 11 | 12 | /** 13 | * @param {ReporterOptions} options 14 | * @return {Simple} 15 | */ 16 | function createReporter(options = {}) { 17 | // eslint-disable-next-line no-console 18 | options = { printer: console.info, ...options }; 19 | 20 | switch (options.name) { 21 | case 'json': return new Json(options); 22 | case 'simple': return new Simple(options); 23 | case 'table': return new Table(options); 24 | default: return new Tree(options); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/resolver/DependencyCache.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const { getLatestVersion } = require('../utils/spec'); 4 | 5 | class DependencyCache { 6 | constructor() { 7 | /** 8 | * @type {Object} 9 | */ 10 | this.cache = {}; 11 | } 12 | 13 | /** 14 | * @param {RealDependency} dependency 15 | */ 16 | add(dependency) { 17 | const name = dependency.spec.name; 18 | if (!name) { 19 | return; 20 | } 21 | 22 | this.cache[name] = this.cache[name] || []; 23 | this.cache[name].push(dependency); 24 | } 25 | 26 | /** 27 | * @param {DependencySpec} dependencySpec 28 | * @return {RealDependency} 29 | */ 30 | async find(dependencySpec) { 31 | const similar = this.cache[dependencySpec.name]; 32 | if (!similar) { 33 | return null; 34 | } 35 | 36 | const pkg = this.findSatisfyingPackage(similar, dependencySpec.versionSpec); 37 | if (pkg) { 38 | return pkg; 39 | } 40 | 41 | await this.waitForPendingDependencies(similar); 42 | 43 | return this.findSatisfyingPackage(similar, dependencySpec.versionSpec); 44 | } 45 | 46 | /** 47 | * @param {RealDependency} newDep 48 | * @param {RealDependency} oldDep 49 | */ 50 | replace(newDep, oldDep) { 51 | const similar = this.cache[oldDep.spec.name]; 52 | if (!similar) { 53 | return; 54 | } 55 | 56 | const index = similar.indexOf(oldDep); 57 | if (index < 0) { 58 | return; 59 | } 60 | 61 | similar[index] = newDep; 62 | } 63 | 64 | /** 65 | * @param {RealDependency[]} dependencies 66 | * @param {string} versionSpec 67 | * @return {?RealDependency} 68 | * @private 69 | */ 70 | findSatisfyingPackage(dependencies, versionSpec) { 71 | const versions = dependencies.map((dep) => dep.version).filter(Boolean); 72 | const maxVersion = getLatestVersion(versions, versionSpec); 73 | return dependencies.find((pkg) => pkg.version === maxVersion); 74 | } 75 | 76 | /** 77 | * @param {RealDependency[]} dependencies 78 | * @return {Promise} 79 | */ 80 | async waitForPendingDependencies(dependencies) { 81 | const pending = dependencies 82 | .filter((dep) => !dep.version) 83 | .map((pkg) => pkg.waitForResolve()); 84 | 85 | return Promise.all(pending); 86 | } 87 | } 88 | 89 | module.exports = DependencyCache; 90 | -------------------------------------------------------------------------------- /src/resolver/DependencyResolver.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const { isRightPlatform } = require('../utils/spec'); 4 | const graph = require('../dependency/graph'); 5 | 6 | class DependencyResolver { 7 | /** @type {PackageFactory} */ 8 | packageFactory; 9 | 10 | /** @type {DependencyFactory} */ 11 | dependencyFactory; 12 | 13 | /** @type {DependencyTypeFilter} */ 14 | typeFilter; 15 | 16 | /** @type {DependencyCache} */ 17 | cache; 18 | 19 | /** 20 | * @param {PackageFactory} packageFactory 21 | * @param {DependencyFactory} dependencyFactory 22 | * @param {DependencyTypeFilter} typeFilter 23 | * @param {DependencyCache} cache 24 | */ 25 | constructor(packageFactory, dependencyFactory, typeFilter, cache) { 26 | this.packageFactory = packageFactory; 27 | this.dependencyFactory = dependencyFactory; 28 | this.typeFilter = typeFilter; 29 | this.cache = cache; 30 | 31 | this.fetchDependency = this.fetchDependency.bind(this); 32 | } 33 | 34 | /** 35 | * @param {Dependency} dependency 36 | * @return {Promise} 37 | */ 38 | async resolve(dependency) { 39 | return graph.mapAsync(dependency, this.fetchDependency); 40 | } 41 | 42 | /** 43 | * @param {(Dependency | RealDependency)} dependency 44 | * @return {Promise} 45 | * @private 46 | */ 47 | async fetchDependency(dependency) { 48 | if (!dependency.isReal()) { 49 | return dependency; 50 | } 51 | 52 | const cached = await this.findOrRegisterInCache(dependency); 53 | if (cached) { 54 | return this.dependencyFactory.createDuplicate(cached, dependency); 55 | } 56 | 57 | const pkg = await this.packageFactory.create(dependency.spec); 58 | dependency.setPackage(pkg); 59 | 60 | if (!isRightPlatform(pkg.requirements)) { 61 | const unmet = this.dependencyFactory.createUnmet(dependency); 62 | this.cache.replace(dependency, unmet); 63 | return unmet; 64 | } 65 | 66 | const children = this.dependencyFactory.createDependenciesOfPackage( 67 | pkg, 68 | { 69 | dev: this.typeFilter.dev && dependency.canIncludeDevDependencies(), 70 | peer: this.typeFilter.peer, 71 | }, 72 | ); 73 | dependency.loadChildren(children); 74 | 75 | return dependency; 76 | } 77 | 78 | /** 79 | * @param {RealDependency} dependency 80 | * @return {Promise<(RealDependency | null)>} 81 | * @private 82 | */ 83 | async findOrRegisterInCache(dependency) { 84 | const cached = await this.cache.find(dependency.spec); 85 | 86 | if (!cached) { 87 | this.cache.add(dependency); 88 | } 89 | 90 | return cached; 91 | } 92 | } 93 | 94 | module.exports = DependencyResolver; 95 | -------------------------------------------------------------------------------- /src/resolver/__specs__/DependencyCache.spec.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const { describe, expect, it } = require('humile'); 4 | const { createDependency } = require('../../__specs__'); 5 | const DependencyCache = require('../DependencyCache'); 6 | 7 | describe('DependencyCache', () => { 8 | it('stores and provides existed deps with specified version', async () => { 9 | const cache = new DependencyCache(); 10 | 11 | const a1 = createDependency('a', '1.0.0'); 12 | const a2 = createDependency('a', '2.0.0'); 13 | 14 | cache.add(a1); 15 | cache.add(a2); 16 | 17 | expect(await cache.find({ name: 'a', versionSpec: '*' })).toBe(a2); 18 | expect(await cache.find({ name: 'a', versionSpec: '^1.0.0' })).toBe(a1); 19 | expect(await cache.find({ name: 'a', versionSpec: '^2.0.0' })).toBe(a2); 20 | expect(await cache.find({ name: 'a', versionSpec: '^3.0.0' })).toBeFalsy(); 21 | expect(await cache.find({ name: 'b', versionSpec: '*' })).toBeFalsy(); 22 | }); 23 | }); 24 | -------------------------------------------------------------------------------- /src/resolver/__specs__/DependencyResolver.spec.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const { describe, expect, it } = require('humile'); 4 | const { loadFixture } = require('../../__specs__'); 5 | const DuplicateDependency = require('../../dependency/DuplicateDependency'); 6 | 7 | describe('DependencyResolver', () => { 8 | it('parses @webassemblyjs/helper-code-frame correctly', async () => { 9 | const graph = await loadFixture('@webassemblyjs/helper-code-frame'); 10 | 11 | expect(graph.getRoot().getStatsRecursive()).toEqual({ 12 | dependencyCount: 10, 13 | unpackedSize: 691071, 14 | fileCount: 96, 15 | }); 16 | }); 17 | 18 | it('should detect duplicated dependencies', async () => { 19 | const graph = await loadFixture('@webassemblyjs/helper-code-frame'); 20 | 21 | const node = graph.getNode([ 22 | '@webassemblyjs/wast-printer', '@webassemblyjs/wast-parser', '@xtuc/long', 23 | ]); 24 | 25 | expect(node.constructor).toBe(DuplicateDependency); 26 | }); 27 | 28 | it('should detect unmet dependencies', async () => { 29 | const graph = await loadFixture('lighter-run'); 30 | 31 | const children = graph.getFixtureRoot().children; 32 | 33 | expect(children.length).toEqual(2); 34 | }); 35 | }); 36 | -------------------------------------------------------------------------------- /src/resolver/index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const DependencyCache = require('./DependencyCache'); 4 | const DependencyResolver = require('./DependencyResolver'); 5 | const { createDependencyFactory } = require('../dependency'); 6 | 7 | module.exports = { 8 | createDependencyResolver, 9 | }; 10 | 11 | /** 12 | * @param {PackageFactory} packageFactory 13 | * @param {DependencyFactory} dependencyFactory 14 | * @param {DependencyTypeFilter} typeFilter 15 | * @return {DependencyResolver} 16 | */ 17 | function createDependencyResolver( 18 | packageFactory, 19 | dependencyFactory = createDependencyFactory(), 20 | typeFilter = {}, 21 | ) { 22 | return new DependencyResolver( 23 | packageFactory, 24 | dependencyFactory, 25 | typeFilter, 26 | new DependencyCache(), 27 | ); 28 | } 29 | -------------------------------------------------------------------------------- /src/utils/__specs__/spec.spec.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const { describe, expect, it } = require('humile'); 4 | const dependencySpec = require('../spec'); 5 | 6 | describe('utils/spec', () => { 7 | it('filterReleases', () => { 8 | const versions = [ 9 | '1.0.0', 10 | '0.2.3', 11 | '1.2.3-beta', 12 | ]; 13 | 14 | expect(dependencySpec.filterReleases(versions)).toEqual([ 15 | '1.0.0', 16 | '0.2.3', 17 | ]); 18 | }); 19 | 20 | it('getLatestVersion', () => { 21 | const versions = [ 22 | '1.0.2', 23 | '0.2.3', 24 | '1.2.3-beta', 25 | ]; 26 | 27 | expect(dependencySpec.getLatestVersion(versions, '^1.0.0')) 28 | .toEqual('1.0.2'); 29 | }); 30 | 31 | describe('isRightPlatform', () => { 32 | it('should return true if in list', () => { 33 | const spec = { platform: ['linux'] }; 34 | const actual = { platform: 'linux', arch: 'x64' }; 35 | 36 | expect(dependencySpec.isRightPlatform(spec, actual)).toBe(true); 37 | }); 38 | 39 | it('should return true if not in exceptions', () => { 40 | const spec = { platform: ['!win32'] }; 41 | const actual = { platform: 'linux', arch: 'x64' }; 42 | 43 | expect(dependencySpec.isRightPlatform(spec, actual)).toBe(true); 44 | }); 45 | 46 | it('should return false if not in list', () => { 47 | const spec = { platform: ['linux'] }; 48 | const actual = { platform: 'win32', arch: 'x64' }; 49 | 50 | expect(dependencySpec.isRightPlatform(spec, actual)).toBe(false); 51 | }); 52 | 53 | it('should return false if in exceptions', () => { 54 | const spec = { platform: ['!linux'] }; 55 | const actual = { platform: 'linux', arch: 'x64' }; 56 | 57 | expect(dependencySpec.isRightPlatform(spec, actual)).toBe(false); 58 | }); 59 | }); 60 | 61 | describe('parseSpec', () => { 62 | it('should parse local deps with absolute path', () => { 63 | expect(dependencySpec.parseSpec('test', '/packages', '/root')).toEqual({ 64 | escapedName: 'test', 65 | name: 'test', 66 | source: 'directory', 67 | versionSpec: '/packages', 68 | }); 69 | }); 70 | 71 | it('should parse local deps with relative path', () => { 72 | expect(dependencySpec.parseSpec('test', '..', '/root')).toEqual({ 73 | escapedName: 'test', 74 | name: 'test', 75 | source: 'directory', 76 | versionSpec: '/', 77 | }); 78 | }); 79 | 80 | it('should parse local deps with relative path and file prefix', () => { 81 | expect(dependencySpec.parseSpec('test', 'file:..', '/root/1')).toEqual({ 82 | escapedName: 'test', 83 | name: 'test', 84 | source: 'directory', 85 | versionSpec: '/root', 86 | }); 87 | }); 88 | 89 | it('should parse NPM dependency', () => { 90 | expect(dependencySpec.parseSpec('test', '^1.0.0')).toEqual({ 91 | escapedName: 'test', 92 | name: 'test', 93 | source: 'npm', 94 | versionSpec: '^1.0.0', 95 | }); 96 | }); 97 | 98 | it('should parse github dependency', () => { 99 | expect(dependencySpec.parseSpec('test', 'test/repo')).toEqual({ 100 | escapedName: 'test/repo', 101 | name: 'test', 102 | source: 'github', 103 | versionSpec: 'master', 104 | }); 105 | }); 106 | 107 | it('should parse git dependency', () => { 108 | expect(dependencySpec.parseSpec('test', 'git+ssh://git@pkg.it:pkg.git')) 109 | .toEqual({ 110 | escapedName: 'git@pkg.it:pkg.git', 111 | name: 'test', 112 | source: 'git', 113 | versionSpec: null, 114 | }); 115 | }); 116 | 117 | it('should parse http dependency', () => { 118 | expect(dependencySpec.parseSpec('test', 'http://pkg.it/1.tgz')).toEqual({ 119 | escapedName: 'test', 120 | name: 'test', 121 | source: 'http', 122 | versionSpec: 'http://pkg.it/1.tgz', 123 | }); 124 | }); 125 | }); 126 | }); 127 | -------------------------------------------------------------------------------- /src/utils/config.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const options = require('package-options'); 4 | 5 | module.exports = { 6 | getConfig, 7 | }; 8 | 9 | /** 10 | * @return {Config} 11 | */ 12 | function getConfig() { 13 | options.help(` 14 | Usage: howfat [package1] [packageN] [OPTIONS] 15 | Options: 16 | -d, --dev-dependencies BOOLEAN Fetch dev dependencies, default false 17 | -p, --peer-dependencies BOOLEAN Fetch peer dependencies, default false 18 | 19 | -r, --reporter STRING 'default', 'table', 'tree' 20 | --fields STRING Displayed fields separated by a comma: 21 | dependencies,size,files,license, 22 | author,description,maintainers,deprec, 23 | deprecated,native,node,os,platform 24 | --sort STRING Sort field. Add minus sign for 25 | desc order, like size-. Default to 'name' 26 | --space NUMBER Use spaces in json output, default null 27 | 28 | -v, --verbose BOOLEAN Show additional logs 29 | --no-colors BOOLEAN Prevent color output 30 | --no-human-readable BOOLEAN Show size in bytes 31 | 32 | --registry-url STRING Default to https://registry.npmjs.org/ 33 | 34 | --http Node.js RequestOptions, like: 35 | --http.timeout NUMBER Request timeout in ms, default 10000 36 | --http.connection-limit NUMBER Max simultaneous connections, default 10 37 | --http.retry-count NUMBER Try to fetch again of failure, default 5 38 | --http.proxy STRING A proxy server url 39 | 40 | --show-config Show the current configuration 41 | --version Show howfat version 42 | --help Show this help 43 | `); 44 | 45 | return new Config(options); 46 | } 47 | 48 | class Config { 49 | /** 50 | * @param {PackageOptions} opts 51 | */ 52 | constructor(opts) { 53 | this.dependencyTypeFilter = { 54 | dev: opts.devDependencies === true, 55 | peer: opts.peerDependencies === true, 56 | }; 57 | 58 | const http = opts.http || {}; 59 | this.httpOptions = { 60 | ...http, 61 | connectionLimit: http.connectionLimit || 10, 62 | timeout: http.timeout || 10000, 63 | proxy: http.proxy || '', 64 | retryCount: http.retryCount || 5, 65 | }; 66 | 67 | /** 68 | * @type {ReporterOptions} 69 | */ 70 | this.reporterOptions = { 71 | name: opts.reporter || 'tree', 72 | fields: opts.fields || 'dependencies,size,files,native,license,deprec', 73 | shortSize: opts.humanReadable !== false, 74 | sort: opts.sort || 'name', 75 | space: opts.space || null, 76 | useColors: typeof opts.colors === 'boolean' 77 | ? opts.colors 78 | : process.stdout.isTTY, 79 | }; 80 | 81 | this.registryUrl = opts.registryUrl || 'https://registry.npmjs.org/'; 82 | 83 | this.isVerbose = opts.verbose || false; 84 | 85 | this.fetchedPackages = opts._; 86 | this.projectPath = opts._.length < 1 ? opts.getProjectPath() : undefined; 87 | 88 | this.helpText = opts.getHelpText(); 89 | 90 | if (opts.showConfig) { 91 | console.info(this.toJSON()); // eslint-disable-line no-console 92 | process.exit(); 93 | } 94 | } 95 | 96 | toJSON() { 97 | const config = { ...this }; 98 | delete config.helpText; 99 | return config; 100 | } 101 | } 102 | -------------------------------------------------------------------------------- /src/utils/http/HttpClient.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const { default: Axios } = require('axios'); 4 | const { EventEmitter } = require('events'); 5 | const axiosCachePlugin = require('./interceptors/axiosCachePlugin'); 6 | const axiosQueuePlugin = require('./interceptors/axiosQueuePlugin'); 7 | const axiosRetryPlugin = require('./interceptors/axiosRetryPlugin'); 8 | 9 | /** 10 | * @typedef {import('axios').AxiosRequestConfig} AxiosRequestConfig 11 | */ 12 | 13 | class HttpClient extends EventEmitter { 14 | /** @type {Axios} */ 15 | #axios; 16 | 17 | /** @type {AxiosRequestConfig & { connectionLimit?, retryCount? }} */ 18 | #config; 19 | 20 | /** 21 | * @param {AxiosRequestConfig & { connectionLimit?, retryCount? }} config 22 | */ 23 | constructor(config = {}) { 24 | super(); 25 | 26 | this.#axios = Axios.create(); 27 | axiosCachePlugin(this.#axios); 28 | axiosQueuePlugin(this.#axios); 29 | axiosRetryPlugin(this.#axios); 30 | 31 | if (typeof config.proxy === 'string' && !process.env.HTTPS_PROXY) { 32 | process.env.HTTPS_PROXY = config.proxy; 33 | } 34 | 35 | this.#config = { 36 | connectionLimit: config.connectionLimit || 10, 37 | retryCount: config.retryCount || 5, 38 | timeout: 10_000, 39 | ...config, 40 | 41 | onRetry: ({ error, url }) => this.emit('error', { error, url }), 42 | 43 | onProcessQueue: ({ activeSize, finishedSize, queueSize }) => { 44 | this.emit('progress', { 45 | finishedCount: finishedSize, 46 | queuedCount: queueSize + activeSize, 47 | }); 48 | }, 49 | 50 | onStartFetching: ({ url }) => this.emit('start', { url }), 51 | }; 52 | } 53 | 54 | /** 55 | * @param {string} url 56 | * @param {AxiosRequestConfig} config 57 | * @return {object} 58 | */ 59 | async get(url, config = {}) { 60 | const response = await this.request({ method: 'GET', url, ...config }); 61 | return response.data; 62 | } 63 | 64 | /** 65 | * @param {string} url 66 | * @param {AxiosRequestConfig} config 67 | * @return {Promise} 68 | */ 69 | async getStream(url, config = {}) { 70 | const response = await this.request({ 71 | method: 'GET', 72 | responseType: 'stream', 73 | url, 74 | ...config, 75 | }); 76 | return response.data; 77 | } 78 | 79 | /** 80 | * @param {AxiosRequestConfig} config 81 | * @return {Promise<{ headers: object, data: any, status: number }>} 82 | */ 83 | async request(config) { 84 | const mergedConfig = { 85 | ...this.#config, 86 | ...config, 87 | }; 88 | 89 | try { 90 | const response = await this.#axios.request(mergedConfig); 91 | 92 | this.emit('finish', { 93 | response: response.data, 94 | status: response.status, 95 | url: config.url, 96 | }); 97 | 98 | return { 99 | data: response.data, 100 | headers: response.headers, 101 | status: response.status, 102 | }; 103 | } catch (error) { 104 | this.emit('error', { 105 | error, 106 | url: config.url, 107 | }); 108 | throw error; 109 | } 110 | } 111 | } 112 | 113 | module.exports = HttpClient; 114 | -------------------------------------------------------------------------------- /src/utils/http/interceptors/axiosCachePlugin.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = axiosCachePlugin; 4 | 5 | function axiosCachePlugin(axios) { 6 | const cache = {}; 7 | 8 | axios.interceptors.request.use((config) => { 9 | if (!cache[config.url] || config.responseType === 'stream') { 10 | return config; 11 | } 12 | 13 | return { 14 | ...config, 15 | adapter(cfg) { 16 | cfg.onCached?.({ url: cfg.url }); 17 | return Promise.resolve(cache[cfg.url]); 18 | }, 19 | }; 20 | }); 21 | 22 | axios.interceptors.response.use((response) => { 23 | if (response.config?.responseType !== 'stream') { 24 | cache[response.config.url] = response; 25 | } 26 | 27 | return response; 28 | }); 29 | } 30 | -------------------------------------------------------------------------------- /src/utils/http/interceptors/axiosQueuePlugin.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = axiosQueuePlugin; 4 | 5 | function axiosQueuePlugin(axios) { 6 | const queue = []; 7 | let activeSize = 0; 8 | let finishedSize = 0; 9 | 10 | axios.interceptors.request.use((config) => { 11 | return new Promise((resolve) => { 12 | queue.push(() => { 13 | activeSize += 1; 14 | resolve(config); 15 | }); 16 | processQueue(config); 17 | }); 18 | }); 19 | 20 | axios.interceptors.response.use( 21 | (response) => { 22 | onTaskFinished(response.config); 23 | return Promise.resolve(response); 24 | }, 25 | (error) => { 26 | onTaskFinished(error.config); 27 | return Promise.reject(error); 28 | }, 29 | ); 30 | 31 | function onTaskFinished(config) { 32 | activeSize -= 1; 33 | finishedSize += 1; 34 | processQueue(config); 35 | } 36 | 37 | function processQueue(config) { 38 | const connectionLimit = config?.connectionLimit || 10; 39 | if (connectionLimit > activeSize) { 40 | queue.shift()?.(); 41 | config?.onStartFetching?.(config); 42 | } 43 | 44 | config?.onProcessQueue?.({ 45 | activeSize, 46 | connectionLimit, 47 | finishedSize, 48 | queueSize: queue.length, 49 | }); 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /src/utils/http/interceptors/axiosRetryPlugin.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = axiosRetryPlugin; 4 | 5 | function axiosRetryPlugin(axios) { 6 | axios.interceptors.response.use(null, async (error) => { 7 | const config = error.config; 8 | 9 | if (!config) { 10 | throw error; 11 | } 12 | 13 | const retryCount = config.retryCount || 0; 14 | const attemptMade = config.attemptMade || 0; 15 | 16 | const shouldRetry = attemptMade < retryCount 17 | && (!error.response || error.response.code >= 500); 18 | 19 | if (shouldRetry) { 20 | config.attemptMade = attemptMade + 1; 21 | config.transformRequest = [(data) => data]; 22 | 23 | config.onRetry?.({ 24 | code: error.code, 25 | error, 26 | message: error.message, 27 | responseCode: error.response?.code, 28 | url: config.url, 29 | }); 30 | 31 | return new Promise((resolve) => { 32 | setTimeout( 33 | () => { 34 | resolve(axios(config)); 35 | config?.onStartFetching?.(config); 36 | }, 37 | exponentialDelay(config.attemptMade), 38 | ); 39 | }); 40 | } 41 | 42 | throw error; 43 | }); 44 | } 45 | 46 | function exponentialDelay(retryNumber = 0, startDelay = 200) { 47 | const delay = 2 ** retryNumber * startDelay; 48 | const randomSum = delay * 0.2 * Math.random(); // 0-20% of the delay 49 | return delay + randomSum; 50 | } 51 | -------------------------------------------------------------------------------- /src/utils/http/progress/ProgressIndicator.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | class ProgressIndicator { 4 | /** 5 | * @param {HttpClient} httpClient 6 | * @param {NodeJS.WriteStream} stream 7 | */ 8 | constructor(httpClient, stream = process.stderr) { 9 | this.httpClient = httpClient; 10 | this.stream = stream; 11 | 12 | if (!this.isDisabled()) { 13 | this.httpClient.on('start', this.onStartDownloading.bind(this)); 14 | this.httpClient.on('progress', this.onProgress.bind(this)); 15 | } 16 | 17 | this.onInit(); 18 | } 19 | 20 | /** 21 | * @final 22 | */ 23 | finish() { 24 | if (!this.isDisabled()) { 25 | this.onFinish(); 26 | } 27 | } 28 | 29 | isDisabled() { 30 | return false; 31 | } 32 | 33 | /** 34 | * @protected 35 | */ 36 | onInit() { 37 | } 38 | 39 | /** 40 | * @param {HttpTask} _task 41 | * @protected 42 | */ 43 | onStartDownloading(_task) { 44 | } 45 | 46 | /** 47 | * @param {HttpProgressEvent} _progress 48 | * @protected 49 | */ 50 | onProgress(_progress) { 51 | } 52 | 53 | /** 54 | * @protected 55 | */ 56 | onFinish() { 57 | } 58 | } 59 | 60 | module.exports = ProgressIndicator; 61 | -------------------------------------------------------------------------------- /src/utils/http/progress/StatProgressIndicator.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const ProgressIndicator = require('./ProgressIndicator'); 4 | 5 | class StatProgressIndicator extends ProgressIndicator { 6 | isDisabled() { 7 | return !this.stream.isTTY; 8 | } 9 | 10 | onInit() { 11 | if (this.isDisabled()) { 12 | this.stream.write('Fetching dependencies...\n'); 13 | } 14 | } 15 | 16 | /** 17 | * @param {HttpProgressEvent} progress 18 | * @protected 19 | */ 20 | onProgress(progress) { 21 | const total = progress.finishedCount + progress.queuedCount; 22 | const text = `Fetching dependencies... Processed: ${total}`; 23 | 24 | this.stream.cursorTo(0); 25 | this.stream.clearLine(0); 26 | this.stream.write(text); 27 | this.stream.cursorTo(0); 28 | } 29 | 30 | onFinish() { 31 | this.stream.cursorTo(0); 32 | this.stream.clearLine(0); 33 | } 34 | } 35 | 36 | module.exports = StatProgressIndicator; 37 | -------------------------------------------------------------------------------- /src/utils/http/progress/UrlProgressIndicator.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const ProgressIndicator = require('./ProgressIndicator'); 4 | 5 | class UrlProgressIndicator extends ProgressIndicator { 6 | /** 7 | * @param {HttpTask} task 8 | */ 9 | onStartDownloading(task) { 10 | this.stream.write('get ' + task.url + '\n'); 11 | } 12 | } 13 | 14 | module.exports = UrlProgressIndicator; 15 | -------------------------------------------------------------------------------- /src/utils/http/progress/index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const StatProgressIndicator = require('./StatProgressIndicator'); 4 | const UrlProgressIndicator = require('./UrlProgressIndicator'); 5 | const ProgressIndicator = require('./UrlProgressIndicator'); 6 | 7 | module.exports = { 8 | createProgressIndicator, 9 | }; 10 | 11 | /** 12 | * 13 | * @param {HttpClient} httpClient 14 | * @param {ProgressIndicatorType} type 15 | * @param {NodeJS.WriteStream} stream 16 | * @return {ProgressIndicator} 17 | */ 18 | function createProgressIndicator( 19 | httpClient, 20 | type = 'stat', 21 | stream = process.stderr, 22 | ) { 23 | switch (type) { 24 | case 'stat': return new StatProgressIndicator(httpClient, stream); 25 | case 'url': return new UrlProgressIndicator(httpClient, stream); 26 | default: return new ProgressIndicator(httpClient, stream); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/utils/index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const { getConfig } = require('./config'); 4 | const { createProgressIndicator } = require('./http/progress'); 5 | 6 | module.exports = { 7 | createProgressIndicator, 8 | getConfig, 9 | }; 10 | -------------------------------------------------------------------------------- /src/utils/spec.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const npa = require('npm-package-arg'); 4 | const semver = require('semver'); 5 | 6 | module.exports = { 7 | filterReleases, 8 | getLatestVersion, 9 | isRightPlatform, 10 | parseSpec, 11 | }; 12 | 13 | /** 14 | * @param {string[]} availableVersions 15 | * @return {string[]} 16 | */ 17 | function filterReleases(availableVersions) { 18 | return availableVersions.filter((v) => !semver.prerelease(v)); 19 | } 20 | 21 | /** 22 | * @param {string[]} availableVersions 23 | * @param {string} versionSpec 24 | * @return {string} 25 | */ 26 | function getLatestVersion(availableVersions, versionSpec) { 27 | return semver.maxSatisfying(availableVersions, versionSpec); 28 | } 29 | 30 | /** 31 | * @param {{ platform?: string[], arch?: string[] }} platformSpec 32 | * @param {{ platform: string, arch: string }} actual 33 | * @return {boolean} 34 | */ 35 | function isRightPlatform(platformSpec, actual = process) { 36 | return isValueMatchList(actual.platform, platformSpec.platform) 37 | && isValueMatchList(actual.arch, platformSpec.arch); 38 | } 39 | 40 | /** 41 | * @param {string} value 42 | * @param {string[]}list 43 | * @return {boolean} 44 | */ 45 | function isValueMatchList(value, list) { 46 | let match = false; 47 | let negotiationCount = 0; 48 | 49 | if (!list) { 50 | return true; 51 | } 52 | 53 | list = Array.isArray(list) ? list : [list]; 54 | 55 | if (list.length === 1 && list[0] === 'any') { 56 | return true; 57 | } 58 | 59 | for (let item of list) { 60 | if (item[0] === '!') { 61 | item = item.slice(1); 62 | if (item === value) { 63 | return false; 64 | } 65 | 66 | negotiationCount++; 67 | } else { 68 | match = match || item === value; 69 | } 70 | } 71 | 72 | return match || negotiationCount === list.length; 73 | } 74 | 75 | /** 76 | * 77 | * @param {string} name 78 | * @param {string} versionSpec 79 | * @param {string} localPath 80 | * @return {DependencySpec} 81 | */ 82 | function parseSpec(name, versionSpec = undefined, localPath = undefined) { 83 | let meta; 84 | if (versionSpec) { 85 | meta = npa.resolve(name, versionSpec, localPath); 86 | } else { 87 | meta = npa(name, localPath); 88 | } 89 | 90 | name = meta.name; 91 | 92 | let source; 93 | versionSpec = meta.fetchSpec; 94 | let escapedName = meta.escapedName; 95 | 96 | switch (meta.type) { 97 | case 'directory': { 98 | source = 'directory'; 99 | break; 100 | } 101 | 102 | case 'remote': { 103 | source = 'http'; 104 | break; 105 | } 106 | 107 | case 'git': { 108 | if (meta.hosted && meta.hosted.type === 'github') { 109 | const { user, project, committish } = meta.hosted; 110 | source = 'github'; 111 | name = name.includes('/') ? project : name; 112 | escapedName = `${user}/${project}`; 113 | versionSpec = committish || 'master'; 114 | } else { 115 | source = 'git'; 116 | escapedName = meta.fetchSpec; 117 | versionSpec = meta.gitCommittish; 118 | } 119 | 120 | break; 121 | } 122 | 123 | default: { 124 | source = 'npm'; 125 | } 126 | } 127 | 128 | return { 129 | escapedName, 130 | name, 131 | source, 132 | versionSpec, 133 | }; 134 | } 135 | -------------------------------------------------------------------------------- /src/utils/tarball/TarballReader.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const tar = require('tar'); 4 | const TarballStat = require('./TarballStat'); 5 | 6 | class TarballReader { 7 | /** @type {HttpClient} */ 8 | #httpClient; 9 | 10 | /** 11 | * @param {object} options 12 | * @param {HttpClient} options.httpClient 13 | */ 14 | constructor({ httpClient }) { 15 | this.#httpClient = httpClient; 16 | } 17 | 18 | /** 19 | * Read gzipped tar package stream and return its stats 20 | * @param {module:stream.internal.Readable} stream 21 | * @returns {Promise} 22 | */ 23 | async readStream(stream) { 24 | return new Promise((resolve, reject) => { 25 | const stat = new TarballStat(); 26 | 27 | const tarStream = stream.pipe(tar.t()); 28 | 29 | tarStream.on('entry', (entry) => { 30 | stat.fileCount += 1; 31 | stat.unpackedSize += entry.size; 32 | 33 | if (entry.path.match(/^[^/]+\/package.json$/)) { 34 | const chunks = []; 35 | entry.on('data', (chunk) => chunks.push(chunk)); 36 | entry.on('error', reject); 37 | entry.on('end', () => { 38 | try { 39 | const content = Buffer.concat(chunks).toString('utf8'); 40 | stat.packageJson = JSON.parse(content); 41 | } catch (e) { 42 | reject(e); 43 | } 44 | }); 45 | } 46 | }); 47 | 48 | tarStream.on('end', () => resolve(stat)); 49 | tarStream.on('error', reject); 50 | }); 51 | } 52 | 53 | /** 54 | * Read url tarball and return its stats 55 | * @param {string} url 56 | * @returns {Promise} 57 | */ 58 | async readUrl(url) { 59 | const stream = await this.#httpClient.getStream(url); 60 | return this.readStream(stream); 61 | } 62 | } 63 | 64 | module.exports = TarballReader; 65 | -------------------------------------------------------------------------------- /src/utils/tarball/TarballStat.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | class TarballStat { 4 | fileCount = 0; 5 | unpackedSize = 0; 6 | packageJson = {}; 7 | } 8 | 9 | module.exports = TarballStat; 10 | -------------------------------------------------------------------------------- /src/utils/tarball/__specs__/TarballReader.spec.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const { describe, expect, it } = require('humile'); 4 | 5 | const fs = require('fs'); 6 | const path = require('path'); 7 | const TarballReader = require('../TarballReader'); 8 | 9 | describe('TarballReader', () => { 10 | describe('readStream', () => { 11 | it('should load package information from tar gzip stream', async () => { 12 | const readStream = fs.createReadStream( 13 | path.join(__dirname, 'tarball.fixture.tgz'), 14 | ); 15 | const reader = new TarballReader({ httpClient: null }); 16 | 17 | const stats = await reader.readStream(readStream); 18 | 19 | expect(stats.fileCount).toBe(9); 20 | expect(stats.unpackedSize).toBe(15651); 21 | expect(stats.packageJson.name).toBe('electron-log'); 22 | }); 23 | }); 24 | }); 25 | -------------------------------------------------------------------------------- /src/utils/tarball/__specs__/tarball.fixture.tgz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/megahertz/howfat/c0d0dd7411de9b21de8034c58b2bcb1f162696ad/src/utils/tarball/__specs__/tarball.fixture.tgz --------------------------------------------------------------------------------