@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
package/src/board.js CHANGED
@@ -55,6 +55,23 @@ module.exports = (() => {
55
55
  }
56
56
  }
57
57
 
58
+ /**
59
+ * Render the simple tasks in a column - lines in the board file that aren't task links. They have
60
+ * no fields to interpolate and take no part in filtering or sorting, so they're shown dimmed at
61
+ * the end of the column, and only in the default layout where the columns on screen are the
62
+ * columns in the file
63
+ * @param {object} index
64
+ * @param {string} columnName
65
+ * @return {string[]} The rendered simple tasks
66
+ */
67
+ function getSimpleTaskStrings(index, columnName) {
68
+ return ((index.columnContent || {})[columnName] || [])
69
+ .filter(entry => !entry.block && !!entry.text && entry.text.indexOf('\n') === -1)
70
+
71
+ // A caret starts a markup sequence in terminal-kit, so any in the user's own text are escaped
72
+ .map(entry => `^-${entry.text.replace(/\^/g, '^^')}^:`);
73
+ }
74
+
58
75
  /**
59
76
  * Get a column heading with icons
60
77
  * @param {object} index
@@ -166,7 +183,13 @@ module.exports = (() => {
166
183
  table.push(board.headings.map(heading => heading.heading));
167
184
  board.lanes.forEach(lane => table.push(
168
185
  [lane.name],
169
- lane.columns.map(column => column.map(task => getTaskString(index, task)).join(TASK_SEPARATOR))
186
+ lane.columns.map((column, i) => {
187
+ const cell = column.map(task => getTaskString(index, task));
188
+ if (view === null) {
189
+ cell.push(...getSimpleTaskStrings(index, board.headings[i].name));
190
+ }
191
+ return cell.join(TASK_SEPARATOR);
192
+ })
170
193
  ));
171
194
 
172
195
  // Display as a table
@@ -178,7 +201,7 @@ module.exports = (() => {
178
201
  borderChars: 'lightRounded',
179
202
  borderAttr: { color: 'grey' },
180
203
  textAttr: { color: 'white', bgColor: 'default' },
181
- width: term.width,
204
+ width: utility.terminalWidth(term.width),
182
205
  fit: true
183
206
  }
184
207
  );
@@ -1,9 +1,12 @@
1
- const kanbn = require('../main');
1
+ const workspace = require('../main');
2
2
  const utility = require('../utility');
3
3
  const inquirer = require('inquirer');
4
4
  const fuzzy = require('fuzzy');
5
5
  const chrono = require('chrono-node');
6
- const getGitUsername = require('git-user-name');
6
+
7
+ // The boards this command targets - set from --board before anything else runs
8
+ let kanbn = workspace;
9
+ let boardTargets = [];
7
10
 
8
11
  inquirer.registerPrompt('datepicker', require('inquirer-datepicker'));
9
12
  inquirer.registerPrompt('recursive', require('inquirer-recursive'));
@@ -15,9 +18,11 @@ inquirer.registerPrompt('autocomplete', require('inquirer-autocomplete-prompt'))
15
18
  * @param {string[]} taskIds
16
19
  * @param {string} columnName
17
20
  * @param {string[]} columnNames
21
+ * @param {?string} currentUser
22
+ * @param {object[]} contributors
18
23
  * @return {Promise<any>}
19
24
  */
20
- async function interactiveCreateTask(taskData, taskIds, columnName, columnNames) {
25
+ async function interactiveCreateTask(taskData, taskIds, columnName, columnNames, currentUser, contributors) {
21
26
  const dueDateExists = (
22
27
  'metadata' in taskData &&
23
28
  'due' in taskData.metadata &&
@@ -123,13 +128,12 @@ async function interactiveCreateTask(taskData, taskIds, columnName, columnNames)
123
128
  default: false,
124
129
  when: answers => !assignedExists
125
130
  },
126
- {
127
- type: 'input',
131
+ utility.assignedPrompt({
128
132
  name: 'assigned',
129
133
  message: 'Assigned to:',
130
- default: assignedExists ? taskData.metadata.assigned : getGitUsername(),
134
+ default: assignedExists ? taskData.metadata.assigned : (currentUser || ''),
131
135
  when: answers => answers.setAssigned || assignedExists
132
- },
136
+ }, contributors),
133
137
  {
134
138
  type: 'recursive',
135
139
  name: 'subTasks',
@@ -235,15 +239,26 @@ async function interactiveAddUntrackedTasks(untrackedTasks, columnName, columnNa
235
239
  * @param {object} taskData
236
240
  * @param {string} columnName
237
241
  */
238
- function createTask(taskData, columnName) {
239
- kanbn
240
- .createTask(taskData, columnName)
241
- .then(taskId => {
242
- console.log(`Created task "${taskId}" in column "${columnName}"`);
243
- })
244
- .catch(error => {
242
+ async function createTask(taskData, columnName) {
243
+ try {
244
+
245
+ // The task is created once, on the first target board, and then added to the others - one task
246
+ // file, referenced by however many boards were named
247
+ const [first, ...rest] = boardTargets;
248
+ const taskId = await first.kanbn.createTask(taskData, columnName);
249
+ console.log(
250
+ `Created task "${taskId}" in column "${columnName}"` +
251
+ (boardTargets.length > 1 || !first.main ? ` on board "${first.slug}"` : '')
252
+ );
253
+ utility.showActionWarnings(first.kanbn);
254
+ for (const target of rest) {
255
+ await target.kanbn.addTaskToBoard(taskId, target.columnName);
256
+ console.log(`Added task "${taskId}" to column "${target.columnName}" on board "${target.slug}"`);
257
+ utility.showActionWarnings(target.kanbn);
258
+ }
259
+ } catch (error) {
245
260
  utility.error(error);
246
- });
261
+ }
247
262
  }
248
263
 
249
264
  /**
@@ -252,50 +267,44 @@ function createTask(taskData, columnName) {
252
267
  * @param {string} columnName
253
268
  */
254
269
  async function addUntrackedTasks(untrackedTasks, columnName) {
255
- for (let untrackedTask of untrackedTasks) {
256
- try {
257
- await kanbn.addUntrackedTaskToIndex(untrackedTask, columnName);
258
- } catch (error) {
259
- utility.error(error);
260
- return;
270
+ for (const target of boardTargets) {
271
+
272
+ // The column chosen interactively or on the command line applies to the first board; the others
273
+ // use whatever resolveBoardTargets() paired them with
274
+ const targetColumn = target === boardTargets[0] ? columnName : target.columnName;
275
+ for (let untrackedTask of untrackedTasks) {
276
+ try {
277
+ await target.kanbn.addUntrackedTaskToIndex(untrackedTask, targetColumn);
278
+ } catch (error) {
279
+ utility.error(error);
280
+ return;
281
+ }
261
282
  }
283
+ console.log(
284
+ `Added ${untrackedTasks.length} task${untrackedTasks.length !== 1 ? 's' : ''} ` +
285
+ `to column "${targetColumn}"` + (boardTargets.length > 1 || !target.main ? ` on board "${target.slug}"` : '')
286
+ );
262
287
  }
263
- console.log(
264
- `Added ${untrackedTasks.length} task${untrackedTasks.length !== 1 ? 's' : ''} to column "${columnName}"`
265
- );
266
288
  }
267
289
 
268
290
  module.exports = async args => {
269
291
 
270
- // Make sure kanbn has been initialised
271
- if (!await kanbn.initialised()) {
272
- utility.error('Kanbn has not been initialised in this folder\nTry running: {b}kanbn init{b}');
292
+ // Work out which boards this command targets, and which column to use on each. -b is repeatable,
293
+ // so one task can be created and placed on several boards at once
294
+ const targets = await utility.resolveBoardTargets(workspace, args);
295
+ if (targets === null) {
273
296
  return;
274
297
  }
275
-
276
- // Get the index and make sure it has some columns
277
- let index;
278
- try {
279
- index = await kanbn.getIndex();
280
- } catch (error) {
281
- utility.error(error);
282
- return;
283
- }
284
- const columnNames = Object.keys(index.columns);
285
- if (!columnNames.length) {
286
- utility.error('No columns defined in the index\nTry running {b}kanbn init -c "column name"{b}');
287
- return;
298
+ boardTargets = targets;
299
+ kanbn = targets[0].kanbn;
300
+ for (const target of boardTargets) {
301
+ target.main = await target.kanbn.isMainBoard();
288
302
  }
303
+ utility.showColumnFallbackNotices(boardTargets, kanbn.isVerbose(targets[0].index));
289
304
 
290
- // Get column name if specified, otherwise default to the first available column
291
- let columnName = columnNames[0];
292
- if (args.column) {
293
- columnName = utility.strArg(args.column);
294
- if (columnNames.indexOf(columnName) === -1) {
295
- utility.error(`Column "${columnName}" doesn't exist`);
296
- return;
297
- }
298
- }
305
+ const index = targets[0].index;
306
+ const columnNames = Object.keys(index.columns);
307
+ const columnName = targets[0].columnName;
299
308
 
300
309
  // Add untracked file(s)
301
310
  if (args.untracked) {
@@ -393,10 +402,12 @@ module.exports = async args => {
393
402
 
394
403
  // Assigned
395
404
  if (args.assigned) {
396
- const gitUsername = getGitUsername();
397
405
  if (args.assigned === true) {
398
- if (gitUsername) {
399
- taskData.metadata.assigned = gitUsername;
406
+
407
+ // A bare --assigned means "me", canonicalised against the contributors list if there is one
408
+ const currentUser = await kanbn.currentUser();
409
+ if (currentUser) {
410
+ taskData.metadata.assigned = currentUser;
400
411
  }
401
412
  } else {
402
413
  taskData.metadata.assigned = utility.strArg(args.assigned);
@@ -501,7 +512,14 @@ module.exports = async args => {
501
512
 
502
513
  // Create task interactively
503
514
  if (args.interactive) {
504
- interactiveCreateTask(taskData, taskIds, columnName, columnNames)
515
+ interactiveCreateTask(
516
+ taskData,
517
+ taskIds,
518
+ columnName,
519
+ columnNames,
520
+ await kanbn.currentUser(),
521
+ await kanbn.getContributors()
522
+ )
505
523
  .then(answers => {
506
524
  taskData.name = answers.name;
507
525
  if ('description' in answers) {
@@ -534,8 +552,7 @@ module.exports = async args => {
534
552
  type: relation.type
535
553
  }));
536
554
  }
537
- columnName = answers.column;
538
- createTask(taskData, columnName);
555
+ createTask(taskData, answers.column);
539
556
  })
540
557
  .catch(error => {
541
558
  utility.error(error);
@@ -1,6 +1,9 @@
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
  * Archive a task
6
9
  * @param {string} taskId
@@ -10,6 +13,7 @@ function archiveTask(taskId) {
10
13
  .archiveTask(taskId)
11
14
  .then(taskId => {
12
15
  console.log(`Archived task "${taskId}"`);
16
+ utility.showActionWarnings(kanbn);
13
17
  })
14
18
  .catch(error => {
15
19
  utility.error(error);
@@ -32,11 +36,12 @@ function listArchivedTasks() {
32
36
 
33
37
  module.exports = async args => {
34
38
 
35
- // Make sure kanbn has been initialised
36
- if (!await kanbn.initialised()) {
37
- 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) {
38
42
  return;
39
43
  }
44
+ kanbn = scoped;
40
45
 
41
46
  // Check if we're listing archived tasks
42
47
  if (args.list) {
@@ -1,12 +1,12 @@
1
- const kanbn = require('../main');
1
+ const workspace = require('../main');
2
2
  const utility = require('../utility');
3
3
  const board = require('../board');
4
4
 
5
5
  module.exports = async args => {
6
6
 
7
- // Make sure kanbn has been initialised
8
- if (!await kanbn.initialised()) {
9
- utility.error('Kanbn has not been initialised in this folder\nTry running: {b}kanbn init{b}');
7
+ // Work out which board we're showing
8
+ const kanbn = await utility.resolveBoard(workspace, args);
9
+ if (kanbn === null) {
10
10
  return;
11
11
  }
12
12
 
@@ -24,19 +24,27 @@ module.exports = async args => {
24
24
  return;
25
25
  }
26
26
 
27
- // Load and hydrate all tracked tasks
28
- const tasks = (await kanbn.loadAllTrackedTasks(index)).map(
29
- task => kanbn.hydrateTask(index, task)
30
- );
27
+ // Load and hydrate all tracked tasks. A task the board references but which has no task file is
28
+ // skipped rather than fatal, and reported by the hint below
29
+ let tasks, missingTaskFiles;
30
+ try {
31
+ tasks = (await kanbn.loadAllTrackedTasks(index)).map(
32
+ task => kanbn.hydrateTask(index, task)
33
+ );
34
+ missingTaskFiles = await kanbn.findMissingTaskFiles(index);
35
+ } catch (error) {
36
+ utility.error(error);
37
+ return;
38
+ }
31
39
 
32
40
  // Show the board
33
41
  board
34
42
  .show(index, tasks, args.view, args.json)
35
- .then(() => {
36
- utility.showDateDriftHint(
37
- kanbn.getDateDrift(index, tasks),
38
- !args.json && kanbn.isVerbose(index)
39
- );
43
+ .then(async () => {
44
+ const verbose = !args.json && kanbn.isVerbose(index);
45
+ utility.showMissingTaskFilesHint(missingTaskFiles, verbose);
46
+ utility.showDateDriftHint(kanbn.getDateDrift(index, tasks), verbose);
47
+ utility.showBoardCountHint(await workspace.listBoards(), verbose);
40
48
  })
41
49
  .catch(error => {
42
50
  utility.error(error);
@@ -0,0 +1,140 @@
1
+ const kanbn = require('../main');
2
+ const utility = require('../utility');
3
+
4
+ /**
5
+ * Show the list of boards in this workspace
6
+ * @param {object[]} boards
7
+ * @param {boolean} json
8
+ */
9
+ function showBoards(boards, json) {
10
+ if (json) {
11
+ console.log(JSON.stringify(boards, null, 2));
12
+ return;
13
+ }
14
+ console.log(`${boards.length} ${boards.length === 1 ? 'board' : 'boards'}:`);
15
+ for (const board of boards) {
16
+ console.log(utility.replaceTags(`\n{b}${board.slug}{b}${board.main ? ' (main board)' : ''}`));
17
+ console.log(` ${board.name}`);
18
+ if (board.description) {
19
+ console.log(` ${board.description.split('\n')[0]}`);
20
+ }
21
+ console.log(
22
+ ` ${board.columns} ${board.columns === 1 ? 'column' : 'columns'}, ` +
23
+ `${board.tasks} ${board.tasks === 1 ? 'task' : 'tasks'}, ` +
24
+ `${board.completedPercentage}% complete` +
25
+ (board.modified ? `, last modified ${board.modified.toISOString()}` : '')
26
+ );
27
+ }
28
+ }
29
+
30
+ /**
31
+ * Show which boards each task appears on
32
+ * @param {object[]} tasks
33
+ * @param {boolean} all True if single-board tasks are included
34
+ * @param {boolean} json
35
+ */
36
+ function showCrossBoardTasks(tasks, all, json) {
37
+ if (json) {
38
+ console.log(JSON.stringify(tasks, null, 2));
39
+ return;
40
+ }
41
+ if (!tasks.length) {
42
+ console.log(all ? 'No tracked tasks found' : 'No tasks appear on more than one board');
43
+ return;
44
+ }
45
+ console.log(
46
+ `${tasks.length} ${tasks.length === 1 ? 'task' : 'tasks'}` +
47
+ (all ? ':' : ' on more than one board:')
48
+ );
49
+ for (const task of tasks) {
50
+ console.log(utility.replaceTags(`\n{b}${task.id}{b}`));
51
+ for (const [slug, columnName] of Object.entries(task.boards)) {
52
+ console.log(` ${slug}: ${columnName}`);
53
+ }
54
+ }
55
+ }
56
+
57
+ module.exports = async args => {
58
+
59
+ // Make sure kanbn has been initialised
60
+ if (!await kanbn.workspaceInitialised()) {
61
+ utility.error('Kanbn has not been initialised in this folder\nTry running: {b}kanbn init{b}');
62
+ return;
63
+ }
64
+
65
+ // Delete a board
66
+ if (args.delete) {
67
+ const slug = utility.strArg(args.delete);
68
+ let orphaned;
69
+ try {
70
+ orphaned = await kanbn.findOrphanedTasks(slug);
71
+ } catch (error) {
72
+ utility.error(error);
73
+ return;
74
+ }
75
+
76
+ // Deleting a board never deletes task files, but it can leave tasks with no board at all
77
+ if (orphaned.length && !args.force) {
78
+ utility.error(
79
+ `Deleting board "${slug}" would leave ${orphaned.length} ` +
80
+ `${orphaned.length === 1 ? 'task' : 'tasks'} untracked:\n` +
81
+ orphaned.map(taskId => ` ${taskId}`).join('\n') +
82
+ '\nUse {b}-f{b} to delete the board anyway'
83
+ );
84
+ return;
85
+ }
86
+ try {
87
+ await kanbn.deleteBoard(slug);
88
+ } catch (error) {
89
+ utility.error(error);
90
+ return;
91
+ }
92
+ console.log(
93
+ `Deleted board "${slug}"` +
94
+ (orphaned.length
95
+ ? `\n${orphaned.length} ${orphaned.length === 1 ? 'task is' : 'tasks are'} now untracked:\n` +
96
+ orphaned.map(taskId => ` ${taskId}`).join('\n')
97
+ : '')
98
+ );
99
+ return;
100
+ }
101
+
102
+ // Rename a board, i.e. kanbn boards --rename design ux --name "UX Pipeline"
103
+ if (args.rename) {
104
+ const slug = utility.strArg(args.rename);
105
+ const newSlug = args._[1] || slug;
106
+ const newName = args.name ? utility.strArg(args.name) : null;
107
+ if (newSlug === slug && newName === null) {
108
+ utility.error('No new board slug or name specified\nTry running {b}kanbn boards --rename "board-slug" "new-slug"{b}');
109
+ return;
110
+ }
111
+ try {
112
+ const renamedSlug = await kanbn.renameBoard(slug, newSlug, newName);
113
+ console.log(
114
+ renamedSlug === slug
115
+ ? `Renamed board "${slug}" to "${newName}"`
116
+ : `Renamed board "${slug}" to "${renamedSlug}"`
117
+ );
118
+ } catch (error) {
119
+ utility.error(error);
120
+ }
121
+ return;
122
+ }
123
+
124
+ // Show which boards each task appears on
125
+ if (args.tasks) {
126
+ try {
127
+ showCrossBoardTasks(await kanbn.getCrossBoardTasks(args.all), args.all, args.json);
128
+ } catch (error) {
129
+ utility.error(error);
130
+ }
131
+ return;
132
+ }
133
+
134
+ // List boards
135
+ try {
136
+ showBoards(await kanbn.getBoardsSummary(), args.json);
137
+ } catch (error) {
138
+ utility.error(error);
139
+ }
140
+ };
@@ -1,10 +1,13 @@
1
- const kanbn = require('../main');
1
+ const workspace = require('../main');
2
2
  const utility = require('../utility');
3
3
  const asciichart = require('asciichart');
4
4
  const term = require('terminal-kit').terminal;
5
5
  const chrono = require('chrono-node');
6
6
  const formatDate = require('dateformat');
7
7
 
8
+ // The board this command targets - set from --board before anything else runs
9
+ let kanbn = workspace;
10
+
8
11
  const getLabelPlacements = (from, to, width, dateFormat, maxLabels) => {
9
12
  for (let count = maxLabels; count >= 1; count--) {
10
13
  const placements = [];
@@ -62,11 +65,12 @@ const renderXAxisLabels = (from, to, width, dateFormat, leftPadding) => {
62
65
 
63
66
  module.exports = async args => {
64
67
 
65
- // Make sure kanbn has been initialised
66
- if (!await kanbn.initialised()) {
67
- 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) {
68
71
  return;
69
72
  }
73
+ kanbn = scoped;
70
74
  const index = await kanbn.getIndex();
71
75
 
72
76
  // Get sprint numbers or names
@@ -94,10 +98,13 @@ module.exports = async args => {
94
98
  }
95
99
  }
96
100
 
97
- // Get assigned
101
+ // Get assigned, expanding "@me" to the current user
98
102
  let assigned = null;
99
103
  if (args.assigned) {
100
- assigned = utility.strArg(args.assigned);
104
+ assigned = await utility.expandCurrentUser(kanbn, utility.strArg(args.assigned));
105
+ if (assigned === null) {
106
+ return;
107
+ }
101
108
  }
102
109
 
103
110
  // Get columns
@@ -127,7 +134,7 @@ module.exports = async args => {
127
134
 
128
135
  // Render chart
129
136
  const PADDING = ' ';
130
- const width = Math.max(1, term.width - (PADDING.length + 1));
137
+ const width = Math.max(1, utility.terminalWidth(term.width) - (PADDING.length + 1));
131
138
 
132
139
  const plots = [];
133
140
  for (const s of data.series) {
@@ -1,7 +1,9 @@
1
- const kanbn = require('../main');
1
+ const workspace = require('../main');
2
2
  const utility = require('../utility');
3
3
  const inquirer = require('inquirer');
4
- const getGitUsername = require('git-user-name');
4
+
5
+ // The board this command targets - set from --board before anything else runs
6
+ let kanbn = workspace;
5
7
 
6
8
  /**
7
9
  * Add a comment interactively
@@ -43,6 +45,7 @@ function addComment(taskId, text, author) {
43
45
  .comment(taskId, text, author)
44
46
  .then(taskId => {
45
47
  console.log(`Added comment to task "${taskId}"`);
48
+ utility.showActionWarnings(kanbn);
46
49
  })
47
50
  .catch(error => {
48
51
  utility.error(error);
@@ -51,11 +54,12 @@ function addComment(taskId, text, author) {
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 add a comment to
61
65
  const taskId = args._[1];
@@ -84,7 +88,10 @@ module.exports = async args => {
84
88
  if (args.author && typeof args.author === 'string') {
85
89
  commentAuthor = utility.strArg(args.author);
86
90
  } else {
87
- commentAuthor = getGitUsername();
91
+
92
+ // Nobody at all is a legitimate answer on a machine with no git identity, and an empty author is
93
+ // what a comment without one looks like
94
+ commentAuthor = (await kanbn.currentUser()) || '';
88
95
  }
89
96
 
90
97
  // Add comment interactively