From 6e94888aa3d4ef76b8d37974ada7ed71b2358e57 Mon Sep 17 00:00:00 2001 From: Saneef Ansari Date: Fri, 26 Dec 2025 12:00:04 +0530 Subject: [PATCH 01/17] memoize the functions --- src/getCollectionNewestGitCommitDate.js | 13 ++++++----- src/getGitCommitDateFromPath.js | 10 +++++---- src/utils/memoize.js | 29 +++++++++++++++++++++++++ 3 files changed, 43 insertions(+), 9 deletions(-) create mode 100644 src/utils/memoize.js diff --git a/src/getCollectionNewestGitCommitDate.js b/src/getCollectionNewestGitCommitDate.js index 64db7c9..60fec8d 100644 --- a/src/getCollectionNewestGitCommitDate.js +++ b/src/getCollectionNewestGitCommitDate.js @@ -1,14 +1,15 @@ // @ts-check + const getGitCommitDateFromPath = require("./getGitCommitDateFromPath"); +const memoize = require("./utils/memoize"); /** * Gets the collection's newest Git commit date. * * @param {Array} collection The collection - * - * @return {Date} The collection newest git commit date. + * @return {Date | undefined} The collection newest git commit date. */ -module.exports = function (collection) { +function getCollectionNewestGitCommitDate(collection) { if (!collection || !collection.length) { return; } @@ -18,9 +19,11 @@ module.exports = function (collection) { // Timestamps will be undefined for the paths not // yet commited to Git. So weeding them out. .filter((ts) => Boolean(ts)) - .map((ts) => ts.getTime()); + .map((ts) => /** @type Date */ (ts).getTime()); if (timestamps.length) { return new Date(Math.max(...timestamps)); } -}; +} + +module.exports = memoize(getCollectionNewestGitCommitDate); diff --git a/src/getGitCommitDateFromPath.js b/src/getGitCommitDateFromPath.js index 0865abc..5d9ffa7 100644 --- a/src/getGitCommitDateFromPath.js +++ b/src/getGitCommitDateFromPath.js @@ -1,6 +1,7 @@ // @ts-check const path = require("path"); const spawn = require("cross-spawn"); +const memoize = require("./utils/memoize"); /** * Gets the Git commit date from path. @@ -9,10 +10,9 @@ const spawn = require("cross-spawn"); * https://github.com/vuejs/vuepress/blob/master/packages/%40vuepress/plugin-last-updated/ * * @param {string} filePath The file path - * - * @return {Date} The git commit date if path is commited to Git. + * @return {Date | undefined} The git commit date if path is commited to Git. */ -module.exports = function (filePath) { +function getGitCommitDateFromPath(filePath) { let output; try { @@ -34,4 +34,6 @@ module.exports = function (filePath) { return new Date(ts); } } -}; +} + +module.exports = memoize(getGitCommitDateFromPath); diff --git a/src/utils/memoize.js b/src/utils/memoize.js new file mode 100644 index 0000000..b7052cb --- /dev/null +++ b/src/utils/memoize.js @@ -0,0 +1,29 @@ +/** + * Memoize function + * + * The code is adapted from MemoizeFunction() of Eleventy project + * See https://github.com/11ty/eleventy/blob/5a65b244235bfcb64ecf085bfc65a99a670e9df4/src/Util/MemoizeFunction.js + * + * @param {Function} fn The function to memoize + * @return {Function} + */ +function memoize(fn) { + const cache = new Map(); + + return (...args) => { + if (args.filter(Boolean).length > 1) { + console.warn("memoize() only supports single argument functions"); + return fn(...args); + } + + const [cacheKey] = args; + + if (!cache.has(cacheKey)) { + cache.set(cacheKey, fn(...args)); + } + + return cache.get(cacheKey); + }; +} + +module.exports = memoize; From e22edb39443a5fd287fb586ce6090a9efcb9b2a4 Mon Sep 17 00:00:00 2001 From: Saneef Ansari Date: Fri, 26 Dec 2025 12:58:36 +0530 Subject: [PATCH 02/17] ci: adds github workflow to run tests --- .github/workflows/ci.yml | 26 ++++++++++++++++++++++++++ .travis.yml | 16 ---------------- 2 files changed, 26 insertions(+), 16 deletions(-) create mode 100644 .github/workflows/ci.yml delete mode 100644 .travis.yml diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..4c334af --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,26 @@ +on: + push: + branches-ignore: + - "gh-pages" +jobs: + build: + runs-on: ${{ matrix.os }} + strategy: + matrix: + os: ["ubuntu-latest", "macos-latest", "windows-latest"] + node: ["18", "22"] + name: Node.js ${{ matrix.node }} on ${{ matrix.os }} + steps: + - name: Checkout repo + uses: actions/checkout@v3 + with: + fetch-depth: 0 + - name: Setup node + uses: actions/setup-node@v3 + with: + node-version: ${{ matrix.node }} + - run: npm install + - run: npm run lint + - run: npm test +env: + YARN_GPG: no \ No newline at end of file diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index 37b1386..0000000 --- a/.travis.yml +++ /dev/null @@ -1,16 +0,0 @@ -language: node_js - -node_js: - - 14 - - 16 - -os: - - linux - - osx - - windows - -before_script: - - npm install -script: - - npm run lint - - npm run test From 036b2808aeecc595a55df8b8c6c1e43be6b2d42e Mon Sep 17 00:00:00 2001 From: Saneef Ansari Date: Fri, 26 Dec 2025 13:41:42 +0530 Subject: [PATCH 03/17] test: bumps rimraf to v5.0 --- package.json | 4 ++-- tests/getCollectionNewestGitCommitDate.js | 3 +-- tests/getGitCommitDateFromPath.js | 3 +-- 3 files changed, 4 insertions(+), 6 deletions(-) diff --git a/package.json b/package.json index d37d908..13af87e 100644 --- a/package.json +++ b/package.json @@ -33,9 +33,9 @@ "eslint-plugin-prettier": "^3.4.0", "husky": "^7.0.1", "lint-staged": "^11.1.2", - "nyc": "^15.1.0", + "nyc": "^17.1.0", "prettier": "^2.3.2", - "rimraf": "^3.0.2" + "rimraf": "^5.0.9" }, "lint-staged": { "*.js": "eslint --cache --fix", diff --git a/tests/getCollectionNewestGitCommitDate.js b/tests/getCollectionNewestGitCommitDate.js index 7478f06..a0da2ea 100644 --- a/tests/getCollectionNewestGitCommitDate.js +++ b/tests/getCollectionNewestGitCommitDate.js @@ -1,8 +1,7 @@ const test = require("ava"); const path = require("path"); const fs = require("fs/promises"); -const { promisify } = require("util"); -const rimraf = promisify(require("rimraf")); +const { rimraf } = require("rimraf"); const getCollectionNewestGitCommitDate = require("../src/getCollectionNewestGitCommitDate.js"); const outputBase = path.join("tests/output/"); diff --git a/tests/getGitCommitDateFromPath.js b/tests/getGitCommitDateFromPath.js index ca3314c..002c20a 100644 --- a/tests/getGitCommitDateFromPath.js +++ b/tests/getGitCommitDateFromPath.js @@ -1,8 +1,7 @@ const test = require("ava"); const path = require("path"); const fs = require("fs/promises"); -const { promisify } = require("util"); -const rimraf = promisify(require("rimraf")); +const { rimraf } = require("rimraf"); const getGitCommitDateFromPath = require("../src/getGitCommitDateFromPath.js"); const outputBase = path.join("tests/output/"); From e43b0b4fea6ee9c5cf414237cda1a3aa0fc82431 Mon Sep 17 00:00:00 2001 From: Saneef Ansari Date: Fri, 26 Dec 2025 14:08:33 +0530 Subject: [PATCH 04/17] chore: bumps prettier and eslint - Adds prettier-plugin-jsdoc - Prettify JS files --- .eslintrc.js | 11 --------- eslint.config.js | 31 +++++++++++++++++++++++++ package.json | 14 +++++++---- prettier.config.js | 6 +++++ src/getCollectionNewestGitCommitDate.js | 4 ++-- src/getGitCommitDateFromPath.js | 6 ++--- src/utils/memoize.js | 8 +++---- 7 files changed, 55 insertions(+), 25 deletions(-) delete mode 100644 .eslintrc.js create mode 100644 eslint.config.js create mode 100644 prettier.config.js diff --git a/.eslintrc.js b/.eslintrc.js deleted file mode 100644 index 41b891e..0000000 --- a/.eslintrc.js +++ /dev/null @@ -1,11 +0,0 @@ -module.exports = { - root: true, - extends: ["xo-space", "plugin:prettier/recommended"], - plugins: ["prettier"], - rules: { - "prettier/prettier": "error", - }, - env: { - browser: true, - }, -}; diff --git a/eslint.config.js b/eslint.config.js new file mode 100644 index 0000000..a4c9fc8 --- /dev/null +++ b/eslint.config.js @@ -0,0 +1,31 @@ +const { defineConfig } = require("eslint/config"); +const prettier = require("eslint-plugin-prettier"); +const globals = require("globals"); +const js = require("@eslint/js"); +const { FlatCompat } = require("@eslint/eslintrc"); + +const compat = new FlatCompat({ + baseDirectory: __dirname, + recommendedConfig: js.configs.recommended, + allConfig: js.configs.all, +}); + +module.exports = defineConfig([ + { + extends: compat.extends("xo-space", "plugin:prettier/recommended"), + + plugins: { + prettier, + }, + + rules: { + "prettier/prettier": "error", + }, + + languageOptions: { + globals: { + ...globals.browser, + }, + }, + }, +]); diff --git a/package.json b/package.json index 13af87e..99ff780 100644 --- a/package.json +++ b/package.json @@ -26,15 +26,19 @@ "cross-spawn": "^7.0.3" }, "devDependencies": { + "@eslint/eslintrc": "^3.3.3", + "@eslint/js": "^9.39.2", "ava": "^3.15.0", - "eslint": "^7.32.0", - "eslint-config-prettier": "^8.3.0", - "eslint-config-xo-space": "^0.29.0", - "eslint-plugin-prettier": "^3.4.0", + "eslint": "^9.39.2", + "eslint-config-prettier": "^10.1.8", + "eslint-config-xo-space": "^0.35.0", + "eslint-plugin-prettier": "^5.5.4", + "globals": "^16.5.0", "husky": "^7.0.1", "lint-staged": "^11.1.2", "nyc": "^17.1.0", - "prettier": "^2.3.2", + "prettier": "^3.7.4", + "prettier-plugin-jsdoc": "^1.8.0", "rimraf": "^5.0.9" }, "lint-staged": { diff --git a/prettier.config.js b/prettier.config.js new file mode 100644 index 0000000..84e8ce6 --- /dev/null +++ b/prettier.config.js @@ -0,0 +1,6 @@ +/** @type {import("prettier").Config} */ +const config = { + plugins: ["prettier-plugin-jsdoc"], +}; + +module.exports = config; diff --git a/src/getCollectionNewestGitCommitDate.js b/src/getCollectionNewestGitCommitDate.js index 60fec8d..3ff42d8 100644 --- a/src/getCollectionNewestGitCommitDate.js +++ b/src/getCollectionNewestGitCommitDate.js @@ -6,8 +6,8 @@ const memoize = require("./utils/memoize"); /** * Gets the collection's newest Git commit date. * - * @param {Array} collection The collection - * @return {Date | undefined} The collection newest git commit date. + * @param {object[]} collection The collection + * @returns {Date | undefined} The collection newest git commit date. */ function getCollectionNewestGitCommitDate(collection) { if (!collection || !collection.length) { diff --git a/src/getGitCommitDateFromPath.js b/src/getGitCommitDateFromPath.js index 5d9ffa7..567b4fe 100644 --- a/src/getGitCommitDateFromPath.js +++ b/src/getGitCommitDateFromPath.js @@ -9,8 +9,8 @@ const memoize = require("./utils/memoize"); * The code is based on @vuepress/plugin-last-updated, * https://github.com/vuejs/vuepress/blob/master/packages/%40vuepress/plugin-last-updated/ * - * @param {string} filePath The file path - * @return {Date | undefined} The git commit date if path is commited to Git. + * @param {string} filePath The file path + * @returns {Date | undefined} The git commit date if path is commited to Git. */ function getGitCommitDateFromPath(filePath) { let output; @@ -19,7 +19,7 @@ function getGitCommitDateFromPath(filePath) { output = spawn.sync( "git", ["log", "-1", "--format=%at", path.basename(filePath)], - { cwd: path.dirname(filePath) } + { cwd: path.dirname(filePath) }, ); } catch { throw new Error("Fail to run 'git log'"); diff --git a/src/utils/memoize.js b/src/utils/memoize.js index b7052cb..7fc1add 100644 --- a/src/utils/memoize.js +++ b/src/utils/memoize.js @@ -1,11 +1,11 @@ /** * Memoize function * - * The code is adapted from MemoizeFunction() of Eleventy project - * See https://github.com/11ty/eleventy/blob/5a65b244235bfcb64ecf085bfc65a99a670e9df4/src/Util/MemoizeFunction.js + * The code is adapted from MemoizeFunction() of Eleventy project See + * https://github.com/11ty/eleventy/blob/5a65b244235bfcb64ecf085bfc65a99a670e9df4/src/Util/MemoizeFunction.js * - * @param {Function} fn The function to memoize - * @return {Function} + * @param {Function} fn The function to memoize + * @returns {Function} */ function memoize(fn) { const cache = new Map(); From e39147d638816217e07f432f9aab2437bea6d9cb Mon Sep 17 00:00:00 2001 From: Saneef Ansari Date: Fri, 26 Dec 2025 14:20:00 +0530 Subject: [PATCH 05/17] chore: bumps ava to v6 - Move out ava config from package.json to ava.config.cjs --- ava.config.cjs | 6 ++++++ package.json | 11 +---------- 2 files changed, 7 insertions(+), 10 deletions(-) create mode 100644 ava.config.cjs diff --git a/ava.config.cjs b/ava.config.cjs new file mode 100644 index 0000000..073ae68 --- /dev/null +++ b/ava.config.cjs @@ -0,0 +1,6 @@ +module.exports = { + files: ["tests/**/*", "!tests/utils.js"], + watchMode: { + ignoreChanges: ["tests/output/**"], + }, +}; \ No newline at end of file diff --git a/package.json b/package.json index 99ff780..6154ea6 100644 --- a/package.json +++ b/package.json @@ -28,7 +28,7 @@ "devDependencies": { "@eslint/eslintrc": "^3.3.3", "@eslint/js": "^9.39.2", - "ava": "^3.15.0", + "ava": "^6.4.1", "eslint": "^9.39.2", "eslint-config-prettier": "^10.1.8", "eslint-config-xo-space": "^0.35.0", @@ -44,14 +44,5 @@ "lint-staged": { "*.js": "eslint --cache --fix", "*.{js,md,json}": "prettier --write" - }, - "ava": { - "files": [ - "tests/**/*", - "!tests/utils.js" - ], - "ignoredByWatcher": [ - "tests/output/**" - ] } } From c1ad76c6f3dfe25ebacf137ab35905a06d6f2f7c Mon Sep 17 00:00:00 2001 From: Saneef Ansari Date: Fri, 26 Dec 2025 16:02:39 +0530 Subject: [PATCH 06/17] makes ESM module --- .eleventy.js | 18 ------------------ ava.config.cjs => ava.config.js | 4 ++-- eslint.config.js | 18 ++++++++++++------ index.js | 16 ++++++++++++++++ package.json | 3 ++- prettier.config.js | 2 +- src/getCollectionNewestGitCommitDate.js | 7 +++---- src/getGitCommitDateFromPath.js | 8 ++++---- src/utils/memoize.js | 4 +--- tests/getCollectionNewestGitCommitDate.js | 18 ++++++++++-------- tests/getGitCommitDateFromPath.js | 23 +++++++++++++---------- 11 files changed, 64 insertions(+), 57 deletions(-) delete mode 100644 .eleventy.js rename ava.config.cjs => ava.config.js (83%) create mode 100644 index.js diff --git a/.eleventy.js b/.eleventy.js deleted file mode 100644 index 0195bc3..0000000 --- a/.eleventy.js +++ /dev/null @@ -1,18 +0,0 @@ -// @ts-check -const getGitCommitDateFromPath = require("./src/getGitCommitDateFromPath"); -const getCollectionNewestGitCommitDate = require("./src/getCollectionNewestGitCommitDate"); - -module.exports = function (eleventyConfig) { - eleventyConfig.addFilter( - "getGitCommitDateFromPath", - getGitCommitDateFromPath - ); - eleventyConfig.addFilter( - "getCollectionNewestGitCommitDate", - getCollectionNewestGitCommitDate - ); -}; - -module.exports.getGitCommitDateFromPath = getGitCommitDateFromPath; -module.exports.getCollectionNewestGitCommitDate = - getCollectionNewestGitCommitDate; diff --git a/ava.config.cjs b/ava.config.js similarity index 83% rename from ava.config.cjs rename to ava.config.js index 073ae68..d283bd1 100644 --- a/ava.config.cjs +++ b/ava.config.js @@ -1,6 +1,6 @@ -module.exports = { +export default { files: ["tests/**/*", "!tests/utils.js"], watchMode: { ignoreChanges: ["tests/output/**"], }, -}; \ No newline at end of file +}; diff --git a/eslint.config.js b/eslint.config.js index a4c9fc8..50bd80a 100644 --- a/eslint.config.js +++ b/eslint.config.js @@ -1,8 +1,14 @@ -const { defineConfig } = require("eslint/config"); -const prettier = require("eslint-plugin-prettier"); -const globals = require("globals"); -const js = require("@eslint/js"); -const { FlatCompat } = require("@eslint/eslintrc"); +import { FlatCompat } from "@eslint/eslintrc"; +import js from "@eslint/js"; +import prettier from "eslint-plugin-prettier"; +import { defineConfig } from "eslint/config"; +import globals from "globals"; +import path from "path"; +import { fileURLToPath } from "url"; + +// Mimic CommonJS variables -- not needed if using CommonJS +const __filename = fileURLToPath(import.meta.url); +const __dirname = path.dirname(__filename); const compat = new FlatCompat({ baseDirectory: __dirname, @@ -10,7 +16,7 @@ const compat = new FlatCompat({ allConfig: js.configs.all, }); -module.exports = defineConfig([ +export default defineConfig([ { extends: compat.extends("xo-space", "plugin:prettier/recommended"), diff --git a/index.js b/index.js new file mode 100644 index 0000000..d8928e2 --- /dev/null +++ b/index.js @@ -0,0 +1,16 @@ +// @ts-check +import getCollectionNewestGitCommitDate from "./src/getCollectionNewestGitCommitDate.js"; +import getGitCommitDateFromPath from "./src/getGitCommitDateFromPath.js"; + +export default function (eleventyConfig) { + eleventyConfig.addFilter( + "getGitCommitDateFromPath", + getGitCommitDateFromPath, + ); + eleventyConfig.addFilter( + "getCollectionNewestGitCommitDate", + getCollectionNewestGitCommitDate, + ); +} + +export { getCollectionNewestGitCommitDate, getGitCommitDateFromPath }; diff --git a/package.json b/package.json index 6154ea6..a312cae 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,8 @@ "name": "eleventy-plugin-git-commit-date", "version": "0.1.3", "description": "Eleventy plugin to get Git commit time of a file, or a Eleventy collection.", - "main": ".eleventy.js", + "main": "index.js", + "type": "module", "repository": { "type": "git", "url": "git+https://github.com:saneef/eleventy-plugin-git-commit-date.git" diff --git a/prettier.config.js b/prettier.config.js index 84e8ce6..da08129 100644 --- a/prettier.config.js +++ b/prettier.config.js @@ -3,4 +3,4 @@ const config = { plugins: ["prettier-plugin-jsdoc"], }; -module.exports = config; +export default config; diff --git a/src/getCollectionNewestGitCommitDate.js b/src/getCollectionNewestGitCommitDate.js index 3ff42d8..5d63142 100644 --- a/src/getCollectionNewestGitCommitDate.js +++ b/src/getCollectionNewestGitCommitDate.js @@ -1,7 +1,6 @@ // @ts-check - -const getGitCommitDateFromPath = require("./getGitCommitDateFromPath"); -const memoize = require("./utils/memoize"); +import getGitCommitDateFromPath from "./getGitCommitDateFromPath.js"; +import memoize from "./utils/memoize.js"; /** * Gets the collection's newest Git commit date. @@ -26,4 +25,4 @@ function getCollectionNewestGitCommitDate(collection) { } } -module.exports = memoize(getCollectionNewestGitCommitDate); +export default memoize(getCollectionNewestGitCommitDate); diff --git a/src/getGitCommitDateFromPath.js b/src/getGitCommitDateFromPath.js index 567b4fe..e3e36cd 100644 --- a/src/getGitCommitDateFromPath.js +++ b/src/getGitCommitDateFromPath.js @@ -1,7 +1,7 @@ // @ts-check -const path = require("path"); -const spawn = require("cross-spawn"); -const memoize = require("./utils/memoize"); +import spawn from "cross-spawn"; +import path from "node:path"; +import memoize from "./utils/memoize.js"; /** * Gets the Git commit date from path. @@ -36,4 +36,4 @@ function getGitCommitDateFromPath(filePath) { } } -module.exports = memoize(getGitCommitDateFromPath); +export default memoize(getGitCommitDateFromPath); diff --git a/src/utils/memoize.js b/src/utils/memoize.js index 7fc1add..0e2a6fa 100644 --- a/src/utils/memoize.js +++ b/src/utils/memoize.js @@ -7,7 +7,7 @@ * @param {Function} fn The function to memoize * @returns {Function} */ -function memoize(fn) { +export default function memoize(fn) { const cache = new Map(); return (...args) => { @@ -25,5 +25,3 @@ function memoize(fn) { return cache.get(cacheKey); }; } - -module.exports = memoize; diff --git a/tests/getCollectionNewestGitCommitDate.js b/tests/getCollectionNewestGitCommitDate.js index a0da2ea..d80366b 100644 --- a/tests/getCollectionNewestGitCommitDate.js +++ b/tests/getCollectionNewestGitCommitDate.js @@ -1,10 +1,11 @@ -const test = require("ava"); -const path = require("path"); -const fs = require("fs/promises"); -const { rimraf } = require("rimraf"); -const getCollectionNewestGitCommitDate = require("../src/getCollectionNewestGitCommitDate.js"); +import test from "ava"; +import { mkdir, writeFile } from "node:fs/promises"; +import path from "node:path"; +import { rimraf } from "rimraf"; +import { fileURLToPath } from "url"; +import { getCollectionNewestGitCommitDate } from "../index.js"; -const outputBase = path.join("tests/output/"); +const __dirname = path.dirname(fileURLToPath(import.meta.url)); test("Get newest commit date of collection", (t) => { const collection = [ @@ -23,6 +24,7 @@ test("Shouldn't get commit date from an empty collection", async (t) => { }); test("Shouldn't get commit date from collection of uncommited files", async (t) => { + const outputBase = path.join("tests/output/"); const collection = [ { inputPath: path.join(outputBase, "test-01.md") }, { inputPath: path.join(outputBase, "test-02.md") }, @@ -30,8 +32,8 @@ test("Shouldn't get commit date from collection of uncommited files", async (t) await rimraf(outputBase); - await fs.mkdir(outputBase, { recursive: true }); - await Promise.all(collection.map((p) => fs.writeFile(p.inputPath, ""))); + await mkdir(outputBase, { recursive: true }); + await Promise.all(collection.map((p) => writeFile(p.inputPath, ""))); t.is(getCollectionNewestGitCommitDate(collection), undefined); diff --git a/tests/getGitCommitDateFromPath.js b/tests/getGitCommitDateFromPath.js index 002c20a..9c2e29a 100644 --- a/tests/getGitCommitDateFromPath.js +++ b/tests/getGitCommitDateFromPath.js @@ -1,11 +1,11 @@ -const test = require("ava"); -const path = require("path"); -const fs = require("fs/promises"); -const { rimraf } = require("rimraf"); -const getGitCommitDateFromPath = require("../src/getGitCommitDateFromPath.js"); +import test from "ava"; +import { mkdir, writeFile } from "node:fs/promises"; +import path from "node:path"; +import { rimraf } from "rimraf"; +import { fileURLToPath } from "url"; +import { getGitCommitDateFromPath } from "../index.js"; -const outputBase = path.join("tests/output/"); -const tempFileName = "test.md"; +const __dirname = path.dirname(fileURLToPath(import.meta.url)); test("Get commit date of a committed file", (t) => { const filePath = path.join(__dirname, "./fixtures/sample.md"); @@ -15,11 +15,14 @@ test("Get commit date of a committed file", (t) => { }); test("Should not get commit date of a uncommitted file", async (t) => { - const filePath = path.join(outputBase, tempFileName); + const outputBase = path.join(__dirname, "/output/"); + const filePath = path.join(outputBase, "test.md"); await rimraf(outputBase); - await fs.mkdir(outputBase, { recursive: true }); - await fs.writeFile(filePath, ""); + await mkdir(outputBase, { recursive: true }, (err) => { + console.error(err); + }); + await writeFile(filePath, ""); t.is(getGitCommitDateFromPath(filePath), undefined); From cc2e52c057ee419be76ed909f30da12e9fcc39ef Mon Sep 17 00:00:00 2001 From: Saneef Ansari Date: Fri, 26 Dec 2025 16:28:01 +0530 Subject: [PATCH 07/17] replaces husky with lefthook --- .husky/.gitignore | 1 - .husky/pre-commit | 5 ----- lefthook.yml | 14 ++++++++++++++ package.json | 10 ++-------- 4 files changed, 16 insertions(+), 14 deletions(-) delete mode 100644 .husky/.gitignore delete mode 100755 .husky/pre-commit create mode 100644 lefthook.yml diff --git a/.husky/.gitignore b/.husky/.gitignore deleted file mode 100644 index 31354ec..0000000 --- a/.husky/.gitignore +++ /dev/null @@ -1 +0,0 @@ -_ diff --git a/.husky/pre-commit b/.husky/pre-commit deleted file mode 100755 index 6a06103..0000000 --- a/.husky/pre-commit +++ /dev/null @@ -1,5 +0,0 @@ -#!/bin/sh -. "$(dirname "$0")/_/husky.sh" - -npm test -npx lint-staged \ No newline at end of file diff --git a/lefthook.yml b/lefthook.yml new file mode 100644 index 0000000..d465849 --- /dev/null +++ b/lefthook.yml @@ -0,0 +1,14 @@ +pre-commit: + commands: + prettier: + glob: "*.{js,md,json}" + run: prettier --write {staged_files} + stage_fixed: true + + eslint: + glob: "*.{js}" + run: eslint --fix {staged_files} + stage_fixed: true + + tests: + run: npm test \ No newline at end of file diff --git a/package.json b/package.json index a312cae..6eeb4b8 100644 --- a/package.json +++ b/package.json @@ -10,8 +10,7 @@ }, "scripts": { "lint": "eslint src/**.js tests/**.js", - "test": "nyc ava --timeout=1m -v --color", - "prepare": "husky install" + "test": "nyc ava --timeout=1m -v --color" }, "keywords": [ "last-updated", @@ -35,15 +34,10 @@ "eslint-config-xo-space": "^0.35.0", "eslint-plugin-prettier": "^5.5.4", "globals": "^16.5.0", - "husky": "^7.0.1", - "lint-staged": "^11.1.2", + "lefthook": "^2.0.13", "nyc": "^17.1.0", "prettier": "^3.7.4", "prettier-plugin-jsdoc": "^1.8.0", "rimraf": "^5.0.9" - }, - "lint-staged": { - "*.js": "eslint --cache --fix", - "*.{js,md,json}": "prettier --write" } } From c74a3bd18e8cc9b90d4d0088c79df22663e8a6cd Mon Sep 17 00:00:00 2001 From: Saneef Ansari Date: Fri, 26 Dec 2025 16:40:49 +0530 Subject: [PATCH 08/17] updates npmignore --- .npmignore | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/.npmignore b/.npmignore index c346e3e..f061151 100644 --- a/.npmignore +++ b/.npmignore @@ -1,9 +1,10 @@ .editorconfig .eslintcache -.eslintrc.js +.eslint.config.js .github/ -.husky/pre-commit .nyc_output tests -.travis.yml -.gitattributes \ No newline at end of file +.gitattributes +prettier.config.js +lefthook.yml +ava.config.js \ No newline at end of file From 7844e15ac43c0afef5d9ab8069f6f8762e8de8a8 Mon Sep 17 00:00:00 2001 From: Saneef Ansari Date: Fri, 26 Dec 2025 16:41:40 +0530 Subject: [PATCH 09/17] chore: run tests only on js file changes --- lefthook.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/lefthook.yml b/lefthook.yml index d465849..1666115 100644 --- a/lefthook.yml +++ b/lefthook.yml @@ -11,4 +11,5 @@ pre-commit: stage_fixed: true tests: + glob: "*.{js}" run: npm test \ No newline at end of file From e159c5ec3a21b5d9c4d473e8527f775c53741355 Mon Sep 17 00:00:00 2001 From: Saneef Ansari Date: Fri, 26 Dec 2025 16:43:05 +0530 Subject: [PATCH 10/17] removes nyc --- package.json | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/package.json b/package.json index 6eeb4b8..2c305bd 100644 --- a/package.json +++ b/package.json @@ -9,8 +9,7 @@ "url": "git+https://github.com:saneef/eleventy-plugin-git-commit-date.git" }, "scripts": { - "lint": "eslint src/**.js tests/**.js", - "test": "nyc ava --timeout=1m -v --color" + "test": "ava" }, "keywords": [ "last-updated", @@ -35,7 +34,6 @@ "eslint-plugin-prettier": "^5.5.4", "globals": "^16.5.0", "lefthook": "^2.0.13", - "nyc": "^17.1.0", "prettier": "^3.7.4", "prettier-plugin-jsdoc": "^1.8.0", "rimraf": "^5.0.9" From 68a0d16fd1808bd84b24f6d5576ce2e3f7b82b7e Mon Sep 17 00:00:00 2001 From: Saneef Ansari Date: Fri, 26 Dec 2025 16:48:47 +0530 Subject: [PATCH 11/17] fix(lefthook): fails to run prettier and eslint --- lefthook.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/lefthook.yml b/lefthook.yml index 1666115..4b49612 100644 --- a/lefthook.yml +++ b/lefthook.yml @@ -2,14 +2,14 @@ pre-commit: commands: prettier: glob: "*.{js,md,json}" - run: prettier --write {staged_files} + run: npx prettier --write {staged_files} stage_fixed: true eslint: glob: "*.{js}" - run: eslint --fix {staged_files} + run: npx eslint --fix {staged_files} stage_fixed: true tests: glob: "*.{js}" - run: npm test \ No newline at end of file + run: npm run test \ No newline at end of file From 874f21dfb0da5873d2e3a65d39704812d49da471 Mon Sep 17 00:00:00 2001 From: Saneef Ansari Date: Fri, 26 Dec 2025 16:49:26 +0530 Subject: [PATCH 12/17] chore: enables type check on config files --- eslint.config.js | 1 + prettier.config.js | 1 + 2 files changed, 2 insertions(+) diff --git a/eslint.config.js b/eslint.config.js index 50bd80a..08269f2 100644 --- a/eslint.config.js +++ b/eslint.config.js @@ -1,3 +1,4 @@ +// @ts-check import { FlatCompat } from "@eslint/eslintrc"; import js from "@eslint/js"; import prettier from "eslint-plugin-prettier"; diff --git a/prettier.config.js b/prettier.config.js index da08129..fb9800e 100644 --- a/prettier.config.js +++ b/prettier.config.js @@ -1,3 +1,4 @@ +// @ts-check /** @type {import("prettier").Config} */ const config = { plugins: ["prettier-plugin-jsdoc"], From b4d3c6d452c9323552e2e5aee80471541995841c Mon Sep 17 00:00:00 2001 From: Saneef Ansari Date: Fri, 26 Dec 2025 17:27:49 +0530 Subject: [PATCH 13/17] feat: make the filters async - Make you Eleventy builds much faster --- index.js | 4 ++-- src/getCollectionNewestGitCommitDate.js | 20 +++++++++------- src/getGitCommitDateFromPath.js | 29 ++++++++++------------- src/utils/spawn.js | 28 ++++++++++++++++++++++ tests/getCollectionNewestGitCommitDate.js | 10 ++++---- tests/getGitCommitDateFromPath.js | 12 ++++------ 6 files changed, 64 insertions(+), 39 deletions(-) create mode 100644 src/utils/spawn.js diff --git a/index.js b/index.js index d8928e2..c8412a3 100644 --- a/index.js +++ b/index.js @@ -3,11 +3,11 @@ import getCollectionNewestGitCommitDate from "./src/getCollectionNewestGitCommit import getGitCommitDateFromPath from "./src/getGitCommitDateFromPath.js"; export default function (eleventyConfig) { - eleventyConfig.addFilter( + eleventyConfig.addAsyncFilter( "getGitCommitDateFromPath", getGitCommitDateFromPath, ); - eleventyConfig.addFilter( + eleventyConfig.addAsyncFilter( "getCollectionNewestGitCommitDate", getCollectionNewestGitCommitDate, ); diff --git a/src/getCollectionNewestGitCommitDate.js b/src/getCollectionNewestGitCommitDate.js index 5d63142..fa0c940 100644 --- a/src/getCollectionNewestGitCommitDate.js +++ b/src/getCollectionNewestGitCommitDate.js @@ -5,23 +5,27 @@ import memoize from "./utils/memoize.js"; /** * Gets the collection's newest Git commit date. * - * @param {object[]} collection The collection - * @returns {Date | undefined} The collection newest git commit date. + * @param {object[]} collection Collection + * @returns {Promise} Newest git commit date among the items + * in the collection */ -function getCollectionNewestGitCommitDate(collection) { +async function getCollectionNewestGitCommitDate(collection) { if (!collection || !collection.length) { return; } - const timestamps = collection - .map((item) => getGitCommitDateFromPath(item.inputPath)) + const timestamps = await Promise.all( + collection.map((item) => getGitCommitDateFromPath(item.inputPath)), + ); + + const dates = timestamps // Timestamps will be undefined for the paths not // yet commited to Git. So weeding them out. .filter((ts) => Boolean(ts)) - .map((ts) => /** @type Date */ (ts).getTime()); + .map((ts) => ts.getTime()); - if (timestamps.length) { - return new Date(Math.max(...timestamps)); + if (dates.length) { + return new Date(Math.max(...dates)); } } diff --git a/src/getGitCommitDateFromPath.js b/src/getGitCommitDateFromPath.js index e3e36cd..d83fef0 100644 --- a/src/getGitCommitDateFromPath.js +++ b/src/getGitCommitDateFromPath.js @@ -1,7 +1,6 @@ // @ts-check -import spawn from "cross-spawn"; -import path from "node:path"; import memoize from "./utils/memoize.js"; +import { spawnAsync } from "./utils/spawn.js"; /** * Gets the Git commit date from path. @@ -10,29 +9,25 @@ import memoize from "./utils/memoize.js"; * https://github.com/vuejs/vuepress/blob/master/packages/%40vuepress/plugin-last-updated/ * * @param {string} filePath The file path - * @returns {Date | undefined} The git commit date if path is commited to Git. + * @returns {Promise} Commit date if path is commited to Git, + * otherwise `undefined` */ -function getGitCommitDateFromPath(filePath) { +async function getGitCommitDateFromPath(filePath) { let output; try { - output = spawn.sync( - "git", - ["log", "-1", "--format=%at", path.basename(filePath)], - { cwd: path.dirname(filePath) }, - ); - } catch { + output = await spawnAsync("git", ["log", "-1", "--format=%at", filePath]); + } catch (e) { + console.log(e); throw new Error("Fail to run 'git log'"); } - if (output && output.stdout) { - const ts = parseInt(output.stdout.toString("utf-8"), 10) * 1000; + const ts = parseInt(output, 10) * 1000; - // Paths not commited to Git returns empty timestamps, resulting in NaN. - // So, convert only valid timestamps. - if (!isNaN(ts)) { - return new Date(ts); - } + // Paths not commited to Git returns empty timestamps, resulting in NaN. + // So, convert only valid timestamps. + if (!isNaN(ts)) { + return new Date(ts); } } diff --git a/src/utils/spawn.js b/src/utils/spawn.js new file mode 100644 index 0000000..f4aa6cc --- /dev/null +++ b/src/utils/spawn.js @@ -0,0 +1,28 @@ +import { spawn } from "node:child_process"; + +export function spawnAsync(command, args, options) { + return new Promise((resolve, reject) => { + const cmd = spawn(command, args, options); + const res = []; + cmd.stdout.on("data", (data) => { + res.push(data.toString("utf8")); + }); + + const err = []; + cmd.stderr.on("data", (data) => { + err.push(data.toString("utf8")); + }); + + cmd.on("close", (code) => { + if (err.length > 0) { + reject(err.join("\n")); + } else if (code === 1) { + reject( + new Error("Internal error: process closed with error exit code."), + ); + } else { + resolve(res.join("\n")); + } + }); + }); +} diff --git a/tests/getCollectionNewestGitCommitDate.js b/tests/getCollectionNewestGitCommitDate.js index d80366b..d8c0469 100644 --- a/tests/getCollectionNewestGitCommitDate.js +++ b/tests/getCollectionNewestGitCommitDate.js @@ -7,12 +7,12 @@ import { getCollectionNewestGitCommitDate } from "../index.js"; const __dirname = path.dirname(fileURLToPath(import.meta.url)); -test("Get newest commit date of collection", (t) => { +test("Get newest commit date of collection", async (t) => { const collection = [ { inputPath: path.join(__dirname, "./fixtures/sample.md") }, { inputPath: path.join(__dirname, "./fixtures/another-sample-file.md") }, ]; - const date = getCollectionNewestGitCommitDate(collection); + const date = await getCollectionNewestGitCommitDate(collection); t.truthy(date); t.is(date.toISOString(), "2021-08-19T09:57:47.000Z"); }); @@ -20,11 +20,11 @@ test("Get newest commit date of collection", (t) => { test("Shouldn't get commit date from an empty collection", async (t) => { const collection = []; - t.is(getCollectionNewestGitCommitDate(collection), undefined); + t.is(await getCollectionNewestGitCommitDate(collection), undefined); }); test("Shouldn't get commit date from collection of uncommited files", async (t) => { - const outputBase = path.join("tests/output/"); + const outputBase = path.join(__dirname, "output/collection"); const collection = [ { inputPath: path.join(outputBase, "test-01.md") }, { inputPath: path.join(outputBase, "test-02.md") }, @@ -35,7 +35,7 @@ test("Shouldn't get commit date from collection of uncommited files", async (t) await mkdir(outputBase, { recursive: true }); await Promise.all(collection.map((p) => writeFile(p.inputPath, ""))); - t.is(getCollectionNewestGitCommitDate(collection), undefined); + t.is(await getCollectionNewestGitCommitDate(collection), undefined); await rimraf(outputBase); }); diff --git a/tests/getGitCommitDateFromPath.js b/tests/getGitCommitDateFromPath.js index 9c2e29a..4a0a1d7 100644 --- a/tests/getGitCommitDateFromPath.js +++ b/tests/getGitCommitDateFromPath.js @@ -7,24 +7,22 @@ import { getGitCommitDateFromPath } from "../index.js"; const __dirname = path.dirname(fileURLToPath(import.meta.url)); -test("Get commit date of a committed file", (t) => { +test("Get commit date of a committed file", async (t) => { const filePath = path.join(__dirname, "./fixtures/sample.md"); - const date = getGitCommitDateFromPath(filePath); + const date = await getGitCommitDateFromPath(filePath); t.truthy(date); t.is(date.toISOString(), "2021-08-19T09:57:47.000Z"); }); test("Should not get commit date of a uncommitted file", async (t) => { - const outputBase = path.join(__dirname, "/output/"); + const outputBase = path.join(__dirname, "/output/single-file-path"); const filePath = path.join(outputBase, "test.md"); await rimraf(outputBase); - await mkdir(outputBase, { recursive: true }, (err) => { - console.error(err); - }); + await mkdir(outputBase, { recursive: true }); await writeFile(filePath, ""); - t.is(getGitCommitDateFromPath(filePath), undefined); + t.is(await getGitCommitDateFromPath(filePath), undefined); await rimraf(outputBase); }); From 1545238efa8f232c20e75bd97a54828f280aad49 Mon Sep 17 00:00:00 2001 From: Saneef Ansari Date: Fri, 26 Dec 2025 17:59:08 +0530 Subject: [PATCH 14/17] fix(package.json): adds back lint script --- package.json | 1 + 1 file changed, 1 insertion(+) diff --git a/package.json b/package.json index 2c305bd..1f31175 100644 --- a/package.json +++ b/package.json @@ -9,6 +9,7 @@ "url": "git+https://github.com:saneef/eleventy-plugin-git-commit-date.git" }, "scripts": { + "lint": "eslint src/**.js tests/**.js", "test": "ava" }, "keywords": [ From 9085dad52ddce18638153f915a13aa28a4d31eff Mon Sep 17 00:00:00 2001 From: Saneef Ansari Date: Fri, 26 Dec 2025 18:58:49 +0530 Subject: [PATCH 15/17] fix(npmignore): wrong file name --- .npmignore | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.npmignore b/.npmignore index f061151..8aeaba2 100644 --- a/.npmignore +++ b/.npmignore @@ -1,10 +1,10 @@ .editorconfig .eslintcache -.eslint.config.js .github/ .nyc_output -tests .gitattributes +eslint.config.js +tests prettier.config.js lefthook.yml ava.config.js \ No newline at end of file From 9f8e97c80812c94453ef25ac257b01582053992c Mon Sep 17 00:00:00 2001 From: Saneef Ansari Date: Fri, 26 Dec 2025 19:02:51 +0530 Subject: [PATCH 16/17] chore(editorconfig): updates MD related settings --- .editorconfig | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.editorconfig b/.editorconfig index 7e66cb8..6726639 100644 --- a/.editorconfig +++ b/.editorconfig @@ -8,5 +8,5 @@ insert_final_newline = false trim_trailing_whitespace = true charset = utf-8 -[*.js] -insert_final_newline = true +[*.{js,md}] +insert_final_newline = true \ No newline at end of file From 18e368fc388d513c48f52c1d1f09035e40a28a28 Mon Sep 17 00:00:00 2001 From: Saneef Ansari Date: Fri, 26 Dec 2025 19:03:39 +0530 Subject: [PATCH 17/17] docs(readme): updates examples to ESM --- README.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index ed5dd47..b69bf4a 100644 --- a/README.md +++ b/README.md @@ -9,7 +9,7 @@ This Eleventy plugin provides two [template filters](https://www.11ty.dev/docs/f 🌏 This plugin is made primarily to populate `` fields in an RSS feed. Here is [a blog post on how to use this plugin](https://saneef.com/tutorials/fix-dates-on-eleventy-rss-feeds/) with [`eleventy-plugin-rss`](https://www.11ty.dev/docs/plugins/rss/). -⚠️ Getting Git commit date is a bit slow (\~50ms for each path). So, use it sparingly. It's recommended to call this filter within a production flag. +⚠️ Git commit date is a bit slow. So, it's recommended to call this filter within a production flag. ## Usage @@ -22,13 +22,13 @@ npm install eleventy-plugin-git-commit-date ### 2. Add to Eleventy config ```js -// .eleventy.js +// eleventy.config.js -const pluginGitCommitDate = require("eleventy-plugin-git-commit-date"); +import pluginGitCommitDate from "eleventy-plugin-git-commit-date"; -module.exports = function (eleventyConfig) { +export default function (eleventyConfig) { eleventyConfig.addPlugin(pluginGitCommitDate); -}; +} ``` ### 3. Use in templates