@fusionfroze/statsy 1.0.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.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 FusionFroze
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.
package/README.md ADDED
@@ -0,0 +1,96 @@
1
+ # Statsy
2
+
3
+ Get basic stats about public **npm** packages maintained by you, in your terminal.
4
+
5
+ ## Installation
6
+
7
+ **Prerequisite:** Node.js v18+
8
+
9
+ **Install Globally:**
10
+
11
+ ```shell
12
+ Publication to NPM registry is pending
13
+ ```
14
+
15
+ **For Local Development:**
16
+
17
+ After cloning the repo into your machine locally, run these -
18
+
19
+ ```shell
20
+ $ npm install
21
+ ```
22
+
23
+ ```shell
24
+ $ npm link
25
+ ```
26
+
27
+ ## How to Use it
28
+
29
+ ### Setting up
30
+
31
+ After installing, run -
32
+
33
+ ```shell
34
+ $ statsy setup
35
+ ```
36
+
37
+ This will open an input interface and ask for two things (both saved locally in your machine) -
38
+
39
+ - **_Your npm username_**
40
+ - **_Your personal Github Access token_**
41
+
42
+ (**Tip:** To know more about github access tokens [click here](https://docs.github.com/en/authentication/keeping-your-account-and-data-secure/managing-your-personal-access-tokens#creating-a-personal-access-token-classic). Remember to select the _repo_ scope, otherwise the api will not be able to fetch info about your repository.)
43
+
44
+ ### Viewing stats
45
+
46
+ **_statsy_** support four options, each showing a different stat. These are as follows -
47
+
48
+ ```shell
49
+ Options:
50
+ -v, --version current version of the package
51
+ -s, --stars star count on the repository of the package on github
52
+ -i, --issues issue count on the repository of the package on github
53
+ -d, --downloads download count of the package from npm registry
54
+ ```
55
+
56
+ You can run the command in four ways -
57
+
58
+ ```shell
59
+ $ statsy
60
+ ```
61
+
62
+ Shows all four stats for all packages maintained by you.
63
+
64
+ ```shell
65
+ $ statsy -d -i
66
+ ```
67
+
68
+ or
69
+
70
+ ```shell
71
+ $ statsy -di
72
+ ```
73
+
74
+ Shows only the download and issues count for all packages.
75
+
76
+ ```shell
77
+ $ statsy examplePackage
78
+ ```
79
+
80
+ Shows all stats for a single specified package (examplePackage).
81
+
82
+ ```shell
83
+ $ statsy examplePackage -d
84
+ ```
85
+
86
+ Shows only the download count for a single specified package.
87
+
88
+ ### Resetting your credentials
89
+
90
+ To delete/reset your credentials, run -
91
+
92
+ ```shell
93
+ $ statsy reset
94
+ ```
95
+
96
+ You will be asked to give your confirmation once. Once confirmed, the action is irreversible.
@@ -0,0 +1,32 @@
1
+ import chalk from "chalk";
2
+ import Conf from "conf";
3
+ import { confirm } from "@inquirer/prompts";
4
+
5
+ const conf = new Conf({ projectName: "statsy" });
6
+
7
+ export default async function reset() {
8
+ const config = conf.get();
9
+ const credentials = Object.keys(config);
10
+
11
+ if (credentials.length === 0) {
12
+ console.log(
13
+ chalk.red(
14
+ `You don't have your credentials set yet\nRun ${chalk.white("statsy setup")} to set it up`,
15
+ ),
16
+ );
17
+ process.exit(1);
18
+ }
19
+
20
+ const answer = await confirm({ message: "Are you sure?" });
21
+ if (!answer) {
22
+ process.exit(0);
23
+ }
24
+
25
+ conf.clear();
26
+ console.log(
27
+ chalk.green(
28
+ `Your credentials have been reset\nRun ${chalk.white("statsy setup")} to set it again`,
29
+ ),
30
+ );
31
+ process.exit(0);
32
+ }
@@ -0,0 +1,52 @@
1
+ import Conf from "conf";
2
+ import { input, password } from "@inquirer/prompts";
3
+ import chalk from "chalk";
4
+
5
+ const conf = new Conf({ projectName: "statsy" });
6
+
7
+ async function takeInput() {
8
+ const npmUsername = await input({
9
+ message: "Enter your NPM username:",
10
+ required: true,
11
+ });
12
+ const githubToken = await password({
13
+ message: "Enter your Github access token:",
14
+ mask: true,
15
+ });
16
+
17
+ const config = {
18
+ npmUsername: npmUsername,
19
+ githubToken: githubToken,
20
+ };
21
+
22
+ return config;
23
+ }
24
+
25
+ export default async function setup() {
26
+ let config = conf.get();
27
+ const credentials = Object.keys(config);
28
+
29
+ if (credentials.length === 0) {
30
+ try {
31
+ const config = await takeInput();
32
+ conf.set(config);
33
+
34
+ console.log(
35
+ `\nYour credentials are saved successfully.\nuse ${chalk.blue("statsy -h")} for usage information`,
36
+ );
37
+
38
+ process.exit(0);
39
+ } catch (error) {
40
+ if (error instanceof Error && error.name === "ExitPromptError") {
41
+ console.log(chalk.magenta("Until next time 🫡"));
42
+ process.exit(1);
43
+ } else {
44
+ throw error;
45
+ }
46
+ }
47
+ }
48
+
49
+ console.log(
50
+ `Already set.\nUse ${chalk.blue("statsy reset")} to clear your current setup.`,
51
+ );
52
+ }
package/index.js ADDED
@@ -0,0 +1,44 @@
1
+ #! /usr/bin/env node
2
+
3
+ import { program } from "commander";
4
+ import showStats from "./lib/showStats.js";
5
+ import setup from "./commands/setup.js";
6
+ import table from "text-table";
7
+ import reset from "./commands/reset.js";
8
+
9
+ program
10
+ .command("setup")
11
+ .description("setup your credentials for fetching package information")
12
+ .action(setup);
13
+
14
+ program.command("reset").description("reset your credentials").action(reset);
15
+
16
+ program
17
+ .argument("[package_name]", "name of the package", undefined)
18
+ .option("-v, --version", "current version of the package")
19
+ .option(
20
+ "-s, --stars",
21
+ "star count on the repository of the package on github",
22
+ )
23
+ .option(
24
+ "-i, --issues",
25
+ "issue count on the repository of the package on github",
26
+ )
27
+ .option("-d, --downloads", "download count of the package from npm registry")
28
+ .addHelpText(
29
+ "after",
30
+ `\nExample usage:\n${table([
31
+ [" statsy", "(all packages, all stats)"],
32
+ [" statsy -d", "(only download count for all packages)"],
33
+ [" statsy example", "(all stats only for package named 'example')"],
34
+ [
35
+ " statsy example -d -s",
36
+ "(only download and star count for package named 'example')",
37
+ ],
38
+ ])}\n`,
39
+ )
40
+ .action((package_name, options) => {
41
+ showStats(package_name, options);
42
+ });
43
+
44
+ program.parse();
@@ -0,0 +1,114 @@
1
+ import chalk from "chalk";
2
+
3
+ export default async function fetchStats(packageName, config) {
4
+ const packageData = await getPackageData(packageName, config);
5
+
6
+ if (!packageData) {
7
+ console.log(
8
+ chalk.red(
9
+ `Something went wrong!\nPlease check your internet connection...`,
10
+ ),
11
+ );
12
+ process.exit(1);
13
+ }
14
+
15
+ if (!packageData[0]) {
16
+ console.error(chalk.red(`Package named ${packageName} not found!`));
17
+ process.exit(1);
18
+ }
19
+
20
+ const rowPromises = packageData.map(async (p) => {
21
+ try {
22
+ const downloadDataResponse = await fetch(
23
+ `https://api.npmjs.org/downloads/point/last-week/${p.package.name}`,
24
+ );
25
+
26
+ if (!downloadDataResponse.ok) {
27
+ throw new Error(
28
+ `Can't fetch download stats: ${downloadDataResponse.status}`,
29
+ );
30
+ }
31
+
32
+ const downloadData = await downloadDataResponse.json();
33
+
34
+ let repositoryData = { starsCount: "N/A", issuesCount: "N/A" };
35
+
36
+ const scopedPackageName = p.package.name;
37
+ const packageVersion = p.package.version;
38
+
39
+ const repositoryUrl = p.package.links?.repository;
40
+
41
+ if (repositoryUrl) {
42
+ const urlWithoutExtention = repositoryUrl.split(".git")[0];
43
+ const finalRepoUrlArray = urlWithoutExtention.split("/");
44
+ const ownerName = finalRepoUrlArray[finalRepoUrlArray.length - 2];
45
+ const repositoryName = finalRepoUrlArray[finalRepoUrlArray.length - 1];
46
+
47
+ try {
48
+ const repositoryDataResponse = await fetch(
49
+ `https://api.github.com/repos/${ownerName}/${repositoryName}`,
50
+ {
51
+ headers: {
52
+ Authorization: `Bearer ${config["githubToken"]}`,
53
+ },
54
+ },
55
+ );
56
+
57
+ if (!repositoryDataResponse.ok) {
58
+ throw new Error(
59
+ `Failed to fetch repository data from Github: ${repositoryDataResponse.status}`,
60
+ );
61
+ }
62
+
63
+ const r = await repositoryDataResponse.json();
64
+ repositoryData.starsCount = r.stargazers_count;
65
+ repositoryData.issuesCount = r.open_issues_count;
66
+ } catch (error) {
67
+ console.error(chalk.red(error.message));
68
+ }
69
+ }
70
+
71
+ return {
72
+ package: scopedPackageName,
73
+ version: packageVersion,
74
+ stars: repositoryData.starsCount,
75
+ issues: repositoryData.issuesCount,
76
+ downloads: downloadData.downloads,
77
+ };
78
+ } catch (error) {
79
+ console.error(chalk.red(error.message));
80
+ }
81
+ });
82
+
83
+ const rows = await Promise.all(rowPromises);
84
+ return rows;
85
+ }
86
+
87
+ async function getPackageData(pkg, config) {
88
+ const npmUrl = `https://registry.npmjs.org/-/v1/search?text=maintainer:${config["npmUsername"]}`;
89
+
90
+ try {
91
+ const allPackageDataResponse = await fetch(npmUrl);
92
+
93
+ if (!allPackageDataResponse.ok) {
94
+ throw new Error(
95
+ `NPM package fetch failed: ${allPackageDataResponse.status}`,
96
+ );
97
+ }
98
+
99
+ const allPackageData = await allPackageDataResponse.json();
100
+
101
+ if (pkg) {
102
+ const packageArray = allPackageData.objects;
103
+ const requestedPackage = packageArray.find((p) =>
104
+ p["package"].name.includes(pkg),
105
+ );
106
+
107
+ return [requestedPackage];
108
+ }
109
+
110
+ return allPackageData.objects;
111
+ } catch (error) {
112
+ console.error(chalk.red(error.message));
113
+ }
114
+ }
@@ -0,0 +1,59 @@
1
+ import Conf from "conf";
2
+ import table from "text-table";
3
+ import chalk from "chalk";
4
+ import fetchStats from "./fetchStats.js";
5
+
6
+ const conf = new Conf({ projectName: "statsy" });
7
+
8
+ export default async function showStats(packageName, options) {
9
+ let config = conf.get();
10
+ const credentials = Object.keys(config);
11
+
12
+ if (credentials.length === 0) {
13
+ console.error(
14
+ chalk.red(
15
+ `Credentials has not been set up.\n${chalk.white(`Run ${chalk.bold("statsy setup")} first...`)}`,
16
+ ),
17
+ );
18
+
19
+ process.exit(1);
20
+ }
21
+
22
+ const rowsObjectArray = await fetchStats(packageName, config);
23
+
24
+ const optionsArray = Object.keys(options);
25
+
26
+ if (optionsArray.length === 0) {
27
+ const rows = rowsObjectArray.map((row) => {
28
+ return [row.package, row.version, row.stars, row.issues, row.downloads];
29
+ });
30
+
31
+ const t = table([
32
+ ["Package", "Version", "Stars", "Issues", "Downloads"],
33
+ ...rows,
34
+ ]);
35
+ console.log(t);
36
+ } else {
37
+ const headerRow = generateHeaderRow(optionsArray);
38
+
39
+ const dataRows = rowsObjectArray.map((row) => {
40
+ const r = [row.package];
41
+ for (let i = 0; i < headerRow.length; i++) {
42
+ const key = headerRow[i].toLowerCase();
43
+ r.push(row[key]);
44
+ }
45
+ return r;
46
+ });
47
+
48
+ const t = table([["Package", ...headerRow], ...dataRows]);
49
+ console.log(t);
50
+ }
51
+ }
52
+
53
+ function generateHeaderRow(optionsArray) {
54
+ const headerRow = optionsArray.map((option) => {
55
+ return option.charAt(0).toUpperCase() + option.slice(1);
56
+ });
57
+
58
+ return headerRow;
59
+ }
package/package.json ADDED
@@ -0,0 +1,36 @@
1
+ {
2
+ "name": "@fusionfroze/statsy",
3
+ "version": "1.0.0",
4
+ "description": "Get stats about the npm packages maintained by you in your terminal",
5
+ "license": "MIT",
6
+ "author": "FusionFroze",
7
+ "type": "module",
8
+ "main": "index.js",
9
+ "scripts": {
10
+ "test": "echo \"Error: no test specified\" && exit 1"
11
+ },
12
+ "dependencies": {
13
+ "@inquirer/prompts": "^8.3.2",
14
+ "chalk": "^5.6.2",
15
+ "commander": "^14.0.3",
16
+ "conf": "^15.1.0",
17
+ "text-table": "^0.2.0"
18
+ },
19
+ "bin": {
20
+ "statsy": "index.js"
21
+ },
22
+ "keywords": [
23
+ "statsy",
24
+ "cli",
25
+ "package",
26
+ "terminal"
27
+ ],
28
+ "repository": {
29
+ "type": "git",
30
+ "url": "git+https://github.com/FusionFroze/statsy"
31
+ },
32
+ "homepage": "https://github.com/FusionFroze/statsy/#readme",
33
+ "bugs": {
34
+ "url": "https://github.com/FusionFroze/statsy/issues"
35
+ }
36
+ }