@basementuniverse/kanbn 2.0.0 → 2.5.0
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/README.md +3 -1
- package/coverage/tmp/coverage-916017-1788028598821-0.json +1 -0
- package/coverage/tmp/{coverage-214293-1787777184569-0.json → coverage-916018-1788028597179-0.json} +1 -1
- package/coverage/tmp/coverage-916036-1788028598796-0.json +1 -0
- package/docs/actions.md +337 -0
- package/docs/advanced-configuration.md +55 -0
- package/docs/commands/add.txt +17 -1
- package/docs/commands/archive.txt +11 -0
- package/docs/commands/board.txt +9 -0
- package/docs/commands/boards.txt +34 -0
- package/docs/commands/burndown.txt +7 -0
- package/docs/commands/comment.txt +13 -1
- package/docs/commands/contributors.txt +58 -0
- package/docs/commands/edit.txt +18 -1
- package/docs/commands/find.txt +39 -1
- package/docs/commands/gantt.txt +6 -0
- package/docs/commands/help.txt +2 -0
- package/docs/commands/history.txt +6 -0
- package/docs/commands/init.txt +13 -0
- package/docs/commands/move.txt +26 -0
- package/docs/commands/remove.txt +22 -1
- package/docs/commands/rename.txt +5 -0
- package/docs/commands/restore.txt +12 -0
- package/docs/commands/sort.txt +23 -0
- package/docs/commands/sprint.txt +9 -0
- package/docs/commands/status.txt +10 -1
- package/docs/commands/task.txt +9 -0
- package/docs/commands/validate.txt +29 -1
- package/docs/contributors.md +145 -0
- package/docs/filtering-and-sorting.md +60 -3
- package/docs/index-structure.md +145 -11
- package/docs/index.md +6 -2
- package/docs/multiple-boards.md +259 -0
- package/docs/quick-start.md +21 -1
- package/docs/task-structure.md +31 -3
- package/example/README.md +23 -0
- package/example/advanced/kanbn.yml +65 -0
- package/example/boards/.kanbn/design.md +31 -0
- package/example/boards/.kanbn/index.md +44 -0
- package/example/boards/.kanbn/tasks/add-usage-alert-emails.md +19 -0
- package/example/boards/.kanbn/tasks/build-tenant-settings-page.md +43 -0
- package/example/boards/.kanbn/tasks/create-organization-switcher.md +44 -0
- package/example/boards/.kanbn/tasks/design-onboarding-checklist.md +22 -0
- package/example/boards/.kanbn/tasks/refresh-marketing-site.md +28 -0
- package/example/boards/.kanbn/tasks/ship-billing-portal.md +29 -0
- package/package.json +9 -7
- package/routes/add.json +40 -12
- package/routes/archive.json +14 -2
- package/routes/board.json +11 -3
- package/routes/boards.json +30 -0
- package/routes/burndown.json +23 -7
- package/routes/comment.json +19 -5
- package/routes/contributors.json +18 -0
- package/routes/edit.json +37 -11
- package/routes/find.json +37 -12
- package/routes/gantt.json +20 -6
- package/routes/history.json +39 -25
- package/routes/init.json +3 -1
- package/routes/move.json +24 -6
- package/routes/remove.json +18 -3
- package/routes/rename.json +11 -3
- package/routes/restore.json +14 -2
- package/routes/sort.json +40 -11
- package/routes/sprint.json +14 -4
- package/routes/status.json +23 -7
- package/routes/task.json +10 -2
- package/routes/validate.json +18 -5
- package/skills/kanbn-plan/SKILL.md +10 -1
- package/skills/kanbn-replan/SKILL.md +6 -1
- package/src/actions.js +904 -0
- package/src/board.js +25 -2
- package/src/controller/add.js +72 -55
- package/src/controller/archive.js +9 -4
- package/src/controller/board.js +21 -13
- package/src/controller/boards.js +140 -0
- package/src/controller/burndown.js +14 -7
- package/src/controller/comment.js +13 -6
- package/src/controller/contributors.js +166 -0
- package/src/controller/edit.js +48 -18
- package/src/controller/find.js +87 -13
- package/src/controller/gantt.js +13 -6
- package/src/controller/history.js +13 -6
- package/src/controller/init.js +39 -4
- package/src/controller/move.js +153 -25
- package/src/controller/remove.js +73 -13
- package/src/controller/rename.js +8 -4
- package/src/controller/restore.js +24 -8
- package/src/controller/sort.js +59 -3
- package/src/controller/sprint.js +31 -7
- package/src/controller/status.js +8 -4
- package/src/controller/task.js +34 -10
- package/src/controller/validate.js +197 -7
- package/src/git-user-name.js +9 -0
- package/src/git-user.js +55 -0
- package/src/main.d.ts +387 -9
- package/src/main.js +2757 -113
- package/src/parse-index.js +219 -17
- package/src/parse-task.js +16 -0
- package/src/utility.js +274 -0
- package/coverage/tmp/coverage-214292-1787777191526-0.json +0 -1
package/src/controller/task.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
const
|
|
1
|
+
const workspace = require("../main");
|
|
2
2
|
const utility = require("../utility");
|
|
3
3
|
const parseTask = require("../parse-task");
|
|
4
4
|
const marked = require("marked");
|
|
@@ -6,19 +6,31 @@ const markedTerminalRenderer = require("marked-terminal");
|
|
|
6
6
|
|
|
7
7
|
/**
|
|
8
8
|
* Show task information
|
|
9
|
+
* @param {object} kanbn The board-scoped kanbn instance
|
|
9
10
|
* @param {string} taskId
|
|
11
|
+
* @param {Record<string, string>} boards The boards this task appears on, and the column on each
|
|
12
|
+
* @param {boolean} json
|
|
10
13
|
*/
|
|
11
|
-
function showTask(taskId, json = false) {
|
|
14
|
+
function showTask(kanbn, taskId, boards, json = false) {
|
|
12
15
|
kanbn
|
|
13
16
|
.getTask(taskId)
|
|
14
17
|
.then((task) => {
|
|
15
18
|
if (json) {
|
|
16
|
-
console.log(JSON.stringify(task, null, 2));
|
|
19
|
+
console.log(JSON.stringify({ ...task, boards }, null, 2));
|
|
17
20
|
} else {
|
|
18
21
|
marked.setOptions({
|
|
19
22
|
renderer: new markedTerminalRenderer(),
|
|
20
23
|
});
|
|
21
|
-
|
|
24
|
+
|
|
25
|
+
// Board membership isn't part of the task file - boards own membership, not tasks - so it's
|
|
26
|
+
// appended here rather than rendered from the task itself. Only worth showing once there is
|
|
27
|
+
// more than one board to be a member of
|
|
28
|
+
const boardSlugs = Object.keys(boards);
|
|
29
|
+
const boardsSection =
|
|
30
|
+
boardSlugs.length > 1
|
|
31
|
+
? `\n## Boards\n\n${boardSlugs.map((slug) => `- ${slug}: ${boards[slug]}`).join("\n")}\n`
|
|
32
|
+
: "";
|
|
33
|
+
console.log(marked.parse(parseTask.json2md(task) + boardsSection));
|
|
22
34
|
}
|
|
23
35
|
})
|
|
24
36
|
.catch((error) => {
|
|
@@ -27,9 +39,10 @@ function showTask(taskId, json = false) {
|
|
|
27
39
|
}
|
|
28
40
|
|
|
29
41
|
module.exports = async (args) => {
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
42
|
+
|
|
43
|
+
// Work out which board this command targets
|
|
44
|
+
const kanbn = await utility.resolveBoard(workspace, args);
|
|
45
|
+
if (kanbn === null) {
|
|
33
46
|
return;
|
|
34
47
|
}
|
|
35
48
|
|
|
@@ -40,14 +53,25 @@ module.exports = async (args) => {
|
|
|
40
53
|
return;
|
|
41
54
|
}
|
|
42
55
|
|
|
43
|
-
// Make sure the task exists
|
|
56
|
+
// Make sure the task exists. A line in a column that isn't a task link has no task file to show,
|
|
57
|
+
// so say what it is and how to turn it into one rather than reporting a missing file
|
|
44
58
|
try {
|
|
45
59
|
await kanbn.taskExists(taskId);
|
|
46
60
|
} catch (error) {
|
|
61
|
+
if (!(await kanbn.taskFileExists(taskId))) {
|
|
62
|
+
const matches = await kanbn.findSimpleTasks(taskId);
|
|
63
|
+
if (matches.length === 1) {
|
|
64
|
+
utility.error(
|
|
65
|
+
`"${matches[0].text}" is a simple task in column "${matches[0].column}" - it has no task file\n` +
|
|
66
|
+
`Run {b}kanbn edit "${matches[0].text}"{b} to promote it to a task`
|
|
67
|
+
);
|
|
68
|
+
return;
|
|
69
|
+
}
|
|
70
|
+
}
|
|
47
71
|
utility.error(error);
|
|
48
72
|
return;
|
|
49
73
|
}
|
|
50
74
|
|
|
51
|
-
// Show the task
|
|
52
|
-
showTask(taskId, args.json);
|
|
75
|
+
// Show the task, along with the boards it appears on
|
|
76
|
+
showTask(kanbn, taskId, await workspace.findTaskBoards(taskId), args.json);
|
|
53
77
|
};
|
|
@@ -1,7 +1,10 @@
|
|
|
1
|
-
const
|
|
1
|
+
const workspace = require('../main');
|
|
2
2
|
const utility = require('../utility');
|
|
3
3
|
const yaml = require('yamljs');
|
|
4
4
|
|
|
5
|
+
// The board this command targets - set from --board before anything else runs
|
|
6
|
+
let kanbn = workspace;
|
|
7
|
+
|
|
5
8
|
/**
|
|
6
9
|
* Show a list of tasks whose dates disagree with the column they're in
|
|
7
10
|
* @param {object[]} drift
|
|
@@ -27,18 +30,127 @@ function showDrift(drift, json) {
|
|
|
27
30
|
}
|
|
28
31
|
}
|
|
29
32
|
|
|
33
|
+
/**
|
|
34
|
+
* Show lines in columns that aren't task links. These are preserved and ignored, so this is
|
|
35
|
+
* information rather than a problem - except for a line that looks like a task link with a typo in
|
|
36
|
+
* it, which is the one way a task can silently stop being tracked
|
|
37
|
+
* @param {object[]} warnings
|
|
38
|
+
*/
|
|
39
|
+
function showColumnContentWarnings(warnings) {
|
|
40
|
+
if (!warnings.length) {
|
|
41
|
+
return;
|
|
42
|
+
}
|
|
43
|
+
const malformed = warnings.filter(w => w.type === 'malformed-task-link');
|
|
44
|
+
const untracked = warnings.filter(w => w.type === 'untracked-task-line');
|
|
45
|
+
const other = warnings.filter(w => w.type === 'non-task-line');
|
|
46
|
+
if (malformed.length) {
|
|
47
|
+
console.log(utility.replaceTags(
|
|
48
|
+
`${malformed.length} ${malformed.length === 1 ? 'line looks' : 'lines look'} like a ` +
|
|
49
|
+
`{b}malformed task link{b} and ${malformed.length === 1 ? 'is' : 'are'} not being tracked:`
|
|
50
|
+
));
|
|
51
|
+
for (const warning of malformed) {
|
|
52
|
+
console.log(` ${warning.column}: ${warning.text}`);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
if (untracked.length) {
|
|
56
|
+
console.log(utility.replaceTags(
|
|
57
|
+
`${untracked.length} ${untracked.length === 1 ? 'line names' : 'lines name'} a task file that ` +
|
|
58
|
+
`exists but ${untracked.length === 1 ? 'is' : 'are'} {b}not written as a link{b}, so ` +
|
|
59
|
+
`${untracked.length === 1 ? 'it isn\'t' : 'they aren\'t'} tracked:`
|
|
60
|
+
));
|
|
61
|
+
for (const warning of untracked) {
|
|
62
|
+
console.log(` ${warning.column}: ${warning.text}`);
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
if (other.length) {
|
|
66
|
+
console.log(
|
|
67
|
+
other.length === 1
|
|
68
|
+
? "1 line in a column isn't a task link (preserved, but ignored):"
|
|
69
|
+
: `${other.length} lines in columns aren't task links (preserved, but ignored):`
|
|
70
|
+
);
|
|
71
|
+
for (const warning of other) {
|
|
72
|
+
console.log(` ${warning.column}: ${warning.text}`);
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Show tasks whose assigned user or comment author isn't a known contributor. These are advisory:
|
|
79
|
+
* contributors are a convenience list, not an enum, and every one of these tasks works exactly as it
|
|
80
|
+
* always has
|
|
81
|
+
* @param {object[]} warnings
|
|
82
|
+
*/
|
|
83
|
+
function showContributorWarnings(warnings) {
|
|
84
|
+
if (!warnings.length) {
|
|
85
|
+
return;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
// One line per name rather than per task, because "Gordon appears in 12 tasks" is the useful shape
|
|
89
|
+
const byValue = new Map();
|
|
90
|
+
for (const warning of warnings) {
|
|
91
|
+
if (!byValue.has(warning.value)) {
|
|
92
|
+
byValue.set(warning.value, []);
|
|
93
|
+
}
|
|
94
|
+
byValue.get(warning.value).push(warning.task);
|
|
95
|
+
}
|
|
96
|
+
console.log(utility.replaceTags(
|
|
97
|
+
`${byValue.size} ${byValue.size === 1 ? 'name is' : 'names are'} used in tasks but ` +
|
|
98
|
+
`${byValue.size === 1 ? "isn't" : "aren't"} a {b}known contributor{b}:`
|
|
99
|
+
));
|
|
100
|
+
for (const [value, tasks] of byValue) {
|
|
101
|
+
console.log(` ${value}: ${tasks.join(', ')}`);
|
|
102
|
+
}
|
|
103
|
+
console.log(utility.replaceTags('Run {b}kanbn contributors --usage{b} for more detail.'));
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* Show rules that are legal but probably not what their author meant. A rule that is wrong in the
|
|
108
|
+
* file is an error rather than a warning, and is reported with the rest of them
|
|
109
|
+
* @param {object[]} warnings
|
|
110
|
+
*/
|
|
111
|
+
function showActionWarnings(warnings) {
|
|
112
|
+
if (!warnings.length) {
|
|
113
|
+
return;
|
|
114
|
+
}
|
|
115
|
+
console.log(utility.replaceTags(
|
|
116
|
+
`${warnings.length} ${warnings.length === 1 ? 'action rule needs' : 'action rules need'} ` +
|
|
117
|
+
'a {b}second look{b}:'
|
|
118
|
+
));
|
|
119
|
+
for (const warning of warnings) {
|
|
120
|
+
console.log(` ${warning.message}`);
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
/**
|
|
125
|
+
* Show warnings about a multi-board workspace
|
|
126
|
+
* @param {object[]} warnings
|
|
127
|
+
*/
|
|
128
|
+
function showBoardWarnings(warnings) {
|
|
129
|
+
if (!warnings.length) {
|
|
130
|
+
return;
|
|
131
|
+
}
|
|
132
|
+
console.log(
|
|
133
|
+
`${warnings.length} ${warnings.length === 1 ? 'warning' : 'warnings'}:`
|
|
134
|
+
);
|
|
135
|
+
for (const warning of warnings) {
|
|
136
|
+
console.log(` ${warning.board === null ? 'workspace' : warning.board}: ${warning.message}`);
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
|
|
30
140
|
module.exports = async args => {
|
|
31
141
|
|
|
32
|
-
//
|
|
33
|
-
|
|
34
|
-
|
|
142
|
+
// Work out which board this command targets
|
|
143
|
+
const scoped = await utility.resolveBoard(workspace, args);
|
|
144
|
+
if (scoped === null) {
|
|
35
145
|
return;
|
|
36
146
|
}
|
|
147
|
+
kanbn = scoped;
|
|
37
148
|
|
|
38
|
-
// Validate kanbn files
|
|
149
|
+
// Validate kanbn files, on this board or on every board in the workspace
|
|
150
|
+
const allBoards = !!args['all-boards'];
|
|
39
151
|
let result;
|
|
40
152
|
try {
|
|
41
|
-
result = await kanbn.validate(args.save);
|
|
153
|
+
result = allBoards ? await kanbn.validateAllBoards(args.save) : await kanbn.validate(args.save);
|
|
42
154
|
} catch (error) {
|
|
43
155
|
utility.error(error);
|
|
44
156
|
return;
|
|
@@ -54,6 +166,49 @@ module.exports = async args => {
|
|
|
54
166
|
return;
|
|
55
167
|
}
|
|
56
168
|
|
|
169
|
+
// Report multi-board problems. These are warnings rather than errors: each one describes a
|
|
170
|
+
// workspace that still works, just not the way its author probably meant it to. A workspace with
|
|
171
|
+
// one board has none of these problems to have, and its output is exactly what it always was
|
|
172
|
+
const multiBoard = (await kanbn.listBoards()).length > 1;
|
|
173
|
+
let boardWarnings = [];
|
|
174
|
+
if (multiBoard) {
|
|
175
|
+
try {
|
|
176
|
+
boardWarnings = await kanbn.findBoardWarnings();
|
|
177
|
+
} catch (error) {
|
|
178
|
+
utility.error(error);
|
|
179
|
+
return;
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
// Report lines in columns that aren't task links. Unlike the warnings above these apply to any
|
|
184
|
+
// workspace, so they're gathered whether or not it has more than one board
|
|
185
|
+
let columnContentWarnings = [];
|
|
186
|
+
try {
|
|
187
|
+
columnContentWarnings = await kanbn.findColumnContentWarnings();
|
|
188
|
+
} catch (error) {
|
|
189
|
+
utility.error(error);
|
|
190
|
+
return;
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
// Report action rules that work but probably don't do what their author meant
|
|
194
|
+
let actionWarnings = [];
|
|
195
|
+
try {
|
|
196
|
+
actionWarnings = await kanbn.findActionWarnings();
|
|
197
|
+
} catch (error) {
|
|
198
|
+
utility.error(error);
|
|
199
|
+
return;
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
// Report names used in tasks that the contributors list doesn't know about. A workspace with no
|
|
203
|
+
// contributors declared has none of these, so its output is exactly what it always was
|
|
204
|
+
let contributorWarnings = [];
|
|
205
|
+
try {
|
|
206
|
+
contributorWarnings = await kanbn.findContributorWarnings();
|
|
207
|
+
} catch (error) {
|
|
208
|
+
utility.error(error);
|
|
209
|
+
return;
|
|
210
|
+
}
|
|
211
|
+
|
|
57
212
|
// Backfill missing started and completed dates
|
|
58
213
|
if (args.fix) {
|
|
59
214
|
let fixed;
|
|
@@ -64,6 +219,12 @@ module.exports = async args => {
|
|
|
64
219
|
return;
|
|
65
220
|
}
|
|
66
221
|
if (!fixed.length) {
|
|
222
|
+
if (!args.json && (columnContentWarnings.length || contributorWarnings.length || actionWarnings.length)) {
|
|
223
|
+
showColumnContentWarnings(columnContentWarnings);
|
|
224
|
+
showContributorWarnings(contributorWarnings);
|
|
225
|
+
showActionWarnings(actionWarnings);
|
|
226
|
+
return;
|
|
227
|
+
}
|
|
67
228
|
console.log('Everything OK, nothing to fix');
|
|
68
229
|
return;
|
|
69
230
|
}
|
|
@@ -86,8 +247,37 @@ module.exports = async args => {
|
|
|
86
247
|
utility.error(error);
|
|
87
248
|
return;
|
|
88
249
|
}
|
|
250
|
+
if (args.json) {
|
|
251
|
+
|
|
252
|
+
// A multi-board workspace has warnings to report as well as drift, so JSON output carries both.
|
|
253
|
+
// With a single board it stays the bare drift list it has always been - unless there are
|
|
254
|
+
// warnings, which have nowhere to go in a bare drift list and are worth more than the shape
|
|
255
|
+
const warnings = [...boardWarnings, ...columnContentWarnings, ...contributorWarnings, ...actionWarnings];
|
|
256
|
+
if (multiBoard || warnings.length) {
|
|
257
|
+
console.log(JSON.stringify({ drift, warnings }, null, 2));
|
|
258
|
+
return;
|
|
259
|
+
}
|
|
260
|
+
if (drift.length) {
|
|
261
|
+
showDrift(drift, true);
|
|
262
|
+
return;
|
|
263
|
+
}
|
|
264
|
+
console.log('Everything OK');
|
|
265
|
+
return;
|
|
266
|
+
}
|
|
89
267
|
if (drift.length) {
|
|
90
|
-
showDrift(drift,
|
|
268
|
+
showDrift(drift, false);
|
|
269
|
+
}
|
|
270
|
+
showColumnContentWarnings(columnContentWarnings);
|
|
271
|
+
showContributorWarnings(contributorWarnings);
|
|
272
|
+
showActionWarnings(actionWarnings);
|
|
273
|
+
showBoardWarnings(boardWarnings);
|
|
274
|
+
if (
|
|
275
|
+
drift.length ||
|
|
276
|
+
boardWarnings.length ||
|
|
277
|
+
columnContentWarnings.length ||
|
|
278
|
+
contributorWarnings.length ||
|
|
279
|
+
actionWarnings.length
|
|
280
|
+
) {
|
|
91
281
|
return;
|
|
92
282
|
}
|
|
93
283
|
console.log('Everything OK');
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
const gitUser = require('./git-user');
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Get the git username of the current user, or null if it can't be found
|
|
5
|
+
*
|
|
6
|
+
* Kept as a named module because it was the public entry point before git-user.js existed
|
|
7
|
+
* @return {string|null} The git username, or null if git isn't available or no username is set
|
|
8
|
+
*/
|
|
9
|
+
module.exports = () => gitUser.name();
|
package/src/git-user.js
ADDED
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
const { execFileSync } = require('child_process');
|
|
2
|
+
|
|
3
|
+
// Reading a git config value means spawning a process, and the current user is resolved from prompt
|
|
4
|
+
// defaults that run in loops. The values can't change while a command is running, so they're read at
|
|
5
|
+
// most once each per process
|
|
6
|
+
const memo = {};
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Read a value from git config, or null if it can't be read. This replaces the git-user-name
|
|
10
|
+
* package, which depends on a version of parse-git-config with an unpatched prototype pollution
|
|
11
|
+
* vulnerability
|
|
12
|
+
* @param {string} key The git config key
|
|
13
|
+
* @return {?string} The value, or null if git isn't available or the value isn't set
|
|
14
|
+
*/
|
|
15
|
+
function gitConfig(key) {
|
|
16
|
+
if (!(key in memo)) {
|
|
17
|
+
try {
|
|
18
|
+
memo[key] = execFileSync('git', ['config', key], {
|
|
19
|
+
encoding: 'utf8',
|
|
20
|
+
stdio: ['ignore', 'pipe', 'ignore']
|
|
21
|
+
}).trim() || null;
|
|
22
|
+
} catch (error) {
|
|
23
|
+
memo[key] = null;
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
return memo[key];
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
module.exports = {
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Get the git username of the current user, or null if it can't be found
|
|
33
|
+
* @return {?string} The git username
|
|
34
|
+
*/
|
|
35
|
+
name() {
|
|
36
|
+
return gitConfig('user.name');
|
|
37
|
+
},
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Get the git email address of the current user, or null if it can't be found
|
|
41
|
+
* @return {?string} The git email address
|
|
42
|
+
*/
|
|
43
|
+
email() {
|
|
44
|
+
return gitConfig('user.email');
|
|
45
|
+
},
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Forget the memoized values. Only useful in tests, where git config is faked per case
|
|
49
|
+
*/
|
|
50
|
+
clearCache() {
|
|
51
|
+
for (const key of Object.keys(memo)) {
|
|
52
|
+
delete memo[key];
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
};
|