@doki-land/nifty 0.1.2 → 0.1.3

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/cli/nifty.mjs CHANGED
@@ -1,4 +1,4 @@
1
- #!/usr/bin /env node
1
+ #!/usr/bin/env node
2
2
 
3
3
  import { createJiti } from "jiti";
4
4
  import { dirname, join } from "node:path";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@doki-land/nifty",
3
- "version": "0.1.2",
3
+ "version": "0.1.3",
4
4
  "description": "Gitmoji-first commit conventions and release helpers (Node-API + TypeScript)",
5
5
  "type": "module",
6
6
  "license": "MPL-2.0",
@@ -17,18 +17,19 @@
17
17
  "src"
18
18
  ],
19
19
  "scripts": {
20
- "typecheck": "tsc --noEmit"
20
+ "typecheck": "tsc --noEmit",
21
+ "test": "node --test tests/*.test.mjs"
21
22
  },
22
23
  "dependencies": {
23
24
  "@vmz/commander": "^0.2.0",
24
25
  "jiti": "^2.4.2"
25
26
  },
26
27
  "optionalDependencies": {
27
- "@doki-land/nifty-win32-x64": "0.1.2",
28
- "@doki-land/nifty-linux-x64": "0.1.2",
29
- "@doki-land/nifty-linux-arm64": "0.1.2",
30
- "@doki-land/nifty-darwin-arm64": "0.1.2",
31
- "@doki-land/nifty-darwin-x64": "0.1.2"
28
+ "@doki-land/nifty-win32-x64": "0.1.3",
29
+ "@doki-land/nifty-linux-x64": "0.1.3",
30
+ "@doki-land/nifty-linux-arm64": "0.1.3",
31
+ "@doki-land/nifty-darwin-arm64": "0.1.3",
32
+ "@doki-land/nifty-darwin-x64": "0.1.3"
32
33
  },
33
34
  "publishConfig": {
34
35
  "access": "public"
@@ -44,9 +44,9 @@ export function authPayload(options: AuthCliOptions): {
44
44
  npm: string;
45
45
  } {
46
46
  return {
47
- ...(options.otp !== undefined ? { otp: options.otp } : { }),
48
- ...(options.totpSecret !== undefined ? { totpSecret: options.totpSecret } : { }),
49
- ...(options.token !== undefined ? { token: options.token } : { }),
47
+ ...(options.otp !== undefined ? { otp: options.otp } : {}),
48
+ ...(options.totpSecret !== undefined ? { totpSecret: options.totpSecret } : {}),
49
+ ...(options.token !== undefined ? { token: options.token } : {}),
50
50
  npm: options.npm,
51
51
  };
52
52
  }
@@ -11,10 +11,10 @@ export function registerBumpCommand(cli: Cli): void {
11
11
  .option("--patch", "cli.opt.patch")
12
12
  .option("--minor", "cli.opt.minor")
13
13
  .option("--major", "cli.opt.major")
14
- .action(async(options) => cmdBump(options));
14
+ .action(async (options) => cmdBump(options));
15
15
  }
16
16
 
17
- export async function cmdBump(options: ParsedOptions): Promise < number> {
17
+ export async function cmdBump(options: ParsedOptions): Promise<number> {
18
18
  await bootstrapFromOptions(options);
19
19
  const bumpOptions = bumpOptionsFromParsed(options);
20
20
  const report = bumpWorkspace(bumpOptions);
package/src/cli/bump.ts CHANGED
@@ -24,7 +24,7 @@ export type BumpReport = {
24
24
 
25
25
  const DEFAULT_KIND: BumpKind = "patch";
26
26
 
27
- export async function runBump(argv: string[]): Promise < void> {
27
+ export async function runBump(argv: string[]): Promise<void> {
28
28
  const options = parseBumpArgs(argv);
29
29
  const report = bumpWorkspace(options);
30
30
  printBumpReport(report, options.dryRun);
@@ -85,27 +85,36 @@ export function bumpWorkspace(options: BumpOptions): BumpReport {
85
85
 
86
86
  const cargoPaths = collectCargoManifests(cratesDir);
87
87
  const npmPaths = collectPackageManifests(packagesDir);
88
- const from = readWorkspaceVersion(cargoPaths, npmPaths);
88
+ const from = readWorkspaceVersion(root, cargoPaths, npmPaths);
89
89
  const to = options.version ?? bumpSemver(from, options.kind ?? DEFAULT_KIND);
90
90
  assertVersion(to);
91
91
 
92
- const cargo = cargoPaths.map((path) => bumpCargoManifest(path, to, options.dryRun));
93
- const npm = npmPaths.map((path) => bumpPackageManifest(path, to, options.dryRun));
92
+ const cargoUpdates = prepareCargoUpdates(root, cargoPaths, to);
93
+ const npmUpdates = npmPaths.map((path) => preparePackageUpdate(path, to));
94
+ if (!options.dryRun) {
95
+ cargoUpdates.concat(npmUpdates).forEach((update) => writeFileSync(update.path, update.next, "utf8"));
96
+ }
94
97
 
95
98
  return {
96
99
  root,
97
100
  from,
98
101
  to,
99
102
  kind: options.version ? undefined : options.kind ?? DEFAULT_KIND,
100
- cargo,
101
- npm,
103
+ cargo: cargoUpdates.map((update) => update.path),
104
+ npm: npmUpdates.map((update) => update.path),
102
105
  };
103
106
  }
104
107
 
105
- function readWorkspaceVersion(cargoPaths: string[], npmPaths: string[]): string {
106
- const versions = new Set < string> ();
108
+ function readWorkspaceVersion(root: string, cargoPaths: string[], npmPaths: string[]): string {
109
+ const versions = new Set<string>();
110
+ let usesWorkspaceVersion = false;
107
111
  for (const path of cargoPaths) {
108
- versions.add(readCargoVersion(path));
112
+ const info = readCargoVersion(path);
113
+ if (info.inherited) usesWorkspaceVersion = true;
114
+ else versions.add(info.version);
115
+ }
116
+ if (usesWorkspaceVersion) {
117
+ versions.add(readWorkspacePackageVersion(join(root, "Cargo.toml")).version);
109
118
  }
110
119
  for (const path of npmPaths) {
111
120
  versions.add(readPackageVersion(path));
@@ -116,20 +125,25 @@ function readWorkspaceVersion(cargoPaths: string[], npmPaths: string[]): string
116
125
  if (versions.size > 1) {
117
126
  throw new Error(`workspace versions are out of sync: ${[...versions].sort().join(", ")}`);
118
127
  }
119
- return[...versions][0];
128
+ return [...versions][0];
120
129
  }
121
130
 
122
- function readCargoVersion(path: string): string {
131
+ type CargoVersion = { version: string; inherited: boolean };
132
+
133
+ type FileUpdate = { path: string; next: string };
134
+
135
+ function readCargoVersion(path: string): CargoVersion {
123
136
  const original = readFileSync(path, "utf8");
124
- const match = original.match(/^version\s*=\s*"([^"]*)"/m);
125
- if (!match) {
126
- throw new Error(`no [package].version found in ${path}`);
137
+ const field = findTomlField(original, "package", "version.workspace") ?? findTomlField(original, "package", "version");
138
+ if (!field) throw new Error(`no [package].version found in ${path}`);
139
+ if (isWorkspaceInheritedVersion(field)) {
140
+ return { version: "", inherited: true };
127
141
  }
128
- return match[1];
142
+ return { version: parseTomlString(field.value, `[package].version in ${path}`), inherited: false };
129
143
  }
130
144
 
131
145
  function readPackageVersion(path: string): string {
132
- const parsed = JSON.parse(readFileSync(path, "utf8"))as { version?: string };
146
+ const parsed = JSON.parse(readFileSync(path, "utf8")) as { version?: string };
133
147
  if (!parsed.version) {
134
148
  throw new Error(`no version field in ${path}`);
135
149
  }
@@ -165,7 +179,7 @@ export function bumpSemver(version: string, kind: BumpKind): string {
165
179
 
166
180
  function collectCargoManifests(cratesDir: string): string[] {
167
181
  if (!existsSync(cratesDir)) {
168
- return[];
182
+ return [];
169
183
  }
170
184
  return readdirSync(cratesDir, { withFileTypes: true })
171
185
  .filter((entry) => entry.isDirectory())
@@ -175,7 +189,7 @@ function collectCargoManifests(cratesDir: string): string[] {
175
189
 
176
190
  function collectPackageManifests(packagesDir: string): string[] {
177
191
  if (!existsSync(packagesDir)) {
178
- return[];
192
+ return [];
179
193
  }
180
194
  return readdirSync(packagesDir, { withFileTypes: true })
181
195
  .filter((entry) => entry.isDirectory() && entry.name !== "node_modules")
@@ -183,33 +197,106 @@ function collectPackageManifests(packagesDir: string): string[] {
183
197
  .filter((path) => existsSync(path));
184
198
  }
185
199
 
186
- function bumpCargoManifest(path: string, version: string, dryRun?: boolean) : string {
187
- const original = readFileSync(path, "utf8");
188
- const next = original.replace(/^version\s*=\s*"[^"]*"/m, `version = "${version}"`);
189
- if (next === original) {
190
- throw new Error(`no [package].version found in ${path}`);
200
+ function prepareCargoUpdates(root: string, cargoPaths: string[], version: string): FileUpdate[] {
201
+ const updates: FileUpdate[] = [];
202
+ let usesWorkspaceVersion = false;
203
+ for (const path of cargoPaths) {
204
+ const original = readFileSync(path, "utf8");
205
+ const field = findTomlField(original, "package", "version.workspace") ?? findTomlField(original, "package", "version");
206
+ if (!field) throw new Error(`no [package].version found in ${path}`);
207
+ if (isWorkspaceInheritedVersion(field)) {
208
+ usesWorkspaceVersion = true;
209
+ continue;
210
+ }
211
+ parseTomlString(field.value, `[package].version in ${path}`);
212
+ updates.push({ path, next: replaceTomlField(original, field, version) });
191
213
  }
192
- if (!dryRun) {
193
- writeFileSync(path, next, "utf8");
214
+ if (usesWorkspaceVersion) {
215
+ const path = join(root, "Cargo.toml");
216
+ const original = readFileSync(path, "utf8");
217
+ const field = findTomlField(original, "workspace.package", "version");
218
+ if (!field) {
219
+ throw new Error(`Cargo members inherit version.workspace but ${path} has no [workspace.package].version`);
220
+ }
221
+ parseTomlString(field.value, `[workspace.package].version in ${path}`);
222
+ updates.push({ path, next: replaceTomlField(original, field, version) });
194
223
  }
195
- return path;
224
+ return updates;
196
225
  }
197
226
 
198
- function bumpPackageManifest(path: string, version: string, dryRun?: boolean) : string {
227
+ function readWorkspacePackageVersion(path: string): CargoVersion {
199
228
  const original = readFileSync(path, "utf8");
200
- const parsed = JSON.parse(original)as { version?: string };
229
+ const field = findTomlField(original, "workspace.package", "version");
230
+ if (!field) throw new Error(`Cargo members inherit version.workspace but ${path} has no [workspace.package].version`);
231
+ return { version: parseTomlString(field.value, `[workspace.package].version in ${path}`), inherited: false };
232
+ }
233
+
234
+ function isWorkspaceInheritedVersion(field: NonNullable<ReturnType<typeof findTomlField>>): boolean {
235
+ if (field.key === "version.workspace") return true;
236
+ const value = field.value.trim();
237
+ if (!value.startsWith("{") || !value.endsWith("}")) return false;
238
+ return value
239
+ .slice(1, -1)
240
+ .split(",")
241
+ .some((assignment) => {
242
+ const equals = assignment.indexOf("=");
243
+ return equals >= 0 && assignment.slice(0, equals).trim() === "workspace" && assignment.slice(equals + 1).trim() === "true";
244
+ });
245
+ }
246
+
247
+ function findTomlField(contents: string, table: string, name: string) {
248
+ let currentTable = "";
249
+ const lines = contents.split(/(?<=\n)/);
250
+ for (let index = 0; index < lines.length; index += 1) {
251
+ const line = lines[index];
252
+ const trimmed = line.trim();
253
+ const tableMatch = trimmed.match(/^\[([^\]]+)\]$/);
254
+ if (tableMatch) {
255
+ currentTable = tableMatch[1].trim();
256
+ continue;
257
+ }
258
+ if (trimmed.startsWith("[[")) {
259
+ currentTable = "";
260
+ continue;
261
+ }
262
+ if (currentTable !== table) continue;
263
+ const fieldMatch = line.match(new RegExp(`^(\\s*)(${name.replace(".", "\\.")})\\s*=\\s*(.*?)(\\r?\\n?)$`));
264
+ if (fieldMatch) return { index, line, indent: fieldMatch[1], key: fieldMatch[2], value: fieldMatch[3], ending: fieldMatch[4] };
265
+ }
266
+ return undefined;
267
+ }
268
+
269
+ function parseTomlString(value: string, description: string): string {
270
+ const trimmed = value.trim();
271
+ const quote = trimmed[0];
272
+ if (quote !== '"' && quote !== "'") throw new Error(`expected a quoted string for ${description}`);
273
+ const closingQuote = trimmed.indexOf(quote, 1);
274
+ if (closingQuote < 0) throw new Error(`expected a quoted string for ${description}`);
275
+ const trailing = trimmed.slice(closingQuote + 1).trim();
276
+ if (trailing && !trailing.startsWith("#")) throw new Error(`expected a quoted string for ${description}`);
277
+ return trimmed.slice(1, closingQuote);
278
+ }
279
+
280
+ function replaceTomlField(contents: string, field: NonNullable<ReturnType<typeof findTomlField>>, version: string): string {
281
+ const value = field.value.trimStart();
282
+ const closingQuote = value.indexOf(value[0], 1);
283
+ const suffix = closingQuote >= 0 ? value.slice(closingQuote + 1) : "";
284
+ const line = `${field.indent}${field.key} = "${version}"${suffix}${field.ending}`;
285
+ return contents.split(/(?<=\n)/).map((current, index) => index === field.index ? line : current).join("");
286
+ }
287
+
288
+ function preparePackageUpdate(path: string, version: string): FileUpdate {
289
+ const original = readFileSync(path, "utf8");
290
+ const parsed = JSON.parse(original) as { version?: string };
201
291
  if (!parsed.version) {
202
292
  throw new Error(`no version field in ${path}`);
203
293
  }
204
294
  parsed.version = version;
205
295
  const next = `${JSON.stringify(parsed, null, 4)}\n`;
206
- if (!dryRun) {
207
- writeFileSync(path, next, "utf8");
208
- }
209
- return path;
296
+ return { path, next };
210
297
  }
211
298
 
212
- export function printBumpReport(report: BumpReport, dryRun?: boolean) : void {
299
+ export function printBumpReport(report: BumpReport, dryRun?: boolean): void {
213
300
  const prefix = dryRun ? "would bump" : "bumped";
214
301
  const kind = report.kind ? ` (${report.kind})` : "";
215
302
  console.log(`${prefix} workspace ${report.root} ${report.from} -> ${report.to}${kind}`);
@@ -233,5 +320,7 @@ Usage:
233
320
  nifty bump --version 1.2.3
234
321
  nifty bump --dry-run
235
322
  nifty bump -C <cwd>
323
+
324
+ Cargo members using version.workspace = true are updated through the root [workspace.package].version.
236
325
  `);
237
326
  }
@@ -27,12 +27,12 @@ export function registerChangeLogsCommand(cli: Cli): void {
27
27
  .option("--map <path>", "cli.opt.author-map")
28
28
  .option("--github-token <token>", "cli.opt.github-token")
29
29
  .option("--fetch", "cli.opt.fetch")
30
- .action(async(options) => cmdChangeLogsLookup(options));
30
+ .action(async (options) => cmdChangeLogsLookup(options));
31
31
 
32
- changeLogs.action(async(options) => cmdChangeLogsRender(options));
32
+ changeLogs.action(async (options) => cmdChangeLogsRender(options));
33
33
  }
34
34
 
35
- export async function cmdChangeLogsRender(options: ParsedOptions): Promise < number> {
35
+ export async function cmdChangeLogsRender(options: ParsedOptions): Promise<number> {
36
36
  await bootstrapFromOptions(options);
37
37
  const cwd = cwdFrom(options) ?? process.cwd();
38
38
  const { config } = await loadConfig({ cwd, createIfMissing: false });
@@ -66,7 +66,7 @@ export async function cmdChangeLogsRender(options: ParsedOptions): Promise < num
66
66
  return 0;
67
67
  }
68
68
 
69
- export async function cmdChangeLogsLookup(options: ParsedOptions): Promise < number> {
69
+ export async function cmdChangeLogsLookup(options: ParsedOptions): Promise<number> {
70
70
  await bootstrapFromOptions(options);
71
71
  const cwd = cwdFrom(options) ?? process.cwd();
72
72
  const { config } = await loadConfig({ cwd, createIfMissing: false });
@@ -86,7 +86,7 @@ export async function cmdChangeLogsLookup(options: ParsedOptions): Promise < num
86
86
  githubToken: str(options, "github-token") ?? config.githubToken ?? process.env.GITHUB_TOKEN,
87
87
  fetch: flag(options, "fetch"),
88
88
  });
89
- const json: Record < string, unknown > = { };
89
+ const json: Record<string, unknown> = {};
90
90
  if (author.id !== undefined) {
91
91
  json.id = author.id;
92
92
  }
package/src/cli/cli.ts CHANGED
@@ -37,7 +37,7 @@ function buildNiftyCli() {
37
37
  return cli;
38
38
  }
39
39
 
40
- export async function runCli(argv: string[]): Promise < number> {
40
+ export async function runCli(argv: string[]): Promise<number> {
41
41
  try {
42
42
  return await buildNiftyCli().parse(argv);
43
43
  } catch (error) {
@@ -17,7 +17,7 @@ export function registerCommitCommand(cli: Cli): void {
17
17
  .option("--path <file>", "cli.opt.path")
18
18
  .option("--with-lint", "cli.opt.with-lint")
19
19
  .option("--errors-only", "cli.opt.errors-only")
20
- .action(async(options) => cmdCommitExport(options));
20
+ .action(async (options) => cmdCommitExport(options));
21
21
 
22
22
  commit
23
23
  .command("apply", "cli.cmd.commit.apply")
@@ -25,7 +25,7 @@ export function registerCommitCommand(cli: Cli): void {
25
25
  .option("--ref <ref>", "cli.opt.ref")
26
26
  .option("--path <file>", "cli.opt.path")
27
27
  .option("--dry-run", "cli.opt.dry-run")
28
- .action(async(options) => cmdCommitApply(options));
28
+ .action(async (options) => cmdCommitApply(options));
29
29
 
30
30
  commit
31
31
  .command("scan", "cli.cmd.commit.scan")
@@ -34,7 +34,7 @@ export function registerCommitCommand(cli: Cli): void {
34
34
  .option("-s, --subject <text>...", "cli.opt.subject")
35
35
  .option("--json", "cli.opt.json")
36
36
  .option("--errors-only", "cli.opt.errors-only")
37
- .action(async(options) => cmdCommitScan(options));
37
+ .action(async (options) => cmdCommitScan(options));
38
38
 
39
39
  commit
40
40
  .command("audit", "cli.cmd.commit.audit")
@@ -43,10 +43,10 @@ export function registerCommitCommand(cli: Cli): void {
43
43
  .option("-s, --subject <text>...", "cli.opt.subject")
44
44
  .option("--json", "cli.opt.json")
45
45
  .option("--errors-only", "cli.opt.errors-only")
46
- .action(async(options) => cmdCommitAudit(options));
46
+ .action(async (options) => cmdCommitAudit(options));
47
47
  }
48
48
 
49
- export async function cmdCommitExport(options: ParsedOptions): Promise < number> {
49
+ export async function cmdCommitExport(options: ParsedOptions): Promise<number> {
50
50
  await bootstrapFromOptions(options);
51
51
  const base = requireStr(options, "base");
52
52
  const path = str(options, "path") ?? "commit.pending.json";
@@ -77,7 +77,7 @@ export async function cmdCommitExport(options: ParsedOptions): Promise < number>
77
77
  return 0;
78
78
  }
79
79
 
80
- export async function cmdCommitApply(options: ParsedOptions): Promise < number> {
80
+ export async function cmdCommitApply(options: ParsedOptions): Promise<number> {
81
81
  await bootstrapFromOptions(options);
82
82
  const base = requireStr(options, "base");
83
83
  const path = requireStr(options, "path");
@@ -117,13 +117,13 @@ export async function cmdCommitApply(options: ParsedOptions): Promise < number>
117
117
  return 0;
118
118
  }
119
119
 
120
- export async function cmdCommitScan(options: ParsedOptions): Promise < number> {
120
+ export async function cmdCommitScan(options: ParsedOptions): Promise<number> {
121
121
  await bootstrapFromOptions(options);
122
122
  await lintCommits({ ...commitLintOptions(options), reportTitle: "commit scan" }, false, true);
123
123
  return 0;
124
124
  }
125
125
 
126
- export async function cmdCommitAudit(options: ParsedOptions): Promise < number> {
126
+ export async function cmdCommitAudit(options: ParsedOptions): Promise<number> {
127
127
  await bootstrapFromOptions(options);
128
128
  const errorCount = await lintCommits(commitLintOptions(options), false, true);
129
129
  return errorCount > 0 ? 1 : 0;
@@ -145,10 +145,10 @@ function commitLintOptions(options: ParsedOptions): LintOptions {
145
145
 
146
146
  type ExportDocument = {
147
147
  version: number;
148
- entries: Array < {
148
+ entries: Array<{
149
149
  hash: string;
150
150
  message: string;
151
- violations?: Array < {
151
+ violations?: Array<{
152
152
  rule: string;
153
153
  severity: "error" | "warning" | "info";
154
154
  message: string;
@@ -164,7 +164,7 @@ async function lintAndAnnotateExport(options: {
164
164
  json: boolean;
165
165
  errorsOnly: boolean;
166
166
  reportTitle: string;
167
- }): Promise < number> {
167
+ }): Promise<number> {
168
168
  const native = loadNiftyNative();
169
169
  const report = native.lint.run({
170
170
  cwd: options.cwd,
@@ -173,7 +173,9 @@ async function lintAndAnnotateExport(options: {
173
173
  commitOnly: true,
174
174
  scanCargo: false,
175
175
  });
176
- const diagnostics = options.errorsOnly ? report.diagnostics.filter((item) => item.severity === "error") : report.diagnostics;
176
+ const diagnostics = options.errorsOnly
177
+ ? report.diagnostics.filter((item) => item.severity === "error")
178
+ : report.diagnostics;
177
179
 
178
180
  if (options.json) {
179
181
  const { printCommitLintReport } = await import("./commit-lint-report.js");
@@ -190,7 +192,7 @@ async function lintAndAnnotateExport(options: {
190
192
  });
191
193
  }
192
194
 
193
- const byHash = new Map < string, Array < { rule: string; severity: "error" | "warning" | "info"; message: string }>>();
195
+ const byHash = new Map<string, Array<{ rule: string; severity: "error" | "warning" | "info"; message: string }>>();
194
196
  for (const item of diagnostics) {
195
197
  if (!item.hash) {
196
198
  continue;
@@ -201,7 +203,7 @@ async function lintAndAnnotateExport(options: {
201
203
  }
202
204
 
203
205
  const text = await readFile(options.path, "utf8");
204
- const document = JSON.parse(text)as ExportDocument;
206
+ const document = JSON.parse(text) as ExportDocument;
205
207
  document.entries = document.entries.map((entry) => {
206
208
  const violations = byHash.get(entry.hash);
207
209
  if (!violations || violations.length === 0) {
@@ -24,9 +24,11 @@ export function printCommitLintReport(
24
24
  diagnostics: CommitLintDiagnostic[],
25
25
  errorCount: number,
26
26
  warningCount: number,
27
- options: CommitLintReportOptions = { },
27
+ options: CommitLintReportOptions = {},
28
28
  ): void {
29
- const filtered = options.errorsOnly ? diagnostics.filter((item) => item.severity === "error") : diagnostics;
29
+ const filtered = options.errorsOnly
30
+ ? diagnostics.filter((item) => item.severity === "error")
31
+ : diagnostics;
30
32
 
31
33
  if (options.json) {
32
34
  const payload = {
@@ -57,7 +59,7 @@ export function printCommitLintReport(
57
59
  const summary = summarizeByRule(filtered.filter((item) => item.severity === "error"));
58
60
  if (summary.length > 0) {
59
61
  console.log("summary by rule:");
60
- for (const[rule, count]of summary) {
62
+ for (const [rule, count] of summary) {
61
63
  console.log(` ${rule} ${count}`);
62
64
  }
63
65
  console.log();
@@ -65,7 +67,7 @@ export function printCommitLintReport(
65
67
  }
66
68
 
67
69
  function groupCommitDiagnostics(diagnostics: CommitLintDiagnostic[]): CommitLintGroup[] {
68
- const groups = new Map < string, CommitLintGroup > ();
70
+ const groups = new Map<string, CommitLintGroup>();
69
71
  const order: string[] = [];
70
72
 
71
73
  for (const item of diagnostics) {
@@ -84,12 +86,12 @@ function groupCommitDiagnostics(diagnostics: CommitLintDiagnostic[]): CommitLint
84
86
  return order.map((key) => groups.get(key)!);
85
87
  }
86
88
 
87
- function summarizeByRule(diagnostics: CommitLintDiagnostic[]): Array < [string, number] > {
88
- const counts = new Map < string, number > ();
89
+ function summarizeByRule(diagnostics: CommitLintDiagnostic[]): Array<[string, number]> {
90
+ const counts = new Map<string, number>();
89
91
  for (const item of diagnostics) {
90
92
  counts.set(item.rule, (counts.get(item.rule) ?? 0) + 1);
91
93
  }
92
- return[...counts.entries()].sort((left, right) => right[1] - left[1] || left[0].localeCompare(right[0]));
94
+ return [...counts.entries()].sort((left, right) => right[1] - left[1] || left[0].localeCompare(right[0]));
93
95
  }
94
96
 
95
97
  function printCommitGroup(group: CommitLintGroup): void {
@@ -23,13 +23,13 @@ export function parseCliContext(argv: string[]): { cwd: string; dryRun: boolean
23
23
  }
24
24
 
25
25
  /** Ensure `nifty.config.*` exists before running a command (skip create on `--dry-run`). */
26
- export async function bootstrapNiftyConfig(argv: string[]): Promise < void> {
26
+ export async function bootstrapNiftyConfig(argv: string[]): Promise<void> {
27
27
  const { cwd, dryRun } = parseCliContext(argv);
28
28
  await loadConfig({ cwd, createIfMissing: !dryRun });
29
29
  }
30
30
 
31
31
  /** Commander action hook: load config from parsed global/command flags. */
32
- export async function bootstrapFromOptions(options: ParsedOptions): Promise < void> {
32
+ export async function bootstrapFromOptions(options: ParsedOptions): Promise<void> {
33
33
  const cwd = cwdFrom(options) ?? process.cwd();
34
34
  const dryRun = options["dry-run"] === true;
35
35
  await loadConfig({ cwd, createIfMissing: !dryRun });
@@ -7,10 +7,10 @@ import { cwdFrom, flag } from "./options.js";
7
7
  export function registerFormatCommand(cli: Cli): void {
8
8
  cli.command("format", "cli.cmd.format")
9
9
  .option("--check", "cli.opt.check")
10
- .action(async(options) => cmdFormat(options));
10
+ .action(async (options) => cmdFormat(options));
11
11
  }
12
12
 
13
- export async function cmdFormat(options: ParsedOptions): Promise < number> {
13
+ export async function cmdFormat(options: ParsedOptions): Promise<number> {
14
14
  await bootstrapFromOptions(options);
15
15
  const exitCode = await formatWorkspace({
16
16
  check: flag(options, "check"),
package/src/cli/format.ts CHANGED
@@ -6,7 +6,7 @@ export type FormatOptions = {
6
6
  cwd?: string;
7
7
  };
8
8
 
9
- export async function formatWorkspace(options: FormatOptions): Promise < number> {
9
+ export async function formatWorkspace(options: FormatOptions): Promise<number> {
10
10
  const cwd = options.cwd ?? process.cwd();
11
11
  const { config } = await loadConfig({ cwd, createIfMissing: false, env: { command: "format" } });
12
12
  const resolved = resolveFormatConfig(config.format);
@@ -6,10 +6,10 @@ import { cwdFrom, str } from "./options.js";
6
6
  export function registerInstallNativeCommand(cli: Cli): void {
7
7
  cli.command("install-native", "cli.cmd.install-native")
8
8
  .option("--from <dir>", "cli.opt.from-artifacts")
9
- .action(async(options) => cmdInstallNative(options));
9
+ .action(async (options) => cmdInstallNative(options));
10
10
  }
11
11
 
12
- export async function cmdInstallNative(options: ParsedOptions): Promise < number> {
12
+ export async function cmdInstallNative(options: ParsedOptions): Promise<number> {
13
13
  const from =
14
14
  str(options, "from") ??
15
15
  process.env.NATIVE_ARTIFACT_ROOT ??
@@ -4,7 +4,7 @@ import { basename, dirname, join, relative, resolve } from "node:path";
4
4
  import { detectProjectLayout } from "../config/detectProject.js";
5
5
 
6
6
  /** Basename of a built .node file → platform package directory under projects/packages/. */
7
- const PACKAGE_BY_ASSET: Record < string, string > = {
7
+ const PACKAGE_BY_ASSET: Record<string, string> = {
8
8
  "nifty-win32-x64-msvc.node": "nifty-win32-x64",
9
9
  "nifty-linux-x64-gnu.node": "nifty-linux-x64",
10
10
  "nifty-linux-arm64-gnu.node": "nifty-linux-arm64",
@@ -15,10 +15,10 @@ function registerLintLike(cli: Cli, name: string, helpId: string, check: boolean
15
15
  .option("--to <ref>", "cli.opt.to")
16
16
  .option("-s, --subject <text>...", "cli.opt.subject")
17
17
  .option("--no-cargo", "cli.opt.no-cargo")
18
- .action(async(options) => cmdLint(options, check));
18
+ .action(async (options) => cmdLint(options, check));
19
19
  }
20
20
 
21
- export async function cmdLint(options: ParsedOptions, check: boolean): Promise < number> {
21
+ export async function cmdLint(options: ParsedOptions, check: boolean): Promise<number> {
22
22
  await bootstrapFromOptions(options);
23
23
  const lintOptions: LintOptions = {
24
24
  subjects: strList(options, "subject"),
package/src/cli/lint.ts CHANGED
@@ -22,7 +22,7 @@ type LintRuleConfig = {
22
22
  severity?: "error" | "warning" | "info";
23
23
  };
24
24
 
25
- export async function runLint(argv: string[], check: boolean): Promise < void> {
25
+ export async function runLint(argv: string[], check: boolean): Promise<void> {
26
26
  const options = parseLintArgs(argv);
27
27
  const errorCount = await lintCommits(options, check);
28
28
  if (check && errorCount > 0) {
@@ -34,7 +34,7 @@ export async function lintCommits(
34
34
  options: LintOptions,
35
35
  check: boolean,
36
36
  commitOnly = options.commitOnly ?? false,
37
- ): Promise < number> {
37
+ ): Promise<number> {
38
38
  const native = loadNiftyNative();
39
39
  const cwd = options.cwd ?? process.cwd();
40
40
  const { config } = await loadConfig({ cwd, createIfMissing: false });
@@ -84,7 +84,11 @@ function printWorkspaceDiagnostic(item: LintDiagnostic): void {
84
84
  return;
85
85
  }
86
86
  const location =
87
- item.path !== undefined ? item.line !== undefined ? `${item.path}:${item.line}` : item.path : undefined;
87
+ item.path !== undefined
88
+ ? item.line !== undefined
89
+ ? `${item.path}:${item.line}`
90
+ : item.path
91
+ : undefined;
88
92
  const suffix = location ? ` @ ${location}` : "";
89
93
  console.log(`${prefix} [${item.rule}] ${item.message}${suffix}`);
90
94
  }
@@ -25,9 +25,9 @@ export function strList(options: ParsedOptions, key: string): string[] {
25
25
  return value.filter((entry): entry is string => typeof entry === "string" && entry.length > 0);
26
26
  }
27
27
  if (typeof value === "string" && value.length > 0) {
28
- return[value];
28
+ return [value];
29
29
  }
30
- return[];
30
+ return [];
31
31
  }
32
32
 
33
33
  export function authFromOptions(options: ParsedOptions): AuthCliOptions {