@basementuniverse/kanbn 2.1.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 +1 -0
- 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 +32 -0
- package/docs/commands/add.txt +8 -1
- package/docs/commands/archive.txt +6 -0
- package/docs/commands/board.txt +4 -0
- package/docs/commands/burndown.txt +1 -0
- package/docs/commands/comment.txt +8 -1
- package/docs/commands/contributors.txt +58 -0
- package/docs/commands/edit.txt +13 -1
- package/docs/commands/find.txt +28 -1
- package/docs/commands/gantt.txt +1 -0
- package/docs/commands/help.txt +1 -0
- package/docs/commands/history.txt +1 -0
- package/docs/commands/move.txt +17 -0
- package/docs/commands/remove.txt +10 -0
- package/docs/commands/restore.txt +6 -0
- package/docs/commands/sort.txt +18 -0
- package/docs/commands/task.txt +4 -0
- package/docs/commands/validate.txt +14 -1
- package/docs/contributors.md +145 -0
- package/docs/filtering-and-sorting.md +60 -3
- package/docs/index-structure.md +119 -11
- package/docs/index.md +3 -1
- package/docs/multiple-boards.md +1 -0
- package/docs/task-structure.md +9 -2
- package/example/advanced/kanbn.yml +65 -0
- package/package.json +1 -1
- package/routes/add.json +5 -1
- package/routes/archive.json +6 -2
- package/routes/comment.json +5 -1
- package/routes/contributors.json +18 -0
- package/routes/edit.json +5 -1
- package/routes/move.json +4 -2
- package/routes/remove.json +6 -2
- package/routes/restore.json +6 -0
- package/routes/sort.json +5 -0
- package/src/actions.js +904 -0
- package/src/board.js +24 -1
- package/src/controller/add.js +21 -10
- package/src/controller/archive.js +1 -0
- package/src/controller/board.js +13 -4
- package/src/controller/burndown.js +5 -2
- package/src/controller/comment.js +5 -2
- package/src/controller/contributors.js +166 -0
- package/src/controller/edit.js +40 -14
- package/src/controller/find.js +75 -4
- package/src/controller/gantt.js +5 -2
- package/src/controller/history.js +5 -2
- package/src/controller/move.js +84 -10
- package/src/controller/remove.js +39 -2
- package/src/controller/restore.js +1 -0
- package/src/controller/sort.js +40 -0
- package/src/controller/task.js +12 -1
- package/src/controller/validate.js +141 -4
- package/src/git-user-name.js +5 -15
- package/src/git-user.js +55 -0
- package/src/main.d.ts +204 -4
- package/src/main.js +1412 -38
- package/src/parse-index.js +205 -17
- package/src/utility.js +135 -1
- package/coverage/tmp/coverage-214292-1787777191526-0.json +0 -1
package/src/controller/move.js
CHANGED
|
@@ -50,6 +50,7 @@ function moveTask(taskId, columnName, position = null, relative = false, add = f
|
|
|
50
50
|
.moveTask(taskId, columnName, position, relative, add)
|
|
51
51
|
.then(taskId => {
|
|
52
52
|
console.log(`Moved task "${taskId}" to column "${columnName}"`);
|
|
53
|
+
utility.showActionWarnings(kanbn);
|
|
53
54
|
})
|
|
54
55
|
.catch(error => {
|
|
55
56
|
utility.error(error);
|
|
@@ -72,6 +73,42 @@ async function addTaskToBoard(taskId, columnName, boardSlug, verbose) {
|
|
|
72
73
|
));
|
|
73
74
|
}
|
|
74
75
|
console.log(`Moved task "${taskId}" to column "${columnName}"`);
|
|
76
|
+
utility.showActionWarnings(kanbn);
|
|
77
|
+
} catch (error) {
|
|
78
|
+
utility.error(error);
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* Move a simple task to another column on the board it's on
|
|
84
|
+
* @param {object} boardKanbn The board-scoped Kanbn instance
|
|
85
|
+
* @param {string} input
|
|
86
|
+
* @param {?string} columnName The column to move to, or null to leave it where it is
|
|
87
|
+
* @param {?number} position
|
|
88
|
+
*/
|
|
89
|
+
async function moveSimpleTask(boardKanbn, input, columnName, position) {
|
|
90
|
+
try {
|
|
91
|
+
const moved = await boardKanbn.moveSimpleTask(input, columnName, position);
|
|
92
|
+
console.log(`Moved simple task "${moved.text}" to column "${moved.toColumn}"`);
|
|
93
|
+
} catch (error) {
|
|
94
|
+
utility.error(error);
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* Move a simple task onto another board
|
|
100
|
+
* @param {object} sourceKanbn The board the simple task is on
|
|
101
|
+
* @param {string} input
|
|
102
|
+
* @param {string} targetSlug
|
|
103
|
+
* @param {?string} columnName
|
|
104
|
+
* @param {?number} position
|
|
105
|
+
*/
|
|
106
|
+
async function moveSimpleTaskToBoard(sourceKanbn, input, targetSlug, columnName, position) {
|
|
107
|
+
try {
|
|
108
|
+
const moved = await sourceKanbn.moveSimpleTaskToBoard(input, targetSlug, columnName, position);
|
|
109
|
+
console.log(
|
|
110
|
+
`Moved simple task "${moved.text}" to column "${moved.toColumn}" on board "${moved.toBoard}"`
|
|
111
|
+
);
|
|
75
112
|
} catch (error) {
|
|
76
113
|
utility.error(error);
|
|
77
114
|
}
|
|
@@ -107,6 +144,53 @@ module.exports = async args => {
|
|
|
107
144
|
return;
|
|
108
145
|
}
|
|
109
146
|
|
|
147
|
+
// Re-use sprint option for position
|
|
148
|
+
let newPosition = args.position || args.p;
|
|
149
|
+
if (newPosition) {
|
|
150
|
+
newPosition = parseInt(utility.trimLeftEscapeCharacters(newPosition));
|
|
151
|
+
if (isNaN(newPosition)) {
|
|
152
|
+
utility.error('Position value must be numeric');
|
|
153
|
+
return;
|
|
154
|
+
}
|
|
155
|
+
} else {
|
|
156
|
+
newPosition = null;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
// A task file always wins, so a simple task - a line in a column that isn't a task link - is only
|
|
160
|
+
// looked for when nothing has this task id. Falling through leaves every existing error unchanged
|
|
161
|
+
if (!(await kanbn.taskFileExists(taskId))) {
|
|
162
|
+
const targetColumn = args.column ? utility.strArg(args.column) : null;
|
|
163
|
+
const matches = await kanbn.findSimpleTasks(taskId);
|
|
164
|
+
if (matches.length) {
|
|
165
|
+
const simpleTask = await utility.resolveSimpleTask(kanbn, taskId, null, matches);
|
|
166
|
+
if (simpleTask !== null) {
|
|
167
|
+
await moveSimpleTask(
|
|
168
|
+
kanbn,
|
|
169
|
+
taskId,
|
|
170
|
+
targetColumn === null ? simpleTask.column : targetColumn,
|
|
171
|
+
newPosition
|
|
172
|
+
);
|
|
173
|
+
}
|
|
174
|
+
return;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
// A simple task lives in one board file rather than in a shared task file, so `-b` names the
|
|
178
|
+
// board it is moving to rather than the board it is on
|
|
179
|
+
if (args.board) {
|
|
180
|
+
const sourceKanbn = await workspace.boardFromArgs({});
|
|
181
|
+
if ((await sourceKanbn.findSimpleTasks(taskId)).length) {
|
|
182
|
+
await moveSimpleTaskToBoard(
|
|
183
|
+
sourceKanbn,
|
|
184
|
+
taskId,
|
|
185
|
+
await kanbn.resolveBoardSlug(),
|
|
186
|
+
targetColumn,
|
|
187
|
+
newPosition
|
|
188
|
+
);
|
|
189
|
+
return;
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
|
|
110
194
|
// Moving a task on a board it isn't on yet adds it there, so that pulling work onto a board is one
|
|
111
195
|
// command rather than two. --no-add turns that back into an error. The main board is unaffected:
|
|
112
196
|
// moving an untracked task there has always been an error and still is
|
|
@@ -157,16 +241,6 @@ module.exports = async args => {
|
|
|
157
241
|
|
|
158
242
|
// Re-use sprint option for position
|
|
159
243
|
const currentPosition = index.columns[currentColumnName].indexOf(taskId);
|
|
160
|
-
let newPosition = args.position || args.p;
|
|
161
|
-
if (newPosition) {
|
|
162
|
-
newPosition = parseInt(utility.trimLeftEscapeCharacters(newPosition));
|
|
163
|
-
if (isNaN(newPosition)) {
|
|
164
|
-
utility.error('Position value must be numeric');
|
|
165
|
-
return;
|
|
166
|
-
}
|
|
167
|
-
} else {
|
|
168
|
-
newPosition = null;
|
|
169
|
-
}
|
|
170
244
|
|
|
171
245
|
// Get a list of sorted columns
|
|
172
246
|
const sortedColumnNames = 'columnSorting' in index.options ? Object.keys(index.options.columnSorting) : [];
|
package/src/controller/remove.js
CHANGED
|
@@ -5,6 +5,34 @@ const inquirer = require('inquirer');
|
|
|
5
5
|
// The board this command targets - set from --board before anything else runs
|
|
6
6
|
let kanbn = workspace;
|
|
7
7
|
|
|
8
|
+
/**
|
|
9
|
+
* Remove a simple task - a line in a column that isn't a task link. There is no file to delete and
|
|
10
|
+
* nothing to archive: the line is the whole of it
|
|
11
|
+
* @param {string} input
|
|
12
|
+
* @param {boolean} force True to skip the confirmation prompt
|
|
13
|
+
*/
|
|
14
|
+
async function removeSimpleTask(input, force) {
|
|
15
|
+
if (!force) {
|
|
16
|
+
const answers = await inquirer.prompt([
|
|
17
|
+
{
|
|
18
|
+
type: 'confirm',
|
|
19
|
+
message: 'Are you sure you want to remove this simple task?',
|
|
20
|
+
name: 'sure',
|
|
21
|
+
default: false
|
|
22
|
+
}
|
|
23
|
+
]);
|
|
24
|
+
if (!answers.sure) {
|
|
25
|
+
return;
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
try {
|
|
29
|
+
const removed = await kanbn.deleteSimpleTask(input);
|
|
30
|
+
console.log(`Removed simple task "${removed.text}" from column "${removed.column}"`);
|
|
31
|
+
} catch (error) {
|
|
32
|
+
utility.error(error);
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
8
36
|
/**
|
|
9
37
|
* Remove a task
|
|
10
38
|
* @param {string} taskId
|
|
@@ -21,6 +49,7 @@ function removeTask(taskId, removeFile, allBoards, boardSlug, secondaryBoard) {
|
|
|
21
49
|
`Removed task "${taskId}"${removeFile ? ' file and index entry' : ' from the index'}` +
|
|
22
50
|
(secondaryBoard && !allBoards ? ` on board "${boardSlug}"` : '')
|
|
23
51
|
);
|
|
52
|
+
utility.showActionWarnings(kanbn);
|
|
24
53
|
})
|
|
25
54
|
.catch(error => {
|
|
26
55
|
|
|
@@ -50,11 +79,19 @@ module.exports = async args => {
|
|
|
50
79
|
return;
|
|
51
80
|
}
|
|
52
81
|
|
|
53
|
-
// Make sure the task exists
|
|
82
|
+
// Make sure the task exists. A task file always wins, so a simple task is only looked for when
|
|
83
|
+
// nothing has this task id
|
|
54
84
|
try {
|
|
55
85
|
await kanbn.taskExists(taskId);
|
|
56
86
|
} catch (error) {
|
|
57
|
-
|
|
87
|
+
if (await kanbn.taskFileExists(taskId)) {
|
|
88
|
+
utility.error(error);
|
|
89
|
+
return;
|
|
90
|
+
}
|
|
91
|
+
const simpleTask = await utility.resolveSimpleTask(kanbn, taskId, error);
|
|
92
|
+
if (simpleTask !== null) {
|
|
93
|
+
await removeSimpleTask(taskId, !!args.force);
|
|
94
|
+
}
|
|
58
95
|
return;
|
|
59
96
|
}
|
|
60
97
|
|
|
@@ -14,6 +14,7 @@ function restoreTask(taskId, columnName, singleBoard) {
|
|
|
14
14
|
.restoreTask(taskId, columnName, singleBoard)
|
|
15
15
|
.then(taskId => {
|
|
16
16
|
console.log(`Restored task "${taskId}" from the archive`);
|
|
17
|
+
utility.showActionWarnings(kanbn);
|
|
17
18
|
|
|
18
19
|
// A task is restored to every board it was on when it was archived. A board that has since been
|
|
19
20
|
// deleted is skipped rather than failing the restore, but the user should hear about it
|
package/src/controller/sort.js
CHANGED
|
@@ -181,6 +181,46 @@ const sorterFields = [
|
|
|
181
181
|
'--assigned'
|
|
182
182
|
],
|
|
183
183
|
filterable: true
|
|
184
|
+
},
|
|
185
|
+
{
|
|
186
|
+
name: 'Overdue',
|
|
187
|
+
field: 'overdue',
|
|
188
|
+
options: [
|
|
189
|
+
'--overdue'
|
|
190
|
+
],
|
|
191
|
+
filterable: false
|
|
192
|
+
},
|
|
193
|
+
{
|
|
194
|
+
name: 'Is started',
|
|
195
|
+
field: 'isStarted',
|
|
196
|
+
options: [
|
|
197
|
+
'--is-started'
|
|
198
|
+
],
|
|
199
|
+
filterable: false
|
|
200
|
+
},
|
|
201
|
+
{
|
|
202
|
+
name: 'Is completed',
|
|
203
|
+
field: 'isCompleted',
|
|
204
|
+
options: [
|
|
205
|
+
'--is-completed'
|
|
206
|
+
],
|
|
207
|
+
filterable: false
|
|
208
|
+
},
|
|
209
|
+
{
|
|
210
|
+
name: 'In started column',
|
|
211
|
+
field: 'inStartedColumn',
|
|
212
|
+
options: [
|
|
213
|
+
'--in-started-column'
|
|
214
|
+
],
|
|
215
|
+
filterable: false
|
|
216
|
+
},
|
|
217
|
+
{
|
|
218
|
+
name: 'In completed column',
|
|
219
|
+
field: 'inCompletedColumn',
|
|
220
|
+
options: [
|
|
221
|
+
'--in-completed-column'
|
|
222
|
+
],
|
|
223
|
+
filterable: false
|
|
184
224
|
}
|
|
185
225
|
];
|
|
186
226
|
|
package/src/controller/task.js
CHANGED
|
@@ -53,10 +53,21 @@ module.exports = async (args) => {
|
|
|
53
53
|
return;
|
|
54
54
|
}
|
|
55
55
|
|
|
56
|
-
// 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
|
|
57
58
|
try {
|
|
58
59
|
await kanbn.taskExists(taskId);
|
|
59
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
|
+
}
|
|
60
71
|
utility.error(error);
|
|
61
72
|
return;
|
|
62
73
|
}
|
|
@@ -30,6 +30,97 @@ function showDrift(drift, json) {
|
|
|
30
30
|
}
|
|
31
31
|
}
|
|
32
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
|
+
|
|
33
124
|
/**
|
|
34
125
|
* Show warnings about a multi-board workspace
|
|
35
126
|
* @param {object[]} warnings
|
|
@@ -89,6 +180,35 @@ module.exports = async args => {
|
|
|
89
180
|
}
|
|
90
181
|
}
|
|
91
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
|
+
|
|
92
212
|
// Backfill missing started and completed dates
|
|
93
213
|
if (args.fix) {
|
|
94
214
|
let fixed;
|
|
@@ -99,6 +219,12 @@ module.exports = async args => {
|
|
|
99
219
|
return;
|
|
100
220
|
}
|
|
101
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
|
+
}
|
|
102
228
|
console.log('Everything OK, nothing to fix');
|
|
103
229
|
return;
|
|
104
230
|
}
|
|
@@ -124,9 +250,11 @@ module.exports = async args => {
|
|
|
124
250
|
if (args.json) {
|
|
125
251
|
|
|
126
252
|
// A multi-board workspace has warnings to report as well as drift, so JSON output carries both.
|
|
127
|
-
// With a single board it stays the bare drift list it has always been
|
|
128
|
-
|
|
129
|
-
|
|
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));
|
|
130
258
|
return;
|
|
131
259
|
}
|
|
132
260
|
if (drift.length) {
|
|
@@ -139,8 +267,17 @@ module.exports = async args => {
|
|
|
139
267
|
if (drift.length) {
|
|
140
268
|
showDrift(drift, false);
|
|
141
269
|
}
|
|
270
|
+
showColumnContentWarnings(columnContentWarnings);
|
|
271
|
+
showContributorWarnings(contributorWarnings);
|
|
272
|
+
showActionWarnings(actionWarnings);
|
|
142
273
|
showBoardWarnings(boardWarnings);
|
|
143
|
-
if (
|
|
274
|
+
if (
|
|
275
|
+
drift.length ||
|
|
276
|
+
boardWarnings.length ||
|
|
277
|
+
columnContentWarnings.length ||
|
|
278
|
+
contributorWarnings.length ||
|
|
279
|
+
actionWarnings.length
|
|
280
|
+
) {
|
|
144
281
|
return;
|
|
145
282
|
}
|
|
146
283
|
console.log('Everything OK');
|
package/src/git-user-name.js
CHANGED
|
@@ -1,19 +1,9 @@
|
|
|
1
|
-
const
|
|
1
|
+
const gitUser = require('./git-user');
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
|
-
* Get the git username of the current user, or null if it can't be found
|
|
5
|
-
*
|
|
6
|
-
*
|
|
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
7
|
* @return {string|null} The git username, or null if git isn't available or no username is set
|
|
8
8
|
*/
|
|
9
|
-
module.exports = () =>
|
|
10
|
-
try {
|
|
11
|
-
const username = execFileSync('git', ['config', 'user.name'], {
|
|
12
|
-
encoding: 'utf8',
|
|
13
|
-
stdio: ['ignore', 'pipe', 'ignore']
|
|
14
|
-
}).trim();
|
|
15
|
-
return username || null;
|
|
16
|
-
} catch (error) {
|
|
17
|
-
return null;
|
|
18
|
-
}
|
|
19
|
-
};
|
|
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
|
+
};
|