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