pkg-stats 0.6.0 → 0.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,18 @@
1
+ import { expect, test } from 'vitest';
2
+ import { parseCliOptions } from '../cli-options.js';
3
+ test('parseCliOptions defaults to version sorting', () => {
4
+ const options = parseCliOptions(['node', 'pkg-stats', 'react', '--color', 'atlas']);
5
+ expect(options.sort).toBe('version');
6
+ });
7
+ test('parseCliOptions supports download sorting', () => {
8
+ const options = parseCliOptions([
9
+ 'node',
10
+ 'pkg-stats',
11
+ 'react',
12
+ '--sort',
13
+ 'downloads',
14
+ '--color',
15
+ 'atlas',
16
+ ]);
17
+ expect(options.sort).toBe('downloads');
18
+ });
@@ -0,0 +1,50 @@
1
+ import { expect, test } from 'vitest';
2
+ import { downloadCompare, filterStats } from '../stats.js';
3
+ const stats = [
4
+ {
5
+ version: { major: 3 },
6
+ versionString: '3.x',
7
+ downloads: 100,
8
+ },
9
+ {
10
+ version: { major: 2 },
11
+ versionString: '2.x',
12
+ downloads: 300,
13
+ },
14
+ {
15
+ version: { major: 1 },
16
+ versionString: '1.x',
17
+ downloads: 200,
18
+ },
19
+ ];
20
+ test('filterStats keeps top downloads in version order by default', () => {
21
+ expect(filterStats(stats, {
22
+ totalDownloads: 600,
23
+ top: 2,
24
+ }).map((stat) => stat.versionString)).toEqual(['2.x', '1.x']);
25
+ });
26
+ test('downloadCompare sorts by downloads descending', () => {
27
+ expect([...stats].sort(downloadCompare).map((stat) => stat.versionString)).toEqual([
28
+ '2.x',
29
+ '1.x',
30
+ '3.x',
31
+ ]);
32
+ });
33
+ test('downloadCompare falls back to version order for ties', () => {
34
+ const tiedStats = [
35
+ {
36
+ version: { major: 1 },
37
+ versionString: '1.x',
38
+ downloads: 100,
39
+ },
40
+ {
41
+ version: { major: 2 },
42
+ versionString: '2.x',
43
+ downloads: 100,
44
+ },
45
+ ];
46
+ expect([...tiedStats].sort(downloadCompare).map((stat) => stat.versionString)).toEqual([
47
+ '2.x',
48
+ '1.x',
49
+ ]);
50
+ });
@@ -12,6 +12,7 @@ Options:
12
12
  ${colorOption('--major')} Group by major version
13
13
  ${colorOption('--minor')} Group by minor version
14
14
  ${colorOption('--patch')} Group by patch version
15
+ ${colorOption('--sort')} <order> Sort results by version or downloads
15
16
  ${colorOption('-t, --top')} <number> Show top <number> most downloaded versions
16
17
  ${colorOption('-a, --all')} Include all versions in output, even those with minimal downloads
17
18
  ${colorOption('-c, --color')} <scheme> ${wrapOption(`Choose color scheme from: ${COLOR_SCHEMES.sort().join(', ')}`, 50, 24)}
@@ -48,6 +49,7 @@ function wrapOption(text, maxLength, indent) {
48
49
  export function showHelp() {
49
50
  console.log(redent(HELP, 2));
50
51
  }
52
+ const SORT_ORDERS = ['version', 'downloads'];
51
53
  export function parseCliOptions(argv) {
52
54
  const cli = meow(HELP, {
53
55
  argv: argv.slice(2),
@@ -69,6 +71,10 @@ export function parseCliOptions(argv) {
69
71
  patch: {
70
72
  type: 'boolean',
71
73
  },
74
+ sort: {
75
+ type: 'string',
76
+ choices: [...SORT_ORDERS],
77
+ },
72
78
  top: {
73
79
  shortFlag: 't',
74
80
  type: 'number',
@@ -100,10 +106,17 @@ export function parseCliOptions(argv) {
100
106
  ? 'patch'
101
107
  : undefined,
102
108
  top: cli.flags.top,
109
+ sort: coalesceSortOrder(cli.flags.sort) ?? 'version',
103
110
  all: cli.flags.all ?? false,
104
111
  color: coalesceColor(cli.flags.color ?? process.env.PKG_STATS_COLOR_SCHEME) ?? getColorOfDay(),
105
112
  };
106
113
  }
114
+ function coalesceSortOrder(sort) {
115
+ if (sort && SORT_ORDERS.includes(sort)) {
116
+ return sort;
117
+ }
118
+ return undefined;
119
+ }
107
120
  function coalesceColor(color) {
108
121
  if (color && COLOR_SCHEMES.includes(color)) {
109
122
  return color;
@@ -3,7 +3,7 @@ import { getPrimaryColor } from '../colors.js';
3
3
  import { formatPercentage } from '../format.js';
4
4
  import { fetchNpmLastWeekDownloads } from '../npm-api.js';
5
5
  import { printChart } from '../output.js';
6
- import { filterStats, groupStats } from '../stats.js';
6
+ import { downloadCompare, filterStats, groupStats } from '../stats.js';
7
7
  import { parseVersion, versionCompare } from '../version.js';
8
8
  export async function printPackageStats(packageName, options) {
9
9
  let data;
@@ -29,11 +29,14 @@ export async function printPackageStats(packageName, options) {
29
29
  .sort(versionCompare);
30
30
  const { type, stats } = groupStats(npmStats, options.group);
31
31
  const totalDownloads = Object.values(stats).reduce((sum, version) => sum + version.downloads, 0);
32
- const statsToDisplay = filterStats(stats, {
32
+ const filteredStats = filterStats(stats, {
33
33
  totalDownloads,
34
34
  all: options.all,
35
35
  top: options.top,
36
36
  });
37
+ const statsToDisplay = options.sort === 'downloads'
38
+ ? [...filteredStats].sort(downloadCompare)
39
+ : filteredStats;
37
40
  const downloadToDisplay = statsToDisplay.reduce((sum, version) => sum + version.downloads, 0);
38
41
  if (totalDownloads - downloadToDisplay > 0) {
39
42
  statsToDisplay.push({
package/dist/stats.js CHANGED
@@ -81,7 +81,11 @@ export function filterStats(stats, options) {
81
81
  return filtered;
82
82
  }
83
83
  function pickTopStats(stats, top) {
84
- const sortedStats = stats.sort((a, b) => b.downloads - a.downloads);
84
+ const sortedStats = [...stats].sort((a, b) => b.downloads - a.downloads);
85
85
  const topStats = sortedStats.slice(0, top);
86
86
  return topStats.sort((a, b) => versionCompare(a.version, b.version));
87
87
  }
88
+ export function downloadCompare(a, b) {
89
+ const downloadDifference = b.downloads - a.downloads;
90
+ return downloadDifference === 0 ? versionCompare(a.version, b.version) : downloadDifference;
91
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pkg-stats",
3
- "version": "0.6.0",
3
+ "version": "0.7.0",
4
4
  "description": "Beautiful NPM package download stats",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -24,6 +24,9 @@
24
24
  "bin": {
25
25
  "pkg-stats": "./bin.js"
26
26
  },
27
+ "engines": {
28
+ "node": ">=22.13.0"
29
+ },
27
30
  "scripts": {
28
31
  "build": "tsc",
29
32
  "test": "vitest",
@@ -33,22 +36,22 @@
33
36
  "validate": "pnpm lint && pnpm typecheck && pnpm test -- --no-watch"
34
37
  },
35
38
  "dependencies": {
36
- "chalk": "^5.4.1",
37
- "meow": "^13.2.0",
39
+ "chalk": "^5.6.2",
40
+ "meow": "^14.1.0",
38
41
  "redent": "^4.0.0",
39
- "tinygradient": "^1.1.5"
42
+ "tinygradient": "^2.0.1"
40
43
  },
41
44
  "devDependencies": {
42
- "@eslint/js": "^9.18.0",
43
- "@release-it/conventional-changelog": "^10.0.0",
44
- "@types/node": "^22.10.5",
45
- "eslint": "^9.18.0",
45
+ "@eslint/js": "^9.39.4",
46
+ "@release-it/conventional-changelog": "^11.0.1",
47
+ "@types/node": "^22.19.20",
48
+ "eslint": "^9.39.4",
46
49
  "eslint-plugin-simple-import-sort": "^12.1.1",
47
- "globals": "^15.14.0",
48
- "release-it": "^18.1.1",
49
- "typescript": "^5.7.3",
50
- "typescript-eslint": "^8.19.1",
51
- "vitest": "^2.1.8"
50
+ "globals": "^15.15.0",
51
+ "release-it": "^20.2.0",
52
+ "typescript": "^5.9.3",
53
+ "typescript-eslint": "^8.61.0",
54
+ "vitest": "^2.1.9"
52
55
  },
53
- "packageManager": "pnpm@9.15.3+sha512.1f79bc245a66eb0b07c5d4d83131240774642caaa86ef7d0434ab47c0d16f66b04e21e0c086eb61e62c77efc4d7f7ec071afad3796af64892fae66509173893a"
56
+ "packageManager": "pnpm@11.5.3+sha512.7ac1c919341c213a34dc0d02afb7143c5c26ac26ee8c4782deea821b8ac64d2134a081fd8941dae6e29bbb48f58dfc2b7fbceeccc07cb2f09d219d342a4969ed"
54
57
  }