diff --git a/lib/release-ops.js b/lib/release-ops.js index 4e1c6e7..3c1acfa 100644 --- a/lib/release-ops.js +++ b/lib/release-ops.js @@ -5,17 +5,12 @@ * MIT License */ -/* global test, cat, rm, mv */ - "use strict"; //------------------------------------------------------------------------------ // Requirements //------------------------------------------------------------------------------ -// TODO: Update to use non-global module to avoid prototype pollution. -require("shelljs/global"); - const fs = require("fs"), path = require("path"), semver = require("semver"), @@ -49,6 +44,21 @@ const commitTagMap = new Map([ // /MIT/, /BSD/, /Apache/, /ISC/, /WTF/, /Public Domain/ // ]; +/** + * Tests if a file exists. + * @param {string} file The path of the file to be tested. + * @returns {boolean} `true` if the specified path denotes an existing file, otherwise `false`. + */ +function fileExists(file) { + + // We can't use the `throwIfNoEntry` option with `fs.statSync`, because it's not supported in Node.js 10, + // so we check if the file exists in advance. + if (!fs.existsSync(file)) { + return false; + } + return fs.statSync(file).isFile(); +} + /** * Loads the package.json file from the current directory. * @returns {void} @@ -66,7 +76,7 @@ function getPackageInfo() { * @private */ function validateSetup() { - if (!test("-f", "package.json")) { + if (!fileExists("package.json")) { console.error("Missing package.json file"); ShellOps.exit(1); } @@ -333,27 +343,27 @@ function calculateReleaseInfo(prereleaseId) { */ function writeChangelog(releaseInfo) { - // get most recent two tags + // get today's date in "mmmm d, yyyy" format const now = new Date(), - timestamp = dateformat(now, "mmmm d, yyyy"); - - // output header - (`v${releaseInfo.version} - ${timestamp}\n`).to("CHANGELOG.tmp"); + today = dateformat(now, "mmmm d, yyyy"); - // output changelog - (`\n${releaseInfo.rawChangelog}\n\n`).toEnd("CHANGELOG.tmp"); + // output header and changelog + fs.writeFileSync( + "CHANGELOG.tmp", + `v${releaseInfo.version} - ${today}\n\n${releaseInfo.rawChangelog}\n\n` + ); // ensure there's a CHANGELOG.md file - if (!test("-f", "CHANGELOG.md")) { + if (!fileExists("CHANGELOG.md")) { fs.writeFileSync("CHANGELOG.md", ""); } - // switch-o change-o - // `cat` returns a ShellString and `fs.writeFileSync` is throwing an error saying that this must be a String. - fs.writeFileSync("CHANGELOG.md.tmp", cat("CHANGELOG.tmp", "CHANGELOG.md").toString()); - rm("CHANGELOG.tmp"); - rm("CHANGELOG.md"); - mv("CHANGELOG.md.tmp", "CHANGELOG.md"); + const data = `${fs.readFileSync("CHANGELOG.tmp", "utf-8")}${fs.readFileSync("CHANGELOG.md", "utf-8")}`; + + fs.writeFileSync("CHANGELOG.md.tmp", data); + fs.unlinkSync("CHANGELOG.tmp"); + fs.unlinkSync("CHANGELOG.md"); + fs.renameSync("CHANGELOG.md.tmp", "CHANGELOG.md"); } /** diff --git a/package.json b/package.json index 3486d5d..d323a8b 100644 --- a/package.json +++ b/package.json @@ -52,7 +52,6 @@ "dateformat": "^3.0.3", "github-api": "^3.2.2", "linefix": "^0.1.1", - "semver": "^6.1.1", - "shelljs": "^0.8.3" + "semver": "^6.1.1" } } diff --git a/tests/lib/release-ops.js b/tests/lib/release-ops.js index 9d32e15..197b947 100644 --- a/tests/lib/release-ops.js +++ b/tests/lib/release-ops.js @@ -12,7 +12,11 @@ //------------------------------------------------------------------------------ const assert = require("chai").assert, + fs = require("fs"), leche = require("leche"), + os = require("os"), + path = require("path"), + sinon = require("sinon"), ReleaseOps = require("../../lib/release-ops"); //------------------------------------------------------------------------------ @@ -479,4 +483,65 @@ describe("ReleaseOps", () => { }); }); + describe("writeChangelog", () => { + + const cwd = process.cwd(); + let sandbox = null; + let tmpDir = null; + + beforeEach(() => { + sandbox = sinon.sandbox.create(); + tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), "writeChangelog-")); + process.chdir(tmpDir); + }); + + afterEach(() => { + sandbox.restore(); + sandbox = null; + process.chdir(cwd); + fs.readdirSync(tmpDir).forEach(filename => fs.unlinkSync(path.join(tmpDir, filename))); // delete files in tmpDir + fs.rmdirSync(tmpDir); + tmpDir = null; + }); + + it("creates a changelog", () => { + const rawChangelog = + "* [`bfb7759`](https://github.com/eslint/eeslint-release/commit/bfb7759a67daeb65410490b4d98bb9da7d1ea2ce) feat: First alpha (Firstname Lastname)"; + const releaseInfo = { version: "1.0.0-alpha.0", rawChangelog }; + const date = new Date(2024, 1, 15); + + sandbox.stub(global, "Date").returns(date); + + ReleaseOps.writeChangelog(releaseInfo); + + assert.deepStrictEqual(fs.readdirSync("."), ["CHANGELOG.md"]); + const newChangelog = fs.readFileSync("CHANGELOG.md", "utf-8"); + + assert.strictEqual(newChangelog, `v1.0.0-alpha.0 - February 15, 2024\n\n${rawChangelog}\n\n`); + }); + + it("extends a changelog", () => { + const oldChangelog = + "v9.0.0 - December 31, 2023\n" + + "\n" + + "* [`0beec7b`](https://github.com/eslint/eeslint-release/commit/0beec7b5ea3f0fdbc95d0dd47f3c5bc275da8a33) chore: Remove a dependency (𐌕𐌄𐌔𐌕)\n" + + "\n"; + const rawChangelog = + "* [`62cdb70`](https://github.com/eslint/eeslint-release/commit/62cdb7020ff920e5aa642c3d4066950dd1f01f4d) fix: Fix something (Abc D. Efg)\n" + + "* [`bbe960a`](https://github.com/eslint/eeslint-release/commit/bbe960a25ea311d21d40669e93df2003ba9b90a2) test: Make sure it's broken (Francesco Trotta)"; + const releaseInfo = { version: "10.0.0", rawChangelog }; + const date = new Date(2024, 1, 2); + + sandbox.stub(global, "Date").returns(date); + fs.writeFileSync("CHANGELOG.md", oldChangelog); + + ReleaseOps.writeChangelog(releaseInfo); + + assert.deepStrictEqual(fs.readdirSync("."), ["CHANGELOG.md"]); + const newChangelog = fs.readFileSync("CHANGELOG.md", "utf-8"); + + assert.strictEqual(newChangelog, `v10.0.0 - February 2, 2024\n\n${rawChangelog}\n\n${oldChangelog}`); + }); + }); + });