imdone-core 1.33.4 → 1.34.1
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/cli.js +176 -103
- package/lib/card.js +1 -0
- package/lib/cli/CliControler.js +77 -22
- package/lib/cli/MDStoryParser.js +15 -15
- package/lib/cli/ProjectConfigFactory.js +20 -5
- package/lib/cli/adapters/Errors.js +8 -0
- package/lib/cli/adapters/StorageAdapter.js +4 -2
- package/lib/cli/domain/BacklogProject.js +6 -12
- package/lib/cli/prompts/MarkdownPlanPrompt.js +5 -5
- package/lib/default-settings.js +0 -1
- package/lib/plugins/plugin-manager.js +7 -4
- package/lib/project.js +11 -1
- package/lib/task.js +2 -2
- package/package.json +1 -1
package/cli.js
CHANGED
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
const { program } = require('commander');
|
|
4
4
|
const ora = require('ora')
|
|
5
5
|
const chalk = require('chalk')
|
|
6
|
+
const { ChangesExistError } = require('./lib/cli/adapters/Errors')
|
|
6
7
|
const {
|
|
7
8
|
imdoneInit,
|
|
8
9
|
planStory,
|
|
@@ -10,10 +11,13 @@ const {
|
|
|
10
11
|
addTask,
|
|
11
12
|
listTasks ,
|
|
12
13
|
completeTask,
|
|
13
|
-
showCurrentTask,
|
|
14
14
|
openBoard,
|
|
15
|
-
openTaskFile
|
|
15
|
+
openTaskFile,
|
|
16
|
+
exportProject
|
|
16
17
|
} = require('./lib/cli/CliControler')
|
|
18
|
+
const {
|
|
19
|
+
STORY_ID
|
|
20
|
+
} = require('./lib/cli/domain/BacklogProject').constants
|
|
17
21
|
const package = require('./package.json')
|
|
18
22
|
|
|
19
23
|
const { log } = hideLogs()
|
|
@@ -28,114 +32,29 @@ function actionCancelled() {
|
|
|
28
32
|
log(chalk.bgRed('Action canceled'))
|
|
29
33
|
}
|
|
30
34
|
|
|
31
|
-
const STORY_OPTION =
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
.command('init')
|
|
35
|
-
.description('initialize backlog')
|
|
36
|
-
.action(async function () {
|
|
37
|
-
await imdoneInit()
|
|
38
|
-
})
|
|
39
|
-
|
|
40
|
-
program
|
|
41
|
-
.command('plan')
|
|
42
|
-
.description('Plan a story with tasks and DoD')
|
|
43
|
-
.action(async function () {
|
|
44
|
-
let markdown
|
|
45
|
-
if (process.stdin.isTTY) return (await planStory(markdown, log))
|
|
46
|
-
|
|
47
|
-
const stdin = process.stdin;
|
|
48
|
-
spinner.start()
|
|
49
|
-
|
|
50
|
-
markdown = ''
|
|
51
|
-
|
|
52
|
-
stdin.on('readable', function() {
|
|
53
|
-
var chunk = stdin.read();
|
|
54
|
-
if(chunk !== null){
|
|
55
|
-
markdown += chunk;
|
|
56
|
-
}
|
|
57
|
-
});
|
|
58
|
-
stdin.on('end', async function() {
|
|
59
|
-
await planStory(markdown, log)
|
|
60
|
-
spinner.stop()
|
|
61
|
-
});
|
|
62
|
-
})
|
|
63
|
-
|
|
64
|
-
program
|
|
65
|
-
.command('open')
|
|
66
|
-
.description('open the current or selected task in the default markdown editor')
|
|
67
|
-
.action(async function () {
|
|
68
|
-
await openTaskFile(log)
|
|
69
|
-
})
|
|
35
|
+
const STORY_OPTION = `-s, --${STORY_ID} `
|
|
36
|
+
const STORY_OPTION_OPTIONAL = `${STORY_OPTION}[${STORY_ID}]`
|
|
37
|
+
const STORY_OPTION_REQUIRED = `${STORY_OPTION}<${STORY_ID}>`
|
|
70
38
|
|
|
71
|
-
program
|
|
72
|
-
.
|
|
73
|
-
.description('open the current task in imdone')
|
|
74
|
-
.action(async function () {
|
|
75
|
-
spinner.start()
|
|
76
|
-
await openBoard(log)
|
|
77
|
-
spinner.stop()
|
|
78
|
-
})
|
|
79
|
-
|
|
80
|
-
program
|
|
81
|
-
.command('task')
|
|
82
|
-
.description('Show the current task')
|
|
83
|
-
.action(async function () {
|
|
84
|
-
await showCurrentTask(log)
|
|
85
|
-
})
|
|
39
|
+
const mainCmd = program
|
|
40
|
+
.version(package.version, '-v, --version', 'output the current version')
|
|
86
41
|
|
|
87
|
-
|
|
88
|
-
.command('
|
|
89
|
-
.description('
|
|
42
|
+
mainCmd
|
|
43
|
+
.command('export')
|
|
44
|
+
.description('Export the current project')
|
|
90
45
|
.action(async function () {
|
|
91
|
-
const taskId = this.args.length > 0 ? this.args[0]: null
|
|
92
46
|
try {
|
|
93
|
-
await
|
|
47
|
+
await exportProject(log)
|
|
94
48
|
} catch (e) {
|
|
95
49
|
console.error(e)
|
|
96
50
|
actionCancelled()
|
|
51
|
+
} finally {
|
|
52
|
+
process.exit(0)
|
|
97
53
|
}
|
|
98
54
|
})
|
|
99
55
|
|
|
100
|
-
|
|
101
|
-
.command('done')
|
|
102
|
-
.description('Mark the current task as done')
|
|
103
|
-
.action(async function () {
|
|
104
|
-
try {
|
|
105
|
-
await completeTask(log)
|
|
106
|
-
} catch (e) {
|
|
107
|
-
actionCancelled()
|
|
108
|
-
}
|
|
109
|
-
})
|
|
56
|
+
// addDevCmd(mainCmd)
|
|
110
57
|
|
|
111
|
-
program
|
|
112
|
-
.command('add-task <task-content>')
|
|
113
|
-
.description('add a task')
|
|
114
|
-
.option(...STORY_OPTION)
|
|
115
|
-
.option('-g, --group <group>', 'The group to add this task to')
|
|
116
|
-
.action(async function () {
|
|
117
|
-
let { storyId, group } = this.opts()
|
|
118
|
-
try {
|
|
119
|
-
await addTask({content: this.args[0], storyId, group, log})
|
|
120
|
-
} catch (e) {
|
|
121
|
-
actionCancelled()
|
|
122
|
-
}
|
|
123
|
-
})
|
|
124
|
-
|
|
125
|
-
program
|
|
126
|
-
.command('ls')
|
|
127
|
-
.description('list tasks')
|
|
128
|
-
.option(...STORY_OPTION)
|
|
129
|
-
.option('-f, --filter <filter>', 'The filter to use')
|
|
130
|
-
.option('-j, --json', 'Output as json')
|
|
131
|
-
.action(async function () {
|
|
132
|
-
let {storyId, filter, json } = this.opts()
|
|
133
|
-
try {
|
|
134
|
-
await listTasks({storyId, filter, json, log})
|
|
135
|
-
} catch (e) {
|
|
136
|
-
actionCancelled()
|
|
137
|
-
}
|
|
138
|
-
})
|
|
139
58
|
program.parse();
|
|
140
59
|
|
|
141
60
|
function hideLogs() {
|
|
@@ -143,10 +62,164 @@ function hideLogs() {
|
|
|
143
62
|
const info = console.info
|
|
144
63
|
const warn = console.warn
|
|
145
64
|
const logQueue = {warn: [], info: [], log: []}
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
65
|
+
if (!process.env.DEBUG) {
|
|
66
|
+
Object.keys(logQueue).forEach((key) => {
|
|
67
|
+
console[key] = function(...args) {
|
|
68
|
+
logQueue[key].push(args)
|
|
69
|
+
}
|
|
70
|
+
})
|
|
71
|
+
}
|
|
72
|
+
return {log, info, warn, logQueue}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
async function planAStory(markdown, storyId, log) {
|
|
77
|
+
try {
|
|
78
|
+
await planStory(markdown, storyId, log)
|
|
79
|
+
} catch (e) {
|
|
80
|
+
log(chalk.yellowBright(e.message))
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
function addDevCmd(mainCmd) {
|
|
85
|
+
const devCmd = mainCmd
|
|
86
|
+
.command('dev')
|
|
87
|
+
.description('Plan and track your storys and tasks')
|
|
88
|
+
|
|
89
|
+
devCmd
|
|
90
|
+
.command('init')
|
|
91
|
+
.description('initialize backlog')
|
|
92
|
+
.action(async function () {
|
|
93
|
+
try {
|
|
94
|
+
await imdoneInit()
|
|
95
|
+
} catch (e) {
|
|
96
|
+
log(chalk.yellowBright(e.message))
|
|
97
|
+
} finally {
|
|
98
|
+
process.exit(0)
|
|
99
|
+
}
|
|
100
|
+
})
|
|
101
|
+
|
|
102
|
+
devCmd
|
|
103
|
+
.command('plan')
|
|
104
|
+
.option(STORY_OPTION_OPTIONAL, 'Update an existing story\'s tasks')
|
|
105
|
+
.description('Plan a story with tasks and DoD')
|
|
106
|
+
.action(async function () {
|
|
107
|
+
let markdown
|
|
108
|
+
let { storyId } = this.opts()
|
|
109
|
+
if (process.stdin.isTTY) {
|
|
110
|
+
await planAStory(markdown, storyId, log)
|
|
111
|
+
process.exit(0)
|
|
112
|
+
} else {
|
|
113
|
+
|
|
114
|
+
const stdin = process.stdin;
|
|
115
|
+
spinner.start()
|
|
116
|
+
|
|
117
|
+
markdown = ''
|
|
118
|
+
|
|
119
|
+
stdin.on('readable', function() {
|
|
120
|
+
var chunk = stdin.read();
|
|
121
|
+
if(chunk !== null){
|
|
122
|
+
markdown += chunk;
|
|
123
|
+
}
|
|
124
|
+
});
|
|
125
|
+
stdin.on('end', async function() {
|
|
126
|
+
await planAStory(markdown, storyId, log)
|
|
127
|
+
spinner.stop()
|
|
128
|
+
process.exit(0)
|
|
129
|
+
});
|
|
130
|
+
}
|
|
131
|
+
})
|
|
132
|
+
|
|
133
|
+
devCmd
|
|
134
|
+
.command('open')
|
|
135
|
+
.description('Open the current or selected task in the default markdown editor')
|
|
136
|
+
.action(async function () {
|
|
137
|
+
try {
|
|
138
|
+
await openTaskFile(log)
|
|
139
|
+
} catch (e) {
|
|
140
|
+
log(chalk.yellowBright(e.message))
|
|
141
|
+
} finally {
|
|
142
|
+
process.exit(0)
|
|
143
|
+
}
|
|
144
|
+
})
|
|
145
|
+
|
|
146
|
+
devCmd
|
|
147
|
+
.command('board')
|
|
148
|
+
.description('Open the current task in imdone')
|
|
149
|
+
.action(async function () {
|
|
150
|
+
try {
|
|
151
|
+
spinner.start()
|
|
152
|
+
await openBoard(log)
|
|
153
|
+
spinner.stop()
|
|
154
|
+
} catch (e) {
|
|
155
|
+
log(chalk.yellowBright(e.message))
|
|
156
|
+
} finally {
|
|
157
|
+
process.exit(0)
|
|
158
|
+
}
|
|
159
|
+
})
|
|
160
|
+
|
|
161
|
+
devCmd
|
|
162
|
+
.command('start [task-id]')
|
|
163
|
+
.description('Start a task by id')
|
|
164
|
+
.action(async function () {
|
|
165
|
+
const taskId = this.args.length > 0 ? this.args[0]: null
|
|
166
|
+
try {
|
|
167
|
+
await startTask(taskId, log)
|
|
168
|
+
} catch (e) {
|
|
169
|
+
if (e instanceof ChangesExistError) {
|
|
170
|
+
log(chalk.yellowBright(e.message))
|
|
171
|
+
} else {
|
|
172
|
+
actionCancelled()
|
|
173
|
+
}
|
|
174
|
+
} finally {
|
|
175
|
+
process.exit(0)
|
|
176
|
+
}
|
|
177
|
+
})
|
|
178
|
+
|
|
179
|
+
devCmd
|
|
180
|
+
.command('done')
|
|
181
|
+
.description('Mark the current task as done')
|
|
182
|
+
.action(async function () {
|
|
183
|
+
try {
|
|
184
|
+
await completeTask(log)
|
|
185
|
+
} catch (e) {
|
|
186
|
+
actionCancelled()
|
|
187
|
+
} finally {
|
|
188
|
+
process.exit(0)
|
|
189
|
+
}
|
|
190
|
+
})
|
|
191
|
+
|
|
192
|
+
devCmd
|
|
193
|
+
.command('add-task <task-content>')
|
|
194
|
+
.description('Add a task to a story')
|
|
195
|
+
.option(STORY_OPTION_REQUIRED, 'The story to add this task to')
|
|
196
|
+
.option('-g, --group <group>', 'The group to add this task to')
|
|
197
|
+
.action(async function () {
|
|
198
|
+
let { storyId, group } = this.opts()
|
|
199
|
+
try {
|
|
200
|
+
await addTask({content: this.args[0], storyId, group, log})
|
|
201
|
+
} catch (e) {
|
|
202
|
+
actionCancelled()
|
|
203
|
+
} finally {
|
|
204
|
+
process.exit(0)
|
|
205
|
+
}
|
|
206
|
+
})
|
|
207
|
+
|
|
208
|
+
devCmd
|
|
209
|
+
.command('ls')
|
|
210
|
+
.description('list tasks')
|
|
211
|
+
.option(STORY_OPTION_REQUIRED, 'The story to list tasks for')
|
|
212
|
+
.option('-f, --filter <filter>', 'The filter to use')
|
|
213
|
+
.option('-j, --json', 'Output as json')
|
|
214
|
+
.action(async function () {
|
|
215
|
+
let {storyId, filter, json } = this.opts()
|
|
216
|
+
try {
|
|
217
|
+
await listTasks({storyId, filter, json, log})
|
|
218
|
+
} catch (e) {
|
|
219
|
+
|
|
220
|
+
actionCancelled()
|
|
221
|
+
} finally {
|
|
222
|
+
process.exit(0)
|
|
149
223
|
}
|
|
150
224
|
})
|
|
151
|
-
return {log, info, warn, logQueue}
|
|
152
225
|
}
|
package/lib/card.js
CHANGED
package/lib/cli/CliControler.js
CHANGED
|
@@ -5,8 +5,10 @@ const eol = require('eol')
|
|
|
5
5
|
const chalk = require('chalk')
|
|
6
6
|
const BacklogProject = require('./domain/BacklogProject')
|
|
7
7
|
const StoryProject = require('./domain/StoryProject')
|
|
8
|
+
const { createFileSystemProject } = require('../project-factory.js')
|
|
8
9
|
const commandExists = require('command-exists')
|
|
9
10
|
const git = simpleGit()
|
|
11
|
+
const _path = require('path')
|
|
10
12
|
const {
|
|
11
13
|
importMarkdown,
|
|
12
14
|
} = require('./MDStoryParser')
|
|
@@ -21,7 +23,8 @@ const applicationContext = () => {
|
|
|
21
23
|
|
|
22
24
|
const {
|
|
23
25
|
configureProject,
|
|
24
|
-
defaultProjectPath
|
|
26
|
+
defaultProjectPath,
|
|
27
|
+
projectExists,
|
|
25
28
|
} = require('./ProjectConfigFactory')
|
|
26
29
|
|
|
27
30
|
const {promptForMarkdownPlan} = require('./prompts/MarkdownPlanPrompt.js')
|
|
@@ -30,10 +33,64 @@ const {
|
|
|
30
33
|
UNGROUPED_TASKS
|
|
31
34
|
} = BacklogProject.constants
|
|
32
35
|
|
|
36
|
+
const replacer = (key, value) =>
|
|
37
|
+
value instanceof Object && !(value instanceof Array) ?
|
|
38
|
+
Object.keys(value)
|
|
39
|
+
.sort()
|
|
40
|
+
.reduce((sorted, key) => {
|
|
41
|
+
sorted[key] = value[key];
|
|
42
|
+
return sorted
|
|
43
|
+
}, {}) :
|
|
44
|
+
value;
|
|
45
|
+
|
|
33
46
|
module.exports = CliController = {}
|
|
34
47
|
|
|
48
|
+
CliController.exportProject = async function (log, verbose = false) {
|
|
49
|
+
const project = createFileSystemProject({path: process.env.PWD})
|
|
50
|
+
await project.init()
|
|
51
|
+
let jsonOutput = verbose
|
|
52
|
+
? project.toImdoneJSON()
|
|
53
|
+
: (() => {
|
|
54
|
+
const result = {}
|
|
55
|
+
const imdoneJSON = project.toImdoneJSON()
|
|
56
|
+
const lists = imdoneJSON.lists.map(list => {
|
|
57
|
+
list.tasks = list.tasks.map(task => {
|
|
58
|
+
const result = {
|
|
59
|
+
created: task.created,
|
|
60
|
+
completed: task.completed,
|
|
61
|
+
due: task.due,
|
|
62
|
+
path: _path.join(task.source.repoId, task.source.path),
|
|
63
|
+
meta: task.allMeta,
|
|
64
|
+
tags: task.allTags,
|
|
65
|
+
context: task.allContext,
|
|
66
|
+
line: task.line,
|
|
67
|
+
content: task.content,
|
|
68
|
+
interpretedContent: task.interpretedContent,
|
|
69
|
+
lastLine: task.lastLine,
|
|
70
|
+
list: task.list,
|
|
71
|
+
progress: task.progress,
|
|
72
|
+
order: task.order,
|
|
73
|
+
beforeText: task.beforeText,
|
|
74
|
+
props: task.props,
|
|
75
|
+
}
|
|
76
|
+
return result
|
|
77
|
+
})
|
|
78
|
+
return list
|
|
79
|
+
})
|
|
80
|
+
result.lists = lists
|
|
81
|
+
result.totals = imdoneJSON.totals
|
|
82
|
+
result.tags = imdoneJSON.tags
|
|
83
|
+
|
|
84
|
+
return result
|
|
85
|
+
})()
|
|
86
|
+
|
|
87
|
+
log(JSON.stringify(jsonOutput, replacer, 3))
|
|
88
|
+
}
|
|
35
89
|
|
|
36
90
|
CliController.imdoneInit = async function () {
|
|
91
|
+
if (await projectExists(defaultProjectPath())) {
|
|
92
|
+
throw new Error('Imdone backlog is already initialized!')
|
|
93
|
+
}
|
|
37
94
|
const defaultBranch = await promptForDefaultBranch()
|
|
38
95
|
const remote = await promptForRemote()
|
|
39
96
|
await configureProject({defaultBranch, remote})
|
|
@@ -66,8 +123,9 @@ CliController.openTaskFile = async function (log) {
|
|
|
66
123
|
const storyId = await promptForStoryId()
|
|
67
124
|
const taskId = await promptForTaskId(storyId)
|
|
68
125
|
const storyProject = await StoryProject.createAndInit(defaultProjectPath(), null, storyId, applicationContext)
|
|
69
|
-
task = storyProject.getTask(taskId)
|
|
126
|
+
task = storyProject.getTask(taskId) || storyProject.getStory()
|
|
70
127
|
}
|
|
128
|
+
|
|
71
129
|
open(task.fullPath)
|
|
72
130
|
}
|
|
73
131
|
|
|
@@ -75,7 +133,7 @@ CliController.openTaskFile = async function (log) {
|
|
|
75
133
|
// TODO: This should ask for a group to add a task to
|
|
76
134
|
CliController.addTask = async function ({content, storyId, group, log}) {
|
|
77
135
|
consoleLog = log
|
|
78
|
-
storyId = await determineStoryId(
|
|
136
|
+
storyId = await determineStoryId(storyId)
|
|
79
137
|
const storyProject = await StoryProject.createAndInit(defaultProjectPath(), null, storyId, applicationContext)
|
|
80
138
|
|
|
81
139
|
const task = await storyProject.addTask({group, content})
|
|
@@ -85,7 +143,7 @@ CliController.addTask = async function ({content, storyId, group, log}) {
|
|
|
85
143
|
|
|
86
144
|
CliController.listTasks = async function ({storyId, filter=null, json, log}) {
|
|
87
145
|
consoleLog = log
|
|
88
|
-
storyId = storyId || await determineStoryId(
|
|
146
|
+
storyId = storyId || await determineStoryId(storyId)
|
|
89
147
|
|
|
90
148
|
const storyProject = await StoryProject.createAndInit(defaultProjectPath(), null, storyId, applicationContext)
|
|
91
149
|
|
|
@@ -105,20 +163,26 @@ CliController.listTasks = async function ({storyId, filter=null, json, log}) {
|
|
|
105
163
|
logGroupedTasks(log, storyProject, groupedTasks)
|
|
106
164
|
}
|
|
107
165
|
|
|
108
|
-
CliController.planStory = async function(markdown, log) {
|
|
166
|
+
CliController.planStory = async function(markdown, storyId, log = console.log) {
|
|
109
167
|
consoleLog = log
|
|
110
|
-
if (
|
|
111
|
-
|
|
112
|
-
|
|
168
|
+
if (storyId) {
|
|
169
|
+
storyId = await determineStoryId(storyId)
|
|
170
|
+
}
|
|
171
|
+
if (!markdown) {
|
|
172
|
+
markdown = await promptForMarkdownPlan()
|
|
173
|
+
}
|
|
174
|
+
return await importMarkdown({
|
|
175
|
+
projectPath: defaultProjectPath(),
|
|
176
|
+
existingStoryId: storyId,
|
|
113
177
|
markdown,
|
|
114
178
|
applicationContext
|
|
115
|
-
)
|
|
179
|
+
})
|
|
116
180
|
}
|
|
117
181
|
|
|
118
182
|
CliController.startTask = async function (taskId, log) {
|
|
119
183
|
consoleLog = log
|
|
120
184
|
if (!taskId) {
|
|
121
|
-
const storyId = await determineStoryId(
|
|
185
|
+
const storyId = await determineStoryId()
|
|
122
186
|
taskId = await promptForTaskId(storyId)
|
|
123
187
|
}
|
|
124
188
|
|
|
@@ -127,14 +191,6 @@ CliController.startTask = async function (taskId, log) {
|
|
|
127
191
|
await project.startTask(taskId)
|
|
128
192
|
}
|
|
129
193
|
|
|
130
|
-
CliController.showCurrentTask = async function (log) {
|
|
131
|
-
consoleLog = log
|
|
132
|
-
const { storyProject, task} = await getCurrentTask()
|
|
133
|
-
if (!task) return noTaskStarted(log)
|
|
134
|
-
log()
|
|
135
|
-
logTask(log, storyProject, task)
|
|
136
|
-
}
|
|
137
|
-
|
|
138
194
|
CliController.completeTask = async function (log) {
|
|
139
195
|
consoleLog = log
|
|
140
196
|
const {storyProject, task} = await getCurrentTask()
|
|
@@ -166,8 +222,8 @@ async function getCurrentTask() {
|
|
|
166
222
|
return { backlogProject, storyProject, storyId, task }
|
|
167
223
|
}
|
|
168
224
|
|
|
169
|
-
async function determineStoryId(
|
|
170
|
-
const project = await BacklogProject.createAndInit(defaultProjectPath(), null,
|
|
225
|
+
async function determineStoryId(storyId) {
|
|
226
|
+
const project = await BacklogProject.createAndInit(defaultProjectPath(), null, applicationContext)
|
|
171
227
|
storyId = storyId || project.getCurrentStoryId()
|
|
172
228
|
storyId = await promptForStoryId(storyId)
|
|
173
229
|
return storyId
|
|
@@ -243,8 +299,7 @@ function getGroupedTasks(tasks) {
|
|
|
243
299
|
}
|
|
244
300
|
|
|
245
301
|
function logGroupedTasks(log, project, groupedTasks) {
|
|
246
|
-
log('## Tasks')
|
|
247
|
-
log('')
|
|
302
|
+
log('\n## Tasks\n')
|
|
248
303
|
Object.keys(groupedTasks).forEach((group) => {
|
|
249
304
|
if (group !== UNGROUPED_TASKS) {
|
|
250
305
|
log('')
|
package/lib/cli/MDStoryParser.js
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
|
+
const { parseMD } = require('../tools')
|
|
2
|
+
const StoryProject = require('./domain/StoryProject')
|
|
3
|
+
const BacklogProject = require('./domain/BacklogProject')
|
|
1
4
|
const {
|
|
2
5
|
UNGROUPED_TASKS,
|
|
3
6
|
TODO,
|
|
4
7
|
DONE,
|
|
5
8
|
ORDER
|
|
6
|
-
} =
|
|
7
|
-
const
|
|
8
|
-
const { newBacklogProject } = require('./ProjectConfigFactory')
|
|
9
|
-
const { parseMD } = require('../tools')
|
|
10
|
-
const BacklogProject = require('./domain/BacklogProject')
|
|
9
|
+
} = BacklogProject.constants
|
|
10
|
+
const ApplicationContext = require('./adapters/ApplicationContext')
|
|
11
11
|
|
|
12
12
|
function isOpen(token, type, tag = token.tag) {
|
|
13
13
|
return token.type === type + '_open' && token.tag === tag
|
|
@@ -33,7 +33,6 @@ function parse(markdown) {
|
|
|
33
33
|
let inParagraph = false
|
|
34
34
|
let inBulletList = false
|
|
35
35
|
let inListItem = false
|
|
36
|
-
let listItemMarkup = ''
|
|
37
36
|
let inTasks = false
|
|
38
37
|
let headingLevel = 0
|
|
39
38
|
const ast = parseMD(markdown)
|
|
@@ -55,7 +54,6 @@ function parse(markdown) {
|
|
|
55
54
|
|
|
56
55
|
if (isOpen(token, listItem)) {
|
|
57
56
|
inListItem = true
|
|
58
|
-
listItemMarkup = markup
|
|
59
57
|
} else if (isClose(token, listItem)) {
|
|
60
58
|
inListItem = false
|
|
61
59
|
}
|
|
@@ -88,28 +86,30 @@ function parse(markdown) {
|
|
|
88
86
|
}
|
|
89
87
|
})
|
|
90
88
|
|
|
91
|
-
|
|
92
|
-
|
|
89
|
+
if (storyText) {
|
|
90
|
+
const result = new RegExp(`# ${storyText}\n(.*)## Tasks`, 'gms').exec(markdown)
|
|
91
|
+
description = result && result.length > 1 ? result[1].trim() : ''
|
|
92
|
+
}
|
|
93
93
|
|
|
94
94
|
return { storyText, description, tasks }
|
|
95
95
|
}
|
|
96
96
|
|
|
97
|
-
async function importMarkdown(projectPath, markdown, ApplicationContext) {
|
|
98
|
-
const { log } =
|
|
97
|
+
async function importMarkdown({projectPath, existingStoryId, markdown, applicationContext = ApplicationContext}) {
|
|
98
|
+
const { log } = applicationContext()
|
|
99
99
|
|
|
100
|
-
const backlogProject = await BacklogProject.createAndInit(projectPath, null,
|
|
100
|
+
const backlogProject = await BacklogProject.createAndInit(projectPath, null, applicationContext)
|
|
101
101
|
|
|
102
102
|
let { storyText, description, tasks } = parse(markdown)
|
|
103
103
|
|
|
104
|
-
const storyId = backlogProject.toStoryId(storyText)
|
|
104
|
+
const storyId = existingStoryId || backlogProject.toStoryId(storyText)
|
|
105
105
|
|
|
106
106
|
log(`Importing story ${storyId}\n${description}`)
|
|
107
107
|
|
|
108
108
|
await backlogProject.storageAdapter.initStoryPlan(storyId)
|
|
109
109
|
|
|
110
|
-
await backlogProject.addStory(`${storyText}\n${description}`)
|
|
110
|
+
if (!existingStoryId) await backlogProject.addStory(`${storyText}\n${description}`)
|
|
111
111
|
|
|
112
|
-
const storyProject = await StoryProject.createAndInit(projectPath, null, storyId,
|
|
112
|
+
const storyProject = await StoryProject.createAndInit(projectPath, null, storyId, applicationContext)
|
|
113
113
|
|
|
114
114
|
await addStoryTasks(storyProject, tasks)
|
|
115
115
|
|
|
@@ -16,6 +16,20 @@ const BACKLOG_IGNORE_PATH = path.join(CONFIG_PATH, 'backlog.imdoneignore')
|
|
|
16
16
|
|
|
17
17
|
const defaultProjectPath = (baseDir = process.env.PWD) => path.join(baseDir, DEFAULT_PROJECT_DIR)
|
|
18
18
|
|
|
19
|
+
const getProjectConfigPath = (projectPath = defaultProjectPath()) => path.join(projectPath, '.imdone', 'config.yml')
|
|
20
|
+
|
|
21
|
+
const projectExists = async (
|
|
22
|
+
projectPath = defaultProjectPath()
|
|
23
|
+
) => {
|
|
24
|
+
const projectConfigPath = getProjectConfigPath(projectPath)
|
|
25
|
+
try {
|
|
26
|
+
await access(projectConfigPath)
|
|
27
|
+
return projectConfigPath
|
|
28
|
+
} catch (e) {
|
|
29
|
+
return false
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
19
33
|
const configureProject = async ({
|
|
20
34
|
projectPath = defaultProjectPath(),
|
|
21
35
|
configPath = BACKLOG_CONFIG_PATH,
|
|
@@ -27,11 +41,11 @@ const configureProject = async ({
|
|
|
27
41
|
projectPath = resolve(projectPath)
|
|
28
42
|
configPath = resolve(configPath)
|
|
29
43
|
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
44
|
+
if (await projectExists(projectPath)) {
|
|
45
|
+
return { projectPath, config: await newConfigFromFile(projectConfigPath)}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
const projectConfigPath = getProjectConfigPath(projectPath)
|
|
35
49
|
|
|
36
50
|
await mkdir(path.join(projectPath, tasksDir), {recursive: true})
|
|
37
51
|
await mkdir(path.join(projectPath, '.imdone'), { recursive: true })
|
|
@@ -92,6 +106,7 @@ function logFsError(ApplicationContext, e) {
|
|
|
92
106
|
}
|
|
93
107
|
|
|
94
108
|
module.exports = {
|
|
109
|
+
projectExists,
|
|
95
110
|
configureProject,
|
|
96
111
|
newBacklogProject,
|
|
97
112
|
newStoryProject,
|
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
const simpleGit = require('simple-git')
|
|
2
|
+
const {changesExistError, ChangesExistError} = require('./Errors')
|
|
3
|
+
|
|
2
4
|
module.exports = class {
|
|
3
5
|
|
|
4
6
|
constructor(project) {
|
|
@@ -24,7 +26,7 @@ module.exports = class {
|
|
|
24
26
|
|
|
25
27
|
async initTaskUpdate(task) {
|
|
26
28
|
if (await this.changesExist()) {
|
|
27
|
-
throw new
|
|
29
|
+
throw new ChangesExistError('start a task')
|
|
28
30
|
}
|
|
29
31
|
|
|
30
32
|
await this.checkoutStoryBranch({task})
|
|
@@ -40,7 +42,7 @@ module.exports = class {
|
|
|
40
42
|
|
|
41
43
|
async initStoryPlan(storyId) {
|
|
42
44
|
if (await this.changesExist()) {
|
|
43
|
-
throw new
|
|
45
|
+
throw new ChangesExistError('plan a story')
|
|
44
46
|
}
|
|
45
47
|
|
|
46
48
|
await this.checkoutStoryBranch({storyId})
|
|
@@ -98,18 +98,12 @@ class BacklogProject {
|
|
|
98
98
|
const storyFilter = `tags=story AND meta.${STORY_ID}="${storyId}"`
|
|
99
99
|
const story = this.project.getAllCards(storyFilter)[0]
|
|
100
100
|
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
await this.storageAdapter.commitTaskUpdate(task)
|
|
108
|
-
} catch (e) {
|
|
109
|
-
console.log('e', e)
|
|
110
|
-
const error = new Error('Error starting task')
|
|
111
|
-
error.cause = e
|
|
112
|
-
}
|
|
101
|
+
await this.storageAdapter.initTaskUpdate(task)
|
|
102
|
+
await this.moveTask(task, DOING, 0)
|
|
103
|
+
await this.project.addMetadata(task, 'branch', this.storageAdapter.getTaskBranchName(task))
|
|
104
|
+
this.project.rollBackFileForTask(task)
|
|
105
|
+
await this.moveTask(story, DOING, 0)
|
|
106
|
+
await this.storageAdapter.commitTaskUpdate(task)
|
|
113
107
|
}
|
|
114
108
|
|
|
115
109
|
async saveStory(storyId) {
|
|
@@ -1,3 +1,7 @@
|
|
|
1
|
+
const defaultStory = `# Story Title
|
|
2
|
+
|
|
3
|
+
Story description
|
|
4
|
+
`
|
|
1
5
|
module.exports.promptForMarkdownPlan = async function () {
|
|
2
6
|
const editor = (await import('@inquirer/editor')).default
|
|
3
7
|
|
|
@@ -7,10 +11,6 @@ module.exports.promptForMarkdownPlan = async function () {
|
|
|
7
11
|
if (!text) return 'Please enter a description'
|
|
8
12
|
return true
|
|
9
13
|
},
|
|
10
|
-
default:
|
|
11
|
-
|
|
12
|
-
Story description
|
|
13
|
-
`
|
|
14
|
-
|
|
14
|
+
default: defaultStory
|
|
15
15
|
});
|
|
16
16
|
}
|
package/lib/default-settings.js
CHANGED
|
@@ -72,9 +72,9 @@ module.exports = class PluginManager extends Emitter {
|
|
|
72
72
|
return reject(new Error('Unable to find plugin:' + pluginName))
|
|
73
73
|
}
|
|
74
74
|
const { info } = this.getPlugin(pluginClassName)
|
|
75
|
-
const { path
|
|
76
|
-
const pkg = { version, name }
|
|
77
|
-
console.log('Uninstalling:',
|
|
75
|
+
const { path } = info
|
|
76
|
+
// const pkg = { version, name }
|
|
77
|
+
console.log('Uninstalling:', info)
|
|
78
78
|
|
|
79
79
|
rimraf(path, (err) => {
|
|
80
80
|
if (err) return reject(err)
|
|
@@ -137,7 +137,7 @@ module.exports = class PluginManager extends Emitter {
|
|
|
137
137
|
throw new Error(`${pluginClass.name} is not a plugin`)
|
|
138
138
|
const name = pluginClass.name.toString()
|
|
139
139
|
const pluginInstance = new pluginClass(this.project)
|
|
140
|
-
let info = {
|
|
140
|
+
let info = {}
|
|
141
141
|
try {
|
|
142
142
|
info = require(`${path}/package.json`)
|
|
143
143
|
delete info.dependencies
|
|
@@ -146,6 +146,9 @@ module.exports = class PluginManager extends Emitter {
|
|
|
146
146
|
delete info.main
|
|
147
147
|
} catch (e) {
|
|
148
148
|
console.info('No info on plugin:', path)
|
|
149
|
+
} finally {
|
|
150
|
+
info.path = path
|
|
151
|
+
info.name = name
|
|
149
152
|
}
|
|
150
153
|
|
|
151
154
|
pluginInstance.getSettings = () => {
|
package/lib/project.js
CHANGED
|
@@ -428,7 +428,17 @@ module.exports = class WorkerProject extends Project {
|
|
|
428
428
|
if (!/^['"]/.test(value) && /\s/.test(value)) value = `"${value}"`
|
|
429
429
|
const metaData = `${key}${this.repo.config.getMetaSep()}${value}`
|
|
430
430
|
if (content.includes(metaData)) return
|
|
431
|
-
|
|
431
|
+
const existingMetadata = Task.parseMetaData(this.config, content)[key]
|
|
432
|
+
if (existingMetadata) {
|
|
433
|
+
content = Task.removeMetaData({
|
|
434
|
+
config: this.config,
|
|
435
|
+
content,
|
|
436
|
+
key,
|
|
437
|
+
value: existingMetadata[0],
|
|
438
|
+
})
|
|
439
|
+
}
|
|
440
|
+
const spaceOrNewLine = this.config.isMetaNewLine() ? '\n' : ' '
|
|
441
|
+
metaContent = `${metaContent}${spaceOrNewLine}${metaData}`
|
|
432
442
|
})
|
|
433
443
|
content = Task.addToLastCommentInContent(content, metaContent.trim(), this.config.isMetaNewLine())
|
|
434
444
|
}
|
package/lib/task.js
CHANGED
|
@@ -430,7 +430,7 @@ Task.getMetaOrderRegex = function (config) {
|
|
|
430
430
|
Task.prototype.updateOrderMeta = function (config, descContent = this.description.join(lineEnd)) {
|
|
431
431
|
if (config.orderMeta) {
|
|
432
432
|
if (!Task.isNumber(this.order)) return this.order = ''
|
|
433
|
-
const
|
|
433
|
+
const addNewLine = config.isMetaNewLine()
|
|
434
434
|
const metaSep = config.getMetaSep()
|
|
435
435
|
if (this.meta.order) {
|
|
436
436
|
const regex = Task.getMetaOrderRegex(config)
|
|
@@ -439,7 +439,7 @@ Task.prototype.updateOrderMeta = function (config, descContent = this.descriptio
|
|
|
439
439
|
descContent = this.addToLastCommentInContent(
|
|
440
440
|
descContent,
|
|
441
441
|
`order${metaSep}${this.order}`,
|
|
442
|
-
|
|
442
|
+
addNewLine
|
|
443
443
|
)
|
|
444
444
|
}
|
|
445
445
|
this.meta.order = [this.order]
|