commit
04e9e5f71a
@ -2,21 +2,20 @@ project:
|
||||
name: changelord
|
||||
homepage: https://git.babyl.ca/yanick/changelord.js
|
||||
with_stats: true
|
||||
next_directory: ./changelog-next
|
||||
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.
|
||||
|
15
README.md
15
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
|
||||
|
2
changelog-next/2023-05-18T19:24:44.720Z-support_ch.yml
Normal file
2
changelog-next/2023-05-18T19:24:44.720Z-support_ch.yml
Normal file
@ -0,0 +1,2 @@
|
||||
- desc: support changelog-next directory
|
||||
type: feat
|
@ -24,11 +24,15 @@
|
||||
"author": "Yanick Champoux <yanick@babyl.ca> (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",
|
||||
"globby": "^13.1.4",
|
||||
"markdown-utils": "^1.0.0",
|
||||
"nanoid": "^4.0.2",
|
||||
"remeda": "^1.14.0",
|
||||
"semver": "^7.5.0",
|
||||
"simple-git": "^3.18.0",
|
||||
|
@ -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",
|
||||
},
|
||||
|
@ -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";
|
||||
@ -21,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);
|
||||
|
||||
@ -32,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);
|
||||
@ -42,6 +67,13 @@ yargs(hideBin(process.argv))
|
||||
throw "changelog is invalid";
|
||||
})
|
||||
);
|
||||
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));
|
||||
@ -54,12 +86,39 @@ 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,
|
||||
[
|
||||
new Date().toISOString(),
|
||||
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);
|
||||
}
|
||||
if (Object.keys(entry).length === 1) {
|
||||
entry = entry.desc;
|
||||
}
|
||||
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 +127,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"))
|
||||
|
@ -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,
|
||||
};
|
||||
|
@ -62,6 +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) {
|
||||
config.consola.info(
|
||||
`removing files in ${changelog.project.next_directory}`
|
||||
);
|
||||
await config.delete_next_dir_entries();
|
||||
}
|
||||
}
|
||||
|
||||
config.consola.success(`version ${next.version} is cut!`);
|
||||
|
@ -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,
|
||||
};
|
||||
|
29
src/command/upcoming.test.js
Normal file
29
src/command/upcoming.test.js
Normal file
@ -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();
|
||||
});
|
Loading…
Reference in New Issue
Block a user