@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/parse-index.js
CHANGED
|
@@ -24,6 +24,23 @@ function validateOptions(options) {
|
|
|
24
24
|
type: 'array',
|
|
25
25
|
items: { type: 'string' }
|
|
26
26
|
},
|
|
27
|
+
'startedField': { type: 'string' },
|
|
28
|
+
'completedField': { type: 'string' },
|
|
29
|
+
'verbose': { type: 'boolean' },
|
|
30
|
+
'defaultBoard': { type: 'string' },
|
|
31
|
+
'boards': {
|
|
32
|
+
type: 'object',
|
|
33
|
+
properties: {
|
|
34
|
+
'exclude': {
|
|
35
|
+
type: 'array',
|
|
36
|
+
items: { type: 'string' }
|
|
37
|
+
},
|
|
38
|
+
'order': {
|
|
39
|
+
type: 'array',
|
|
40
|
+
items: { type: 'string' }
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
},
|
|
27
44
|
'sprints': {
|
|
28
45
|
type: 'array',
|
|
29
46
|
items: {
|
package/src/parse-markdown.js
CHANGED
|
@@ -26,7 +26,9 @@ module.exports = function parseMarkdown(markdown) {
|
|
|
26
26
|
parsed[headings[i].title] = {
|
|
27
27
|
heading: headings[i].heading,
|
|
28
28
|
content: markdown.slice(
|
|
29
|
-
|
|
29
|
+
// The + 1 skips the newline that ends the heading line; the synthetic 'raw' entry for
|
|
30
|
+
// content before the first heading has no heading line, so it starts at index 0
|
|
31
|
+
headings[i].index + headings[i].heading.length + (headings[i].heading ? 1 : 0),
|
|
30
32
|
i < headings.length - 1
|
|
31
33
|
? headings[i + 1].index
|
|
32
34
|
: undefined
|
package/src/parse-task.js
CHANGED
|
@@ -200,6 +200,7 @@ function validateHistoryFromMarkdown(history) {
|
|
|
200
200
|
'column': { type: 'string' },
|
|
201
201
|
'fromColumn': { type: 'string' },
|
|
202
202
|
'toColumn': { type: 'string' },
|
|
203
|
+
'board': { type: 'string' },
|
|
203
204
|
'author': { type: 'string' },
|
|
204
205
|
'fromProgress': { oneOf: [{ type: 'number' }, { type: 'string' }] },
|
|
205
206
|
'toProgress': { oneOf: [{ type: 'number' }, { type: 'string' }] }
|
|
@@ -226,6 +227,7 @@ function validateHistoryFromJSON(history) {
|
|
|
226
227
|
'column': { type: 'string' },
|
|
227
228
|
'fromColumn': { type: 'string' },
|
|
228
229
|
'toColumn': { type: 'string' },
|
|
230
|
+
'board': { type: 'string' },
|
|
229
231
|
'author': { type: 'string' },
|
|
230
232
|
'fromProgress': { type: 'number' },
|
|
231
233
|
'toProgress': { type: 'number' }
|
|
@@ -239,6 +241,11 @@ function validateHistoryFromJSON(history) {
|
|
|
239
241
|
|
|
240
242
|
/**
|
|
241
243
|
* Validate required fields in a history event
|
|
244
|
+
*
|
|
245
|
+
* An event with an unrecognised type is left alone rather than rejected: a task file written by a
|
|
246
|
+
* newer version of kanbn may contain event types this version doesn't know about, and refusing to
|
|
247
|
+
* parse the whole task would make the file unreadable. Unknown events are preserved on write and
|
|
248
|
+
* ignored when history is replayed.
|
|
242
249
|
* @param {object} historyEvent
|
|
243
250
|
*/
|
|
244
251
|
function validateHistoryEvent(historyEvent) {
|
|
@@ -254,6 +261,13 @@ function validateHistoryEvent(historyEvent) {
|
|
|
254
261
|
throw new Error('created history event is missing column');
|
|
255
262
|
}
|
|
256
263
|
break;
|
|
264
|
+
case 'added':
|
|
265
|
+
if (!historyEvent.column) {
|
|
266
|
+
throw new Error('added history event is missing column');
|
|
267
|
+
}
|
|
268
|
+
break;
|
|
269
|
+
case 'removed':
|
|
270
|
+
break;
|
|
257
271
|
case 'moved':
|
|
258
272
|
if (!historyEvent.fromColumn || !historyEvent.toColumn) {
|
|
259
273
|
throw new Error('moved history event is missing fromColumn or toColumn');
|
|
@@ -275,10 +289,25 @@ function validateHistoryEvent(historyEvent) {
|
|
|
275
289
|
}
|
|
276
290
|
break;
|
|
277
291
|
default:
|
|
278
|
-
|
|
292
|
+
break;
|
|
279
293
|
}
|
|
280
294
|
}
|
|
281
295
|
|
|
296
|
+
/**
|
|
297
|
+
* History event keys that this version understands, in the order they should be written
|
|
298
|
+
*/
|
|
299
|
+
const KNOWN_HISTORY_EVENT_KEYS = [
|
|
300
|
+
'type',
|
|
301
|
+
'date',
|
|
302
|
+
'column',
|
|
303
|
+
'fromColumn',
|
|
304
|
+
'toColumn',
|
|
305
|
+
'board',
|
|
306
|
+
'fromProgress',
|
|
307
|
+
'toProgress',
|
|
308
|
+
'author'
|
|
309
|
+
];
|
|
310
|
+
|
|
282
311
|
module.exports = {
|
|
283
312
|
|
|
284
313
|
/**
|
|
@@ -651,6 +680,12 @@ module.exports = {
|
|
|
651
680
|
if ('toColumn' in historyEvent) {
|
|
652
681
|
historyEventOutput.push(`toColumn: ${historyEvent.toColumn}`);
|
|
653
682
|
}
|
|
683
|
+
|
|
684
|
+
// Events on the main board carry no board key, so existing task files - and files
|
|
685
|
+
// written by single-board workspaces - are byte-identical to what they always were
|
|
686
|
+
if ('board' in historyEvent && historyEvent.board) {
|
|
687
|
+
historyEventOutput.push(`board: ${historyEvent.board}`);
|
|
688
|
+
}
|
|
654
689
|
if ('fromProgress' in historyEvent) {
|
|
655
690
|
historyEventOutput.push(`fromProgress: ${historyEvent.fromProgress}`);
|
|
656
691
|
}
|
|
@@ -660,6 +695,14 @@ module.exports = {
|
|
|
660
695
|
if ('author' in historyEvent && historyEvent.author) {
|
|
661
696
|
historyEventOutput.push(`author: ${historyEvent.author}`);
|
|
662
697
|
}
|
|
698
|
+
|
|
699
|
+
// Write back any keys this version doesn't recognise, so that a file written by a
|
|
700
|
+
// newer version of kanbn survives a read/write cycle here without losing data
|
|
701
|
+
for (const key of Object.keys(historyEvent)) {
|
|
702
|
+
if (KNOWN_HISTORY_EVENT_KEYS.indexOf(key) === -1 && historyEvent[key] !== undefined) {
|
|
703
|
+
historyEventOutput.push(`${key}: ${historyEvent[key]}`);
|
|
704
|
+
}
|
|
705
|
+
}
|
|
663
706
|
return `- ${historyEventOutput.map((v, i) => i > 0 ? ` ${v}` : v).join('\n')}`;
|
|
664
707
|
}).join('\n')
|
|
665
708
|
);
|
package/src/utility.js
CHANGED
|
@@ -19,6 +19,155 @@ module.exports = (() => {
|
|
|
19
19
|
!dontExit && process.env.KANBN_ENV !== 'test' && process.exit(1);
|
|
20
20
|
},
|
|
21
21
|
|
|
22
|
+
/**
|
|
23
|
+
* Resolve the board a command targets and return a Kanbn instance scoped to it, printing an
|
|
24
|
+
* error and returning null if the workspace or the board doesn't exist
|
|
25
|
+
* @param {object} workspace The workspace-scoped Kanbn instance
|
|
26
|
+
* @param {object} args The parsed command arguments
|
|
27
|
+
* @return {Promise<object|null>} A board-scoped Kanbn instance, or null if it can't be resolved
|
|
28
|
+
*/
|
|
29
|
+
async resolveBoard(workspace, args) {
|
|
30
|
+
|
|
31
|
+
// Sprint numbers and names are relative to one board's list, so an aggregate across boards
|
|
32
|
+
// can't resolve them. Refuse rather than guess
|
|
33
|
+
if (args['all-boards'] && args.sprint) {
|
|
34
|
+
this.error(
|
|
35
|
+
'{b}--sprint{b} and {b}--all-boards{b} cannot be used together, because sprint numbers ' +
|
|
36
|
+
'and names are relative to a single board'
|
|
37
|
+
);
|
|
38
|
+
return null;
|
|
39
|
+
}
|
|
40
|
+
const kanbn = await workspace.boardFromArgs(args);
|
|
41
|
+
if (!await kanbn.workspaceInitialised()) {
|
|
42
|
+
this.error('Kanbn has not been initialised in this folder\nTry running: {b}kanbn init{b}');
|
|
43
|
+
return null;
|
|
44
|
+
}
|
|
45
|
+
if (!await kanbn.initialised()) {
|
|
46
|
+
this.error(
|
|
47
|
+
`Board "${await kanbn.resolveBoardSlug()}" doesn't exist\n` +
|
|
48
|
+
'Try running: {b}kanbn boards{b}'
|
|
49
|
+
);
|
|
50
|
+
return null;
|
|
51
|
+
}
|
|
52
|
+
return kanbn;
|
|
53
|
+
},
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Resolve the boards a command targets, along with the column to use on each. `-b` is repeatable
|
|
57
|
+
* and pairs positionally with `-c`: board i uses the i-th `-c` if there is one, otherwise the
|
|
58
|
+
* first `-c`, otherwise that board's first column (Q5). Prints an error and returns null if any
|
|
59
|
+
* board can't be resolved
|
|
60
|
+
* @param {object} workspace The workspace-scoped Kanbn instance
|
|
61
|
+
* @param {object} args The parsed command arguments
|
|
62
|
+
* @return {Promise<object[]|null>} One entry per target board, or null on error
|
|
63
|
+
*/
|
|
64
|
+
async resolveBoardTargets(workspace, args) {
|
|
65
|
+
const boardArgs = args.board === undefined
|
|
66
|
+
? []
|
|
67
|
+
: this.arrayArg(args.board).filter(slug => slug !== undefined && slug !== '');
|
|
68
|
+
const columnArgs = args.column === undefined ? [] : this.arrayArg(args.column);
|
|
69
|
+
const slugs = boardArgs.length ? boardArgs : [await workspace.resolveTargetBoard(null)];
|
|
70
|
+
|
|
71
|
+
// With a single target board an unknown column is an error, exactly as it has always been.
|
|
72
|
+
// Falling back only makes sense when several boards are targeted at once, where a column can
|
|
73
|
+
// legitimately exist on one and not another
|
|
74
|
+
const allowFallback = slugs.length > 1;
|
|
75
|
+
|
|
76
|
+
const targets = [];
|
|
77
|
+
for (let i = 0; i < slugs.length; i++) {
|
|
78
|
+
const kanbn = await this.resolveBoard(workspace, { board: slugs[i] });
|
|
79
|
+
if (kanbn === null) {
|
|
80
|
+
return null;
|
|
81
|
+
}
|
|
82
|
+
let index;
|
|
83
|
+
try {
|
|
84
|
+
index = await kanbn.getIndex();
|
|
85
|
+
} catch (error) {
|
|
86
|
+
this.error(error);
|
|
87
|
+
return null;
|
|
88
|
+
}
|
|
89
|
+
const columnNames = Object.keys(index.columns);
|
|
90
|
+
if (!columnNames.length) {
|
|
91
|
+
this.error('No columns defined in the index\nTry running {b}kanbn init -c "column name"{b}');
|
|
92
|
+
return null;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
const requestedColumn = columnArgs.length
|
|
96
|
+
? (columnArgs[i] !== undefined ? columnArgs[i] : columnArgs[0])
|
|
97
|
+
: null;
|
|
98
|
+
let columnName = requestedColumn === null ? columnNames[0] : requestedColumn;
|
|
99
|
+
let fallback = false;
|
|
100
|
+
if (!(columnName in index.columns)) {
|
|
101
|
+
if (!allowFallback) {
|
|
102
|
+
this.error(`Column "${columnName}" doesn't exist`);
|
|
103
|
+
return null;
|
|
104
|
+
}
|
|
105
|
+
columnName = columnNames[0];
|
|
106
|
+
fallback = true;
|
|
107
|
+
}
|
|
108
|
+
targets.push({
|
|
109
|
+
kanbn,
|
|
110
|
+
index,
|
|
111
|
+
slug: await kanbn.resolveBoardSlug(),
|
|
112
|
+
columnName,
|
|
113
|
+
requestedColumn,
|
|
114
|
+
fallback
|
|
115
|
+
});
|
|
116
|
+
}
|
|
117
|
+
return targets;
|
|
118
|
+
},
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* Tell the user when a target board didn't have the column that was asked for, so that a task
|
|
122
|
+
* quietly landing somewhere else is never a surprise
|
|
123
|
+
* @param {object[]} targets Targets from resolveBoardTargets()
|
|
124
|
+
* @param {boolean} show False to suppress the notice
|
|
125
|
+
*/
|
|
126
|
+
showColumnFallbackNotices(targets, show = true) {
|
|
127
|
+
if (!show) {
|
|
128
|
+
return;
|
|
129
|
+
}
|
|
130
|
+
for (const target of targets.filter(t => t.fallback)) {
|
|
131
|
+
console.log(this.replaceTags(
|
|
132
|
+
`{d}Board "${target.slug}" has no column "${target.requestedColumn}" - using "${target.columnName}"{d}`
|
|
133
|
+
));
|
|
134
|
+
}
|
|
135
|
+
},
|
|
136
|
+
|
|
137
|
+
/**
|
|
138
|
+
* Show a hint about how many boards this workspace has, so that a command showing one board
|
|
139
|
+
* doesn't look like it's showing everything. Suppressed for machine-readable output and when the
|
|
140
|
+
* board sets verbose to false
|
|
141
|
+
* @param {object[]} boards The list of boards
|
|
142
|
+
* @param {boolean} show False to suppress the hint
|
|
143
|
+
*/
|
|
144
|
+
showBoardCountHint(boards, show = true) {
|
|
145
|
+
if (!show || boards.length < 2) {
|
|
146
|
+
return;
|
|
147
|
+
}
|
|
148
|
+
console.log(this.replaceTags(`\n{d}${boards.length} boards available -{d} {b}kanbn boards{b}`));
|
|
149
|
+
},
|
|
150
|
+
|
|
151
|
+
/**
|
|
152
|
+
* Show a hint about tasks whose dates don't match the column they're in. Hints are suppressed
|
|
153
|
+
* for machine-readable output and when the board sets verbose to false
|
|
154
|
+
* @param {object[]} drift Drift entries from kanbn.getDateDrift()
|
|
155
|
+
* @param {boolean} show False to suppress the hint
|
|
156
|
+
*/
|
|
157
|
+
showDateDriftHint(drift, show = true) {
|
|
158
|
+
if (!show || !drift.length) {
|
|
159
|
+
return;
|
|
160
|
+
}
|
|
161
|
+
const fixable = drift.filter(d => d.fixable).length;
|
|
162
|
+
const summary =
|
|
163
|
+
`${drift.length} ${drift.length === 1 ? 'task has' : 'tasks have'} dates that don't match ` +
|
|
164
|
+
`${drift.length === 1 ? 'its' : 'their'} column`;
|
|
165
|
+
console.log(this.replaceTags(
|
|
166
|
+
`\n{d}${summary}${fixable ? ' - run{d} {b}kanbn validate --fix{b}' : '{d}'}` +
|
|
167
|
+
(fixable ? ` {d}to fill in the missing ${fixable === 1 ? 'date' : 'dates'}{d}` : '')
|
|
168
|
+
));
|
|
169
|
+
},
|
|
170
|
+
|
|
22
171
|
/**
|
|
23
172
|
* Convert a string to simplified paramcase, e.g:
|
|
24
173
|
* PascalCase -> pascalcase
|
|
@@ -34,7 +183,12 @@ module.exports = (() => {
|
|
|
34
183
|
)
|
|
35
184
|
.split(/[\s!?.,@:;|\\/"'`£$%\^&*{}[\]()<>~#+\-=_¬]+/g)
|
|
36
185
|
.join('-')
|
|
37
|
-
.replace(/(^-|-$)/g, '')
|
|
186
|
+
.replace(/(^-|-$)/g, '')
|
|
187
|
+
// The word-boundary replacement above only matches an uppercase run that is followed by
|
|
188
|
+
// another character, so a trailing capital ("Task A" -> "task-A") and any non-ASCII capital
|
|
189
|
+
// ("Ünïcodé" -> "Ünïcodé") would otherwise survive. Board slugs use this function, so ids
|
|
190
|
+
// must be lowercase throughout.
|
|
191
|
+
.toLowerCase();
|
|
38
192
|
},
|
|
39
193
|
|
|
40
194
|
/**
|
|
@@ -141,6 +295,17 @@ module.exports = (() => {
|
|
|
141
295
|
return s;
|
|
142
296
|
},
|
|
143
297
|
|
|
298
|
+
/**
|
|
299
|
+
* Coerce a terminal width into a usable number. The terminal width is unavailable when stdout
|
|
300
|
+
* isn't a TTY (i.e. when the output is piped or redirected), so fall back to a default
|
|
301
|
+
* @param {any} width The reported terminal width
|
|
302
|
+
* @param {number} [fallback=80] The width to use when the reported width is unusable
|
|
303
|
+
* @return {number} A usable terminal width
|
|
304
|
+
*/
|
|
305
|
+
terminalWidth(width, fallback = 80) {
|
|
306
|
+
return Number.isInteger(width) && width > 0 ? width : fallback;
|
|
307
|
+
},
|
|
308
|
+
|
|
144
309
|
/**
|
|
145
310
|
* Zip 2 arrays together, i.e. ([1, 2, 3], [a, b, c]) => [[1, a], [2, b], [3, c]]
|
|
146
311
|
* @param {any[]} a
|
package/PLANS.md
DELETED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|