diff --git a/README.md b/README.md index 8b66857..a185861 100644 --- a/README.md +++ b/README.md @@ -8,7 +8,7 @@ write-npmstat makes it easy to collect, filter and save npm statistics to csv fi # Installation -write-npmstat requires `npm-stat-api`, `enum`, `csv-writer` and `csv-parser` packages. +write-npmstat requires `npm-stat-api`, `enum`, `csv-writer`, `csv-parser` and `node-fetch` packages. ```sh npm install write-npmstat @@ -27,6 +27,8 @@ writenpmstat.writeNpmStat("2021", "2022-03"); writenpmstat.datePeriod = "month"; writenpmstat.writeNpmStat("2022-01", "2022-04-15"); + +writenpmstat.writeLastWeekNpmStat(); ``` Visit our [documentation](https://veghdev.github.io/write-npmstat/) site for code reference or diff --git a/package.json b/package.json index 90ec807..10d9ef8 100644 --- a/package.json +++ b/package.json @@ -35,7 +35,8 @@ "npm-stat-api": "^1.0.0", "enum": "^3.0.0", "csv-writer": "^1.6.0", - "csv-parser": "^3.0.0" + "csv-parser": "^3.0.0", + "node-fetch": "<3.0.0" }, "devDependencies": { "npm-run-all": "^4.1.5", diff --git a/src/npmstat.js b/src/npmstat.js index 21816ac..e2d77b5 100644 --- a/src/npmstat.js +++ b/src/npmstat.js @@ -4,6 +4,7 @@ const npm = require("npm-stat-api"); const Enum = require("enum"); const csv = require("csv-parser"); const createCsvWriter = require("csv-writer").createArrayCsvWriter; +const fetch = require("node-fetch"); const StatDate = require("./statdate.js"); @@ -119,6 +120,16 @@ class WriteNpmStat { }); } + /** + * Returns last week npm statistics for a package + * @returns {Promise} Promise object represents the last week npm statistics for a package + */ + getLastWeekNpmStat() { + return new Promise((resolve) => { + return resolve(this.#getLastWeekStat(100)); + }); + } + static getDays(startDate, endDate) { const arr = []; const dt = new Date(startDate); @@ -151,6 +162,45 @@ class WriteNpmStat { }); } + #getLastWeekStat(retryLimit, retryCount) { + retryLimit = retryLimit || Number.MAX_VALUE; + retryCount = Math.max(retryCount || 0, 0); + return fetch( + "https://api.npmjs.org/versions/" + this.#packageName + "/last-week" + ) + .then((response) => { + if (response.status !== 200) { + throw new Error("response.status: " + response.status); + } + return new Promise((resolve) => { + response.json().then((response) => { + const responseObject = {}; + const day = StatDate.formatStart(new Date()); + for (const [key, value] of Object.entries( + response.downloads + )) { + const statKey = day + "_" + key; + const statValues = []; + statValues.push(day); + if (this.writePackageName) { + statValues.push(this.#packageName); + } + statValues.push(key); + statValues.push(value); + responseObject[statKey] = statValues; + } + return resolve(responseObject); + }); + }); + }) + .catch((err) => { + if (retryCount < retryLimit) { + return this.#getLastWeekStat(retryLimit, retryCount + 1); + } + throw err; + }); + } + /** * Writes npm statistics for a package * @param {string|null} [startDate] - start date of the statistics @@ -173,28 +223,56 @@ class WriteNpmStat { *
   - "%Y-%m-%d", for example "2022-12-31", which means to be collected until "2022-12-31" * *
   - undefined, which means to be collected until the actual day - * @param {string|null} [endDate=npmstat] - postfix of the csv file + * @param {string|null} [postfix=npmstat] - postfix of the csv file * @returns {Promise} Promise object represents the npm statistics for a package */ writeNpmStat(startDate, endDate, postfix = "npmstat") { return new Promise((resolve) => { + const lastWeek = false; const stats = this.getNpmStat(startDate, endDate); stats.then((stats) => { const grouped = this.#groupStats( + lastWeek, stats, startDate, endDate, postfix ); - this.#mergeStats(grouped).then((merged) => { - this.#writeStats(merged); + this.#mergeStats(lastWeek, grouped).then((merged) => { + this.#writeStats(lastWeek, merged); return resolve(merged); }); }); }); } - #groupStats(stats, startDate, endDate, postfix) { + /** + * Writes last week npm statistics for a package + * @param {string|null} [postfix=lastweek_npmstat] - postfix of the csv file + * @returns {Promise} Promise object represents the last week npm statistics for a package + */ + writeLastWeekNpmStat(postfix = "lastweek_npmstat") { + return new Promise((resolve) => { + const lastWeek = true; + const stats = this.getLastWeekNpmStat(); + stats.then((stats) => { + const day = StatDate.formatStart(new Date()); + const grouped = this.#groupStats( + lastWeek, + stats, + day, + day, + postfix + ); + this.#mergeStats(lastWeek, grouped).then((merged) => { + this.#writeStats(lastWeek, merged); + return resolve(merged); + }); + }); + }); + } + + #groupStats(lastWeek, stats, startDate, endDate, postfix) { const statDate = new StatDate(startDate, endDate); const days = WriteNpmStat.getDays(statDate.start, statDate.end); const grouped = {}; @@ -212,9 +290,17 @@ class WriteNpmStat { const prefix = day.substring(0, substring); if (!initialized[prefix]) { initialized[prefix] = true; - grouped[prefix + "_" + postfix + ".csv"] = [ - [day, stats[day]], - ]; + grouped[prefix + "_" + postfix + ".csv"] = []; + } + if (lastWeek) { + for (const [key, value] of Object.entries(stats)) { + if (key.startsWith(day)) { + grouped[prefix + "_" + postfix + ".csv"].push([ + key, + value, + ]); + } + } } else { grouped[prefix + "_" + postfix + ".csv"].push([ day, @@ -225,13 +311,21 @@ class WriteNpmStat { } else { grouped[postfix + ".csv"] = []; days.forEach((day) => { - grouped[postfix + ".csv"].push([day, stats[day]]); + if (lastWeek) { + for (const [key, value] of Object.entries(stats)) { + if (key.startsWith(day)) { + grouped[postfix + ".csv"].push([key, value]); + } + } + } else { + grouped[postfix + ".csv"].push([day, stats[day]]); + } }); } return grouped; } - #mergeStats(stats) { + #mergeStats(lastWeek, stats) { return new Promise((resolve) => { if (!this.mergeStoredData) { return resolve(stats); @@ -239,7 +333,7 @@ class WriteNpmStat { const csvFiles = {}; const csvFilesReady = []; for (const [key, value] of Object.entries(stats)) { - const csvFileReady = this.#readCsv(key, value[0]); + const csvFileReady = this.#readCsv(lastWeek, key, value[0]); csvFilesReady.push(csvFileReady); csvFileReady.then((csvData) => { Object.assign(csvFiles, csvData); @@ -256,7 +350,7 @@ class WriteNpmStat { }); } - #readCsv(csvFile, firstNewLine) { + #readCsv(lastWeek, csvFile, firstNewLine) { return new Promise((resolve) => { const csvData = {}; csvData[csvFile] = []; @@ -273,13 +367,16 @@ class WriteNpmStat { .pipe(csv()) .on("data", (row) => { if (firstNewLine) { - if (row.date < firstNewLine[0]) { + if (row.date < firstNewLine[0].substring(0, 10)) { const statKey = row.date; const statValues = []; statValues.push(row.date); if (writePackageName) { statValues.push(row.package); } + if (lastWeek) { + statValues.push(row.version); + } statValues.push(row.downloads); csvData[csvFile].push([statKey, statValues]); } @@ -292,7 +389,7 @@ class WriteNpmStat { }); } - #writeStats(stats) { + #writeStats(lastWeek, stats) { if (this.outDir) { fs.mkdir(this.outDir, { recursive: true }, (err) => { if (err) { @@ -300,11 +397,17 @@ class WriteNpmStat { } for (const [key, value] of Object.entries(stats)) { const csvFilePath = this.outDir + "/" + key; + const header = ["date"]; + if (this.writePackageName) { + header.push("package"); + } + if (lastWeek) { + header.push("version"); + } + header.push("downloads"); const csvWriter = createCsvWriter({ path: csvFilePath, - header: this.writePackageName - ? ["date", "package", "downloads"] - : ["date", "downloads"], + header, }); const postProcessedStats = []; value.forEach((stat) => {