@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/move.js
CHANGED
|
@@ -1,7 +1,10 @@
|
|
|
1
|
-
const
|
|
1
|
+
const workspace = require('../main');
|
|
2
2
|
const utility = require('../utility');
|
|
3
3
|
const inquirer = require('inquirer');
|
|
4
4
|
|
|
5
|
+
// The board this command targets - set from --board before anything else runs
|
|
6
|
+
let kanbn = workspace;
|
|
7
|
+
|
|
5
8
|
inquirer.registerPrompt('selectLine', require('inquirer-select-line'));
|
|
6
9
|
|
|
7
10
|
/**
|
|
@@ -42,24 +45,83 @@ async function interactive(columns, columnName, columnNames, sortedColumnNames,
|
|
|
42
45
|
* @param {?number} [position=null]
|
|
43
46
|
* @param {boolean} [relative=false]
|
|
44
47
|
*/
|
|
45
|
-
function moveTask(taskId, columnName, position = null, relative = false) {
|
|
48
|
+
function moveTask(taskId, columnName, position = null, relative = false, add = false) {
|
|
46
49
|
kanbn
|
|
47
|
-
.moveTask(taskId, columnName, position, relative)
|
|
50
|
+
.moveTask(taskId, columnName, position, relative, add)
|
|
48
51
|
.then(taskId => {
|
|
49
52
|
console.log(`Moved task "${taskId}" to column "${columnName}"`);
|
|
53
|
+
utility.showActionWarnings(kanbn);
|
|
50
54
|
})
|
|
51
55
|
.catch(error => {
|
|
52
56
|
utility.error(error);
|
|
53
57
|
});
|
|
54
58
|
}
|
|
55
59
|
|
|
60
|
+
/**
|
|
61
|
+
* Add a task to the board it's being moved on, because it isn't on that board yet
|
|
62
|
+
* @param {string} taskId
|
|
63
|
+
* @param {string} columnName
|
|
64
|
+
* @param {string} boardSlug
|
|
65
|
+
* @param {boolean} verbose
|
|
66
|
+
*/
|
|
67
|
+
async function addTaskToBoard(taskId, columnName, boardSlug, verbose) {
|
|
68
|
+
try {
|
|
69
|
+
await kanbn.addTaskToBoard(taskId, columnName);
|
|
70
|
+
if (verbose) {
|
|
71
|
+
console.log(utility.replaceTags(
|
|
72
|
+
`{d}Task "${taskId}" wasn't on board "${boardSlug}" - it has been added{d}`
|
|
73
|
+
));
|
|
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
|
+
);
|
|
112
|
+
} catch (error) {
|
|
113
|
+
utility.error(error);
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
|
|
56
117
|
module.exports = async args => {
|
|
57
118
|
|
|
58
|
-
//
|
|
59
|
-
|
|
60
|
-
|
|
119
|
+
// Work out which board this command targets
|
|
120
|
+
const scoped = await utility.resolveBoard(workspace, args);
|
|
121
|
+
if (scoped === null) {
|
|
61
122
|
return;
|
|
62
123
|
}
|
|
124
|
+
kanbn = scoped;
|
|
63
125
|
|
|
64
126
|
// Get the task that we're moving
|
|
65
127
|
const taskId = args._[1];
|
|
@@ -68,14 +130,6 @@ module.exports = async args => {
|
|
|
68
130
|
return;
|
|
69
131
|
}
|
|
70
132
|
|
|
71
|
-
// Make sure the task exists
|
|
72
|
-
try {
|
|
73
|
-
await kanbn.taskExists(taskId);
|
|
74
|
-
} catch (error) {
|
|
75
|
-
utility.error(error);
|
|
76
|
-
return;
|
|
77
|
-
}
|
|
78
|
-
|
|
79
133
|
// Get the index and make sure it has some columns
|
|
80
134
|
let index;
|
|
81
135
|
try {
|
|
@@ -90,6 +144,90 @@ module.exports = async args => {
|
|
|
90
144
|
return;
|
|
91
145
|
}
|
|
92
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
|
+
|
|
194
|
+
// Moving a task on a board it isn't on yet adds it there, so that pulling work onto a board is one
|
|
195
|
+
// command rather than two. --no-add turns that back into an error. The main board is unaffected:
|
|
196
|
+
// moving an untracked task there has always been an error and still is
|
|
197
|
+
const boardSlug = await kanbn.resolveBoardSlug();
|
|
198
|
+
const onThisBoard = boardSlug in await workspace.findTaskBoards(taskId);
|
|
199
|
+
if (!onThisBoard && !(await kanbn.isMainBoard())) {
|
|
200
|
+
if (!(await kanbn.taskFileExists(taskId))) {
|
|
201
|
+
utility.error(`No task file found with id "${taskId}"`);
|
|
202
|
+
return;
|
|
203
|
+
}
|
|
204
|
+
if (!args.add) {
|
|
205
|
+
utility.error(
|
|
206
|
+
`Task "${taskId}" is not on board "${boardSlug}"\n` +
|
|
207
|
+
'Drop {b}--no-add{b} to add it to this board while moving it'
|
|
208
|
+
);
|
|
209
|
+
return;
|
|
210
|
+
}
|
|
211
|
+
let targetColumn = columnNames[0];
|
|
212
|
+
if (args.column) {
|
|
213
|
+
targetColumn = utility.strArg(args.column);
|
|
214
|
+
if (columnNames.indexOf(targetColumn) === -1) {
|
|
215
|
+
utility.error(`Column "${targetColumn}" doesn't exist`);
|
|
216
|
+
return;
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
await addTaskToBoard(taskId, targetColumn, boardSlug, kanbn.isVerbose(index));
|
|
220
|
+
return;
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
// Make sure the task exists
|
|
224
|
+
try {
|
|
225
|
+
await kanbn.taskExists(taskId);
|
|
226
|
+
} catch (error) {
|
|
227
|
+
utility.error(error);
|
|
228
|
+
return;
|
|
229
|
+
}
|
|
230
|
+
|
|
93
231
|
// Get column name if specified
|
|
94
232
|
const currentColumnName = await kanbn.findTaskColumn(taskId);
|
|
95
233
|
let columnName = currentColumnName;
|
|
@@ -103,16 +241,6 @@ module.exports = async args => {
|
|
|
103
241
|
|
|
104
242
|
// Re-use sprint option for position
|
|
105
243
|
const currentPosition = index.columns[currentColumnName].indexOf(taskId);
|
|
106
|
-
let newPosition = args.position || args.p;
|
|
107
|
-
if (newPosition) {
|
|
108
|
-
newPosition = parseInt(utility.trimLeftEscapeCharacters(newPosition));
|
|
109
|
-
if (isNaN(newPosition)) {
|
|
110
|
-
utility.error('Position value must be numeric');
|
|
111
|
-
return;
|
|
112
|
-
}
|
|
113
|
-
} else {
|
|
114
|
-
newPosition = null;
|
|
115
|
-
}
|
|
116
244
|
|
|
117
245
|
// Get a list of sorted columns
|
|
118
246
|
const sortedColumnNames = 'columnSorting' in index.options ? Object.keys(index.options.columnSorting) : [];
|
|
@@ -138,6 +266,6 @@ module.exports = async args => {
|
|
|
138
266
|
|
|
139
267
|
// Otherwise move task non-interactively
|
|
140
268
|
} else {
|
|
141
|
-
moveTask(taskId, columnName, newPosition, args.relative);
|
|
269
|
+
moveTask(taskId, columnName, newPosition, args.relative, args.add);
|
|
142
270
|
}
|
|
143
271
|
};
|
package/src/controller/remove.js
CHANGED
|
@@ -1,30 +1,76 @@
|
|
|
1
|
-
const
|
|
1
|
+
const workspace = require('../main');
|
|
2
2
|
const utility = require('../utility');
|
|
3
3
|
const inquirer = require('inquirer');
|
|
4
4
|
|
|
5
|
+
// The board this command targets - set from --board before anything else runs
|
|
6
|
+
let kanbn = workspace;
|
|
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
|
+
|
|
5
36
|
/**
|
|
6
37
|
* Remove a task
|
|
7
38
|
* @param {string} taskId
|
|
8
|
-
* @param {boolean} removeFile
|
|
39
|
+
* @param {boolean} removeFile True if the task file should be deleted as well as the index entry
|
|
40
|
+
* @param {boolean} allBoards Remove the task from every board that references it
|
|
41
|
+
* @param {string} boardSlug The board the task is being removed from
|
|
42
|
+
* @param {boolean} secondaryBoard True if that board isn't the main board
|
|
9
43
|
*/
|
|
10
|
-
function removeTask(taskId, removeFile) {
|
|
44
|
+
function removeTask(taskId, removeFile, allBoards, boardSlug, secondaryBoard) {
|
|
11
45
|
kanbn
|
|
12
|
-
.deleteTask(taskId, removeFile)
|
|
46
|
+
.deleteTask(taskId, removeFile, allBoards)
|
|
13
47
|
.then(taskId => {
|
|
14
|
-
console.log(
|
|
48
|
+
console.log(
|
|
49
|
+
`Removed task "${taskId}"${removeFile ? ' file and index entry' : ' from the index'}` +
|
|
50
|
+
(secondaryBoard && !allBoards ? ` on board "${boardSlug}"` : '')
|
|
51
|
+
);
|
|
52
|
+
utility.showActionWarnings(kanbn);
|
|
15
53
|
})
|
|
16
54
|
.catch(error => {
|
|
17
|
-
|
|
55
|
+
|
|
56
|
+
// Only a task that's on other boards can be removed from all of them, or from this one alone
|
|
57
|
+
utility.error(utility.replaceTags(
|
|
58
|
+
/other board/.test(error.message)
|
|
59
|
+
? `${error.message}\nUse {b}--all-boards{b} to remove it from all of them, or {b}--index{b} ` +
|
|
60
|
+
'to remove it from this board only'
|
|
61
|
+
: error.message
|
|
62
|
+
));
|
|
18
63
|
});
|
|
19
64
|
}
|
|
20
65
|
|
|
21
66
|
module.exports = async args => {
|
|
22
67
|
|
|
23
|
-
//
|
|
24
|
-
|
|
25
|
-
|
|
68
|
+
// Work out which board this command targets
|
|
69
|
+
const scoped = await utility.resolveBoard(workspace, args);
|
|
70
|
+
if (scoped === null) {
|
|
26
71
|
return;
|
|
27
72
|
}
|
|
73
|
+
kanbn = scoped;
|
|
28
74
|
|
|
29
75
|
// Get the task that we're removing
|
|
30
76
|
const taskId = args._[1];
|
|
@@ -33,11 +79,19 @@ module.exports = async args => {
|
|
|
33
79
|
return;
|
|
34
80
|
}
|
|
35
81
|
|
|
36
|
-
// 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
|
|
37
84
|
try {
|
|
38
85
|
await kanbn.taskExists(taskId);
|
|
39
86
|
} catch (error) {
|
|
40
|
-
|
|
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
|
+
}
|
|
41
95
|
return;
|
|
42
96
|
}
|
|
43
97
|
|
|
@@ -50,9 +104,15 @@ module.exports = async args => {
|
|
|
50
104
|
return;
|
|
51
105
|
}
|
|
52
106
|
|
|
107
|
+
// A task file is shared between boards, so deleting it while another board still references it
|
|
108
|
+
// needs --all-boards. Removing the task from this board's index alone is always safe
|
|
109
|
+
const allBoards = !!args['all-boards'];
|
|
110
|
+
const boardSlug = await kanbn.resolveBoardSlug();
|
|
111
|
+
const secondaryBoard = !(await kanbn.isMainBoard());
|
|
112
|
+
|
|
53
113
|
// If the force flag is specified, remove the task without asking
|
|
54
114
|
if (args.force) {
|
|
55
|
-
removeTask(taskId, args.index);
|
|
115
|
+
removeTask(taskId, !args.index, allBoards, boardSlug, secondaryBoard);
|
|
56
116
|
|
|
57
117
|
// Otherwise, prompt for confirmation first
|
|
58
118
|
} else {
|
|
@@ -65,7 +125,7 @@ module.exports = async args => {
|
|
|
65
125
|
}
|
|
66
126
|
]).then(async answers => {
|
|
67
127
|
if (answers.sure) {
|
|
68
|
-
removeTask(taskId, args.index);
|
|
128
|
+
removeTask(taskId, !args.index, allBoards, boardSlug, secondaryBoard);
|
|
69
129
|
}
|
|
70
130
|
}).catch(error => {
|
|
71
131
|
utility.error(error);
|
package/src/controller/rename.js
CHANGED
|
@@ -1,7 +1,10 @@
|
|
|
1
|
-
const
|
|
1
|
+
const workspace = require('../main');
|
|
2
2
|
const utility = require('../utility');
|
|
3
3
|
const inquirer = require('inquirer');
|
|
4
4
|
|
|
5
|
+
// The board this command targets - set from --board before anything else runs
|
|
6
|
+
let kanbn = workspace;
|
|
7
|
+
|
|
5
8
|
/**
|
|
6
9
|
* Rename a task interactively
|
|
7
10
|
* @param {object} taskData
|
|
@@ -51,11 +54,12 @@ function renameTask(taskId, newTaskName, currentTaskName) {
|
|
|
51
54
|
|
|
52
55
|
module.exports = async args => {
|
|
53
56
|
|
|
54
|
-
//
|
|
55
|
-
|
|
56
|
-
|
|
57
|
+
// Work out which board this command targets
|
|
58
|
+
const scoped = await utility.resolveBoard(workspace, args);
|
|
59
|
+
if (scoped === null) {
|
|
57
60
|
return;
|
|
58
61
|
}
|
|
62
|
+
kanbn = scoped;
|
|
59
63
|
|
|
60
64
|
// Get the task that we're renaming
|
|
61
65
|
const taskId = args._[1];
|
|
@@ -1,16 +1,30 @@
|
|
|
1
|
-
const
|
|
1
|
+
const workspace = require('../main');
|
|
2
2
|
const utility = require('../utility');
|
|
3
3
|
|
|
4
|
+
// The board this command targets - set from --board before anything else runs
|
|
5
|
+
let kanbn = workspace;
|
|
6
|
+
|
|
4
7
|
/**
|
|
5
8
|
* Restore a task from the archive
|
|
6
9
|
* @param {string} taskId
|
|
7
10
|
* @param {string|null} columnName
|
|
8
11
|
*/
|
|
9
|
-
function restoreTask(taskId, columnName) {
|
|
12
|
+
function restoreTask(taskId, columnName, singleBoard) {
|
|
10
13
|
kanbn
|
|
11
|
-
.restoreTask(taskId, columnName)
|
|
14
|
+
.restoreTask(taskId, columnName, singleBoard)
|
|
12
15
|
.then(taskId => {
|
|
13
16
|
console.log(`Restored task "${taskId}" from the archive`);
|
|
17
|
+
utility.showActionWarnings(kanbn);
|
|
18
|
+
|
|
19
|
+
// A task is restored to every board it was on when it was archived. A board that has since been
|
|
20
|
+
// deleted is skipped rather than failing the restore, but the user should hear about it
|
|
21
|
+
const missing = kanbn.lastRestoreWarnings || [];
|
|
22
|
+
if (missing.length) {
|
|
23
|
+
console.log(
|
|
24
|
+
`Couldn't restore "${taskId}" to ${missing.length === 1 ? 'board' : 'boards'} ` +
|
|
25
|
+
`${missing.map(slug => `"${slug}"`).join(', ')} - no longer ${missing.length === 1 ? 'exists' : 'exist'}`
|
|
26
|
+
);
|
|
27
|
+
}
|
|
14
28
|
})
|
|
15
29
|
.catch(error => {
|
|
16
30
|
utility.error(error);
|
|
@@ -19,11 +33,12 @@ function restoreTask(taskId, columnName) {
|
|
|
19
33
|
|
|
20
34
|
module.exports = async args => {
|
|
21
35
|
|
|
22
|
-
//
|
|
23
|
-
|
|
24
|
-
|
|
36
|
+
// Work out which board this command targets
|
|
37
|
+
const scoped = await utility.resolveBoard(workspace, args);
|
|
38
|
+
if (scoped === null) {
|
|
25
39
|
return;
|
|
26
40
|
}
|
|
41
|
+
kanbn = scoped;
|
|
27
42
|
|
|
28
43
|
// Get the task that we're archiving
|
|
29
44
|
const taskId = args._[1];
|
|
@@ -56,6 +71,7 @@ module.exports = async args => {
|
|
|
56
71
|
}
|
|
57
72
|
}
|
|
58
73
|
|
|
59
|
-
//
|
|
60
|
-
|
|
74
|
+
// Restore the task. Without --board it goes back to every board it was on when it was archived;
|
|
75
|
+
// with --board it goes only to that one
|
|
76
|
+
restoreTask(taskId, columnName, !!args.board);
|
|
61
77
|
};
|
package/src/controller/sort.js
CHANGED
|
@@ -1,7 +1,10 @@
|
|
|
1
|
-
const
|
|
1
|
+
const workspace = require('../main');
|
|
2
2
|
const utility = require('../utility');
|
|
3
3
|
const inquirer = require('inquirer');
|
|
4
4
|
|
|
5
|
+
// The board this command targets - set from --board before anything else runs
|
|
6
|
+
let kanbn = workspace;
|
|
7
|
+
|
|
5
8
|
inquirer.registerPrompt('recursive', require('inquirer-recursive'));
|
|
6
9
|
|
|
7
10
|
const sorterFields = [
|
|
@@ -178,6 +181,46 @@ const sorterFields = [
|
|
|
178
181
|
'--assigned'
|
|
179
182
|
],
|
|
180
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
|
|
181
224
|
}
|
|
182
225
|
];
|
|
183
226
|
|
|
@@ -275,6 +318,13 @@ function sortColumn(columnName, sorters, save) {
|
|
|
275
318
|
|
|
276
319
|
module.exports = async (args, argv) => {
|
|
277
320
|
|
|
321
|
+
// Work out which board this command targets
|
|
322
|
+
const scoped = await utility.resolveBoard(workspace, args);
|
|
323
|
+
if (scoped === null) {
|
|
324
|
+
return;
|
|
325
|
+
}
|
|
326
|
+
kanbn = scoped;
|
|
327
|
+
|
|
278
328
|
// Sortable fields and aliases
|
|
279
329
|
const sortOptions = Object.fromEntries(utility.zip(
|
|
280
330
|
sorterFields.map(sorterField => sorterField.options).flat(),
|
|
@@ -296,8 +346,14 @@ module.exports = async (args, argv) => {
|
|
|
296
346
|
'--save'
|
|
297
347
|
];
|
|
298
348
|
|
|
299
|
-
// Get the column that we're sorting
|
|
300
|
-
|
|
349
|
+
// Get the column that we're sorting. --board is consumed above, so drop it and its value before
|
|
350
|
+
// the sorter walk below, which would otherwise read the board slug as a sorter filter
|
|
351
|
+
argv = argv.slice(3).filter((arg, i, all) => {
|
|
352
|
+
if (arg === '--board' || arg === '-b') {
|
|
353
|
+
return false;
|
|
354
|
+
}
|
|
355
|
+
return !(i > 0 && (all[i - 1] === '--board' || all[i - 1] === '-b'));
|
|
356
|
+
});
|
|
301
357
|
const columnName = args._.length > 1 ? args._[1] : null;
|
|
302
358
|
|
|
303
359
|
// Column name must be defined if not sorting interactively
|
package/src/controller/sprint.js
CHANGED
|
@@ -1,7 +1,10 @@
|
|
|
1
|
-
const
|
|
1
|
+
const workspace = require('../main');
|
|
2
2
|
const utility = require('../utility');
|
|
3
3
|
const inquirer = require('inquirer');
|
|
4
4
|
|
|
5
|
+
// The board this command targets - set from --board before anything else runs
|
|
6
|
+
let kanbn = workspace;
|
|
7
|
+
|
|
5
8
|
/**
|
|
6
9
|
* Start a new sprint interactively
|
|
7
10
|
* @param {?string} [name=null] The sprint name
|
|
@@ -43,11 +46,21 @@ async function interactive(name = null, description = null) {
|
|
|
43
46
|
* @param {string} name
|
|
44
47
|
* @param {string} description
|
|
45
48
|
*/
|
|
46
|
-
function startSprint(name, description) {
|
|
49
|
+
function startSprint(name, description, verbose, secondaryBoard) {
|
|
47
50
|
kanbn
|
|
48
51
|
.sprint(name, description, new Date())
|
|
49
52
|
.then(sprint => {
|
|
50
53
|
console.log(`Started new sprint "${sprint.name}" at ${sprint.start.toISOString()}`);
|
|
54
|
+
|
|
55
|
+
// Sprints are workspace-level unless a board declares its own list, so say where this one went
|
|
56
|
+
// rather than leaving the user to guess which file changed
|
|
57
|
+
if (verbose && secondaryBoard) {
|
|
58
|
+
console.log(utility.replaceTags(
|
|
59
|
+
sprint.board === null
|
|
60
|
+
? '{d}Added to the workspace sprint list{d}'
|
|
61
|
+
: `{d}Added to board "${sprint.board}"'s own sprint list{d}`
|
|
62
|
+
));
|
|
63
|
+
}
|
|
51
64
|
})
|
|
52
65
|
.catch(error => {
|
|
53
66
|
utility.error(error);
|
|
@@ -56,11 +69,22 @@ function startSprint(name, description) {
|
|
|
56
69
|
|
|
57
70
|
module.exports = async args => {
|
|
58
71
|
|
|
59
|
-
//
|
|
60
|
-
|
|
61
|
-
|
|
72
|
+
// Work out which board this command targets
|
|
73
|
+
const scoped = await utility.resolveBoard(workspace, args);
|
|
74
|
+
if (scoped === null) {
|
|
75
|
+
return;
|
|
76
|
+
}
|
|
77
|
+
kanbn = scoped;
|
|
78
|
+
|
|
79
|
+
let index;
|
|
80
|
+
try {
|
|
81
|
+
index = await kanbn.getIndex();
|
|
82
|
+
} catch (error) {
|
|
83
|
+
utility.error(error);
|
|
62
84
|
return;
|
|
63
85
|
}
|
|
86
|
+
const verbose = kanbn.isVerbose(index);
|
|
87
|
+
const secondaryBoard = !(await kanbn.isMainBoard());
|
|
64
88
|
|
|
65
89
|
// Get sprint settings from arguments
|
|
66
90
|
// Name
|
|
@@ -79,7 +103,7 @@ module.exports = async args => {
|
|
|
79
103
|
if (args.interactive) {
|
|
80
104
|
interactive(name, description)
|
|
81
105
|
.then(answers => {
|
|
82
|
-
startSprint(answers.name, answers.description || '');
|
|
106
|
+
startSprint(answers.name, answers.description || '', verbose, secondaryBoard);
|
|
83
107
|
})
|
|
84
108
|
.catch(error => {
|
|
85
109
|
utility.error(error);
|
|
@@ -87,6 +111,6 @@ module.exports = async args => {
|
|
|
87
111
|
|
|
88
112
|
// Otherwise start sprint non-interactively
|
|
89
113
|
} else {
|
|
90
|
-
startSprint(name, description);
|
|
114
|
+
startSprint(name, description, verbose, secondaryBoard);
|
|
91
115
|
}
|
|
92
116
|
};
|
package/src/controller/status.js
CHANGED
|
@@ -1,15 +1,19 @@
|
|
|
1
|
-
const
|
|
1
|
+
const workspace = require('../main');
|
|
2
2
|
const utility = require('../utility');
|
|
3
3
|
const chrono = require('chrono-node');
|
|
4
4
|
const yaml = require('yamljs');
|
|
5
5
|
|
|
6
|
+
// The board this command targets - set from --board before anything else runs
|
|
7
|
+
let kanbn = workspace;
|
|
8
|
+
|
|
6
9
|
module.exports = async args => {
|
|
7
10
|
|
|
8
|
-
//
|
|
9
|
-
|
|
10
|
-
|
|
11
|
+
// Work out which board this command targets
|
|
12
|
+
const scoped = await utility.resolveBoard(workspace, args);
|
|
13
|
+
if (scoped === null) {
|
|
11
14
|
return;
|
|
12
15
|
}
|
|
16
|
+
kanbn = scoped;
|
|
13
17
|
|
|
14
18
|
// Get sprint number or name
|
|
15
19
|
let sprint = null;
|