imdone-core 2.0.0 → 2.0.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.
Files changed (160) hide show
  1. package/.dependency-cruiser.cjs +412 -0
  2. package/.github/ISSUE_TEMPLATE/bug_report.md +37 -0
  3. package/.github/workflows/ci.yml +24 -0
  4. package/.imdoneignore +13 -0
  5. package/.mocharc.json +10 -0
  6. package/.prettierrc +6 -0
  7. package/CHANGELOG.md +160 -0
  8. package/_index.md +7 -0
  9. package/_navbar.md +1 -0
  10. package/build.js +57 -0
  11. package/esm-migration.md +446 -0
  12. package/index.html +184 -0
  13. package/lib/FileProjectContext.js +13 -0
  14. package/lib/ProjectContext.js +43 -0
  15. package/lib/adapters/date-time.js +15 -0
  16. package/lib/adapters/environment.js +3 -0
  17. package/lib/adapters/file-gateway.js +154 -0
  18. package/lib/adapters/git-download.js +55 -0
  19. package/lib/adapters/index.js +9 -0
  20. package/lib/adapters/logger.js +136 -0
  21. package/lib/adapters/markdown.js +180 -0
  22. package/lib/adapters/parsers/DateLanguageParser.js +12 -0
  23. package/lib/adapters/parsers/content-transformer.js +116 -0
  24. package/lib/adapters/parsers/file/LocalFileParserFactory.js +36 -0
  25. package/lib/adapters/parsers/file/ReadStreamCodeParser.js +126 -0
  26. package/lib/adapters/parsers/file/ReadStreamMarkdownParser.js +17 -0
  27. package/lib/adapters/parsers/file/ReadStreamTaskParser.js +106 -0
  28. package/lib/adapters/parsers/file/index.js +4 -0
  29. package/lib/adapters/parsers/function-parser.js +25 -0
  30. package/lib/adapters/parsers/index.js +5 -0
  31. package/lib/adapters/parsers/task/CardContentParser.js +367 -0
  32. package/lib/adapters/parsers/task/CheckStyleTaskParser.js +28 -0
  33. package/lib/adapters/parsers/task/CodeStyleTaskParser.js +24 -0
  34. package/lib/adapters/parsers/task/HashStyleTaskParser.js +27 -0
  35. package/lib/adapters/parsers/task/MarkdownStyleTaskParser.js +24 -0
  36. package/lib/adapters/parsers/task/TaskParser.js +38 -0
  37. package/lib/adapters/parsers/task/index.js +6 -0
  38. package/lib/adapters/storage/config.js +44 -0
  39. package/lib/adapters/storage/index.js +2 -0
  40. package/lib/adapters/storage/tags.js +29 -0
  41. package/lib/adapters/yaml.js +9 -0
  42. package/lib/card.js +299 -0
  43. package/lib/checksum.js +22 -0
  44. package/lib/config.js +249 -0
  45. package/lib/constants.js +68 -0
  46. package/lib/context/ApplicationContext.js +35 -0
  47. package/lib/context/index.js +1 -0
  48. package/lib/default-settings.js +37 -0
  49. package/lib/domain/entities/View.js +23 -0
  50. package/lib/domain/entities/index.js +1 -0
  51. package/lib/domain/index.js +2 -0
  52. package/lib/domain/usecases/GetViewsUsecase.js +44 -0
  53. package/lib/domain/usecases/SaveViewUsecase.js +51 -0
  54. package/lib/domain/usecases/index.js +2 -0
  55. package/lib/file.js +1391 -0
  56. package/lib/get-tasks-to-modify.js +117 -0
  57. package/lib/languages.js +113 -0
  58. package/lib/list.js +56 -0
  59. package/lib/migrate-config.js +165 -0
  60. package/lib/mixins/index.js +2 -0
  61. package/lib/mixins/repo-fs-store.js +359 -0
  62. package/lib/mixins/repo-watched-fs-store.js +206 -0
  63. package/lib/plugins/archive-plugin.js +72 -0
  64. package/lib/plugins/default-board-actions-plugin.js +56 -0
  65. package/lib/plugins/default-board-properties-plugin.js +101 -0
  66. package/lib/plugins/default-plugins.js +7 -0
  67. package/lib/plugins/epic-plugin.js +177 -0
  68. package/lib/plugins/extension-plugin.js +94 -0
  69. package/lib/plugins/index.js +9 -0
  70. package/lib/plugins/persist-tags-plugin.js +14 -0
  71. package/lib/plugins/plugin-manager.js +458 -0
  72. package/lib/plugins/plugin-registry.js +57 -0
  73. package/lib/project-factory.js +42 -0
  74. package/lib/project.js +864 -0
  75. package/lib/repository.js +1514 -0
  76. package/lib/task.js +780 -0
  77. package/lib/tools.js +176 -0
  78. package/lib/usecases/actions.js +32 -0
  79. package/lib/usecases/get-imdone-url.js +6 -0
  80. package/lib/usecases/get-project-tags.js +7 -0
  81. package/lib/usecases/get-tasks-in-file.js +26 -0
  82. package/lib/usecases/index.js +4 -0
  83. package/package.json +2 -6
  84. package/scripts/delete-markdown-indexes.js +87 -0
  85. package/scripts/generate-index.js +35 -0
  86. package/scripts/index-markdown.js +55 -0
  87. package/tsconfig.json +15 -0
  88. package/types/FileProjectContext.d.ts +4 -0
  89. package/types/ProjectContext.d.ts +9 -0
  90. package/types/adapters/date-time.d.ts +2 -0
  91. package/types/adapters/environment.d.ts +1 -0
  92. package/types/adapters/file-gateway.d.ts +51 -0
  93. package/types/adapters/git-download.d.ts +1 -0
  94. package/types/adapters/index.d.ts +9 -0
  95. package/types/adapters/logger.d.ts +24 -0
  96. package/types/adapters/markdown.d.ts +13 -0
  97. package/types/adapters/parsers/DateLanguageParser.d.ts +1 -0
  98. package/types/adapters/parsers/content-transformer.d.ts +10 -0
  99. package/types/adapters/parsers/file/LocalFileParserFactory.d.ts +5 -0
  100. package/types/adapters/parsers/file/ReadStreamCodeParser.d.ts +13 -0
  101. package/types/adapters/parsers/file/ReadStreamMarkdownParser.d.ts +7 -0
  102. package/types/adapters/parsers/file/ReadStreamTaskParser.d.ts +19 -0
  103. package/types/adapters/parsers/file/index.d.ts +4 -0
  104. package/types/adapters/parsers/function-parser.d.ts +1 -0
  105. package/types/adapters/parsers/index.d.ts +5 -0
  106. package/types/adapters/parsers/task/CardContentParser.d.ts +45 -0
  107. package/types/adapters/parsers/task/CheckStyleTaskParser.d.ts +12 -0
  108. package/types/adapters/parsers/task/CodeStyleTaskParser.d.ts +14 -0
  109. package/types/adapters/parsers/task/HashStyleTaskParser.d.ts +13 -0
  110. package/types/adapters/parsers/task/MarkdownStyleTaskParser.d.ts +14 -0
  111. package/types/adapters/parsers/task/TaskParser.d.ts +7 -0
  112. package/types/adapters/parsers/task/index.d.ts +6 -0
  113. package/types/adapters/storage/config.d.ts +6 -0
  114. package/types/adapters/storage/index.d.ts +2 -0
  115. package/types/adapters/storage/tags.d.ts +3 -0
  116. package/types/adapters/yaml.d.ts +2 -0
  117. package/types/card.d.ts +166 -0
  118. package/types/checksum.d.ts +8 -0
  119. package/types/config.d.ts +61 -0
  120. package/types/constants.d.ts +46 -0
  121. package/types/context/ApplicationContext.d.ts +24 -0
  122. package/types/context/index.d.ts +1 -0
  123. package/types/default-settings.d.ts +2 -0
  124. package/types/domain/entities/FileProjectContext.d.ts +5 -0
  125. package/types/domain/entities/View.d.ts +17 -0
  126. package/types/domain/entities/index.d.ts +1 -0
  127. package/types/domain/index.d.ts +2 -0
  128. package/types/domain/usecases/GetViewsUsecase.d.ts +4 -0
  129. package/types/domain/usecases/SaveViewUsecase.d.ts +8 -0
  130. package/types/domain/usecases/index.d.ts +2 -0
  131. package/types/file.d.ts +344 -0
  132. package/types/get-tasks-to-modify.d.ts +3 -0
  133. package/types/index.d.ts +23 -0
  134. package/types/languages.d.ts +649 -0
  135. package/types/list.d.ts +30 -0
  136. package/types/migrate-config.d.ts +1 -0
  137. package/types/mixins/index.d.ts +2 -0
  138. package/types/mixins/repo-fs-store.d.ts +2 -0
  139. package/types/mixins/repo-watched-fs-store.d.ts +2 -0
  140. package/types/plugins/archive-plugin.d.ts +8 -0
  141. package/types/plugins/default-board-actions-plugin.d.ts +9 -0
  142. package/types/plugins/default-board-properties-plugin.d.ts +16 -0
  143. package/types/plugins/default-plugins.d.ts +6 -0
  144. package/types/plugins/epic-plugin.d.ts +19 -0
  145. package/types/plugins/extension-plugin.d.ts +15 -0
  146. package/types/plugins/index.d.ts +9 -0
  147. package/types/plugins/persist-tags-plugin.d.ts +4 -0
  148. package/types/plugins/plugin-manager.d.ts +68 -0
  149. package/types/plugins/plugin-registry.d.ts +1 -0
  150. package/types/project-factory.d.ts +14 -0
  151. package/types/project.d.ts +158 -0
  152. package/types/repository.d.ts +996 -0
  153. package/types/task.d.ts +202 -0
  154. package/types/tools.d.ts +29 -0
  155. package/types/usecases/actions.d.ts +7 -0
  156. package/types/usecases/get-imdone-url.d.ts +1 -0
  157. package/types/usecases/get-project-tags.d.ts +1 -0
  158. package/types/usecases/get-tasks-in-file.d.ts +4 -0
  159. package/types/usecases/index.d.ts +4 -0
  160. package/vitest.config.js +13 -0
@@ -0,0 +1,458 @@
1
+ import Emitter from 'events'
2
+ import Plugin from 'imdone-api'
3
+ import _path from 'path'
4
+ import { downloadPlugin } from '../adapters/git-download.js'
5
+ import rimraf from 'rimraf'
6
+ import chokidar from 'chokidar'
7
+ import debounce from 'lodash.debounce'
8
+ import { exists, mkdir, readdir } from '../adapters/file-gateway.js'
9
+ import { appContext } from '../context/ApplicationContext.js'
10
+ import { createRequire } from 'node:module';
11
+
12
+ import PersistTagsPlugin from './persist-tags-plugin.js'
13
+ import DefaultBoardPropertiesPlugin from './default-board-properties-plugin.js'
14
+ import DefaultBoardActionsPlugin from './default-board-actions-plugin.js'
15
+ import ArchivePlugin from './archive-plugin.js'
16
+ import EpicPlugin from './epic-plugin.js'
17
+ import ExtensionPlugin from './extension-plugin.js'
18
+ import { logger } from '../adapters/logger.js'
19
+
20
+ const require = createRequire(import.meta.url);
21
+ const url = new URL(import.meta.url)
22
+ const __dirname = _path.dirname(url.pathname)
23
+
24
+ export class PluginManager extends Emitter {
25
+ constructor(project) {
26
+ super()
27
+ this.project = project
28
+ this.defaultPlugins = [
29
+ PersistTagsPlugin,
30
+ DefaultBoardPropertiesPlugin,
31
+ DefaultBoardActionsPlugin,
32
+ ArchivePlugin,
33
+ EpicPlugin,
34
+ ExtensionPlugin,
35
+ ]
36
+
37
+ this.pluginsMap = {}
38
+ this.pluginPath = _path.join(project.path, '.imdone', 'plugins')
39
+ this.onDevChange = debounce(this.onDevChange.bind(this), 1000)
40
+ }
41
+
42
+ async startDevMode() {
43
+ if (this.project && this.project.config.devMode && !this.watcher) {
44
+ if (!(await exists(this.pluginPath)))
45
+ await mkdir(this.pluginPath)
46
+ this.watcher = chokidar(this.pluginPath, {
47
+ ignored(path) {
48
+ return /node_modules/.test(path)
49
+ },
50
+ })
51
+ this.watcher.on('change', (path, root, stat) => {
52
+ this.onDevChange()
53
+ })
54
+ this.watcher.on('add', (path, root, stat) => {
55
+ this.onDevChange()
56
+ })
57
+ }
58
+ }
59
+
60
+ stopDevMode() {
61
+ this.watcher && this.watcher.close()
62
+ this.watcher = null
63
+ }
64
+
65
+ initDevMode() {
66
+ if (!this.project.config.devMode) this.stopDevMode()
67
+ this.startDevMode()
68
+ }
69
+
70
+ async onDevChange() {
71
+ try {
72
+ await this.reloadPlugins()
73
+ this.emit('plugins-reloaded')
74
+ } catch {
75
+ logger.error('Error reloading plugins.', err)
76
+ throw err
77
+ }
78
+ }
79
+
80
+ async reloadPlugins() {
81
+ this.destroyPlugins()
82
+ await this.loadPlugins()
83
+ await this.startDevMode()
84
+ }
85
+
86
+ async uninstallPlugin(pluginName) {
87
+ return new Promise((resolve, reject) => {
88
+ logger.log('Preparing to uninstall plugin:', pluginName)
89
+ const pluginClassName = Object.keys(this.pluginsMap).find((key) => {
90
+ return this.getPlugin(key).info.name === pluginName
91
+ })
92
+ if (!pluginClassName) {
93
+ return reject(new Error('Unable to find plugin:' + pluginName))
94
+ }
95
+ const { info } = this.getPlugin(pluginClassName)
96
+ const { path } = info
97
+ // const pkg = { version, name }
98
+ logger.log('Uninstalling:', info)
99
+
100
+ rimraf(path, (err) => {
101
+ if (err) return reject(err)
102
+ delete this.pluginsMap[pluginClassName]
103
+ this.emit('plugin-uninstalled', pluginName)
104
+ resolve()
105
+ })
106
+ })
107
+ }
108
+
109
+ async installPlugin({ name, version }) {
110
+ if (!(await exists(this.pluginPath)))
111
+ await mkdir(this.pluginPath)
112
+ const installPath = _path.join(this.pluginPath, name)
113
+ await downloadPlugin(version, installPath)
114
+ logger.log(`Done installing ${name}`)
115
+ await this.loadPlugin(installPath)
116
+ this.emit('plugin-installed', name)
117
+ }
118
+
119
+ async loadPlugins() {
120
+ for (const PluginClass of this.defaultPlugins) {
121
+ await this.createPlugin(PluginClass)
122
+ }
123
+
124
+ await this.loadInstalledPlugins()
125
+ await this.loadPluginsNotInstalled()
126
+ }
127
+
128
+ async loadInstalledPlugins() {
129
+ const path = await exists(this.pluginPath)
130
+ if (!path) {
131
+ await mkdir(this.pluginPath)
132
+ }
133
+ const paths = await readdir(this.pluginPath, { withFileTypes: true })
134
+ const pluginPaths = paths
135
+ .filter(
136
+ (entry) =>
137
+ entry.name !== 'node_modules' &&
138
+ (entry.isDirectory() || entry.isSymbolicLink())
139
+ )
140
+ .map((entry) => _path.join(this.pluginPath, entry.name))
141
+
142
+ for (const path of pluginPaths) {
143
+ await this.loadPlugin(path)
144
+ }
145
+ }
146
+
147
+ async loadPluginsNotInstalled() {
148
+ const availablePlugins = await appContext().pluginRegistry.getAvailablePlugins()
149
+ const configPluginNames = Object.keys(this.project.config.plugins)
150
+ if (!configPluginNames) return
151
+ const installedPluginNames = Object.keys(this.pluginsMap)
152
+ const pluginsNotInstalled = configPluginNames.filter(name => !installedPluginNames.includes(name))
153
+ for (const pluginName of pluginsNotInstalled) {
154
+ const plugin = availablePlugins.find(p => p.name === pluginName)
155
+ if (plugin && plugin.name) await this.installPlugin(plugin)
156
+ }
157
+ }
158
+
159
+ async loadPlugin(path) {
160
+ logger.log('Loading plugin: ', path)
161
+ const fullPath = path.endsWith('.js')
162
+ ? _path.resolve(path)
163
+ : _path.join(_path.resolve(path), 'bundle.js')
164
+
165
+ try {
166
+ // eslint-disable-next-line
167
+ delete require.cache[require.resolve(fullPath)]
168
+ // eslint-disable-next-line
169
+ const pluginClass = await import(_path.resolve(path))
170
+ const pluginInstance = await this.createPlugin(pluginClass.default, path)
171
+ return pluginInstance
172
+ } catch (e) {
173
+ logger.error(`Error loading plugin at: ${path}`, e)
174
+ }
175
+ }
176
+
177
+ getPackageInfo(path) {
178
+ if (!path) return {}
179
+ let info = { path }
180
+ try {
181
+ info = {...info, ...require(`${path}/package.json`) }
182
+ delete info.dependencies
183
+ delete info.devDependencies
184
+ delete info.scripts
185
+ delete info.main
186
+ } catch (e) {
187
+ logger.info('No info on plugin:', path)
188
+ }
189
+ }
190
+
191
+ async createPlugin(pluginClass, path = undefined) {
192
+ if (!this.isPlugin(pluginClass))
193
+ throw new Error(`${pluginClass.name} is not a plugin`)
194
+ const name = pluginClass.name.toString()
195
+ const pluginInstance = new pluginClass(this.project)
196
+ let info = {
197
+ name,
198
+ ...this.getPackageInfo(path),
199
+ }
200
+
201
+ pluginInstance.getSettings = () => {
202
+ return this.getPluginSettings(name)
203
+ }
204
+
205
+ this.pluginsMap[name] = {
206
+ pluginInstance,
207
+ pluginClass,
208
+ info,
209
+ }
210
+
211
+ if (pluginInstance.init) await pluginInstance.init()
212
+
213
+ return pluginInstance
214
+ }
215
+
216
+ destroyPlugins() {
217
+ this.stopDevMode()
218
+ this.eachPlugin(({ pluginInstance }) => {
219
+ try {
220
+ pluginInstance.destroy()
221
+ } catch (e) {
222
+ this.pluginError('destroy', pluginInstance, e)
223
+ }
224
+ })
225
+ }
226
+
227
+ isPlugin(pluginClass) {
228
+ // const pluginClassName = Plugin.name.toString()
229
+ // return pluginClass.proname.toString() === pluginClassName
230
+ const pluginClassPrototypeName = Object.getPrototypeOf(pluginClass.prototype).constructor.name
231
+ const pluginClassName = Plugin.name.toString()
232
+ logger.log('pluginClassPrototypeName', pluginClassPrototypeName)
233
+ logger.log('pluginClassName', pluginClassName)
234
+ return pluginClassPrototypeName === pluginClassName
235
+
236
+ // return Object.getPrototypeOf(pluginClass.prototype) === Plugin.prototype
237
+ }
238
+
239
+ eachPlugin(cb) {
240
+ Object.keys(this.pluginsMap).forEach((key) => {
241
+ cb(this.getPlugin(key))
242
+ })
243
+ }
244
+
245
+ async eachPluginAsync(cb) {
246
+ for (const key of Object.keys(this.pluginsMap)) {
247
+ await cb(this.getPlugin(key))
248
+ }
249
+ }
250
+
251
+ getPlugins() {
252
+ return Object.keys(this.pluginsMap).map((key) => {
253
+ const { info, pluginInstance } = this.getPlugin(key)
254
+ const schema = pluginInstance.getSettingsSchema()
255
+ if (schema) {
256
+ schema.id = info.name
257
+ schema.title = `${info.name} settings`
258
+ }
259
+ return { ...info, schema }
260
+ })
261
+ }
262
+
263
+ disablePlugin(name) {
264
+ delete this.pluginsMap[name]
265
+ }
266
+
267
+ getPluginName(pluginInstance) {
268
+ return Object.keys(this.pluginsMap).find(
269
+ (pluginName) =>
270
+ this.getPlugin(pluginName).pluginInstance === pluginInstance
271
+ )
272
+ }
273
+
274
+ getPluginInstance(name) {
275
+ const plugin = this.getPlugin(name)
276
+ if (!plugin) throw new Error(`Plugin ${name} not found`)
277
+ return plugin.pluginInstance
278
+ }
279
+
280
+ getPlugin(name) {
281
+ return this.pluginsMap[name]
282
+ }
283
+
284
+ getPluginSettings(name) {
285
+ return this.project.config.plugins[name] || {}
286
+ }
287
+
288
+ pluginError(method, pluginInstance, error) {
289
+ logger.warn(
290
+ `Plugin: ${this.getPluginName(
291
+ pluginInstance
292
+ )} threw an error on ${method}: `,
293
+ error
294
+ )
295
+ }
296
+
297
+ async onBoardUpdate(lists) {
298
+ if (!lists || lists.length == 0) return
299
+ await this.eachPluginAsync(async ({ pluginInstance }) => {
300
+ // const timeLabel = `${this.getPluginName(pluginInstance)} onBoardUpdate time`
301
+ // logger.time(timeLabel)
302
+ try {
303
+ await pluginInstance.onBoardUpdate(lists)
304
+ } catch (e) {
305
+ this.pluginError('onBoardUpdate', pluginInstance, e)
306
+ }
307
+ // logger.timeEnd(timeLabel)
308
+ })
309
+ return lists
310
+ }
311
+
312
+ async onBeforeBoardUpdate() {
313
+ await this.eachPluginAsync(async ({ pluginInstance }) => {
314
+ try {
315
+ await pluginInstance.onBeforeBoardUpdate()
316
+ } catch (e) {
317
+ this.pluginError('onBeforeBoardUpdate', pluginInstance, e)
318
+ }
319
+ })
320
+ }
321
+
322
+ onTaskUpdate(task) {
323
+ this.eachPlugin(({ pluginInstance }) => {
324
+ try {
325
+ pluginInstance.onTaskUpdate(task)
326
+ } catch (e) {
327
+ this.pluginError('onTaskUpdate', pluginInstance, e)
328
+ }
329
+ })
330
+ }
331
+
332
+ async onTaskFound(task) {
333
+ await this.eachPluginAsync(async ({ pluginInstance }) => {
334
+ try {
335
+ if (pluginInstance.onTaskFound) await pluginInstance.onTaskFound(task)
336
+ } catch (e) {
337
+ this.pluginError('onTaskFound', pluginInstance, e)
338
+ }
339
+ })
340
+ }
341
+
342
+ async onBeforeAddTask({path, list, content, tags, contexts, meta, useCardTemplate}) {
343
+ await this.eachPluginAsync(async ({ pluginInstance }) => {
344
+ try {
345
+ const pluginMods = await pluginInstance.onBeforeAddTask({path, list, content, tags, contexts, meta, useCardTemplate})
346
+ path = pluginMods.path
347
+ content = pluginMods.content
348
+ tags = pluginMods.tags
349
+ contexts = pluginMods.contexts
350
+ meta = pluginMods.meta
351
+ } catch (e) {
352
+ this.pluginError('onBeforeAddTask', pluginInstance, e)
353
+ }
354
+ })
355
+ return {path, content, tags, contexts, meta}
356
+ }
357
+
358
+ async onAfterDeleteTask(task) {
359
+ await this.eachPluginAsync(async ({ pluginInstance }) => {
360
+ try {
361
+ await pluginInstance.onAfterDeleteTask(task)
362
+ } catch (e) {
363
+ this.pluginError('onAfterDeleteTask', pluginInstance, e)
364
+ }
365
+ })
366
+ }
367
+
368
+ getCardProperties(props) {
369
+ let cardProps = {}
370
+ this.eachPlugin(({ pluginInstance }) => {
371
+ try {
372
+ cardProps = { ...cardProps, ...pluginInstance.getCardProperties(props) }
373
+ } catch (e) {
374
+ this.pluginError('getCardProperties', pluginInstance, e)
375
+ }
376
+ })
377
+ return cardProps
378
+ }
379
+
380
+
381
+ async getBoardProperties() {
382
+ let boardProps = {}
383
+ await this.eachPluginAsync(async ({ pluginInstance }) => {
384
+ try {
385
+ const pluginProps = pluginInstance.getBoardProperties ? await pluginInstance.getBoardProperties() : {}
386
+ boardProps = { ...boardProps, ...pluginProps }
387
+ } catch (e) {
388
+ this.pluginError('getBoardProperties', pluginInstance, e)
389
+ }
390
+ })
391
+ return boardProps
392
+ }
393
+
394
+ getCardActions(task) {
395
+ let cardLinks = []
396
+ this.eachPlugin(({ pluginInstance }) => {
397
+ try {
398
+ cardLinks = [
399
+ ...cardLinks,
400
+ ...pluginInstance.getCardActions(task).map((link, index) => {
401
+ return {
402
+ ...link,
403
+ action: { plugin: this.getPluginName(pluginInstance), index },
404
+ }
405
+ }),
406
+ ]
407
+ } catch (e) {
408
+ this.pluginError('getCardActions', pluginInstance, e)
409
+ }
410
+ })
411
+ return cardLinks
412
+ }
413
+
414
+ getBoardActions() {
415
+ let actions = []
416
+ this.eachPlugin(({ pluginInstance }) => {
417
+ try {
418
+ actions = [
419
+ ...actions,
420
+ ...pluginInstance.getBoardActions().map((item, index) => {
421
+ if (item.title) item.name = item.title
422
+ return {
423
+ ...item,
424
+ plugin: this.getPluginName(pluginInstance),
425
+ index,
426
+ }
427
+ }),
428
+ ]
429
+ } catch (e) {
430
+ this.pluginError('getBoardActions', pluginInstance, e)
431
+ }
432
+ })
433
+ return actions
434
+ }
435
+
436
+ performCardAction(action, task) {
437
+ const plugin = this.getPluginInstance(action.plugin)
438
+ try {
439
+ return plugin.getCardActions(task)[action.index].action()
440
+ } catch (e) {
441
+ this.pluginError('getCardActions', plugin, e)
442
+ }
443
+ }
444
+
445
+ async performBoardAction(action, task) {
446
+ const title = action.title || action.name
447
+ const plugin = this.getPluginInstance(action.plugin)
448
+ try {
449
+ return action.index
450
+ ? await plugin.getBoardActions()[action.index].action(task)
451
+ : await plugin.getBoardActions().find(a => a.title === title).action(task)
452
+ } catch (e) {
453
+
454
+ this.pluginError('getBoardActions', plugin, e)
455
+ throw new Error(`Error performing action ${title} on plugin ${plugin.name}: ${e.message}`)
456
+ }
457
+ }
458
+ }
@@ -0,0 +1,57 @@
1
+ import https from 'https'
2
+ import {
3
+ renderMarkdown,
4
+ } from '../adapters/markdown.js'
5
+
6
+ export async function getAvailablePlugins() {
7
+ const response = await fetch('https://raw.githubusercontent.com/imdone/imdone-plugins/main/index.json')
8
+ const plugins = response.body
9
+ for (const plugin of plugins) {
10
+ plugin.content = renderMarkdown(await getReadmeContent(plugin))
11
+ }
12
+ return plugins
13
+ }
14
+
15
+ async function getReadmeContent({version}) {
16
+ const response = await fetch(`https://api.github.com/repos/${version}/readme`)
17
+ const { content } = response?.body
18
+ if (!content) return ''
19
+ const buffer = Buffer.from(content, 'base64');
20
+ return buffer.toString('utf-8');
21
+ }
22
+
23
+ async function fetch(url, opts = { method: 'GET', headers: {} }) {
24
+ const { method, headers, body, debug = () => { } } = opts
25
+ if (body) headers['Content-Length'] = body.length
26
+ headers['Cache-Control'] = 'no-cache'; // Add Cache-Control header
27
+ url = new URL(url)
28
+ const port = url.protocol === 'https:' ? 443 : 80
29
+ const options = {
30
+ hostname: url.hostname,
31
+ port: port,
32
+ path: url.pathname,
33
+ method,
34
+ headers: {
35
+ 'User-Agent': 'imdone',
36
+ ...headers,
37
+ },
38
+ }
39
+
40
+ debug("Fetch request:", JSON.stringify({ method, url, body }))
41
+
42
+ return new Promise((resolve, reject) => {
43
+ let result = []
44
+
45
+ const req = https.request(options, (res) => {
46
+ res.on('data', (chunk) => result.push(chunk))
47
+ res.on('end', () => {
48
+ const data = Buffer.concat(result).toString()
49
+ debug("Fetch response:", JSON.stringify({ data, status: res.statusCode }))
50
+ resolve({ body: data && JSON.parse(data), status: res.statusCode })
51
+ })
52
+ })
53
+ req.on('error', reject)
54
+ if (body) req.write(body)
55
+ req.end()
56
+ })
57
+ }
@@ -0,0 +1,42 @@
1
+ import { Repository } from './repository.js'
2
+ import { watchedFileSystemStoreMixin } from './mixins/repo-watched-fs-store.js'
3
+ import { fileSystemStoreMixin } from './mixins/repo-fs-store.js'
4
+ import { ProjectContext } from './ProjectContext.js'
5
+ import { WorkerProject } from './project.js'
6
+ import { appContext as context } from './context/ApplicationContext.js'
7
+ import _path from 'node:path'
8
+
9
+ export function createFileSystemProject({
10
+ path,
11
+ config,
12
+ repo = fileSystemStoreMixin(new Repository(_path.resolve(path), config)),
13
+ loadInstalledPlugins,
14
+ loadPluginsNotInstalled
15
+ }) {
16
+ context().repo = repo
17
+ context().projectContext = new ProjectContext(repo)
18
+ context().project = new WorkerProject(repo)
19
+ if (loadInstalledPlugins) {
20
+ context().project.pluginManager.loadInstalledPlugins = loadInstalledPlugins
21
+ }
22
+ if (loadPluginsNotInstalled) {
23
+ context().project.pluginManager.loadPluginsNotInstalled = loadPluginsNotInstalled
24
+ }
25
+ return context().project
26
+ }
27
+
28
+ export function createWatchedFileSystemProject({
29
+ path,
30
+ config,
31
+ repo = watchedFileSystemStoreMixin(new Repository(_path.resolve(path), config)),
32
+ loadInstalledPlugins,
33
+ loadPluginsNotInstalled
34
+ }) {
35
+ return createFileSystemProject({
36
+ path,
37
+ config,
38
+ repo,
39
+ loadInstalledPlugins,
40
+ loadPluginsNotInstalled
41
+ })
42
+ }