@basementuniverse/kanbn 1.1.1 → 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 +3 -0
- package/coverage/base.css +224 -0
- package/coverage/block-navigation.js +87 -0
- package/coverage/coverage-summary.json +31 -0
- package/coverage/favicon.png +0 -0
- package/coverage/index.html +146 -0
- package/coverage/kanbn/index.html +116 -0
- package/coverage/kanbn/index.js.html +217 -0
- package/coverage/kanbn/src/board.js.html +646 -0
- package/coverage/kanbn/src/controller/add.js.html +1729 -0
- package/coverage/kanbn/src/controller/archive.js.html +277 -0
- package/coverage/kanbn/src/controller/board.js.html +217 -0
- package/coverage/kanbn/src/controller/burndown.js.html +607 -0
- package/coverage/kanbn/src/controller/comment.js.html +397 -0
- package/coverage/kanbn/src/controller/edit.js.html +2920 -0
- package/coverage/kanbn/src/controller/find.js.html +1597 -0
- package/coverage/kanbn/src/controller/gantt.js.html +1342 -0
- package/coverage/kanbn/src/controller/help.js.html +115 -0
- package/coverage/kanbn/src/controller/history.js.html +400 -0
- package/coverage/kanbn/src/controller/index.html +431 -0
- package/coverage/kanbn/src/controller/init.js.html +511 -0
- package/coverage/kanbn/src/controller/move.js.html +514 -0
- package/coverage/kanbn/src/controller/remove-all.js.html +226 -0
- package/coverage/kanbn/src/controller/remove.js.html +307 -0
- package/coverage/kanbn/src/controller/rename.js.html +397 -0
- package/coverage/kanbn/src/controller/restore.js.html +268 -0
- package/coverage/kanbn/src/controller/sort.js.html +1318 -0
- package/coverage/kanbn/src/controller/sprint.js.html +361 -0
- package/coverage/kanbn/src/controller/status.js.html +310 -0
- package/coverage/kanbn/src/controller/task.js.html +244 -0
- package/coverage/kanbn/src/controller/validate.js.html +367 -0
- package/coverage/kanbn/src/controller/version.js.html +100 -0
- package/coverage/kanbn/src/index.html +191 -0
- package/coverage/kanbn/src/main.js.html +9787 -0
- package/coverage/kanbn/src/parse-index.js.html +1063 -0
- package/coverage/kanbn/src/parse-markdown.js.html +202 -0
- package/coverage/kanbn/src/parse-task.js.html +2191 -0
- package/coverage/kanbn/src/utility.js.html +616 -0
- package/coverage/lcov.info +11837 -0
- package/coverage/prettify.css +1 -0
- package/coverage/prettify.js +2 -0
- package/coverage/sort-arrow-sprite.png +0 -0
- package/coverage/sorter.js +210 -0
- package/coverage/tmp/coverage-214292-1787777191526-0.json +1 -0
- package/coverage/tmp/coverage-214293-1787777184569-0.json +1 -0
- package/docs/advanced-configuration.md +25 -0
- package/docs/commands/add.txt +19 -0
- package/docs/commands/archive.txt +5 -0
- package/docs/commands/board.txt +8 -1
- package/docs/commands/boards.txt +34 -0
- package/docs/commands/burndown.txt +8 -4
- package/docs/commands/comment.txt +5 -0
- package/docs/commands/edit.txt +27 -0
- package/docs/commands/find.txt +56 -1
- package/docs/commands/gantt.txt +5 -0
- package/docs/commands/help.txt +2 -0
- package/docs/commands/history.txt +5 -0
- package/docs/commands/init.txt +13 -0
- package/docs/commands/move.txt +10 -1
- 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 +18 -2
- 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 +27 -0
- package/docs/filtering-and-sorting.md +218 -0
- package/docs/index-structure.md +94 -19
- package/docs/index.md +28 -2
- package/docs/migration-2.0.md +92 -0
- package/docs/multiple-boards.md +258 -0
- package/docs/quick-start.md +57 -1
- package/docs/task-structure.md +33 -3
- package/docs/views.md +229 -0
- package/example/README.md +93 -0
- package/example/advanced/.kanbn/index.md +26 -0
- package/example/advanced/.kanbn/tasks/add-rate-limiting.md +18 -0
- package/example/advanced/.kanbn/tasks/add-sso-support.md +15 -0
- package/example/advanced/.kanbn/tasks/migrate-to-new-cdn.md +14 -0
- package/example/advanced/.kanbn/tasks/refresh-marketing-site.md +14 -0
- package/example/advanced/.kanbn/tasks/rotate-api-keys.md +15 -0
- package/example/advanced/.kanbn/tasks/split-billing-service.md +17 -0
- package/example/advanced/.kanbn/tasks/upgrade-node-runtime.md +19 -0
- package/example/advanced/kanbn.yml +59 -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/example/views/.kanbn/index.md +140 -0
- package/example/views/.kanbn/tasks/add-audit-log-export.md +18 -0
- package/example/views/.kanbn/tasks/add-dark-mode-toggle.md +16 -0
- package/example/views/.kanbn/tasks/build-webhook-retry-queue.md +15 -0
- package/example/views/.kanbn/tasks/fix-avatar-upload-crash.md +13 -0
- package/example/views/.kanbn/tasks/fix-expired-session-redirect.md +15 -0
- package/example/views/.kanbn/tasks/investigate-offline-mode.md +12 -0
- package/example/views/.kanbn/tasks/rewrite-search-indexer.md +12 -0
- package/example/views/.kanbn/tasks/tidy-up-settings-copy.md +12 -0
- package/package.json +12 -9
- 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 +33 -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 +19 -4
- package/skills/kanbn-plan/SKILL.md +10 -1
- package/skills/kanbn-plan/references/index-structure.md +1 -0
- package/skills/kanbn-plan/references/task-structure.md +2 -0
- package/skills/kanbn-replan/SKILL.md +6 -1
- package/src/board.js +2 -1
- package/src/controller/add.js +52 -46
- package/src/controller/archive.js +8 -4
- package/src/controller/board.js +9 -4
- 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 +74 -17
- 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 +20 -4
- package/src/controller/task.js +22 -9
- package/src/controller/validate.js +139 -23
- package/src/git-user-name.js +19 -0
- package/src/main.d.ts +184 -6
- package/src/main.js +1642 -221
- package/src/parse-index.js +17 -0
- package/src/parse-markdown.js +3 -1
- package/src/parse-task.js +44 -1
- package/src/utility.js +166 -1
- package/PLANS.md +0 -9
- /package/example/{.kanbn → basic/.kanbn}/index.md +0 -0
- /package/example/{.kanbn → basic/.kanbn}/tasks/add-basic-activity-feed.md +0 -0
- /package/example/{.kanbn → basic/.kanbn}/tasks/add-passwordless-login-option.md +0 -0
- /package/example/{.kanbn → basic/.kanbn}/tasks/add-usage-alert-email-thresholds.md +0 -0
- /package/example/{.kanbn → basic/.kanbn}/tasks/build-email-template-system.md +0 -0
- /package/example/{.kanbn → basic/.kanbn}/tasks/build-invoice-download-endpoint.md +0 -0
- /package/example/{.kanbn → basic/.kanbn}/tasks/build-tenant-settings-page.md +0 -0
- /package/example/{.kanbn → basic/.kanbn}/tasks/create-organization-switcher.md +0 -0
- /package/example/{.kanbn → basic/.kanbn}/tasks/create-sandbox-environment-provisioner.md +0 -0
- /package/example/{.kanbn → basic/.kanbn}/tasks/create-self-serve-cancellation-flow.md +0 -0
- /package/example/{.kanbn → basic/.kanbn}/tasks/define-product-pricing-strategy.md +0 -0
- /package/example/{.kanbn → basic/.kanbn}/tasks/design-onboarding-checklist.md +0 -0
- /package/example/{.kanbn → basic/.kanbn}/tasks/design-team-invite-expiry-flow.md +0 -0
- /package/example/{.kanbn → basic/.kanbn}/tasks/implement-data-retention-policy-jobs.md +0 -0
- /package/example/{.kanbn → basic/.kanbn}/tasks/implement-feature-flags-foundation.md +0 -0
- /package/example/{.kanbn → basic/.kanbn}/tasks/implement-project-creation-wizard.md +0 -0
- /package/example/{.kanbn → basic/.kanbn}/tasks/implement-stripe-webhook-signature-check.md +0 -0
- /package/example/{.kanbn → basic/.kanbn}/tasks/implement-team-permissions-ui.md +0 -0
- /package/example/{.kanbn → basic/.kanbn}/tasks/implement-user-signup-and-login.md +0 -0
- /package/example/{.kanbn → basic/.kanbn}/tasks/integrate-crm-lead-sync.md +0 -0
- /package/example/{.kanbn → basic/.kanbn}/tasks/legal-review-terms-and-privacy.md +0 -0
- /package/example/{.kanbn → basic/.kanbn}/tasks/migrate-legacy-events-to-new-schema.md +0 -0
- /package/example/{.kanbn → basic/.kanbn}/tasks/optimize-dashboard-first-load.md +0 -0
- /package/example/{.kanbn → basic/.kanbn}/tasks/prototype-report-export-scheduler.md +0 -0
- /package/example/{.kanbn → basic/.kanbn}/tasks/publish-internal-qa-checklist.md +0 -0
- /package/example/{.kanbn → basic/.kanbn}/tasks/setup-ci-pipeline.md +0 -0
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,6 +32,11 @@ module.exports = async args => {
|
|
|
32
32
|
// Show the board
|
|
33
33
|
board
|
|
34
34
|
.show(index, tasks, args.view, args.json)
|
|
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);
|
|
39
|
+
})
|
|
35
40
|
.catch(error => {
|
|
36
41
|
utility.error(error);
|
|
37
42
|
});
|
|
@@ -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'));
|
|
@@ -412,7 +415,7 @@ async function interactive(taskData, taskIds, columnName, columnNames) {
|
|
|
412
415
|
type: 'input',
|
|
413
416
|
name: 'type',
|
|
414
417
|
message: 'Relation type:',
|
|
415
|
-
default: answers => taskData.relations.find(relation => relation.task === answers.selectRelation).
|
|
418
|
+
default: answers => taskData.relations.find(relation => relation.task === answers.selectRelation).type,
|
|
416
419
|
when: answers => answers.editRelation === 'edit'
|
|
417
420
|
}
|
|
418
421
|
]
|
|
@@ -420,15 +423,31 @@ async function interactive(taskData, taskIds, columnName, columnNames) {
|
|
|
420
423
|
]);
|
|
421
424
|
}
|
|
422
425
|
|
|
426
|
+
/**
|
|
427
|
+
* Metadata fields that can be cleared with --unset, in addition to any custom fields defined in
|
|
428
|
+
* the index options
|
|
429
|
+
*/
|
|
430
|
+
const UNSETTABLE_FIELDS = [
|
|
431
|
+
'started',
|
|
432
|
+
'completed',
|
|
433
|
+
'due',
|
|
434
|
+
'plannedStart',
|
|
435
|
+
'plannedFinish',
|
|
436
|
+
'assigned',
|
|
437
|
+
'progress',
|
|
438
|
+
'tags'
|
|
439
|
+
];
|
|
440
|
+
|
|
423
441
|
/**
|
|
424
442
|
* Update a task
|
|
425
443
|
* @param {string} taskId
|
|
426
444
|
* @param {object} taskData
|
|
427
445
|
* @param {?string} columnName
|
|
446
|
+
* @param {string[]} [unsetFields=[]]
|
|
428
447
|
*/
|
|
429
|
-
function updateTask(taskId, taskData, columnName) {
|
|
448
|
+
function updateTask(taskId, taskData, columnName, unsetFields = []) {
|
|
430
449
|
kanbn
|
|
431
|
-
.updateTask(taskId, taskData, columnName)
|
|
450
|
+
.updateTask(taskId, taskData, columnName, unsetFields)
|
|
432
451
|
.then(taskId => {
|
|
433
452
|
console.log(`Updated task "${taskId}"`);
|
|
434
453
|
})
|
|
@@ -439,11 +458,12 @@ function updateTask(taskId, taskData, columnName) {
|
|
|
439
458
|
|
|
440
459
|
module.exports = async args => {
|
|
441
460
|
|
|
442
|
-
//
|
|
443
|
-
|
|
444
|
-
|
|
461
|
+
// Work out which board this command targets
|
|
462
|
+
const scoped = await utility.resolveBoard(workspace, args);
|
|
463
|
+
if (scoped === null) {
|
|
445
464
|
return;
|
|
446
465
|
}
|
|
466
|
+
kanbn = scoped;
|
|
447
467
|
|
|
448
468
|
// Get the task that we're editing
|
|
449
469
|
const taskId = args._[1];
|
|
@@ -578,8 +598,8 @@ module.exports = async args => {
|
|
|
578
598
|
|
|
579
599
|
// Check that the sub-tasks being removed currently exist
|
|
580
600
|
for (let removedSubTask of removedSubTasks) {
|
|
581
|
-
if (taskData.subTasks.find(subTask => subTask.text === removedSubTask
|
|
582
|
-
utility.error(`Sub-task "${removedSubTask
|
|
601
|
+
if (taskData.subTasks.find(subTask => subTask.text === removedSubTask) === undefined) {
|
|
602
|
+
utility.error(`Sub-task "${removedSubTask}" doesn't exist`);
|
|
583
603
|
return;
|
|
584
604
|
}
|
|
585
605
|
}
|
|
@@ -633,7 +653,7 @@ module.exports = async args => {
|
|
|
633
653
|
// Check that the tags being removed currently exist
|
|
634
654
|
for (let removedTag of removedTags) {
|
|
635
655
|
if (taskData.metadata.tags.indexOf(removedTag) === -1) {
|
|
636
|
-
utility.error(`Tag "${
|
|
656
|
+
utility.error(`Tag "${removedTag}" doesn't exist`);
|
|
637
657
|
return;
|
|
638
658
|
}
|
|
639
659
|
}
|
|
@@ -652,8 +672,8 @@ module.exports = async args => {
|
|
|
652
672
|
|
|
653
673
|
// Check that the relations being removed currently exist
|
|
654
674
|
for (let removedRelation of removedRelations) {
|
|
655
|
-
if (taskData.relations.find(relation => relation.task === removedRelation
|
|
656
|
-
utility.error(`Relation "${removedRelation
|
|
675
|
+
if (taskData.relations.find(relation => relation.task === removedRelation) === undefined) {
|
|
676
|
+
utility.error(`Relation "${removedRelation}" doesn't exist`);
|
|
657
677
|
return;
|
|
658
678
|
}
|
|
659
679
|
}
|
|
@@ -754,6 +774,43 @@ module.exports = async args => {
|
|
|
754
774
|
}
|
|
755
775
|
}
|
|
756
776
|
|
|
777
|
+
// Unset metadata fields
|
|
778
|
+
const unsetFields = [];
|
|
779
|
+
if (args.unset) {
|
|
780
|
+
const customFieldNames = 'customFields' in index.options
|
|
781
|
+
? index.options.customFields.map(customField => customField.name)
|
|
782
|
+
: [];
|
|
783
|
+
const unsettableFields = [...UNSETTABLE_FIELDS, ...customFieldNames];
|
|
784
|
+
|
|
785
|
+
// Arguments that set the value of a metadata field, so that setting and unsetting the same
|
|
786
|
+
// field in one command can be rejected instead of silently resolving one way or the other
|
|
787
|
+
const settingArgs = {
|
|
788
|
+
due: args.due,
|
|
789
|
+
plannedStart: args.plannedStart,
|
|
790
|
+
plannedFinish: args.plannedFinish,
|
|
791
|
+
progress: args.progress,
|
|
792
|
+
assigned: args.assigned,
|
|
793
|
+
tags: args.tag,
|
|
794
|
+
...Object.fromEntries(customFieldNames.map(name => [name, args[name]]))
|
|
795
|
+
};
|
|
796
|
+
|
|
797
|
+
for (let field of utility.arrayArg(args.unset)) {
|
|
798
|
+
if (unsettableFields.indexOf(field) === -1) {
|
|
799
|
+
utility.error(
|
|
800
|
+
`Cannot unset "${field}"\nAvailable fields: ${unsettableFields.join(', ')}`
|
|
801
|
+
);
|
|
802
|
+
return;
|
|
803
|
+
}
|
|
804
|
+
if (settingArgs[field] !== undefined) {
|
|
805
|
+
utility.error(`Cannot set and unset "${field}" in the same command`);
|
|
806
|
+
return;
|
|
807
|
+
}
|
|
808
|
+
if (unsetFields.indexOf(field) === -1) {
|
|
809
|
+
unsetFields.push(field);
|
|
810
|
+
}
|
|
811
|
+
}
|
|
812
|
+
}
|
|
813
|
+
|
|
757
814
|
// Update task interactively
|
|
758
815
|
if (args.interactive) {
|
|
759
816
|
interactive(taskData, taskIds, columnName, columnNames)
|
|
@@ -810,7 +867,7 @@ module.exports = async args => {
|
|
|
810
867
|
// Edit or remove sub-tasks
|
|
811
868
|
if ('editSubTasks' in answers) {
|
|
812
869
|
for (editSubTask of answers.editSubTasks) {
|
|
813
|
-
const i = taskData.subTasks.findIndex(subTask => subTask.
|
|
870
|
+
const i = taskData.subTasks.findIndex(subTask => subTask.text === editSubTask.selectSubTask);
|
|
814
871
|
if (i !== -1) {
|
|
815
872
|
switch (editSubTask.editSubTask) {
|
|
816
873
|
case 'remove':
|
|
@@ -837,7 +894,7 @@ module.exports = async args => {
|
|
|
837
894
|
// Remove tags
|
|
838
895
|
if ('removeTags' in answers && 'metadata' in taskData && 'tags' in taskData.metadata) {
|
|
839
896
|
for (removeTag of answers.removeTags) {
|
|
840
|
-
const i = taskData.metadata.tags.indexOf(removeTag.
|
|
897
|
+
const i = taskData.metadata.tags.indexOf(removeTag.selectTag);
|
|
841
898
|
if (i !== -1) {
|
|
842
899
|
taskData.metadata.tags.splice(i, 1);
|
|
843
900
|
}
|
|
@@ -878,7 +935,7 @@ module.exports = async args => {
|
|
|
878
935
|
|
|
879
936
|
// Update task
|
|
880
937
|
columnName = answers.column !== currentColumnName ? answers.column : null;
|
|
881
|
-
updateTask(taskId, taskData, columnName);
|
|
938
|
+
updateTask(taskId, taskData, columnName, unsetFields);
|
|
882
939
|
})
|
|
883
940
|
.catch(error => {
|
|
884
941
|
utility.error(error);
|
|
@@ -887,6 +944,6 @@ module.exports = async args => {
|
|
|
887
944
|
// Otherwise edit task non-interactively
|
|
888
945
|
} else {
|
|
889
946
|
columnName = columnName !== currentColumnName ? columnName : null;
|
|
890
|
-
updateTask(taskId, taskData, columnName);
|
|
947
|
+
updateTask(taskId, taskData, columnName, unsetFields);
|
|
891
948
|
}
|
|
892
949
|
};
|
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
|