imdone-core 1.45.1 → 1.46.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.
@@ -1,135 +0,0 @@
1
- const simpleGit = require('simple-git')
2
- const {changesExistError, ChangesExistError} = require('./Errors')
3
-
4
- module.exports = class {
5
-
6
- constructor(project) {
7
- this.project = project
8
- this.git = simpleGit(this.path)
9
- }
10
-
11
- get path() {
12
- return this.project.path
13
- }
14
-
15
- get remote() {
16
- return this.pluginConfig.remote
17
- }
18
-
19
- get defaultBranch() {
20
- return this.pluginConfig.defaultBranch
21
- }
22
-
23
- get pluginConfig() {
24
- return this.project.config.settings.plugins.CliBacklogPlugin
25
- }
26
-
27
- async initTaskUpdate(task) {
28
- if (await this.changesExist()) {
29
- throw new ChangesExistError('start a task')
30
- }
31
-
32
- await this.checkoutStoryBranch({task})
33
- }
34
-
35
- async commitTaskUpdate(task) {
36
- const taskBranchName = this.getTaskBranchName(task)
37
- await this.project.addMetadata(task, 'branch', taskBranchName)
38
- await this.git.add('.').commit(`Update task ${this.getTaskName(task)}`)
39
- await this.git.push(this.remote)
40
- await this.git.checkoutBranch(taskBranchName, this.getStoryBranchName({task}))
41
- }
42
-
43
- async initStoryPlan(storyId) {
44
- if (await this.changesExist()) {
45
- throw new ChangesExistError('plan a story')
46
- }
47
-
48
- await this.checkoutStoryBranch({storyId})
49
- }
50
-
51
- async saveStoryPlan(storyId) {
52
- const storyBranchName = this.getStoryBranchName({storyId})
53
- await this.git.add('.').commit(`Update story ${storyId}`)
54
- await this.git.push(this.remote)
55
- }
56
-
57
- getTaskName(task) {
58
- return this.project.getTaskName(task)
59
- }
60
-
61
- getStoryId(task) {
62
- return this.project.getStoryId(task)
63
- }
64
-
65
- getStoryBranchName({task, storyId}) {
66
- return `${this.getStoryBranchPrefix({task, storyId})}/main`
67
- }
68
-
69
- getStoryBranchPrefix({task, storyId}) {
70
- return `story/${storyId || this.getStoryId(task)}`
71
- }
72
-
73
- getTaskBranchName(task) {
74
- const storyBranchPrefix = this.getStoryBranchPrefix({task})
75
- const taskName = this.getTaskName(task)
76
- return `${storyBranchPrefix}/task/${taskName}`
77
- }
78
-
79
- async getCurrentTask() {
80
- const currentBranch = await this.getCurrentBranch()
81
- const tasks = this.project.getTasks(`meta.branch="${currentBranch}"`)
82
- return tasks && tasks.length == 1 && tasks[0]
83
- }
84
-
85
- async getCurrentBranch() {
86
- return (await this.git.branchLocal()).current
87
- }
88
-
89
- async changesExist() {
90
- return !(await this.git.status()).isClean()
91
- }
92
-
93
- async checkoutDefaultBranch() {
94
- const defaultBranch = this.defaultBranch
95
- await this.fetch()
96
- await this.git.checkout(defaultBranch)
97
- await this.pull()
98
- }
99
-
100
- async checkoutStoryBranch({task, storyId}) {
101
- const storyBranchName = this.getStoryBranchName({task, storyId})
102
-
103
- await this.checkoutDefaultBranch()
104
-
105
- if (await this.branchExists(storyBranchName)) {
106
- await this.git.checkout(storyBranchName)
107
- await this.pull()
108
- } else {
109
- await this.git.checkoutBranch(
110
- storyBranchName,
111
- this.defaultBranch
112
- )
113
- await this.pushNewBranch(storyBranchName)
114
- }
115
- await this.git.merge([this.defaultBranch])
116
- }
117
-
118
- async branchExists(branchName) {
119
- const branches = await this.git.branchLocal()
120
- return branches.all.includes(branchName)
121
- }
122
-
123
- async pull() {
124
- await this.git.pull(this.remote)
125
- }
126
-
127
- async fetch() {
128
- await this.git.fetch(this.remote)
129
- }
130
-
131
- async pushNewBranch(branchName) {
132
- await this.git.push(this.remote, branchName, {'--set-upstream': null})
133
- }
134
-
135
- }
@@ -1,170 +0,0 @@
1
- const { createFileSystemProject } = require('../../project-factory')
2
- const { resolve } = path = require('path')
3
- const STORIES_DIR = 'stories'
4
- const STORY_ID = 'story-id'
5
- const TASK_ID = 'task-id'
6
- const TODO = 'TODO'
7
- const DOING = 'DOING'
8
- const DONE = 'DONE'
9
- const ORDER = 'order'
10
- const UNGROUPED_TASKS = 'Ungrouped Tasks'
11
-
12
- class BacklogProject {
13
- constructor(projectPath, config, ApplicationContext = require('../adapters/ApplicationContext')) {
14
- this.ApplicationContext = ApplicationContext
15
- this.StorageAdapter = ApplicationContext().StorageAdapter
16
- this.projectPath = resolve(projectPath)
17
- this._config = config
18
- this.project = null
19
- }
20
-
21
- get path() {
22
- return this.projectPath
23
- }
24
-
25
- get name() {
26
- return this.project.name
27
- }
28
-
29
- get defaultList() {
30
- return this.config.getDefaultList()
31
- }
32
-
33
- get config() {
34
- return this.project && this.project.config || this._config
35
- }
36
-
37
- async init() {
38
- this.project = createFileSystemProject({path: this.projectPath, config: this.config})
39
- await this.project.init()
40
- this.storageAdapter = new this.StorageAdapter(this)
41
- return this
42
- }
43
-
44
- getStoryProjectPath(storyId) {
45
- return getStoryProjectPath(this.path, storyId)
46
- }
47
-
48
- async addStory(content) {
49
- await this.addTaskToFile({list: TODO, content })
50
- }
51
-
52
- toStoryId(text) {
53
- return this.project.sanitizeFileName(text)
54
- }
55
-
56
- removeList(list) {
57
- return this.project.removeList(list)
58
- }
59
-
60
- getStoryIds() {
61
- return [
62
- ...new Set(
63
- this.project.getCards(`meta.${STORY_ID}=* AND tags=story`).map(({meta, text, list}) => ({text, list, storyId: meta[STORY_ID][0]}))
64
- )
65
- ]
66
- }
67
-
68
- tasksToJson(tasks) {
69
- return tasks.map(({text, type, list, meta, content, tags, context, description, source, beforeText, order}) =>
70
- ({
71
- list,
72
- type,
73
- order,
74
- path: source.path,
75
- prefix: beforeText,
76
- text,
77
- content,
78
- description,
79
- tags,
80
- context,
81
- meta
82
- })
83
- )
84
- }
85
-
86
- getTaskName(task) {
87
- return this.toStoryId(task.text)
88
- }
89
-
90
- async startTask(taskId) {
91
- await this.init()
92
-
93
- const taskFilter = `meta.${TASK_ID}="${taskId}"`
94
- const task = this.project.getAllCards(taskFilter)[0]
95
- if (!task) throw new Error(`No task found with id ${taskId}`)
96
-
97
- const storyId = task.meta[STORY_ID][0]
98
- const storyFilter = `tags=story AND meta.${STORY_ID}="${storyId}"`
99
- const story = this.project.getAllCards(storyFilter)[0]
100
-
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)
107
- }
108
-
109
- async saveStory(storyId) {
110
- // // Switch to the story branch or create it
111
- // await git.checkout(storyBranchName);
112
- // await git.pull('origin', storyBranchName, { '--force': null });
113
-
114
- // // Push the changes to the story branch with force
115
- // await git.push('origin', storyBranchName, { '--force': null });
116
-
117
- // // Return to the original branch
118
- // await git.checkout(currentBranch);
119
-
120
- // Merge the story branch into the current branch
121
-
122
- }
123
-
124
- async moveTask(task, list, order) {
125
- return await this.project.moveTask(task, list, order)
126
- }
127
-
128
- async addTaskToFile({path, list, tags, content, meta, contexts=[]}) {
129
- return await this.project.addTaskToFile({path, list, tags, content, meta, contexts, useCardTemplate: true})
130
- }
131
-
132
- async addMetadata(task, key, value) {
133
- return await this.project.addMetadata(task, key, value)
134
- }
135
-
136
- getCurrentStoryId() {
137
- const tasks = this.project.getAllCards(`tags = "story" AND list = "${DOING}"`)
138
- if (tasks.length < 1) return
139
- return this.getStoryId(tasks[0])
140
- }
141
-
142
- getStoryId(task) {
143
- return task.meta[STORY_ID][0]
144
- }
145
-
146
- async getCurrentTask() {
147
- return await this.storageAdapter.getCurrentTask()
148
- }
149
- }
150
-
151
- function getStoryProjectPath(projectPath, storyId) {
152
- return path.join(projectPath, STORIES_DIR, storyId)
153
- }
154
-
155
- BacklogProject.getStoryProjectPath = getStoryProjectPath
156
- BacklogProject.createAndInit = async function(projectPath, config, ApplicationContext) {
157
- return (await (new BacklogProject(projectPath, config, ApplicationContext)).init())
158
- }
159
-
160
- BacklogProject.constants = {
161
- STORIES_DIR,
162
- STORY_ID,
163
- TASK_ID,
164
- TODO,
165
- DOING,
166
- DONE,
167
- ORDER,
168
- UNGROUPED_TASKS
169
- }
170
- module.exports = BacklogProject
@@ -1,67 +0,0 @@
1
- const path = require('path')
2
- const BacklogProject = require('./BacklogProject')
3
- const {
4
- STORY_ID,
5
- TASK_ID,
6
- DONE,
7
- DOING,
8
- UNGROUPED_TASKS
9
- } = BacklogProject.constants
10
-
11
- class StoryProject extends BacklogProject {
12
- constructor(projectPath, config, storyId, ApplicationContext = require('../adapters/ApplicationContext')) {
13
- super(BacklogProject.getStoryProjectPath(projectPath, storyId), config, ApplicationContext)
14
- this.storyId = storyId
15
- }
16
-
17
- async addTask({content, group = UNGROUPED_TASKS}) {
18
- const file = await this.addTaskToFile({list: this.defaultList, content})
19
- file.rollback().extractTasks(this.config)
20
- const task = file.tasks[0]
21
- if (group !== UNGROUPED_TASKS) {
22
- await this.project.removeMetadata(task, 'group', UNGROUPED_TASKS)
23
- await this.project.addMetadata(task, 'group', group)
24
- }
25
- return file.tasks[0]
26
- }
27
-
28
- async completeTask() {
29
- const task = await this.getCurrentTask()
30
- if (!task) throw new Error('No current task')
31
- await this.project.moveTask(task, DONE)
32
- return task
33
- }
34
-
35
- getTaskId(task) {
36
- return task.meta[TASK_ID][0]
37
- }
38
-
39
- getTaskName(task) {
40
- const ext = path.extname(task.file.path)
41
- return path.basename(task.file.path, ext)
42
- }
43
-
44
- getTasks(filter) {
45
- filter = filter ? ` AND ${filter}` : ''
46
- return this.project.getAllCards('tags=task' + filter)
47
- }
48
-
49
- getTask(taskId) {
50
- return this.project.getCards(`tags=task AND meta.${TASK_ID}="${taskId}"`)[0]
51
- }
52
-
53
- getStory() {
54
- return this.project.getAllCards(`tags=story`)[0]
55
- }
56
-
57
- get name() {
58
- return this.project.name
59
- }
60
-
61
- }
62
-
63
- StoryProject.createAndInit = async function(projectPath, config, storyId, ApplicationContext) {
64
- return (await (new StoryProject(projectPath, config, storyId, ApplicationContext)).init())
65
- }
66
-
67
- module.exports = StoryProject
@@ -1,16 +0,0 @@
1
- const defaultStory = `# Story Title
2
-
3
- Story description
4
- `
5
- module.exports.promptForMarkdownPlan = async function () {
6
- const editor = (await import('@inquirer/editor')).default
7
-
8
- return await editor({
9
- message: 'Enter the story title and description markdown',
10
- validate: function (text) {
11
- if (!text) return 'Please enter a description'
12
- return true
13
- },
14
- default: defaultStory
15
- });
16
- }
@@ -1,152 +0,0 @@
1
- const Plugin = require('imdone-api')
2
- const eol = require('eol')
3
- const _path = require('path')
4
- const {
5
- dirname
6
- } = require('path')
7
- const { rmdir } = require('fs').promises
8
- const {
9
- UNGROUPED_TASKS,
10
- STORY_ID,
11
- ORDER
12
- } = require('../cli/domain/BacklogProject').constants
13
- const { newStoryProject } = require('../cli/ProjectConfigFactory')
14
-
15
-
16
- const generateRandomString = (length) => {
17
- let result = '';
18
- const characters =
19
- 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
20
- const charactersLength = characters.length;
21
- for (let i = 0; i < length; i++) {
22
- result += characters.charAt(Math.floor(Math.random() * charactersLength));
23
- }
24
- return result;
25
- };
26
-
27
- const PROJECT_TYPES = {
28
- BACKLOG_PROJECT: 'BACKLOG_PROJECT',
29
- STORY_PROJECT: 'STORY_PROJECT'
30
- }
31
-
32
- module.exports = class CliBacklogPlugin extends Plugin {
33
- get projectType() {
34
- return this.getSettings().projectType
35
- }
36
-
37
- get isStoryProject() {
38
- return this.projectType === PROJECT_TYPES.STORY_PROJECT
39
- }
40
-
41
- get isBacklogProject() {
42
- return this.projectType === PROJECT_TYPES.BACKLOG_PROJECT
43
- }
44
-
45
- get cardActionsMethod() {
46
- return this.isStoryProject
47
- ? this.getStoryProjectCardActions
48
- : this.isBacklogProject
49
- ? this.getBacklogProjectCardActions
50
- : () => []
51
- }
52
-
53
- get boardActionsMethod() {
54
- return this.isStoryProject
55
- ? this.getStoryProjectBoardActions
56
- : this.isBacklogProject
57
- ? this.getBacklogProjectBoardActions
58
- : () => []
59
-
60
- }
61
-
62
- async onBeforeAddTask({path, list, meta, tags, contexts, content}) {
63
- if (!this.isBacklogProject) return {path, content, meta, tags, contexts}
64
- const project = this.project
65
- const storyId = project.sanitizeFileName(eol.split(content)[0])
66
- const storyProject = await newStoryProject({projectPath: project.path, storyId})
67
- path = _path.join(storyProject.path, 'README.md')
68
- meta.push({key: STORY_ID, value: storyId})
69
- meta.push({key: ORDER, value: 0})
70
- return {path, content, meta, tags, contexts}
71
- }
72
-
73
- async onAfterDeleteTask(task) {
74
- if (!this.isBacklogProject) return
75
- const project = this.project
76
- await rmdir(dirname(task.fullPath), {recursive: true})
77
- }
78
-
79
- getStoryProjectBoardActions() {
80
- const project = this.project
81
-
82
- const groups = [...new Set(
83
- project.getCards('meta.group = *').map((card) => card.meta.group && card.meta.group[0])
84
- )].map(group => {
85
- const name = group
86
- const value = `"${group}"`
87
- return { name, value}
88
- })
89
- const groupActions = [{name: 'All tasks', value: '*'}, ...groups].map(({name, value}) => {
90
- const filterValue = encodeURIComponent(`meta.group = ${value} and tags = task`)
91
- return {
92
- name,
93
- action: function () {
94
- project.openUrl(`imdone://active.repo?filter=${filterValue}`)
95
- }
96
- }
97
- })
98
- const storyId = project.getAllCards('meta.story-id = *')[0].meta['story-id'][0]
99
- const storiesDir = _path.dirname(project.path)
100
- const backlogDir = _path.dirname(storiesDir)
101
- const showStoryAction = {
102
- name: 'Show story',
103
- action: function () {
104
- const query = encodeURIComponent(`allMeta.story-id="${storyId}"`)
105
- project.openUrl(`imdone://${backlogDir}?filter=${query}`) // imdone:///Users/jesse/projects/imdone-projects/imdone-core/backlog?filter=allMeta.story-id="Board-action-to-show-backlog-from-story-project"
106
- }
107
- }
108
-
109
- return [showStoryAction, ...groupActions]
110
- }
111
-
112
- getBacklogProjectBoardActions() {
113
- return []
114
- }
115
-
116
- getStoryProjectCardActions(card) {
117
- return []
118
- }
119
-
120
- getBacklogProjectCardActions(card) {
121
- const fullPath = card.fullPath
122
- const project = this.project
123
- const storyProjectPath = dirname(fullPath)
124
- const title = `Open story tasks`
125
- return card.tags.includes('story') ? [
126
- {
127
- action: function () {
128
- project.openUrl(`imdone://${storyProjectPath}`)
129
- },
130
- pack: 'fas',
131
- icon: 'check-square',
132
- title
133
- }
134
- ] : []
135
- }
136
-
137
- getCardActions(card) {
138
- return this.cardActionsMethod(card)
139
- }
140
-
141
- getBoardActions() {
142
- return this.boardActionsMethod()
143
- }
144
-
145
- getCardProperties(task) {
146
- return {
147
- sid: generateRandomString(5),
148
- projectName: this.project.name,
149
- ungroupedTasks: UNGROUPED_TASKS
150
- }
151
- }
152
- }