@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.
Files changed (100) hide show
  1. package/README.md +3 -1
  2. package/coverage/tmp/coverage-916017-1788028598821-0.json +1 -0
  3. package/coverage/tmp/{coverage-214293-1787777184569-0.json → coverage-916018-1788028597179-0.json} +1 -1
  4. package/coverage/tmp/coverage-916036-1788028598796-0.json +1 -0
  5. package/docs/actions.md +337 -0
  6. package/docs/advanced-configuration.md +55 -0
  7. package/docs/commands/add.txt +17 -1
  8. package/docs/commands/archive.txt +11 -0
  9. package/docs/commands/board.txt +9 -0
  10. package/docs/commands/boards.txt +34 -0
  11. package/docs/commands/burndown.txt +7 -0
  12. package/docs/commands/comment.txt +13 -1
  13. package/docs/commands/contributors.txt +58 -0
  14. package/docs/commands/edit.txt +18 -1
  15. package/docs/commands/find.txt +39 -1
  16. package/docs/commands/gantt.txt +6 -0
  17. package/docs/commands/help.txt +2 -0
  18. package/docs/commands/history.txt +6 -0
  19. package/docs/commands/init.txt +13 -0
  20. package/docs/commands/move.txt +26 -0
  21. package/docs/commands/remove.txt +22 -1
  22. package/docs/commands/rename.txt +5 -0
  23. package/docs/commands/restore.txt +12 -0
  24. package/docs/commands/sort.txt +23 -0
  25. package/docs/commands/sprint.txt +9 -0
  26. package/docs/commands/status.txt +10 -1
  27. package/docs/commands/task.txt +9 -0
  28. package/docs/commands/validate.txt +29 -1
  29. package/docs/contributors.md +145 -0
  30. package/docs/filtering-and-sorting.md +60 -3
  31. package/docs/index-structure.md +145 -11
  32. package/docs/index.md +6 -2
  33. package/docs/multiple-boards.md +259 -0
  34. package/docs/quick-start.md +21 -1
  35. package/docs/task-structure.md +31 -3
  36. package/example/README.md +23 -0
  37. package/example/advanced/kanbn.yml +65 -0
  38. package/example/boards/.kanbn/design.md +31 -0
  39. package/example/boards/.kanbn/index.md +44 -0
  40. package/example/boards/.kanbn/tasks/add-usage-alert-emails.md +19 -0
  41. package/example/boards/.kanbn/tasks/build-tenant-settings-page.md +43 -0
  42. package/example/boards/.kanbn/tasks/create-organization-switcher.md +44 -0
  43. package/example/boards/.kanbn/tasks/design-onboarding-checklist.md +22 -0
  44. package/example/boards/.kanbn/tasks/refresh-marketing-site.md +28 -0
  45. package/example/boards/.kanbn/tasks/ship-billing-portal.md +29 -0
  46. package/package.json +9 -7
  47. package/routes/add.json +40 -12
  48. package/routes/archive.json +14 -2
  49. package/routes/board.json +11 -3
  50. package/routes/boards.json +30 -0
  51. package/routes/burndown.json +23 -7
  52. package/routes/comment.json +19 -5
  53. package/routes/contributors.json +18 -0
  54. package/routes/edit.json +37 -11
  55. package/routes/find.json +37 -12
  56. package/routes/gantt.json +20 -6
  57. package/routes/history.json +39 -25
  58. package/routes/init.json +3 -1
  59. package/routes/move.json +24 -6
  60. package/routes/remove.json +18 -3
  61. package/routes/rename.json +11 -3
  62. package/routes/restore.json +14 -2
  63. package/routes/sort.json +40 -11
  64. package/routes/sprint.json +14 -4
  65. package/routes/status.json +23 -7
  66. package/routes/task.json +10 -2
  67. package/routes/validate.json +18 -5
  68. package/skills/kanbn-plan/SKILL.md +10 -1
  69. package/skills/kanbn-replan/SKILL.md +6 -1
  70. package/src/actions.js +904 -0
  71. package/src/board.js +25 -2
  72. package/src/controller/add.js +72 -55
  73. package/src/controller/archive.js +9 -4
  74. package/src/controller/board.js +21 -13
  75. package/src/controller/boards.js +140 -0
  76. package/src/controller/burndown.js +14 -7
  77. package/src/controller/comment.js +13 -6
  78. package/src/controller/contributors.js +166 -0
  79. package/src/controller/edit.js +48 -18
  80. package/src/controller/find.js +87 -13
  81. package/src/controller/gantt.js +13 -6
  82. package/src/controller/history.js +13 -6
  83. package/src/controller/init.js +39 -4
  84. package/src/controller/move.js +153 -25
  85. package/src/controller/remove.js +73 -13
  86. package/src/controller/rename.js +8 -4
  87. package/src/controller/restore.js +24 -8
  88. package/src/controller/sort.js +59 -3
  89. package/src/controller/sprint.js +31 -7
  90. package/src/controller/status.js +8 -4
  91. package/src/controller/task.js +34 -10
  92. package/src/controller/validate.js +197 -7
  93. package/src/git-user-name.js +9 -0
  94. package/src/git-user.js +55 -0
  95. package/src/main.d.ts +387 -9
  96. package/src/main.js +2757 -113
  97. package/src/parse-index.js +219 -17
  98. package/src/parse-task.js +16 -0
  99. package/src/utility.js +274 -0
  100. package/coverage/tmp/coverage-214292-1787777191526-0.json +0 -1
@@ -1,7 +1,10 @@
1
- const kanbn = require('../main');
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
- // Make sure kanbn has been initialised
59
- if (!await kanbn.initialised()) {
60
- utility.error('Kanbn has not been initialised in this folder\nTry running: {b}kanbn init{b}');
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
  };
@@ -1,30 +1,76 @@
1
- const kanbn = require('../main');
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(`Removed task "${taskId}"${removeFile ? ' from the index' : ' file and index entry'}`);
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
- utility.error(error);
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
- // Make sure kanbn has been initialised
24
- if (!await kanbn.initialised()) {
25
- utility.error('Kanbn has not been initialised in this folder\nTry running: {b}kanbn init{b}');
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
- utility.error(error);
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);
@@ -1,7 +1,10 @@
1
- const kanbn = require('../main');
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
- // Make sure kanbn has been initialised
55
- if (!await kanbn.initialised()) {
56
- utility.error('Kanbn has not been initialised in this folder\nTry running: {b}kanbn init{b}');
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 kanbn = require('../main');
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
- // Make sure kanbn has been initialised
23
- if (!await kanbn.initialised()) {
24
- utility.error('Kanbn has not been initialised in this folder\nTry running: {b}kanbn init{b}');
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
- // Archive task
60
- restoreTask(taskId, columnName);
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
  };
@@ -1,7 +1,10 @@
1
- const kanbn = require('../main');
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
- argv = argv.slice(3);
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
@@ -1,7 +1,10 @@
1
- const kanbn = require('../main');
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
- // Make sure kanbn has been initialised
60
- if (!await kanbn.initialised()) {
61
- utility.error('Kanbn has not been initialised in this folder\nTry running: {b}kanbn init{b}');
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
  };
@@ -1,15 +1,19 @@
1
- const kanbn = require('../main');
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
- // Make sure kanbn has been initialised
9
- if (!await kanbn.initialised()) {
10
- utility.error('Kanbn has not been initialised in this folder\nTry running: {b}kanbn init{b}');
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;