Repository navigation
Expand file tree
/
Copy pathcommitRecords.js
More file actions
87 lines (79 loc) · 2.94 KB
/
Copy pathcommitRecords.js
File metadata and controls
87 lines (79 loc) · 2.94 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
#!/usr/bin/env node
// @ts-check
/* Draws commit records as a Semantic Diff page: one card per commit, each
* the commit's identity and its message drawn. A record is a commit message
* in the format of the `records` skill (.claude/skills/records/SKILL.md).
* This file reads git; scripts/recordRender.js draws.
*
* node scripts/commitRecords.js --out review.html <rev>...
* node scripts/commitRecords.js --out review.html <base>..<tip>...
* --pr <number> and --title <text> head the page with the pull request.
*/
import { execFileSync } from "node:child_process";
import { writeFileSync } from "node:fs";
import { CSS, esc, pageBody, pageTitle, parseRecord } from "./recordRender.js";
/** @import { CommitRecord, Group } from "./recordRender.js" */
/** @param {string[]} args */
const git = args =>
execFileSync("git", args, {
encoding: "utf8",
maxBuffer: 1 << 28,
stdio: ["ignore", "pipe", "ignore"],
});
/**
* The record of one commit: its short hash, author, date and message.
* @param {string} rev
* @returns {CommitRecord}
*/
function readRecord(rev) {
const show = (/** @type {string[]} */ ...format) =>
git(["show", "-s", ...format, rev]);
return parseRecord({
sha: show("--format=%h", "--abbrev=9").trim(),
author: show("--format=%an").trim(),
date: show("--format=%ad", "--date=format:%b %d, %Y").trim(),
message: show("--format=%B"),
});
}
/**
* A revision is one card; a range `base..tip` is its commits, oldest first.
* @param {string} arg
* @returns {Group}
*/
function group(arg) {
if (!arg.includes("..")) return { records: [readRecord(arg)] };
const revs = git(["rev-list", "--reverse", "--no-merges", arg])
.split("\n")
.filter(Boolean);
return { range: arg, records: revs.map(readRecord) };
}
const args = process.argv.slice(2);
/** @type {Record<string, string>} */
const options = {};
/** @type {string[]} */
const targets = [];
for (let i = 0; i < args.length; i++) {
if (["--out", "--pr", "--title"].includes(args[i]) && i + 1 < args.length)
options[args[i].slice(2)] = args[++i];
else targets.push(args[i]);
}
const outFile = options.out;
if (!outFile) {
console.error(
"usage: commitRecords.js --out <page.html> [--pr <number>] [--title <text>] <rev | base..tip>...",
);
process.exit(1);
}
const heading = {
pr: (options.pr ?? "").replace(/\D/g, ""),
title: options.title ?? "",
};
const body = pageBody(targets.map(group), heading);
const head = `<title>${esc(pageTitle(heading))}</title>
<link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Fraunces:opsz,wght@9..144,600&family=Inter:wght@400;500;600&family=JetBrains+Mono:wght@400;500&family=STIX+Two+Text:ital,wght@0,400;0,500;1,400&display=swap">
<style>${CSS}</style>`;
writeFileSync(
outFile,
`<!doctype html>\n<html lang="en"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width, initial-scale=1">\n${head}</head><body>${body}</body></html>\n`,
);
console.log(outFile);