Compare commits
7 Commits
0ed4ca9cab
...
0d1fca8dbb
Author | SHA1 | Date | |
---|---|---|---|
0d1fca8dbb | |||
13f443b61c | |||
385c09896a | |||
36e2aade92 | |||
3f266fa068 | |||
a326be7211 | |||
8f8a16d025 |
@ -5,12 +5,13 @@ project:
|
|||||||
ticket_url: null
|
ticket_url: null
|
||||||
releases:
|
releases:
|
||||||
- version: NEXT
|
- version: NEXT
|
||||||
changes:
|
changes:
|
||||||
- port the Perl changelord to JavaScript.
|
- port the core of the Perl changelord to JavaScript.
|
||||||
change_types:
|
change_types:
|
||||||
- title: ''
|
- title: ""
|
||||||
level: minor
|
level: minor
|
||||||
keywords: ['']
|
keywords:
|
||||||
|
- ""
|
||||||
- title: Features
|
- title: Features
|
||||||
level: minor
|
level: minor
|
||||||
keywords:
|
keywords:
|
||||||
|
17
_templates/command/new/command.ejs.t
Normal file
17
_templates/command/new/command.ejs.t
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
---
|
||||||
|
to: src/command/<%= name %>.js
|
||||||
|
---
|
||||||
|
|
||||||
|
const handler = async (config) => {
|
||||||
|
// DO SOMETHING
|
||||||
|
};
|
||||||
|
|
||||||
|
export default {
|
||||||
|
command: '<%= name %>',
|
||||||
|
desc : 'TODO',
|
||||||
|
builder: (yargs) => {
|
||||||
|
yargs
|
||||||
|
},
|
||||||
|
handler,
|
||||||
|
}
|
||||||
|
|
5
_templates/generator/help/index.ejs.t
Normal file
5
_templates/generator/help/index.ejs.t
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
---
|
||||||
|
message: |
|
||||||
|
hygen {bold generator new} --name [NAME] --action [ACTION]
|
||||||
|
hygen {bold generator with-prompt} --name [NAME] --action [ACTION]
|
||||||
|
---
|
18
_templates/generator/new/hello.ejs.t
Normal file
18
_templates/generator/new/hello.ejs.t
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
---
|
||||||
|
to: _templates/<%= name %>/<%= action || 'new' %>/hello.ejs.t
|
||||||
|
---
|
||||||
|
---
|
||||||
|
to: app/hello.js
|
||||||
|
---
|
||||||
|
const hello = ```
|
||||||
|
Hello!
|
||||||
|
This is your first hygen template.
|
||||||
|
|
||||||
|
Learn what it can do here:
|
||||||
|
|
||||||
|
https://github.com/jondot/hygen
|
||||||
|
```
|
||||||
|
|
||||||
|
console.log(hello)
|
||||||
|
|
||||||
|
|
18
_templates/generator/with-prompt/hello.ejs.t
Normal file
18
_templates/generator/with-prompt/hello.ejs.t
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
---
|
||||||
|
to: _templates/<%= name %>/<%= action || 'new' %>/hello.ejs.t
|
||||||
|
---
|
||||||
|
---
|
||||||
|
to: app/hello.js
|
||||||
|
---
|
||||||
|
const hello = ```
|
||||||
|
Hello!
|
||||||
|
This is your first prompt based hygen template.
|
||||||
|
|
||||||
|
Learn what it can do here:
|
||||||
|
|
||||||
|
https://github.com/jondot/hygen
|
||||||
|
```
|
||||||
|
|
||||||
|
console.log(hello)
|
||||||
|
|
||||||
|
|
14
_templates/generator/with-prompt/prompt.ejs.t
Normal file
14
_templates/generator/with-prompt/prompt.ejs.t
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
---
|
||||||
|
to: _templates/<%= name %>/<%= action || 'new' %>/prompt.js
|
||||||
|
---
|
||||||
|
|
||||||
|
// see types of prompts:
|
||||||
|
// https://github.com/enquirer/enquirer/tree/master/examples
|
||||||
|
//
|
||||||
|
module.exports = [
|
||||||
|
{
|
||||||
|
type: 'input',
|
||||||
|
name: 'message',
|
||||||
|
message: "What's your message?"
|
||||||
|
}
|
||||||
|
]
|
4
_templates/init/repo/new-repo.ejs.t
Normal file
4
_templates/init/repo/new-repo.ejs.t
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
---
|
||||||
|
setup: <%= name %>
|
||||||
|
force: true # this is because mostly, people init into existing folders is safe
|
||||||
|
---
|
@ -16,10 +16,13 @@
|
|||||||
"author": "Yanick Champoux <yanick@babyl.ca> (http://techblog.babyl.ca/)",
|
"author": "Yanick Champoux <yanick@babyl.ca> (http://techblog.babyl.ca/)",
|
||||||
"license": "ISC",
|
"license": "ISC",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"@yanick/updeep-remeda": "^2.2.0",
|
||||||
"consola": "^3.1.0",
|
"consola": "^3.1.0",
|
||||||
"fs-extra": "^11.1.1",
|
"fs-extra": "^11.1.1",
|
||||||
"markdown-utils": "^1.0.0",
|
"markdown-utils": "^1.0.0",
|
||||||
"remeda": "^1.14.0",
|
"remeda": "^1.14.0",
|
||||||
|
"semver": "^7.5.0",
|
||||||
|
"simple-git": "^3.18.0",
|
||||||
"yaml": "^2.2.2",
|
"yaml": "^2.2.2",
|
||||||
"yargs": "^17.7.2"
|
"yargs": "^17.7.2"
|
||||||
},
|
},
|
||||||
|
@ -1,29 +1,34 @@
|
|||||||
#!/usr/bin/env node
|
#!/usr/bin/env node
|
||||||
|
|
||||||
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 yaml from "yaml";
|
||||||
import fs from 'fs-extra';
|
import fs from "fs-extra";
|
||||||
|
import consola from "consola";
|
||||||
|
|
||||||
import print from './command/print.js'
|
import print from "./command/print.js";
|
||||||
import init from './command/init.js'
|
import init from "./command/init.js";
|
||||||
import schema from './command/schema.js';
|
import schema from "./command/schema.js";
|
||||||
import consola from 'consola';
|
import add from "./command/add.js";
|
||||||
|
import cut from "./command/cut.js";
|
||||||
|
import upcoming from "./command/upcoming.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({
|
.command(print)
|
||||||
...print,
|
.command(init)
|
||||||
command: '$0',
|
.command(add)
|
||||||
})
|
.command(schema)
|
||||||
.command(init)
|
.command(cut)
|
||||||
.command(schema)
|
.command(print)
|
||||||
.command(print).help().parse();
|
.command(upcoming)
|
||||||
|
.strictCommands()
|
||||||
|
.help()
|
||||||
|
.parse();
|
||||||
|
51
src/command/add.js
Normal file
51
src/command/add.js
Normal file
@ -0,0 +1,51 @@
|
|||||||
|
import fs from "fs-extra";
|
||||||
|
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");
|
||||||
|
|
||||||
|
const entry = {
|
||||||
|
desc: config.change.join(" "),
|
||||||
|
};
|
||||||
|
|
||||||
|
if (config.ticket) entry.ticket = config.ticket;
|
||||||
|
if (config.type) entry.type = config.type;
|
||||||
|
|
||||||
|
config.consola.start(`adding '${entry.desc}' to the changelog`);
|
||||||
|
|
||||||
|
config.add_to_next(entry);
|
||||||
|
|
||||||
|
config.consola.success("done!");
|
||||||
|
};
|
||||||
|
|
||||||
|
export default {
|
||||||
|
command: "add [change...]",
|
||||||
|
desc: "add a change to the NEXT release",
|
||||||
|
builder: (yargs) => {
|
||||||
|
yargs
|
||||||
|
.string("ticket")
|
||||||
|
.string("type")
|
||||||
|
.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" }));
|
||||||
|
if (!next) {
|
||||||
|
next = { version: "NEXT", changes: [] };
|
||||||
|
changelog.releases.unshift(next);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Object.keys(entry).length === 1) {
|
||||||
|
entry = entry.desc;
|
||||||
|
}
|
||||||
|
|
||||||
|
next.changes.push(entry);
|
||||||
|
|
||||||
|
return fs.writeFile(argv.source, yaml.stringify(changelog));
|
||||||
|
};
|
||||||
|
});
|
||||||
|
},
|
||||||
|
handler,
|
||||||
|
};
|
68
src/command/cut.js
Normal file
68
src/command/cut.js
Normal file
@ -0,0 +1,68 @@
|
|||||||
|
import yaml from "yaml";
|
||||||
|
import fs from "fs-extra";
|
||||||
|
import u from "@yanick/updeep-remeda";
|
||||||
|
import semverInc from "semver/functions/inc.js";
|
||||||
|
import { simpleGit } from "simple-git";
|
||||||
|
|
||||||
|
const code_churn = async (previous_version) => {
|
||||||
|
previous_version = previous_version
|
||||||
|
? "v" + previous_version
|
||||||
|
: "4b825dc642cb6eb9a060e54bf8d69288fbee4904"; // empty tree sha1
|
||||||
|
|
||||||
|
return simpleGit().diff(["--shortstat", previous_version, "HEAD"]);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handler = async (config) => {
|
||||||
|
config.consola.start("cutting the next version...");
|
||||||
|
const changelog = yaml.parse(await fs.readFile(config.source, "utf-8"));
|
||||||
|
|
||||||
|
const next = changelog.releases.find(u.matches({ version: "NEXT" }));
|
||||||
|
|
||||||
|
if (!next) throw new Error("no changes since last version, aborting");
|
||||||
|
|
||||||
|
const previous_version = changelog.releases.find(
|
||||||
|
({ version }) => version !== "NEXT"
|
||||||
|
);
|
||||||
|
|
||||||
|
next.date = new Date().toISOString().replace(/T.*/, "");
|
||||||
|
|
||||||
|
const type_to_level = (type = "") =>
|
||||||
|
changelog.change_types.find((t) => t.keywords.includes(type)).level;
|
||||||
|
|
||||||
|
const bump_level = (types) =>
|
||||||
|
types.includes("major")
|
||||||
|
? "major"
|
||||||
|
: types.includes("minor")
|
||||||
|
? "minor"
|
||||||
|
: "patch";
|
||||||
|
|
||||||
|
const bumper = bump_level(
|
||||||
|
next.changes.map(({ type }) => type_to_level(type))
|
||||||
|
);
|
||||||
|
|
||||||
|
if (changelog.project.with_stats) {
|
||||||
|
next.changes.push({
|
||||||
|
type: "stats",
|
||||||
|
desc: "code churn:" + (await code_churn(previous_version)),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
next.version = semverInc(previous_version ?? "0.0.0", bumper);
|
||||||
|
|
||||||
|
if (config.dry) {
|
||||||
|
config.consola.info("running in dry mode, not saving\n", next);
|
||||||
|
} else {
|
||||||
|
await fs.writeFile(config.source, yaml.stringify(changelog));
|
||||||
|
}
|
||||||
|
|
||||||
|
config.consola.success(`version ${next.version} is cut!`);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default {
|
||||||
|
command: "cut",
|
||||||
|
desc: "cut the next version",
|
||||||
|
builder: (yargs) => {
|
||||||
|
yargs.boolean("dry");
|
||||||
|
},
|
||||||
|
handler,
|
||||||
|
};
|
@ -1,131 +1,135 @@
|
|||||||
import * as R from 'remeda';
|
import * as R from "remeda";
|
||||||
import fs from 'fs-extra';
|
import fs from "fs-extra";
|
||||||
import yaml from 'yaml';
|
import yaml from "yaml";
|
||||||
import mkd from 'markdown-utils';
|
import mkd from "markdown-utils";
|
||||||
|
|
||||||
const render_header = (source ) => {
|
const render_header = (source) => {
|
||||||
let header = '# Changelog';
|
let header = "# Changelog";
|
||||||
|
|
||||||
let name = source.project?.name
|
|
||||||
|
|
||||||
const links = {};
|
let name = source.project?.name;
|
||||||
|
|
||||||
if( source?.project?.homepage) {
|
const links = {};
|
||||||
links.homepage = source?.project?.homepage;
|
|
||||||
name = `[${name}][homepage]`;
|
|
||||||
}
|
|
||||||
|
|
||||||
if(name) header += ` for ${name}`;
|
if (source?.project?.homepage) {
|
||||||
|
links.homepage = source?.project?.homepage;
|
||||||
|
name = `[${name}][homepage]`;
|
||||||
|
}
|
||||||
|
|
||||||
return { header, links }
|
if (name) header += ` for ${name}`;
|
||||||
|
|
||||||
|
return { header, links };
|
||||||
};
|
};
|
||||||
|
|
||||||
function render_links(links) {
|
function render_links(links) {
|
||||||
|
return Object.entries(links)
|
||||||
return Object.entries(links).map(
|
.map(([name, url]) => ` [${name}]: ${url}`)
|
||||||
([name,url]) => ` [${name}]: ${url}`
|
.join("\n");
|
||||||
).join( "\n")
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const render_change = (config,source)=>(change) => {
|
const render_change = (config, source) => (change) => {
|
||||||
let body = change.desc;
|
let body = change.desc;
|
||||||
let links = {};
|
let links = {};
|
||||||
|
|
||||||
return {
|
return {
|
||||||
body, links
|
body,
|
||||||
}
|
links,
|
||||||
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
export const render_release = (config,source) => ( release ) => {
|
export const render_release = (config, source) => (release) => {
|
||||||
let links = {};
|
let links = {};
|
||||||
|
|
||||||
if( typeof release === 'string') return {
|
if (typeof release === "string")
|
||||||
body: release,
|
return {
|
||||||
links
|
body: release,
|
||||||
|
links,
|
||||||
};
|
};
|
||||||
|
|
||||||
if(release.version === 'NEXT' && !config.next) return {
|
if (release.version === "NEXT" && !config.next)
|
||||||
body: '',
|
return {
|
||||||
links,
|
body: "",
|
||||||
|
links,
|
||||||
};
|
};
|
||||||
|
|
||||||
let body = "## " + release.version;
|
let body = "## " + release.version;
|
||||||
|
|
||||||
if( release.date) {
|
if (release.date) {
|
||||||
body += " " + 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 += "\n" + mkd.h3(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 };
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
body += "\n\n";
|
return { body, links };
|
||||||
|
|
||||||
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 handler = async (config) => {
|
||||||
const source = await fs.readFile(config.source,'utf-8').then(yaml.parse);
|
const source = await fs.readFile(config.source, "utf-8").then(yaml.parse);
|
||||||
|
|
||||||
let output = '';
|
let output = "";
|
||||||
|
|
||||||
let {header,links} = render_header(source);
|
let { header, links } = render_header(source);
|
||||||
|
|
||||||
output += header + "\n\n";
|
output += header + "\n\n";
|
||||||
|
|
||||||
for ( const res of source.releases.map( render_release(config,source) )) {
|
for (const res of source.releases.map(render_release(config, source))) {
|
||||||
output += res.body + "\n\n";
|
output += res.body + "\n\n";
|
||||||
links = {...links,...res.links};
|
links = { ...links, ...res.links };
|
||||||
}
|
}
|
||||||
|
|
||||||
output += "\n\n\n\n" + render_links(links);
|
|
||||||
|
|
||||||
config.consola.raw(output);
|
|
||||||
|
|
||||||
|
output += "\n\n\n\n" + render_links(links);
|
||||||
|
|
||||||
|
config.consola.raw(output);
|
||||||
};
|
};
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
command: 'print',
|
command: "print",
|
||||||
desc : 'render the changelog',
|
desc: "render the changelog",
|
||||||
builder: (yargs) => {
|
builder: (yargs) => {
|
||||||
yargs
|
yargs
|
||||||
//.boolean('json')
|
//.boolean('json')
|
||||||
.boolean('next')
|
.boolean("next")
|
||||||
// .default('json',false)
|
// .default('json',false)
|
||||||
.default('next',true);
|
.default("next", true);
|
||||||
},
|
},
|
||||||
handler,
|
handler,
|
||||||
}
|
};
|
||||||
|
|
||||||
|
24
src/command/upcoming.js
Normal file
24
src/command/upcoming.js
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
import u from "@yanick/updeep-remeda";
|
||||||
|
import fs from "fs-extra";
|
||||||
|
import yaml from "yaml";
|
||||||
|
import { render_release } from "./print.js";
|
||||||
|
|
||||||
|
const handler = async (config) => {
|
||||||
|
const source = await fs.readFile(config.source, "utf-8").then(yaml.parse);
|
||||||
|
|
||||||
|
const res = render_release(
|
||||||
|
{ ...config, next: true },
|
||||||
|
source
|
||||||
|
)(source.releases.find(u.matches({ version: "NEXT" })));
|
||||||
|
|
||||||
|
config.consola.raw("\n" + res.body);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default {
|
||||||
|
command: "upcoming",
|
||||||
|
desc: "output the changes in NEXT",
|
||||||
|
builder: (yargs) => {
|
||||||
|
yargs;
|
||||||
|
},
|
||||||
|
handler,
|
||||||
|
};
|
Loading…
Reference in New Issue
Block a user