@doki-land/nifty 0.0.6 → 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 +3 -0
- package/package.json +4 -4
- package/src/cli/change-logs-cmd.ts +8 -2
- package/src/cli/commit-cmd.ts +104 -6
- package/src/cli/commit-lint-report.ts +110 -0
- package/src/cli/format.ts +1 -1
- package/src/cli/lint.ts +23 -18
- package/src/config/authorMap.ts +26 -0
- package/src/config/formatPresets.ts +16 -7
- package/src/config/index.ts +6 -1
- package/src/config/loadConfig.ts +1 -8
- package/src/config/types.ts +19 -9
- package/src/index.ts +4 -1
- package/src/native.ts +10 -3
- package/src/nifty.ts +3 -2
package/locales/en-US/cli.json
CHANGED
|
@@ -14,6 +14,9 @@
|
|
|
14
14
|
"cli.cmd.commit.apply": "Apply commit message edits from a JSON map and update the branch ref",
|
|
15
15
|
"cli.cmd.commit.scan": "Scan commit messages for gitmoji and hygiene issues",
|
|
16
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",
|
|
17
20
|
"cli.cmd.retime": "Spread commit timestamps across a date range on a new branch",
|
|
18
21
|
"cli.cmd.retime.root": "Retime all commits from repository root through tip",
|
|
19
22
|
"cli.cmd.change-logs": "Draft release reference changelogs from git tags",
|
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/commit-cmd.ts
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { readFile, writeFile } from "node:fs/promises";
|
|
2
|
+
|
|
1
3
|
import type { Cli, ParsedOptions } from "@vmz/commander";
|
|
2
4
|
|
|
3
5
|
import { loadNiftyNative } from "../native.js";
|
|
@@ -13,6 +15,8 @@ export function registerCommitCommand(cli: Cli): void {
|
|
|
13
15
|
.option("--base <ref>", "cli.opt.base")
|
|
14
16
|
.option("--ref <ref>", "cli.opt.ref")
|
|
15
17
|
.option("--path <file>", "cli.opt.path")
|
|
18
|
+
.option("--with-lint", "cli.opt.with-lint")
|
|
19
|
+
.option("--errors-only", "cli.opt.errors-only")
|
|
16
20
|
.action(async (options) => cmdCommitExport(options));
|
|
17
21
|
|
|
18
22
|
commit
|
|
@@ -28,6 +32,8 @@ export function registerCommitCommand(cli: Cli): void {
|
|
|
28
32
|
.option("--from <ref>", "cli.opt.from")
|
|
29
33
|
.option("--to <ref>", "cli.opt.to")
|
|
30
34
|
.option("-s, --subject <text>...", "cli.opt.subject")
|
|
35
|
+
.option("--json", "cli.opt.json")
|
|
36
|
+
.option("--errors-only", "cli.opt.errors-only")
|
|
31
37
|
.action(async (options) => cmdCommitScan(options));
|
|
32
38
|
|
|
33
39
|
commit
|
|
@@ -35,6 +41,8 @@ export function registerCommitCommand(cli: Cli): void {
|
|
|
35
41
|
.option("--from <ref>", "cli.opt.from")
|
|
36
42
|
.option("--to <ref>", "cli.opt.to")
|
|
37
43
|
.option("-s, --subject <text>...", "cli.opt.subject")
|
|
44
|
+
.option("--json", "cli.opt.json")
|
|
45
|
+
.option("--errors-only", "cli.opt.errors-only")
|
|
38
46
|
.action(async (options) => cmdCommitAudit(options));
|
|
39
47
|
}
|
|
40
48
|
|
|
@@ -50,6 +58,22 @@ export async function cmdCommitExport(options: ParsedOptions): Promise<number> {
|
|
|
50
58
|
path,
|
|
51
59
|
});
|
|
52
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
|
+
|
|
53
77
|
return 0;
|
|
54
78
|
}
|
|
55
79
|
|
|
@@ -95,17 +119,14 @@ export async function cmdCommitApply(options: ParsedOptions): Promise<number> {
|
|
|
95
119
|
|
|
96
120
|
export async function cmdCommitScan(options: ParsedOptions): Promise<number> {
|
|
97
121
|
await bootstrapFromOptions(options);
|
|
98
|
-
await lintCommits(commitLintOptions(options), false, true);
|
|
122
|
+
await lintCommits({ ...commitLintOptions(options), reportTitle: "commit scan" }, false, true);
|
|
99
123
|
return 0;
|
|
100
124
|
}
|
|
101
125
|
|
|
102
126
|
export async function cmdCommitAudit(options: ParsedOptions): Promise<number> {
|
|
103
127
|
await bootstrapFromOptions(options);
|
|
104
|
-
const errorCount = await lintCommits(commitLintOptions(options),
|
|
105
|
-
|
|
106
|
-
throw new Error(`found ${errorCount} commit audit error(s)`);
|
|
107
|
-
}
|
|
108
|
-
return 0;
|
|
128
|
+
const errorCount = await lintCommits(commitLintOptions(options), false, true);
|
|
129
|
+
return errorCount > 0 ? 1 : 0;
|
|
109
130
|
}
|
|
110
131
|
|
|
111
132
|
function commitLintOptions(options: ParsedOptions): LintOptions {
|
|
@@ -116,9 +137,86 @@ function commitLintOptions(options: ParsedOptions): LintOptions {
|
|
|
116
137
|
cwd: cwdFrom(options),
|
|
117
138
|
scanCargo: false,
|
|
118
139
|
commitOnly: true,
|
|
140
|
+
json: flag(options, "json"),
|
|
141
|
+
errorsOnly: flag(options, "errors-only"),
|
|
142
|
+
reportTitle: "commit audit",
|
|
119
143
|
};
|
|
120
144
|
}
|
|
121
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
|
+
|
|
122
220
|
function requireStr(options: ParsedOptions, key: string): string {
|
|
123
221
|
const value = str(options, key);
|
|
124
222
|
if (!value) {
|
|
@@ -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
|
@@ -18,7 +18,7 @@ export async function formatWorkspace(options: FormatOptions): Promise<number> {
|
|
|
18
18
|
excludes: resolved.excludes,
|
|
19
19
|
rust: resolved.rust,
|
|
20
20
|
javascript: resolved.javascript,
|
|
21
|
-
|
|
21
|
+
style: resolved.style,
|
|
22
22
|
});
|
|
23
23
|
|
|
24
24
|
if (report.errors.length > 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[];
|
|
@@ -8,17 +9,12 @@ export type LintOptions = {
|
|
|
8
9
|
cwd?: string;
|
|
9
10
|
scanCargo?: boolean;
|
|
10
11
|
commitOnly?: boolean;
|
|
12
|
+
json?: boolean;
|
|
13
|
+
errorsOnly?: boolean;
|
|
14
|
+
reportTitle?: string;
|
|
11
15
|
};
|
|
12
16
|
|
|
13
|
-
type LintDiagnostic =
|
|
14
|
-
rule: string;
|
|
15
|
-
severity: "error" | "warning" | "info";
|
|
16
|
-
message: string;
|
|
17
|
-
subject?: string;
|
|
18
|
-
hash?: string;
|
|
19
|
-
path?: string;
|
|
20
|
-
line?: number;
|
|
21
|
-
};
|
|
17
|
+
type LintDiagnostic = CommitLintDiagnostic;
|
|
22
18
|
|
|
23
19
|
type LintRuleConfig = {
|
|
24
20
|
id: string;
|
|
@@ -59,23 +55,32 @@ export async function lintCommits(
|
|
|
59
55
|
commitOnly: commitOnly || options.commitOnly,
|
|
60
56
|
});
|
|
61
57
|
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
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
|
+
});
|
|
68
64
|
} else {
|
|
69
|
-
|
|
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
|
+
}
|
|
70
74
|
}
|
|
71
75
|
|
|
72
76
|
return report.errorCount;
|
|
73
77
|
}
|
|
74
78
|
|
|
75
|
-
function
|
|
79
|
+
function printWorkspaceDiagnostic(item: LintDiagnostic): void {
|
|
76
80
|
const prefix = item.severity === "error" ? "error" : item.severity === "warning" ? "warning" : "info";
|
|
77
81
|
if (item.subject) {
|
|
78
|
-
|
|
82
|
+
const hash = item.hash ? `${item.hash.slice(0, 8)} ` : "";
|
|
83
|
+
console.log(`${prefix} [${item.rule}] ${hash}${item.message} (${item.subject})`);
|
|
79
84
|
return;
|
|
80
85
|
}
|
|
81
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
|
+
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { NiftyFormatConfig, NiftyFormatPreset } from "./types.js";
|
|
1
|
+
import type { NiftyFormatConfig, NiftyFormatPreset, NiftyFormatStyleConfig } from "./types.js";
|
|
2
2
|
|
|
3
3
|
export type ResolvedFormatOptions = {
|
|
4
4
|
preset: NiftyFormatPreset;
|
|
@@ -6,16 +6,22 @@ export type ResolvedFormatOptions = {
|
|
|
6
6
|
excludes?: string[];
|
|
7
7
|
rust?: boolean;
|
|
8
8
|
javascript?: boolean;
|
|
9
|
-
style
|
|
9
|
+
style: NiftyFormatStyleConfig;
|
|
10
10
|
};
|
|
11
11
|
|
|
12
|
-
const
|
|
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 = [
|
|
13
20
|
"scripts/**",
|
|
14
21
|
"projects/packages/**",
|
|
15
22
|
"projects/conformance/**",
|
|
16
23
|
"projects/dashboard/**",
|
|
17
24
|
"package.json",
|
|
18
|
-
"biome.json",
|
|
19
25
|
"nifty.config.ts",
|
|
20
26
|
] as const;
|
|
21
27
|
|
|
@@ -30,13 +36,13 @@ const VMZ_INCLUDES = [
|
|
|
30
36
|
"packages/content/**",
|
|
31
37
|
"packages/homepage/**",
|
|
32
38
|
"package.json",
|
|
33
|
-
"biome.json",
|
|
34
39
|
"nifty.config.ts",
|
|
35
40
|
] as const;
|
|
36
41
|
|
|
37
42
|
const PRESET_INCLUDES: Record<NiftyFormatPreset, readonly string[] | undefined> = {
|
|
38
43
|
default: undefined,
|
|
39
|
-
|
|
44
|
+
nifty: NIFTY_PRESET_INCLUDES,
|
|
45
|
+
"npm-tools": NIFTY_PRESET_INCLUDES,
|
|
40
46
|
vmz: VMZ_INCLUDES,
|
|
41
47
|
};
|
|
42
48
|
|
|
@@ -50,6 +56,9 @@ export function resolveFormatConfig(format: NiftyFormatConfig | undefined): Reso
|
|
|
50
56
|
excludes: format?.excludes,
|
|
51
57
|
rust: format?.rust,
|
|
52
58
|
javascript: format?.javascript,
|
|
53
|
-
style:
|
|
59
|
+
style: {
|
|
60
|
+
...DEFAULT_FORMAT_STYLE,
|
|
61
|
+
...format?.style,
|
|
62
|
+
},
|
|
54
63
|
};
|
|
55
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,
|
|
@@ -25,4 +29,5 @@ export {
|
|
|
25
29
|
type NiftyConfigExport,
|
|
26
30
|
type NiftyFormatConfig,
|
|
27
31
|
type NiftyFormatPreset,
|
|
32
|
+
type NiftyFormatStyleConfig,
|
|
28
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, format presets, authorMap, and lint rules.
|
|
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,27 @@
|
|
|
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
7
|
/** Built-in workspace format presets (`nifty.config` `format.preset`). */
|
|
8
|
-
export type NiftyFormatPreset = "default" | "npm-tools" | "vmz";
|
|
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
|
+
};
|
|
9
21
|
|
|
10
22
|
/** Workspace format options (`nifty format` reads `nifty.config` `format`). */
|
|
11
23
|
export type NiftyFormatConfig = {
|
|
12
|
-
/** Select built-in include roots
|
|
24
|
+
/** Select built-in include roots for Nifty hybrid monorepos. */
|
|
13
25
|
preset?: NiftyFormatPreset;
|
|
14
26
|
/** Extra include globs (override preset list when set alone). */
|
|
15
27
|
includes?: string[];
|
|
@@ -19,16 +31,16 @@ export type NiftyFormatConfig = {
|
|
|
19
31
|
rust?: boolean;
|
|
20
32
|
/** Run `oxc_formatter` on JS/TS targets (default: true). */
|
|
21
33
|
javascript?: boolean;
|
|
22
|
-
/**
|
|
23
|
-
style?:
|
|
34
|
+
/** Inline oxc formatter style (not an external config file path). */
|
|
35
|
+
style?: NiftyFormatStyleConfig;
|
|
24
36
|
};
|
|
25
37
|
|
|
26
38
|
/** Nifty project configuration (`nifty.config.ts` / `nifty.config.js`). */
|
|
27
39
|
export type NiftyConfig = {
|
|
28
40
|
/** GitHub personal access token for email search / profile fetch. */
|
|
29
41
|
githubToken?: string;
|
|
30
|
-
/**
|
|
31
|
-
authorMap?:
|
|
42
|
+
/** Path to `author-github.json` (default: `documentation/maintenance/author-github.json`). */
|
|
43
|
+
authorMap?: string;
|
|
32
44
|
/** Repository root override when discovering git metadata. */
|
|
33
45
|
repoRoot?: string;
|
|
34
46
|
/** Cargo workspace / crate root override. */
|
|
@@ -44,8 +56,6 @@ export type NiftyConfig = {
|
|
|
44
56
|
changelog?: {
|
|
45
57
|
/** GitHub `owner/repo` for contrib.rocks (default: parse `origin`). */
|
|
46
58
|
repo?: string;
|
|
47
|
-
/** Email → GitHub map path (default: `documentation/maintenance/author-github.json`). */
|
|
48
|
-
authorMap?: string;
|
|
49
59
|
/** `--write` output directory (default: `documentation/maintenance/releases`). */
|
|
50
60
|
releasesDir?: string;
|
|
51
61
|
};
|
package/src/index.ts
CHANGED
|
@@ -21,7 +21,9 @@ 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,
|
|
@@ -34,6 +36,7 @@ export {
|
|
|
34
36
|
type NiftyConfigEnv,
|
|
35
37
|
type NiftyFormatConfig,
|
|
36
38
|
type NiftyFormatPreset,
|
|
39
|
+
type NiftyFormatStyleConfig,
|
|
37
40
|
type ProjectKind,
|
|
38
41
|
type ProjectLayout,
|
|
39
42
|
type ResolvedFormatOptions,
|
package/src/native.ts
CHANGED
|
@@ -199,6 +199,13 @@ export type FormatReportRecord = {
|
|
|
199
199
|
errors: string[];
|
|
200
200
|
};
|
|
201
201
|
|
|
202
|
+
export type FormatterStyleOptions = {
|
|
203
|
+
indentStyle?: string;
|
|
204
|
+
indentWidth?: number;
|
|
205
|
+
lineWidth?: number;
|
|
206
|
+
quoteStyle?: string;
|
|
207
|
+
};
|
|
208
|
+
|
|
202
209
|
export type FormatterRunOptions = {
|
|
203
210
|
cwd?: string;
|
|
204
211
|
check?: boolean;
|
|
@@ -206,7 +213,7 @@ export type FormatterRunOptions = {
|
|
|
206
213
|
excludes?: string[];
|
|
207
214
|
rust?: boolean;
|
|
208
215
|
javascript?: boolean;
|
|
209
|
-
|
|
216
|
+
style?: FormatterStyleOptions;
|
|
210
217
|
};
|
|
211
218
|
|
|
212
219
|
export type FormatterExports = {
|
|
@@ -451,7 +458,7 @@ type NativeBinding = {
|
|
|
451
458
|
excludes?: string[] | null;
|
|
452
459
|
rust?: boolean | null;
|
|
453
460
|
javascript?: boolean | null;
|
|
454
|
-
|
|
461
|
+
style?: FormatterStyleOptions | null;
|
|
455
462
|
}) => {
|
|
456
463
|
formatted: number;
|
|
457
464
|
unchanged: number;
|
|
@@ -679,7 +686,7 @@ function wrapBinding(binding: NativeBinding): NiftyNative {
|
|
|
679
686
|
...(options.excludes !== undefined ? { excludes: options.excludes } : {}),
|
|
680
687
|
...(options.rust !== undefined ? { rust: options.rust } : {}),
|
|
681
688
|
...(options.javascript !== undefined ? { javascript: options.javascript } : {}),
|
|
682
|
-
...(options.
|
|
689
|
+
...(options.style !== undefined ? { style: options.style } : {}),
|
|
683
690
|
}),
|
|
684
691
|
},
|
|
685
692
|
publisher: {
|
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,
|