From 3f258a6e232e3e56a1137716e1f1bfdfbac3ca4a Mon Sep 17 00:00:00 2001 From: Yanick Champoux Date: Thu, 18 May 2023 14:24:22 -0400 Subject: [PATCH 1/8] add the option to the schema --- src/changelog-schema.js | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/changelog-schema.js b/src/changelog-schema.js index 041b4e9..2c64300 100644 --- a/src/changelog-schema.js +++ b/src/changelog-schema.js @@ -50,6 +50,11 @@ export default { with_stats: { description: "if true, add git statistics when bumping the version.", }, + next_directory: { + type: "string", + description: + "directory where the changes for the NEXT release are stashed", + }, }, type: "object", }, From 8faa2ef1d81f5ca8cd24e646fb67336bff828897 Mon Sep 17 00:00:00 2001 From: Yanick Champoux Date: Thu, 18 May 2023 14:24:32 -0400 Subject: [PATCH 2/8] add documentation --- README.md | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/README.md b/README.md index 5d6037a..d2fed47 100644 --- a/README.md +++ b/README.md @@ -11,6 +11,14 @@ read its [introductory article][blog] on my blog. pnpm install changelord +## `changelog-next` directory + +If you want to mininize merge conflicts in `CHANGELOG.yml`, +you can set the option `project.next_directory` to a directory (typically +`./changelog-next`) that will hold yaml files containing the +changes for the NEXT release. Each of those files is expected to +have a list of changes. + ## CLI commands ### Global options @@ -41,6 +49,9 @@ sections. Adds an entry to the `NEXT` release. +If `project.next_directory` is defined, the entry will be added to that +directory instead of directly into `CHANGELOG.yml`. + $ changelord add --type=maint added a changelog to the project. #### Options @@ -62,6 +73,10 @@ Cuts the next release. That is, resolves the `NEXT` version number based on the latest version and the changes in the `NEXT` section, and sets its date as today. Modifies the source file with the result. +If the `project.next_directory` option is present, +all the changes in that directory are +merged to `CHANGELOG.yml` and the files themselves are deleted. + #### Options - `--dry` -- Resolves the next version but only outputs the resulting section From 09d60859e1d56c8eb2318cc7a1f5166252d2a1c5 Mon Sep 17 00:00:00 2001 From: Yanick Champoux Date: Thu, 18 May 2023 14:24:51 -0400 Subject: [PATCH 3/8] dog-food next_directory --- CHANGELOG.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.yml b/CHANGELOG.yml index e69f1d2..9fd2765 100644 --- a/CHANGELOG.yml +++ b/CHANGELOG.yml @@ -2,6 +2,7 @@ project: name: changelord homepage: https://git.babyl.ca/yanick/changelord.js with_stats: true + next_directory: ./changelog-next releases: - version: NEXT changes: From f759989698b738c8610365250bedbc0dc5eac087 Mon Sep 17 00:00:00 2001 From: Yanick Champoux Date: Thu, 18 May 2023 14:48:47 -0400 Subject: [PATCH 4/8] add to the directory --- package.json | 3 +++ src/changelord.js | 33 +++++++++++++++++++++++++++++---- src/command/add.js | 40 ++++++++++++++++++++-------------------- 3 files changed, 52 insertions(+), 24 deletions(-) diff --git a/package.json b/package.json index 7f21b17..3725072 100644 --- a/package.json +++ b/package.json @@ -24,11 +24,14 @@ "author": "Yanick Champoux (http://techblog.babyl.ca/)", "license": "ISC", "dependencies": { + "@sindresorhus/slugify": "^2.2.1", "@yanick/updeep-remeda": "^2.2.0", "ajv": "^8.12.0", "consola": "^3.1.0", + "filenamify": "^6.0.0", "fs-extra": "^11.1.1", "markdown-utils": "^1.0.0", + "nanoid": "^4.0.2", "remeda": "^1.14.0", "semver": "^7.5.0", "simple-git": "^3.18.0", diff --git a/src/changelord.js b/src/changelord.js index 985addf..83c3579 100755 --- a/src/changelord.js +++ b/src/changelord.js @@ -10,6 +10,8 @@ import u from "@yanick/updeep-remeda"; import { once } from "remeda"; import simpleGit from "simple-git"; import Ajv from "ajv"; +import { nanoid } from "nanoid"; +import slugify from "@sindresorhus/slugify"; import print from "./command/print.js"; import init from "./command/init.js"; @@ -54,12 +56,35 @@ yargs(hideBin(process.argv)) argv.yargs = yargs; argv.add_to_next = async (entry) => { - const changelog = yaml.parse(await fs.readFile(argv.source, "utf-8")); + const dir = await argv.changelog().then((c) => c.project.next_directory); + + if (dir) { + await fs.ensureDir(dir); + const filename = join( + dir, + [ + entry.ticket, + entry.feat, + slugify(entry.desc, { + separator: "_", + }).slice(0, 10), + ] + .filter((x) => x) + .join("-") + ".yml" + ); + argv.consola.info(`writing change to ${filename}`); + if (fs.existsSync(filename)) { + argv.consola.error(`file ${filename} already exist`); + yargs.exit(1); + } + return fs.writeFile(filename, yaml.stringify([entry])); + } + + const changelog = await argv.changelog(); let next = changelog.releases.find(u.matches({ version: "NEXT" })); if (!next) { - next = { version: "NEXT", changes: [] }; - changelog.releases.unshift(next); + changelog.releases.unshift(base_next_version); } if (Object.keys(entry).length === 1) { @@ -68,7 +93,7 @@ yargs(hideBin(process.argv)) next.changes.push(entry); - return fs.writeFile(argv.source, yaml.stringify(changelog)); + return argv.save_changelog(); }; }) .default("source", join(process.cwd(), "CHANGELOG.yml")) diff --git a/src/command/add.js b/src/command/add.js index c4a2b24..2abc612 100644 --- a/src/command/add.js +++ b/src/command/add.js @@ -3,32 +3,32 @@ import yaml from "yaml"; import u from "@yanick/updeep-remeda"; const handler = async (config) => { - if (!config.change) - throw new Error("can't add a change without a description"); + if (!config.change) + throw new Error("can't add a change without a description"); - const entry = { - desc: config.change.join(" "), - }; + const entry = { + desc: config.change.join(" "), + }; - if (config.ticket) entry.ticket = config.ticket; - if (config.type) entry.type = config.type; + if (config.ticket) entry.ticket = config.ticket; + if (config.type) entry.type = config.type; - config.consola.start(`adding '${entry.desc}' to the changelog`); + config.consola.start(`adding '${entry.desc}' to the changelog`); - config.add_to_next(entry); + await config.add_to_next(entry); - config.consola.success("done!"); + config.consola.success("done!"); }; export default { - command: "add [change...]", - desc: "add a change to the NEXT release", - builder: (yargs) => { - yargs - .string("ticket") - .describe("ticket", "ticket associated with the change") - .string("type") - .describe("type", "type of change"); - }, - handler, + command: "add [change...]", + desc: "add a change to the NEXT release", + builder: (yargs) => { + yargs + .string("ticket") + .describe("ticket", "ticket associated with the change") + .string("type") + .describe("type", "type of change"); + }, + handler, }; From 251dce5b56792c4896f4dc08110173c2c97ae334 Mon Sep 17 00:00:00 2001 From: Yanick Champoux Date: Thu, 18 May 2023 15:26:47 -0400 Subject: [PATCH 5/8] include the dir entries to the changelog --- CHANGELOG.yml | 8 +++--- .../2023-05-18T19:24:44.720Z-support_ch.yml | 2 ++ package.json | 1 + src/changelord.js | 27 +++++++++++++++++++ 4 files changed, 33 insertions(+), 5 deletions(-) create mode 100644 changelog-next/2023-05-18T19:24:44.720Z-support_ch.yml diff --git a/CHANGELOG.yml b/CHANGELOG.yml index 9fd2765..8464b6e 100644 --- a/CHANGELOG.yml +++ b/CHANGELOG.yml @@ -6,18 +6,16 @@ project: releases: - version: NEXT changes: - - add `git-gather` command - type: feat - desc: " add 'next' to alias to 'upcoming'" - commit: 363c195477231a6b3b770e74ebfe316296ec5af2 + desc: add `git-gather` command + - type: feat + desc: add 'next' to alias to 'upcoming' - type: feat desc: cutting a release also add a new NEXT release - commit: 167f631d1fe4eadba3ed5fdadbe378b8255d4ad2 - type: feat desc: git-gather also filters on descs - type: feat desc: add the validate command - commit: 5ba75a8e3d42f633e38c3584898ef0085c48fb04 - version: 0.1.0 changes: - port the core of the Perl changelord to JavaScript. diff --git a/changelog-next/2023-05-18T19:24:44.720Z-support_ch.yml b/changelog-next/2023-05-18T19:24:44.720Z-support_ch.yml new file mode 100644 index 0000000..d117f58 --- /dev/null +++ b/changelog-next/2023-05-18T19:24:44.720Z-support_ch.yml @@ -0,0 +1,2 @@ +- desc: support changelog-next directory + type: feat diff --git a/package.json b/package.json index 3725072..0a27a57 100644 --- a/package.json +++ b/package.json @@ -30,6 +30,7 @@ "consola": "^3.1.0", "filenamify": "^6.0.0", "fs-extra": "^11.1.1", + "globby": "^13.1.4", "markdown-utils": "^1.0.0", "nanoid": "^4.0.2", "remeda": "^1.14.0", diff --git a/src/changelord.js b/src/changelord.js index 83c3579..1dd55d4 100755 --- a/src/changelord.js +++ b/src/changelord.js @@ -23,6 +23,8 @@ import latest, { latest_version } from "./command/latest-version.js"; import validate from "./command/validate.js"; import git_gather from "./command/git-gather.js"; import schemaV1 from "./changelog-schema.js"; +import { globby } from "globby"; +import { flatMap } from "remeda"; consola.raw = (...args) => console.log(...args); @@ -34,6 +36,27 @@ yargs(hideBin(process.argv)) fs .readFile(config.source, "utf-8") .then(yaml.parse) + .then(async (doc) => { + if (!doc.project.next_directory) return doc; + + const changes = await globby([ + doc.project.next_directory + "/*.yml", + doc.project.next_directory + "/*.yaml", + ]) + .then((files) => + Promise.all( + files.map((f) => fs.readFile(f, "utf-8").then(yaml.parse)) + ) + ) + .then((r) => r.flat()); + + if (changes.length) + doc.releases + .find((r) => r.version === "NEXT") + .changes.push(...changes); + + return doc; + }) .then((doc) => { const ajv = new Ajv(); const validate = ajv.compile(schemaV1); @@ -63,6 +86,7 @@ yargs(hideBin(process.argv)) const filename = join( dir, [ + new Date().toISOString(), entry.ticket, entry.feat, slugify(entry.desc, { @@ -77,6 +101,9 @@ yargs(hideBin(process.argv)) argv.consola.error(`file ${filename} already exist`); yargs.exit(1); } + if (Object.keys(entry).length === 1) { + entry = entry.desc; + } return fs.writeFile(filename, yaml.stringify([entry])); } From a841165c51390ef5ecf9b60ab3ed8c36d29cee85 Mon Sep 17 00:00:00 2001 From: Yanick Champoux Date: Thu, 18 May 2023 15:42:34 -0400 Subject: [PATCH 6/8] delete directory entries when cutting --- src/changelord.js | 5 +++++ src/command/cut.js | 2 ++ 2 files changed, 7 insertions(+) diff --git a/src/changelord.js b/src/changelord.js index 1dd55d4..0e27eb6 100755 --- a/src/changelord.js +++ b/src/changelord.js @@ -67,6 +67,11 @@ yargs(hideBin(process.argv)) throw "changelog is invalid"; }) ); + config.delete_next_dir_entries = () => + globby([ + config.changelog().project.next_directory + "/*.yml", + config.changelog().project.next_directory + "/*.yaml", + ]).then((files) => Promise.all(files.map(fs.remove))); config.save_changelog = async (changelog) => { if (!changelog) changelog = await config.changelog(); return fs.writeFile(config.source, yaml.stringify(changelog)); diff --git a/src/command/cut.js b/src/command/cut.js index a3669ed..cb72bfe 100644 --- a/src/command/cut.js +++ b/src/command/cut.js @@ -62,6 +62,8 @@ const handler = async (config) => { config.consola.info("running in dry mode, not saving\n", next); } else { await config.save_changelog(changelog); + if (changelog.project.next_directory) + await config.delete_next_dir_entries(); } config.consola.success(`version ${next.version} is cut!`); From 9b9c15ecc448d52b140d7a0d9dfc52756c55dd7a Mon Sep 17 00:00:00 2001 From: Yanick Champoux Date: Thu, 18 May 2023 15:52:37 -0400 Subject: [PATCH 7/8] cut supports next-directory --- src/changelord.js | 12 +++++++----- src/command/cut.js | 6 +++++- 2 files changed, 12 insertions(+), 6 deletions(-) diff --git a/src/changelord.js b/src/changelord.js index 0e27eb6..a11c64b 100755 --- a/src/changelord.js +++ b/src/changelord.js @@ -67,11 +67,13 @@ yargs(hideBin(process.argv)) throw "changelog is invalid"; }) ); - config.delete_next_dir_entries = () => - globby([ - config.changelog().project.next_directory + "/*.yml", - config.changelog().project.next_directory + "/*.yaml", - ]).then((files) => Promise.all(files.map(fs.remove))); + config.delete_next_dir_entries = async () => { + const changelog = await config.changelog(); + return globby([ + changelog.project.next_directory + "/*.yml", + changelog.project.next_directory + "/*.yaml", + ]).then((files) => Promise.all(files.map((f) => fs.remove(f)))); + }; config.save_changelog = async (changelog) => { if (!changelog) changelog = await config.changelog(); return fs.writeFile(config.source, yaml.stringify(changelog)); diff --git a/src/command/cut.js b/src/command/cut.js index cb72bfe..b325763 100644 --- a/src/command/cut.js +++ b/src/command/cut.js @@ -62,8 +62,12 @@ const handler = async (config) => { config.consola.info("running in dry mode, not saving\n", next); } else { await config.save_changelog(changelog); - if (changelog.project.next_directory) + if (changelog.project.next_directory) { + config.consola.info( + `removing files in ${changelog.project.next_directory}` + ); await config.delete_next_dir_entries(); + } } config.consola.success(`version ${next.version} is cut!`); From f6141b139e8ad20e81a8c1fb2bead8037ceb59e2 Mon Sep 17 00:00:00 2001 From: Yanick Champoux Date: Thu, 18 May 2023 16:04:04 -0400 Subject: [PATCH 8/8] add a test --- src/command/cut.js | 2 +- src/command/upcoming.js | 32 ++++++++++++++++---------------- src/command/upcoming.test.js | 29 +++++++++++++++++++++++++++++ 3 files changed, 46 insertions(+), 17 deletions(-) create mode 100644 src/command/upcoming.test.js diff --git a/src/command/cut.js b/src/command/cut.js index b325763..1ba7714 100644 --- a/src/command/cut.js +++ b/src/command/cut.js @@ -62,7 +62,7 @@ const handler = async (config) => { config.consola.info("running in dry mode, not saving\n", next); } else { await config.save_changelog(changelog); - if (changelog.project.next_directory) { + if (changelog.project?.next_directory) { config.consola.info( `removing files in ${changelog.project.next_directory}` ); diff --git a/src/command/upcoming.js b/src/command/upcoming.js index 3d480e4..031016e 100644 --- a/src/command/upcoming.js +++ b/src/command/upcoming.js @@ -4,28 +4,28 @@ import yaml from "yaml"; import { render_release } from "./print.js"; export async function next_release() { - const source = await fs.readFile(this.source, "utf-8").then(yaml.parse); - return ( - source.releases.find(u.matches({ version: "NEXT" })) ?? { - version: "NEXT", - changes: [], - } - ); + const source = await this.changelog(); + return ( + source.releases.find(u.matches({ version: "NEXT" })) ?? { + version: "NEXT", + changes: [], + } + ); } const handler = async (config) => { - const source = await fs.readFile(config.source, "utf-8").then(yaml.parse); + const source = await config.changelog(); - const res = render_release( - { ...config, next: true }, - source - )(source.releases.find(u.matches({ version: "NEXT" }))); + const { body } = render_release( + { ...config, next: true }, + source + )(await config.next_release()); - config.consola.raw("\n" + res.body); + config.consola.raw("\n" + body); }; export default { - command: "upcoming", - desc: "output the changes in NEXT", - handler, + command: "upcoming", + desc: "output the changes in NEXT", + handler, }; diff --git a/src/command/upcoming.test.js b/src/command/upcoming.test.js new file mode 100644 index 0000000..2d63ba8 --- /dev/null +++ b/src/command/upcoming.test.js @@ -0,0 +1,29 @@ +import { test, expect, vi } from "vitest"; +import upcoming from "./upcoming.js"; + +test("basic", async () => { + const changelog = { + releases: [{ version: "NEXT", changes: [] }, { version: "1.0.0" }], + change_types: [], + }; + + const noop = () => {}; + const config = { + consola: { + start: noop, + raw: vi.fn(), + }, + changelog: () => changelog, + next_release: () => ({ + changes: [], + }), + latest_version: () => ({ version: "1.2.3" }), + git: () => ({ + log: () => ({ all: [] }), + }), + }; + + await upcoming.handler(config); + + expect(config.consola.raw).toHaveBeenCalled(); +});