webpack-stats-diff
Advanced tools
Sorry, the diff of this file is not supported yet
| const { getStatsDiff } = require('../index'); | ||
| test('handles blank inputs', () => { | ||
| expect(getStatsDiff([], [])).toMatchSnapshot(); | ||
| }); | ||
| test('reports stat diffs between old and new assets', () => { | ||
| const oldAssets = [ | ||
| { name: 'commons.js', size: 32768 }, | ||
| { name: 'logo.svg', size: 588 }, | ||
| { name: 'me.jpg', size: 1200000 }, | ||
| { name: 'main-site.js', size: 68000 }, | ||
| { name: 'other-page.js', size: 40000 } | ||
| ]; | ||
| const newAssets = [ | ||
| { name: 'commons.js', size: 65536 }, | ||
| { name: 'Roboto-Regular.ttf', size: 176999 }, | ||
| { name: 'me.jpg', size: 1200000 }, | ||
| { name: 'main-site.js', size: 38000 }, | ||
| { name: 'other-page.js', size: 12345 } | ||
| ]; | ||
| expect(getStatsDiff(oldAssets, newAssets)).toMatchSnapshot(); | ||
| }); | ||
| test('filters assets by ext config', () => { | ||
| const oldAssets = [ | ||
| { name: 'commons.js', size: 32768 }, | ||
| { name: 'main-site.js', size: 68000 }, | ||
| { name: 'old-page.js', size: 8000 }, | ||
| { name: 'styles.css', size: 10000 }, | ||
| { name: 'logo.svg', size: 588 }, | ||
| { name: 'me.jpg', size: 1200000 } | ||
| ]; | ||
| const newAssets = [ | ||
| { name: 'commons.js', size: 65536 }, | ||
| { name: 'main-site.js', size: 38000 }, | ||
| { name: 'styles.css', size: 10000 }, | ||
| { name: 'me.jpg', size: 1200000 }, | ||
| { name: 'Roboto-Regular.ttf', size: 176999 } | ||
| ]; | ||
| expect( | ||
| getStatsDiff(oldAssets, newAssets, { extensions: ['.js', '.css'] }) | ||
| ).toMatchObject({ | ||
| added: [], | ||
| removed: [{ name: 'old-page.js' }], | ||
| bigger: [{ name: 'commons.js' }], | ||
| smaller: [{ name: 'main-site.js' }], | ||
| sameSize: [{ name: 'styles.css' }], | ||
| total: { name: 'Total' } | ||
| }); | ||
| }); | ||
| test('Sorts by greatest size differences first', () => { | ||
| const newAssets = [ | ||
| { name: 'commons.js', size: 65536 }, | ||
| { name: 'new-page.js', size: 8000 }, | ||
| { name: 'main-site.js', size: 38000 } | ||
| ]; | ||
| expect(getStatsDiff([], newAssets).added).toMatchObject([ | ||
| { name: 'commons.js' }, | ||
| { name: 'main-site.js' }, | ||
| { name: 'new-page.js' } | ||
| ]); | ||
| }); | ||
| describe('Marks an asset as sameSize if % change is below threshold', () => { | ||
| const oldAssets = [ | ||
| { name: 'big-file-small-change', size: 100000 }, | ||
| { name: 'big-file-biggest-change', size: 100000 }, | ||
| { name: 'big-file-no-change', size: 100000 }, | ||
| { name: 'small-file-small-change', size: 1000 }, | ||
| { name: 'small-file-big-change', size: 1000 } | ||
| ]; | ||
| const newAssets = [ | ||
| { name: 'big-file-small-change', size: 104000 }, | ||
| { name: 'big-file-biggest-change', size: 110001 }, | ||
| { name: 'big-file-no-change', size: 100000 }, | ||
| { name: 'small-file-small-change', size: 960 }, | ||
| { name: 'small-file-big-change', size: 949 } | ||
| ]; | ||
| test('Default threshold is 5%', () => { | ||
| expect(getStatsDiff(oldAssets, newAssets)).toMatchObject({ | ||
| bigger: [{ name: 'big-file-biggest-change' }], | ||
| smaller: [{ name: 'small-file-big-change' }], | ||
| sameSize: [ | ||
| { name: 'big-file-small-change' }, | ||
| { name: 'big-file-no-change' }, | ||
| { name: 'small-file-small-change' } | ||
| ] | ||
| }); | ||
| }); | ||
| test('Threshold can be adjusted by configuration', () => { | ||
| expect(getStatsDiff(oldAssets, newAssets, { threshold: 0 })).toMatchObject({ | ||
| bigger: [ | ||
| { name: 'big-file-biggest-change' }, | ||
| { name: 'big-file-small-change' } | ||
| ], | ||
| smaller: [ | ||
| { name: 'small-file-big-change' }, | ||
| { name: 'small-file-small-change' } | ||
| ], | ||
| sameSize: [{ name: 'big-file-no-change' }] | ||
| }); | ||
| expect(getStatsDiff(oldAssets, newAssets, { threshold: 10 })).toMatchObject( | ||
| { | ||
| bigger: [{ name: 'big-file-biggest-change' }], | ||
| smaller: [], | ||
| sameSize: [ | ||
| { name: 'big-file-small-change' }, | ||
| { name: 'big-file-no-change' }, | ||
| { name: 'small-file-small-change' }, | ||
| { name: 'small-file-big-change' } | ||
| ] | ||
| } | ||
| ); | ||
| }); | ||
| }); |
| const path = require('path'); | ||
| const DIFF_THRESHOLD = 5; | ||
| const filterByExtension = (assets, config) => { | ||
| return config.extensions | ||
| ? assets.filter(({ name }) => | ||
| config.extensions.includes(path.extname(name)) | ||
| ) | ||
| : assets; | ||
| }; | ||
| const indexByName = assets => { | ||
| return assets.reduce((assetsByName, asset) => { | ||
| assetsByName[asset.name] = asset; | ||
| return assetsByName; | ||
| }, {}); | ||
| }; | ||
| const diffDesc = (diff1, diff2) => Math.abs(diff2.diff) - Math.abs(diff1.diff); | ||
| const createDiff = (oldSize, newSize) => ({ | ||
| newSize, | ||
| oldSize, | ||
| diff: newSize - oldSize, | ||
| diffPercentage: +((1 - newSize / oldSize) * -100).toFixed(5) || 0 | ||
| }); | ||
| const webpackStatsDiff = (oldAssets, newAssets, config = {}) => { | ||
| const oldAssetsByName = indexByName(filterByExtension(oldAssets, config)); | ||
| const newAssetsByName = indexByName(filterByExtension(newAssets, config)); | ||
| const added = []; | ||
| const removed = []; | ||
| const bigger = []; | ||
| const smaller = []; | ||
| const sameSize = []; | ||
| let newSizeTotal = 0; | ||
| let oldSizeTotal = 0; | ||
| Object.keys(oldAssetsByName).forEach(name => { | ||
| const oldAsset = oldAssetsByName[name]; | ||
| oldSizeTotal += oldAsset.size; | ||
| if (!newAssetsByName[name]) { | ||
| removed.push(Object.assign({ name }, createDiff(oldAsset.size, 0))); | ||
| } else { | ||
| const diff = Object.assign( | ||
| { name }, | ||
| createDiff(oldAsset.size, newAssetsByName[name].size) | ||
| ); | ||
| const diffThreshold = config.hasOwnProperty('threshold') | ||
| ? config.threshold | ||
| : DIFF_THRESHOLD; | ||
| if (diff.diffPercentage > diffThreshold) { | ||
| bigger.push(diff); | ||
| } else if (diff.diffPercentage < -1 * diffThreshold) { | ||
| smaller.push(diff); | ||
| } else { | ||
| sameSize.push(diff); | ||
| } | ||
| } | ||
| }); | ||
| Object.keys(newAssetsByName).forEach(name => { | ||
| const newAsset = newAssetsByName[name]; | ||
| newSizeTotal += newAsset.size; | ||
| if (!oldAssetsByName[newAsset.name]) { | ||
| added.push(Object.assign({ name }, createDiff(0, newAsset.size))); | ||
| } | ||
| }); | ||
| return { | ||
| added: added.sort(diffDesc), | ||
| removed: removed.sort(diffDesc), | ||
| bigger: bigger.sort(diffDesc), | ||
| smaller: smaller.sort(diffDesc), | ||
| sameSize, | ||
| total: Object.assign( | ||
| { name: 'Total' }, | ||
| createDiff(oldSizeTotal, newSizeTotal) | ||
| ) | ||
| }; | ||
| }; | ||
| module.exports = webpackStatsDiff; |
| /* eslint-disable no-console */ | ||
| const { table, getBorderCharacters } = require('table'); | ||
| const chalk = require('chalk'); | ||
| const ASSET_TABLE_CONFIG = { | ||
| border: getBorderCharacters('void'), | ||
| columnDefault: { | ||
| alignment: 'right', | ||
| paddingLeft: 2, | ||
| paddingRight: 2 | ||
| }, | ||
| columns: { | ||
| 0: { alignment: 'left' } | ||
| }, | ||
| drawHorizontalLine: () => false | ||
| }; | ||
| const TABLE_HEADERS = [ | ||
| chalk.bold('Asset'), | ||
| chalk.bold('Old size'), | ||
| chalk.bold('New size'), | ||
| chalk.bold('Diff'), | ||
| chalk.bold('Diff %') | ||
| ]; | ||
| const capitalize = text => text[0].toUpperCase() + text.slice(1); | ||
| const getSizeText = size => { | ||
| if (size === 0) { | ||
| return '0'; | ||
| } | ||
| const abbreviations = ['bytes', 'KiB', 'MiB', 'GiB']; | ||
| const index = Math.floor(Math.log(Math.abs(size)) / Math.log(1024)); | ||
| return `${+(size / Math.pow(1024, index)).toPrecision(3)} ${ | ||
| abbreviations[index] | ||
| }`; | ||
| }; | ||
| const printAssetsTables = results => { | ||
| ['added', 'removed', 'bigger', 'smaller'].forEach(field => { | ||
| const assets = results[field]; | ||
| if (assets.length > 0) { | ||
| const sectionColor = ['added', 'bigger'].includes(field) | ||
| ? chalk.green.underline.bold | ||
| : chalk.red.underline.bold; | ||
| console.log(sectionColor(capitalize(field))); | ||
| if (['added', 'removed'].includes(field)) { | ||
| const tableData = [ | ||
| [chalk.bold('Asset'), chalk.bold('Diff')], | ||
| ...assets.map(asset => [asset.name, getSizeText(asset.diff)]) | ||
| ]; | ||
| console.log(table(tableData, ASSET_TABLE_CONFIG)); | ||
| } else { | ||
| const tableData = [ | ||
| TABLE_HEADERS, | ||
| ...assets.map(asset => [ | ||
| asset.name, | ||
| getSizeText(asset.oldSize), | ||
| getSizeText(asset.newSize), | ||
| getSizeText(asset.diff), | ||
| `${asset.diffPercentage} %` | ||
| ]) | ||
| ]; | ||
| console.log(table(tableData, ASSET_TABLE_CONFIG)); | ||
| } | ||
| } | ||
| }); | ||
| }; | ||
| const printTotalTable = total => { | ||
| const totalData = []; | ||
| totalData.push(['', ...TABLE_HEADERS.slice(1)]); | ||
| const diffColor = total.diff > 0 ? chalk.green.bold : chalk.red.bold; | ||
| totalData.push([ | ||
| total.name, | ||
| getSizeText(total.oldSize), | ||
| getSizeText(total.newSize), | ||
| diffColor(getSizeText(total.diff)), | ||
| diffColor(`${total.diffPercentage} %`) | ||
| ]); | ||
| console.log(table(totalData, { columnDefault: { alignment: 'right' } })); | ||
| }; | ||
| module.exports = statsDiff => { | ||
| printAssetsTables(statsDiff); | ||
| printTotalTable(statsDiff.total); | ||
| }; |
+1
-1
| { | ||
| "name": "webpack-stats-diff", | ||
| "version": "0.7.0", | ||
| "version": "1.0.0", | ||
| "description": "CLI tool to report changes in bundle sizes across builds", | ||
@@ -5,0 +5,0 @@ "bin": "src/cli.js", |
+2
-89
@@ -6,27 +6,5 @@ #!/usr/bin/env node | ||
| const fs = require('fs'); | ||
| const { table, getBorderCharacters } = require('table'); | ||
| const chalk = require('chalk'); | ||
| const webpackStatsDiff = require('./'); | ||
| const { getStatsDiff, printStatsDiff } = require('./'); | ||
| const ASSET_TABLE_CONFIG = { | ||
| border: getBorderCharacters('void'), | ||
| columnDefault: { | ||
| alignment: 'right', | ||
| paddingLeft: 2, | ||
| paddingRight: 2 | ||
| }, | ||
| columns: { | ||
| 0: { alignment: 'left' } | ||
| }, | ||
| drawHorizontalLine: () => false | ||
| }; | ||
| const TABLE_HEADERS = [ | ||
| chalk.bold('Asset'), | ||
| chalk.bold('Old size'), | ||
| chalk.bold('New size'), | ||
| chalk.bold('Diff'), | ||
| chalk.bold('Diff %') | ||
| ]; | ||
| const printError = text => { | ||
@@ -43,65 +21,2 @@ console.error(chalk.red(text)); | ||
| const getSizeText = size => { | ||
| if (size === 0) { | ||
| return '0'; | ||
| } | ||
| const abbreviations = ['bytes', 'KiB', 'MiB', 'GiB']; | ||
| const index = Math.floor(Math.log(Math.abs(size)) / Math.log(1024)); | ||
| return `${+(size / Math.pow(1024, index)).toPrecision(3)} ${ | ||
| abbreviations[index] | ||
| }`; | ||
| }; | ||
| const capitalize = text => text[0].toUpperCase() + text.slice(1); | ||
| const printAssetsTables = results => { | ||
| ['added', 'removed', 'bigger', 'smaller'].forEach(field => { | ||
| const assets = results[field]; | ||
| if (assets.length > 0) { | ||
| const sectionColor = ['added', 'bigger'].includes(field) | ||
| ? chalk.green.underline.bold | ||
| : chalk.red.underline.bold; | ||
| console.log(sectionColor(capitalize(field))); | ||
| if (['added', 'removed'].includes(field)) { | ||
| const tableData = [ | ||
| [chalk.bold('Asset'), chalk.bold('Diff')], | ||
| ...assets.map(asset => [asset.name, getSizeText(asset.diff)]) | ||
| ]; | ||
| console.log(table(tableData, ASSET_TABLE_CONFIG)); | ||
| } else { | ||
| const tableData = [ | ||
| TABLE_HEADERS, | ||
| ...assets.map(asset => [ | ||
| asset.name, | ||
| getSizeText(asset.oldSize), | ||
| getSizeText(asset.newSize), | ||
| getSizeText(asset.diff), | ||
| `${asset.diffPercentage} %` | ||
| ]) | ||
| ]; | ||
| console.log(table(tableData, ASSET_TABLE_CONFIG)); | ||
| } | ||
| } | ||
| }); | ||
| }; | ||
| const printTotalTable = total => { | ||
| const totalData = []; | ||
| totalData.push(['', ...TABLE_HEADERS.slice(1)]); | ||
| const diffColor = total.diff > 0 ? chalk.green.bold : chalk.red.bold; | ||
| totalData.push([ | ||
| total.name, | ||
| getSizeText(total.oldSize), | ||
| getSizeText(total.newSize), | ||
| diffColor(getSizeText(total.diff)), | ||
| diffColor(`${total.diffPercentage} %`) | ||
| ]); | ||
| console.log(table(totalData, { columnDefault: { alignment: 'right' } })); | ||
| }; | ||
| const formatExtensions = extensions => | ||
@@ -138,5 +53,3 @@ extensions.split(',').map(ext => (ext[0] === '.' ? ext : `.${ext}`)); | ||
| const results = webpackStatsDiff(oldAssets, newAssets, config); | ||
| printAssetsTables(results); | ||
| printTotalTable(results.total); | ||
| printStatsDiff(getStatsDiff(oldAssets, newAssets, config)); | ||
| }) | ||
@@ -143,0 +56,0 @@ .on('--help', () => { |
+5
-83
@@ -1,85 +0,7 @@ | ||
| const path = require('path'); | ||
| const getStatsDiff = require('./getStatsDiff'); | ||
| const printStatsDiff = require('./printStatsDiff'); | ||
| const DIFF_THRESHOLD = 5; | ||
| const filterByExtension = (assets, config) => { | ||
| return config.extensions | ||
| ? assets.filter(({ name }) => | ||
| config.extensions.includes(path.extname(name)) | ||
| ) | ||
| : assets; | ||
| module.exports = { | ||
| getStatsDiff, | ||
| printStatsDiff | ||
| }; | ||
| const indexByName = assets => { | ||
| return assets.reduce((assetsByName, asset) => { | ||
| assetsByName[asset.name] = asset; | ||
| return assetsByName; | ||
| }, {}); | ||
| }; | ||
| const diffDesc = (diff1, diff2) => Math.abs(diff2.diff) - Math.abs(diff1.diff); | ||
| const createDiff = (oldSize, newSize) => ({ | ||
| newSize, | ||
| oldSize, | ||
| diff: newSize - oldSize, | ||
| diffPercentage: +((1 - newSize / oldSize) * -100).toFixed(5) || 0 | ||
| }); | ||
| const webpackStatsDiff = (oldAssets, newAssets, config = {}) => { | ||
| const oldAssetsByName = indexByName(filterByExtension(oldAssets, config)); | ||
| const newAssetsByName = indexByName(filterByExtension(newAssets, config)); | ||
| const added = []; | ||
| const removed = []; | ||
| const bigger = []; | ||
| const smaller = []; | ||
| const sameSize = []; | ||
| let newSizeTotal = 0; | ||
| let oldSizeTotal = 0; | ||
| Object.keys(oldAssetsByName).forEach(name => { | ||
| const oldAsset = oldAssetsByName[name]; | ||
| oldSizeTotal += oldAsset.size; | ||
| if (!newAssetsByName[name]) { | ||
| removed.push(Object.assign({ name }, createDiff(oldAsset.size, 0))); | ||
| } else { | ||
| const diff = Object.assign( | ||
| { name }, | ||
| createDiff(oldAsset.size, newAssetsByName[name].size) | ||
| ); | ||
| const diffThreshold = config.hasOwnProperty('threshold') | ||
| ? config.threshold | ||
| : DIFF_THRESHOLD; | ||
| if (diff.diffPercentage > diffThreshold) { | ||
| bigger.push(diff); | ||
| } else if (diff.diffPercentage < -1 * diffThreshold) { | ||
| smaller.push(diff); | ||
| } else { | ||
| sameSize.push(diff); | ||
| } | ||
| } | ||
| }); | ||
| Object.keys(newAssetsByName).forEach(name => { | ||
| const newAsset = newAssetsByName[name]; | ||
| newSizeTotal += newAsset.size; | ||
| if (!oldAssetsByName[newAsset.name]) { | ||
| added.push(Object.assign({ name }, createDiff(0, newAsset.size))); | ||
| } | ||
| }); | ||
| return { | ||
| added: added.sort(diffDesc), | ||
| removed: removed.sort(diffDesc), | ||
| bigger: bigger.sort(diffDesc), | ||
| smaller: smaller.sort(diffDesc), | ||
| sameSize, | ||
| total: Object.assign( | ||
| { name: 'Total' }, | ||
| createDiff(oldSizeTotal, newSizeTotal) | ||
| ) | ||
| }; | ||
| }; | ||
| module.exports = webpackStatsDiff; |
Sorry, the diff of this file is not supported yet
| const webpackStatsDiff = require('../index'); | ||
| test('handles blank inputs', () => { | ||
| expect(webpackStatsDiff([], [])).toMatchSnapshot(); | ||
| }); | ||
| test('reports stat diffs between old and new assets', () => { | ||
| const oldAssets = [ | ||
| { name: 'commons.js', size: 32768 }, | ||
| { name: 'logo.svg', size: 588 }, | ||
| { name: 'me.jpg', size: 1200000 }, | ||
| { name: 'main-site.js', size: 68000 }, | ||
| { name: 'other-page.js', size: 40000 } | ||
| ]; | ||
| const newAssets = [ | ||
| { name: 'commons.js', size: 65536 }, | ||
| { name: 'Roboto-Regular.ttf', size: 176999 }, | ||
| { name: 'me.jpg', size: 1200000 }, | ||
| { name: 'main-site.js', size: 38000 }, | ||
| { name: 'other-page.js', size: 12345 } | ||
| ]; | ||
| expect(webpackStatsDiff(oldAssets, newAssets)).toMatchSnapshot(); | ||
| }); | ||
| test('filters assets by ext config', () => { | ||
| const oldAssets = [ | ||
| { name: 'commons.js', size: 32768 }, | ||
| { name: 'main-site.js', size: 68000 }, | ||
| { name: 'old-page.js', size: 8000 }, | ||
| { name: 'styles.css', size: 10000 }, | ||
| { name: 'logo.svg', size: 588 }, | ||
| { name: 'me.jpg', size: 1200000 } | ||
| ]; | ||
| const newAssets = [ | ||
| { name: 'commons.js', size: 65536 }, | ||
| { name: 'main-site.js', size: 38000 }, | ||
| { name: 'styles.css', size: 10000 }, | ||
| { name: 'me.jpg', size: 1200000 }, | ||
| { name: 'Roboto-Regular.ttf', size: 176999 } | ||
| ]; | ||
| expect( | ||
| webpackStatsDiff(oldAssets, newAssets, { extensions: ['.js', '.css'] }) | ||
| ).toMatchObject({ | ||
| added: [], | ||
| removed: [{ name: 'old-page.js' }], | ||
| bigger: [{ name: 'commons.js' }], | ||
| smaller: [{ name: 'main-site.js' }], | ||
| sameSize: [{ name: 'styles.css' }], | ||
| total: { name: 'Total' } | ||
| }); | ||
| }); | ||
| test('Sorts by greatest size differences first', () => { | ||
| const newAssets = [ | ||
| { name: 'commons.js', size: 65536 }, | ||
| { name: 'new-page.js', size: 8000 }, | ||
| { name: 'main-site.js', size: 38000 } | ||
| ]; | ||
| expect(webpackStatsDiff([], newAssets).added).toMatchObject([ | ||
| { name: 'commons.js' }, | ||
| { name: 'main-site.js' }, | ||
| { name: 'new-page.js' } | ||
| ]); | ||
| }); | ||
| describe('Marks an asset as sameSize if % change is below threshold', () => { | ||
| const oldAssets = [ | ||
| { name: 'big-file-small-change', size: 100000 }, | ||
| { name: 'big-file-biggest-change', size: 100000 }, | ||
| { name: 'big-file-no-change', size: 100000 }, | ||
| { name: 'small-file-small-change', size: 1000 }, | ||
| { name: 'small-file-big-change', size: 1000 } | ||
| ]; | ||
| const newAssets = [ | ||
| { name: 'big-file-small-change', size: 104000 }, | ||
| { name: 'big-file-biggest-change', size: 110001 }, | ||
| { name: 'big-file-no-change', size: 100000 }, | ||
| { name: 'small-file-small-change', size: 960 }, | ||
| { name: 'small-file-big-change', size: 949 } | ||
| ]; | ||
| test('Default threshold is 5%', () => { | ||
| expect(webpackStatsDiff(oldAssets, newAssets)).toMatchObject({ | ||
| bigger: [{ name: 'big-file-biggest-change' }], | ||
| smaller: [{ name: 'small-file-big-change' }], | ||
| sameSize: [ | ||
| { name: 'big-file-small-change' }, | ||
| { name: 'big-file-no-change' }, | ||
| { name: 'small-file-small-change' } | ||
| ] | ||
| }); | ||
| }); | ||
| test('Threshold can be adjusted by configuration', () => { | ||
| expect( | ||
| webpackStatsDiff(oldAssets, newAssets, { threshold: 0 }) | ||
| ).toMatchObject({ | ||
| bigger: [ | ||
| { name: 'big-file-biggest-change' }, | ||
| { name: 'big-file-small-change' } | ||
| ], | ||
| smaller: [ | ||
| { name: 'small-file-big-change' }, | ||
| { name: 'small-file-small-change' } | ||
| ], | ||
| sameSize: [{ name: 'big-file-no-change' }] | ||
| }); | ||
| expect( | ||
| webpackStatsDiff(oldAssets, newAssets, { threshold: 10 }) | ||
| ).toMatchObject({ | ||
| bigger: [{ name: 'big-file-biggest-change' }], | ||
| smaller: [], | ||
| sameSize: [ | ||
| { name: 'big-file-small-change' }, | ||
| { name: 'big-file-no-change' }, | ||
| { name: 'small-file-small-change' }, | ||
| { name: 'small-file-big-change' } | ||
| ] | ||
| }); | ||
| }); | ||
| }); |
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
Dynamic require
Supply chain riskDynamic require can indicate the package is performing dangerous or unsafe dynamic code execution.
Found 1 instance in 1 package
Filesystem access
Supply chain riskAccesses the file system, and could potentially read sensitive data.
Found 1 instance in 1 package
Dynamic require
Supply chain riskDynamic require can indicate the package is performing dangerous or unsafe dynamic code execution.
Found 1 instance in 1 package
Filesystem access
Supply chain riskAccesses the file system, and could potentially read sensitive data.
Found 1 instance in 1 package
No v1
QualityPackage is not semver >=1. This means it is not stable and does not support ^ ranges.
Found 1 instance in 1 package
252297
0.1%15
15.38%328
2.5%0
-100%1
Infinity%