Compare commits

...

2 Commits

Author SHA1 Message Date
0ed4ca9cab Merge branch 'lord6-print' 2023-05-10 14:37:24 -04:00
01e5381b77 add print command
fix #6
2023-05-10 14:36:56 -04:00
8 changed files with 211 additions and 47 deletions

View File

@ -1,12 +1,16 @@
project: project:
name: null name: changelord
homepage: null homepage: https://git.babyl.ca/yanick/changelord.js
with_stats: true with_stats: true
ticket_url: null ticket_url: null
releases: releases:
- version: NEXT - version: NEXT
changes: [] changes:
- port the Perl changelord to JavaScript.
change_types: change_types:
- title: ''
level: minor
keywords: ['']
- title: Features - title: Features
level: minor level: minor
keywords: keywords:

6
Taskfile.yaml Normal file
View File

@ -0,0 +1,6 @@
# https://taskfile.dev
version: '3'
tasks:
test: vitest run src
test:dev: vitest src

View File

@ -18,10 +18,13 @@
"dependencies": { "dependencies": {
"consola": "^3.1.0", "consola": "^3.1.0",
"fs-extra": "^11.1.1", "fs-extra": "^11.1.1",
"markdown-utils": "^1.0.0",
"remeda": "^1.14.0",
"yaml": "^2.2.2", "yaml": "^2.2.2",
"yargs": "^17.7.2" "yargs": "^17.7.2"
}, },
"devDependencies": { "devDependencies": {
"prettier": "^2.8.8" "prettier": "^2.8.8",
"vitest": "^0.31.0"
} }
} }

9
prettier.config.mjs Normal file
View File

@ -0,0 +1,9 @@
export default {
endOfLine: "lf",
semi: true,
singleQuote: false,
tabWidth: 2,
trailingComma: "es5",
bracketSpacing: true,
proseWrap: "always",
};

View File

@ -3,6 +3,8 @@
import { hideBin } from 'yargs/helpers'; import { hideBin } from 'yargs/helpers';
import yargs from 'yargs'; import yargs from 'yargs';
import { join } from 'path'; import { join } from 'path';
import yaml from 'yaml';
import fs from 'fs-extra';
import print from './command/print.js' import print from './command/print.js'
import init from './command/init.js' import init from './command/init.js'
@ -11,9 +13,10 @@ import consola from 'consola';
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')

View File

@ -1,50 +1,52 @@
import fs from 'fs-extra'; import fs from "fs-extra";
import { consola } from 'consola'; import { consola } from "consola";
import { stringify } from 'yaml'; import { stringify } from "yaml";
const change_types = [ const change_types = [
{ title: 'Features' , level: 'minor', keywords: [ 'feat' ] } , { title: "", level: "minor", keywords: [""] },
{ title : 'Bug fixes' , level : 'patch', keywords : [ 'fix' ] }, { title: "Features", level: "minor", keywords: ["feat"] },
{ title : 'Package maintenance' , level : 'patch', keywords : [ 'chore', 'maint', 'refactor' ] }, { title: "Bug fixes", level: "patch", keywords: ["fix"] },
{ title : 'Statistics' , level : 'patch', keywords : [ 'stats' ] }, {
]; title: "Package maintenance",
level: "patch",
keywords: ["chore", "maint", "refactor"],
},
{ title: "Statistics", level: "patch", keywords: ["stats"] },
];
export const base_changelog = {
const base_changelog = { project: {
project: { name: null,
name: null, homepage: null,
homepage: null, with_stats: true,
with_stats: true, ticket_url: null,
ticket_url: null, },
}, releases: [{ version: "NEXT", changes: [] }],
releases: [ change_types,
{ version: 'NEXT', changes: [] }
],
change_types,
}; };
const handler = async (config) => { const handler = async (config) => {
if( await fs.pathExists(config.source) ) { if (await fs.pathExists(config.source)) {
consola.error(`${config.source} already exist, aborting.`); consola.error(`${config.source} already exist, aborting.`);
process.exit(); process.exit();
} }
consola.start(`creating ${config.source}...`); consola.start(`creating ${config.source}...`);
await fs.writeFile( config.source, stringify(base_changelog) ); await fs.writeFile(config.source, stringify(base_changelog));
consola.success('done!');
consola.success("done!");
}; };
export default { export default {
command: 'init', command: "init",
desc : 'initialize new changelog source file', desc: "initialize new changelog source file",
builder: (yargs) => { builder: (yargs) => {
yargs.boolean('json') yargs
.boolean('next') .boolean("json")
.default('json',false) .boolean("next")
.default('next',true); .default("json", false)
}, .default("next", true);
handler, },
} handler,
};

View File

@ -1,16 +1,131 @@
import * as R from 'remeda';
import fs from 'fs-extra';
import yaml from 'yaml';
import mkd from 'markdown-utils';
const render_header = (source ) => {
let header = '# Changelog';
let name = source.project?.name
const links = {};
if( source?.project?.homepage) {
links.homepage = source?.project?.homepage;
name = `[${name}][homepage]`;
}
if(name) header += ` for ${name}`;
return { header, links }
};
function render_links(links) {
return Object.entries(links).map(
([name,url]) => ` [${name}]: ${url}`
).join( "\n")
}
const render_change = (config,source)=>(change) => {
let body = change.desc;
let links = {};
return {
body, links
}
};
export const render_release = (config,source) => ( release ) => {
let links = {};
if( typeof release === 'string') return {
body: release,
links
};
if(release.version === 'NEXT' && !config.next) return {
body: '',
links,
};
let body = "## " + release.version;
if( release.date) {
body += " " + release.date;
}
body += "\n\n";
const type_map = Object.fromEntries(source.change_types.flatMap(
entry => entry.keywords.map( k => [ k, entry ])
));
const changes = release.changes.map( change => typeof change === 'string'? { desc: change, type: '' } : change );
const unknown = R.uniq( changes.map(R.prop('type')).map(
t => t ?? ''
).filter( type => !type_map[type]));
if( unknown.length ) {
throw new Error(
"unknown change types encountered: " + unknown.map(t => `'${t}'`).join(', ')
);
}
for ( const change_type of source.change_types ) {
const type_changes = changes.filter(
({type}) => change_type.keywords.includes(type)
);
if( type_changes.length === 0) continue;
if( change_type.title )
body += mkd.h2( change_type.title) + "\n\n";
for( const c of type_changes ) {
const res = render_change(config,source)(c);
body += mkd.li(res.body,1)+"\n";
links = { ...links, ...res.links};
}
}
return { body, links };
};
const handler = async (config) => {
const source = await fs.readFile(config.source,'utf-8').then(yaml.parse);
let output = '';
let {header,links} = render_header(source);
output += header + "\n\n";
for ( const res of source.releases.map( render_release(config,source) )) {
output += res.body + "\n\n";
links = {...links,...res.links};
}
output += "\n\n\n\n" + render_links(links);
config.consola.raw(output);
const handler = (...args) => {
console.log('hi!',args);
}; };
export default { export default {
command: 'print', command: 'print',
desc : 'render the changelog', desc : 'render the changelog',
builder: (yargs) => { builder: (yargs) => {
yargs.boolean('json') yargs
//.boolean('json')
.boolean('next') .boolean('next')
.default('json',false) // .default('json',false)
.default('next',true); .default('next',true);
}, },
handler, handler,
} }

22
src/command/print.test.js Normal file
View File

@ -0,0 +1,22 @@
import { test, expect, vi } from 'vitest';
import { render_release } from './print.js';
import { base_changelog } from './init.js';
test( 'render_release a string', () => {
const {body} = render_release({})("as is");
expect(body).toEqual('as is');
});
test( 'render_release', () => {
const consola = { error: vi.fn() };
const res= render_release({consola},base_changelog)({
version: '1.2.3',
date: 'today',
changes: [
"this",
"that",
]
});
expect(res.body).toContain('# 1.2.3 today');
expect(res.body).toContain('* this');
});