@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/routes/task.json
CHANGED
package/routes/validate.json
CHANGED
|
@@ -7,13 +7,26 @@
|
|
|
7
7
|
"boolean": [
|
|
8
8
|
"json",
|
|
9
9
|
"save",
|
|
10
|
-
"fix"
|
|
10
|
+
"fix",
|
|
11
|
+
"all-boards"
|
|
11
12
|
],
|
|
12
13
|
"alias": {
|
|
13
|
-
"json": [
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
14
|
+
"json": [
|
|
15
|
+
"j"
|
|
16
|
+
],
|
|
17
|
+
"save": [
|
|
18
|
+
"s"
|
|
19
|
+
],
|
|
20
|
+
"fix": [
|
|
21
|
+
"f"
|
|
22
|
+
],
|
|
23
|
+
"board": [
|
|
24
|
+
"b"
|
|
25
|
+
]
|
|
26
|
+
},
|
|
27
|
+
"string": [
|
|
28
|
+
"board"
|
|
29
|
+
]
|
|
17
30
|
},
|
|
18
31
|
"controller": "./src/controller/validate",
|
|
19
32
|
"help": "./docs/commands/validate.txt"
|
|
@@ -13,9 +13,18 @@ Do not use this skill to implement the project, edit source code, scaffold files
|
|
|
13
13
|
|
|
14
14
|
Your output must stay inside the Kanbn planning surface:
|
|
15
15
|
|
|
16
|
-
- `.kanbn/index.md`
|
|
16
|
+
- `.kanbn/index.md` — the main board
|
|
17
|
+
- `.kanbn/{board-slug}.md` — any secondary boards, in exactly the same format as the index
|
|
17
18
|
- `.kanbn/tasks/*.md`
|
|
18
19
|
|
|
20
|
+
A workspace may hold several boards over one shared pool of tasks. Every board file is a sibling of
|
|
21
|
+
`index.md` inside `.kanbn/`, and boards own membership: a task can be referenced by several boards,
|
|
22
|
+
in a different column on each. Task files never record which boards they belong to.
|
|
23
|
+
|
|
24
|
+
Unless the user asks for more than one board, plan onto `.kanbn/index.md` alone. When they do want
|
|
25
|
+
several, write each board file yourself in the same format, and make sure any task referenced by a
|
|
26
|
+
board has a file in `.kanbn/tasks/`.
|
|
27
|
+
|
|
19
28
|
Do not create application code, tests, CI files, or documentation unrelated to the Kanbn board.
|
|
20
29
|
|
|
21
30
|
If the user asks for planning plus implementation, complete the planning portion in Kanbn format first and keep the planning files cleanly separated from any later work.
|
|
@@ -13,9 +13,14 @@ Do not use this skill to redesign scope, rewrite completed tasks, or invent new
|
|
|
13
13
|
|
|
14
14
|
Your output must stay inside the Kanbn task surface:
|
|
15
15
|
|
|
16
|
-
- `.kanbn/index.md`
|
|
16
|
+
- `.kanbn/index.md` — the main board
|
|
17
|
+
- `.kanbn/{board-slug}.md` — any secondary boards, in exactly the same format as the index
|
|
17
18
|
- `.kanbn/tasks/*.md`
|
|
18
19
|
|
|
20
|
+
A workspace may hold several boards over one shared pool of tasks. Rescheduling changes task
|
|
21
|
+
metadata dates, which are shared, so it applies to every board at once — you do not need to touch
|
|
22
|
+
board files unless a task's column changes.
|
|
23
|
+
|
|
19
24
|
You may read the current board and gantt output, but the end result should be a revised schedule for existing tasks, not a new plan.
|
|
20
25
|
|
|
21
26
|
Do not modify completed tasks unless the user explicitly asks to reopen them. Leave completed task descriptions, relations, and completion history intact.
|
package/src/board.js
CHANGED
package/src/controller/add.js
CHANGED
|
@@ -1,9 +1,13 @@
|
|
|
1
|
-
const
|
|
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
|
+
const getGitUsername = require('../git-user-name');
|
|
7
|
+
|
|
8
|
+
// The boards this command targets - set from --board before anything else runs
|
|
9
|
+
let kanbn = workspace;
|
|
10
|
+
let boardTargets = [];
|
|
7
11
|
|
|
8
12
|
inquirer.registerPrompt('datepicker', require('inquirer-datepicker'));
|
|
9
13
|
inquirer.registerPrompt('recursive', require('inquirer-recursive'));
|
|
@@ -235,15 +239,24 @@ async function interactiveAddUntrackedTasks(untrackedTasks, columnName, columnNa
|
|
|
235
239
|
* @param {object} taskData
|
|
236
240
|
* @param {string} columnName
|
|
237
241
|
*/
|
|
238
|
-
function createTask(taskData, columnName) {
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
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
|
+
for (const target of rest) {
|
|
254
|
+
await target.kanbn.addTaskToBoard(taskId, target.columnName);
|
|
255
|
+
console.log(`Added task "${taskId}" to column "${target.columnName}" on board "${target.slug}"`);
|
|
256
|
+
}
|
|
257
|
+
} catch (error) {
|
|
245
258
|
utility.error(error);
|
|
246
|
-
}
|
|
259
|
+
}
|
|
247
260
|
}
|
|
248
261
|
|
|
249
262
|
/**
|
|
@@ -252,50 +265,44 @@ function createTask(taskData, columnName) {
|
|
|
252
265
|
* @param {string} columnName
|
|
253
266
|
*/
|
|
254
267
|
async function addUntrackedTasks(untrackedTasks, columnName) {
|
|
255
|
-
for (
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
268
|
+
for (const target of boardTargets) {
|
|
269
|
+
|
|
270
|
+
// The column chosen interactively or on the command line applies to the first board; the others
|
|
271
|
+
// use whatever resolveBoardTargets() paired them with
|
|
272
|
+
const targetColumn = target === boardTargets[0] ? columnName : target.columnName;
|
|
273
|
+
for (let untrackedTask of untrackedTasks) {
|
|
274
|
+
try {
|
|
275
|
+
await target.kanbn.addUntrackedTaskToIndex(untrackedTask, targetColumn);
|
|
276
|
+
} catch (error) {
|
|
277
|
+
utility.error(error);
|
|
278
|
+
return;
|
|
279
|
+
}
|
|
261
280
|
}
|
|
281
|
+
console.log(
|
|
282
|
+
`Added ${untrackedTasks.length} task${untrackedTasks.length !== 1 ? 's' : ''} ` +
|
|
283
|
+
`to column "${targetColumn}"` + (boardTargets.length > 1 || !target.main ? ` on board "${target.slug}"` : '')
|
|
284
|
+
);
|
|
262
285
|
}
|
|
263
|
-
console.log(
|
|
264
|
-
`Added ${untrackedTasks.length} task${untrackedTasks.length !== 1 ? 's' : ''} to column "${columnName}"`
|
|
265
|
-
);
|
|
266
286
|
}
|
|
267
287
|
|
|
268
288
|
module.exports = async args => {
|
|
269
289
|
|
|
270
|
-
//
|
|
271
|
-
|
|
272
|
-
|
|
290
|
+
// Work out which boards this command targets, and which column to use on each. -b is repeatable,
|
|
291
|
+
// so one task can be created and placed on several boards at once
|
|
292
|
+
const targets = await utility.resolveBoardTargets(workspace, args);
|
|
293
|
+
if (targets === null) {
|
|
273
294
|
return;
|
|
274
295
|
}
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
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;
|
|
296
|
+
boardTargets = targets;
|
|
297
|
+
kanbn = targets[0].kanbn;
|
|
298
|
+
for (const target of boardTargets) {
|
|
299
|
+
target.main = await target.kanbn.isMainBoard();
|
|
288
300
|
}
|
|
301
|
+
utility.showColumnFallbackNotices(boardTargets, kanbn.isVerbose(targets[0].index));
|
|
289
302
|
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
columnName = utility.strArg(args.column);
|
|
294
|
-
if (columnNames.indexOf(columnName) === -1) {
|
|
295
|
-
utility.error(`Column "${columnName}" doesn't exist`);
|
|
296
|
-
return;
|
|
297
|
-
}
|
|
298
|
-
}
|
|
303
|
+
const index = targets[0].index;
|
|
304
|
+
const columnNames = Object.keys(index.columns);
|
|
305
|
+
const columnName = targets[0].columnName;
|
|
299
306
|
|
|
300
307
|
// Add untracked file(s)
|
|
301
308
|
if (args.untracked) {
|
|
@@ -534,8 +541,7 @@ module.exports = async args => {
|
|
|
534
541
|
type: relation.type
|
|
535
542
|
}));
|
|
536
543
|
}
|
|
537
|
-
|
|
538
|
-
createTask(taskData, columnName);
|
|
544
|
+
createTask(taskData, answers.column);
|
|
539
545
|
})
|
|
540
546
|
.catch(error => {
|
|
541
547
|
utility.error(error);
|
|
@@ -1,6 +1,9 @@
|
|
|
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
|
* Archive a task
|
|
6
9
|
* @param {string} taskId
|
|
@@ -32,11 +35,12 @@ function listArchivedTasks() {
|
|
|
32
35
|
|
|
33
36
|
module.exports = async args => {
|
|
34
37
|
|
|
35
|
-
//
|
|
36
|
-
|
|
37
|
-
|
|
38
|
+
// Work out which board this command targets
|
|
39
|
+
const scoped = await utility.resolveBoard(workspace, args);
|
|
40
|
+
if (scoped === null) {
|
|
38
41
|
return;
|
|
39
42
|
}
|
|
43
|
+
kanbn = scoped;
|
|
40
44
|
|
|
41
45
|
// Check if we're listing archived tasks
|
|
42
46
|
if (args.list) {
|
package/src/controller/board.js
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
|
-
const
|
|
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
|
-
//
|
|
8
|
-
|
|
9
|
-
|
|
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
|
|
|
@@ -32,11 +32,10 @@ module.exports = async args => {
|
|
|
32
32
|
// Show the board
|
|
33
33
|
board
|
|
34
34
|
.show(index, tasks, args.view, args.json)
|
|
35
|
-
.then(() => {
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
);
|
|
35
|
+
.then(async () => {
|
|
36
|
+
const verbose = !args.json && kanbn.isVerbose(index);
|
|
37
|
+
utility.showDateDriftHint(kanbn.getDateDrift(index, tasks), verbose);
|
|
38
|
+
utility.showBoardCountHint(await workspace.listBoards(), verbose);
|
|
40
39
|
})
|
|
41
40
|
.catch(error => {
|
|
42
41
|
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
|
|
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
|
-
//
|
|
66
|
-
|
|
67
|
-
|
|
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
|
|
@@ -127,7 +131,7 @@ module.exports = async args => {
|
|
|
127
131
|
|
|
128
132
|
// Render chart
|
|
129
133
|
const PADDING = ' ';
|
|
130
|
-
const width = Math.max(1, term.width - (PADDING.length + 1));
|
|
134
|
+
const width = Math.max(1, utility.terminalWidth(term.width) - (PADDING.length + 1));
|
|
131
135
|
|
|
132
136
|
const plots = [];
|
|
133
137
|
for (const s of data.series) {
|
|
@@ -1,7 +1,10 @@
|
|
|
1
|
-
const
|
|
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
|
+
const getGitUsername = require('../git-user-name');
|
|
5
|
+
|
|
6
|
+
// The board this command targets - set from --board before anything else runs
|
|
7
|
+
let kanbn = workspace;
|
|
5
8
|
|
|
6
9
|
/**
|
|
7
10
|
* Add a comment interactively
|
|
@@ -51,11 +54,12 @@ function addComment(taskId, text, author) {
|
|
|
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 add a comment to
|
|
61
65
|
const taskId = args._[1];
|
package/src/controller/edit.js
CHANGED
|
@@ -1,9 +1,12 @@
|
|
|
1
|
-
const
|
|
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
|
+
const getGitUsername = require('../git-user-name');
|
|
7
|
+
|
|
8
|
+
// The board this command targets - set from --board before anything else runs
|
|
9
|
+
let kanbn = workspace;
|
|
7
10
|
|
|
8
11
|
inquirer.registerPrompt('datepicker', require('inquirer-datepicker'));
|
|
9
12
|
inquirer.registerPrompt('recursive', require('inquirer-recursive'));
|
|
@@ -455,11 +458,12 @@ function updateTask(taskId, taskData, columnName, unsetFields = []) {
|
|
|
455
458
|
|
|
456
459
|
module.exports = async args => {
|
|
457
460
|
|
|
458
|
-
//
|
|
459
|
-
|
|
460
|
-
|
|
461
|
+
// Work out which board this command targets
|
|
462
|
+
const scoped = await utility.resolveBoard(workspace, args);
|
|
463
|
+
if (scoped === null) {
|
|
461
464
|
return;
|
|
462
465
|
}
|
|
466
|
+
kanbn = scoped;
|
|
463
467
|
|
|
464
468
|
// Get the task that we're editing
|
|
465
469
|
const taskId = args._[1];
|
package/src/controller/find.js
CHANGED
|
@@ -1,9 +1,12 @@
|
|
|
1
|
-
const
|
|
1
|
+
const workspace = require('../main');
|
|
2
2
|
const utility = require('../utility');
|
|
3
3
|
const inquirer = require('inquirer');
|
|
4
4
|
const chrono = require('chrono-node');
|
|
5
5
|
const yaml = require('yamljs');
|
|
6
6
|
|
|
7
|
+
// The board this command targets - set from --board before anything else runs
|
|
8
|
+
let kanbn = workspace;
|
|
9
|
+
|
|
7
10
|
inquirer.registerPrompt('recursive', require('inquirer-recursive'));
|
|
8
11
|
|
|
9
12
|
const searchFields = [
|
|
@@ -213,12 +216,11 @@ async function interactive() {
|
|
|
213
216
|
* @param {boolean} quiet
|
|
214
217
|
* @param {boolean} json
|
|
215
218
|
*/
|
|
216
|
-
function findTasks(filters, quiet, json) {
|
|
219
|
+
function findTasks(filters, quiet, json, allBoards = false) {
|
|
217
220
|
const removeEmptyProperties = o => Object.fromEntries(Object.entries(o).filter(
|
|
218
221
|
([k, v]) => !(Array.isArray(v) && v.length == 0) && !!v
|
|
219
222
|
));
|
|
220
|
-
kanbn
|
|
221
|
-
.search(filters, quiet)
|
|
223
|
+
(allBoards ? kanbn.searchAllBoards(filters, quiet) : kanbn.search(filters, quiet))
|
|
222
224
|
.then(results => {
|
|
223
225
|
if (quiet) {
|
|
224
226
|
console.log(json ? JSON.stringify(results, null, 2) : results.join('\n'));
|
|
@@ -309,11 +311,12 @@ function convertDateFilters(filters, filterName) {
|
|
|
309
311
|
|
|
310
312
|
module.exports = async args => {
|
|
311
313
|
|
|
312
|
-
//
|
|
313
|
-
|
|
314
|
-
|
|
314
|
+
// Work out which board this command targets
|
|
315
|
+
const scoped = await utility.resolveBoard(workspace, args);
|
|
316
|
+
if (scoped === null) {
|
|
315
317
|
return;
|
|
316
318
|
}
|
|
319
|
+
kanbn = scoped;
|
|
317
320
|
|
|
318
321
|
// Get the index and make sure it has some columns
|
|
319
322
|
let index;
|
|
@@ -487,7 +490,7 @@ module.exports = async args => {
|
|
|
487
490
|
filter.value
|
|
488
491
|
);
|
|
489
492
|
}
|
|
490
|
-
findTasks(filters, !otherAnswers.nonquiet, otherAnswers.json);
|
|
493
|
+
findTasks(filters, !otherAnswers.nonquiet, otherAnswers.json, !!args['all-boards']);
|
|
491
494
|
})
|
|
492
495
|
.catch(error => {
|
|
493
496
|
utility.error(error);
|
|
@@ -499,6 +502,6 @@ module.exports = async args => {
|
|
|
499
502
|
|
|
500
503
|
// Otherwise create task non-interactively
|
|
501
504
|
} else {
|
|
502
|
-
findTasks(filters, args.quiet, args.json);
|
|
505
|
+
findTasks(filters, args.quiet, args.json, !!args['all-boards']);
|
|
503
506
|
}
|
|
504
507
|
};
|
package/src/controller/gantt.js
CHANGED
|
@@ -1,8 +1,11 @@
|
|
|
1
|
-
const
|
|
1
|
+
const workspace = require('../main');
|
|
2
2
|
const utility = require('../utility');
|
|
3
3
|
const formatDate = require('dateformat');
|
|
4
4
|
const chrono = require('chrono-node');
|
|
5
5
|
|
|
6
|
+
// The board this command targets - set from --board before anything else runs
|
|
7
|
+
let kanbn = workspace;
|
|
8
|
+
|
|
6
9
|
const NOW_MARKER = '┆';
|
|
7
10
|
const ANSI_RESET = '\x1b[0m';
|
|
8
11
|
const ANSI_FG_LINE = '\x1b[97m';
|
|
@@ -313,11 +316,12 @@ function truncateName(name, maxWidth) {
|
|
|
313
316
|
|
|
314
317
|
module.exports = async args => {
|
|
315
318
|
|
|
316
|
-
//
|
|
317
|
-
|
|
318
|
-
|
|
319
|
+
// Work out which board this command targets
|
|
320
|
+
const scoped = await utility.resolveBoard(workspace, args);
|
|
321
|
+
if (scoped === null) {
|
|
319
322
|
return;
|
|
320
323
|
}
|
|
324
|
+
kanbn = scoped;
|
|
321
325
|
const index = await kanbn.getIndex();
|
|
322
326
|
|
|
323
327
|
// Get assigned
|
|
@@ -1,8 +1,11 @@
|
|
|
1
|
-
const
|
|
1
|
+
const workspace = require('../main');
|
|
2
2
|
const utility = require('../utility');
|
|
3
3
|
const chrono = require('chrono-node');
|
|
4
4
|
const formatDate = require('dateformat');
|
|
5
5
|
|
|
6
|
+
// The board this command targets - set from --board before anything else runs
|
|
7
|
+
let kanbn = workspace;
|
|
8
|
+
|
|
6
9
|
function formatHistoryEvent(event) {
|
|
7
10
|
const details = [];
|
|
8
11
|
if ('column' in event.event) {
|
|
@@ -29,11 +32,12 @@ function formatMetadataEvent(event, dateFormat) {
|
|
|
29
32
|
|
|
30
33
|
module.exports = async args => {
|
|
31
34
|
|
|
32
|
-
//
|
|
33
|
-
|
|
34
|
-
|
|
35
|
+
// Work out which board this command targets
|
|
36
|
+
const scoped = await utility.resolveBoard(workspace, args);
|
|
37
|
+
if (scoped === null) {
|
|
35
38
|
return;
|
|
36
39
|
}
|
|
40
|
+
kanbn = scoped;
|
|
37
41
|
const index = await kanbn.getIndex();
|
|
38
42
|
|
|
39
43
|
// Get sprint numbers or names
|