@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
|
@@ -1,7 +1,10 @@
|
|
|
1
|
-
const
|
|
1
|
+
const workspace = require('../main');
|
|
2
2
|
const utility = require('../utility');
|
|
3
3
|
const yaml = require('yamljs');
|
|
4
4
|
|
|
5
|
+
// The board this command targets - set from --board before anything else runs
|
|
6
|
+
let kanbn = workspace;
|
|
7
|
+
|
|
5
8
|
/**
|
|
6
9
|
* Show a list of tasks whose dates disagree with the column they're in
|
|
7
10
|
* @param {object[]} drift
|
|
@@ -27,18 +30,36 @@ function showDrift(drift, json) {
|
|
|
27
30
|
}
|
|
28
31
|
}
|
|
29
32
|
|
|
33
|
+
/**
|
|
34
|
+
* Show warnings about a multi-board workspace
|
|
35
|
+
* @param {object[]} warnings
|
|
36
|
+
*/
|
|
37
|
+
function showBoardWarnings(warnings) {
|
|
38
|
+
if (!warnings.length) {
|
|
39
|
+
return;
|
|
40
|
+
}
|
|
41
|
+
console.log(
|
|
42
|
+
`${warnings.length} ${warnings.length === 1 ? 'warning' : 'warnings'}:`
|
|
43
|
+
);
|
|
44
|
+
for (const warning of warnings) {
|
|
45
|
+
console.log(` ${warning.board === null ? 'workspace' : warning.board}: ${warning.message}`);
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
30
49
|
module.exports = async args => {
|
|
31
50
|
|
|
32
|
-
//
|
|
33
|
-
|
|
34
|
-
|
|
51
|
+
// Work out which board this command targets
|
|
52
|
+
const scoped = await utility.resolveBoard(workspace, args);
|
|
53
|
+
if (scoped === null) {
|
|
35
54
|
return;
|
|
36
55
|
}
|
|
56
|
+
kanbn = scoped;
|
|
37
57
|
|
|
38
|
-
// Validate kanbn files
|
|
58
|
+
// Validate kanbn files, on this board or on every board in the workspace
|
|
59
|
+
const allBoards = !!args['all-boards'];
|
|
39
60
|
let result;
|
|
40
61
|
try {
|
|
41
|
-
result = await kanbn.validate(args.save);
|
|
62
|
+
result = allBoards ? await kanbn.validateAllBoards(args.save) : await kanbn.validate(args.save);
|
|
42
63
|
} catch (error) {
|
|
43
64
|
utility.error(error);
|
|
44
65
|
return;
|
|
@@ -54,6 +75,20 @@ module.exports = async args => {
|
|
|
54
75
|
return;
|
|
55
76
|
}
|
|
56
77
|
|
|
78
|
+
// Report multi-board problems. These are warnings rather than errors: each one describes a
|
|
79
|
+
// workspace that still works, just not the way its author probably meant it to. A workspace with
|
|
80
|
+
// one board has none of these problems to have, and its output is exactly what it always was
|
|
81
|
+
const multiBoard = (await kanbn.listBoards()).length > 1;
|
|
82
|
+
let boardWarnings = [];
|
|
83
|
+
if (multiBoard) {
|
|
84
|
+
try {
|
|
85
|
+
boardWarnings = await kanbn.findBoardWarnings();
|
|
86
|
+
} catch (error) {
|
|
87
|
+
utility.error(error);
|
|
88
|
+
return;
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
57
92
|
// Backfill missing started and completed dates
|
|
58
93
|
if (args.fix) {
|
|
59
94
|
let fixed;
|
|
@@ -86,8 +121,26 @@ module.exports = async args => {
|
|
|
86
121
|
utility.error(error);
|
|
87
122
|
return;
|
|
88
123
|
}
|
|
124
|
+
if (args.json) {
|
|
125
|
+
|
|
126
|
+
// A multi-board workspace has warnings to report as well as drift, so JSON output carries both.
|
|
127
|
+
// With a single board it stays the bare drift list it has always been
|
|
128
|
+
if (multiBoard) {
|
|
129
|
+
console.log(JSON.stringify({ drift, warnings: boardWarnings }, null, 2));
|
|
130
|
+
return;
|
|
131
|
+
}
|
|
132
|
+
if (drift.length) {
|
|
133
|
+
showDrift(drift, true);
|
|
134
|
+
return;
|
|
135
|
+
}
|
|
136
|
+
console.log('Everything OK');
|
|
137
|
+
return;
|
|
138
|
+
}
|
|
89
139
|
if (drift.length) {
|
|
90
|
-
showDrift(drift,
|
|
140
|
+
showDrift(drift, false);
|
|
141
|
+
}
|
|
142
|
+
showBoardWarnings(boardWarnings);
|
|
143
|
+
if (drift.length || boardWarnings.length) {
|
|
91
144
|
return;
|
|
92
145
|
}
|
|
93
146
|
console.log('Everything OK');
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
const { execFileSync } = require('child_process');
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Get the git username of the current user, or null if it can't be found. This replaces the
|
|
5
|
+
* git-user-name package, which depends on a version of parse-git-config with an unpatched
|
|
6
|
+
* prototype pollution vulnerability
|
|
7
|
+
* @return {string|null} The git username, or null if git isn't available or no username is set
|
|
8
|
+
*/
|
|
9
|
+
module.exports = () => {
|
|
10
|
+
try {
|
|
11
|
+
const username = execFileSync('git', ['config', 'user.name'], {
|
|
12
|
+
encoding: 'utf8',
|
|
13
|
+
stdio: ['ignore', 'pipe', 'ignore']
|
|
14
|
+
}).trim();
|
|
15
|
+
return username || null;
|
|
16
|
+
} catch (error) {
|
|
17
|
+
return null;
|
|
18
|
+
}
|
|
19
|
+
};
|
package/src/main.d.ts
CHANGED
|
@@ -4,7 +4,29 @@ declare type config = {
|
|
|
4
4
|
mainFolder?: string,
|
|
5
5
|
indexFile?: string,
|
|
6
6
|
taskFolder?: string,
|
|
7
|
-
archiveFolder?: string
|
|
7
|
+
archiveFolder?: string,
|
|
8
|
+
defaultBoard?: string,
|
|
9
|
+
boards?: {
|
|
10
|
+
exclude?: string[],
|
|
11
|
+
order?: string[],
|
|
12
|
+
[slug: string]: any
|
|
13
|
+
}
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
declare type board = {
|
|
17
|
+
slug: string,
|
|
18
|
+
path: string,
|
|
19
|
+
name: string,
|
|
20
|
+
description: string,
|
|
21
|
+
main: boolean
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
declare type boardSummary = board & {
|
|
25
|
+
columns: number,
|
|
26
|
+
tasks: number,
|
|
27
|
+
completed: number,
|
|
28
|
+
completedPercentage: number,
|
|
29
|
+
modified: Date | null
|
|
8
30
|
};
|
|
9
31
|
|
|
10
32
|
declare type index = {
|
|
@@ -59,11 +81,24 @@ declare type sprint = {
|
|
|
59
81
|
};
|
|
60
82
|
|
|
61
83
|
export class Kanbn {
|
|
62
|
-
constructor(root?: any);
|
|
84
|
+
constructor(root?: any, options?: { board?: string, caches?: any });
|
|
63
85
|
ROOT: string;
|
|
64
86
|
CONFIG_YAML: string;
|
|
65
87
|
CONFIG_JSON: string;
|
|
66
88
|
configMemo: any;
|
|
89
|
+
/**
|
|
90
|
+
* The board this instance is scoped to, or null for the main board
|
|
91
|
+
*/
|
|
92
|
+
boardSlug: string | null;
|
|
93
|
+
/**
|
|
94
|
+
* Get a copy of this instance scoped to another board, sharing this one's cached config
|
|
95
|
+
* @param {?string} [slug=null] The board slug, or null/"main"/"default" for the main board
|
|
96
|
+
*/
|
|
97
|
+
board(slug?: string | null): Kanbn;
|
|
98
|
+
/**
|
|
99
|
+
* Alias for board()
|
|
100
|
+
*/
|
|
101
|
+
withBoard(slug?: string | null): Kanbn;
|
|
67
102
|
/**
|
|
68
103
|
* Check if a separate config file exists
|
|
69
104
|
* @returns {Promise<boolean>} True if a config file exists
|
|
@@ -108,7 +143,25 @@ export class Kanbn {
|
|
|
108
143
|
*/
|
|
109
144
|
getMainFolder(): Promise<string>;
|
|
110
145
|
/**
|
|
111
|
-
* Get the index
|
|
146
|
+
* Get the main board's slug, i.e. the index file name without its extension
|
|
147
|
+
* @return {Promise<string>} The main board slug
|
|
148
|
+
*/
|
|
149
|
+
getMainBoardSlug(): Promise<string>;
|
|
150
|
+
/**
|
|
151
|
+
* Resolve a board slug, mapping the reserved aliases and an absent slug onto the main board
|
|
152
|
+
* @param {?string} [slug] The board slug, defaulting to this instance's board
|
|
153
|
+
*/
|
|
154
|
+
resolveBoardSlug(slug?: string | null): Promise<string>;
|
|
155
|
+
/**
|
|
156
|
+
* Check if a slug refers to the main board
|
|
157
|
+
*/
|
|
158
|
+
isMainBoard(slug?: string | null): Promise<boolean>;
|
|
159
|
+
/**
|
|
160
|
+
* Get the file path for a board
|
|
161
|
+
*/
|
|
162
|
+
getBoardPath(slug?: string | null): Promise<string>;
|
|
163
|
+
/**
|
|
164
|
+
* Get the index path, i.e. the path of the board this instance is scoped to
|
|
112
165
|
* @return {Promise<string>} The kanbn index path
|
|
113
166
|
*/
|
|
114
167
|
getIndexPath(): Promise<string>;
|
|
@@ -154,6 +207,126 @@ export class Kanbn {
|
|
|
154
207
|
* @param {object} indexData Index data to save
|
|
155
208
|
*/
|
|
156
209
|
saveIndex(indexData: object): Promise<void>;
|
|
210
|
+
/**
|
|
211
|
+
* Get the workspace-scoped options, from the config file or the main board's front matter
|
|
212
|
+
*/
|
|
213
|
+
getWorkspaceOptions(): Promise<Record<string, any>>;
|
|
214
|
+
/**
|
|
215
|
+
* Load the workspace options along with where they came from
|
|
216
|
+
*/
|
|
217
|
+
loadWorkspaceOptions(): Promise<{ options: Record<string, any>, fromConfig: boolean }>;
|
|
218
|
+
/**
|
|
219
|
+
* Get the options a secondary board inherits from the workspace
|
|
220
|
+
*/
|
|
221
|
+
getInheritedBoardOptions(): Promise<Record<string, any>>;
|
|
222
|
+
/**
|
|
223
|
+
* Get the per-board options declared for a board under the `boards` key in the config file
|
|
224
|
+
*/
|
|
225
|
+
getBoardConfig(slug?: string | null): Promise<Record<string, any>>;
|
|
226
|
+
/**
|
|
227
|
+
* Layer a board's own front matter options over the workspace options
|
|
228
|
+
*/
|
|
229
|
+
resolveBoardOptions(resolvedSlug: string, ownOptions: Record<string, any>): Promise<Record<string, any>>;
|
|
230
|
+
/**
|
|
231
|
+
* Work out which options belong in a secondary board's own front matter
|
|
232
|
+
*/
|
|
233
|
+
getOwnBoardOptions(resolvedSlug: string, indexData: object): Promise<Record<string, any>>;
|
|
234
|
+
/**
|
|
235
|
+
* Overwrite a board file with the specified data
|
|
236
|
+
*/
|
|
237
|
+
saveBoard(slug: string | null, indexData: object): Promise<void>;
|
|
238
|
+
/**
|
|
239
|
+
* Load a board file and parse it to an object
|
|
240
|
+
*/
|
|
241
|
+
loadBoard(slug?: string | null): Promise<index>;
|
|
242
|
+
/**
|
|
243
|
+
* Work out which board a command should target: --board, then KANBN_BOARD, then defaultBoard
|
|
244
|
+
*/
|
|
245
|
+
resolveTargetBoard(slug?: string | null): Promise<string | null>;
|
|
246
|
+
/**
|
|
247
|
+
* Get a Kanbn instance scoped to the board a command's arguments point at
|
|
248
|
+
*/
|
|
249
|
+
boardFromArgs(args?: object): Promise<Kanbn>;
|
|
250
|
+
/**
|
|
251
|
+
* Find all boards in the workspace
|
|
252
|
+
*/
|
|
253
|
+
listBoards(): Promise<board[]>;
|
|
254
|
+
/**
|
|
255
|
+
* List boards with column and task counts, completion percentage and last modified date
|
|
256
|
+
*/
|
|
257
|
+
getBoardsSummary(): Promise<boardSummary[]>;
|
|
258
|
+
/**
|
|
259
|
+
* Get every task that appears on more than one board, with the column it occupies on each
|
|
260
|
+
*/
|
|
261
|
+
getCrossBoardTasks(allTasks?: boolean): Promise<{ id: string, boards: Record<string, string> }[]>;
|
|
262
|
+
/**
|
|
263
|
+
* Check if a board exists
|
|
264
|
+
*/
|
|
265
|
+
boardExists(slug: string): Promise<boolean>;
|
|
266
|
+
/**
|
|
267
|
+
* Get the value to record in a history event's `board` key, or null for the main board
|
|
268
|
+
*/
|
|
269
|
+
historyBoard(): Promise<string | null>;
|
|
270
|
+
/**
|
|
271
|
+
* Add an existing task to this board
|
|
272
|
+
*/
|
|
273
|
+
addTaskToBoard(taskId: string, columnName: string): Promise<string>;
|
|
274
|
+
/**
|
|
275
|
+
* Check if a task file exists, regardless of whether any board references it
|
|
276
|
+
*/
|
|
277
|
+
taskFileExists(taskId: string): Promise<boolean>;
|
|
278
|
+
/**
|
|
279
|
+
* Boards named in an archived task's metadata that no longer existed when it was restored
|
|
280
|
+
*/
|
|
281
|
+
lastRestoreWarnings: string[];
|
|
282
|
+
/**
|
|
283
|
+
* Create a new board
|
|
284
|
+
*/
|
|
285
|
+
createBoard(slug: string, options?: object): Promise<string>;
|
|
286
|
+
/**
|
|
287
|
+
* Create a secondary board, or update an existing one
|
|
288
|
+
*/
|
|
289
|
+
initialiseBoard(slug: string, options?: object): Promise<string>;
|
|
290
|
+
/**
|
|
291
|
+
* Delete a board file, returning the ids of tasks that are no longer on any board
|
|
292
|
+
*/
|
|
293
|
+
deleteBoard(slug: string): Promise<string[]>;
|
|
294
|
+
/**
|
|
295
|
+
* Find the tasks that would become untracked if a board were deleted
|
|
296
|
+
*/
|
|
297
|
+
findOrphanedTasks(slug: string): Promise<string[]>;
|
|
298
|
+
/**
|
|
299
|
+
* Rename a board
|
|
300
|
+
*/
|
|
301
|
+
renameBoard(slug: string, newSlug: string, newName?: string | null): Promise<string>;
|
|
302
|
+
/**
|
|
303
|
+
* Find every board that references a task, and the column it occupies on each
|
|
304
|
+
*/
|
|
305
|
+
findTaskBoards(taskId: string): Promise<Record<string, string>>;
|
|
306
|
+
/**
|
|
307
|
+
* Alias for findTaskBoards()
|
|
308
|
+
*/
|
|
309
|
+
getTaskBoardColumns(taskId: string): Promise<Record<string, string>>;
|
|
310
|
+
/**
|
|
311
|
+
* Get the slugs that can't be used for a board
|
|
312
|
+
*/
|
|
313
|
+
getReservedBoardSlugs(): Promise<string[]>;
|
|
314
|
+
/**
|
|
315
|
+
* Check that a slug can be used for a new board, throwing if it can't
|
|
316
|
+
*/
|
|
317
|
+
validateBoardSlug(slug: string): Promise<string>;
|
|
318
|
+
/**
|
|
319
|
+
* Get the boards config from the config file, i.e. the exclude list and display order
|
|
320
|
+
*/
|
|
321
|
+
getBoardsConfig(): Promise<{ exclude: string[], order: string[] }>;
|
|
322
|
+
/**
|
|
323
|
+
* Find tasks that no board references at all
|
|
324
|
+
*/
|
|
325
|
+
findWorkspaceUntrackedTasks(): Promise<Set<string>>;
|
|
326
|
+
/**
|
|
327
|
+
* Find tasks that other boards track but this one doesn't
|
|
328
|
+
*/
|
|
329
|
+
findTasksOnOtherBoards(): Promise<Record<string, Record<string, string>>>;
|
|
157
330
|
/**
|
|
158
331
|
* Load the index file and parse it to an object
|
|
159
332
|
* @return {Promise<object>} The index object
|
|
@@ -201,6 +374,11 @@ export class Kanbn {
|
|
|
201
374
|
* @return {Promise<boolean>} True if the current working directory has been initialised, otherwise false
|
|
202
375
|
*/
|
|
203
376
|
initialised(): Promise<boolean>;
|
|
377
|
+
/**
|
|
378
|
+
* Check if the workspace has been initialised, regardless of which board this instance is scoped to
|
|
379
|
+
* @return {Promise<boolean>} True if the main board exists
|
|
380
|
+
*/
|
|
381
|
+
workspaceInitialised(): Promise<boolean>;
|
|
204
382
|
/**
|
|
205
383
|
* Initialise a kanbn board in the current working directory
|
|
206
384
|
* @param {object} [options={}] Initial columns and other config options
|
|
@@ -265,14 +443,14 @@ export class Kanbn {
|
|
|
265
443
|
* @param {boolean} [relative=false] Treat the position argument as relative instead of absolute
|
|
266
444
|
* @return {Promise<string>} The id of the task that was moved
|
|
267
445
|
*/
|
|
268
|
-
moveTask(taskId: string, columnName: string, position?: number | null, relative?: boolean): Promise<string>;
|
|
446
|
+
moveTask(taskId: string, columnName: string, position?: number | null, relative?: boolean, add?: boolean): Promise<string>;
|
|
269
447
|
/**
|
|
270
448
|
* Remove a task from the index and optionally delete the task file as well
|
|
271
449
|
* @param {string} taskId The id of the task to remove
|
|
272
450
|
* @param {boolean} [removeFile=false] True if the task file should be removed
|
|
273
451
|
* @return {Promise<string>} The id of the task that was deleted
|
|
274
452
|
*/
|
|
275
|
-
deleteTask(taskId: string, removeFile?: boolean): Promise<string>;
|
|
453
|
+
deleteTask(taskId: string, removeFile?: boolean, allBoards?: boolean): Promise<string>;
|
|
276
454
|
/**
|
|
277
455
|
* Search for indexed tasks
|
|
278
456
|
* @param {object} [filters={}] The filters to apply
|
|
@@ -347,7 +525,7 @@ export class Kanbn {
|
|
|
347
525
|
* @param {?string} [columnName=null] The column to restore the task to
|
|
348
526
|
* @return {Promise<string>} The task id
|
|
349
527
|
*/
|
|
350
|
-
restoreTask(taskId: string, columnName?: string | null): Promise<string>;
|
|
528
|
+
restoreTask(taskId: string, columnName?: string | null, singleBoard?: boolean): Promise<string>;
|
|
351
529
|
/**
|
|
352
530
|
* Nuke it from orbit, it's the only way to be sure
|
|
353
531
|
*/
|