pkg-stats 0.2.0 → 0.3.0

Sign up to get free protection for your applications and to get access to all the features.
package/bin.js CHANGED
@@ -2,4 +2,4 @@
2
2
 
3
3
  import { pkgStats } from './dist/index.js';
4
4
 
5
- pkgStats(process.argv.slice(2));
5
+ pkgStats(process.argv);
@@ -0,0 +1,7 @@
1
+ import { expect, test } from 'vitest';
2
+ import { renderChart } from '../chart.js';
3
+ test('renderChart basic tests', () => {
4
+ expect(renderChart(0.0, { length: 10 })).toMatchInlineSnapshot(`" "`);
5
+ expect(renderChart(0.5, { length: 10 })).toMatchInlineSnapshot(`"█████ "`);
6
+ expect(renderChart(1.0, { length: 10 })).toMatchInlineSnapshot(`"██████████"`);
7
+ });
package/dist/bin.js CHANGED
@@ -1,26 +1,22 @@
1
1
  import chalk from 'chalk';
2
- import minimist from 'minimist';
3
2
  import { renderChart } from './chart.js';
3
+ import { parseCliOptions } from './cli-options.js';
4
4
  import { getColors } from './colors.js';
5
- import { parseVersion, versionCompare } from './version.js';
5
+ import { fetchNpmLastWeekDownloads } from './npm-api.js';
6
6
  import { groupByType, pickTopStats } from './stats.js';
7
+ import { parseVersion, versionCompare } from './version.js';
7
8
  export async function pkgStats(argv) {
8
9
  const options = parseCliOptions(argv);
9
- if (options.help) {
10
- printHelp();
11
- return;
12
- }
13
10
  let data;
14
11
  try {
15
- const response = await fetch(`https://api.npmjs.org/versions/${encodeURIComponent(options.name)}/last-week`);
16
- data = await response.json();
12
+ data = await fetchNpmLastWeekDownloads(options.packageName);
17
13
  }
18
14
  catch (error) {
19
- console.error(`Failed to fetch data for package "${options.name}"`);
15
+ console.error(`Failed to fetch data for package "${options.packageName}"`, error);
20
16
  return;
21
17
  }
22
18
  if (!Object.keys(data.downloads).length) {
23
- console.error(`No data found for package "${options.name}".\n`);
19
+ console.error(`No data found for package "${options.packageName}".\n`);
24
20
  process.exit(1);
25
21
  }
26
22
  const rawStats = Object.keys(data.downloads)
@@ -32,14 +28,14 @@ export async function pkgStats(argv) {
32
28
  };
33
29
  })
34
30
  .sort(versionCompare);
35
- let groupedStats = groupByType(options.group, rawStats);
31
+ const groupedStats = groupByType(options.group, rawStats);
36
32
  const totalDownloads = Object.values(groupedStats).reduce((sum, version) => sum + version.downloads, 0);
37
33
  const groupedStatsToDisplay = options.top
38
34
  ? pickTopStats(groupedStats, options.top)
39
35
  : groupedStats;
40
- const colors = getColors(groupedStatsToDisplay.length);
36
+ const colors = getColors(groupedStatsToDisplay.length, options.color);
41
37
  const primaryColor = chalk.hex(colors[0]);
42
- console.log(chalk.bold(`\nNPM weekly downloads for ${primaryColor(options.name)}\n`));
38
+ console.log(chalk.bold(`\nNPM weekly downloads for ${primaryColor(options.packageName)}\n`));
43
39
  console.log(`Total: ${primaryColor(totalDownloads.toLocaleString())} last week\n`);
44
40
  console.log(options.top ? `Top ${options.top} versions:\n` : 'By version:\n');
45
41
  const maxDownloads = Math.max(...groupedStats.map((v) => v.downloads));
@@ -53,43 +49,6 @@ export async function pkgStats(argv) {
53
49
  });
54
50
  console.log('');
55
51
  }
56
- function parseCliOptions(argv) {
57
- const options = minimist(argv, {
58
- string: ['group', 'top'],
59
- boolean: ['help'],
60
- alias: { g: 'group', h: 'help', t: 'top' },
61
- });
62
- let group;
63
- if (options.group === 'major' || options.major) {
64
- group = 'major';
65
- }
66
- else if (options.group === 'minor' || options.minor) {
67
- group = 'minor';
68
- }
69
- else if (options.group === 'patch' || options.patch) {
70
- group = 'patch';
71
- }
72
- const top = options.top ? parseInt(options.top) : undefined;
73
- if (!options._[0]) {
74
- console.error('Package name is required');
75
- process.exit(1);
76
- }
77
- return { name: options._[0], group, help: options.help, top };
78
- }
79
- function printHelp() {
80
- console.log(`
81
- Usage:
82
- pkg-stats [options] <package-name>
83
-
84
- Options:
85
- -h, --help Show help
86
- --group <group> Group by major, minor, or patch (default: major)
87
- --major Group by major
88
- --minor Group by minor
89
- --patch Group by patch
90
- --top <number> Show top <number> versions
91
- `);
92
- }
93
52
  function formatDownloads(downloads, maxDownloads) {
94
53
  if (maxDownloads > 1000000) {
95
54
  return `${(downloads / 1000000).toFixed(1)}M`;
@@ -0,0 +1,24 @@
1
+ import { program } from 'commander';
2
+ import { COLOR_SCHEMES } from './colors.js';
3
+ export function parseCliOptions(argv) {
4
+ program
5
+ .name('pkg-stats')
6
+ .description('Show NPM weekly downloads stats for a package')
7
+ .argument('<package-name>', 'Package name')
8
+ .option('--major', 'Group by major version')
9
+ .option('--minor', 'Group by minor version')
10
+ .option('--patch', 'Group by patch version')
11
+ .option('-t, --top <number>', 'Show top <number> versions')
12
+ .option('-c, --color <color>', 'Color scheme: ' + COLOR_SCHEMES.join(', '))
13
+ .parse(argv);
14
+ const args = program.args;
15
+ const options = program.opts();
16
+ return {
17
+ packageName: args[0],
18
+ group: options.major ? 'major' : options.minor ? 'minor' : options.patch ? 'patch' : undefined,
19
+ top: options.top !== undefined ? parseInt(options.top) : undefined,
20
+ color: options.color && COLOR_SCHEMES.includes(options.color)
21
+ ? options.color
22
+ : undefined,
23
+ };
24
+ }
package/dist/colors.js CHANGED
@@ -9,7 +9,7 @@ const gradients = {
9
9
  vice: { colors: ['#5ee7df', '#b490ca'], options: { interpolation: 'hsv' } },
10
10
  passion: { colors: ['#f43b47', '#453a94'], options: {} },
11
11
  fruit: { colors: ['#ff4e50', '#f9d423'], options: {} },
12
- instagram: { colors: ['#833ab4', '#fd1d1d', '#fcb045'], options: {} },
12
+ insta: { colors: ['#833ab4', '#fd1d1d', '#fcb045'], options: {} },
13
13
  retro: {
14
14
  colors: [
15
15
  '#3f51b1',
@@ -25,20 +25,30 @@ const gradients = {
25
25
  options: {},
26
26
  },
27
27
  summer: { colors: ['#fdbb2d', '#22c1c3'], options: {} },
28
- rainbow: { colors: ['#ff0000', '#ff0100'], options: { interpolation: 'hsv', hsvSpin: 'long' } },
29
- pastel: { colors: ['#74ebd5', '#74ecd5'], options: { interpolation: 'hsv', hsvSpin: 'long' } },
28
+ rainbow: {
29
+ colors: ['#ff0100', '#ff0000'],
30
+ options: { interpolation: 'hsv', hsvSpin: 'long', padEnd: 0.1 },
31
+ },
32
+ pastel: {
33
+ colors: ['#74ebd5', '#74ecd5'],
34
+ options: { interpolation: 'hsv', hsvSpin: 'long', padEnd: 0.1 },
35
+ },
30
36
  };
37
+ export const COLOR_SCHEMES = Object.keys(gradients);
31
38
  export function getColors(count, colorScheme) {
32
- const { colors, options } = gradients[colorScheme ?? getRandomScheme()];
33
- if (count < colors.length) {
39
+ const { colors, options } = gradients[colorScheme ?? getColorOfDay()];
40
+ const paddedCount = count + (options.padEnd ? Math.ceil(count * options.padEnd) : 0);
41
+ if (paddedCount < colors.length) {
34
42
  return colors;
35
43
  }
36
44
  const gradient = tinygradient(colors);
37
45
  const tinyColors = options.interpolation === 'hsv'
38
- ? gradient.hsv(count, options.hsvSpin ?? false)
39
- : gradient.rgb(count);
46
+ ? gradient.hsv(paddedCount, options.hsvSpin ?? false)
47
+ : gradient.rgb(paddedCount);
40
48
  return tinyColors.map((c) => c.toHexString());
41
49
  }
42
- function getRandomScheme() {
43
- return Object.keys(gradients)[Math.floor(Math.random() * Object.keys(gradients).length)];
50
+ function getColorOfDay() {
51
+ const date = new Date();
52
+ const index = date.getDate() + date.getMonth() * 30 + date.getFullYear() * 360;
53
+ return COLOR_SCHEMES[index % COLOR_SCHEMES.length];
44
54
  }
@@ -0,0 +1,14 @@
1
+ export async function fetchNpmLastWeekDownloads(packageName) {
2
+ const response = await fetch(`https://api.npmjs.org/versions/${encodeURIComponent(packageName)}/last-week`);
3
+ if (!response.ok) {
4
+ throw new Error(`Failed to fetch data for package "${packageName}. Status: ${response.status}`);
5
+ }
6
+ const json = await response.json();
7
+ if (!json.downloads) {
8
+ throw new Error('No downloads found');
9
+ }
10
+ return {
11
+ package: packageName,
12
+ downloads: json.downloads,
13
+ };
14
+ }
package/package.json CHANGED
@@ -1,9 +1,14 @@
1
1
  {
2
2
  "name": "pkg-stats",
3
- "version": "0.2.0",
3
+ "version": "0.3.0",
4
4
  "description": "Beautiful NPM package download stats",
5
- "author": "Maciej Jastrzębski <mdjastrzebski@gmail.com> (https://github.com/mdjastrzebski)",
6
5
  "license": "MIT",
6
+ "repository": {
7
+ "type": "git",
8
+ "url": "https://github.com/mdjastrzebski/pkg-stats"
9
+ },
10
+ "homepage": "https://github.com/mdjastrzebski/pkg-stats",
11
+ "author": "Maciej Jastrzębski <mdjastrzebski@gmail.com> (https://github.com/mdjastrzebski)",
7
12
  "keywords": [
8
13
  "npm",
9
14
  "package",
@@ -19,17 +24,32 @@
19
24
  "bin": {
20
25
  "pkg-stats": "./bin.js"
21
26
  },
27
+ "scripts": {
28
+ "build": "tsc",
29
+ "test": "vitest",
30
+ "typecheck": "tsc --noEmit",
31
+ "lint": "eslint",
32
+ "release": "release-it",
33
+ "validate": "pnpm lint && pnpm typecheck && pnpm test -- --no-watch"
34
+ },
22
35
  "dependencies": {
23
36
  "chalk": "^5.4.1",
24
- "minimist": "^1.2.8",
37
+ "commander": "^13.0.0",
25
38
  "tinygradient": "^1.1.5"
26
39
  },
27
40
  "devDependencies": {
41
+ "@eslint/js": "^9.18.0",
42
+ "@release-it/conventional-changelog": "^10.0.0",
28
43
  "@types/minimist": "^1.2.5",
29
44
  "@types/node": "^22.10.5",
30
- "typescript": "^5.7.3"
45
+ "eslint": "^9.18.0",
46
+ "eslint-plugin-simple-import-sort": "^12.1.1",
47
+ "globals": "^15.14.0",
48
+ "redent": "^4.0.0",
49
+ "release-it": "^18.1.1",
50
+ "typescript": "^5.7.3",
51
+ "typescript-eslint": "^8.19.1",
52
+ "vitest": "^2.1.8"
31
53
  },
32
- "scripts": {
33
- "build": "tsc"
34
- }
35
- }
54
+ "packageManager": "pnpm@9.15.3+sha512.1f79bc245a66eb0b07c5d4d83131240774642caaa86ef7d0434ab47c0d16f66b04e21e0c086eb61e62c77efc4d7f7ec071afad3796af64892fae66509173893a"
55
+ }