Skip to content

Commit 35c7aa3

Browse files
authored
Merge pull request #51 from veghdev/lastweek
npmstat.js: add writeLastWeekNpmStat
2 parents 4d6cc72 + 89fb50c commit 35c7aa3

File tree

3 files changed

+124
-18
lines changed

3 files changed

+124
-18
lines changed

README.md

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ write-npmstat makes it easy to collect, filter and save npm statistics to csv fi
88

99
# Installation
1010

11-
write-npmstat requires `npm-stat-api`, `enum`, `csv-writer` and `csv-parser` packages.
11+
write-npmstat requires `npm-stat-api`, `enum`, `csv-writer`, `csv-parser` and `node-fetch` packages.
1212

1313
```sh
1414
npm install write-npmstat
@@ -27,6 +27,8 @@ writenpmstat.writeNpmStat("2021", "2022-03");
2727

2828
writenpmstat.datePeriod = "month";
2929
writenpmstat.writeNpmStat("2022-01", "2022-04-15");
30+
31+
writenpmstat.writeLastWeekNpmStat();
3032
```
3133

3234
Visit our [documentation](https://veghdev.github.io/write-npmstat/) site for code reference or

package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,8 @@
3535
"npm-stat-api": "^1.0.0",
3636
"enum": "^3.0.0",
3737
"csv-writer": "^1.6.0",
38-
"csv-parser": "^3.0.0"
38+
"csv-parser": "^3.0.0",
39+
"node-fetch": "<3.0.0"
3940
},
4041
"devDependencies": {
4142
"npm-run-all": "^4.1.5",

src/npmstat.js

Lines changed: 119 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ const npm = require("npm-stat-api");
44
const Enum = require("enum");
55
const csv = require("csv-parser");
66
const createCsvWriter = require("csv-writer").createArrayCsvWriter;
7+
const fetch = require("node-fetch");
78

89
const StatDate = require("./statdate.js");
910

@@ -119,6 +120,16 @@ class WriteNpmStat {
119120
});
120121
}
121122

123+
/**
124+
* Returns last week npm statistics for a package
125+
* @returns {Promise} Promise object represents the last week npm statistics for a package
126+
*/
127+
getLastWeekNpmStat() {
128+
return new Promise((resolve) => {
129+
return resolve(this.#getLastWeekStat(100));
130+
});
131+
}
132+
122133
static getDays(startDate, endDate) {
123134
const arr = [];
124135
const dt = new Date(startDate);
@@ -151,6 +162,45 @@ class WriteNpmStat {
151162
});
152163
}
153164

165+
#getLastWeekStat(retryLimit, retryCount) {
166+
retryLimit = retryLimit || Number.MAX_VALUE;
167+
retryCount = Math.max(retryCount || 0, 0);
168+
return fetch(
169+
"https://api.npmjs.org/versions/" + this.#packageName + "/last-week"
170+
)
171+
.then((response) => {
172+
if (response.status !== 200) {
173+
throw new Error("response.status: " + response.status);
174+
}
175+
return new Promise((resolve) => {
176+
response.json().then((response) => {
177+
const responseObject = {};
178+
const day = StatDate.formatStart(new Date());
179+
for (const [key, value] of Object.entries(
180+
response.downloads
181+
)) {
182+
const statKey = day + "_" + key;
183+
const statValues = [];
184+
statValues.push(day);
185+
if (this.writePackageName) {
186+
statValues.push(this.#packageName);
187+
}
188+
statValues.push(key);
189+
statValues.push(value);
190+
responseObject[statKey] = statValues;
191+
}
192+
return resolve(responseObject);
193+
});
194+
});
195+
})
196+
.catch((err) => {
197+
if (retryCount < retryLimit) {
198+
return this.#getLastWeekStat(retryLimit, retryCount + 1);
199+
}
200+
throw err;
201+
});
202+
}
203+
154204
/**
155205
* Writes npm statistics for a package
156206
* @param {string|null} [startDate] - start date of the statistics
@@ -173,28 +223,56 @@ class WriteNpmStat {
173223
* <br>&nbsp;&nbsp; - "%Y-%m-%d", for example "2022-12-31", which means to be collected until "2022-12-31"
174224
*
175225
* <br>&nbsp;&nbsp; - undefined, which means to be collected until the actual day
176-
* @param {string|null} [endDate=npmstat] - postfix of the csv file
226+
* @param {string|null} [postfix=npmstat] - postfix of the csv file
177227
* @returns {Promise} Promise object represents the npm statistics for a package
178228
*/
179229
writeNpmStat(startDate, endDate, postfix = "npmstat") {
180230
return new Promise((resolve) => {
231+
const lastWeek = false;
181232
const stats = this.getNpmStat(startDate, endDate);
182233
stats.then((stats) => {
183234
const grouped = this.#groupStats(
235+
lastWeek,
184236
stats,
185237
startDate,
186238
endDate,
187239
postfix
188240
);
189-
this.#mergeStats(grouped).then((merged) => {
190-
this.#writeStats(merged);
241+
this.#mergeStats(lastWeek, grouped).then((merged) => {
242+
this.#writeStats(lastWeek, merged);
191243
return resolve(merged);
192244
});
193245
});
194246
});
195247
}
196248

197-
#groupStats(stats, startDate, endDate, postfix) {
249+
/**
250+
* Writes last week npm statistics for a package
251+
* @param {string|null} [postfix=lastweek_npmstat] - postfix of the csv file
252+
* @returns {Promise} Promise object represents the last week npm statistics for a package
253+
*/
254+
writeLastWeekNpmStat(postfix = "lastweek_npmstat") {
255+
return new Promise((resolve) => {
256+
const lastWeek = true;
257+
const stats = this.getLastWeekNpmStat();
258+
stats.then((stats) => {
259+
const day = StatDate.formatStart(new Date());
260+
const grouped = this.#groupStats(
261+
lastWeek,
262+
stats,
263+
day,
264+
day,
265+
postfix
266+
);
267+
this.#mergeStats(lastWeek, grouped).then((merged) => {
268+
this.#writeStats(lastWeek, merged);
269+
return resolve(merged);
270+
});
271+
});
272+
});
273+
}
274+
275+
#groupStats(lastWeek, stats, startDate, endDate, postfix) {
198276
const statDate = new StatDate(startDate, endDate);
199277
const days = WriteNpmStat.getDays(statDate.start, statDate.end);
200278
const grouped = {};
@@ -212,9 +290,17 @@ class WriteNpmStat {
212290
const prefix = day.substring(0, substring);
213291
if (!initialized[prefix]) {
214292
initialized[prefix] = true;
215-
grouped[prefix + "_" + postfix + ".csv"] = [
216-
[day, stats[day]],
217-
];
293+
grouped[prefix + "_" + postfix + ".csv"] = [];
294+
}
295+
if (lastWeek) {
296+
for (const [key, value] of Object.entries(stats)) {
297+
if (key.startsWith(day)) {
298+
grouped[prefix + "_" + postfix + ".csv"].push([
299+
key,
300+
value,
301+
]);
302+
}
303+
}
218304
} else {
219305
grouped[prefix + "_" + postfix + ".csv"].push([
220306
day,
@@ -225,21 +311,29 @@ class WriteNpmStat {
225311
} else {
226312
grouped[postfix + ".csv"] = [];
227313
days.forEach((day) => {
228-
grouped[postfix + ".csv"].push([day, stats[day]]);
314+
if (lastWeek) {
315+
for (const [key, value] of Object.entries(stats)) {
316+
if (key.startsWith(day)) {
317+
grouped[postfix + ".csv"].push([key, value]);
318+
}
319+
}
320+
} else {
321+
grouped[postfix + ".csv"].push([day, stats[day]]);
322+
}
229323
});
230324
}
231325
return grouped;
232326
}
233327

234-
#mergeStats(stats) {
328+
#mergeStats(lastWeek, stats) {
235329
return new Promise((resolve) => {
236330
if (!this.mergeStoredData) {
237331
return resolve(stats);
238332
}
239333
const csvFiles = {};
240334
const csvFilesReady = [];
241335
for (const [key, value] of Object.entries(stats)) {
242-
const csvFileReady = this.#readCsv(key, value[0]);
336+
const csvFileReady = this.#readCsv(lastWeek, key, value[0]);
243337
csvFilesReady.push(csvFileReady);
244338
csvFileReady.then((csvData) => {
245339
Object.assign(csvFiles, csvData);
@@ -256,7 +350,7 @@ class WriteNpmStat {
256350
});
257351
}
258352

259-
#readCsv(csvFile, firstNewLine) {
353+
#readCsv(lastWeek, csvFile, firstNewLine) {
260354
return new Promise((resolve) => {
261355
const csvData = {};
262356
csvData[csvFile] = [];
@@ -273,13 +367,16 @@ class WriteNpmStat {
273367
.pipe(csv())
274368
.on("data", (row) => {
275369
if (firstNewLine) {
276-
if (row.date < firstNewLine[0]) {
370+
if (row.date < firstNewLine[0].substring(0, 10)) {
277371
const statKey = row.date;
278372
const statValues = [];
279373
statValues.push(row.date);
280374
if (writePackageName) {
281375
statValues.push(row.package);
282376
}
377+
if (lastWeek) {
378+
statValues.push(row.version);
379+
}
283380
statValues.push(row.downloads);
284381
csvData[csvFile].push([statKey, statValues]);
285382
}
@@ -292,19 +389,25 @@ class WriteNpmStat {
292389
});
293390
}
294391

295-
#writeStats(stats) {
392+
#writeStats(lastWeek, stats) {
296393
if (this.outDir) {
297394
fs.mkdir(this.outDir, { recursive: true }, (err) => {
298395
if (err) {
299396
throw err;
300397
}
301398
for (const [key, value] of Object.entries(stats)) {
302399
const csvFilePath = this.outDir + "/" + key;
400+
const header = ["date"];
401+
if (this.writePackageName) {
402+
header.push("package");
403+
}
404+
if (lastWeek) {
405+
header.push("version");
406+
}
407+
header.push("downloads");
303408
const csvWriter = createCsvWriter({
304409
path: csvFilePath,
305-
header: this.writePackageName
306-
? ["date", "package", "downloads"]
307-
: ["date", "downloads"],
410+
header,
308411
});
309412
const postProcessedStats = [];
310413
value.forEach((stat) => {

0 commit comments

Comments
 (0)