Compare commits

..

6 Commits

7 changed files with 196 additions and 53 deletions

86
README.md Normal file
View File

@ -0,0 +1,86 @@
# Changelord, registrar of deeds extraordinaire
Changelord is a changelog manager scratching my particular itches.
It's cli-based, and keep its data in a YAML file adhering
to a well-defined schema.
The first iteration of `changelord` was written [in Perl][original]. You can
read its [introductory article][blog] on my blog.
## Installation
pnpm install changelord
## CLI commands
### Global options
#### `--help`
Outputs the list of commands and options.
#### `--version`
Outputs the `changelord` version.
#### `--source`
Specifies which source yaml file to use. Defaults to the `CHANGELOG.yml` file
in the current directory.
### `changelord init`
Initializes the changelog source file. The YAML file is made of three
sections.
- `project` -- contains information and configuration about the project itself.
- `releases` -- the entries for the changelog proper.
- `change_types` -- defines all types of changes this project supports.
### `changelord add`
Adds an entry to the `NEXT` release.
$ changelord add --type=maint added a changelog to the project.
#### Options
- `--type` -- type of change.
- `--ticket` -- associated ticket.
### `changelord print`
Renders the changelog as markdown.
#### Options
- `--no-next` -- don't show the `NEXT` section.
### `changelord cut`
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.
#### Options
- `--dry` -- Resolves the next version but only outputs the resulting section
without changing the source file.
### `changelord schema`
Outputs the JSON schema defining the structure of the source file.
### `changelord upcoming`
Outputs the changes listed in the `NEXT` release.
### `changelord latest-version`
Outputs the latest non-NEXT release.
$ changelord latest-version
3.2.0
[blog]: https://techblog.babyl.ca/entry/changelord/
[original]: https://metacpan.org/dist/App-Changelord/view/bin/changelord

View File

@ -1,6 +1,22 @@
# https://taskfile.dev # https://taskfile.dev
version: '3' version: "3"
vars:
PARENT_BRANCH: main
tasks: tasks:
test: vitest run src test: vitest run src
test:dev: vitest src test:dev: vitest src
integrate:
deps: [test]
preconditions:
- sh: git is-clean
msg: checkout not clean
- sh: git diff-ls {{.PARENT_BRANCH}} | grep test
msg: no tests were added
- sh: git diff-ls {{.PARENT_BRANCH}} | grep CHANGELOG.yml
msg: no changelog entry detected
cmds:
- git checkout {{.PARENT_BRANCH}}
- git weld -

View File

@ -3,10 +3,18 @@
"version": "0.0.1", "version": "0.0.1",
"description": "cli-based changelog manager", "description": "cli-based changelog manager",
"type": "module", "type": "module",
"main": "src/index.js", "main": "src/changelord.js",
"bin": { "bin": {
"changelord": "./src/changelord.js" "changelord": "./src/changelord.js"
}, },
"repository": {
"type": "git",
"url": "https://git.babyl.ca/yanick/changelord.js.git"
},
"bugs": {
"url": "https://git.babyl.ca/yanick/changelord.js/issues"
},
"homepage": "https://git.babyl.ca/yanick/changelord.js",
"scripts": { "scripts": {
"test": "echo \"Error: no test specified\" && exit 1" "test": "echo \"Error: no test specified\" && exit 1"
}, },

View File

@ -13,22 +13,23 @@ import schema from "./command/schema.js";
import add from "./command/add.js"; import add from "./command/add.js";
import cut from "./command/cut.js"; import cut from "./command/cut.js";
import upcoming from "./command/upcoming.js"; import upcoming from "./command/upcoming.js";
import latest from "./command/latest-version.js";
consola.raw = (...args) => console.log(...args); consola.raw = (...args) => console.log(...args);
yargs(hideBin(process.argv)) yargs(hideBin(process.argv))
.config({ .config({
consola, consola,
}) })
.default("source", join(process.cwd(), "CHANGELOG.yml")) .default("source", join(process.cwd(), "CHANGELOG.yml"))
.describe("source", "changelog source") .describe("source", "changelog source")
.command(print) .command(init)
.command(init) .command(add)
.command(add) .command(print)
.command(schema) .command(cut)
.command(cut) .command(schema)
.command(print) .command(upcoming)
.command(upcoming) .command(latest)
.strictCommands() .strictCommands()
.help() .help()
.parse(); .parse();

View File

@ -3,49 +3,51 @@ import yaml from "yaml";
import u from "@yanick/updeep-remeda"; import u from "@yanick/updeep-remeda";
const handler = async (config) => { const handler = async (config) => {
if (!config.change) if (!config.change)
throw new Error("can't add a change without a description"); throw new Error("can't add a change without a description");
const entry = { const entry = {
desc: config.change.join(" "), desc: config.change.join(" "),
}; };
if (config.ticket) entry.ticket = config.ticket; if (config.ticket) entry.ticket = config.ticket;
if (config.type) entry.type = config.type; 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); config.add_to_next(entry);
config.consola.success("done!"); config.consola.success("done!");
}; };
export default { export default {
command: "add [change...]", command: "add [change...]",
desc: "add a change to the NEXT release", desc: "add a change to the NEXT release",
builder: (yargs) => { builder: (yargs) => {
yargs yargs
.string("ticket") .string("ticket")
.string("type") .describe("ticket", "ticket associated with the change")
.middleware((argv) => { .string("type")
argv.add_to_next = async (entry) => { .describe("type", "type of change")
const changelog = yaml.parse(await fs.readFile(argv.source, "utf-8")); .middleware((argv) => {
argv.add_to_next = async (entry) => {
const changelog = yaml.parse(await fs.readFile(argv.source, "utf-8"));
let next = changelog.releases.find(u.matches({ version: "NEXT" })); let next = changelog.releases.find(u.matches({ version: "NEXT" }));
if (!next) { if (!next) {
next = { version: "NEXT", changes: [] }; next = { version: "NEXT", changes: [] };
changelog.releases.unshift(next); changelog.releases.unshift(next);
} }
if (Object.keys(entry).length === 1) { if (Object.keys(entry).length === 1) {
entry = entry.desc; entry = entry.desc;
} }
next.changes.push(entry); next.changes.push(entry);
return fs.writeFile(argv.source, yaml.stringify(changelog)); return fs.writeFile(argv.source, yaml.stringify(changelog));
}; };
}); });
}, },
handler, handler,
}; };

View File

@ -6,7 +6,7 @@ import { simpleGit } from "simple-git";
const code_churn = async (previous_version) => { const code_churn = async (previous_version) => {
previous_version = previous_version previous_version = previous_version
? "v" + previous_version ? "v" + previous_version.version
: "4b825dc642cb6eb9a060e54bf8d69288fbee4904"; // empty tree sha1 : "4b825dc642cb6eb9a060e54bf8d69288fbee4904"; // empty tree sha1
return simpleGit().diff(["--shortstat", previous_version, "HEAD"]); return simpleGit().diff(["--shortstat", previous_version, "HEAD"]);
@ -47,7 +47,10 @@ const handler = async (config) => {
}); });
} }
next.version = semverInc(previous_version ?? "0.0.0", bumper); next.version = semverInc(
previous_version ? previous_version.version : "0.0.0",
bumper
);
if (config.dry) { if (config.dry) {
config.consola.info("running in dry mode, not saving\n", next); config.consola.info("running in dry mode, not saving\n", next);

View File

@ -0,0 +1,27 @@
import yaml from "yaml";
import fs from "fs-extra";
const handler = async (config) => {
const changelog = yaml.parse(await fs.readFile(config.source, "utf-8"));
const latest_version = changelog.releases.find(
({ version }) => version !== "NEXT"
);
if (config.json) {
config.consola.raw(latest_version);
} else {
config.consola.raw(latest_version.version);
}
};
export default {
command: "latest-version",
desc: "output the latest version present in the changelog",
builder: (yargs) => {
yargs
.boolean("json")
.describe("json", "output latest version entry as json");
},
handler,
};