better-commits 1.7.2 → 1.8.1
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/dist/branch.js +3 -0
- package/dist/git.js +89 -0
- package/dist/index.js +124 -76
- package/dist/init.js +3 -0
- package/dist/utils.js +5 -5
- package/dist/zod-state.js +3 -0
- package/package.json +1 -2
- package/src/git.ts +57 -0
- package/src/index.ts +23 -22
- package/src/utils.ts +3 -7
package/dist/branch.js
CHANGED
|
@@ -36,6 +36,9 @@ var import_fs = __toESM(require("fs"));
|
|
|
36
36
|
var import_zod_validation_error = require("zod-validation-error");
|
|
37
37
|
var CONFIG_FILE_NAME = ".better-commits.json";
|
|
38
38
|
var SPACE_TO_SELECT = `${import_picocolors.default.dim("(<space> to select)")}`;
|
|
39
|
+
var A_FOR_ALL = `${import_picocolors.default.dim(
|
|
40
|
+
"(<space> to select, <a> to select all)"
|
|
41
|
+
)}`;
|
|
39
42
|
var OPTIONAL_PROMPT = `${import_picocolors.default.dim("(optional)")}`;
|
|
40
43
|
var CACHE_PROMPT = `${import_picocolors.default.dim("(value will be saved)")}`;
|
|
41
44
|
var REGEX_SLASH_TAG = new RegExp(/\/(\w+-\d+)/);
|
package/dist/git.js
ADDED
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
#! /usr/bin/env node
|
|
2
|
+
"use strict";
|
|
3
|
+
var __create = Object.create;
|
|
4
|
+
var __defProp = Object.defineProperty;
|
|
5
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
6
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
7
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
8
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
9
|
+
var __export = (target, all) => {
|
|
10
|
+
for (var name in all)
|
|
11
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
12
|
+
};
|
|
13
|
+
var __copyProps = (to, from, except, desc) => {
|
|
14
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
15
|
+
for (let key of __getOwnPropNames(from))
|
|
16
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
17
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
18
|
+
}
|
|
19
|
+
return to;
|
|
20
|
+
};
|
|
21
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
22
|
+
// If the importer is in node compatibility mode or this is not an ESM
|
|
23
|
+
// file that has been converted to a CommonJS file using a Babel-
|
|
24
|
+
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
25
|
+
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
26
|
+
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
27
|
+
mod
|
|
28
|
+
));
|
|
29
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
30
|
+
|
|
31
|
+
// src/git.ts
|
|
32
|
+
var git_exports = {};
|
|
33
|
+
__export(git_exports, {
|
|
34
|
+
git_add: () => git_add,
|
|
35
|
+
git_status: () => git_status
|
|
36
|
+
});
|
|
37
|
+
module.exports = __toCommonJS(git_exports);
|
|
38
|
+
var import_child_process = require("child_process");
|
|
39
|
+
var p = __toESM(require("@clack/prompts"));
|
|
40
|
+
var import_picocolors = __toESM(require("picocolors"));
|
|
41
|
+
var porcelain_states = ["M", "T", "R", "D", "A", "C"];
|
|
42
|
+
function git_status() {
|
|
43
|
+
let status = "";
|
|
44
|
+
try {
|
|
45
|
+
status = (0, import_child_process.execSync)("git status --porcelain", { stdio: "pipe" }).toString();
|
|
46
|
+
} catch (err) {
|
|
47
|
+
p.log.error(import_picocolors.default.red("Failed to git status"));
|
|
48
|
+
return { index: [], work_tree: [] };
|
|
49
|
+
}
|
|
50
|
+
const lines = status.split("\n");
|
|
51
|
+
const work_tree = [];
|
|
52
|
+
const index = [];
|
|
53
|
+
lines.forEach((v) => {
|
|
54
|
+
const line = v.trimEnd();
|
|
55
|
+
if (!line)
|
|
56
|
+
return;
|
|
57
|
+
const path_plus_file = line.substring(2).trim();
|
|
58
|
+
const first_char = line.charAt(0).trim();
|
|
59
|
+
const second_char = line.charAt(1).trim();
|
|
60
|
+
if (first_char === "?" || second_char === "?") {
|
|
61
|
+
work_tree.push(path_plus_file);
|
|
62
|
+
}
|
|
63
|
+
if (porcelain_states.includes(first_char)) {
|
|
64
|
+
index.push(path_plus_file);
|
|
65
|
+
}
|
|
66
|
+
if (porcelain_states.includes(second_char)) {
|
|
67
|
+
work_tree.push(path_plus_file);
|
|
68
|
+
}
|
|
69
|
+
});
|
|
70
|
+
return { index, work_tree };
|
|
71
|
+
}
|
|
72
|
+
function git_add(files) {
|
|
73
|
+
const space_delimited_files = files.join(" ");
|
|
74
|
+
if (space_delimited_files) {
|
|
75
|
+
try {
|
|
76
|
+
(0, import_child_process.execSync)(`git add ${space_delimited_files}`, {
|
|
77
|
+
stdio: "pipe"
|
|
78
|
+
}).toString();
|
|
79
|
+
p.log.success(import_picocolors.default.green("Changes successfully staged"));
|
|
80
|
+
} catch (err) {
|
|
81
|
+
p.log.error(import_picocolors.default.red("Failed to stage changes"));
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
86
|
+
0 && (module.exports = {
|
|
87
|
+
git_add,
|
|
88
|
+
git_status
|
|
89
|
+
});
|
package/dist/index.js
CHANGED
|
@@ -34,10 +34,10 @@ __export(src_exports, {
|
|
|
34
34
|
main: () => main
|
|
35
35
|
});
|
|
36
36
|
module.exports = __toCommonJS(src_exports);
|
|
37
|
-
var
|
|
38
|
-
var
|
|
39
|
-
var
|
|
40
|
-
var
|
|
37
|
+
var p3 = __toESM(require("@clack/prompts"));
|
|
38
|
+
var import_picocolors3 = __toESM(require("picocolors"));
|
|
39
|
+
var import_child_process3 = require("child_process");
|
|
40
|
+
var import_process = require("process");
|
|
41
41
|
|
|
42
42
|
// src/zod-state.ts
|
|
43
43
|
var import_zod2 = require("zod");
|
|
@@ -52,6 +52,9 @@ var import_fs = __toESM(require("fs"));
|
|
|
52
52
|
var import_zod_validation_error = require("zod-validation-error");
|
|
53
53
|
var CONFIG_FILE_NAME = ".better-commits.json";
|
|
54
54
|
var SPACE_TO_SELECT = `${import_picocolors.default.dim("(<space> to select)")}`;
|
|
55
|
+
var A_FOR_ALL = `${import_picocolors.default.dim(
|
|
56
|
+
"(<space> to select, <a> to select all)"
|
|
57
|
+
)}`;
|
|
55
58
|
var OPTIONAL_PROMPT = `${import_picocolors.default.dim("(optional)")}`;
|
|
56
59
|
var CACHE_PROMPT = `${import_picocolors.default.dim("(value will be saved)")}`;
|
|
57
60
|
var REGEX_SLASH_TAG = new RegExp(/\/(\w+-\d+)/);
|
|
@@ -217,9 +220,6 @@ function get_git_root() {
|
|
|
217
220
|
function get_default_config_path() {
|
|
218
221
|
return (0, import_os.homedir)() + "/" + CONFIG_FILE_NAME;
|
|
219
222
|
}
|
|
220
|
-
function check_missing_stage(stats) {
|
|
221
|
-
return stats.files.filter((f) => f.index.trim() === "" || f.index === "?").map((f) => f.path);
|
|
222
|
-
}
|
|
223
223
|
function addNewLine(arr, i) {
|
|
224
224
|
return i === arr.length - 1 ? "" : "\n";
|
|
225
225
|
}
|
|
@@ -367,37 +367,82 @@ var BranchState = import_zod2.z.object({
|
|
|
367
367
|
description: import_zod2.z.string().default("")
|
|
368
368
|
}).default({});
|
|
369
369
|
|
|
370
|
+
// src/git.ts
|
|
371
|
+
var import_child_process2 = require("child_process");
|
|
372
|
+
var p2 = __toESM(require("@clack/prompts"));
|
|
373
|
+
var import_picocolors2 = __toESM(require("picocolors"));
|
|
374
|
+
var porcelain_states = ["M", "T", "R", "D", "A", "C"];
|
|
375
|
+
function git_status() {
|
|
376
|
+
let status = "";
|
|
377
|
+
try {
|
|
378
|
+
status = (0, import_child_process2.execSync)("git status --porcelain", { stdio: "pipe" }).toString();
|
|
379
|
+
} catch (err) {
|
|
380
|
+
p2.log.error(import_picocolors2.default.red("Failed to git status"));
|
|
381
|
+
return { index: [], work_tree: [] };
|
|
382
|
+
}
|
|
383
|
+
const lines = status.split("\n");
|
|
384
|
+
const work_tree = [];
|
|
385
|
+
const index = [];
|
|
386
|
+
lines.forEach((v) => {
|
|
387
|
+
const line = v.trimEnd();
|
|
388
|
+
if (!line)
|
|
389
|
+
return;
|
|
390
|
+
const path_plus_file = line.substring(2).trim();
|
|
391
|
+
const first_char = line.charAt(0).trim();
|
|
392
|
+
const second_char = line.charAt(1).trim();
|
|
393
|
+
if (first_char === "?" || second_char === "?") {
|
|
394
|
+
work_tree.push(path_plus_file);
|
|
395
|
+
}
|
|
396
|
+
if (porcelain_states.includes(first_char)) {
|
|
397
|
+
index.push(path_plus_file);
|
|
398
|
+
}
|
|
399
|
+
if (porcelain_states.includes(second_char)) {
|
|
400
|
+
work_tree.push(path_plus_file);
|
|
401
|
+
}
|
|
402
|
+
});
|
|
403
|
+
return { index, work_tree };
|
|
404
|
+
}
|
|
405
|
+
function git_add(files) {
|
|
406
|
+
const space_delimited_files = files.join(" ");
|
|
407
|
+
if (space_delimited_files) {
|
|
408
|
+
try {
|
|
409
|
+
(0, import_child_process2.execSync)(`git add ${space_delimited_files}`, {
|
|
410
|
+
stdio: "pipe"
|
|
411
|
+
}).toString();
|
|
412
|
+
p2.log.success(import_picocolors2.default.green("Changes successfully staged"));
|
|
413
|
+
} catch (err) {
|
|
414
|
+
p2.log.error(import_picocolors2.default.red("Failed to stage changes"));
|
|
415
|
+
}
|
|
416
|
+
}
|
|
417
|
+
}
|
|
418
|
+
|
|
370
419
|
// src/index.ts
|
|
371
420
|
main(load_setup());
|
|
372
421
|
async function main(config) {
|
|
373
422
|
let commit_state = CommitState.parse({});
|
|
374
|
-
|
|
375
|
-
let git_status = await simple_git.status();
|
|
423
|
+
(0, import_process.chdir)(get_git_root());
|
|
376
424
|
if (config.check_status) {
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
const staged_files =
|
|
380
|
-
|
|
381
|
-
if (
|
|
382
|
-
const unstaged_files =
|
|
383
|
-
|
|
384
|
-
const selected_for_staging = await
|
|
425
|
+
let { index, work_tree } = git_status();
|
|
426
|
+
p3.log.step(import_picocolors3.default.black(import_picocolors3.default.bgGreen(" Checking Git Status ")));
|
|
427
|
+
const staged_files = index.reduce((acc, curr, i) => import_picocolors3.default.green(acc + curr + addNewLine(index, i)), "");
|
|
428
|
+
p3.log.success("Changes to be committed:\n" + staged_files);
|
|
429
|
+
if (work_tree.length) {
|
|
430
|
+
const unstaged_files = work_tree.reduce((acc, curr, i) => import_picocolors3.default.red(acc + curr + addNewLine(work_tree, i)), "");
|
|
431
|
+
p3.log.error("Changes not staged for commit:\n" + unstaged_files);
|
|
432
|
+
const selected_for_staging = await p3.multiselect({
|
|
385
433
|
message: `Some files have not been staged, would you like to add them now? ${SPACE_TO_SELECT}`,
|
|
386
|
-
options: [{ value: ".", label: "." }, ...
|
|
434
|
+
options: [{ value: ".", label: "." }, ...work_tree.map((v) => ({ value: v, label: v }))],
|
|
387
435
|
required: false
|
|
388
436
|
});
|
|
389
|
-
if (
|
|
437
|
+
if (p3.isCancel(selected_for_staging))
|
|
390
438
|
process.exit(0);
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
439
|
+
git_add(selected_for_staging);
|
|
440
|
+
}
|
|
441
|
+
let updated_status = git_status();
|
|
442
|
+
if (!updated_status.index.length) {
|
|
443
|
+
p3.log.error(import_picocolors3.default.red('no changes added to commit (use "git add" and/or "git commit -a")'));
|
|
444
|
+
process.exit(0);
|
|
396
445
|
}
|
|
397
|
-
}
|
|
398
|
-
if (!git_status.staged.length) {
|
|
399
|
-
p2.log.error(import_picocolors2.default.red('no changes added to commit (use "git add" and/or "git commit -a")'));
|
|
400
|
-
process.exit(0);
|
|
401
446
|
}
|
|
402
447
|
if (config.commit_type.enable) {
|
|
403
448
|
let message = "Select a commit type";
|
|
@@ -406,7 +451,7 @@ async function main(config) {
|
|
|
406
451
|
const options = config.commit_type.options.map((o) => o.value);
|
|
407
452
|
const type_from_branch = infer_type_from_branch(options);
|
|
408
453
|
if (type_from_branch) {
|
|
409
|
-
message = `Commit type inferred from branch ${
|
|
454
|
+
message = `Commit type inferred from branch ${import_picocolors3.default.dim("(confirm / edit)")}`;
|
|
410
455
|
initial_value = type_from_branch;
|
|
411
456
|
}
|
|
412
457
|
}
|
|
@@ -414,38 +459,38 @@ async function main(config) {
|
|
|
414
459
|
(acc, curr) => ({ ...acc, [curr.value]: curr.emoji ?? "" }),
|
|
415
460
|
{}
|
|
416
461
|
);
|
|
417
|
-
const commit_type = await
|
|
462
|
+
const commit_type = await p3.select(
|
|
418
463
|
{
|
|
419
464
|
message,
|
|
420
465
|
initialValue: initial_value,
|
|
421
466
|
options: config.commit_type.options
|
|
422
467
|
}
|
|
423
468
|
);
|
|
424
|
-
if (
|
|
469
|
+
if (p3.isCancel(commit_type))
|
|
425
470
|
process.exit(0);
|
|
426
471
|
commit_state.type = config.commit_type.append_emoji_to_commit ? `${value_to_emoji[commit_type]} ${commit_type}`.trim() : commit_type;
|
|
427
472
|
}
|
|
428
473
|
if (config.commit_scope.enable) {
|
|
429
|
-
let commit_scope = await
|
|
474
|
+
let commit_scope = await p3.select({
|
|
430
475
|
message: "Select a commit scope",
|
|
431
476
|
initialValue: config.commit_scope.initial_value,
|
|
432
477
|
options: config.commit_scope.options
|
|
433
478
|
});
|
|
434
|
-
if (
|
|
479
|
+
if (p3.isCancel(commit_scope))
|
|
435
480
|
process.exit(0);
|
|
436
481
|
if (commit_scope === CUSTOM_SCOPE_KEY && config.commit_scope.custom_scope) {
|
|
437
|
-
commit_scope = await
|
|
482
|
+
commit_scope = await p3.text({
|
|
438
483
|
message: "Write a custom scope",
|
|
439
484
|
placeholder: ""
|
|
440
485
|
});
|
|
441
|
-
if (
|
|
486
|
+
if (p3.isCancel(commit_scope))
|
|
442
487
|
process.exit(0);
|
|
443
488
|
}
|
|
444
489
|
commit_state.scope = commit_scope;
|
|
445
490
|
}
|
|
446
491
|
if (config.check_ticket.infer_ticket) {
|
|
447
492
|
try {
|
|
448
|
-
const branch = (0,
|
|
493
|
+
const branch = (0, import_child_process3.execSync)("git branch --show-current", { stdio: "pipe" }).toString();
|
|
449
494
|
const found = [
|
|
450
495
|
branch.match(REGEX_START_UND),
|
|
451
496
|
branch.match(REGEX_SLASH_UND),
|
|
@@ -461,16 +506,16 @@ async function main(config) {
|
|
|
461
506
|
}
|
|
462
507
|
}
|
|
463
508
|
if (config.check_ticket.confirm_ticket) {
|
|
464
|
-
const user_commit_ticket = await
|
|
465
|
-
message: commit_state.ticket ? `Ticket / issue inferred from branch ${
|
|
509
|
+
const user_commit_ticket = await p3.text({
|
|
510
|
+
message: commit_state.ticket ? `Ticket / issue inferred from branch ${import_picocolors3.default.dim("(confirm / edit)")}` : `Add ticket / issue ${OPTIONAL_PROMPT}`,
|
|
466
511
|
placeholder: "",
|
|
467
512
|
initialValue: commit_state.ticket
|
|
468
513
|
});
|
|
469
|
-
if (
|
|
514
|
+
if (p3.isCancel(user_commit_ticket))
|
|
470
515
|
process.exit(0);
|
|
471
516
|
commit_state.ticket = user_commit_ticket ?? "";
|
|
472
517
|
}
|
|
473
|
-
const commit_title = await
|
|
518
|
+
const commit_title = await p3.text(
|
|
474
519
|
{
|
|
475
520
|
message: "Write a brief title describing the commit",
|
|
476
521
|
placeholder: "",
|
|
@@ -485,11 +530,11 @@ async function main(config) {
|
|
|
485
530
|
}
|
|
486
531
|
}
|
|
487
532
|
);
|
|
488
|
-
if (
|
|
533
|
+
if (p3.isCancel(commit_title))
|
|
489
534
|
process.exit(0);
|
|
490
535
|
commit_state.title = clean_commit_title(commit_title);
|
|
491
536
|
if (config.commit_body.enable) {
|
|
492
|
-
const commit_body = await
|
|
537
|
+
const commit_body = await p3.text({
|
|
493
538
|
message: `Write a detailed description of the changes ${OPTIONAL_PROMPT}`,
|
|
494
539
|
placeholder: "",
|
|
495
540
|
validate: (val) => {
|
|
@@ -497,21 +542,21 @@ async function main(config) {
|
|
|
497
542
|
return "Please enter a description";
|
|
498
543
|
}
|
|
499
544
|
});
|
|
500
|
-
if (
|
|
545
|
+
if (p3.isCancel(commit_body))
|
|
501
546
|
process.exit(0);
|
|
502
547
|
commit_state.body = commit_body ?? "";
|
|
503
548
|
}
|
|
504
549
|
if (config.commit_footer.enable) {
|
|
505
|
-
const commit_footer = await
|
|
550
|
+
const commit_footer = await p3.multiselect({
|
|
506
551
|
message: `Select optional footers ${SPACE_TO_SELECT}`,
|
|
507
552
|
initialValues: config.commit_footer.initial_value,
|
|
508
553
|
options: COMMIT_FOOTER_OPTIONS,
|
|
509
554
|
required: false
|
|
510
555
|
});
|
|
511
|
-
if (
|
|
556
|
+
if (p3.isCancel(commit_footer))
|
|
512
557
|
process.exit(0);
|
|
513
558
|
if (commit_footer.includes("breaking-change")) {
|
|
514
|
-
const breaking_changes_title = await
|
|
559
|
+
const breaking_changes_title = await p3.text({
|
|
515
560
|
message: "Breaking changes: Write a short title / summary",
|
|
516
561
|
placeholder: "",
|
|
517
562
|
validate: (value) => {
|
|
@@ -519,19 +564,19 @@ async function main(config) {
|
|
|
519
564
|
return "Please enter a title / summary";
|
|
520
565
|
}
|
|
521
566
|
});
|
|
522
|
-
if (
|
|
567
|
+
if (p3.isCancel(breaking_changes_title))
|
|
523
568
|
process.exit(0);
|
|
524
|
-
const breaking_changes_body = await
|
|
569
|
+
const breaking_changes_body = await p3.text({
|
|
525
570
|
message: `Breaking Changes: Write a description & migration instructions ${OPTIONAL_PROMPT}`,
|
|
526
571
|
placeholder: ""
|
|
527
572
|
});
|
|
528
|
-
if (
|
|
573
|
+
if (p3.isCancel(breaking_changes_body))
|
|
529
574
|
process.exit(0);
|
|
530
575
|
commit_state.breaking_title = breaking_changes_title;
|
|
531
576
|
commit_state.breaking_body = breaking_changes_body;
|
|
532
577
|
}
|
|
533
578
|
if (commit_footer.includes("deprecated")) {
|
|
534
|
-
const deprecated_title = await
|
|
579
|
+
const deprecated_title = await p3.text({
|
|
535
580
|
message: "Deprecated: Write a short title / summary",
|
|
536
581
|
placeholder: "",
|
|
537
582
|
validate: (value) => {
|
|
@@ -539,13 +584,13 @@ async function main(config) {
|
|
|
539
584
|
return "Please enter a title / summary";
|
|
540
585
|
}
|
|
541
586
|
});
|
|
542
|
-
if (
|
|
587
|
+
if (p3.isCancel(deprecated_title))
|
|
543
588
|
process.exit(0);
|
|
544
|
-
const deprecated_body = await
|
|
589
|
+
const deprecated_body = await p3.text({
|
|
545
590
|
message: `Deprecated: Write a description ${OPTIONAL_PROMPT}`,
|
|
546
591
|
placeholder: ""
|
|
547
592
|
});
|
|
548
|
-
if (
|
|
593
|
+
if (p3.isCancel(deprecated_body))
|
|
549
594
|
process.exit(0);
|
|
550
595
|
commit_state.deprecates_body = deprecated_body;
|
|
551
596
|
commit_state.deprecates_title = deprecated_title;
|
|
@@ -554,46 +599,46 @@ async function main(config) {
|
|
|
554
599
|
commit_state.closes = "Closes:";
|
|
555
600
|
}
|
|
556
601
|
if (commit_footer.includes("custom")) {
|
|
557
|
-
const custom_footer = await
|
|
602
|
+
const custom_footer = await p3.text({
|
|
558
603
|
message: "Write a custom footer",
|
|
559
604
|
placeholder: ""
|
|
560
605
|
});
|
|
561
|
-
if (
|
|
606
|
+
if (p3.isCancel(custom_footer))
|
|
562
607
|
process.exit(0);
|
|
563
608
|
commit_state.custom_footer = custom_footer;
|
|
564
609
|
}
|
|
565
610
|
}
|
|
566
611
|
let continue_commit = true;
|
|
567
|
-
|
|
612
|
+
p3.note(build_commit_string(commit_state, config, true, false), "Commit Preview");
|
|
568
613
|
if (config.confirm_commit) {
|
|
569
|
-
continue_commit = await
|
|
570
|
-
if (
|
|
614
|
+
continue_commit = await p3.confirm({ message: "Confirm Commit?" });
|
|
615
|
+
if (p3.isCancel(continue_commit))
|
|
571
616
|
process.exit(0);
|
|
572
617
|
}
|
|
573
618
|
if (!continue_commit) {
|
|
574
|
-
|
|
619
|
+
p3.log.info("Exiting without commit");
|
|
575
620
|
process.exit(0);
|
|
576
621
|
}
|
|
577
622
|
try {
|
|
578
623
|
const options = config.overrides.shell ? { shell: config.overrides.shell } : {};
|
|
579
|
-
const output = (0,
|
|
624
|
+
const output = (0, import_child_process3.execSync)(`git commit -m "${build_commit_string(commit_state, config, false, true)}"`, options).toString().trim();
|
|
580
625
|
if (config.print_commit_output)
|
|
581
|
-
|
|
626
|
+
p3.log.info(output);
|
|
582
627
|
} catch (err) {
|
|
583
|
-
|
|
628
|
+
p3.log.error("Something went wrong when committing: " + err);
|
|
584
629
|
}
|
|
585
630
|
}
|
|
586
|
-
function build_commit_string(commit_state, config, colorize = false) {
|
|
631
|
+
function build_commit_string(commit_state, config, colorize = false, escape_quotes = false) {
|
|
587
632
|
let commit_string = "";
|
|
588
633
|
if (commit_state.type) {
|
|
589
|
-
commit_string += colorize ?
|
|
634
|
+
commit_string += colorize ? import_picocolors3.default.blue(commit_state.type) : commit_state.type;
|
|
590
635
|
}
|
|
591
636
|
if (commit_state.scope) {
|
|
592
|
-
const scope = colorize ?
|
|
637
|
+
const scope = colorize ? import_picocolors3.default.cyan(commit_state.scope) : commit_state.scope;
|
|
593
638
|
commit_string += `(${scope})`;
|
|
594
639
|
}
|
|
595
640
|
if (commit_state.breaking_title && config.breaking_change.add_exclamation_to_title) {
|
|
596
|
-
commit_string += colorize ?
|
|
641
|
+
commit_string += colorize ? import_picocolors3.default.red("!") : "!";
|
|
597
642
|
}
|
|
598
643
|
if (commit_state.scope || commit_state.type) {
|
|
599
644
|
commit_string += ": ";
|
|
@@ -601,17 +646,17 @@ function build_commit_string(commit_state, config, colorize = false) {
|
|
|
601
646
|
const position_start = config.check_ticket.title_position === "start";
|
|
602
647
|
const position_end = config.check_ticket.title_position === "end";
|
|
603
648
|
if (commit_state.ticket && config.check_ticket.add_to_title && position_start) {
|
|
604
|
-
commit_string += colorize ?
|
|
649
|
+
commit_string += colorize ? import_picocolors3.default.magenta(commit_state.ticket) + " " : commit_state.ticket + " ";
|
|
605
650
|
}
|
|
606
651
|
if (commit_state.title) {
|
|
607
|
-
commit_string += colorize ?
|
|
652
|
+
commit_string += colorize ? import_picocolors3.default.reset(commit_state.title) : commit_state.title;
|
|
608
653
|
}
|
|
609
654
|
if (commit_state.ticket && config.check_ticket.add_to_title && position_end) {
|
|
610
|
-
commit_string += " " + (colorize ?
|
|
655
|
+
commit_string += " " + (colorize ? import_picocolors3.default.magenta(commit_state.ticket) : commit_state.ticket);
|
|
611
656
|
}
|
|
612
657
|
if (commit_state.body) {
|
|
613
658
|
const temp = commit_state.body.split("\\n");
|
|
614
|
-
const res = temp.map((v) => colorize ?
|
|
659
|
+
const res = temp.map((v) => colorize ? import_picocolors3.default.reset(v.trim()) : v.trim()).join("\n");
|
|
615
660
|
commit_string += colorize ? `
|
|
616
661
|
|
|
617
662
|
${res}` : `
|
|
@@ -619,32 +664,32 @@ ${res}` : `
|
|
|
619
664
|
${res}`;
|
|
620
665
|
}
|
|
621
666
|
if (commit_state.breaking_title) {
|
|
622
|
-
const title = colorize ?
|
|
667
|
+
const title = colorize ? import_picocolors3.default.red(`BREAKING CHANGE: ${commit_state.breaking_title}`) : `BREAKING CHANGE: ${commit_state.breaking_title}`;
|
|
623
668
|
commit_string += `
|
|
624
669
|
|
|
625
670
|
${title}`;
|
|
626
671
|
}
|
|
627
672
|
if (commit_state.breaking_body) {
|
|
628
|
-
const body = colorize ?
|
|
673
|
+
const body = colorize ? import_picocolors3.default.red(commit_state.breaking_body) : commit_state.breaking_body;
|
|
629
674
|
commit_string += `
|
|
630
675
|
|
|
631
676
|
${body}`;
|
|
632
677
|
}
|
|
633
678
|
if (commit_state.deprecates_title) {
|
|
634
|
-
const title = colorize ?
|
|
679
|
+
const title = colorize ? import_picocolors3.default.yellow(`DEPRECATED: ${commit_state.deprecates_title}`) : `DEPRECATED: ${commit_state.deprecates_title}`;
|
|
635
680
|
commit_string += `
|
|
636
681
|
|
|
637
682
|
${title}`;
|
|
638
683
|
}
|
|
639
684
|
if (commit_state.deprecates_body) {
|
|
640
|
-
const body = colorize ?
|
|
685
|
+
const body = colorize ? import_picocolors3.default.yellow(commit_state.deprecates_body) : commit_state.deprecates_body;
|
|
641
686
|
commit_string += `
|
|
642
687
|
|
|
643
688
|
${body}`;
|
|
644
689
|
}
|
|
645
690
|
if (commit_state.custom_footer) {
|
|
646
691
|
const temp = commit_state.custom_footer.split("\\n");
|
|
647
|
-
const res = temp.map((v) => colorize ?
|
|
692
|
+
const res = temp.map((v) => colorize ? import_picocolors3.default.reset(v.trim()) : v.trim()).join("\n");
|
|
648
693
|
commit_string += colorize ? `
|
|
649
694
|
|
|
650
695
|
${res}` : `
|
|
@@ -654,10 +699,13 @@ ${res}`;
|
|
|
654
699
|
if (commit_state.closes && commit_state.ticket) {
|
|
655
700
|
commit_string += colorize ? `
|
|
656
701
|
|
|
657
|
-
${
|
|
702
|
+
${import_picocolors3.default.reset(commit_state.closes)} ${import_picocolors3.default.magenta(commit_state.ticket)}` : `
|
|
658
703
|
|
|
659
704
|
${commit_state.closes} ${commit_state.ticket}`;
|
|
660
705
|
}
|
|
706
|
+
if (escape_quotes) {
|
|
707
|
+
commit_string = commit_string.replaceAll('"', '\\"');
|
|
708
|
+
}
|
|
661
709
|
return commit_string;
|
|
662
710
|
}
|
|
663
711
|
// Annotate the CommonJS export names for ESM import in node:
|
package/dist/init.js
CHANGED
|
@@ -34,6 +34,9 @@ var p = __toESM(require("@clack/prompts"));
|
|
|
34
34
|
var import_zod_validation_error = require("zod-validation-error");
|
|
35
35
|
var CONFIG_FILE_NAME = ".better-commits.json";
|
|
36
36
|
var SPACE_TO_SELECT = `${import_picocolors.default.dim("(<space> to select)")}`;
|
|
37
|
+
var A_FOR_ALL = `${import_picocolors.default.dim(
|
|
38
|
+
"(<space> to select, <a> to select all)"
|
|
39
|
+
)}`;
|
|
37
40
|
var OPTIONAL_PROMPT = `${import_picocolors.default.dim("(optional)")}`;
|
|
38
41
|
var CACHE_PROMPT = `${import_picocolors.default.dim("(value will be saved)")}`;
|
|
39
42
|
var REGEX_SLASH_TAG = new RegExp(/\/(\w+-\d+)/);
|
package/dist/utils.js
CHANGED
|
@@ -30,6 +30,7 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
|
|
|
30
30
|
// src/utils.ts
|
|
31
31
|
var utils_exports = {};
|
|
32
32
|
__export(utils_exports, {
|
|
33
|
+
A_FOR_ALL: () => A_FOR_ALL,
|
|
33
34
|
BRANCH_ACTION_OPTIONS: () => BRANCH_ACTION_OPTIONS,
|
|
34
35
|
CACHE_PROMPT: () => CACHE_PROMPT,
|
|
35
36
|
COMMIT_FOOTER_OPTIONS: () => COMMIT_FOOTER_OPTIONS,
|
|
@@ -49,7 +50,6 @@ __export(utils_exports, {
|
|
|
49
50
|
Z_BRANCH_ACTIONS: () => Z_BRANCH_ACTIONS,
|
|
50
51
|
Z_FOOTER_OPTIONS: () => Z_FOOTER_OPTIONS,
|
|
51
52
|
addNewLine: () => addNewLine,
|
|
52
|
-
check_missing_stage: () => check_missing_stage,
|
|
53
53
|
clean_commit_title: () => clean_commit_title,
|
|
54
54
|
get_default_config_path: () => get_default_config_path,
|
|
55
55
|
get_git_root: () => get_git_root,
|
|
@@ -204,6 +204,9 @@ var BranchState = import_zod.z.object({
|
|
|
204
204
|
// src/utils.ts
|
|
205
205
|
var CONFIG_FILE_NAME = ".better-commits.json";
|
|
206
206
|
var SPACE_TO_SELECT = `${import_picocolors.default.dim("(<space> to select)")}`;
|
|
207
|
+
var A_FOR_ALL = `${import_picocolors.default.dim(
|
|
208
|
+
"(<space> to select, <a> to select all)"
|
|
209
|
+
)}`;
|
|
207
210
|
var OPTIONAL_PROMPT = `${import_picocolors.default.dim("(optional)")}`;
|
|
208
211
|
var CACHE_PROMPT = `${import_picocolors.default.dim("(value will be saved)")}`;
|
|
209
212
|
var REGEX_SLASH_TAG = new RegExp(/\/(\w+-\d+)/);
|
|
@@ -373,9 +376,6 @@ function get_git_root() {
|
|
|
373
376
|
function get_default_config_path() {
|
|
374
377
|
return (0, import_os.homedir)() + "/" + CONFIG_FILE_NAME;
|
|
375
378
|
}
|
|
376
|
-
function check_missing_stage(stats) {
|
|
377
|
-
return stats.files.filter((f) => f.index.trim() === "" || f.index === "?").map((f) => f.path);
|
|
378
|
-
}
|
|
379
379
|
function addNewLine(arr, i) {
|
|
380
380
|
return i === arr.length - 1 ? "" : "\n";
|
|
381
381
|
}
|
|
@@ -389,6 +389,7 @@ function clean_commit_title(title) {
|
|
|
389
389
|
}
|
|
390
390
|
// Annotate the CommonJS export names for ESM import in node:
|
|
391
391
|
0 && (module.exports = {
|
|
392
|
+
A_FOR_ALL,
|
|
392
393
|
BRANCH_ACTION_OPTIONS,
|
|
393
394
|
CACHE_PROMPT,
|
|
394
395
|
COMMIT_FOOTER_OPTIONS,
|
|
@@ -408,7 +409,6 @@ function clean_commit_title(title) {
|
|
|
408
409
|
Z_BRANCH_ACTIONS,
|
|
409
410
|
Z_FOOTER_OPTIONS,
|
|
410
411
|
addNewLine,
|
|
411
|
-
check_missing_stage,
|
|
412
412
|
clean_commit_title,
|
|
413
413
|
get_default_config_path,
|
|
414
414
|
get_git_root,
|
package/dist/zod-state.js
CHANGED
|
@@ -43,6 +43,9 @@ var import_picocolors = __toESM(require("picocolors"));
|
|
|
43
43
|
var p = __toESM(require("@clack/prompts"));
|
|
44
44
|
var import_zod_validation_error = require("zod-validation-error");
|
|
45
45
|
var SPACE_TO_SELECT = `${import_picocolors.default.dim("(<space> to select)")}`;
|
|
46
|
+
var A_FOR_ALL = `${import_picocolors.default.dim(
|
|
47
|
+
"(<space> to select, <a> to select all)"
|
|
48
|
+
)}`;
|
|
46
49
|
var OPTIONAL_PROMPT = `${import_picocolors.default.dim("(optional)")}`;
|
|
47
50
|
var CACHE_PROMPT = `${import_picocolors.default.dim("(value will be saved)")}`;
|
|
48
51
|
var REGEX_SLASH_TAG = new RegExp(/\/(\w+-\d+)/);
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "better-commits",
|
|
3
3
|
"private": false,
|
|
4
|
-
"version": "1.
|
|
4
|
+
"version": "1.8.1",
|
|
5
5
|
"description": "A CLI for creating better commits following the conventional commit guidelines",
|
|
6
6
|
"author": "Erik Verduin (https://github.com/everduin94)",
|
|
7
7
|
"keywords": [
|
|
@@ -24,7 +24,6 @@
|
|
|
24
24
|
"@clack/prompts": "^0.6.2",
|
|
25
25
|
"configstore": "^5.0.1",
|
|
26
26
|
"picocolors": "^1.0.0",
|
|
27
|
-
"simple-git": "^3.16.1",
|
|
28
27
|
"zod": "^3.21.3",
|
|
29
28
|
"zod-validation-error": "^1.0.1"
|
|
30
29
|
},
|
package/src/git.ts
ADDED
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
#! /usr/bin/env node
|
|
2
|
+
import { execSync } from "child_process";
|
|
3
|
+
import * as p from "@clack/prompts";
|
|
4
|
+
import color from "picocolors";
|
|
5
|
+
|
|
6
|
+
const porcelain_states = ["M", "T", "R", "D", "A", "C"];
|
|
7
|
+
|
|
8
|
+
export function git_status(): { index: string[]; work_tree: string[] } {
|
|
9
|
+
let status = "";
|
|
10
|
+
try {
|
|
11
|
+
status = execSync("git status --porcelain", { stdio: "pipe" }).toString();
|
|
12
|
+
} catch (err) {
|
|
13
|
+
p.log.error(color.red("Failed to git status"));
|
|
14
|
+
return { index: [], work_tree: [] };
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
const lines = status.split("\n");
|
|
18
|
+
const work_tree: string[] = [];
|
|
19
|
+
const index: string[] = [];
|
|
20
|
+
lines.forEach((v) => {
|
|
21
|
+
const line = v.trimEnd();
|
|
22
|
+
if (!line) return;
|
|
23
|
+
|
|
24
|
+
const path_plus_file = line.substring(2).trim();
|
|
25
|
+
const first_char = line.charAt(0).trim();
|
|
26
|
+
const second_char = line.charAt(1).trim();
|
|
27
|
+
|
|
28
|
+
// Untracked, always dirty
|
|
29
|
+
if (first_char === "?" || second_char === "?") {
|
|
30
|
+
work_tree.push(path_plus_file);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
if (porcelain_states.includes(first_char)) {
|
|
34
|
+
index.push(path_plus_file);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
if (porcelain_states.includes(second_char)) {
|
|
38
|
+
work_tree.push(path_plus_file);
|
|
39
|
+
}
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
return { index, work_tree };
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export function git_add(files: string[]) {
|
|
46
|
+
const space_delimited_files = files.join(" ");
|
|
47
|
+
if (space_delimited_files) {
|
|
48
|
+
try {
|
|
49
|
+
execSync(`git add ${space_delimited_files}`, {
|
|
50
|
+
stdio: "pipe",
|
|
51
|
+
}).toString();
|
|
52
|
+
p.log.success(color.green("Changes successfully staged"));
|
|
53
|
+
} catch (err) {
|
|
54
|
+
p.log.error(color.red("Failed to stage changes"));
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
}
|
package/src/index.ts
CHANGED
|
@@ -2,46 +2,43 @@
|
|
|
2
2
|
|
|
3
3
|
import * as p from '@clack/prompts';
|
|
4
4
|
import color from 'picocolors';
|
|
5
|
-
import { simpleGit } from "simple-git"
|
|
6
5
|
import { execSync } from 'child_process';
|
|
6
|
+
import { chdir } from "process";
|
|
7
7
|
import { z } from "zod";
|
|
8
8
|
import { CommitState, Config } from './zod-state';
|
|
9
|
-
import { load_setup,
|
|
9
|
+
import { load_setup, addNewLine, SPACE_TO_SELECT, REGEX_SLASH_TAG, REGEX_SLASH_NUM, REGEX_START_TAG, REGEX_START_NUM, OPTIONAL_PROMPT, clean_commit_title, COMMIT_FOOTER_OPTIONS, infer_type_from_branch, Z_FOOTER_OPTIONS, CUSTOM_SCOPE_KEY, get_git_root, REGEX_SLASH_UND, REGEX_START_UND } from './utils';
|
|
10
|
+
import { git_add, git_status } from './git';
|
|
10
11
|
|
|
11
12
|
main(load_setup());
|
|
12
13
|
|
|
13
14
|
export async function main(config: z.infer<typeof Config>) {
|
|
14
15
|
let commit_state = CommitState.parse({})
|
|
15
|
-
|
|
16
|
-
|
|
16
|
+
chdir(get_git_root());
|
|
17
|
+
|
|
17
18
|
if (config.check_status) {
|
|
19
|
+
let {index, work_tree} = git_status()
|
|
18
20
|
p.log.step(color.black(color.bgGreen(' Checking Git Status ')))
|
|
19
|
-
const
|
|
20
|
-
const staged_files = git_status.staged.reduce((acc,curr,i: number) => color.green(acc+curr+addNewLine(git_status.staged,i)), '');
|
|
21
|
+
const staged_files = index.reduce((acc,curr,i: number) => color.green(acc+curr+addNewLine(index,i)), '');
|
|
21
22
|
p.log.success('Changes to be committed:\n'+staged_files)
|
|
22
|
-
if (
|
|
23
|
-
const unstaged_files =
|
|
23
|
+
if (work_tree.length) {
|
|
24
|
+
const unstaged_files = work_tree.reduce((acc,curr,i: number) => color.red(acc+curr+addNewLine(work_tree,i)), '');
|
|
24
25
|
p.log.error('Changes not staged for commit:\n'+unstaged_files)
|
|
25
26
|
const selected_for_staging = await p.multiselect({
|
|
26
27
|
message: `Some files have not been staged, would you like to add them now? ${SPACE_TO_SELECT}`,
|
|
27
|
-
options: [{value: '.', label: '.'}, ...
|
|
28
|
+
options: [{value: '.', label: '.'}, ...work_tree.map(v => ({value: v, label: v}))],
|
|
28
29
|
required: false,
|
|
29
30
|
}) as string[]
|
|
30
31
|
if (p.isCancel(selected_for_staging)) process.exit(0)
|
|
32
|
+
git_add(selected_for_staging);
|
|
33
|
+
}
|
|
31
34
|
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
}
|
|
35
|
+
let updated_status = git_status()
|
|
36
|
+
if (!updated_status.index.length) {
|
|
37
|
+
p.log.error(color.red('no changes added to commit (use "git add" and/or "git commit -a")'))
|
|
38
|
+
process.exit(0);
|
|
37
39
|
}
|
|
38
40
|
}
|
|
39
41
|
|
|
40
|
-
if (!git_status.staged.length) {
|
|
41
|
-
p.log.error(color.red('no changes added to commit (use "git add" and/or "git commit -a")'))
|
|
42
|
-
process.exit(0);
|
|
43
|
-
}
|
|
44
|
-
|
|
45
42
|
if (config.commit_type.enable) {
|
|
46
43
|
let message = 'Select a commit type';
|
|
47
44
|
let initial_value = config.commit_type.initial_value
|
|
@@ -208,7 +205,7 @@ export async function main(config: z.infer<typeof Config>) {
|
|
|
208
205
|
|
|
209
206
|
|
|
210
207
|
let continue_commit = true;
|
|
211
|
-
p.note(build_commit_string(commit_state, config, true), 'Commit Preview')
|
|
208
|
+
p.note(build_commit_string(commit_state, config, true, false), 'Commit Preview')
|
|
212
209
|
if (config.confirm_commit) {
|
|
213
210
|
continue_commit = await p.confirm({message: 'Confirm Commit?'}) as boolean;
|
|
214
211
|
if (p.isCancel(continue_commit)) process.exit(0)
|
|
@@ -221,14 +218,14 @@ export async function main(config: z.infer<typeof Config>) {
|
|
|
221
218
|
|
|
222
219
|
try {
|
|
223
220
|
const options = config.overrides.shell ? { shell: config.overrides.shell } : {}
|
|
224
|
-
const output = execSync(`git commit -m "${build_commit_string(commit_state, config, false)}"`, options).toString().trim();
|
|
221
|
+
const output = execSync(`git commit -m "${build_commit_string(commit_state, config, false, true)}"`, options).toString().trim();
|
|
225
222
|
if (config.print_commit_output) p.log.info(output)
|
|
226
223
|
} catch(err) {
|
|
227
224
|
p.log.error('Something went wrong when committing: ' + err)
|
|
228
225
|
}
|
|
229
226
|
}
|
|
230
227
|
|
|
231
|
-
function build_commit_string(commit_state: z.infer<typeof CommitState>, config: z.infer<typeof Config>, colorize: boolean = false): string {
|
|
228
|
+
function build_commit_string(commit_state: z.infer<typeof CommitState>, config: z.infer<typeof Config>, colorize: boolean = false, escape_quotes: boolean = false): string {
|
|
232
229
|
let commit_string = '';
|
|
233
230
|
if (commit_state.type) {
|
|
234
231
|
commit_string += colorize ? color.blue(commit_state.type) : commit_state.type
|
|
@@ -298,6 +295,10 @@ function build_commit_string(commit_state: z.infer<typeof CommitState>, config:
|
|
|
298
295
|
commit_string += colorize ? `\n\n${color.reset(commit_state.closes)} ${color.magenta(commit_state.ticket)}` : `\n\n${commit_state.closes} ${commit_state.ticket}`;
|
|
299
296
|
}
|
|
300
297
|
|
|
298
|
+
if (escape_quotes) {
|
|
299
|
+
commit_string = commit_string.replaceAll('"', '\\"')
|
|
300
|
+
}
|
|
301
|
+
|
|
301
302
|
return commit_string;
|
|
302
303
|
}
|
|
303
304
|
|
package/src/utils.ts
CHANGED
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import { homedir } from "os";
|
|
2
|
-
import { StatusResult } from "simple-git";
|
|
3
2
|
import { z } from "zod";
|
|
4
3
|
import color from "picocolors";
|
|
5
4
|
import { execSync } from "child_process";
|
|
@@ -10,6 +9,9 @@ import { Config } from "./zod-state";
|
|
|
10
9
|
|
|
11
10
|
export const CONFIG_FILE_NAME = ".better-commits.json";
|
|
12
11
|
export const SPACE_TO_SELECT = `${color.dim("(<space> to select)")}`;
|
|
12
|
+
export const A_FOR_ALL = `${color.dim(
|
|
13
|
+
"(<space> to select, <a> to select all)"
|
|
14
|
+
)}`;
|
|
13
15
|
export const OPTIONAL_PROMPT = `${color.dim("(optional)")}`;
|
|
14
16
|
export const CACHE_PROMPT = `${color.dim("(value will be saved)")}`;
|
|
15
17
|
export const REGEX_SLASH_TAG = new RegExp(/\/(\w+-\d+)/);
|
|
@@ -206,12 +208,6 @@ export function get_default_config_path(): string {
|
|
|
206
208
|
return homedir() + "/" + CONFIG_FILE_NAME;
|
|
207
209
|
}
|
|
208
210
|
|
|
209
|
-
export function check_missing_stage(stats: StatusResult): string[] {
|
|
210
|
-
return stats.files
|
|
211
|
-
.filter((f) => f.index.trim() === "" || f.index === "?")
|
|
212
|
-
.map((f) => f.path);
|
|
213
|
-
}
|
|
214
|
-
|
|
215
211
|
export function addNewLine(arr: string[], i: number) {
|
|
216
212
|
return i === arr.length - 1 ? "" : "\n";
|
|
217
213
|
}
|