@basementuniverse/kanbn 2.0.0 → 2.1.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 +2 -1
- package/docs/advanced-configuration.md +23 -0
- package/docs/commands/add.txt +9 -0
- package/docs/commands/archive.txt +5 -0
- package/docs/commands/board.txt +5 -0
- package/docs/commands/boards.txt +34 -0
- package/docs/commands/burndown.txt +6 -0
- package/docs/commands/comment.txt +5 -0
- package/docs/commands/edit.txt +5 -0
- package/docs/commands/find.txt +11 -0
- package/docs/commands/gantt.txt +5 -0
- package/docs/commands/help.txt +1 -0
- package/docs/commands/history.txt +5 -0
- package/docs/commands/init.txt +13 -0
- package/docs/commands/move.txt +9 -0
- package/docs/commands/remove.txt +12 -1
- package/docs/commands/rename.txt +5 -0
- package/docs/commands/restore.txt +6 -0
- package/docs/commands/sort.txt +5 -0
- package/docs/commands/sprint.txt +9 -0
- package/docs/commands/status.txt +10 -1
- package/docs/commands/task.txt +5 -0
- package/docs/commands/validate.txt +15 -0
- package/docs/index-structure.md +26 -0
- package/docs/index.md +3 -1
- package/docs/multiple-boards.md +258 -0
- package/docs/quick-start.md +21 -1
- package/docs/task-structure.md +22 -1
- package/example/README.md +23 -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 +35 -11
- package/routes/archive.json +10 -2
- package/routes/board.json +11 -3
- package/routes/boards.json +30 -0
- package/routes/burndown.json +23 -7
- package/routes/comment.json +14 -4
- package/routes/edit.json +32 -10
- 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 +22 -6
- package/routes/remove.json +15 -4
- package/routes/rename.json +11 -3
- package/routes/restore.json +8 -2
- package/routes/sort.json +35 -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/board.js +1 -1
- package/src/controller/add.js +52 -46
- package/src/controller/archive.js +8 -4
- package/src/controller/board.js +8 -9
- package/src/controller/boards.js +140 -0
- package/src/controller/burndown.js +9 -5
- package/src/controller/comment.js +9 -5
- package/src/controller/edit.js +9 -5
- package/src/controller/find.js +12 -9
- package/src/controller/gantt.js +8 -4
- package/src/controller/history.js +8 -4
- package/src/controller/init.js +39 -4
- package/src/controller/move.js +69 -15
- package/src/controller/remove.js +34 -11
- package/src/controller/rename.js +8 -4
- package/src/controller/restore.js +23 -8
- package/src/controller/sort.js +19 -3
- package/src/controller/sprint.js +31 -7
- package/src/controller/status.js +8 -4
- package/src/controller/task.js +22 -9
- package/src/controller/validate.js +60 -7
- package/src/git-user-name.js +19 -0
- package/src/main.d.ts +184 -6
- package/src/main.js +1333 -63
- package/src/parse-index.js +14 -0
- package/src/parse-task.js +16 -0
- package/src/utility.js +140 -0
package/src/controller/init.js
CHANGED
|
@@ -1,8 +1,11 @@
|
|
|
1
1
|
const kanbn_module = require('../main');
|
|
2
|
-
const
|
|
2
|
+
const workspace = new kanbn_module.Kanbn();
|
|
3
3
|
const utility = require('../utility');
|
|
4
4
|
const inquirer = require('inquirer');
|
|
5
5
|
|
|
6
|
+
// The board being initialised - set from --board before anything else runs
|
|
7
|
+
let kanbn = workspace;
|
|
8
|
+
|
|
6
9
|
inquirer.registerPrompt('recursive', require('inquirer-recursive'));
|
|
7
10
|
|
|
8
11
|
/**
|
|
@@ -73,11 +76,20 @@ async function interactive(options, initialised) {
|
|
|
73
76
|
* Initialise kanbn
|
|
74
77
|
* @param {object} options
|
|
75
78
|
* @param {boolean} initialised
|
|
79
|
+
* @param {?string} boardSlug The slug of the board being initialised, or null for the main board
|
|
76
80
|
*/
|
|
77
|
-
async function initialise(options, initialised) {
|
|
81
|
+
async function initialise(options, initialised, boardSlug = null) {
|
|
78
82
|
const mainFolder = await kanbn.getMainFolder();
|
|
79
83
|
kanbn.initialise(options)
|
|
80
84
|
.then(() => {
|
|
85
|
+
if (boardSlug !== null) {
|
|
86
|
+
console.log(
|
|
87
|
+
initialised
|
|
88
|
+
? `Updated board "${boardSlug}" in ${mainFolder}`
|
|
89
|
+
: `Created board "${boardSlug}" in ${mainFolder}`
|
|
90
|
+
);
|
|
91
|
+
return;
|
|
92
|
+
}
|
|
81
93
|
if (initialised) {
|
|
82
94
|
console.log(`Reinitialised existing kanbn board in ${mainFolder}`);
|
|
83
95
|
} else {
|
|
@@ -92,6 +104,29 @@ async function initialise(options, initialised) {
|
|
|
92
104
|
module.exports = async args => {
|
|
93
105
|
let options = {};
|
|
94
106
|
|
|
107
|
+
// Work out which board we're initialising. Without --board this is the main board, i.e. the whole
|
|
108
|
+
// workspace, which is exactly what init has always done
|
|
109
|
+
let boardSlug = null;
|
|
110
|
+
if (args.board) {
|
|
111
|
+
boardSlug = utility.strArg(args.board);
|
|
112
|
+
|
|
113
|
+
// A secondary board lives beside the main board and shares its tasks, so the workspace has to
|
|
114
|
+
// exist first
|
|
115
|
+
if (!await workspace.initialised()) {
|
|
116
|
+
utility.error('Kanbn has not been initialised in this folder\nTry running: {b}kanbn init{b}');
|
|
117
|
+
return;
|
|
118
|
+
}
|
|
119
|
+
try {
|
|
120
|
+
boardSlug = await workspace.validateBoardSlug(boardSlug);
|
|
121
|
+
} catch (error) {
|
|
122
|
+
utility.error(error);
|
|
123
|
+
return;
|
|
124
|
+
}
|
|
125
|
+
kanbn = workspace.board(boardSlug);
|
|
126
|
+
} else {
|
|
127
|
+
kanbn = workspace;
|
|
128
|
+
}
|
|
129
|
+
|
|
95
130
|
// If this folder is already initialised, set the default name and description using the current values
|
|
96
131
|
const initialised = await kanbn.initialised();
|
|
97
132
|
if (initialised) {
|
|
@@ -129,7 +164,7 @@ module.exports = async args => {
|
|
|
129
164
|
if ('columns' in answers) {
|
|
130
165
|
answers.columns = answers.columns.map(column => column.columnName);
|
|
131
166
|
}
|
|
132
|
-
await initialise(answers, initialised);
|
|
167
|
+
await initialise(answers, initialised, boardSlug);
|
|
133
168
|
})
|
|
134
169
|
.catch(error => {
|
|
135
170
|
utility.error(error);
|
|
@@ -137,6 +172,6 @@ module.exports = async args => {
|
|
|
137
172
|
|
|
138
173
|
// Non-interactive initialisation
|
|
139
174
|
} else {
|
|
140
|
-
await initialise(options, initialised);
|
|
175
|
+
await initialise(options, initialised, boardSlug);
|
|
141
176
|
}
|
|
142
177
|
};
|
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,9 +45,9 @@ 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}"`);
|
|
50
53
|
})
|
|
@@ -53,13 +56,35 @@ function moveTask(taskId, columnName, position = null, relative = false) {
|
|
|
53
56
|
});
|
|
54
57
|
}
|
|
55
58
|
|
|
59
|
+
/**
|
|
60
|
+
* Add a task to the board it's being moved on, because it isn't on that board yet
|
|
61
|
+
* @param {string} taskId
|
|
62
|
+
* @param {string} columnName
|
|
63
|
+
* @param {string} boardSlug
|
|
64
|
+
* @param {boolean} verbose
|
|
65
|
+
*/
|
|
66
|
+
async function addTaskToBoard(taskId, columnName, boardSlug, verbose) {
|
|
67
|
+
try {
|
|
68
|
+
await kanbn.addTaskToBoard(taskId, columnName);
|
|
69
|
+
if (verbose) {
|
|
70
|
+
console.log(utility.replaceTags(
|
|
71
|
+
`{d}Task "${taskId}" wasn't on board "${boardSlug}" - it has been added{d}`
|
|
72
|
+
));
|
|
73
|
+
}
|
|
74
|
+
console.log(`Moved task "${taskId}" to column "${columnName}"`);
|
|
75
|
+
} catch (error) {
|
|
76
|
+
utility.error(error);
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
56
80
|
module.exports = async args => {
|
|
57
81
|
|
|
58
|
-
//
|
|
59
|
-
|
|
60
|
-
|
|
82
|
+
// Work out which board this command targets
|
|
83
|
+
const scoped = await utility.resolveBoard(workspace, args);
|
|
84
|
+
if (scoped === null) {
|
|
61
85
|
return;
|
|
62
86
|
}
|
|
87
|
+
kanbn = scoped;
|
|
63
88
|
|
|
64
89
|
// Get the task that we're moving
|
|
65
90
|
const taskId = args._[1];
|
|
@@ -68,14 +93,6 @@ module.exports = async args => {
|
|
|
68
93
|
return;
|
|
69
94
|
}
|
|
70
95
|
|
|
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
96
|
// Get the index and make sure it has some columns
|
|
80
97
|
let index;
|
|
81
98
|
try {
|
|
@@ -90,6 +107,43 @@ module.exports = async args => {
|
|
|
90
107
|
return;
|
|
91
108
|
}
|
|
92
109
|
|
|
110
|
+
// Moving a task on a board it isn't on yet adds it there, so that pulling work onto a board is one
|
|
111
|
+
// command rather than two. --no-add turns that back into an error. The main board is unaffected:
|
|
112
|
+
// moving an untracked task there has always been an error and still is
|
|
113
|
+
const boardSlug = await kanbn.resolveBoardSlug();
|
|
114
|
+
const onThisBoard = boardSlug in await workspace.findTaskBoards(taskId);
|
|
115
|
+
if (!onThisBoard && !(await kanbn.isMainBoard())) {
|
|
116
|
+
if (!(await kanbn.taskFileExists(taskId))) {
|
|
117
|
+
utility.error(`No task file found with id "${taskId}"`);
|
|
118
|
+
return;
|
|
119
|
+
}
|
|
120
|
+
if (!args.add) {
|
|
121
|
+
utility.error(
|
|
122
|
+
`Task "${taskId}" is not on board "${boardSlug}"\n` +
|
|
123
|
+
'Drop {b}--no-add{b} to add it to this board while moving it'
|
|
124
|
+
);
|
|
125
|
+
return;
|
|
126
|
+
}
|
|
127
|
+
let targetColumn = columnNames[0];
|
|
128
|
+
if (args.column) {
|
|
129
|
+
targetColumn = utility.strArg(args.column);
|
|
130
|
+
if (columnNames.indexOf(targetColumn) === -1) {
|
|
131
|
+
utility.error(`Column "${targetColumn}" doesn't exist`);
|
|
132
|
+
return;
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
await addTaskToBoard(taskId, targetColumn, boardSlug, kanbn.isVerbose(index));
|
|
136
|
+
return;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
// Make sure the task exists
|
|
140
|
+
try {
|
|
141
|
+
await kanbn.taskExists(taskId);
|
|
142
|
+
} catch (error) {
|
|
143
|
+
utility.error(error);
|
|
144
|
+
return;
|
|
145
|
+
}
|
|
146
|
+
|
|
93
147
|
// Get column name if specified
|
|
94
148
|
const currentColumnName = await kanbn.findTaskColumn(taskId);
|
|
95
149
|
let columnName = currentColumnName;
|
|
@@ -138,6 +192,6 @@ module.exports = async args => {
|
|
|
138
192
|
|
|
139
193
|
// Otherwise move task non-interactively
|
|
140
194
|
} else {
|
|
141
|
-
moveTask(taskId, columnName, newPosition, args.relative);
|
|
195
|
+
moveTask(taskId, columnName, newPosition, args.relative, args.add);
|
|
142
196
|
}
|
|
143
197
|
};
|
package/src/controller/remove.js
CHANGED
|
@@ -1,30 +1,47 @@
|
|
|
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
|
* Remove a task
|
|
7
10
|
* @param {string} taskId
|
|
8
|
-
* @param {boolean} removeFile
|
|
11
|
+
* @param {boolean} removeFile True if the task file should be deleted as well as the index entry
|
|
12
|
+
* @param {boolean} allBoards Remove the task from every board that references it
|
|
13
|
+
* @param {string} boardSlug The board the task is being removed from
|
|
14
|
+
* @param {boolean} secondaryBoard True if that board isn't the main board
|
|
9
15
|
*/
|
|
10
|
-
function removeTask(taskId, removeFile) {
|
|
16
|
+
function removeTask(taskId, removeFile, allBoards, boardSlug, secondaryBoard) {
|
|
11
17
|
kanbn
|
|
12
|
-
.deleteTask(taskId, removeFile)
|
|
18
|
+
.deleteTask(taskId, removeFile, allBoards)
|
|
13
19
|
.then(taskId => {
|
|
14
|
-
console.log(
|
|
20
|
+
console.log(
|
|
21
|
+
`Removed task "${taskId}"${removeFile ? ' file and index entry' : ' from the index'}` +
|
|
22
|
+
(secondaryBoard && !allBoards ? ` on board "${boardSlug}"` : '')
|
|
23
|
+
);
|
|
15
24
|
})
|
|
16
25
|
.catch(error => {
|
|
17
|
-
|
|
26
|
+
|
|
27
|
+
// Only a task that's on other boards can be removed from all of them, or from this one alone
|
|
28
|
+
utility.error(utility.replaceTags(
|
|
29
|
+
/other board/.test(error.message)
|
|
30
|
+
? `${error.message}\nUse {b}--all-boards{b} to remove it from all of them, or {b}--index{b} ` +
|
|
31
|
+
'to remove it from this board only'
|
|
32
|
+
: error.message
|
|
33
|
+
));
|
|
18
34
|
});
|
|
19
35
|
}
|
|
20
36
|
|
|
21
37
|
module.exports = async args => {
|
|
22
38
|
|
|
23
|
-
//
|
|
24
|
-
|
|
25
|
-
|
|
39
|
+
// Work out which board this command targets
|
|
40
|
+
const scoped = await utility.resolveBoard(workspace, args);
|
|
41
|
+
if (scoped === null) {
|
|
26
42
|
return;
|
|
27
43
|
}
|
|
44
|
+
kanbn = scoped;
|
|
28
45
|
|
|
29
46
|
// Get the task that we're removing
|
|
30
47
|
const taskId = args._[1];
|
|
@@ -50,9 +67,15 @@ module.exports = async args => {
|
|
|
50
67
|
return;
|
|
51
68
|
}
|
|
52
69
|
|
|
70
|
+
// A task file is shared between boards, so deleting it while another board still references it
|
|
71
|
+
// needs --all-boards. Removing the task from this board's index alone is always safe
|
|
72
|
+
const allBoards = !!args['all-boards'];
|
|
73
|
+
const boardSlug = await kanbn.resolveBoardSlug();
|
|
74
|
+
const secondaryBoard = !(await kanbn.isMainBoard());
|
|
75
|
+
|
|
53
76
|
// If the force flag is specified, remove the task without asking
|
|
54
77
|
if (args.force) {
|
|
55
|
-
removeTask(taskId, args.index);
|
|
78
|
+
removeTask(taskId, !args.index, allBoards, boardSlug, secondaryBoard);
|
|
56
79
|
|
|
57
80
|
// Otherwise, prompt for confirmation first
|
|
58
81
|
} else {
|
|
@@ -65,7 +88,7 @@ module.exports = async args => {
|
|
|
65
88
|
}
|
|
66
89
|
]).then(async answers => {
|
|
67
90
|
if (answers.sure) {
|
|
68
|
-
removeTask(taskId, args.index);
|
|
91
|
+
removeTask(taskId, !args.index, allBoards, boardSlug, secondaryBoard);
|
|
69
92
|
}
|
|
70
93
|
}).catch(error => {
|
|
71
94
|
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,29 @@
|
|
|
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
|
+
|
|
18
|
+
// A task is restored to every board it was on when it was archived. A board that has since been
|
|
19
|
+
// deleted is skipped rather than failing the restore, but the user should hear about it
|
|
20
|
+
const missing = kanbn.lastRestoreWarnings || [];
|
|
21
|
+
if (missing.length) {
|
|
22
|
+
console.log(
|
|
23
|
+
`Couldn't restore "${taskId}" to ${missing.length === 1 ? 'board' : 'boards'} ` +
|
|
24
|
+
`${missing.map(slug => `"${slug}"`).join(', ')} - no longer ${missing.length === 1 ? 'exists' : 'exist'}`
|
|
25
|
+
);
|
|
26
|
+
}
|
|
14
27
|
})
|
|
15
28
|
.catch(error => {
|
|
16
29
|
utility.error(error);
|
|
@@ -19,11 +32,12 @@ function restoreTask(taskId, columnName) {
|
|
|
19
32
|
|
|
20
33
|
module.exports = async args => {
|
|
21
34
|
|
|
22
|
-
//
|
|
23
|
-
|
|
24
|
-
|
|
35
|
+
// Work out which board this command targets
|
|
36
|
+
const scoped = await utility.resolveBoard(workspace, args);
|
|
37
|
+
if (scoped === null) {
|
|
25
38
|
return;
|
|
26
39
|
}
|
|
40
|
+
kanbn = scoped;
|
|
27
41
|
|
|
28
42
|
// Get the task that we're archiving
|
|
29
43
|
const taskId = args._[1];
|
|
@@ -56,6 +70,7 @@ module.exports = async args => {
|
|
|
56
70
|
}
|
|
57
71
|
}
|
|
58
72
|
|
|
59
|
-
//
|
|
60
|
-
|
|
73
|
+
// Restore the task. Without --board it goes back to every board it was on when it was archived;
|
|
74
|
+
// with --board it goes only to that one
|
|
75
|
+
restoreTask(taskId, columnName, !!args.board);
|
|
61
76
|
};
|
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 = [
|
|
@@ -275,6 +278,13 @@ function sortColumn(columnName, sorters, save) {
|
|
|
275
278
|
|
|
276
279
|
module.exports = async (args, argv) => {
|
|
277
280
|
|
|
281
|
+
// Work out which board this command targets
|
|
282
|
+
const scoped = await utility.resolveBoard(workspace, args);
|
|
283
|
+
if (scoped === null) {
|
|
284
|
+
return;
|
|
285
|
+
}
|
|
286
|
+
kanbn = scoped;
|
|
287
|
+
|
|
278
288
|
// Sortable fields and aliases
|
|
279
289
|
const sortOptions = Object.fromEntries(utility.zip(
|
|
280
290
|
sorterFields.map(sorterField => sorterField.options).flat(),
|
|
@@ -296,8 +306,14 @@ module.exports = async (args, argv) => {
|
|
|
296
306
|
'--save'
|
|
297
307
|
];
|
|
298
308
|
|
|
299
|
-
// Get the column that we're sorting
|
|
300
|
-
|
|
309
|
+
// Get the column that we're sorting. --board is consumed above, so drop it and its value before
|
|
310
|
+
// the sorter walk below, which would otherwise read the board slug as a sorter filter
|
|
311
|
+
argv = argv.slice(3).filter((arg, i, all) => {
|
|
312
|
+
if (arg === '--board' || arg === '-b') {
|
|
313
|
+
return false;
|
|
314
|
+
}
|
|
315
|
+
return !(i > 0 && (all[i - 1] === '--board' || all[i - 1] === '-b'));
|
|
316
|
+
});
|
|
301
317
|
const columnName = args._.length > 1 ? args._[1] : null;
|
|
302
318
|
|
|
303
319
|
// 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;
|
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
|
|
|
@@ -48,6 +61,6 @@ module.exports = async (args) => {
|
|
|
48
61
|
return;
|
|
49
62
|
}
|
|
50
63
|
|
|
51
|
-
// Show the task
|
|
52
|
-
showTask(taskId, args.json);
|
|
64
|
+
// Show the task, along with the boards it appears on
|
|
65
|
+
showTask(kanbn, taskId, await workspace.findTaskBoards(taskId), args.json);
|
|
53
66
|
};
|