@doki-land/nifty 0.0.5 → 0.0.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/locales/en-US/cli.json +9 -4
- package/package.json +4 -4
- package/src/cli/change-logs-cmd.ts +8 -2
- package/src/cli/cli.ts +2 -2
- package/src/cli/commit-cmd.ts +226 -0
- package/src/cli/commit-lint-report.ts +110 -0
- package/src/cli/format.ts +14 -3
- package/src/cli/lint.ts +30 -19
- package/src/config/authorMap.ts +26 -0
- package/src/config/formatPresets.ts +64 -0
- package/src/config/index.ts +9 -1
- package/src/config/loadConfig.ts +1 -8
- package/src/config/types.ts +36 -5
- package/src/index.ts +8 -1
- package/src/native.ts +104 -6
- package/src/nifty.ts +3 -2
- package/src/cli/reword-cmd.ts +0 -87
package/locales/en-US/cli.json
CHANGED
|
@@ -9,9 +9,14 @@
|
|
|
9
9
|
"cli.cmd.bump": "Align workspace crate and package versions",
|
|
10
10
|
"cli.cmd.publish": "Publish workspace npm packages (OIDC or token + TOTP)",
|
|
11
11
|
"cli.cmd.trust": "Configure npm Trusted Publisher for workspace packages",
|
|
12
|
-
"cli.cmd.
|
|
13
|
-
"cli.cmd.
|
|
14
|
-
"cli.cmd.
|
|
12
|
+
"cli.cmd.commit": "Commit message export, apply, scan, and audit",
|
|
13
|
+
"cli.cmd.commit.export": "Export a JSON commit message map template",
|
|
14
|
+
"cli.cmd.commit.apply": "Apply commit message edits from a JSON map and update the branch ref",
|
|
15
|
+
"cli.cmd.commit.scan": "Scan commit messages for gitmoji and hygiene issues",
|
|
16
|
+
"cli.cmd.commit.audit": "Audit commit messages and fail on errors",
|
|
17
|
+
"cli.opt.with-lint": "Run commit lint after export and annotate the JSON map with violations",
|
|
18
|
+
"cli.opt.json": "Print commit lint output as JSON",
|
|
19
|
+
"cli.opt.errors-only": "Only report error-level commit lint diagnostics",
|
|
15
20
|
"cli.cmd.retime": "Spread commit timestamps across a date range on a new branch",
|
|
16
21
|
"cli.cmd.retime.root": "Retime all commits from repository root through tip",
|
|
17
22
|
"cli.cmd.change-logs": "Draft release reference changelogs from git tags",
|
|
@@ -51,7 +56,7 @@
|
|
|
51
56
|
"cli.opt.npm-token": "npm auth token (overrides NPM_TOKEN)",
|
|
52
57
|
"cli.opt.base": "Exclusive range start commit (e.g. 34e1e665^)",
|
|
53
58
|
"cli.opt.ref": "Branch ref to rewrite (default: HEAD)",
|
|
54
|
-
"cli.opt.path": "Path to
|
|
59
|
+
"cli.opt.path": "Path to commit JSON map (default: commit.pending.json)",
|
|
55
60
|
"cli.opt.start-date": "Random window start (YYYY-MM-DD or ISO datetime)",
|
|
56
61
|
"cli.opt.end-date": "Random window end",
|
|
57
62
|
"cli.opt.branch": "Output branch name (default: time-travel)",
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@doki-land/nifty",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.7",
|
|
4
4
|
"description": "Gitmoji-first commit conventions and release helpers (Node-API + TypeScript)",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MPL-2.0",
|
|
@@ -24,9 +24,9 @@
|
|
|
24
24
|
"jiti": "^2.4.2"
|
|
25
25
|
},
|
|
26
26
|
"optionalDependencies": {
|
|
27
|
-
"@doki-land/nifty-win32-x64": "0.0.
|
|
28
|
-
"@doki-land/nifty-linux-x64": "0.0.
|
|
29
|
-
"@doki-land/nifty-darwin-arm64": "0.0.
|
|
27
|
+
"@doki-land/nifty-win32-x64": "0.0.7",
|
|
28
|
+
"@doki-land/nifty-linux-x64": "0.0.7",
|
|
29
|
+
"@doki-land/nifty-darwin-arm64": "0.0.7"
|
|
30
30
|
},
|
|
31
31
|
"publishConfig": {
|
|
32
32
|
"access": "public"
|
|
@@ -2,6 +2,8 @@ import { join } from "node:path";
|
|
|
2
2
|
|
|
3
3
|
import type { Cli, ParsedOptions } from "@vmz/commander";
|
|
4
4
|
|
|
5
|
+
import { resolveAuthorMapPath } from "../config/authorMap.js";
|
|
6
|
+
import { detectProjectLayout } from "../config/detectProject.js";
|
|
5
7
|
import { loadConfig } from "../config/loadConfig.js";
|
|
6
8
|
import { loadNiftyNative } from "../native.js";
|
|
7
9
|
import { bootstrapFromOptions } from "./context.js";
|
|
@@ -34,6 +36,8 @@ export async function cmdChangeLogsRender(options: ParsedOptions): Promise<numbe
|
|
|
34
36
|
await bootstrapFromOptions(options);
|
|
35
37
|
const cwd = cwdFrom(options) ?? process.cwd();
|
|
36
38
|
const { config } = await loadConfig({ cwd, createIfMissing: false });
|
|
39
|
+
const layout = detectProjectLayout(cwd);
|
|
40
|
+
const repoRoot = config.repoRoot ?? layout.root;
|
|
37
41
|
const native = loadNiftyNative();
|
|
38
42
|
const report = native.history["changelog-render"]({
|
|
39
43
|
cwd,
|
|
@@ -43,7 +47,7 @@ export async function cmdChangeLogsRender(options: ParsedOptions): Promise<numbe
|
|
|
43
47
|
write: flag(options, "write"),
|
|
44
48
|
tags: flag(options, "tags"),
|
|
45
49
|
repo: str(options, "repo") ?? config.changelog?.repo,
|
|
46
|
-
authorMap: str(options, "author-map") ?? config
|
|
50
|
+
authorMap: str(options, "author-map") ?? resolveAuthorMapPath(config, repoRoot),
|
|
47
51
|
releasesDir: str(options, "releases-dir") ?? config.changelog?.releasesDir,
|
|
48
52
|
});
|
|
49
53
|
|
|
@@ -66,6 +70,8 @@ export async function cmdChangeLogsLookup(options: ParsedOptions): Promise<numbe
|
|
|
66
70
|
await bootstrapFromOptions(options);
|
|
67
71
|
const cwd = cwdFrom(options) ?? process.cwd();
|
|
68
72
|
const { config } = await loadConfig({ cwd, createIfMissing: false });
|
|
73
|
+
const layout = detectProjectLayout(cwd);
|
|
74
|
+
const repoRoot = config.repoRoot ?? layout.root;
|
|
69
75
|
const email = str(options, "email");
|
|
70
76
|
const login = str(options, "login");
|
|
71
77
|
if (!email && !login) {
|
|
@@ -76,7 +82,7 @@ export async function cmdChangeLogsLookup(options: ParsedOptions): Promise<numbe
|
|
|
76
82
|
cwd,
|
|
77
83
|
email,
|
|
78
84
|
login,
|
|
79
|
-
map: str(options, "map") ?? config
|
|
85
|
+
map: str(options, "map") ?? resolveAuthorMapPath(config, repoRoot),
|
|
80
86
|
githubToken: str(options, "github-token") ?? config.githubToken ?? process.env.GITHUB_TOKEN,
|
|
81
87
|
fetch: flag(options, "fetch"),
|
|
82
88
|
});
|
package/src/cli/cli.ts
CHANGED
|
@@ -10,7 +10,7 @@ import { registerInstallNativeCommand } from "./install-native-cmd.js";
|
|
|
10
10
|
import { registerLintCommands } from "./lint-cmd.js";
|
|
11
11
|
import { registerPublishCommand } from "./publish-cmd.js";
|
|
12
12
|
import { registerRetimeCommand } from "./retime-cmd.js";
|
|
13
|
-
import {
|
|
13
|
+
import { registerCommitCommand } from "./commit-cmd.js";
|
|
14
14
|
import { registerTrustCommand } from "./trust-cmd.js";
|
|
15
15
|
import { registerUpdateCommand } from "./update-cmd.js";
|
|
16
16
|
import { registerUploadCommand } from "./upload-cmd.js";
|
|
@@ -30,7 +30,7 @@ function buildNiftyCli() {
|
|
|
30
30
|
registerBumpCommand(cli);
|
|
31
31
|
registerPublishCommand(cli);
|
|
32
32
|
registerTrustCommand(cli);
|
|
33
|
-
|
|
33
|
+
registerCommitCommand(cli);
|
|
34
34
|
registerRetimeCommand(cli);
|
|
35
35
|
registerChangeLogsCommand(cli);
|
|
36
36
|
|
|
@@ -0,0 +1,226 @@
|
|
|
1
|
+
import { readFile, writeFile } from "node:fs/promises";
|
|
2
|
+
|
|
3
|
+
import type { Cli, ParsedOptions } from "@vmz/commander";
|
|
4
|
+
|
|
5
|
+
import { loadNiftyNative } from "../native.js";
|
|
6
|
+
import { bootstrapFromOptions } from "./context.js";
|
|
7
|
+
import { lintCommits, type LintOptions } from "./lint.js";
|
|
8
|
+
import { cwdFrom, flag, str, strList } from "./options.js";
|
|
9
|
+
|
|
10
|
+
export function registerCommitCommand(cli: Cli): void {
|
|
11
|
+
const commit = cli.command("commit", "cli.cmd.commit");
|
|
12
|
+
|
|
13
|
+
commit
|
|
14
|
+
.command("export", "cli.cmd.commit.export")
|
|
15
|
+
.option("--base <ref>", "cli.opt.base")
|
|
16
|
+
.option("--ref <ref>", "cli.opt.ref")
|
|
17
|
+
.option("--path <file>", "cli.opt.path")
|
|
18
|
+
.option("--with-lint", "cli.opt.with-lint")
|
|
19
|
+
.option("--errors-only", "cli.opt.errors-only")
|
|
20
|
+
.action(async (options) => cmdCommitExport(options));
|
|
21
|
+
|
|
22
|
+
commit
|
|
23
|
+
.command("apply", "cli.cmd.commit.apply")
|
|
24
|
+
.option("--base <ref>", "cli.opt.base")
|
|
25
|
+
.option("--ref <ref>", "cli.opt.ref")
|
|
26
|
+
.option("--path <file>", "cli.opt.path")
|
|
27
|
+
.option("--dry-run", "cli.opt.dry-run")
|
|
28
|
+
.action(async (options) => cmdCommitApply(options));
|
|
29
|
+
|
|
30
|
+
commit
|
|
31
|
+
.command("scan", "cli.cmd.commit.scan")
|
|
32
|
+
.option("--from <ref>", "cli.opt.from")
|
|
33
|
+
.option("--to <ref>", "cli.opt.to")
|
|
34
|
+
.option("-s, --subject <text>...", "cli.opt.subject")
|
|
35
|
+
.option("--json", "cli.opt.json")
|
|
36
|
+
.option("--errors-only", "cli.opt.errors-only")
|
|
37
|
+
.action(async (options) => cmdCommitScan(options));
|
|
38
|
+
|
|
39
|
+
commit
|
|
40
|
+
.command("audit", "cli.cmd.commit.audit")
|
|
41
|
+
.option("--from <ref>", "cli.opt.from")
|
|
42
|
+
.option("--to <ref>", "cli.opt.to")
|
|
43
|
+
.option("-s, --subject <text>...", "cli.opt.subject")
|
|
44
|
+
.option("--json", "cli.opt.json")
|
|
45
|
+
.option("--errors-only", "cli.opt.errors-only")
|
|
46
|
+
.action(async (options) => cmdCommitAudit(options));
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export async function cmdCommitExport(options: ParsedOptions): Promise<number> {
|
|
50
|
+
await bootstrapFromOptions(options);
|
|
51
|
+
const base = requireStr(options, "base");
|
|
52
|
+
const path = str(options, "path") ?? "commit.pending.json";
|
|
53
|
+
const native = loadNiftyNative();
|
|
54
|
+
const report = native.history["commit-export"]({
|
|
55
|
+
cwd: cwdFrom(options),
|
|
56
|
+
base,
|
|
57
|
+
ref: str(options, "ref"),
|
|
58
|
+
path,
|
|
59
|
+
});
|
|
60
|
+
console.log(`export: wrote ${report.count} commit entry(ies) to ${report.path}`);
|
|
61
|
+
|
|
62
|
+
if (flag(options, "with-lint")) {
|
|
63
|
+
const errorCount = await lintAndAnnotateExport({
|
|
64
|
+
path,
|
|
65
|
+
from: base,
|
|
66
|
+
to: str(options, "ref") ?? "HEAD",
|
|
67
|
+
cwd: cwdFrom(options),
|
|
68
|
+
json: flag(options, "json"),
|
|
69
|
+
errorsOnly: flag(options, "errors-only"),
|
|
70
|
+
reportTitle: "commit export lint",
|
|
71
|
+
});
|
|
72
|
+
if (errorCount > 0) {
|
|
73
|
+
return 1;
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
return 0;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
export async function cmdCommitApply(options: ParsedOptions): Promise<number> {
|
|
81
|
+
await bootstrapFromOptions(options);
|
|
82
|
+
const base = requireStr(options, "base");
|
|
83
|
+
const path = requireStr(options, "path");
|
|
84
|
+
const dryRun = flag(options, "dry-run");
|
|
85
|
+
const native = loadNiftyNative();
|
|
86
|
+
const report = native.history["commit-apply"]({
|
|
87
|
+
cwd: cwdFrom(options),
|
|
88
|
+
base,
|
|
89
|
+
ref: str(options, "ref"),
|
|
90
|
+
path,
|
|
91
|
+
dryRun,
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
if (dryRun) {
|
|
95
|
+
if (report.changes.length === 0) {
|
|
96
|
+
console.log("dry-run: no commit objects need rewriting");
|
|
97
|
+
return 0;
|
|
98
|
+
}
|
|
99
|
+
console.log(`dry-run: ${report.changes.length} commit object(s) would be rewritten on ${report.refName}\n`);
|
|
100
|
+
for (const change of report.changes) {
|
|
101
|
+
console.log(`${change.oldOid} ${change.oldSubject}`);
|
|
102
|
+
console.log(` -> ${change.newSubject}`);
|
|
103
|
+
if (change.parentsRelinked && !change.messageChanged) {
|
|
104
|
+
console.log(" (parent chain relink only)");
|
|
105
|
+
}
|
|
106
|
+
console.log();
|
|
107
|
+
}
|
|
108
|
+
return 0;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
console.log(
|
|
112
|
+
`applied ${report.changes.length} commit object(s); ${report.oldTip} -> ${report.newTip ?? report.oldTip}`,
|
|
113
|
+
);
|
|
114
|
+
for (const change of report.changes) {
|
|
115
|
+
console.log(` ${change.oldOid} ${change.newSubject}`);
|
|
116
|
+
}
|
|
117
|
+
return 0;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
export async function cmdCommitScan(options: ParsedOptions): Promise<number> {
|
|
121
|
+
await bootstrapFromOptions(options);
|
|
122
|
+
await lintCommits({ ...commitLintOptions(options), reportTitle: "commit scan" }, false, true);
|
|
123
|
+
return 0;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
export async function cmdCommitAudit(options: ParsedOptions): Promise<number> {
|
|
127
|
+
await bootstrapFromOptions(options);
|
|
128
|
+
const errorCount = await lintCommits(commitLintOptions(options), false, true);
|
|
129
|
+
return errorCount > 0 ? 1 : 0;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
function commitLintOptions(options: ParsedOptions): LintOptions {
|
|
133
|
+
return {
|
|
134
|
+
subjects: strList(options, "subject"),
|
|
135
|
+
from: str(options, "from"),
|
|
136
|
+
to: str(options, "to"),
|
|
137
|
+
cwd: cwdFrom(options),
|
|
138
|
+
scanCargo: false,
|
|
139
|
+
commitOnly: true,
|
|
140
|
+
json: flag(options, "json"),
|
|
141
|
+
errorsOnly: flag(options, "errors-only"),
|
|
142
|
+
reportTitle: "commit audit",
|
|
143
|
+
};
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
type ExportDocument = {
|
|
147
|
+
version: number;
|
|
148
|
+
entries: Array<{
|
|
149
|
+
hash: string;
|
|
150
|
+
message: string;
|
|
151
|
+
violations?: Array<{
|
|
152
|
+
rule: string;
|
|
153
|
+
severity: "error" | "warning" | "info";
|
|
154
|
+
message: string;
|
|
155
|
+
}>;
|
|
156
|
+
}>;
|
|
157
|
+
};
|
|
158
|
+
|
|
159
|
+
async function lintAndAnnotateExport(options: {
|
|
160
|
+
path: string;
|
|
161
|
+
from: string;
|
|
162
|
+
to: string;
|
|
163
|
+
cwd: string;
|
|
164
|
+
json: boolean;
|
|
165
|
+
errorsOnly: boolean;
|
|
166
|
+
reportTitle: string;
|
|
167
|
+
}): Promise<number> {
|
|
168
|
+
const native = loadNiftyNative();
|
|
169
|
+
const report = native.lint.run({
|
|
170
|
+
cwd: options.cwd,
|
|
171
|
+
fromRef: options.from,
|
|
172
|
+
toRef: options.to,
|
|
173
|
+
commitOnly: true,
|
|
174
|
+
scanCargo: false,
|
|
175
|
+
});
|
|
176
|
+
const diagnostics = options.errorsOnly
|
|
177
|
+
? report.diagnostics.filter((item) => item.severity === "error")
|
|
178
|
+
: report.diagnostics;
|
|
179
|
+
|
|
180
|
+
if (options.json) {
|
|
181
|
+
const { printCommitLintReport } = await import("./commit-lint-report.js");
|
|
182
|
+
printCommitLintReport(diagnostics, report.errorCount, report.warningCount, {
|
|
183
|
+
json: true,
|
|
184
|
+
errorsOnly: options.errorsOnly,
|
|
185
|
+
title: options.reportTitle,
|
|
186
|
+
});
|
|
187
|
+
} else {
|
|
188
|
+
const { printCommitLintReport } = await import("./commit-lint-report.js");
|
|
189
|
+
printCommitLintReport(diagnostics, report.errorCount, report.warningCount, {
|
|
190
|
+
errorsOnly: options.errorsOnly,
|
|
191
|
+
title: options.reportTitle,
|
|
192
|
+
});
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
const byHash = new Map<string, Array<{ rule: string; severity: "error" | "warning" | "info"; message: string }>>();
|
|
196
|
+
for (const item of diagnostics) {
|
|
197
|
+
if (!item.hash) {
|
|
198
|
+
continue;
|
|
199
|
+
}
|
|
200
|
+
const bucket = byHash.get(item.hash) ?? [];
|
|
201
|
+
bucket.push({ rule: item.rule, severity: item.severity, message: item.message });
|
|
202
|
+
byHash.set(item.hash, bucket);
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
const text = await readFile(options.path, "utf8");
|
|
206
|
+
const document = JSON.parse(text) as ExportDocument;
|
|
207
|
+
document.entries = document.entries.map((entry) => {
|
|
208
|
+
const violations = byHash.get(entry.hash);
|
|
209
|
+
if (!violations || violations.length === 0) {
|
|
210
|
+
return entry;
|
|
211
|
+
}
|
|
212
|
+
return { ...entry, violations };
|
|
213
|
+
});
|
|
214
|
+
await writeFile(options.path, `${JSON.stringify(document, null, 2)}\n`, "utf8");
|
|
215
|
+
const violationCount = document.entries.filter((entry) => entry.violations && entry.violations.length > 0).length;
|
|
216
|
+
console.log(`export: annotated ${violationCount} commit entry(ies) with lint violations in ${options.path}`);
|
|
217
|
+
return report.errorCount;
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
function requireStr(options: ParsedOptions, key: string): string {
|
|
221
|
+
const value = str(options, key);
|
|
222
|
+
if (!value) {
|
|
223
|
+
throw new Error(`missing required --${key}`);
|
|
224
|
+
}
|
|
225
|
+
return value;
|
|
226
|
+
}
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
export type CommitLintDiagnostic = {
|
|
2
|
+
rule: string;
|
|
3
|
+
severity: "error" | "warning" | "info";
|
|
4
|
+
message: string;
|
|
5
|
+
subject?: string;
|
|
6
|
+
hash?: string;
|
|
7
|
+
path?: string;
|
|
8
|
+
line?: number;
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
export type CommitLintReportOptions = {
|
|
12
|
+
json?: boolean;
|
|
13
|
+
errorsOnly?: boolean;
|
|
14
|
+
title?: string;
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
type CommitLintGroup = {
|
|
18
|
+
hash?: string;
|
|
19
|
+
subject?: string;
|
|
20
|
+
items: CommitLintDiagnostic[];
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
export function printCommitLintReport(
|
|
24
|
+
diagnostics: CommitLintDiagnostic[],
|
|
25
|
+
errorCount: number,
|
|
26
|
+
warningCount: number,
|
|
27
|
+
options: CommitLintReportOptions = {},
|
|
28
|
+
): void {
|
|
29
|
+
const filtered = options.errorsOnly
|
|
30
|
+
? diagnostics.filter((item) => item.severity === "error")
|
|
31
|
+
: diagnostics;
|
|
32
|
+
|
|
33
|
+
if (options.json) {
|
|
34
|
+
const payload = {
|
|
35
|
+
errorCount,
|
|
36
|
+
warningCount,
|
|
37
|
+
diagnostics: filtered,
|
|
38
|
+
groups: groupCommitDiagnostics(filtered),
|
|
39
|
+
summaryByRule: summarizeByRule(filtered),
|
|
40
|
+
};
|
|
41
|
+
console.log(JSON.stringify(payload, null, 2));
|
|
42
|
+
return;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
const title = options.title ?? "commit lint";
|
|
46
|
+
if (filtered.length === 0) {
|
|
47
|
+
console.log(`${title}: no issues found`);
|
|
48
|
+
return;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
const groups = groupCommitDiagnostics(filtered);
|
|
52
|
+
const commitCount = groups.filter((group) => group.hash || group.subject).length;
|
|
53
|
+
console.log(`${title}: ${errorCount} error(s), ${warningCount} warning(s) in ${commitCount} commit(s)\n`);
|
|
54
|
+
|
|
55
|
+
for (const group of groups) {
|
|
56
|
+
printCommitGroup(group);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
const summary = summarizeByRule(filtered.filter((item) => item.severity === "error"));
|
|
60
|
+
if (summary.length > 0) {
|
|
61
|
+
console.log("summary by rule:");
|
|
62
|
+
for (const [rule, count] of summary) {
|
|
63
|
+
console.log(` ${rule} ${count}`);
|
|
64
|
+
}
|
|
65
|
+
console.log();
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
function groupCommitDiagnostics(diagnostics: CommitLintDiagnostic[]): CommitLintGroup[] {
|
|
70
|
+
const groups = new Map<string, CommitLintGroup>();
|
|
71
|
+
const order: string[] = [];
|
|
72
|
+
|
|
73
|
+
for (const item of diagnostics) {
|
|
74
|
+
const key = item.hash ?? item.subject ?? "__inline__";
|
|
75
|
+
if (!groups.has(key)) {
|
|
76
|
+
groups.set(key, {
|
|
77
|
+
hash: item.hash,
|
|
78
|
+
subject: item.subject,
|
|
79
|
+
items: [],
|
|
80
|
+
});
|
|
81
|
+
order.push(key);
|
|
82
|
+
}
|
|
83
|
+
groups.get(key)?.items.push(item);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
return order.map((key) => groups.get(key)!);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
function summarizeByRule(diagnostics: CommitLintDiagnostic[]): Array<[string, number]> {
|
|
90
|
+
const counts = new Map<string, number>();
|
|
91
|
+
for (const item of diagnostics) {
|
|
92
|
+
counts.set(item.rule, (counts.get(item.rule) ?? 0) + 1);
|
|
93
|
+
}
|
|
94
|
+
return [...counts.entries()].sort((left, right) => right[1] - left[1] || left[0].localeCompare(right[0]));
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
function printCommitGroup(group: CommitLintGroup): void {
|
|
98
|
+
if (group.hash || group.subject) {
|
|
99
|
+
const hash = group.hash ? `${group.hash.slice(0, 8)} ` : "";
|
|
100
|
+
const subject = group.subject ?? "(no subject)";
|
|
101
|
+
console.log(`${hash}${subject}`);
|
|
102
|
+
} else {
|
|
103
|
+
console.log("(inline subject lint)");
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
for (const item of group.items) {
|
|
107
|
+
console.log(` ${item.severity} ${item.rule} ${item.message}`);
|
|
108
|
+
}
|
|
109
|
+
console.log();
|
|
110
|
+
}
|
package/src/cli/format.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { loadConfig, resolveFormatConfig } from "../config/index.js";
|
|
1
2
|
import { loadNiftyNative } from "../native.js";
|
|
2
3
|
|
|
3
4
|
export type FormatOptions = {
|
|
@@ -6,10 +7,18 @@ export type FormatOptions = {
|
|
|
6
7
|
};
|
|
7
8
|
|
|
8
9
|
export async function formatWorkspace(options: FormatOptions): Promise<number> {
|
|
10
|
+
const cwd = options.cwd ?? process.cwd();
|
|
11
|
+
const { config } = await loadConfig({ cwd, createIfMissing: false, env: { command: "format" } });
|
|
12
|
+
const resolved = resolveFormatConfig(config.format);
|
|
9
13
|
const native = loadNiftyNative();
|
|
10
14
|
const report = native.formatter.run({
|
|
11
|
-
cwd
|
|
15
|
+
cwd,
|
|
12
16
|
check: options.check,
|
|
17
|
+
includes: resolved.includes,
|
|
18
|
+
excludes: resolved.excludes,
|
|
19
|
+
rust: resolved.rust,
|
|
20
|
+
javascript: resolved.javascript,
|
|
21
|
+
style: resolved.style,
|
|
13
22
|
});
|
|
14
23
|
|
|
15
24
|
if (report.errors.length > 0) {
|
|
@@ -19,9 +28,11 @@ export async function formatWorkspace(options: FormatOptions): Promise<number> {
|
|
|
19
28
|
}
|
|
20
29
|
|
|
21
30
|
if (report.formatted === 0 && report.unchanged > 0 && report.errors.length === 0) {
|
|
22
|
-
console.log(
|
|
31
|
+
console.log(`format: no changes needed (preset ${resolved.preset})`);
|
|
23
32
|
} else if (report.errors.length === 0) {
|
|
24
|
-
console.log(
|
|
33
|
+
console.log(
|
|
34
|
+
`format: ${report.formatted} file(s) updated, ${report.unchanged} unchanged (preset ${resolved.preset})`,
|
|
35
|
+
);
|
|
25
36
|
}
|
|
26
37
|
|
|
27
38
|
return report.errors.length > 0 ? 1 : 0;
|
package/src/cli/lint.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { loadConfig } from "../config/loadConfig.js";
|
|
2
2
|
import { loadNiftyNative } from "../native.js";
|
|
3
|
+
import { printCommitLintReport, type CommitLintDiagnostic } from "./commit-lint-report.js";
|
|
3
4
|
|
|
4
5
|
export type LintOptions = {
|
|
5
6
|
subjects: string[];
|
|
@@ -7,17 +8,13 @@ export type LintOptions = {
|
|
|
7
8
|
to?: string;
|
|
8
9
|
cwd?: string;
|
|
9
10
|
scanCargo?: boolean;
|
|
11
|
+
commitOnly?: boolean;
|
|
12
|
+
json?: boolean;
|
|
13
|
+
errorsOnly?: boolean;
|
|
14
|
+
reportTitle?: string;
|
|
10
15
|
};
|
|
11
16
|
|
|
12
|
-
type LintDiagnostic =
|
|
13
|
-
rule: string;
|
|
14
|
-
severity: "error" | "warning" | "info";
|
|
15
|
-
message: string;
|
|
16
|
-
subject?: string;
|
|
17
|
-
hash?: string;
|
|
18
|
-
path?: string;
|
|
19
|
-
line?: number;
|
|
20
|
-
};
|
|
17
|
+
type LintDiagnostic = CommitLintDiagnostic;
|
|
21
18
|
|
|
22
19
|
type LintRuleConfig = {
|
|
23
20
|
id: string;
|
|
@@ -33,7 +30,11 @@ export async function runLint(argv: string[], check: boolean): Promise<void> {
|
|
|
33
30
|
}
|
|
34
31
|
}
|
|
35
32
|
|
|
36
|
-
export async function lintCommits(
|
|
33
|
+
export async function lintCommits(
|
|
34
|
+
options: LintOptions,
|
|
35
|
+
check: boolean,
|
|
36
|
+
commitOnly = options.commitOnly ?? false,
|
|
37
|
+
): Promise<number> {
|
|
37
38
|
const native = loadNiftyNative();
|
|
38
39
|
const cwd = options.cwd ?? process.cwd();
|
|
39
40
|
const { config } = await loadConfig({ cwd, createIfMissing: false });
|
|
@@ -51,25 +52,35 @@ export async function lintCommits(options: LintOptions, check: boolean): Promise
|
|
|
51
52
|
subjects: options.subjects.length > 0 ? options.subjects : undefined,
|
|
52
53
|
rules,
|
|
53
54
|
scanCargo: options.scanCargo,
|
|
55
|
+
commitOnly: commitOnly || options.commitOnly,
|
|
54
56
|
});
|
|
55
57
|
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
58
|
+
if (commitOnly || options.commitOnly) {
|
|
59
|
+
printCommitLintReport(report.diagnostics, report.errorCount, report.warningCount, {
|
|
60
|
+
json: options.json,
|
|
61
|
+
errorsOnly: options.errorsOnly,
|
|
62
|
+
title: options.reportTitle ?? "commit lint",
|
|
63
|
+
});
|
|
62
64
|
} else {
|
|
63
|
-
|
|
65
|
+
for (const item of report.diagnostics) {
|
|
66
|
+
printWorkspaceDiagnostic(item);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
if (report.diagnostics.length > 0) {
|
|
70
|
+
console.log(`lint: ${report.errorCount} error(s), ${report.warningCount} warning(s)`);
|
|
71
|
+
} else {
|
|
72
|
+
console.log("lint: no issues found");
|
|
73
|
+
}
|
|
64
74
|
}
|
|
65
75
|
|
|
66
76
|
return report.errorCount;
|
|
67
77
|
}
|
|
68
78
|
|
|
69
|
-
function
|
|
79
|
+
function printWorkspaceDiagnostic(item: LintDiagnostic): void {
|
|
70
80
|
const prefix = item.severity === "error" ? "error" : item.severity === "warning" ? "warning" : "info";
|
|
71
81
|
if (item.subject) {
|
|
72
|
-
|
|
82
|
+
const hash = item.hash ? `${item.hash.slice(0, 8)} ` : "";
|
|
83
|
+
console.log(`${prefix} [${item.rule}] ${hash}${item.message} (${item.subject})`);
|
|
73
84
|
return;
|
|
74
85
|
}
|
|
75
86
|
const location =
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { existsSync, readFileSync } from "node:fs";
|
|
2
|
+
import { isAbsolute, join } from "node:path";
|
|
3
|
+
|
|
4
|
+
import type { NiftyConfig } from "./types.js";
|
|
5
|
+
|
|
6
|
+
/** Default path relative to repo root (email → GitHub author JSON). */
|
|
7
|
+
export const DEFAULT_AUTHOR_MAP_PATH = "documentation/maintenance/author-github.json";
|
|
8
|
+
|
|
9
|
+
/** Resolve configured author map path to an absolute filesystem path. */
|
|
10
|
+
export function resolveAuthorMapPath(config: NiftyConfig, repoRoot: string): string {
|
|
11
|
+
const configured = config.authorMap ?? DEFAULT_AUTHOR_MAP_PATH;
|
|
12
|
+
return isAbsolute(configured) ? configured : join(repoRoot, configured);
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/** Load author map file contents for native helpers expecting JSON text. */
|
|
16
|
+
export function loadAuthorMapJson(repoRoot: string, authorMapPath?: string): string {
|
|
17
|
+
const abs = authorMapPath
|
|
18
|
+
? isAbsolute(authorMapPath)
|
|
19
|
+
? authorMapPath
|
|
20
|
+
: join(repoRoot, authorMapPath)
|
|
21
|
+
: resolveAuthorMapPath({}, repoRoot);
|
|
22
|
+
if (!existsSync(abs)) {
|
|
23
|
+
return "{}";
|
|
24
|
+
}
|
|
25
|
+
return readFileSync(abs, "utf8");
|
|
26
|
+
}
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import type { NiftyFormatConfig, NiftyFormatPreset, NiftyFormatStyleConfig } from "./types.js";
|
|
2
|
+
|
|
3
|
+
export type ResolvedFormatOptions = {
|
|
4
|
+
preset: NiftyFormatPreset;
|
|
5
|
+
includes?: string[];
|
|
6
|
+
excludes?: string[];
|
|
7
|
+
rust?: boolean;
|
|
8
|
+
javascript?: boolean;
|
|
9
|
+
style: NiftyFormatStyleConfig;
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
const DEFAULT_FORMAT_STYLE: NiftyFormatStyleConfig = {
|
|
13
|
+
indentStyle: "space",
|
|
14
|
+
indentWidth: 4,
|
|
15
|
+
lineWidth: 144,
|
|
16
|
+
quoteStyle: "single",
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
const NIFTY_PRESET_INCLUDES = [
|
|
20
|
+
"scripts/**",
|
|
21
|
+
"projects/packages/**",
|
|
22
|
+
"projects/conformance/**",
|
|
23
|
+
"projects/dashboard/**",
|
|
24
|
+
"package.json",
|
|
25
|
+
"nifty.config.ts",
|
|
26
|
+
] as const;
|
|
27
|
+
|
|
28
|
+
/** VMZ hybrid monorepo — JS/TS surfaces only, never whole-repo walk. */
|
|
29
|
+
const VMZ_INCLUDES = [
|
|
30
|
+
"scripts/**",
|
|
31
|
+
"packages/runtimes/**",
|
|
32
|
+
"packages/examples/**",
|
|
33
|
+
"packages/editors/**",
|
|
34
|
+
"packages/ui/**",
|
|
35
|
+
"packages/plugins/**",
|
|
36
|
+
"packages/content/**",
|
|
37
|
+
"packages/homepage/**",
|
|
38
|
+
"package.json",
|
|
39
|
+
"nifty.config.ts",
|
|
40
|
+
] as const;
|
|
41
|
+
|
|
42
|
+
const PRESET_INCLUDES: Record<NiftyFormatPreset, readonly string[] | undefined> = {
|
|
43
|
+
default: undefined,
|
|
44
|
+
nifty: NIFTY_PRESET_INCLUDES,
|
|
45
|
+
"npm-tools": NIFTY_PRESET_INCLUDES,
|
|
46
|
+
vmz: VMZ_INCLUDES,
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
/** Merge `nifty.config` `format` with built-in preset defaults. */
|
|
50
|
+
export function resolveFormatConfig(format: NiftyFormatConfig | undefined): ResolvedFormatOptions {
|
|
51
|
+
const preset = format?.preset ?? "default";
|
|
52
|
+
const presetIncludes = PRESET_INCLUDES[preset];
|
|
53
|
+
return {
|
|
54
|
+
preset,
|
|
55
|
+
includes: format?.includes ?? (presetIncludes ? [...presetIncludes] : undefined),
|
|
56
|
+
excludes: format?.excludes,
|
|
57
|
+
rust: format?.rust,
|
|
58
|
+
javascript: format?.javascript,
|
|
59
|
+
style: {
|
|
60
|
+
...DEFAULT_FORMAT_STYLE,
|
|
61
|
+
...format?.style,
|
|
62
|
+
},
|
|
63
|
+
};
|
|
64
|
+
}
|
package/src/config/index.ts
CHANGED
|
@@ -8,7 +8,11 @@ export {
|
|
|
8
8
|
type ProjectLayout,
|
|
9
9
|
} from "./detectProject.js";
|
|
10
10
|
export {
|
|
11
|
-
|
|
11
|
+
DEFAULT_AUTHOR_MAP_PATH,
|
|
12
|
+
loadAuthorMapJson,
|
|
13
|
+
resolveAuthorMapPath,
|
|
14
|
+
} from "./authorMap.js";
|
|
15
|
+
export {
|
|
12
16
|
defaultConfigPath,
|
|
13
17
|
ensureConfigFile,
|
|
14
18
|
findConfigFile,
|
|
@@ -16,10 +20,14 @@ export {
|
|
|
16
20
|
type LoadConfigOptions,
|
|
17
21
|
type LoadedNiftyConfig,
|
|
18
22
|
} from "./loadConfig.js";
|
|
23
|
+
export { resolveFormatConfig, type ResolvedFormatOptions } from "./formatPresets.js";
|
|
19
24
|
export {
|
|
20
25
|
CONFIG_FILE_NAMES,
|
|
21
26
|
type NiftyAuthorEntry,
|
|
22
27
|
type NiftyConfig,
|
|
23
28
|
type NiftyConfigEnv,
|
|
24
29
|
type NiftyConfigExport,
|
|
30
|
+
type NiftyFormatConfig,
|
|
31
|
+
type NiftyFormatPreset,
|
|
32
|
+
type NiftyFormatStyleConfig,
|
|
25
33
|
} from "./types.js";
|
package/src/config/loadConfig.ts
CHANGED
|
@@ -28,7 +28,7 @@ export type LoadedNiftyConfig = {
|
|
|
28
28
|
};
|
|
29
29
|
|
|
30
30
|
const DEFAULT_CONFIG_TEMPLATE = `// Nifty project configuration.
|
|
31
|
-
// See @doki-land/nifty for defineConfig,
|
|
31
|
+
// See @doki-land/nifty for defineConfig, format presets, authorMap path, and lint rules.
|
|
32
32
|
export default {};
|
|
33
33
|
`;
|
|
34
34
|
|
|
@@ -120,10 +120,3 @@ export async function loadConfig(options: LoadConfigOptions = {}): Promise<Loade
|
|
|
120
120
|
return { config, configFile, created };
|
|
121
121
|
}
|
|
122
122
|
|
|
123
|
-
/** Serialize `authorMap` for native helpers expecting JSON text. */
|
|
124
|
-
export function authorMapToJson(authorMap: NiftyConfig["authorMap"]): string {
|
|
125
|
-
if (!authorMap || Object.keys(authorMap).length === 0) {
|
|
126
|
-
return "{}";
|
|
127
|
-
}
|
|
128
|
-
return JSON.stringify(authorMap);
|
|
129
|
-
}
|
package/src/config/types.ts
CHANGED
|
@@ -1,15 +1,46 @@
|
|
|
1
|
-
/** GitHub author
|
|
1
|
+
/** One email → GitHub author row in `author-github.json`. */
|
|
2
2
|
export type NiftyAuthorEntry = {
|
|
3
3
|
id?: number;
|
|
4
4
|
login?: string;
|
|
5
5
|
};
|
|
6
6
|
|
|
7
|
+
/** Built-in workspace format presets (`nifty.config` `format.preset`). */
|
|
8
|
+
export type NiftyFormatPreset = "default" | "nifty" | "npm-tools" | "vmz";
|
|
9
|
+
|
|
10
|
+
/** `oxc_formatter` style knobs (`nifty.config` `format.style`). */
|
|
11
|
+
export type NiftyFormatStyleConfig = {
|
|
12
|
+
/** Indent with spaces or tabs (default: `space`). */
|
|
13
|
+
indentStyle?: "space" | "tab";
|
|
14
|
+
/** Spaces or tabs per indent level (default: `4`). */
|
|
15
|
+
indentWidth?: number;
|
|
16
|
+
/** Soft line width (default: `144`). */
|
|
17
|
+
lineWidth?: number;
|
|
18
|
+
/** String quote preference (default: `single`). */
|
|
19
|
+
quoteStyle?: "single" | "double";
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
/** Workspace format options (`nifty format` reads `nifty.config` `format`). */
|
|
23
|
+
export type NiftyFormatConfig = {
|
|
24
|
+
/** Select built-in include roots for Nifty hybrid monorepos. */
|
|
25
|
+
preset?: NiftyFormatPreset;
|
|
26
|
+
/** Extra include globs (override preset list when set alone). */
|
|
27
|
+
includes?: string[];
|
|
28
|
+
/** Skip paths under these globs after includes expand. */
|
|
29
|
+
excludes?: string[];
|
|
30
|
+
/** Run `cargo fmt` (default: true when a Cargo workspace is detected). */
|
|
31
|
+
rust?: boolean;
|
|
32
|
+
/** Run `oxc_formatter` on JS/TS targets (default: true). */
|
|
33
|
+
javascript?: boolean;
|
|
34
|
+
/** Inline oxc formatter style (not an external config file path). */
|
|
35
|
+
style?: NiftyFormatStyleConfig;
|
|
36
|
+
};
|
|
37
|
+
|
|
7
38
|
/** Nifty project configuration (`nifty.config.ts` / `nifty.config.js`). */
|
|
8
39
|
export type NiftyConfig = {
|
|
9
40
|
/** GitHub personal access token for email search / profile fetch. */
|
|
10
41
|
githubToken?: string;
|
|
11
|
-
/**
|
|
12
|
-
authorMap?:
|
|
42
|
+
/** Path to `author-github.json` (default: `documentation/maintenance/author-github.json`). */
|
|
43
|
+
authorMap?: string;
|
|
13
44
|
/** Repository root override when discovering git metadata. */
|
|
14
45
|
repoRoot?: string;
|
|
15
46
|
/** Cargo workspace / crate root override. */
|
|
@@ -25,8 +56,6 @@ export type NiftyConfig = {
|
|
|
25
56
|
changelog?: {
|
|
26
57
|
/** GitHub `owner/repo` for contrib.rocks (default: parse `origin`). */
|
|
27
58
|
repo?: string;
|
|
28
|
-
/** Email → GitHub map path (default: `documentation/maintenance/author-github.json`). */
|
|
29
|
-
authorMap?: string;
|
|
30
59
|
/** `--write` output directory (default: `documentation/maintenance/releases`). */
|
|
31
60
|
releasesDir?: string;
|
|
32
61
|
};
|
|
@@ -38,6 +67,8 @@ export type NiftyConfig = {
|
|
|
38
67
|
severity?: "error" | "warning" | "info";
|
|
39
68
|
}>;
|
|
40
69
|
};
|
|
70
|
+
/** Workspace format targets and engines (`nifty format`). */
|
|
71
|
+
format?: NiftyFormatConfig;
|
|
41
72
|
};
|
|
42
73
|
|
|
43
74
|
export type NiftyConfigExport = NiftyConfig | ((env: NiftyConfigEnv) => NiftyConfig | Promise<NiftyConfig>);
|
package/src/index.ts
CHANGED
|
@@ -21,16 +21,23 @@ export {
|
|
|
21
21
|
defineConfig,
|
|
22
22
|
loadConfig,
|
|
23
23
|
findConfigFile,
|
|
24
|
-
|
|
24
|
+
DEFAULT_AUTHOR_MAP_PATH,
|
|
25
|
+
loadAuthorMapJson,
|
|
26
|
+
resolveAuthorMapPath,
|
|
25
27
|
detectProjectLayout,
|
|
26
28
|
discoverPackageDirs,
|
|
27
29
|
findCargoManifest,
|
|
28
30
|
findPackageManifest,
|
|
29
31
|
type LoadConfigOptions,
|
|
30
32
|
type LoadedNiftyConfig,
|
|
33
|
+
resolveFormatConfig,
|
|
31
34
|
type NiftyAuthorEntry,
|
|
32
35
|
type NiftyConfig,
|
|
33
36
|
type NiftyConfigEnv,
|
|
37
|
+
type NiftyFormatConfig,
|
|
38
|
+
type NiftyFormatPreset,
|
|
39
|
+
type NiftyFormatStyleConfig,
|
|
34
40
|
type ProjectKind,
|
|
35
41
|
type ProjectLayout,
|
|
42
|
+
type ResolvedFormatOptions,
|
|
36
43
|
} from "./config/index.js";
|
package/src/native.ts
CHANGED
|
@@ -85,12 +85,27 @@ export type RewordRewriteReport = {
|
|
|
85
85
|
};
|
|
86
86
|
|
|
87
87
|
export type HistoryExports = {
|
|
88
|
+
"commit-export": (options: {
|
|
89
|
+
cwd?: string;
|
|
90
|
+
base: string;
|
|
91
|
+
ref?: string;
|
|
92
|
+
path: string;
|
|
93
|
+
}) => { count: number; path: string };
|
|
94
|
+
"commit-apply": (options: {
|
|
95
|
+
cwd?: string;
|
|
96
|
+
base: string;
|
|
97
|
+
ref?: string;
|
|
98
|
+
path: string;
|
|
99
|
+
dryRun?: boolean;
|
|
100
|
+
}) => RewordRewriteReport;
|
|
101
|
+
/** @deprecated use `commit-export` */
|
|
88
102
|
"reword-export": (options: {
|
|
89
103
|
cwd?: string;
|
|
90
104
|
base: string;
|
|
91
105
|
ref?: string;
|
|
92
106
|
path: string;
|
|
93
107
|
}) => { count: number; path: string };
|
|
108
|
+
/** @deprecated use `commit-apply` */
|
|
94
109
|
"reword-rewrite": (options: {
|
|
95
110
|
cwd?: string;
|
|
96
111
|
base: string;
|
|
@@ -160,6 +175,7 @@ export type LintRunOptions = {
|
|
|
160
175
|
subjects?: string[];
|
|
161
176
|
rules?: Array<{ id: string; enabled?: boolean; severity?: "error" | "warning" | "info" }>;
|
|
162
177
|
scanCargo?: boolean;
|
|
178
|
+
commitOnly?: boolean;
|
|
163
179
|
};
|
|
164
180
|
|
|
165
181
|
export type LintReportRecord = {
|
|
@@ -183,8 +199,25 @@ export type FormatReportRecord = {
|
|
|
183
199
|
errors: string[];
|
|
184
200
|
};
|
|
185
201
|
|
|
202
|
+
export type FormatterStyleOptions = {
|
|
203
|
+
indentStyle?: string;
|
|
204
|
+
indentWidth?: number;
|
|
205
|
+
lineWidth?: number;
|
|
206
|
+
quoteStyle?: string;
|
|
207
|
+
};
|
|
208
|
+
|
|
209
|
+
export type FormatterRunOptions = {
|
|
210
|
+
cwd?: string;
|
|
211
|
+
check?: boolean;
|
|
212
|
+
includes?: string[];
|
|
213
|
+
excludes?: string[];
|
|
214
|
+
rust?: boolean;
|
|
215
|
+
javascript?: boolean;
|
|
216
|
+
style?: FormatterStyleOptions;
|
|
217
|
+
};
|
|
218
|
+
|
|
186
219
|
export type FormatterExports = {
|
|
187
|
-
run: (options:
|
|
220
|
+
run: (options: FormatterRunOptions) => FormatReportRecord;
|
|
188
221
|
};
|
|
189
222
|
|
|
190
223
|
export type PublisherExports = {
|
|
@@ -280,6 +313,31 @@ type NativeBinding = {
|
|
|
280
313
|
totpSecret?: string | null;
|
|
281
314
|
token?: string | null;
|
|
282
315
|
}) => TrustReport;
|
|
316
|
+
gitToolsCommitExport: (options: {
|
|
317
|
+
cwd?: string | null;
|
|
318
|
+
base: string;
|
|
319
|
+
ref?: string | null;
|
|
320
|
+
path: string;
|
|
321
|
+
}) => { count: number; path: string };
|
|
322
|
+
gitToolsCommitApply: (options: {
|
|
323
|
+
cwd?: string | null;
|
|
324
|
+
base: string;
|
|
325
|
+
ref?: string | null;
|
|
326
|
+
path: string;
|
|
327
|
+
dryRun?: boolean | null;
|
|
328
|
+
}) => {
|
|
329
|
+
changes: Array<{
|
|
330
|
+
oldOid: string;
|
|
331
|
+
oldSubject: string;
|
|
332
|
+
newSubject: string;
|
|
333
|
+
parentsRelinked: boolean;
|
|
334
|
+
messageChanged: boolean;
|
|
335
|
+
}>;
|
|
336
|
+
oldTip: string;
|
|
337
|
+
newTip?: string | null;
|
|
338
|
+
refName: string;
|
|
339
|
+
dryRun: boolean;
|
|
340
|
+
};
|
|
283
341
|
gitToolsRewordExport: (options: {
|
|
284
342
|
cwd?: string | null;
|
|
285
343
|
base: string;
|
|
@@ -355,6 +413,7 @@ type NativeBinding = {
|
|
|
355
413
|
subjects?: string[] | null;
|
|
356
414
|
rules?: Array<{ id: string; enabled?: boolean | null; severity?: string | null }> | null;
|
|
357
415
|
scanCargo?: boolean | null;
|
|
416
|
+
commitOnly?: boolean | null;
|
|
358
417
|
check?: boolean | null;
|
|
359
418
|
}) => {
|
|
360
419
|
diagnostics: Array<{
|
|
@@ -376,6 +435,7 @@ type NativeBinding = {
|
|
|
376
435
|
subjects?: string[] | null;
|
|
377
436
|
rules?: Array<{ id: string; enabled?: boolean | null; severity?: string | null }> | null;
|
|
378
437
|
scanCargo?: boolean | null;
|
|
438
|
+
commitOnly?: boolean | null;
|
|
379
439
|
check?: boolean | null;
|
|
380
440
|
}) => {
|
|
381
441
|
diagnostics: Array<{
|
|
@@ -391,7 +451,15 @@ type NativeBinding = {
|
|
|
391
451
|
warningCount: number;
|
|
392
452
|
};
|
|
393
453
|
updaterRun: (options: { cwd?: string | null; interactive?: boolean | null }) => void;
|
|
394
|
-
formatterRun: (options: {
|
|
454
|
+
formatterRun: (options: {
|
|
455
|
+
cwd?: string | null;
|
|
456
|
+
check?: boolean | null;
|
|
457
|
+
includes?: string[] | null;
|
|
458
|
+
excludes?: string[] | null;
|
|
459
|
+
rust?: boolean | null;
|
|
460
|
+
javascript?: boolean | null;
|
|
461
|
+
style?: FormatterStyleOptions | null;
|
|
462
|
+
}) => {
|
|
395
463
|
formatted: number;
|
|
396
464
|
unchanged: number;
|
|
397
465
|
errors: string[];
|
|
@@ -424,6 +492,7 @@ function mapLintOptions(options: LintRunOptions) {
|
|
|
424
492
|
...(options.subjects !== undefined ? { subjects: options.subjects } : {}),
|
|
425
493
|
...(options.rules !== undefined ? { rules: options.rules } : {}),
|
|
426
494
|
...(options.scanCargo !== undefined ? { scanCargo: options.scanCargo } : {}),
|
|
495
|
+
...(options.commitOnly !== undefined ? { commitOnly: options.commitOnly } : {}),
|
|
427
496
|
};
|
|
428
497
|
}
|
|
429
498
|
|
|
@@ -498,6 +567,29 @@ function wrapBinding(binding: NativeBinding): NiftyNative {
|
|
|
498
567
|
"parse-github-remote": (url) => binding.gitParseGithubRemote(url) ?? undefined,
|
|
499
568
|
},
|
|
500
569
|
history: {
|
|
570
|
+
"commit-export": (options) =>
|
|
571
|
+
binding.gitToolsCommitExport({
|
|
572
|
+
...(options.cwd !== undefined ? { cwd: options.cwd } : {}),
|
|
573
|
+
base: options.base,
|
|
574
|
+
...(options.ref !== undefined ? { ref: options.ref } : {}),
|
|
575
|
+
path: options.path,
|
|
576
|
+
}),
|
|
577
|
+
"commit-apply": (options) => {
|
|
578
|
+
const report = binding.gitToolsCommitApply({
|
|
579
|
+
...(options.cwd !== undefined ? { cwd: options.cwd } : {}),
|
|
580
|
+
base: options.base,
|
|
581
|
+
...(options.ref !== undefined ? { ref: options.ref } : {}),
|
|
582
|
+
path: options.path,
|
|
583
|
+
...(options.dryRun !== undefined ? { dryRun: options.dryRun } : {}),
|
|
584
|
+
});
|
|
585
|
+
return {
|
|
586
|
+
changes: report.changes,
|
|
587
|
+
oldTip: report.oldTip,
|
|
588
|
+
newTip: report.newTip ?? undefined,
|
|
589
|
+
refName: report.refName,
|
|
590
|
+
dryRun: report.dryRun,
|
|
591
|
+
};
|
|
592
|
+
},
|
|
501
593
|
"reword-export": (options) =>
|
|
502
594
|
binding.gitToolsRewordExport({
|
|
503
595
|
...(options.cwd !== undefined ? { cwd: options.cwd } : {}),
|
|
@@ -586,10 +678,16 @@ function wrapBinding(binding: NativeBinding): NiftyNative {
|
|
|
586
678
|
},
|
|
587
679
|
},
|
|
588
680
|
formatter: {
|
|
589
|
-
run: (options) =>
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
681
|
+
run: (options) =>
|
|
682
|
+
binding.formatterRun({
|
|
683
|
+
...(options.cwd !== undefined ? { cwd: options.cwd } : {}),
|
|
684
|
+
...(options.check !== undefined ? { check: options.check } : {}),
|
|
685
|
+
...(options.includes !== undefined ? { includes: options.includes } : {}),
|
|
686
|
+
...(options.excludes !== undefined ? { excludes: options.excludes } : {}),
|
|
687
|
+
...(options.rust !== undefined ? { rust: options.rust } : {}),
|
|
688
|
+
...(options.javascript !== undefined ? { javascript: options.javascript } : {}),
|
|
689
|
+
...(options.style !== undefined ? { style: options.style } : {}),
|
|
690
|
+
}),
|
|
593
691
|
},
|
|
594
692
|
publisher: {
|
|
595
693
|
"publish-workspace": (options) =>
|
package/src/nifty.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import {
|
|
2
|
-
authorMapToJson,
|
|
3
2
|
detectProjectLayout,
|
|
3
|
+
loadAuthorMapJson,
|
|
4
4
|
loadConfig,
|
|
5
5
|
type LoadConfigOptions,
|
|
6
6
|
type NiftyConfig,
|
|
@@ -79,8 +79,9 @@ export class Nifty {
|
|
|
79
79
|
);
|
|
80
80
|
|
|
81
81
|
const native = loadNiftyNative();
|
|
82
|
+
const resolvedRepoRoot = merged.repoRoot ?? layout.root;
|
|
82
83
|
return new Nifty(
|
|
83
|
-
new Gitmoji(native.gitmoji,
|
|
84
|
+
new Gitmoji(native.gitmoji, loadAuthorMapJson(resolvedRepoRoot, merged.authorMap)),
|
|
84
85
|
new Git(native.git),
|
|
85
86
|
Github.open(merged.githubToken),
|
|
86
87
|
merged,
|
package/src/cli/reword-cmd.ts
DELETED
|
@@ -1,87 +0,0 @@
|
|
|
1
|
-
import type { Cli, ParsedOptions } from "@vmz/commander";
|
|
2
|
-
|
|
3
|
-
import { loadNiftyNative } from "../native.js";
|
|
4
|
-
import { bootstrapFromOptions } from "./context.js";
|
|
5
|
-
import { cwdFrom, flag, str } from "./options.js";
|
|
6
|
-
|
|
7
|
-
export function registerRewordCommand(cli: Cli): void {
|
|
8
|
-
const reword = cli.command("reword", "cli.cmd.reword");
|
|
9
|
-
|
|
10
|
-
reword
|
|
11
|
-
.command("export", "cli.cmd.reword.export")
|
|
12
|
-
.option("--base <ref>", "cli.opt.base")
|
|
13
|
-
.option("--ref <ref>", "cli.opt.ref")
|
|
14
|
-
.option("--path <file>", "cli.opt.path")
|
|
15
|
-
.action(async (options) => cmdRewordExport(options));
|
|
16
|
-
|
|
17
|
-
reword
|
|
18
|
-
.command("rewrite", "cli.cmd.reword.rewrite")
|
|
19
|
-
.option("--base <ref>", "cli.opt.base")
|
|
20
|
-
.option("--ref <ref>", "cli.opt.ref")
|
|
21
|
-
.option("--path <file>", "cli.opt.path")
|
|
22
|
-
.option("--dry-run", "cli.opt.dry-run")
|
|
23
|
-
.action(async (options) => cmdRewordRewrite(options));
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
export async function cmdRewordExport(options: ParsedOptions): Promise<number> {
|
|
27
|
-
await bootstrapFromOptions(options);
|
|
28
|
-
const base = requireStr(options, "base");
|
|
29
|
-
const path = str(options, "path") ?? "reword.pending.json";
|
|
30
|
-
const native = loadNiftyNative();
|
|
31
|
-
const report = native.history["reword-export"]({
|
|
32
|
-
cwd: cwdFrom(options),
|
|
33
|
-
base,
|
|
34
|
-
ref: str(options, "ref"),
|
|
35
|
-
path,
|
|
36
|
-
});
|
|
37
|
-
console.log(`export: wrote ${report.count} commit entry(ies) to ${report.path}`);
|
|
38
|
-
return 0;
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
export async function cmdRewordRewrite(options: ParsedOptions): Promise<number> {
|
|
42
|
-
await bootstrapFromOptions(options);
|
|
43
|
-
const base = requireStr(options, "base");
|
|
44
|
-
const path = requireStr(options, "path");
|
|
45
|
-
const dryRun = flag(options, "dry-run");
|
|
46
|
-
const native = loadNiftyNative();
|
|
47
|
-
const report = native.history["reword-rewrite"]({
|
|
48
|
-
cwd: cwdFrom(options),
|
|
49
|
-
base,
|
|
50
|
-
ref: str(options, "ref"),
|
|
51
|
-
path,
|
|
52
|
-
dryRun,
|
|
53
|
-
});
|
|
54
|
-
|
|
55
|
-
if (dryRun) {
|
|
56
|
-
if (report.changes.length === 0) {
|
|
57
|
-
console.log("dry-run: no commit objects need rewriting");
|
|
58
|
-
return 0;
|
|
59
|
-
}
|
|
60
|
-
console.log(`dry-run: ${report.changes.length} commit object(s) would be rewritten on ${report.refName}\n`);
|
|
61
|
-
for (const change of report.changes) {
|
|
62
|
-
console.log(`${change.oldOid} ${change.oldSubject}`);
|
|
63
|
-
console.log(` -> ${change.newSubject}`);
|
|
64
|
-
if (change.parentsRelinked && !change.messageChanged) {
|
|
65
|
-
console.log(" (parent chain relink only)");
|
|
66
|
-
}
|
|
67
|
-
console.log();
|
|
68
|
-
}
|
|
69
|
-
return 0;
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
console.log(
|
|
73
|
-
`rewrote ${report.changes.length} commit object(s); ${report.oldTip} -> ${report.newTip ?? report.oldTip}`,
|
|
74
|
-
);
|
|
75
|
-
for (const change of report.changes) {
|
|
76
|
-
console.log(` ${change.oldOid} ${change.newSubject}`);
|
|
77
|
-
}
|
|
78
|
-
return 0;
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
function requireStr(options: ParsedOptions, key: string): string {
|
|
82
|
-
const value = str(options, key);
|
|
83
|
-
if (!value) {
|
|
84
|
-
throw new Error(`missing required --${key}`);
|
|
85
|
-
}
|
|
86
|
-
return value;
|
|
87
|
-
}
|