@lapis-notes/api 0.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.
Files changed (400) hide show
  1. package/CHANGELOG.md +9 -0
  2. package/LICENSE.md +4 -0
  3. package/README.md +43 -0
  4. package/dist/__tests__/data-adapter-conformance.js +205 -0
  5. package/dist/__tests__/lapis-runtime-manifest.js +32 -0
  6. package/dist/__tests__/svelte-internal-client.d.ts +3 -0
  7. package/dist/agent-result-views.d.ts +52 -0
  8. package/dist/agent-result-views.js +89 -0
  9. package/dist/agent-skills.d.ts +99 -0
  10. package/dist/agent-skills.js +182 -0
  11. package/dist/agent-tools.d.ts +92 -0
  12. package/dist/agent-tools.js +139 -0
  13. package/dist/app-database-host.d.ts +4 -0
  14. package/dist/app-database-host.js +4 -0
  15. package/dist/app-shell-plugin-persistence.d.ts +15 -0
  16. package/dist/app-shell-plugin-persistence.js +34 -0
  17. package/dist/app-url.d.ts +54 -0
  18. package/dist/app-url.js +269 -0
  19. package/dist/application-compatibility.d.ts +31 -0
  20. package/dist/application-compatibility.js +64 -0
  21. package/dist/application-state.svelte.d.ts +14 -0
  22. package/dist/application-state.svelte.js +30 -0
  23. package/dist/assets/lapis-logo.svg +471 -0
  24. package/dist/bases.d.ts +281 -0
  25. package/dist/bases.js +475 -0
  26. package/dist/cache.svelte.d.ts +569 -0
  27. package/dist/cache.svelte.js +1433 -0
  28. package/dist/command.svelte.d.ts +337 -0
  29. package/dist/command.svelte.js +571 -0
  30. package/dist/compat.d.ts +29 -0
  31. package/dist/compat.js +30 -0
  32. package/dist/components/configuration/configuration.css +199 -0
  33. package/dist/components/configuration/date-setting.svelte +71 -0
  34. package/dist/components/configuration/date-setting.svelte.d.ts +8 -0
  35. package/dist/components/configuration/list-setting.svelte +128 -0
  36. package/dist/components/configuration/list-setting.svelte.d.ts +13 -0
  37. package/dist/components/configuration/object-array-row-grip.svelte +78 -0
  38. package/dist/components/configuration/object-array-row-grip.svelte.d.ts +9 -0
  39. package/dist/components/configuration/object-array-row.svelte +163 -0
  40. package/dist/components/configuration/object-array-row.svelte.d.ts +19 -0
  41. package/dist/components/configuration/object-array-setting.svelte +196 -0
  42. package/dist/components/configuration/object-array-setting.svelte.d.ts +15 -0
  43. package/dist/components/configuration/object-array-table-dnd.d.ts +8 -0
  44. package/dist/components/configuration/object-array-table-dnd.js +10 -0
  45. package/dist/components/configuration/object-array-utils.d.ts +11 -0
  46. package/dist/components/configuration/object-array-utils.js +103 -0
  47. package/dist/components/configuration/object-grid-setting.svelte +79 -0
  48. package/dist/components/configuration/object-grid-setting.svelte.d.ts +10 -0
  49. package/dist/components/configuration/object-map-row.svelte +147 -0
  50. package/dist/components/configuration/object-map-row.svelte.d.ts +15 -0
  51. package/dist/components/configuration/object-map-setting.svelte +137 -0
  52. package/dist/components/configuration/object-map-setting.svelte.d.ts +14 -0
  53. package/dist/components/configuration/object-map-types.d.ts +5 -0
  54. package/dist/components/configuration/object-map-types.js +1 -0
  55. package/dist/components/configuration/options-combobox-setting.svelte +129 -0
  56. package/dist/components/configuration/options-combobox-setting.svelte.d.ts +12 -0
  57. package/dist/components/copy-button/button.svelte +93 -0
  58. package/dist/components/copy-button/button.svelte.d.ts +13 -0
  59. package/dist/components/copy-button/index.d.ts +2 -0
  60. package/dist/components/copy-button/index.js +2 -0
  61. package/dist/components/editor/editor.css +565 -0
  62. package/dist/components/editor/editor.d.ts +33 -0
  63. package/dist/components/editor/editor.js +132 -0
  64. package/dist/components/editor/editor.svelte +176 -0
  65. package/dist/components/editor/editor.svelte.d.ts +15 -0
  66. package/dist/components/editor/embedded-editor-surface.d.ts +12 -0
  67. package/dist/components/editor/embedded-editor-surface.js +22 -0
  68. package/dist/components/editor/embedded-editor-surface.svelte +115 -0
  69. package/dist/components/editor/embedded-editor-surface.svelte.d.ts +17 -0
  70. package/dist/components/editor/extensions/autocomplete/index.d.ts +2 -0
  71. package/dist/components/editor/extensions/autocomplete/index.js +2 -0
  72. package/dist/components/editor/extensions/autocomplete/lapis-code-mirror-autocomplete.d.ts +16 -0
  73. package/dist/components/editor/extensions/autocomplete/lapis-code-mirror-autocomplete.js +34 -0
  74. package/dist/components/editor/extensions/autocomplete/vault-completion-sources.d.ts +27 -0
  75. package/dist/components/editor/extensions/autocomplete/vault-completion-sources.js +55 -0
  76. package/dist/components/editor/extensions/class-highlighter/index.d.ts +1 -0
  77. package/dist/components/editor/extensions/class-highlighter/index.js +47 -0
  78. package/dist/components/editor/extensions/fold-gutter/index.d.ts +8 -0
  79. package/dist/components/editor/extensions/fold-gutter/index.js +40 -0
  80. package/dist/components/editor/extensions/gutter-line-style-sync/index.d.ts +2 -0
  81. package/dist/components/editor/extensions/gutter-line-style-sync/index.js +118 -0
  82. package/dist/components/editor/extensions/lint/index.d.ts +4 -0
  83. package/dist/components/editor/extensions/lint/index.js +4 -0
  84. package/dist/components/editor/extensions/lint/lapis-code-mirror-lint.d.ts +26 -0
  85. package/dist/components/editor/extensions/lint/lapis-code-mirror-lint.js +101 -0
  86. package/dist/components/editor/extensions/lint/lapis-lint-diagnostic-helpers.d.ts +8 -0
  87. package/dist/components/editor/extensions/lint/lapis-lint-diagnostic-helpers.js +7 -0
  88. package/dist/components/editor/extensions/lint/lapis-lint-diagnostic.d.ts +20 -0
  89. package/dist/components/editor/extensions/lint/lapis-lint-diagnostic.js +47 -0
  90. package/dist/components/editor/extensions/lint/lapis-lint-hover-tooltip.d.ts +9 -0
  91. package/dist/components/editor/extensions/lint/lapis-lint-hover-tooltip.js +373 -0
  92. package/dist/components/editor/extensions/lint/lapis-lint-inline-widget.d.ts +31 -0
  93. package/dist/components/editor/extensions/lint/lapis-lint-inline-widget.js +191 -0
  94. package/dist/components/editor/extensions/lint/lapis-lint-tooltip.svelte +176 -0
  95. package/dist/components/editor/extensions/lint/lapis-lint-tooltip.svelte.d.ts +15 -0
  96. package/dist/components/editor/extensions/lint/lint-tooltip-actions.d.ts +11 -0
  97. package/dist/components/editor/extensions/lint/lint-tooltip-actions.js +21 -0
  98. package/dist/components/editor/extensions/lint/mount-lint-tooltip.d.ts +16 -0
  99. package/dist/components/editor/extensions/lint/mount-lint-tooltip.js +60 -0
  100. package/dist/components/editor/extensions/search/dom.d.ts +2 -0
  101. package/dist/components/editor/extensions/search/dom.js +6 -0
  102. package/dist/components/editor/extensions/search/index.d.ts +35 -0
  103. package/dist/components/editor/extensions/search/index.js +44 -0
  104. package/dist/components/editor/extensions/search/search.svelte +335 -0
  105. package/dist/components/editor/extensions/search/search.svelte.d.ts +8 -0
  106. package/dist/components/editor/index.d.ts +9 -0
  107. package/dist/components/editor/index.js +9 -0
  108. package/dist/components/editor/language-service/dom.d.ts +1 -0
  109. package/dist/components/editor/language-service/dom.js +6 -0
  110. package/dist/components/editor/language-service/index.d.ts +28 -0
  111. package/dist/components/editor/language-service/index.js +342 -0
  112. package/dist/components/empty-view/empty-view.css +39 -0
  113. package/dist/components/empty-view/empty-view.svelte +26 -0
  114. package/dist/components/empty-view/empty-view.svelte.d.ts +9 -0
  115. package/dist/components/empty-view/index.d.ts +2 -0
  116. package/dist/components/empty-view/index.js +2 -0
  117. package/dist/components/icon/icon-list.css +74 -0
  118. package/dist/components/icon/icon-list.svelte +213 -0
  119. package/dist/components/icon/icon-list.svelte.d.ts +11 -0
  120. package/dist/components/icon/icon.svelte +15 -0
  121. package/dist/components/icon/icon.svelte.d.ts +7 -0
  122. package/dist/components/icon/index.d.ts +3 -0
  123. package/dist/components/icon/index.js +3 -0
  124. package/dist/components/menu/context-menu.svelte +89 -0
  125. package/dist/components/menu/context-menu.svelte.d.ts +11 -0
  126. package/dist/components/menu/drawer-menu.svelte +83 -0
  127. package/dist/components/menu/drawer-menu.svelte.d.ts +8 -0
  128. package/dist/components/menu/dropdown-menu.svelte +89 -0
  129. package/dist/components/menu/dropdown-menu.svelte.d.ts +11 -0
  130. package/dist/components/menu/index.d.ts +6 -0
  131. package/dist/components/menu/index.js +6 -0
  132. package/dist/components/menu/menu.css +139 -0
  133. package/dist/components/menu/menu.svelte +37 -0
  134. package/dist/components/menu/menu.svelte.d.ts +11 -0
  135. package/dist/components/menu/mobile-menu-query.svelte.d.ts +3 -0
  136. package/dist/components/menu/mobile-menu-query.svelte.js +21 -0
  137. package/dist/components/menu/popover-menu.svelte +90 -0
  138. package/dist/components/menu/popover-menu.svelte.d.ts +10 -0
  139. package/dist/components/select/index.d.ts +2 -0
  140. package/dist/components/select/index.js +2 -0
  141. package/dist/components/select/select.svelte +72 -0
  142. package/dist/components/select/select.svelte.d.ts +25 -0
  143. package/dist/configuration-option-source-providers.d.ts +17 -0
  144. package/dist/configuration-option-source-providers.js +59 -0
  145. package/dist/configuration-option-source-registry.d.ts +59 -0
  146. package/dist/configuration-option-source-registry.js +123 -0
  147. package/dist/configuration.svelte.d.ts +258 -0
  148. package/dist/configuration.svelte.js +887 -0
  149. package/dist/context-keys.svelte.d.ts +42 -0
  150. package/dist/context-keys.svelte.js +278 -0
  151. package/dist/context.svelte.d.ts +247 -0
  152. package/dist/context.svelte.js +540 -0
  153. package/dist/daily-document-provider.d.ts +38 -0
  154. package/dist/daily-document-provider.js +176 -0
  155. package/dist/design-core-button-types.d.ts +7 -0
  156. package/dist/design-core-button-types.js +1 -0
  157. package/dist/diagnostics.d.ts +93 -0
  158. package/dist/diagnostics.js +84 -0
  159. package/dist/dialog-portal.d.ts +3 -0
  160. package/dist/dialog-portal.js +3 -0
  161. package/dist/docs-api.d.ts +44 -0
  162. package/dist/docs-api.js +29 -0
  163. package/dist/editor-view-registry.d.ts +35 -0
  164. package/dist/editor-view-registry.js +112 -0
  165. package/dist/editor.svelte.d.ts +126 -0
  166. package/dist/editor.svelte.js +454 -0
  167. package/dist/embed-registry.d.ts +15 -0
  168. package/dist/embed-registry.js +16 -0
  169. package/dist/enhance.d.ts +111 -0
  170. package/dist/enhance.js +309 -0
  171. package/dist/events.d.ts +28 -0
  172. package/dist/events.js +49 -0
  173. package/dist/file-manager.d.ts +90 -0
  174. package/dist/file-manager.js +289 -0
  175. package/dist/file-tools/app-tools.d.ts +3 -0
  176. package/dist/file-tools/app-tools.js +242 -0
  177. package/dist/file-tools/index.d.ts +3 -0
  178. package/dist/file-tools/index.js +3 -0
  179. package/dist/file-tools/paths.d.ts +14 -0
  180. package/dist/file-tools/paths.js +33 -0
  181. package/dist/file-tools/vault-operations.d.ts +3 -0
  182. package/dist/file-tools/vault-operations.js +60 -0
  183. package/dist/generated/plugin-host-modules.schema.generated.d.ts +371 -0
  184. package/dist/generated/plugin-host-modules.schema.generated.js +374 -0
  185. package/dist/glob.d.ts +15 -0
  186. package/dist/glob.js +68 -0
  187. package/dist/history.svelte.d.ts +24 -0
  188. package/dist/history.svelte.js +74 -0
  189. package/dist/hooks/createVirtualizer.svelte.d.ts +7 -0
  190. package/dist/hooks/createVirtualizer.svelte.js +70 -0
  191. package/dist/hooks/index.d.ts +5 -0
  192. package/dist/hooks/index.js +5 -0
  193. package/dist/hooks/is-mobile.svelte.d.ts +5 -0
  194. package/dist/hooks/is-mobile.svelte.js +23 -0
  195. package/dist/hooks/mountComponent.svelte.d.ts +7 -0
  196. package/dist/hooks/mountComponent.svelte.js +16 -0
  197. package/dist/hooks/use-text-highlight.d.ts +17 -0
  198. package/dist/hooks/use-text-highlight.js +82 -0
  199. package/dist/hooks/useBoundingRect.svelte.d.ts +11 -0
  200. package/dist/hooks/useBoundingRect.svelte.js +46 -0
  201. package/dist/icons/index.d.ts +57 -0
  202. package/dist/icons/index.js +288 -0
  203. package/dist/index.d.ts +87 -0
  204. package/dist/index.js +87 -0
  205. package/dist/language-service/index.d.ts +9 -0
  206. package/dist/language-service/index.js +7 -0
  207. package/dist/language-service/manager.d.ts +47 -0
  208. package/dist/language-service/manager.js +404 -0
  209. package/dist/language-service/types.d.ts +92 -0
  210. package/dist/language-service/types.js +1 -0
  211. package/dist/language-service/virtual-document.d.ts +11 -0
  212. package/dist/language-service/virtual-document.js +45 -0
  213. package/dist/language-service/worker-protocol.d.ts +63 -0
  214. package/dist/language-service/worker-protocol.js +1 -0
  215. package/dist/language-service/worker-provider.d.ts +9 -0
  216. package/dist/language-service/worker-provider.js +38 -0
  217. package/dist/lapis-extension.d.ts +244 -0
  218. package/dist/lapis-extension.js +591 -0
  219. package/dist/links.d.ts +34 -0
  220. package/dist/links.js +191 -0
  221. package/dist/loaders.d.ts +9 -0
  222. package/dist/loaders.js +59 -0
  223. package/dist/localization-manager.svelte.d.ts +56 -0
  224. package/dist/localization-manager.svelte.js +157 -0
  225. package/dist/logging.d.ts +172 -0
  226. package/dist/logging.js +352 -0
  227. package/dist/markdown-extension-registry.d.ts +31 -0
  228. package/dist/markdown-extension-registry.js +31 -0
  229. package/dist/markdown-file-surface-registry.d.ts +25 -0
  230. package/dist/markdown-file-surface-registry.js +23 -0
  231. package/dist/markdown.d.ts +205 -0
  232. package/dist/markdown.js +147 -0
  233. package/dist/menu-utils.d.ts +1 -0
  234. package/dist/menu-utils.js +47 -0
  235. package/dist/menu.svelte.d.ts +121 -0
  236. package/dist/menu.svelte.js +295 -0
  237. package/dist/metadata-value.d.ts +10 -0
  238. package/dist/metadata-value.js +173 -0
  239. package/dist/metadata.svelte.d.ts +106 -0
  240. package/dist/metadata.svelte.js +608 -0
  241. package/dist/notifications.d.ts +209 -0
  242. package/dist/notifications.js +338 -0
  243. package/dist/open-file.d.ts +23 -0
  244. package/dist/open-file.js +102 -0
  245. package/dist/platform.d.ts +86 -0
  246. package/dist/platform.js +86 -0
  247. package/dist/plugin-asset-server.d.ts +60 -0
  248. package/dist/plugin-asset-server.js +222 -0
  249. package/dist/plugin-assets.d.ts +4 -0
  250. package/dist/plugin-assets.js +3 -0
  251. package/dist/plugin-capability-facade.d.ts +104 -0
  252. package/dist/plugin-capability-facade.js +61 -0
  253. package/dist/plugin-dependency-resolver.d.ts +36 -0
  254. package/dist/plugin-dependency-resolver.js +54 -0
  255. package/dist/plugin-dependency-scanner.d.ts +9 -0
  256. package/dist/plugin-dependency-scanner.js +274 -0
  257. package/dist/plugin-distribution/canonical-json.d.ts +1 -0
  258. package/dist/plugin-distribution/canonical-json.js +40 -0
  259. package/dist/plugin-distribution/compatibility.d.ts +5 -0
  260. package/dist/plugin-distribution/compatibility.js +38 -0
  261. package/dist/plugin-distribution/errors.d.ts +11 -0
  262. package/dist/plugin-distribution/errors.js +13 -0
  263. package/dist/plugin-distribution/hashes.d.ts +2 -0
  264. package/dist/plugin-distribution/hashes.js +17 -0
  265. package/dist/plugin-distribution/index.d.ts +18 -0
  266. package/dist/plugin-distribution/index.js +18 -0
  267. package/dist/plugin-distribution/installed-plugin-state.d.ts +20 -0
  268. package/dist/plugin-distribution/installed-plugin-state.js +73 -0
  269. package/dist/plugin-distribution/installer.d.ts +89 -0
  270. package/dist/plugin-distribution/installer.js +395 -0
  271. package/dist/plugin-distribution/manager.d.ts +128 -0
  272. package/dist/plugin-distribution/manager.js +358 -0
  273. package/dist/plugin-distribution/path-safety.d.ts +2 -0
  274. package/dist/plugin-distribution/path-safety.js +20 -0
  275. package/dist/plugin-distribution/plugin-bundle-verifier.d.ts +28 -0
  276. package/dist/plugin-distribution/plugin-bundle-verifier.js +96 -0
  277. package/dist/plugin-distribution/plugin-bundle-worker.d.ts +34 -0
  278. package/dist/plugin-distribution/plugin-bundle-worker.js +99 -0
  279. package/dist/plugin-distribution/plugin-bundle.d.ts +16 -0
  280. package/dist/plugin-distribution/plugin-bundle.js +244 -0
  281. package/dist/plugin-distribution/plugin-bundle.worker.d.ts +1 -0
  282. package/dist/plugin-distribution/plugin-bundle.worker.js +38 -0
  283. package/dist/plugin-distribution/registry-cache.d.ts +26 -0
  284. package/dist/plugin-distribution/registry-cache.js +85 -0
  285. package/dist/plugin-distribution/registry-client.d.ts +36 -0
  286. package/dist/plugin-distribution/registry-client.js +115 -0
  287. package/dist/plugin-distribution/reserved-ids.d.ts +4 -0
  288. package/dist/plugin-distribution/reserved-ids.js +10 -0
  289. package/dist/plugin-distribution/runtime-validation.d.ts +14 -0
  290. package/dist/plugin-distribution/runtime-validation.js +388 -0
  291. package/dist/plugin-distribution/schemas.d.ts +4895 -0
  292. package/dist/plugin-distribution/schemas.js +299 -0
  293. package/dist/plugin-distribution/signing.d.ts +5 -0
  294. package/dist/plugin-distribution/signing.js +38 -0
  295. package/dist/plugin-distribution/types.d.ts +217 -0
  296. package/dist/plugin-distribution/types.js +1 -0
  297. package/dist/plugin-install-progress.d.ts +15 -0
  298. package/dist/plugin-install-progress.js +86 -0
  299. package/dist/plugin-manager.d.ts +306 -0
  300. package/dist/plugin-manager.js +2848 -0
  301. package/dist/plugin-runtime-entry.d.ts +19 -0
  302. package/dist/plugin-runtime-entry.js +135 -0
  303. package/dist/plugin.d.ts +350 -0
  304. package/dist/plugin.js +754 -0
  305. package/dist/popover.d.ts +20 -0
  306. package/dist/popover.js +32 -0
  307. package/dist/promise-worker.d.ts +22 -0
  308. package/dist/promise-worker.js +65 -0
  309. package/dist/prompt-confirm.d.ts +9 -0
  310. package/dist/prompt-confirm.js +33 -0
  311. package/dist/search-document-provider.d.ts +45 -0
  312. package/dist/search-document-provider.js +54 -0
  313. package/dist/search-query/index.d.ts +78 -0
  314. package/dist/search-query/index.js +426 -0
  315. package/dist/search-query/parser.d.ts +3 -0
  316. package/dist/search-query/parser.js +16 -0
  317. package/dist/search-query/parser.terms.js +26 -0
  318. package/dist/search-query/search-query.grammar +128 -0
  319. package/dist/search.d.ts +19 -0
  320. package/dist/search.js +104 -0
  321. package/dist/setting-section-layout.d.ts +19 -0
  322. package/dist/setting-section-layout.js +194 -0
  323. package/dist/settings.svelte.d.ts +474 -0
  324. package/dist/settings.svelte.js +1421 -0
  325. package/dist/source-text-file-view.d.ts +25 -0
  326. package/dist/source-text-file-view.js +78 -0
  327. package/dist/status-bar.svelte.d.ts +29 -0
  328. package/dist/status-bar.svelte.js +72 -0
  329. package/dist/storage/app-database.d.ts +860 -0
  330. package/dist/storage/app-database.js +2372 -0
  331. package/dist/storage/blob-store.d.ts +12 -0
  332. package/dist/storage/blob-store.js +1 -0
  333. package/dist/storage/browser-app-database-coordination.d.ts +35 -0
  334. package/dist/storage/browser-app-database-coordination.js +207 -0
  335. package/dist/storage/browser-coordinated-app-database.d.ts +109 -0
  336. package/dist/storage/browser-coordinated-app-database.js +704 -0
  337. package/dist/storage/browser.d.ts +153 -0
  338. package/dist/storage/browser.js +794 -0
  339. package/dist/storage/desktop-native.d.ts +184 -0
  340. package/dist/storage/desktop-native.js +415 -0
  341. package/dist/storage/fs.d.ts +188 -0
  342. package/dist/storage/fs.js +307 -0
  343. package/dist/storage/index-projection.d.ts +236 -0
  344. package/dist/storage/index-projection.js +402 -0
  345. package/dist/storage/index.d.ts +17 -0
  346. package/dist/storage/index.js +17 -0
  347. package/dist/storage/keyval.d.ts +94 -0
  348. package/dist/storage/keyval.js +199 -0
  349. package/dist/storage/memory-vault-adapter.d.ts +44 -0
  350. package/dist/storage/memory-vault-adapter.js +351 -0
  351. package/dist/storage/native-vault-bootstrap-kv.d.ts +22 -0
  352. package/dist/storage/native-vault-bootstrap-kv.js +96 -0
  353. package/dist/storage/path.d.ts +22 -0
  354. package/dist/storage/path.js +110 -0
  355. package/dist/storage/search-embedding-provider.d.ts +75 -0
  356. package/dist/storage/search-embedding-provider.js +446 -0
  357. package/dist/storage/search-embedding-worker.d.ts +1 -0
  358. package/dist/storage/search-embedding-worker.js +63 -0
  359. package/dist/storage/search-query-evaluator.d.ts +14 -0
  360. package/dist/storage/search-query-evaluator.js +678 -0
  361. package/dist/storage/task-projection.d.ts +152 -0
  362. package/dist/storage/task-projection.js +154 -0
  363. package/dist/storage/turso-app-database.d.ts +150 -0
  364. package/dist/storage/turso-app-database.js +2671 -0
  365. package/dist/storage/vault-session.d.ts +37 -0
  366. package/dist/storage/vault-session.js +196 -0
  367. package/dist/storage/vault-state.d.ts +74 -0
  368. package/dist/storage/vault-state.js +181 -0
  369. package/dist/storage/vault.svelte.d.ts +257 -0
  370. package/dist/storage/vault.svelte.js +661 -0
  371. package/dist/suggest.d.ts +193 -0
  372. package/dist/suggest.js +391 -0
  373. package/dist/tasks.d.ts +7 -0
  374. package/dist/tasks.js +18 -0
  375. package/dist/telemetry.d.ts +116 -0
  376. package/dist/telemetry.js +103 -0
  377. package/dist/utils.d.ts +55 -0
  378. package/dist/utils.js +314 -0
  379. package/dist/vault-api.d.ts +5 -0
  380. package/dist/vault-api.js +3 -0
  381. package/dist/vault-maintenance-commands.d.ts +5 -0
  382. package/dist/vault-maintenance-commands.js +32 -0
  383. package/dist/view.svelte.d.ts +215 -0
  384. package/dist/view.svelte.js +381 -0
  385. package/dist/vite-env.d.ts +2 -0
  386. package/dist/workspace-host-internal.d.ts +7 -0
  387. package/dist/workspace-host-internal.js +7 -0
  388. package/dist/workspace-host.d.ts +10 -0
  389. package/dist/workspace-host.js +14 -0
  390. package/dist/workspace-layout-commands.d.ts +3 -0
  391. package/dist/workspace-layout-commands.js +72 -0
  392. package/dist/workspace-layout-normalizer.d.ts +92 -0
  393. package/dist/workspace-layout-normalizer.js +301 -0
  394. package/dist/workspace-layouts.d.ts +7 -0
  395. package/dist/workspace-layouts.js +23 -0
  396. package/dist/workspace-trust.d.ts +32 -0
  397. package/dist/workspace-trust.js +72 -0
  398. package/dist/workspace.svelte.d.ts +796 -0
  399. package/dist/workspace.svelte.js +4380 -0
  400. package/package.json +240 -0
@@ -0,0 +1,2848 @@
1
+ import { Platform } from "./platform";
2
+ import { getNativeDesktopBridge, } from "./storage/desktop-native";
3
+ import { activationEventForContribution, buildLapisContributionIndex, classifyLapisPlugin, commandContributionToCommand, isLapisManifest, normalizeManifestCommandId, permissionsToCapabilities, validateLapisManifest, } from "./lapis-extension";
4
+ import { selectPluginRuntimeEntry, } from "./plugin-runtime-entry";
5
+ import { Plugin, resolvePluginDataPath, } from "./plugin";
6
+ import { HOSTED_PLUGIN_CAPABILITIES, } from "./plugin-capability-facade";
7
+ import { isPluginDependencyResolver, LegacyObjectDependencyResolver, } from "./plugin-dependency-resolver";
8
+ import { ELECTRON_PLUGIN_ASSET_SCHEME, WEB_PLUGIN_ASSET_ROUTE_PREFIX, } from "./plugin-asset-server";
9
+ import { scanBarePluginDependencySpecifiers } from "./plugin-dependency-scanner";
10
+ import pluginHostModuleRegistry from "./generated/plugin-host-modules.schema.generated";
11
+ import { InstalledPluginStateStore } from "./plugin-distribution/installed-plugin-state";
12
+ import { dirname, getAdapterVaultId, joinPath, normalizePath } from "./storage";
13
+ import { debounce } from "lodash-es";
14
+ import { EventDispatcher } from "./events";
15
+ function formatPluginError(error) {
16
+ return error instanceof Error ? error.message : String(error);
17
+ }
18
+ function resolvePluginModuleExport(moduleExports) {
19
+ const exportRecord = isRecord(moduleExports) ? moduleExports : {};
20
+ for (const [, value] of Object.entries(exportRecord)) {
21
+ if (value === Plugin) {
22
+ return value;
23
+ }
24
+ }
25
+ return (moduleExports?.default ||
26
+ Object.values(exportRecord)[0] ||
27
+ moduleExports);
28
+ }
29
+ function isRecord(value) {
30
+ return typeof value === "object" && value !== null && !Array.isArray(value);
31
+ }
32
+ function isMissingPathError(error) {
33
+ const err = error;
34
+ return err?.name === "NotFoundError" || err?.code === "ENOENT";
35
+ }
36
+ function pluginSourceForProvenance(provenance) {
37
+ return provenance === "official" ? "official" : "community";
38
+ }
39
+ function provenanceForPluginSource(source) {
40
+ switch (source) {
41
+ case "core":
42
+ case "system":
43
+ return "bundled";
44
+ case "official":
45
+ return "official";
46
+ default:
47
+ return "community";
48
+ }
49
+ }
50
+ function isExternalPluginSource(source) {
51
+ return source === "community" || source === "official";
52
+ }
53
+ function requiresWorkspaceTrust(manifest) {
54
+ const hostRequiresBrokeredCapabilities = normalizeStringArray(manifest.requiredCapabilities).length > 0;
55
+ return Boolean(hostRequiresBrokeredCapabilities || manifest.lapis?.runtime?.trustedDesktop);
56
+ }
57
+ function trustFailureMessage(manifest) {
58
+ if (manifest.lapis?.runtime?.trustedDesktop) {
59
+ return `Plugin ${manifest.id} requires a trusted workspace before loading trusted desktop runtime code`;
60
+ }
61
+ return `Plugin ${manifest.id} requires a trusted workspace before using brokered plugin capabilities`;
62
+ }
63
+ function hasDeclaredLapisRuntimeCode(manifest) {
64
+ const runtime = manifest.lapis?.runtime;
65
+ if (!isRecord(runtime)) {
66
+ return false;
67
+ }
68
+ if (typeof runtime.workspace === "string" ||
69
+ typeof runtime.desktop === "string" ||
70
+ typeof runtime.trustedDesktop === "string") {
71
+ return true;
72
+ }
73
+ if (!isRecord(runtime.entries)) {
74
+ return false;
75
+ }
76
+ return Object.values(runtime.entries).some((entry) => isRecord(entry) && typeof entry.path === "string");
77
+ }
78
+ export class RendererCommunityPluginExecutionHost {
79
+ adapter;
80
+ id = "renderer";
81
+ resolver;
82
+ constructor(adapter, dependenciesOrResolver) {
83
+ this.adapter = adapter;
84
+ this.resolver = isPluginDependencyResolver(dependenciesOrResolver)
85
+ ? dependenciesOrResolver
86
+ : new LegacyObjectDependencyResolver(dependenciesOrResolver);
87
+ }
88
+ async evaluate(request) {
89
+ try {
90
+ const dependencyContext = {
91
+ pluginId: request.pluginId,
92
+ pluginPath: request.pluginPath,
93
+ manifest: request.manifest,
94
+ host: request.selectedRuntime?.host ?? "workspace",
95
+ format: "commonjs",
96
+ };
97
+ const moduleSources = await this.readPluginModuleSources(request.pluginPath);
98
+ await this.resolver.prepare(getBareDependencySpecifiersFromSources(moduleSources), dependencyContext);
99
+ const moduleCache = new Map();
100
+ const loadModule = (modulePath) => {
101
+ const normalizedModulePath = normalizePath(modulePath);
102
+ const cached = moduleCache.get(normalizedModulePath);
103
+ if (cached) {
104
+ return cached.exports;
105
+ }
106
+ const code = moduleSources.get(normalizedModulePath);
107
+ if (code === undefined) {
108
+ throw new Error(`Cannot find local module ${normalizedModulePath} in plugin ${request.pluginId}`);
109
+ }
110
+ const exports = {};
111
+ const module = { exports };
112
+ moduleCache.set(normalizedModulePath, module);
113
+ const executeModule = new Function("exports", "module", "require", code);
114
+ executeModule(exports, module, (dep) => this.requireDependency(normalizedModulePath, dep, moduleSources, loadModule, dependencyContext));
115
+ return module.exports;
116
+ };
117
+ const moduleExports = loadModule(request.modulePath);
118
+ return resolvePluginModuleExport(moduleExports);
119
+ }
120
+ catch (error) {
121
+ throw new Error(`Error evaluating plugin module for ${request.pluginId}: ${formatPluginError(error)}`);
122
+ }
123
+ }
124
+ requireDependency(importerPath, dep, moduleSources, loadModule, dependencyContext) {
125
+ if (dep.startsWith("./") || dep.startsWith("../")) {
126
+ return loadModule(this.resolveLocalModulePath(importerPath, dep, moduleSources));
127
+ }
128
+ return this.wrapDependencyForRequire(dep, this.resolver.require(dep, dependencyContext));
129
+ }
130
+ wrapDependencyForRequire(dep, dependency) {
131
+ if (!isRecord(dependency)) {
132
+ return dependency;
133
+ }
134
+ return new Proxy(dependency, {
135
+ get(target, prop, receiver) {
136
+ const value = Reflect.get(target, prop, receiver);
137
+ if (value === undefined) {
138
+ console.log(`error: Unable to find: ${prop.toString()} in ${dep}`);
139
+ }
140
+ return value;
141
+ },
142
+ });
143
+ }
144
+ resolveLocalModulePath(importerPath, dep, moduleSources) {
145
+ const basePath = normalizePath(joinPath(dirname(importerPath), dep));
146
+ const candidates = [
147
+ basePath,
148
+ `${basePath}.js`,
149
+ `${basePath}.cjs`,
150
+ `${basePath}.mjs`,
151
+ joinPath(basePath, "index.js"),
152
+ joinPath(basePath, "index.cjs"),
153
+ joinPath(basePath, "index.mjs"),
154
+ ];
155
+ return candidates.find((path) => moduleSources.has(path)) ?? basePath;
156
+ }
157
+ async readPluginModuleSources(pluginPath) {
158
+ const sources = new Map();
159
+ const visit = async (folderPath) => {
160
+ const listing = await this.adapter.list(folderPath);
161
+ await Promise.all(listing.files
162
+ .filter((file) => /\.(?:cjs|mjs|js)$/i.test(file))
163
+ .map(async (file) => {
164
+ const filePath = normalizePath(joinPath(folderPath, file));
165
+ sources.set(filePath, await this.adapter.read(filePath));
166
+ }));
167
+ for (const folder of listing.folders) {
168
+ await visit(joinPath(folderPath, folder));
169
+ }
170
+ };
171
+ await visit(pluginPath);
172
+ return sources;
173
+ }
174
+ }
175
+ export class RendererEsmPluginExecutionHost {
176
+ adapter;
177
+ pluginAssetServer;
178
+ diagnostics;
179
+ id = "renderer";
180
+ resolver;
181
+ constructor(adapter, pluginAssetServer, dependenciesOrResolver, diagnostics = {}) {
182
+ this.adapter = adapter;
183
+ this.pluginAssetServer = pluginAssetServer;
184
+ this.diagnostics = diagnostics;
185
+ this.resolver = isPluginDependencyResolver(dependenciesOrResolver)
186
+ ? dependenciesOrResolver
187
+ : new LegacyObjectDependencyResolver(dependenciesOrResolver);
188
+ }
189
+ async evaluate(request) {
190
+ const selectedRuntime = request.selectedRuntime;
191
+ if (selectedRuntime?.format !== "esm") {
192
+ throw new Error(`Plugin ${request.pluginId} was routed to the renderer ESM host without an ESM runtime entry`);
193
+ }
194
+ const dependencyContext = {
195
+ pluginId: request.pluginId,
196
+ pluginPath: request.pluginPath,
197
+ manifest: request.manifest,
198
+ host: selectedRuntime.host,
199
+ format: "esm",
200
+ };
201
+ await this.prepareDependencies(request.modulePath, dependencyContext);
202
+ const moduleUrl = await this.pluginAssetServer.getPluginAssetUrl({
203
+ pluginId: request.pluginId,
204
+ pluginPath: request.pluginPath,
205
+ relativePath: selectedRuntime.path,
206
+ version: request.manifest.version,
207
+ });
208
+ this.diagnostics.onPluginAssetUrl?.(request.pluginId, moduleUrl);
209
+ try {
210
+ const moduleExports = await import(/* @vite-ignore */ moduleUrl);
211
+ return resolvePluginModuleExport(moduleExports);
212
+ }
213
+ catch (error) {
214
+ throw new Error(`Error importing ESM plugin module for ${request.pluginId} from ${moduleUrl}: ${formatPluginError(error)}`);
215
+ }
216
+ }
217
+ registerDependencies(_deps) { }
218
+ async prepareDependencies(modulePath, dependencyContext) {
219
+ let source = "";
220
+ try {
221
+ source = await this.adapter.read(modulePath);
222
+ }
223
+ catch {
224
+ // Preflight already checks the selected entry path. Preserve import failure
225
+ // as the actionable error if the file disappears between preflight/import.
226
+ }
227
+ await this.resolver.prepare(source ? scanBarePluginDependencySpecifiers(source) : [], dependencyContext);
228
+ }
229
+ }
230
+ export class HybridCommunityPluginExecutionHost {
231
+ commonJsHost;
232
+ esmHost;
233
+ diagnostics;
234
+ id;
235
+ constructor(commonJsHost, esmHost, diagnostics = {}) {
236
+ this.commonJsHost = commonJsHost;
237
+ this.esmHost = esmHost;
238
+ this.diagnostics = diagnostics;
239
+ this.id = commonJsHost.id;
240
+ }
241
+ hostIdForManifest(manifest) {
242
+ return (this.commonJsHost.hostIdForManifest?.(manifest) ?? this.commonJsHost.id);
243
+ }
244
+ registerDependencies(deps) {
245
+ this.commonJsHost.registerDependencies?.(deps);
246
+ this.esmHost.registerDependencies?.(deps);
247
+ }
248
+ async evaluate(request) {
249
+ if (request.selectedRuntime?.format !== "esm" ||
250
+ request.selectedRuntime.host === "electron-sidecar") {
251
+ return this.commonJsHost.evaluate(request);
252
+ }
253
+ try {
254
+ return await this.esmHost.evaluate(request);
255
+ }
256
+ catch (error) {
257
+ const fallbackPath = request.selectedRuntime.fallbackPath;
258
+ if (!fallbackPath) {
259
+ throw error;
260
+ }
261
+ this.diagnostics.onFallbackUsed?.(request.pluginId, fallbackPath);
262
+ return this.commonJsHost.evaluate({
263
+ ...request,
264
+ modulePath: normalizePath(joinPath(request.pluginPath, fallbackPath)),
265
+ selectedRuntime: {
266
+ ...request.selectedRuntime,
267
+ path: fallbackPath,
268
+ format: "commonjs",
269
+ },
270
+ });
271
+ }
272
+ }
273
+ }
274
+ export class NativeDesktopCommunityPluginExecutionHost {
275
+ adapter;
276
+ bridge;
277
+ id = "electron-plugin-sidecar";
278
+ prepared = false;
279
+ contextId;
280
+ rootPath;
281
+ dependencies = {};
282
+ rendererHost;
283
+ constructor(adapter, bridge = requireNativeDesktopBridge(), createRendererDependencyResolver) {
284
+ this.adapter = adapter;
285
+ this.bridge = bridge;
286
+ const rendererDependenciesOrResolver = createRendererDependencyResolver?.(this.dependencies) ??
287
+ this.dependencies;
288
+ this.rendererHost = new RendererCommunityPluginExecutionHost(adapter, rendererDependenciesOrResolver);
289
+ this.contextId = getAdapterVaultId(adapter);
290
+ this.rootPath =
291
+ typeof adapter.rootPath === "string"
292
+ ? adapter.rootPath
293
+ : undefined;
294
+ }
295
+ async evaluate(request) {
296
+ if (!this.shouldUseSidecar(request.manifest)) {
297
+ return this.rendererHost.evaluate(request);
298
+ }
299
+ await this.prepare();
300
+ try {
301
+ await this.bridge.invoke("desktop_plugin_host_evaluate", {
302
+ contextId: this.contextId,
303
+ pluginId: request.pluginId,
304
+ pluginPath: request.pluginPath,
305
+ modulePath: request.modulePath,
306
+ manifest: request.manifest,
307
+ selectedRuntime: request.selectedRuntime,
308
+ });
309
+ }
310
+ catch (error) {
311
+ const runtime = request.selectedRuntime;
312
+ throw new Error(`Plugin ${request.pluginId} failed in ${this.id} while evaluating ${runtime?.format ?? "commonjs"} entry ${runtime?.path ?? request.modulePath}: ${error instanceof Error ? error.message : String(error)}`, { cause: error });
313
+ }
314
+ const host = this;
315
+ return class NativeDesktopHostedCommunityPlugin extends Plugin {
316
+ async onload() {
317
+ await host.activate({
318
+ pluginId: this.manifest.id,
319
+ manifest: this.manifest,
320
+ });
321
+ }
322
+ async onunload() {
323
+ await host.deactivate({
324
+ pluginId: this.manifest.id,
325
+ });
326
+ }
327
+ };
328
+ }
329
+ hostIdForManifest(manifest) {
330
+ return this.shouldUseSidecar(manifest) ? this.id : "renderer";
331
+ }
332
+ registerDependencies(deps) {
333
+ for (const [key, value] of Object.entries(deps)) {
334
+ if (isRecord(this.dependencies[key]) && isRecord(value)) {
335
+ this.dependencies[key] = { ...this.dependencies[key], ...value };
336
+ continue;
337
+ }
338
+ this.dependencies[key] = value;
339
+ }
340
+ }
341
+ shouldUseSidecar(manifest) {
342
+ if (normalizeStringArray(manifest.requiredCapabilities).length) {
343
+ return true;
344
+ }
345
+ const supportedRuntimes = normalizeStringArray(manifest.supportedRuntimes).map((runtime) => runtime.toLowerCase());
346
+ if (supportedRuntimes.some((runtime) => [
347
+ "desktop",
348
+ "electron",
349
+ "electron-sidecar",
350
+ "electron-plugin-sidecar",
351
+ "plugin-sidecar",
352
+ ].includes(runtime))) {
353
+ return true;
354
+ }
355
+ return Boolean(manifest.lapis?.runtime?.desktop ||
356
+ manifest.lapis?.runtime?.trustedDesktop);
357
+ }
358
+ async prepare() {
359
+ if (this.prepared) {
360
+ return;
361
+ }
362
+ await this.bridge.invoke("desktop_plugin_host_prepare", {
363
+ contextId: this.contextId,
364
+ rootPath: this.rootPath,
365
+ });
366
+ this.prepared = true;
367
+ }
368
+ async activate(payload) {
369
+ await this.prepare();
370
+ await this.bridge.invoke("desktop_plugin_host_activate", {
371
+ ...payload,
372
+ contextId: this.contextId,
373
+ });
374
+ }
375
+ async deactivate(payload) {
376
+ await this.bridge.invoke("desktop_plugin_host_deactivate", {
377
+ ...payload,
378
+ contextId: this.contextId,
379
+ });
380
+ }
381
+ }
382
+ function requireNativeDesktopBridge() {
383
+ const bridge = getNativeDesktopBridge();
384
+ if (!bridge) {
385
+ throw new Error("Native desktop bridge is unavailable");
386
+ }
387
+ return bridge;
388
+ }
389
+ export class PluginManager extends EventDispatcher {
390
+ app;
391
+ pluginsPath;
392
+ adapter;
393
+ plugins = new Map();
394
+ dependencies = {};
395
+ #loaded = false;
396
+ #configuredCommunityPlugins = new Set();
397
+ #disabledCorePlugins = new Set();
398
+ #enabledCorePlugins = new Set();
399
+ #defaultDisabledCorePlugins = new Set();
400
+ #corePluginStyles = new Map();
401
+ #corePluginDistribution = new Map();
402
+ #installedPluginProvenance = new Map();
403
+ #installedPluginStateStore;
404
+ #basesViewRegistrations = new Map();
405
+ #communityPluginDiagnostics = new Map();
406
+ #communityPluginDependencyDiagnostics = new Map();
407
+ #communityPluginRuntimeExecutionDiagnostics = new Map();
408
+ #lapisExtensionIndex = new Map();
409
+ #manifestContributionDisposers = new Map();
410
+ #manifestContributionDiagnostics = new Map();
411
+ #systemExtensionManifests = new Map();
412
+ #systemExtensionPlugins = new Map();
413
+ #systemExtensionServiceProviders = new Map();
414
+ #indexedActivationRegistry = new Map();
415
+ #deferredIndexedExtensions = new Map();
416
+ #communityPluginHost;
417
+ #pluginAssetServer;
418
+ constructor(app, pluginsPath, adapter, options = {}) {
419
+ super();
420
+ this.app = app;
421
+ this.pluginsPath = pluginsPath;
422
+ this.adapter = adapter;
423
+ this.#installedPluginStateStore = new InstalledPluginStateStore(adapter);
424
+ const dependencyResolver = options.createCommunityPluginDependencyResolver?.(this.dependencies) ??
425
+ this.dependencies;
426
+ this.#pluginAssetServer = options.pluginAssetServer;
427
+ const commonJsHost = options.communityPluginHost ??
428
+ new RendererCommunityPluginExecutionHost(adapter, dependencyResolver);
429
+ this.#communityPluginHost = options.pluginAssetServer
430
+ ? new HybridCommunityPluginExecutionHost(commonJsHost, new RendererEsmPluginExecutionHost(adapter, options.pluginAssetServer, dependencyResolver, {
431
+ onPluginAssetUrl: (pluginId, url) => this.recordPluginAssetUrlDiagnostics(pluginId, url),
432
+ }), {
433
+ onFallbackUsed: (pluginId) => this.recordPluginFallbackDiagnostics(pluginId),
434
+ })
435
+ : commonJsHost;
436
+ this.app.configuration.on("plugin-data-updated", (event) => {
437
+ if (event.origin === "plugin-save") {
438
+ return;
439
+ }
440
+ void this.syncLegacyPluginData(event.pluginId);
441
+ });
442
+ }
443
+ getPluginFolder() {
444
+ return this.pluginsPath;
445
+ }
446
+ get corePlugins() {
447
+ return [...this.plugins.values()].filter((plugin) => plugin.source === "core" || plugin.source === "official");
448
+ }
449
+ get communityPlugins() {
450
+ return [...this.plugins.values()].filter((plugin) => plugin.source === "community");
451
+ }
452
+ isPluginEnabled(pluginId) {
453
+ return Boolean(this.plugins.get(pluginId)?.enabled);
454
+ }
455
+ get corePluginEntries() {
456
+ const runtimePlugins = this.corePlugins.map((plugin) => ({
457
+ manifest: plugin.manifest,
458
+ enabled: plugin.enabled,
459
+ required: plugin.required,
460
+ source: plugin.source,
461
+ provenance: plugin.provenance,
462
+ distribution: this.#corePluginDistribution.get(plugin.manifest.id) ??
463
+ (plugin.source === "official"
464
+ ? "first-party-external"
465
+ : "bundled"),
466
+ errorMessage: plugin.errorMessage,
467
+ }));
468
+ const runtimeSystemPlugins = [...this.plugins.values()].filter((plugin) => plugin.source === "system");
469
+ const runtimePluginIds = new Set([...runtimePlugins, ...runtimeSystemPlugins].map((plugin) => plugin.manifest.id));
470
+ const systemExtensions = [
471
+ ...runtimeSystemPlugins.map((plugin) => ({
472
+ manifest: plugin.manifest,
473
+ enabled: plugin.enabled,
474
+ required: plugin.required,
475
+ source: plugin.source,
476
+ provenance: plugin.provenance,
477
+ distribution: "bundled",
478
+ errorMessage: plugin.errorMessage,
479
+ })),
480
+ ...this.systemExtensions
481
+ .filter((extension) => !runtimePluginIds.has(extension.pluginId))
482
+ .map((extension) => {
483
+ const diagnostics = this.#communityPluginDiagnostics.get(extension.pluginId);
484
+ return {
485
+ manifest: this.#systemExtensionManifests.get(extension.pluginId) ?? {
486
+ id: extension.pluginId,
487
+ name: extension.name ?? extension.pluginId,
488
+ author: "Lapis Notes",
489
+ version: "0.0.0",
490
+ minAppVersion: "0.0.0",
491
+ description: "",
492
+ },
493
+ enabled: extension.enabled,
494
+ required: extension.locked,
495
+ source: "system",
496
+ provenance: "bundled",
497
+ distribution: "bundled",
498
+ errorMessage: diagnostics?.state === "failed"
499
+ ? diagnostics.lastFailureMessage
500
+ : null,
501
+ };
502
+ }),
503
+ ];
504
+ return [...runtimePlugins, ...systemExtensions];
505
+ }
506
+ get communityPluginHostId() {
507
+ return this.#communityPluginHost.id;
508
+ }
509
+ get pluginAssetServer() {
510
+ return this.#pluginAssetServer;
511
+ }
512
+ get communityPluginDiagnostics() {
513
+ return [...this.#communityPluginDiagnostics.values()];
514
+ }
515
+ get lapisExtensions() {
516
+ return [...this.#lapisExtensionIndex.values()];
517
+ }
518
+ get systemExtensions() {
519
+ return this.lapisExtensions.filter((extension) => extension.source === "system");
520
+ }
521
+ getLapisExtension(pluginId) {
522
+ return this.#lapisExtensionIndex.get(pluginId) ?? null;
523
+ }
524
+ getLapisContributionDiagnostics(pluginId) {
525
+ return [...(this.#manifestContributionDiagnostics.get(pluginId) ?? [])];
526
+ }
527
+ getCommunityPluginDiagnostics(pluginId) {
528
+ return this.#communityPluginDiagnostics.get(pluginId) ?? null;
529
+ }
530
+ registerDependencies(deps) {
531
+ for (const [key, value] of Object.entries(deps)) {
532
+ if (isRecord(this.dependencies[key]) && isRecord(value)) {
533
+ this.dependencies[key] = { ...this.dependencies[key], ...value };
534
+ continue;
535
+ }
536
+ this.dependencies[key] = value;
537
+ }
538
+ this.#communityPluginHost.registerDependencies?.(deps);
539
+ }
540
+ async migratePluginDataAliases(migrations) {
541
+ const migrated = [];
542
+ for (const migration of migrations) {
543
+ if (!this.app.configuration.hasPluginData(migration.fromPluginId) ||
544
+ this.app.configuration.hasPluginData(migration.toPluginId)) {
545
+ continue;
546
+ }
547
+ await this.app.configuration.updatePluginData(migration.toPluginId, this.app.configuration.getPluginData(migration.fromPluginId), { origin: "configuration-update" });
548
+ if (migration.removeSource) {
549
+ await this.app.configuration.removePluginData(migration.fromPluginId, {
550
+ origin: "configuration-update",
551
+ });
552
+ }
553
+ migrated.push(`${migration.fromPluginId}:${migration.toPluginId}`);
554
+ }
555
+ return migrated;
556
+ }
557
+ registerStaticPlugins(profile) {
558
+ for (const registration of profile) {
559
+ const { plugin: PluginType, required = false, enabledByDefault = true, distribution = "bundled", styles, } = typeof registration === "function"
560
+ ? {
561
+ plugin: registration,
562
+ required: false,
563
+ enabledByDefault: true,
564
+ distribution: "bundled",
565
+ styles: undefined,
566
+ }
567
+ : registration;
568
+ try {
569
+ const plugin = new PluginType(this.app);
570
+ const pluginPath = normalizePath(joinPath(this.pluginsPath, plugin.manifest.id));
571
+ plugin.manifest.dir ??= pluginPath;
572
+ if (!required && !enabledByDefault) {
573
+ this.#defaultDisabledCorePlugins.add(plugin.manifest.id);
574
+ }
575
+ const registeredPlugin = this.registerPlugin(plugin, {
576
+ source: "core",
577
+ provenance: distribution === "first-party-external"
578
+ ? "official"
579
+ : "bundled",
580
+ required,
581
+ basePath: pluginPath,
582
+ });
583
+ this.#corePluginDistribution.set(registeredPlugin.manifest.id, distribution);
584
+ this.registerCorePluginManifestContributions(registeredPlugin, pluginPath, { required, enabledByDefault });
585
+ if (styles !== undefined) {
586
+ this.#corePluginStyles.set(registeredPlugin.manifest.id, styles);
587
+ }
588
+ }
589
+ catch (error) {
590
+ this.reportPluginError("core", `Failed to register core plugin: ${formatPluginError(error)}`, error);
591
+ }
592
+ }
593
+ }
594
+ /** @deprecated Use {@link registerStaticPlugins}. */
595
+ registerCorePlugins(profile) {
596
+ this.registerStaticPlugins(profile);
597
+ }
598
+ registerCorePluginManifestContributions(plugin, pluginPath, options) {
599
+ if (plugin.manifest.lapis === undefined) {
600
+ return;
601
+ }
602
+ const diagnostics = validateLapisManifest(plugin.manifest.id, plugin.manifest.lapis);
603
+ if (diagnostics.length) {
604
+ throw new Error(diagnostics.join("; "));
605
+ }
606
+ if (!isLapisManifest(plugin.manifest.lapis)) {
607
+ throw new Error(`Plugin ${plugin.manifest.id} has invalid lapis metadata`);
608
+ }
609
+ const indexedExtension = buildLapisContributionIndex({
610
+ pluginId: plugin.manifest.id,
611
+ name: plugin.manifest.name,
612
+ source: "system",
613
+ manifestPath: joinPath(pluginPath, "manifest.json"),
614
+ classification: classifyLapisPlugin(plugin.manifest, { hasMainJs: true }),
615
+ lapis: {
616
+ ...plugin.manifest.lapis,
617
+ source: "system",
618
+ locked: plugin.manifest.lapis.locked ?? options.required,
619
+ enabledByDefault: plugin.manifest.lapis.enabledByDefault ?? options.enabledByDefault,
620
+ },
621
+ requestedCapabilities: this.getRequestedCapabilities(plugin.manifest),
622
+ grantedCapabilities: this.getGrantedCapabilities(plugin.manifest),
623
+ });
624
+ this.#lapisExtensionIndex.set(plugin.manifest.id, indexedExtension);
625
+ this.#manifestContributionDiagnostics.set(plugin.manifest.id, indexedExtension.contributions.flatMap((entry) => entry.diagnostics));
626
+ this.syncIndexedExtensionActivation(indexedExtension);
627
+ this.recordCommunityPluginDiagnostics(plugin.manifest, "disabled");
628
+ const configurationDisposers = [];
629
+ for (const entry of indexedExtension.contributions) {
630
+ if (entry.kind !== "configuration" || entry.state === "invalid") {
631
+ continue;
632
+ }
633
+ configurationDisposers.push({
634
+ kind: entry.kind,
635
+ id: entry.id,
636
+ dispose: this.installManifestConfiguration(entry),
637
+ });
638
+ }
639
+ if (configurationDisposers.length > 0) {
640
+ this.#manifestContributionDisposers.set(plugin.manifest.id, configurationDisposers);
641
+ }
642
+ }
643
+ registerSystemExtensions(registrations) {
644
+ for (const registration of registrations) {
645
+ const manifest = this.createSystemExtensionManifest(registration);
646
+ if (this.plugins.has(manifest.id) ||
647
+ this.#lapisExtensionIndex.has(manifest.id)) {
648
+ throw new Error(`Plugin id ${manifest.id} is already registered`);
649
+ }
650
+ const diagnostics = validateLapisManifest(manifest.id, manifest.lapis);
651
+ if (diagnostics.length) {
652
+ throw new Error(diagnostics.join("; "));
653
+ }
654
+ if (!isLapisManifest(manifest.lapis)) {
655
+ throw new Error(`Plugin ${manifest.id} has invalid lapis metadata`);
656
+ }
657
+ const requestedCapabilities = permissionsToCapabilities(manifest.lapis.permissions);
658
+ const indexedExtension = buildLapisContributionIndex({
659
+ pluginId: manifest.id,
660
+ name: manifest.name,
661
+ source: "system",
662
+ manifestPath: this.getSystemExtensionManifestPath(registration.basePath, manifest.id),
663
+ classification: classifyLapisPlugin(manifest, { hasMainJs: false }),
664
+ lapis: manifest.lapis,
665
+ requestedCapabilities,
666
+ grantedCapabilities: [...requestedCapabilities],
667
+ privileges: registration.privileges,
668
+ });
669
+ this.#manifestContributionDiagnostics.set(manifest.id, indexedExtension.contributions.flatMap((entry) => entry.diagnostics));
670
+ if (!indexedExtension.locked && !indexedExtension.enabledByDefault) {
671
+ this.#defaultDisabledCorePlugins.add(manifest.id);
672
+ }
673
+ this.#lapisExtensionIndex.set(manifest.id, indexedExtension);
674
+ this.#systemExtensionManifests.set(manifest.id, manifest);
675
+ if (registration.plugin) {
676
+ this.#systemExtensionPlugins.set(manifest.id, registration.plugin);
677
+ }
678
+ this.storeSystemExtensionServiceProviders(manifest.id, registration.serviceProviders ?? []);
679
+ this.syncIndexedExtensionActivation(indexedExtension);
680
+ this.recordIndexedExtensionDiagnostics(manifest.id, "disabled");
681
+ }
682
+ }
683
+ async loadPlugins(options = {}) {
684
+ return this.app.telemetry.measureAsync("plugins.load_all", async (span) => {
685
+ span.setAttribute("plugins.path", this.pluginsPath);
686
+ await this.app.workspaceTrust.ready();
687
+ this.#loaded = false;
688
+ const communityPluginsEnabled = options.communityPlugins !== "disabled";
689
+ const optionalCorePluginsEnabled = options.optionalCorePlugins !== "disabled";
690
+ span.setAttribute("plugins.community_plugins_enabled", communityPluginsEnabled);
691
+ span.setAttribute("plugins.optional_core_plugins_enabled", optionalCorePluginsEnabled);
692
+ try {
693
+ await this.app.vault.mkpath(this.pluginsPath);
694
+ }
695
+ catch (error) {
696
+ this.reportPluginError(this.pluginsPath, `Failed to initialize plugin folder ${this.pluginsPath}: ${formatPluginError(error)}`, error);
697
+ }
698
+ await this.refreshInstalledPluginProvenance();
699
+ try {
700
+ const pluginFolders = await this.adapter.list(this.pluginsPath);
701
+ span.setAttribute("plugins.discovered_folder_count", pluginFolders.folders.length);
702
+ for (const folder of pluginFolders.folders) {
703
+ const registeredPlugin = this.plugins.get(folder);
704
+ if (registeredPlugin) {
705
+ continue;
706
+ }
707
+ await this.loadPlugin(joinPath(this.pluginsPath, folder));
708
+ }
709
+ }
710
+ catch (error) {
711
+ this.reportPluginError(this.pluginsPath, `Failed to discover plugins in ${this.pluginsPath}: ${formatPluginError(error)}`, error);
712
+ }
713
+ const corePluginState = await this.readCorePluginStateConfig();
714
+ this.#disabledCorePlugins = new Set(corePluginState.disabled);
715
+ this.#enabledCorePlugins = new Set(corePluginState.enabled);
716
+ this.#configuredCommunityPlugins = new Set(await this.readPluginListConfig("community-plugins.json", {
717
+ createIfMissing: true,
718
+ }));
719
+ const prunedCommunityPlugins = await this.pruneMissingConfiguredCommunityPlugins();
720
+ span.setAttribute("plugins.pruned_community_config_count", prunedCommunityPlugins.length);
721
+ const activationOrder = [
722
+ ...new Set([
723
+ ...this.corePlugins
724
+ .filter((plugin) => plugin.source === "core" &&
725
+ (plugin.required ||
726
+ (optionalCorePluginsEnabled &&
727
+ this.isCorePluginEnabled(plugin.manifest.id))))
728
+ .map((plugin) => plugin.manifest.id),
729
+ ...[...this.#configuredCommunityPlugins].filter((pluginId) => {
730
+ const plugin = this.plugins.get(pluginId);
731
+ if (!plugin) {
732
+ return false;
733
+ }
734
+ if (plugin.source === "official") {
735
+ return optionalCorePluginsEnabled;
736
+ }
737
+ return plugin.source === "community" && communityPluginsEnabled;
738
+ }),
739
+ ]),
740
+ ];
741
+ span.setAttribute("plugins.activation_count", activationOrder.length);
742
+ for (const [index, pluginId] of activationOrder.entries()) {
743
+ const plugin = this.plugins.get(pluginId);
744
+ options.onProgress?.({
745
+ id: pluginId,
746
+ name: plugin?.manifest.name ?? pluginId,
747
+ index,
748
+ total: activationOrder.length,
749
+ });
750
+ await this.enablePlugin(pluginId);
751
+ }
752
+ await this.enableConfiguredIndexedExtensions({
753
+ communityPluginsEnabled,
754
+ optionalCorePluginsEnabled,
755
+ });
756
+ await this.activateIndexedExtensionsForWorkspace();
757
+ this.#loaded = true;
758
+ this.emit("plugins-loaded");
759
+ await this.activateByEvent("onStartupFinished");
760
+ }, {
761
+ attributes: { "plugins.path": this.pluginsPath },
762
+ slowThresholdMs: 500,
763
+ });
764
+ }
765
+ get enabledPlugins() {
766
+ return [...this.#configuredCommunityPlugins];
767
+ }
768
+ getBasesViewRegistrations() {
769
+ return new Map([...this.#basesViewRegistrations.entries()].map(([viewId, entry]) => [viewId, entry.registration]));
770
+ }
771
+ registerBasesView(pluginId, viewId, registration) {
772
+ const existing = this.#basesViewRegistrations.get(viewId);
773
+ if (existing && existing.pluginId !== pluginId) {
774
+ throw new Error(`Bases view ${viewId} is already registered`);
775
+ }
776
+ this.#basesViewRegistrations.set(viewId, { pluginId, registration });
777
+ return true;
778
+ }
779
+ unregisterBasesView(pluginId, viewId) {
780
+ const existing = this.#basesViewRegistrations.get(viewId);
781
+ if (!existing) {
782
+ return false;
783
+ }
784
+ if (existing.pluginId !== pluginId) {
785
+ throw new Error(`Bases view ${viewId} is owned by plugin ${existing.pluginId}`);
786
+ }
787
+ this.#basesViewRegistrations.delete(viewId);
788
+ return true;
789
+ }
790
+ registerLapisServiceProvider(registration) {
791
+ if (!isLanguageServiceId(registration.service)) {
792
+ throw new Error(`Unsupported Lapis service provider: ${registration.service}`);
793
+ }
794
+ const declaration = this.getLapisServiceContribution(registration.pluginId, registration.id, registration.service);
795
+ const metadata = this.createLanguageServiceProviderMetadata(registration, declaration);
796
+ const provider = {
797
+ ...registration.provider,
798
+ metadata,
799
+ };
800
+ const disposeProvider = this.app.languageServices.registerProvider(provider);
801
+ return () => {
802
+ disposeProvider();
803
+ void registration.dispose?.();
804
+ };
805
+ }
806
+ getLapisServiceContribution(pluginId, providerId, service) {
807
+ const indexedExtension = this.#lapisExtensionIndex.get(pluginId);
808
+ const contribution = indexedExtension?.contributions.find((entry) => entry.kind === "services" &&
809
+ entry.id === providerId &&
810
+ isLanguageServiceId(entry.contribution.service) &&
811
+ isLanguageServiceId(service));
812
+ return contribution?.contribution ?? null;
813
+ }
814
+ createLanguageServiceProviderMetadata(registration, declaration) {
815
+ const explicitMetadata = isRecord(registration.metadata)
816
+ ? registration.metadata
817
+ : {};
818
+ const providerId = scopeLapisProviderId(registration.pluginId, explicitMetadata.id ?? registration.id);
819
+ const capabilities = normalizeLanguageServiceCapabilities(explicitMetadata.capabilities ?? declaration?.capabilities);
820
+ const languages = normalizeStringArray(explicitMetadata.languages ?? declaration?.languages);
821
+ if (!languages.length) {
822
+ throw new Error(`Lapis language-service provider ${registration.id} must declare languages`);
823
+ }
824
+ return {
825
+ ...explicitMetadata,
826
+ id: providerId,
827
+ languages,
828
+ runtime: explicitMetadata.runtime ??
829
+ languageServiceRuntimeFromDeclaration(declaration?.runtime),
830
+ priority: explicitMetadata.priority ?? declaration?.priority,
831
+ capabilities,
832
+ };
833
+ }
834
+ getEnabledInternalPluginById(pluginId) {
835
+ const plugin = this.plugins.get(pluginId);
836
+ if (!plugin?.enabled) {
837
+ const systemExtension = this.#lapisExtensionIndex.get(pluginId);
838
+ if (systemExtension?.source === "system" && systemExtension.enabled) {
839
+ return {
840
+ manifest: this.#systemExtensionManifests.get(pluginId) ?? {
841
+ id: pluginId,
842
+ name: systemExtension.name ?? pluginId,
843
+ author: "Lapis Notes",
844
+ version: "0.0.0",
845
+ minAppVersion: "0.0.0",
846
+ description: "",
847
+ },
848
+ };
849
+ }
850
+ return null;
851
+ }
852
+ if (pluginId === "bases") {
853
+ return {
854
+ manifest: plugin.manifest,
855
+ registrations: Object.fromEntries(this.getBasesViewRegistrations()),
856
+ };
857
+ }
858
+ return { manifest: plugin.manifest };
859
+ }
860
+ get internalPlugins() {
861
+ const plugins = Object.fromEntries(this.corePluginEntries.map((plugin) => [plugin.manifest.id, plugin]));
862
+ return {
863
+ plugins,
864
+ getEnabledPluginById: (pluginId) => this.getEnabledInternalPluginById(pluginId),
865
+ };
866
+ }
867
+ getPluginStatePath(filename) {
868
+ return normalizePath(joinPath(`${this.pluginsPath}/../`, filename));
869
+ }
870
+ async refreshInstalledPluginProvenance() {
871
+ try {
872
+ const installed = await this.#installedPluginStateStore.list();
873
+ this.#installedPluginProvenance = new Map(installed.map((record) => [record.pluginId, record.provenance]));
874
+ }
875
+ catch (error) {
876
+ this.#installedPluginProvenance = new Map();
877
+ this.reportPluginError("installed-plugins", `Failed to load installed plugin provenance: ${formatPluginError(error)}`, error);
878
+ }
879
+ }
880
+ resolveExternalPluginSource(pluginId, provenanceOverride) {
881
+ const provenance = provenanceOverride ??
882
+ this.#installedPluginProvenance.get(pluginId) ??
883
+ "community";
884
+ return {
885
+ source: pluginSourceForProvenance(provenance),
886
+ provenance,
887
+ };
888
+ }
889
+ async readPluginListConfig(filename, options = {}) {
890
+ const configPath = this.getPluginStatePath(filename);
891
+ try {
892
+ const exists = await this.adapter.exists(configPath);
893
+ if (!exists) {
894
+ if (options.createIfMissing) {
895
+ await this.adapter.write(configPath, "[]");
896
+ }
897
+ return [];
898
+ }
899
+ const data = await this.adapter.read(configPath);
900
+ const plugins = JSON.parse(data);
901
+ if (Array.isArray(plugins)) {
902
+ return plugins.map((it) => it.toString());
903
+ }
904
+ }
905
+ catch (error) {
906
+ this.reportPluginError(configPath, `Failed to load plugin config ${configPath}: ${formatPluginError(error)}`, error);
907
+ }
908
+ return [];
909
+ }
910
+ async readCorePluginStateConfig() {
911
+ const configPath = this.getPluginStatePath("core-plugins.json");
912
+ try {
913
+ const exists = await this.adapter.exists(configPath);
914
+ if (!exists) {
915
+ return { disabled: [], enabled: [] };
916
+ }
917
+ const data = await this.adapter.read(configPath);
918
+ const config = JSON.parse(data);
919
+ if (Array.isArray(config)) {
920
+ return {
921
+ disabled: config.map((it) => it.toString()),
922
+ enabled: [],
923
+ };
924
+ }
925
+ if (isRecord(config)) {
926
+ return {
927
+ disabled: Array.isArray(config.disabled)
928
+ ? config.disabled.map((it) => it.toString())
929
+ : [],
930
+ enabled: Array.isArray(config.enabled)
931
+ ? config.enabled.map((it) => it.toString())
932
+ : [],
933
+ };
934
+ }
935
+ }
936
+ catch (error) {
937
+ this.reportPluginError(configPath, `Failed to load plugin config ${configPath}: ${formatPluginError(error)}`, error);
938
+ }
939
+ return { disabled: [], enabled: [] };
940
+ }
941
+ async savePluginListConfig(filename, pluginIds) {
942
+ const configPath = this.getPluginStatePath(filename);
943
+ try {
944
+ const file = this.app.vault.getFileByPath(configPath);
945
+ const payload = JSON.stringify([...pluginIds], null, 2);
946
+ if (file) {
947
+ await this.app.vault.modify(file, payload);
948
+ return;
949
+ }
950
+ await this.app.vault.create(configPath, payload);
951
+ }
952
+ catch (error) {
953
+ this.reportPluginError(configPath, `Failed to save plugin config ${configPath}: ${formatPluginError(error)}`, error);
954
+ }
955
+ }
956
+ async saveCorePluginStateConfig() {
957
+ const configPath = this.getPluginStatePath("core-plugins.json");
958
+ try {
959
+ const file = this.app.vault.getFileByPath(configPath);
960
+ const payload = this.#enabledCorePlugins.size === 0
961
+ ? JSON.stringify([...this.#disabledCorePlugins], null, 2)
962
+ : JSON.stringify({
963
+ disabled: [...this.#disabledCorePlugins],
964
+ enabled: [...this.#enabledCorePlugins],
965
+ }, null, 2);
966
+ if (file) {
967
+ await this.app.vault.modify(file, payload);
968
+ return;
969
+ }
970
+ await this.app.vault.create(configPath, payload);
971
+ }
972
+ catch (error) {
973
+ this.reportPluginError(configPath, `Failed to save plugin config ${configPath}: ${formatPluginError(error)}`, error);
974
+ }
975
+ }
976
+ saveCommunityPluginState = debounce(() => {
977
+ return void this.savePluginListConfig("community-plugins.json", this.#configuredCommunityPlugins);
978
+ }, 500);
979
+ saveCorePluginState = debounce(() => {
980
+ return void this.saveCorePluginStateConfig();
981
+ }, 500);
982
+ flushPendingConfigWrites() {
983
+ this.saveCommunityPluginState.flush();
984
+ this.saveCorePluginState.flush();
985
+ }
986
+ isCorePluginEnabled(pluginId) {
987
+ if (this.#disabledCorePlugins.has(pluginId)) {
988
+ return false;
989
+ }
990
+ if (this.#enabledCorePlugins.has(pluginId)) {
991
+ return true;
992
+ }
993
+ return !this.#defaultDisabledCorePlugins.has(pluginId);
994
+ }
995
+ async pruneMissingConfiguredCommunityPlugins() {
996
+ const missingPluginIds = [];
997
+ for (const pluginId of this.#configuredCommunityPlugins) {
998
+ const manifestPath = joinPath(this.pluginsPath, pluginId, "manifest.json");
999
+ let exists = false;
1000
+ try {
1001
+ exists = await this.adapter.exists(manifestPath);
1002
+ }
1003
+ catch (error) {
1004
+ if (!isMissingPathError(error)) {
1005
+ throw error;
1006
+ }
1007
+ }
1008
+ if (!exists) {
1009
+ missingPluginIds.push(pluginId);
1010
+ }
1011
+ }
1012
+ if (!missingPluginIds.length) {
1013
+ return [];
1014
+ }
1015
+ for (const pluginId of missingPluginIds) {
1016
+ this.#configuredCommunityPlugins.delete(pluginId);
1017
+ }
1018
+ await this.savePluginListConfig("community-plugins.json", this.#configuredCommunityPlugins);
1019
+ this.emit("community-plugin-config-pruned", missingPluginIds);
1020
+ return missingPluginIds;
1021
+ }
1022
+ async enableConfiguredIndexedExtensions(options) {
1023
+ for (const indexedExtension of this.#lapisExtensionIndex.values()) {
1024
+ if (this.plugins.has(indexedExtension.pluginId)) {
1025
+ continue;
1026
+ }
1027
+ if (indexedExtension.source === "community") {
1028
+ if (!options.communityPluginsEnabled ||
1029
+ !this.#configuredCommunityPlugins.has(indexedExtension.pluginId)) {
1030
+ continue;
1031
+ }
1032
+ }
1033
+ else if (indexedExtension.source === "official") {
1034
+ if (!options.optionalCorePluginsEnabled ||
1035
+ !this.#configuredCommunityPlugins.has(indexedExtension.pluginId)) {
1036
+ continue;
1037
+ }
1038
+ }
1039
+ else if (!indexedExtension.locked &&
1040
+ !this.isCorePluginEnabled(indexedExtension.pluginId)) {
1041
+ continue;
1042
+ }
1043
+ if (this.shouldDeferIndexedExtensionActivation(indexedExtension)) {
1044
+ this.enableDeferredIndexedExtension(indexedExtension.pluginId);
1045
+ continue;
1046
+ }
1047
+ if (indexedExtension.source === "system" &&
1048
+ this.#systemExtensionPlugins.has(indexedExtension.pluginId)) {
1049
+ const plugin = this.instantiateSystemExtensionPlugin(indexedExtension.pluginId);
1050
+ if (plugin) {
1051
+ await this.enablePlugin(plugin.manifest.id);
1052
+ }
1053
+ continue;
1054
+ }
1055
+ if (this.hasIndexedExtensionCodeEntry(indexedExtension)) {
1056
+ continue;
1057
+ }
1058
+ this.enableManifestOnlyExtension(indexedExtension.pluginId);
1059
+ }
1060
+ }
1061
+ async loadPlugin(pluginPath, options = {}) {
1062
+ return this.app.telemetry.measureAsync("plugin.load", async (span) => {
1063
+ span.setAttribute("plugin.path", pluginPath.split("/").at(-1) ?? pluginPath);
1064
+ let manifest = null;
1065
+ let pluginId = pluginPath.split("/").at(-1) ?? pluginPath;
1066
+ try {
1067
+ const manifestPath = joinPath(pluginPath, "manifest.json");
1068
+ if (!(await this.adapter.exists(manifestPath))) {
1069
+ return null;
1070
+ }
1071
+ const manifestContent = await this.adapter.read(manifestPath);
1072
+ manifest = JSON.parse(manifestContent);
1073
+ pluginId = manifest.id || pluginId;
1074
+ span.setAttribute("plugin.id", manifest.id);
1075
+ manifest.dir ??= pluginPath;
1076
+ const runtimeSource = options.source !== undefined
1077
+ ? {
1078
+ source: options.source,
1079
+ provenance: options.provenance ??
1080
+ provenanceForPluginSource(options.source),
1081
+ }
1082
+ : this.resolveExternalPluginSource(manifest.id, options.provenance);
1083
+ span.setAttribute("plugin.source", runtimeSource.source);
1084
+ span.setAttribute("plugin.provenance", runtimeSource.provenance);
1085
+ const mainModulePath = joinPath(pluginPath, "main.js");
1086
+ const hasMainJs = await this.pathExists(mainModulePath);
1087
+ const classification = this.indexCommunityManifest(manifest, manifestPath, hasMainJs, runtimeSource.source, runtimeSource.provenance);
1088
+ span.setAttribute("plugin.host", this.getHostIdForManifest(manifest));
1089
+ span.setAttribute("plugin.classification", classification);
1090
+ await this.preflightManifest(manifest, pluginPath, runtimeSource.source, {
1091
+ classification,
1092
+ hasMainJs,
1093
+ });
1094
+ if (this.isManifestOnlyLapisExtension(manifest, classification, hasMainJs)) {
1095
+ this.recordCommunityPluginDiagnostics(manifest, "indexed");
1096
+ return null;
1097
+ }
1098
+ if (!options.loadDeferredRuntime &&
1099
+ this.shouldDeferManifestActivation(manifest.id)) {
1100
+ this.recordCommunityPluginDiagnostics(manifest, "dormant");
1101
+ return null;
1102
+ }
1103
+ const modulePath = this.getSelectedCommunityModulePath(manifest, pluginPath, classification, hasMainJs);
1104
+ if (!modulePath) {
1105
+ throw new Error(`Plugin ${manifest.id} does not have a code entry`);
1106
+ }
1107
+ await this.updateCommunityPluginDependencyDiagnostics(manifest, pluginPath, classification, hasMainJs);
1108
+ const PluginClass = await this.#communityPluginHost.evaluate({
1109
+ pluginId: manifest.id,
1110
+ pluginPath,
1111
+ modulePath,
1112
+ manifest,
1113
+ selectedRuntime: this.selectRuntimeEntryForManifest(manifest, {
1114
+ classification,
1115
+ hasMainJs,
1116
+ }),
1117
+ });
1118
+ if (!PluginClass || typeof PluginClass !== "function") {
1119
+ throw new Error(`Plugin ${manifest.id} does not export a valid plugin class`);
1120
+ }
1121
+ const PluginType = PluginClass;
1122
+ const plugin = new PluginType(this.app, manifest);
1123
+ if (!(plugin instanceof Plugin)) {
1124
+ throw new Error(`Plugin ${manifest.id} does not extend the Plugin base class`);
1125
+ }
1126
+ return this.registerPlugin(plugin, {
1127
+ source: runtimeSource.source,
1128
+ provenance: runtimeSource.provenance,
1129
+ basePath: pluginPath,
1130
+ });
1131
+ }
1132
+ catch (error) {
1133
+ const message = `Failed to load plugin from ${pluginPath}: ${formatPluginError(error)}`;
1134
+ this.recordCommunityPluginFailure(pluginId, manifest, message);
1135
+ this.reportPluginError(pluginPath, message, error);
1136
+ return null;
1137
+ }
1138
+ }, { slowThresholdMs: 200 });
1139
+ }
1140
+ registerPlugin(plugin, options) {
1141
+ if (!plugin.manifest.id?.trim()) {
1142
+ throw new Error("Plugin manifest is missing an id");
1143
+ }
1144
+ if (this.plugins.has(plugin.manifest.id)) {
1145
+ throw new Error(`Plugin id ${plugin.manifest.id} is already registered`);
1146
+ }
1147
+ plugin.configureRuntime({
1148
+ source: options.source,
1149
+ provenance: options.provenance,
1150
+ basePath: options.basePath,
1151
+ required: options.required,
1152
+ hostMode: isExternalPluginSource(options.source)
1153
+ ? this.getHostIdForManifest(plugin.manifest)
1154
+ : options.source === "system"
1155
+ ? this.getHostIdForIndexedExtension(this.#lapisExtensionIndex.get(plugin.manifest.id) ?? {
1156
+ pluginId: plugin.manifest.id,
1157
+ source: "system",
1158
+ classification: "lapis-extension",
1159
+ manifestPath: plugin.manifest.dir ?? "",
1160
+ activationEvents: [],
1161
+ runtime: plugin.manifest.lapis?.runtime,
1162
+ permissions: [],
1163
+ requestedCapabilities: [],
1164
+ grantedCapabilities: [],
1165
+ enabled: false,
1166
+ locked: plugin.required,
1167
+ enabledByDefault: true,
1168
+ activationErrors: [],
1169
+ privileges: [],
1170
+ contributions: [],
1171
+ })
1172
+ : "renderer",
1173
+ requestedCapabilities: isExternalPluginSource(options.source)
1174
+ ? this.getRequestedCapabilities(plugin.manifest)
1175
+ : options.source === "system"
1176
+ ? (this.#lapisExtensionIndex.get(plugin.manifest.id)
1177
+ ?.requestedCapabilities ?? [])
1178
+ : [],
1179
+ grantedCapabilities: isExternalPluginSource(options.source)
1180
+ ? this.getGrantedCapabilities(plugin.manifest)
1181
+ : options.source === "system"
1182
+ ? (this.#lapisExtensionIndex.get(plugin.manifest.id)
1183
+ ?.grantedCapabilities ?? [])
1184
+ : [],
1185
+ });
1186
+ this.plugins.set(plugin.manifest.id, plugin);
1187
+ if (isExternalPluginSource(options.source) ||
1188
+ this.#lapisExtensionIndex.get(plugin.manifest.id)?.source === "system") {
1189
+ this.updatePluginDiagnosticsFromPlugin(plugin);
1190
+ }
1191
+ this.emit("plugin-loaded", plugin);
1192
+ void this.syncLegacyPluginData(plugin.manifest.id);
1193
+ return plugin;
1194
+ }
1195
+ async syncLegacyPluginData(pluginId) {
1196
+ const plugin = this.plugins.get(pluginId);
1197
+ if (!plugin) {
1198
+ return;
1199
+ }
1200
+ const path = resolvePluginDataPath(plugin.manifest.id, plugin.source, plugin.basePath);
1201
+ if (!path) {
1202
+ return;
1203
+ }
1204
+ try {
1205
+ if (!this.app.configuration.hasPluginData(pluginId)) {
1206
+ const file = this.app.vault.getFileByPath(path);
1207
+ if (file) {
1208
+ await this.app.vault.delete(file, true);
1209
+ }
1210
+ return;
1211
+ }
1212
+ const payload = JSON.stringify(this.app.configuration.getPluginData(pluginId), null, 2);
1213
+ const file = this.app.vault.getFileByPath(path);
1214
+ if (file) {
1215
+ await this.app.vault.modify(file, payload);
1216
+ return;
1217
+ }
1218
+ await this.app.vault.mkpath(dirname(path));
1219
+ await this.app.vault.create(path, payload);
1220
+ }
1221
+ catch (error) {
1222
+ this.reportPluginError(pluginId, `Failed to sync legacy plugin data for ${pluginId}: ${formatPluginError(error)}`, error);
1223
+ }
1224
+ }
1225
+ async enablePlugin(pluginId) {
1226
+ const plugin = this.plugins.get(pluginId);
1227
+ if (!plugin) {
1228
+ const indexedExtension = this.#lapisExtensionIndex.get(pluginId);
1229
+ if (indexedExtension) {
1230
+ if (this.shouldDeferIndexedExtensionActivation(indexedExtension)) {
1231
+ const enabled = this.enableDeferredIndexedExtension(pluginId);
1232
+ if (enabled && this.#loaded) {
1233
+ if (indexedExtension.source === "community" ||
1234
+ indexedExtension.source === "official") {
1235
+ this.#configuredCommunityPlugins.add(pluginId);
1236
+ this.saveCommunityPluginState();
1237
+ }
1238
+ else if (!indexedExtension.locked) {
1239
+ this.#disabledCorePlugins.delete(pluginId);
1240
+ if (this.#defaultDisabledCorePlugins.has(pluginId)) {
1241
+ this.#enabledCorePlugins.add(pluginId);
1242
+ }
1243
+ else {
1244
+ this.#enabledCorePlugins.delete(pluginId);
1245
+ }
1246
+ this.saveCorePluginState();
1247
+ }
1248
+ }
1249
+ return enabled;
1250
+ }
1251
+ if (indexedExtension.source === "system" &&
1252
+ this.#systemExtensionPlugins.has(pluginId)) {
1253
+ const runtimePlugin = this.instantiateSystemExtensionPlugin(pluginId);
1254
+ if (runtimePlugin) {
1255
+ return this.enablePlugin(pluginId);
1256
+ }
1257
+ }
1258
+ if ((indexedExtension.source === "community" ||
1259
+ indexedExtension.source === "official") &&
1260
+ this.hasIndexedExtensionCodeEntry(indexedExtension)) {
1261
+ const loadedPlugin = await this.loadPlugin(dirname(indexedExtension.manifestPath));
1262
+ if (loadedPlugin) {
1263
+ return this.enablePlugin(pluginId);
1264
+ }
1265
+ return false;
1266
+ }
1267
+ const enabled = this.enableManifestOnlyExtension(pluginId);
1268
+ if (enabled && this.#loaded) {
1269
+ if (indexedExtension.source === "community" ||
1270
+ indexedExtension.source === "official") {
1271
+ this.#configuredCommunityPlugins.add(pluginId);
1272
+ this.saveCommunityPluginState();
1273
+ }
1274
+ else if (!indexedExtension.locked) {
1275
+ this.#disabledCorePlugins.delete(pluginId);
1276
+ if (this.#defaultDisabledCorePlugins.has(pluginId)) {
1277
+ this.#enabledCorePlugins.add(pluginId);
1278
+ }
1279
+ else {
1280
+ this.#enabledCorePlugins.delete(pluginId);
1281
+ }
1282
+ this.saveCorePluginState();
1283
+ }
1284
+ }
1285
+ return enabled;
1286
+ }
1287
+ const communityPluginPath = joinPath(this.pluginsPath, pluginId);
1288
+ const communityManifestPath = joinPath(communityPluginPath, "manifest.json");
1289
+ if (await this.pathExists(communityManifestPath)) {
1290
+ const loadedPlugin = await this.loadPlugin(communityPluginPath);
1291
+ if (loadedPlugin) {
1292
+ return this.enablePlugin(pluginId);
1293
+ }
1294
+ return false;
1295
+ }
1296
+ this.reportPluginError(pluginId, `Unknown plugin: ${pluginId}`);
1297
+ return false;
1298
+ }
1299
+ return this.app.telemetry.measureAsync("plugin.enable", async (span) => {
1300
+ span.setAttribute("plugin.id", pluginId);
1301
+ span.setAttribute("plugin.source", plugin.source);
1302
+ span.setAttribute("plugin.required", plugin.required);
1303
+ try {
1304
+ await plugin.enable();
1305
+ await this.restorePluginOwnedLeaves(plugin);
1306
+ const indexedExtension = this.#lapisExtensionIndex.get(pluginId);
1307
+ if (indexedExtension?.classification === "hybrid" &&
1308
+ !this.#manifestContributionDisposers.has(pluginId)) {
1309
+ this.enableManifestOnlyExtension(pluginId);
1310
+ }
1311
+ await this.loadPluginStyles(plugin);
1312
+ this.updatePluginDiagnosticsFromPlugin(plugin);
1313
+ this.emit("plugin-enabled", plugin);
1314
+ if (this.#loaded) {
1315
+ if (plugin.source === "community" || plugin.source === "official") {
1316
+ this.#configuredCommunityPlugins.add(pluginId);
1317
+ this.saveCommunityPluginState();
1318
+ }
1319
+ else if ((plugin.source === "core" || plugin.source === "system") &&
1320
+ !plugin.required) {
1321
+ this.#disabledCorePlugins.delete(pluginId);
1322
+ if (this.#defaultDisabledCorePlugins.has(pluginId)) {
1323
+ this.#enabledCorePlugins.add(pluginId);
1324
+ }
1325
+ else {
1326
+ this.#enabledCorePlugins.delete(pluginId);
1327
+ }
1328
+ this.saveCorePluginState();
1329
+ }
1330
+ }
1331
+ return true;
1332
+ }
1333
+ catch (error) {
1334
+ this.updatePluginDiagnosticsFromPlugin(plugin);
1335
+ this.reportPluginError(pluginId, `Failed to enable plugin ${pluginId}: ${formatPluginError(error)}`, error);
1336
+ return false;
1337
+ }
1338
+ }, {
1339
+ attributes: {
1340
+ "plugin.id": pluginId,
1341
+ "plugin.source": plugin.source,
1342
+ },
1343
+ slowThresholdMs: 300,
1344
+ });
1345
+ }
1346
+ async disablePlugin(pluginId) {
1347
+ const plugin = this.plugins.get(pluginId);
1348
+ if (!plugin) {
1349
+ const indexedExtension = this.#lapisExtensionIndex.get(pluginId);
1350
+ if (indexedExtension) {
1351
+ if (indexedExtension.source === "system" && indexedExtension.locked) {
1352
+ this.reportPluginError(pluginId, `Plugin ${pluginId} is required and cannot be disabled`);
1353
+ return false;
1354
+ }
1355
+ this.disableManifestOnlyExtension(pluginId);
1356
+ if (this.#loaded) {
1357
+ if (indexedExtension.source === "community" ||
1358
+ indexedExtension.source === "official") {
1359
+ this.#configuredCommunityPlugins.delete(pluginId);
1360
+ this.saveCommunityPluginState();
1361
+ }
1362
+ else {
1363
+ this.#enabledCorePlugins.delete(pluginId);
1364
+ if (this.#defaultDisabledCorePlugins.has(pluginId)) {
1365
+ this.#disabledCorePlugins.delete(pluginId);
1366
+ }
1367
+ else {
1368
+ this.#disabledCorePlugins.add(pluginId);
1369
+ }
1370
+ this.saveCorePluginState();
1371
+ }
1372
+ }
1373
+ return true;
1374
+ }
1375
+ if (this.#configuredCommunityPlugins.delete(pluginId)) {
1376
+ this.disableManifestOnlyExtension(pluginId);
1377
+ this.saveCommunityPluginState();
1378
+ const diagnostics = this.#communityPluginDiagnostics.get(pluginId);
1379
+ if (diagnostics) {
1380
+ this.#communityPluginDiagnostics.set(pluginId, {
1381
+ ...diagnostics,
1382
+ state: "disabled",
1383
+ });
1384
+ }
1385
+ return true;
1386
+ }
1387
+ this.reportPluginError(pluginId, `Unknown plugin: ${pluginId}`);
1388
+ return false;
1389
+ }
1390
+ if (plugin.required) {
1391
+ this.reportPluginError(pluginId, `Plugin ${pluginId} is required and cannot be disabled`);
1392
+ return false;
1393
+ }
1394
+ return this.app.telemetry.measureAsync("plugin.disable", async (span) => {
1395
+ span.setAttribute("plugin.id", pluginId);
1396
+ span.setAttribute("plugin.source", plugin.source);
1397
+ try {
1398
+ const ownedLeaves = this.capturePluginOwnedLeaves(plugin);
1399
+ await plugin.disable();
1400
+ await this.replacePluginOwnedLeaves(ownedLeaves);
1401
+ if (this.#manifestContributionDisposers.has(pluginId)) {
1402
+ this.disposeInstalledManifestContributions(pluginId);
1403
+ }
1404
+ this.unloadPluginCSS(pluginId);
1405
+ this.updatePluginDiagnosticsFromPlugin(plugin);
1406
+ this.emit("plugin-disabled", plugin);
1407
+ if (this.#loaded) {
1408
+ if (plugin.source === "community" || plugin.source === "official") {
1409
+ this.#configuredCommunityPlugins.delete(pluginId);
1410
+ this.saveCommunityPluginState();
1411
+ }
1412
+ else if (plugin.source === "core" || plugin.source === "system") {
1413
+ this.#enabledCorePlugins.delete(pluginId);
1414
+ if (this.#defaultDisabledCorePlugins.has(pluginId)) {
1415
+ this.#disabledCorePlugins.delete(pluginId);
1416
+ }
1417
+ else {
1418
+ this.#disabledCorePlugins.add(pluginId);
1419
+ }
1420
+ this.saveCorePluginState();
1421
+ }
1422
+ }
1423
+ return true;
1424
+ }
1425
+ catch (error) {
1426
+ this.updatePluginDiagnosticsFromPlugin(plugin);
1427
+ this.reportPluginError(pluginId, `Failed to disable plugin ${pluginId}: ${formatPluginError(error)}`, error);
1428
+ return false;
1429
+ }
1430
+ }, {
1431
+ attributes: {
1432
+ "plugin.id": pluginId,
1433
+ "plugin.source": plugin.source,
1434
+ },
1435
+ slowThresholdMs: 300,
1436
+ });
1437
+ }
1438
+ async restartPlugin(pluginId) {
1439
+ let plugin = this.plugins.get(pluginId);
1440
+ if (!plugin) {
1441
+ plugin =
1442
+ (await this.loadPlugin(joinPath(this.pluginsPath, pluginId))) ??
1443
+ undefined;
1444
+ }
1445
+ if (!plugin && this.#lapisExtensionIndex.has(pluginId)) {
1446
+ this.disableManifestOnlyExtension(pluginId);
1447
+ return this.enablePlugin(pluginId);
1448
+ }
1449
+ if (!plugin) {
1450
+ this.reportPluginError(pluginId, `Unknown plugin: ${pluginId}`);
1451
+ return false;
1452
+ }
1453
+ try {
1454
+ if (plugin.loaded || plugin.enabled || plugin.state === "failed") {
1455
+ await plugin.disable();
1456
+ this.unloadPluginCSS(pluginId);
1457
+ this.updatePluginDiagnosticsFromPlugin(plugin);
1458
+ }
1459
+ }
1460
+ catch (error) {
1461
+ this.updatePluginDiagnosticsFromPlugin(plugin);
1462
+ this.reportPluginError(pluginId, `Failed to restart plugin ${pluginId}: ${formatPluginError(error)}`, error);
1463
+ return false;
1464
+ }
1465
+ return this.enablePlugin(pluginId);
1466
+ }
1467
+ capturePluginOwnedLeaves(plugin) {
1468
+ const leaves = new Set();
1469
+ for (const viewType of plugin.registeredViewTypes) {
1470
+ for (const leaf of this.app.workspace.getLeavesOfType(viewType)) {
1471
+ leaves.add(leaf);
1472
+ }
1473
+ }
1474
+ return [...leaves].map((leaf) => ({
1475
+ leaf,
1476
+ state: leaf.captureCurrentViewState(),
1477
+ }));
1478
+ }
1479
+ async replacePluginOwnedLeaves(entries) {
1480
+ await Promise.all(entries.map(({ leaf, state }) => leaf.setViewState({
1481
+ ...state,
1482
+ type: "empty",
1483
+ state: {
1484
+ ...(state.state ?? {}),
1485
+ __missingViewType: state.type,
1486
+ },
1487
+ }, { history: false, activatePlugins: false })));
1488
+ }
1489
+ async restorePluginOwnedLeaves(plugin) {
1490
+ const leaves = [];
1491
+ this.app.workspace.iterateAllLeaves((leaf) => {
1492
+ const state = leaf.getViewState();
1493
+ const missingViewType = state.state?.["__missingViewType"];
1494
+ if (state.type === "empty" &&
1495
+ typeof missingViewType === "string" &&
1496
+ plugin.registeredViewTypes.has(missingViewType)) {
1497
+ leaves.push(leaf);
1498
+ }
1499
+ });
1500
+ await Promise.all(leaves.map((leaf) => leaf.setViewState(leaf.getViewState(), { history: false })));
1501
+ }
1502
+ isVersionCompatible(minVersion) {
1503
+ const appVersion = this.app.version;
1504
+ const appParts = appVersion.split(".").map(Number);
1505
+ const minParts = minVersion.split(".").map(Number);
1506
+ for (let i = 0; i < Math.max(appParts.length, minParts.length); i++) {
1507
+ const appPart = appParts[i] || 0;
1508
+ const minPart = minParts[i] || 0;
1509
+ if (appPart > minPart)
1510
+ return true;
1511
+ if (appPart < minPart)
1512
+ return false;
1513
+ }
1514
+ return true;
1515
+ }
1516
+ async enableAllPlugins(...pluginIds) {
1517
+ const ids = pluginIds.flat().filter((it) => it);
1518
+ const targetIds = pluginIds.length > 0 ? ids : [...this.plugins.keys()];
1519
+ for (const id of targetIds) {
1520
+ await this.enablePlugin(id);
1521
+ }
1522
+ }
1523
+ async loadPluginStyles(plugin) {
1524
+ if (plugin.source === "core") {
1525
+ const handled = await this.loadCorePluginStyles(plugin);
1526
+ if (handled) {
1527
+ return;
1528
+ }
1529
+ }
1530
+ if (!plugin.basePath) {
1531
+ return;
1532
+ }
1533
+ const cssPath = joinPath(plugin.basePath, "styles.css");
1534
+ let exists = false;
1535
+ try {
1536
+ exists = await this.adapter.exists(cssPath);
1537
+ }
1538
+ catch (error) {
1539
+ if (isMissingPathError(error)) {
1540
+ return;
1541
+ }
1542
+ throw error;
1543
+ }
1544
+ if (!exists) {
1545
+ return;
1546
+ }
1547
+ try {
1548
+ await this.loadPluginCSS(plugin.manifest.id, cssPath);
1549
+ }
1550
+ catch (error) {
1551
+ this.reportPluginError(plugin.manifest.id, `Failed to load plugin styles for ${plugin.manifest.id}: ${formatPluginError(error)}`, error);
1552
+ }
1553
+ }
1554
+ async loadCorePluginStyles(plugin) {
1555
+ const styleSource = this.#corePluginStyles.get(plugin.manifest.id);
1556
+ if (styleSource === undefined) {
1557
+ return false;
1558
+ }
1559
+ try {
1560
+ const cssContent = typeof styleSource === "function" ? await styleSource() : styleSource;
1561
+ if (cssContent.trim()) {
1562
+ this.loadPluginCSSContent(plugin.manifest.id, cssContent);
1563
+ }
1564
+ else {
1565
+ this.unloadPluginCSS(plugin.manifest.id);
1566
+ }
1567
+ }
1568
+ catch (error) {
1569
+ this.reportPluginError(plugin.manifest.id, `Failed to load bundled plugin styles for ${plugin.manifest.id}: ${formatPluginError(error)}`, error);
1570
+ }
1571
+ return true;
1572
+ }
1573
+ unloadPluginCSS(pluginId) {
1574
+ const el = document.querySelector(`#plugin-css-${pluginId}`);
1575
+ if (el) {
1576
+ el.detach();
1577
+ this.emit("css-change");
1578
+ }
1579
+ }
1580
+ async loadPluginCSS(pluginId, cssPath) {
1581
+ const cssContent = await this.adapter.read(cssPath);
1582
+ this.loadPluginCSSContent(pluginId, cssContent);
1583
+ }
1584
+ loadPluginCSSContent(pluginId, cssContent) {
1585
+ const id = `plugin-css-${pluginId}`;
1586
+ const el = document.querySelector(`#${id}`) ||
1587
+ document.createElement("style");
1588
+ el.id = id;
1589
+ el.textContent = cssContent;
1590
+ document.head.appendChild(el);
1591
+ this.emit("css-change");
1592
+ }
1593
+ async preflightManifest(manifest, pluginPath, source, options = {}) {
1594
+ if (!manifest.id?.trim()) {
1595
+ throw new Error(`Plugin at ${pluginPath} is missing a manifest id`);
1596
+ }
1597
+ if (this.plugins.has(manifest.id)) {
1598
+ throw new Error(`Plugin id ${manifest.id} is already registered`);
1599
+ }
1600
+ if (manifest.minAppVersion &&
1601
+ !this.isVersionCompatible(manifest.minAppVersion)) {
1602
+ throw new Error(`Plugin ${manifest.id} requires app version ${manifest.minAppVersion} or newer`);
1603
+ }
1604
+ if (manifest.isDesktopOnly && !Platform.isDesktopApp) {
1605
+ throw new Error(`Plugin ${manifest.id} can only run in the desktop app`);
1606
+ }
1607
+ if (isExternalPluginSource(source)) {
1608
+ if (requiresWorkspaceTrust(manifest) &&
1609
+ !this.app.workspaceTrust.trusted) {
1610
+ throw new Error(trustFailureMessage(manifest));
1611
+ }
1612
+ if (!this.isRuntimeSupported(manifest)) {
1613
+ throw new Error(`Plugin ${manifest.id} does not support host ${this.getHostIdForManifest(manifest)}`);
1614
+ }
1615
+ const invalidCapabilities = this.getInvalidRequiredCapabilities(manifest);
1616
+ if (invalidCapabilities.length) {
1617
+ throw new Error(`Plugin ${manifest.id} declares unknown required capabilities: ${invalidCapabilities.join(", ")}`);
1618
+ }
1619
+ const grantedCapabilities = new Set(this.getGrantedCapabilities(manifest));
1620
+ const missingCapabilities = this.getRequestedCapabilities(manifest).filter((capability) => !grantedCapabilities.has(capability));
1621
+ if (missingCapabilities.length) {
1622
+ throw new Error(`Plugin ${manifest.id} requires unavailable capabilities: ${missingCapabilities.join(", ")}`);
1623
+ }
1624
+ const mainPath = joinPath(pluginPath, "main.js");
1625
+ const hasMainJs = options.hasMainJs ?? (await this.pathExists(mainPath));
1626
+ const classification = options.classification ??
1627
+ classifyLapisPlugin(manifest, {
1628
+ hasMainJs,
1629
+ });
1630
+ const selectedEntry = this.getSelectedCodeEntry(manifest, classification, hasMainJs);
1631
+ if (!selectedEntry) {
1632
+ if (classification === "obsidian-compatible" && !hasMainJs) {
1633
+ throw new Error(`Plugin ${manifest.id} is missing main.js`);
1634
+ }
1635
+ if (hasDeclaredLapisRuntimeCode(manifest)) {
1636
+ throw new Error(`Plugin ${manifest.id} does not have a supported Lapis runtime entry for host ${this.getHostIdForManifest(manifest)}`);
1637
+ }
1638
+ return;
1639
+ }
1640
+ const modulePath = this.getSelectedCommunityModulePath(manifest, pluginPath, classification, hasMainJs);
1641
+ if (!modulePath || !(await this.pathExists(modulePath))) {
1642
+ if (selectedEntry === "main.js") {
1643
+ throw new Error(`Plugin ${manifest.id} is missing main.js`);
1644
+ }
1645
+ throw new Error(`Plugin ${manifest.id} is missing Lapis runtime entry ${selectedEntry}`);
1646
+ }
1647
+ }
1648
+ }
1649
+ reportPluginError(id, message, error) {
1650
+ console.error(message, error);
1651
+ this.app.telemetry.recordEvent("plugin.error", {
1652
+ "plugin.id": id,
1653
+ "plugin.message": message,
1654
+ });
1655
+ this.emit("plugin-error", id, message, error);
1656
+ }
1657
+ isRuntimeSupported(manifest) {
1658
+ const runtimes = normalizeStringArray(manifest.supportedRuntimes);
1659
+ if (!runtimes.length) {
1660
+ return true;
1661
+ }
1662
+ const hostId = this.getHostIdForManifest(manifest);
1663
+ return runtimes.some((runtime) => runtimeMatchesHost(runtime.toLowerCase(), hostId));
1664
+ }
1665
+ getRequestedCapabilities(manifest) {
1666
+ const validCapabilities = new Set(HOSTED_PLUGIN_CAPABILITIES);
1667
+ return [
1668
+ ...new Set([
1669
+ ...normalizeStringArray(manifest.requiredCapabilities).filter((capability) => validCapabilities.has(capability)),
1670
+ ...permissionsToCapabilities(manifest.lapis?.permissions),
1671
+ ]),
1672
+ ];
1673
+ }
1674
+ getInvalidRequiredCapabilities(manifest) {
1675
+ const validCapabilities = new Set(HOSTED_PLUGIN_CAPABILITIES);
1676
+ return normalizeStringArray(manifest.requiredCapabilities).filter((capability) => !validCapabilities.has(capability));
1677
+ }
1678
+ getGrantedCapabilities(manifest) {
1679
+ if (!this.getRequestedCapabilities(manifest).length) {
1680
+ return [];
1681
+ }
1682
+ if (!this.app.workspaceTrust.trusted) {
1683
+ return [];
1684
+ }
1685
+ if (isSidecarHost(this.getHostIdForManifest(manifest))) {
1686
+ return [...HOSTED_PLUGIN_CAPABILITIES];
1687
+ }
1688
+ return permissionsToCapabilities(manifest.lapis?.permissions);
1689
+ }
1690
+ recordCommunityPluginDiagnostics(manifest, state, runtime = {}) {
1691
+ this.syncPluginContextKeys(manifest.id, state);
1692
+ const previous = this.#communityPluginDiagnostics.get(manifest.id);
1693
+ const indexedExtension = this.#lapisExtensionIndex.get(manifest.id);
1694
+ const source = runtime.source ??
1695
+ this.plugins.get(manifest.id)?.source ??
1696
+ indexedExtension?.source ??
1697
+ previous?.source ??
1698
+ "community";
1699
+ const provenance = runtime.provenance ??
1700
+ this.plugins.get(manifest.id)?.provenance ??
1701
+ previous?.provenance ??
1702
+ provenanceForPluginSource(source);
1703
+ const classification = indexedExtension?.classification ??
1704
+ previous?.classification ??
1705
+ "obsidian-compatible";
1706
+ const selectedRuntime = this.selectRuntimeEntryForManifest(manifest, {
1707
+ classification,
1708
+ });
1709
+ const dependencyDiagnostics = this.getCommunityPluginDependencyDiagnosticsForRecord(manifest.id, previous);
1710
+ const runtimeDiagnostics = this.getCommunityPluginRuntimeExecutionDiagnosticsForRecord(manifest.id, previous);
1711
+ this.#communityPluginDiagnostics.set(manifest.id, {
1712
+ pluginId: manifest.id,
1713
+ name: manifest.name,
1714
+ version: manifest.version,
1715
+ author: manifest.author,
1716
+ description: manifest.description,
1717
+ source,
1718
+ provenance,
1719
+ classification,
1720
+ hostMode: this.getHostIdForManifest(manifest),
1721
+ activationMode: "not-activated",
1722
+ activationTrigger: previous?.activationTrigger ?? null,
1723
+ selectedRuntimeHost: selectedRuntime?.host ?? null,
1724
+ selectedRuntimeEntry: selectedRuntime?.path ?? null,
1725
+ moduleFormat: selectedRuntime?.format ?? null,
1726
+ fallbackRuntimeEntry: selectedRuntime?.fallbackPath ?? null,
1727
+ fallbackUsed: runtimeDiagnostics.fallbackUsed,
1728
+ requiresReloadOnUpdate: selectedRuntime?.requiresReloadOnUpdate ?? false,
1729
+ sharedDependencies: selectedRuntime?.sharedDependencies ?? [],
1730
+ usedSharedDependencies: dependencyDiagnostics.usedSharedDependencies,
1731
+ undeclaredSharedDependencies: dependencyDiagnostics.undeclaredSharedDependencies,
1732
+ missingSharedDependencies: dependencyDiagnostics.missingSharedDependencies,
1733
+ deprecatedSharedDependencies: dependencyDiagnostics.deprecatedSharedDependencies,
1734
+ privateSharedDependencies: dependencyDiagnostics.privateSharedDependencies,
1735
+ assetUrlMode: runtimeDiagnostics.assetUrlMode,
1736
+ pluginAssetUrl: runtimeDiagnostics.pluginAssetUrl,
1737
+ requestedCapabilities: this.getRequestedCapabilities(manifest),
1738
+ grantedCapabilities: this.getGrantedCapabilities(manifest),
1739
+ privileges: indexedExtension?.privileges ?? previous?.privileges ?? [],
1740
+ indexedContributionCount: indexedExtension?.contributions.length ??
1741
+ previous?.indexedContributionCount ??
1742
+ 0,
1743
+ contributionDiagnostics: [
1744
+ ...(this.#manifestContributionDiagnostics.get(manifest.id) ??
1745
+ previous?.contributionDiagnostics ??
1746
+ []),
1747
+ ],
1748
+ lastFailureMessage: previous?.lastFailureMessage ?? null,
1749
+ state,
1750
+ });
1751
+ }
1752
+ recordCommunityPluginFailure(pluginId, manifest, message) {
1753
+ this.syncPluginContextKeys(pluginId, "failed");
1754
+ const previous = this.#communityPluginDiagnostics.get(pluginId);
1755
+ const indexedExtension = this.#lapisExtensionIndex.get(pluginId);
1756
+ const source = this.plugins.get(pluginId)?.source ??
1757
+ indexedExtension?.source ??
1758
+ previous?.source ??
1759
+ (manifest
1760
+ ? this.resolveExternalPluginSource(manifest.id).source
1761
+ : "community");
1762
+ const provenance = this.plugins.get(pluginId)?.provenance ??
1763
+ previous?.provenance ??
1764
+ provenanceForPluginSource(source);
1765
+ const classification = indexedExtension?.classification ??
1766
+ previous?.classification ??
1767
+ "obsidian-compatible";
1768
+ const selectedRuntime = manifest
1769
+ ? this.selectRuntimeEntryForManifest(manifest, { classification })
1770
+ : null;
1771
+ const dependencyDiagnostics = this.getCommunityPluginDependencyDiagnosticsForRecord(pluginId, previous);
1772
+ const runtimeDiagnostics = this.getCommunityPluginRuntimeExecutionDiagnosticsForRecord(pluginId, previous);
1773
+ this.#communityPluginDiagnostics.set(pluginId, {
1774
+ pluginId,
1775
+ name: manifest?.name ?? previous?.name,
1776
+ version: manifest?.version ?? previous?.version,
1777
+ author: manifest?.author ?? previous?.author,
1778
+ description: manifest?.description ?? previous?.description,
1779
+ source,
1780
+ provenance,
1781
+ classification,
1782
+ hostMode: manifest
1783
+ ? this.getHostIdForManifest(manifest)
1784
+ : (previous?.hostMode ?? this.#communityPluginHost.id),
1785
+ activationMode: manifest
1786
+ ? "not-activated"
1787
+ : (previous?.activationMode ?? "not-activated"),
1788
+ activationTrigger: previous?.activationTrigger ?? null,
1789
+ selectedRuntimeHost: selectedRuntime?.host ?? previous?.selectedRuntimeHost ?? null,
1790
+ selectedRuntimeEntry: selectedRuntime?.path ?? previous?.selectedRuntimeEntry ?? null,
1791
+ moduleFormat: selectedRuntime?.format ?? previous?.moduleFormat ?? null,
1792
+ fallbackRuntimeEntry: selectedRuntime?.fallbackPath ?? previous?.fallbackRuntimeEntry ?? null,
1793
+ fallbackUsed: runtimeDiagnostics.fallbackUsed,
1794
+ requiresReloadOnUpdate: selectedRuntime?.requiresReloadOnUpdate ??
1795
+ previous?.requiresReloadOnUpdate ??
1796
+ false,
1797
+ sharedDependencies: selectedRuntime?.sharedDependencies ??
1798
+ previous?.sharedDependencies ??
1799
+ [],
1800
+ usedSharedDependencies: dependencyDiagnostics.usedSharedDependencies,
1801
+ undeclaredSharedDependencies: dependencyDiagnostics.undeclaredSharedDependencies,
1802
+ missingSharedDependencies: dependencyDiagnostics.missingSharedDependencies,
1803
+ deprecatedSharedDependencies: dependencyDiagnostics.deprecatedSharedDependencies,
1804
+ privateSharedDependencies: dependencyDiagnostics.privateSharedDependencies,
1805
+ assetUrlMode: runtimeDiagnostics.assetUrlMode,
1806
+ pluginAssetUrl: runtimeDiagnostics.pluginAssetUrl,
1807
+ requestedCapabilities: manifest
1808
+ ? this.getRequestedCapabilities(manifest)
1809
+ : (previous?.requestedCapabilities ?? []),
1810
+ grantedCapabilities: manifest
1811
+ ? this.getGrantedCapabilities(manifest)
1812
+ : (previous?.grantedCapabilities ?? []),
1813
+ privileges: indexedExtension?.privileges ?? previous?.privileges ?? [],
1814
+ indexedContributionCount: indexedExtension?.contributions.length ??
1815
+ previous?.indexedContributionCount ??
1816
+ 0,
1817
+ contributionDiagnostics: [
1818
+ ...(this.#manifestContributionDiagnostics.get(pluginId) ??
1819
+ previous?.contributionDiagnostics ??
1820
+ []),
1821
+ ],
1822
+ lastFailureMessage: message,
1823
+ state: "failed",
1824
+ });
1825
+ }
1826
+ updatePluginDiagnosticsFromPlugin(plugin) {
1827
+ this.syncPluginContextKeys(plugin.manifest.id, plugin.state);
1828
+ if (!isExternalPluginSource(plugin.source) &&
1829
+ this.#lapisExtensionIndex.get(plugin.manifest.id)?.source !== "system") {
1830
+ return;
1831
+ }
1832
+ const previous = this.#communityPluginDiagnostics.get(plugin.manifest.id);
1833
+ const indexedExtension = this.#lapisExtensionIndex.get(plugin.manifest.id);
1834
+ const classification = indexedExtension?.classification ??
1835
+ previous?.classification ??
1836
+ "obsidian-compatible";
1837
+ const selectedRuntime = this.selectRuntimeEntryForManifest(plugin.manifest, {
1838
+ classification,
1839
+ });
1840
+ const dependencyDiagnostics = this.getCommunityPluginDependencyDiagnosticsForRecord(plugin.manifest.id, previous);
1841
+ const runtimeDiagnostics = this.getCommunityPluginRuntimeExecutionDiagnosticsForRecord(plugin.manifest.id, previous);
1842
+ this.#communityPluginDiagnostics.set(plugin.manifest.id, {
1843
+ pluginId: plugin.manifest.id,
1844
+ name: plugin.manifest.name,
1845
+ version: plugin.manifest.version,
1846
+ author: plugin.manifest.author,
1847
+ description: plugin.manifest.description,
1848
+ source: plugin.source,
1849
+ provenance: plugin.provenance,
1850
+ classification,
1851
+ hostMode: plugin.hostMode,
1852
+ activationMode: "code",
1853
+ activationTrigger: previous?.activationTrigger ?? null,
1854
+ selectedRuntimeHost: selectedRuntime?.host ?? null,
1855
+ selectedRuntimeEntry: selectedRuntime?.path ?? null,
1856
+ moduleFormat: selectedRuntime?.format ?? null,
1857
+ fallbackRuntimeEntry: selectedRuntime?.fallbackPath ?? null,
1858
+ fallbackUsed: runtimeDiagnostics.fallbackUsed,
1859
+ requiresReloadOnUpdate: selectedRuntime?.requiresReloadOnUpdate ?? false,
1860
+ sharedDependencies: selectedRuntime?.sharedDependencies ?? [],
1861
+ usedSharedDependencies: dependencyDiagnostics.usedSharedDependencies,
1862
+ undeclaredSharedDependencies: dependencyDiagnostics.undeclaredSharedDependencies,
1863
+ missingSharedDependencies: dependencyDiagnostics.missingSharedDependencies,
1864
+ deprecatedSharedDependencies: dependencyDiagnostics.deprecatedSharedDependencies,
1865
+ privateSharedDependencies: dependencyDiagnostics.privateSharedDependencies,
1866
+ assetUrlMode: runtimeDiagnostics.assetUrlMode,
1867
+ pluginAssetUrl: runtimeDiagnostics.pluginAssetUrl,
1868
+ requestedCapabilities: plugin.requestedCapabilities,
1869
+ grantedCapabilities: plugin.grantedCapabilities,
1870
+ privileges: indexedExtension?.privileges ?? previous?.privileges ?? [],
1871
+ indexedContributionCount: indexedExtension?.contributions.length ??
1872
+ previous?.indexedContributionCount ??
1873
+ 0,
1874
+ contributionDiagnostics: [
1875
+ ...(this.#manifestContributionDiagnostics.get(plugin.manifest.id) ??
1876
+ previous?.contributionDiagnostics ??
1877
+ []),
1878
+ ],
1879
+ lastFailureMessage: plugin.lastFailureMessage ?? previous?.lastFailureMessage ?? null,
1880
+ state: plugin.state,
1881
+ });
1882
+ }
1883
+ async updateCommunityPluginDependencyDiagnostics(manifest, pluginPath, classification, hasMainJs) {
1884
+ const selectedRuntime = this.selectRuntimeEntryForManifest(manifest, {
1885
+ classification,
1886
+ hasMainJs,
1887
+ });
1888
+ const moduleSources = await this.readCommunityPluginModuleSources(pluginPath);
1889
+ const usedSharedDependencies = getBareDependencySpecifiersFromSources(moduleSources);
1890
+ const declaredSharedDependencies = selectedRuntime?.sharedDependencies ?? [];
1891
+ const declarationsAreExplicit = selectedRuntime?.source === "lapis-runtime-entry" ||
1892
+ declaredSharedDependencies.length > 0;
1893
+ const declared = new Set(declaredSharedDependencies);
1894
+ const undeclaredSharedDependencies = declarationsAreExplicit
1895
+ ? usedSharedDependencies.filter((specifier) => this.isKnownHostDependency(specifier, selectedRuntime?.host) &&
1896
+ !declared.has(specifier))
1897
+ : [];
1898
+ const missingSharedDependencies = declaredSharedDependencies.filter((specifier) => !this.isKnownHostDependency(specifier, selectedRuntime?.host));
1899
+ const declaredOrUsedSharedDependencies = [
1900
+ ...new Set([...declaredSharedDependencies, ...usedSharedDependencies]),
1901
+ ].sort();
1902
+ const deprecatedSharedDependencies = declaredOrUsedSharedDependencies.filter((specifier) => {
1903
+ const metadata = this.getHostDependencyMetadata(specifier);
1904
+ return Boolean(metadata && "deprecated" in metadata && metadata.deprecated === true);
1905
+ });
1906
+ const privateSharedDependencies = declaredOrUsedSharedDependencies.filter((specifier) => this.getHostDependencyMetadata(specifier)?.public === false);
1907
+ this.#communityPluginDependencyDiagnostics.set(manifest.id, {
1908
+ usedSharedDependencies,
1909
+ undeclaredSharedDependencies,
1910
+ missingSharedDependencies,
1911
+ deprecatedSharedDependencies,
1912
+ privateSharedDependencies,
1913
+ });
1914
+ }
1915
+ getCommunityPluginDependencyDiagnosticsForRecord(pluginId, previous) {
1916
+ return (this.#communityPluginDependencyDiagnostics.get(pluginId) ?? {
1917
+ usedSharedDependencies: previous?.usedSharedDependencies ?? [],
1918
+ undeclaredSharedDependencies: previous?.undeclaredSharedDependencies ?? [],
1919
+ missingSharedDependencies: previous?.missingSharedDependencies ?? [],
1920
+ deprecatedSharedDependencies: previous?.deprecatedSharedDependencies ?? [],
1921
+ privateSharedDependencies: previous?.privateSharedDependencies ?? [],
1922
+ });
1923
+ }
1924
+ getCommunityPluginRuntimeExecutionDiagnosticsForRecord(pluginId, previous) {
1925
+ return (this.#communityPluginRuntimeExecutionDiagnostics.get(pluginId) ?? {
1926
+ fallbackUsed: previous?.fallbackUsed ?? false,
1927
+ assetUrlMode: previous?.assetUrlMode ?? null,
1928
+ pluginAssetUrl: previous?.pluginAssetUrl ?? null,
1929
+ });
1930
+ }
1931
+ recordPluginAssetUrlDiagnostics(pluginId, url) {
1932
+ const previous = this.#communityPluginRuntimeExecutionDiagnostics.get(pluginId);
1933
+ this.#communityPluginRuntimeExecutionDiagnostics.set(pluginId, {
1934
+ ...previous,
1935
+ fallbackUsed: false,
1936
+ assetUrlMode: getPluginAssetUrlMode(url),
1937
+ pluginAssetUrl: url,
1938
+ });
1939
+ }
1940
+ recordPluginFallbackDiagnostics(pluginId) {
1941
+ const previous = this.#communityPluginRuntimeExecutionDiagnostics.get(pluginId);
1942
+ this.#communityPluginRuntimeExecutionDiagnostics.set(pluginId, {
1943
+ fallbackUsed: true,
1944
+ assetUrlMode: previous?.assetUrlMode ?? null,
1945
+ pluginAssetUrl: previous?.pluginAssetUrl ?? null,
1946
+ });
1947
+ }
1948
+ isKnownHostDependency(specifier, host) {
1949
+ const metadata = this.getHostDependencyMetadata(specifier);
1950
+ if (!metadata) {
1951
+ return false;
1952
+ }
1953
+ if (!host) {
1954
+ return true;
1955
+ }
1956
+ return metadata.platforms.some((platform) => hostPlatformMatchesDependencyPlatform(host, platform));
1957
+ }
1958
+ getHostDependencyMetadata(specifier) {
1959
+ return (pluginHostModuleRegistry[specifier] ?? undefined);
1960
+ }
1961
+ async readCommunityPluginModuleSources(pluginPath) {
1962
+ const sources = new Map();
1963
+ const visit = async (folderPath) => {
1964
+ const listing = await this.adapter.list(folderPath);
1965
+ await Promise.all(listing.files
1966
+ .filter((file) => /\.(?:cjs|mjs|js)$/i.test(file))
1967
+ .map(async (file) => {
1968
+ const filePath = normalizePath(joinPath(folderPath, file));
1969
+ sources.set(filePath, await this.adapter.read(filePath));
1970
+ }));
1971
+ for (const folder of listing.folders) {
1972
+ await visit(joinPath(folderPath, folder));
1973
+ }
1974
+ };
1975
+ await visit(pluginPath);
1976
+ return sources;
1977
+ }
1978
+ indexCommunityManifest(manifest, manifestPath, hasMainJs, source = "community", provenance = provenanceForPluginSource(source)) {
1979
+ const classification = classifyLapisPlugin(manifest, { hasMainJs });
1980
+ if (manifest.lapis === undefined) {
1981
+ this.#lapisExtensionIndex.delete(manifest.id);
1982
+ this.recordCommunityPluginDiagnostics(manifest, "disabled", {
1983
+ source,
1984
+ provenance,
1985
+ });
1986
+ return classification;
1987
+ }
1988
+ const diagnostics = validateLapisManifest(manifest.id, manifest.lapis);
1989
+ if (diagnostics.length) {
1990
+ throw new Error(diagnostics.join("; "));
1991
+ }
1992
+ if (!isLapisManifest(manifest.lapis)) {
1993
+ throw new Error(`Plugin ${manifest.id} has invalid lapis metadata`);
1994
+ }
1995
+ const indexedExtension = buildLapisContributionIndex({
1996
+ pluginId: manifest.id,
1997
+ name: manifest.name,
1998
+ source: isExternalPluginSource(source) ? source : "community",
1999
+ manifestPath,
2000
+ classification,
2001
+ lapis: manifest.lapis,
2002
+ requestedCapabilities: this.getRequestedCapabilities(manifest),
2003
+ grantedCapabilities: this.getGrantedCapabilities(manifest),
2004
+ });
2005
+ this.#lapisExtensionIndex.set(manifest.id, indexedExtension);
2006
+ this.#manifestContributionDiagnostics.set(manifest.id, indexedExtension.contributions.flatMap((entry) => entry.diagnostics));
2007
+ this.syncIndexedExtensionActivation(indexedExtension);
2008
+ this.recordCommunityPluginDiagnostics(manifest, "disabled", {
2009
+ source,
2010
+ provenance,
2011
+ });
2012
+ return classification;
2013
+ }
2014
+ enableManifestOnlyExtension(pluginId) {
2015
+ const indexedExtension = this.#lapisExtensionIndex.get(pluginId);
2016
+ if (!indexedExtension) {
2017
+ return false;
2018
+ }
2019
+ this.disableManifestOnlyExtension(pluginId);
2020
+ this.#deferredIndexedExtensions.delete(pluginId);
2021
+ const disposers = [];
2022
+ const diagnostics = indexedExtension.contributions.flatMap((entry) => entry.diagnostics);
2023
+ const contributions = indexedExtension.contributions.map((entry) => ({
2024
+ ...entry,
2025
+ diagnostics: [...entry.diagnostics],
2026
+ state: entry.state,
2027
+ }));
2028
+ for (const entry of contributions) {
2029
+ if (entry.state === "invalid") {
2030
+ continue;
2031
+ }
2032
+ try {
2033
+ switch (entry.kind) {
2034
+ case "commands":
2035
+ disposers.push({
2036
+ kind: entry.kind,
2037
+ id: entry.id,
2038
+ dispose: this.installManifestCommand(entry),
2039
+ });
2040
+ break;
2041
+ case "configuration":
2042
+ disposers.push({
2043
+ kind: entry.kind,
2044
+ id: entry.id,
2045
+ dispose: this.installManifestConfiguration(entry),
2046
+ });
2047
+ break;
2048
+ case "statusBarItems":
2049
+ disposers.push({
2050
+ kind: entry.kind,
2051
+ id: entry.id,
2052
+ dispose: this.installManifestStatusBarItem(entry),
2053
+ });
2054
+ break;
2055
+ case "editorViews":
2056
+ disposers.push({
2057
+ kind: entry.kind,
2058
+ id: entry.id,
2059
+ dispose: this.installManifestEditorView(entry),
2060
+ });
2061
+ break;
2062
+ case "services":
2063
+ if (indexedExtension.source === "system") {
2064
+ disposers.push({
2065
+ kind: entry.kind,
2066
+ id: entry.id,
2067
+ dispose: this.installManifestService(entry),
2068
+ });
2069
+ break;
2070
+ }
2071
+ {
2072
+ const message = `Contribution ${entry.kind}:${entry.id} is indexed but not installed in this phase`;
2073
+ entry.diagnostics.push(message);
2074
+ diagnostics.push(message);
2075
+ break;
2076
+ }
2077
+ default: {
2078
+ const message = `Contribution ${entry.kind}:${entry.id} is indexed but not installed in this phase`;
2079
+ entry.diagnostics.push(message);
2080
+ diagnostics.push(message);
2081
+ }
2082
+ }
2083
+ }
2084
+ catch (error) {
2085
+ entry.state = "invalid";
2086
+ const message = `Failed to install contribution ${entry.kind}:${entry.id}: ${formatPluginError(error)}`;
2087
+ entry.diagnostics.push(message);
2088
+ diagnostics.push(message);
2089
+ }
2090
+ }
2091
+ this.#manifestContributionDisposers.set(pluginId, disposers);
2092
+ this.#manifestContributionDiagnostics.set(pluginId, diagnostics);
2093
+ this.#lapisExtensionIndex.set(pluginId, {
2094
+ ...indexedExtension,
2095
+ contributions,
2096
+ });
2097
+ this.recordIndexedExtensionDiagnostics(pluginId, "enabled");
2098
+ return true;
2099
+ }
2100
+ enableDeferredIndexedExtension(pluginId) {
2101
+ const indexedExtension = this.#lapisExtensionIndex.get(pluginId);
2102
+ if (!indexedExtension) {
2103
+ return false;
2104
+ }
2105
+ this.disableManifestOnlyExtension(pluginId);
2106
+ const disposers = [];
2107
+ const diagnostics = indexedExtension.contributions.flatMap((entry) => entry.diagnostics);
2108
+ const contributions = indexedExtension.contributions.map((entry) => ({
2109
+ ...entry,
2110
+ diagnostics: [...entry.diagnostics],
2111
+ state: entry.state,
2112
+ }));
2113
+ for (const entry of contributions) {
2114
+ if (entry.state === "invalid") {
2115
+ continue;
2116
+ }
2117
+ try {
2118
+ switch (entry.kind) {
2119
+ case "commands":
2120
+ disposers.push({
2121
+ kind: entry.kind,
2122
+ id: entry.id,
2123
+ dispose: this.installManifestCommand(entry, {
2124
+ activateEvent: activationEventForContribution(pluginId, entry) ??
2125
+ `onCommand:${entry.id}`,
2126
+ }),
2127
+ });
2128
+ break;
2129
+ case "configuration":
2130
+ disposers.push({
2131
+ kind: entry.kind,
2132
+ id: entry.id,
2133
+ dispose: this.installManifestConfiguration(entry),
2134
+ });
2135
+ break;
2136
+ case "statusBarItems":
2137
+ disposers.push({
2138
+ kind: entry.kind,
2139
+ id: entry.id,
2140
+ dispose: this.installManifestStatusBarItem(entry),
2141
+ });
2142
+ break;
2143
+ case "editorViews":
2144
+ disposers.push({
2145
+ kind: entry.kind,
2146
+ id: entry.id,
2147
+ dispose: this.installManifestEditorView(entry),
2148
+ });
2149
+ break;
2150
+ default:
2151
+ break;
2152
+ }
2153
+ }
2154
+ catch (error) {
2155
+ entry.state = "invalid";
2156
+ const message = `Failed to install deferred contribution ${entry.kind}:${entry.id}: ${formatPluginError(error)}`;
2157
+ entry.diagnostics.push(message);
2158
+ diagnostics.push(message);
2159
+ }
2160
+ }
2161
+ this.#manifestContributionDisposers.set(pluginId, disposers);
2162
+ this.#manifestContributionDiagnostics.set(pluginId, diagnostics);
2163
+ this.#lapisExtensionIndex.set(pluginId, {
2164
+ ...indexedExtension,
2165
+ contributions,
2166
+ enabled: true,
2167
+ });
2168
+ this.#deferredIndexedExtensions.set(pluginId, {
2169
+ pluginId,
2170
+ source: indexedExtension.source,
2171
+ mode: "lazy-code",
2172
+ });
2173
+ this.recordIndexedExtensionDiagnostics(pluginId, "dormant");
2174
+ return true;
2175
+ }
2176
+ disableManifestOnlyExtension(pluginId) {
2177
+ this.disposeInstalledManifestContributions(pluginId);
2178
+ this.#deferredIndexedExtensions.delete(pluginId);
2179
+ this.recordIndexedExtensionDiagnostics(pluginId, "disabled");
2180
+ }
2181
+ disposeInstalledManifestContributions(pluginId) {
2182
+ const disposers = this.#manifestContributionDisposers.get(pluginId) ?? [];
2183
+ for (const entry of [...disposers].reverse()) {
2184
+ try {
2185
+ entry.dispose();
2186
+ }
2187
+ catch (error) {
2188
+ this.reportPluginError(pluginId, `Failed to dispose manifest contribution for ${pluginId}: ${formatPluginError(error)}`, error);
2189
+ }
2190
+ }
2191
+ this.#manifestContributionDisposers.delete(pluginId);
2192
+ this.#manifestContributionDiagnostics.delete(pluginId);
2193
+ }
2194
+ installManifestCommand(entry, options = {}) {
2195
+ const command = commandContributionToCommand(entry.contribution);
2196
+ const idPrefix = `${entry.pluginId}:`;
2197
+ if (!command.id.startsWith(idPrefix)) {
2198
+ command.id = `${idPrefix}${command.id}`;
2199
+ }
2200
+ if (!command.name.startsWith(idPrefix)) {
2201
+ command.name = `${idPrefix} ${command.name}`;
2202
+ }
2203
+ const commandId = command.id;
2204
+ const activateEvent = options.activateEvent ?? null;
2205
+ command.sourcePlugin = entry.pluginId;
2206
+ command.activationEvent = activateEvent ?? undefined;
2207
+ command.callback = async (...args) => {
2208
+ if (!activateEvent) {
2209
+ this.reportPluginError(entry.pluginId, `Manifest command ${entry.id} is declarative; runtime activation is not implemented yet`);
2210
+ return;
2211
+ }
2212
+ this.app.commands.unregisterCommand(commandId);
2213
+ const activated = await this.activateByEvent(activateEvent, `command:${commandId}`);
2214
+ if (activated && this.app.commands.getCommand(commandId)) {
2215
+ return this.app.commands.executeCommand(commandId, ...args);
2216
+ }
2217
+ this.app.commands.registerCommand(command);
2218
+ this.reportPluginError(entry.pluginId, `Deferred command ${commandId} did not activate a runtime command`);
2219
+ };
2220
+ this.app.commands.registerCommand(command);
2221
+ return () => {
2222
+ this.app.commands.unregisterCommand(command.id);
2223
+ };
2224
+ }
2225
+ installManifestConfiguration(entry) {
2226
+ const contribution = entry.contribution;
2227
+ const schema = {
2228
+ id: contribution.id ?? entry.pluginId,
2229
+ title: contribution.title ?? entry.id,
2230
+ type: "object",
2231
+ properties: contribution.properties ?? {},
2232
+ };
2233
+ this.app.configuration.schema.register(schema);
2234
+ void this.app.configuration.materializeSchemaDefaults();
2235
+ return () => {
2236
+ this.app.configuration.schema.unregister(schema);
2237
+ };
2238
+ }
2239
+ installManifestStatusBarItem(entry) {
2240
+ const contribution = entry.contribution;
2241
+ return this.app.statusBar.registerItem({
2242
+ id: `${entry.pluginId}:${contribution.id}`,
2243
+ text: contribution.text,
2244
+ icon: contribution.icon,
2245
+ tooltip: contribution.tooltip,
2246
+ when: contribution.when,
2247
+ alignment: contribution.alignment,
2248
+ priority: contribution.priority,
2249
+ command: contribution.command
2250
+ ? normalizeManifestCommandId(entry.pluginId, contribution.command)
2251
+ : undefined,
2252
+ sourcePlugin: entry.pluginId,
2253
+ });
2254
+ }
2255
+ installManifestEditorView(entry) {
2256
+ const contribution = entry.contribution;
2257
+ return this.app.workspace.registerEditorView({
2258
+ ...contribution,
2259
+ pluginId: entry.pluginId,
2260
+ source: "manifest",
2261
+ });
2262
+ }
2263
+ installManifestService(entry) {
2264
+ const contribution = entry.contribution;
2265
+ const registration = this.getSystemExtensionServiceProviderRegistration(entry.pluginId, contribution.service, entry.id);
2266
+ if (!registration) {
2267
+ throw new Error(`System extension ${entry.pluginId} is missing a provider binding for ${contribution.service}:${entry.id}`);
2268
+ }
2269
+ const provider = registration.createProvider
2270
+ ? registration.createProvider(this.app)
2271
+ : registration.provider;
2272
+ if (provider === undefined) {
2273
+ throw new Error(`System extension ${entry.pluginId} did not return a provider for ${contribution.service}:${entry.id}`);
2274
+ }
2275
+ return this.registerLapisServiceProvider({
2276
+ pluginId: entry.pluginId,
2277
+ service: contribution.service,
2278
+ id: entry.id,
2279
+ metadata: registration.metadata,
2280
+ provider,
2281
+ dispose: registration.dispose,
2282
+ });
2283
+ }
2284
+ recordIndexedExtensionDiagnostics(pluginId, state) {
2285
+ this.syncPluginContextKeys(pluginId, state);
2286
+ const indexedExtension = this.#lapisExtensionIndex.get(pluginId);
2287
+ if (!indexedExtension) {
2288
+ return;
2289
+ }
2290
+ const updatedExtension = {
2291
+ ...indexedExtension,
2292
+ enabled: state === "enabled",
2293
+ };
2294
+ this.#lapisExtensionIndex.set(pluginId, updatedExtension);
2295
+ const previous = this.#communityPluginDiagnostics.get(pluginId);
2296
+ const selectedRuntime = this.selectRuntimeEntryForIndexedExtension(updatedExtension);
2297
+ const dependencyDiagnostics = this.getCommunityPluginDependencyDiagnosticsForRecord(pluginId, previous);
2298
+ const runtimeDiagnostics = this.getCommunityPluginRuntimeExecutionDiagnosticsForRecord(pluginId, previous);
2299
+ this.#communityPluginDiagnostics.set(pluginId, {
2300
+ pluginId,
2301
+ name: updatedExtension.name ?? previous?.name,
2302
+ version: previous?.version,
2303
+ author: previous?.author,
2304
+ description: previous?.description,
2305
+ source: updatedExtension.source === "system"
2306
+ ? "system"
2307
+ : updatedExtension.source,
2308
+ provenance: updatedExtension.source === "official"
2309
+ ? "official"
2310
+ : updatedExtension.source === "system"
2311
+ ? "bundled"
2312
+ : (previous?.provenance ?? "community"),
2313
+ classification: updatedExtension.classification,
2314
+ hostMode: this.getHostIdForIndexedExtension(updatedExtension),
2315
+ activationMode: state === "enabled"
2316
+ ? this.#deferredIndexedExtensions.get(pluginId)?.mode === "lazy-code"
2317
+ ? "not-activated"
2318
+ : "manifest-only"
2319
+ : "not-activated",
2320
+ activationTrigger: previous?.activationTrigger ?? null,
2321
+ selectedRuntimeHost: selectedRuntime?.host ?? null,
2322
+ selectedRuntimeEntry: selectedRuntime?.path ?? null,
2323
+ moduleFormat: selectedRuntime?.format ?? null,
2324
+ fallbackRuntimeEntry: selectedRuntime?.fallbackPath ?? null,
2325
+ fallbackUsed: runtimeDiagnostics.fallbackUsed,
2326
+ requiresReloadOnUpdate: selectedRuntime?.requiresReloadOnUpdate ?? false,
2327
+ sharedDependencies: selectedRuntime?.sharedDependencies ?? [],
2328
+ usedSharedDependencies: dependencyDiagnostics.usedSharedDependencies,
2329
+ undeclaredSharedDependencies: dependencyDiagnostics.undeclaredSharedDependencies,
2330
+ missingSharedDependencies: dependencyDiagnostics.missingSharedDependencies,
2331
+ deprecatedSharedDependencies: dependencyDiagnostics.deprecatedSharedDependencies,
2332
+ privateSharedDependencies: dependencyDiagnostics.privateSharedDependencies,
2333
+ assetUrlMode: runtimeDiagnostics.assetUrlMode,
2334
+ pluginAssetUrl: runtimeDiagnostics.pluginAssetUrl,
2335
+ requestedCapabilities: updatedExtension.requestedCapabilities,
2336
+ grantedCapabilities: updatedExtension.grantedCapabilities,
2337
+ privileges: updatedExtension.privileges,
2338
+ indexedContributionCount: updatedExtension.contributions.length,
2339
+ contributionDiagnostics: this.getLapisContributionDiagnostics(pluginId),
2340
+ lastFailureMessage: previous?.lastFailureMessage ?? null,
2341
+ state,
2342
+ });
2343
+ }
2344
+ syncPluginContextKeys(pluginId, state) {
2345
+ this.app.contextKeys.set(`plugin.state.${pluginId}`, state);
2346
+ this.app.contextKeys.set(`plugin.enabled.${pluginId}`, state === "enabled" || state === "enabling" || state === "dormant");
2347
+ }
2348
+ async activateByEvent(event, trigger = event) {
2349
+ const pluginIds = [...(this.#indexedActivationRegistry.get(event) ?? [])];
2350
+ if (!pluginIds.length) {
2351
+ return false;
2352
+ }
2353
+ let activated = false;
2354
+ for (const pluginId of pluginIds) {
2355
+ activated =
2356
+ (await this.activateDeferredIndexedExtension(pluginId, trigger)) ||
2357
+ activated;
2358
+ }
2359
+ return activated;
2360
+ }
2361
+ async activateForViewType(viewType) {
2362
+ return this.activateByEvent(`onView:${viewType}`, `view:${viewType}`);
2363
+ }
2364
+ async activateForService(serviceId) {
2365
+ return this.activateByEvent(`onService:${serviceId}`, `service:${serviceId}`);
2366
+ }
2367
+ async activateForLanguage(languageId) {
2368
+ return this.activateByEvent(`onLanguage:${languageId}`, `language:${languageId}`);
2369
+ }
2370
+ async activateForPath(path) {
2371
+ const events = this.matchingPathActivationEvents(path);
2372
+ let activated = false;
2373
+ for (const event of events) {
2374
+ activated =
2375
+ (await this.activateByEvent(event, `path:${path}`)) || activated;
2376
+ }
2377
+ return activated;
2378
+ }
2379
+ async activateIndexedExtensionsForWorkspace() {
2380
+ const files = this.app.vault.getFiles().map((file) => file.path);
2381
+ for (const filePath of files) {
2382
+ await this.activateForPath(filePath);
2383
+ const languageIds = this.findLanguageIdsForPath(filePath);
2384
+ for (const languageId of languageIds) {
2385
+ await this.activateForLanguage(languageId);
2386
+ }
2387
+ }
2388
+ }
2389
+ async activateDeferredIndexedExtension(pluginId, trigger) {
2390
+ const deferred = this.#deferredIndexedExtensions.get(pluginId);
2391
+ if (!deferred) {
2392
+ return false;
2393
+ }
2394
+ if (this.plugins.get(pluginId)?.enabled) {
2395
+ return false;
2396
+ }
2397
+ const diagnostics = this.#communityPluginDiagnostics.get(pluginId);
2398
+ if (diagnostics) {
2399
+ this.#communityPluginDiagnostics.set(pluginId, {
2400
+ ...diagnostics,
2401
+ activationTrigger: trigger,
2402
+ state: "enabling",
2403
+ });
2404
+ }
2405
+ const indexedExtension = this.#lapisExtensionIndex.get(pluginId);
2406
+ const commandShims = this.#manifestContributionDisposers
2407
+ .get(pluginId)
2408
+ ?.filter((entry) => entry.kind === "commands") ?? [];
2409
+ for (const entry of commandShims) {
2410
+ entry.dispose();
2411
+ }
2412
+ if (commandShims.length) {
2413
+ const remaining = this.#manifestContributionDisposers
2414
+ .get(pluginId)
2415
+ ?.filter((entry) => entry.kind !== "commands") ?? [];
2416
+ this.#manifestContributionDisposers.set(pluginId, remaining);
2417
+ }
2418
+ let enabled = false;
2419
+ try {
2420
+ if (indexedExtension?.source === "system" &&
2421
+ this.#systemExtensionPlugins.has(pluginId)) {
2422
+ const runtimePlugin = this.instantiateSystemExtensionPlugin(pluginId);
2423
+ if (!runtimePlugin) {
2424
+ return false;
2425
+ }
2426
+ enabled = await this.enablePlugin(pluginId);
2427
+ }
2428
+ else {
2429
+ const pluginPath = joinPath(this.pluginsPath, pluginId);
2430
+ const runtimePlugin = (await this.loadPlugin(pluginPath, {
2431
+ loadDeferredRuntime: true,
2432
+ })) ?? null;
2433
+ if (!runtimePlugin) {
2434
+ return false;
2435
+ }
2436
+ enabled = await this.enablePlugin(pluginId);
2437
+ }
2438
+ }
2439
+ catch (error) {
2440
+ const message = `Failed to activate deferred extension ${pluginId}: ${formatPluginError(error)}`;
2441
+ this.recordCommunityPluginFailure(pluginId, this.plugins.get(pluginId)?.manifest ??
2442
+ this.#systemExtensionManifests.get(pluginId) ??
2443
+ null, message);
2444
+ this.enableDeferredIndexedExtension(pluginId);
2445
+ return false;
2446
+ }
2447
+ if (!enabled) {
2448
+ this.enableDeferredIndexedExtension(pluginId);
2449
+ return false;
2450
+ }
2451
+ this.#deferredIndexedExtensions.delete(pluginId);
2452
+ const updatedDiagnostics = this.#communityPluginDiagnostics.get(pluginId);
2453
+ if (updatedDiagnostics) {
2454
+ this.#communityPluginDiagnostics.set(pluginId, {
2455
+ ...updatedDiagnostics,
2456
+ activationTrigger: trigger,
2457
+ });
2458
+ }
2459
+ return true;
2460
+ }
2461
+ shouldDeferManifestActivation(pluginId) {
2462
+ const indexedExtension = this.#lapisExtensionIndex.get(pluginId);
2463
+ return indexedExtension
2464
+ ? this.shouldDeferIndexedExtensionActivation(indexedExtension)
2465
+ : false;
2466
+ }
2467
+ shouldDeferIndexedExtensionActivation(indexedExtension) {
2468
+ if (indexedExtension.classification !== "lapis-extension") {
2469
+ return false;
2470
+ }
2471
+ if (indexedExtension.source === "system") {
2472
+ return this.#systemExtensionPlugins.has(indexedExtension.pluginId);
2473
+ }
2474
+ if (!this.getSelectedRuntimeEntryForIndexedExtension(indexedExtension)) {
2475
+ return false;
2476
+ }
2477
+ const allEvents = this.collectIndexedExtensionActivationEvents(indexedExtension);
2478
+ if (!allEvents.length) {
2479
+ return false;
2480
+ }
2481
+ return indexedExtension.contributions.every((entry) => {
2482
+ if (entry.kind === "configuration") {
2483
+ return true;
2484
+ }
2485
+ return Boolean(activationEventForContribution(indexedExtension.pluginId, entry));
2486
+ });
2487
+ }
2488
+ collectIndexedExtensionActivationEvents(indexedExtension) {
2489
+ const events = new Set(indexedExtension.activationEvents);
2490
+ for (const entry of indexedExtension.contributions) {
2491
+ const event = activationEventForContribution(indexedExtension.pluginId, entry);
2492
+ if (event) {
2493
+ events.add(event);
2494
+ }
2495
+ if (entry.kind === "services") {
2496
+ const contribution = entry.contribution;
2497
+ events.add(`onService:${contribution.service}`);
2498
+ }
2499
+ }
2500
+ return [...events];
2501
+ }
2502
+ syncIndexedExtensionActivation(indexedExtension) {
2503
+ for (const pluginIds of this.#indexedActivationRegistry.values()) {
2504
+ pluginIds.delete(indexedExtension.pluginId);
2505
+ }
2506
+ if (!this.shouldDeferIndexedExtensionActivation(indexedExtension)) {
2507
+ return;
2508
+ }
2509
+ for (const event of this.collectIndexedExtensionActivationEvents(indexedExtension)) {
2510
+ const pluginIds = this.#indexedActivationRegistry.get(event) ?? new Set();
2511
+ pluginIds.add(indexedExtension.pluginId);
2512
+ this.#indexedActivationRegistry.set(event, pluginIds);
2513
+ }
2514
+ }
2515
+ matchingPathActivationEvents(path) {
2516
+ const matches = [];
2517
+ for (const event of this.#indexedActivationRegistry.keys()) {
2518
+ if (!event.startsWith("workspaceContains:")) {
2519
+ if (event.startsWith("onFileSystem:")) {
2520
+ const pattern = event.slice("onFileSystem:".length);
2521
+ if (matchesGlob(path, pattern)) {
2522
+ matches.push(event);
2523
+ }
2524
+ }
2525
+ continue;
2526
+ }
2527
+ const pattern = event.slice("workspaceContains:".length);
2528
+ if (matchesGlob(path, pattern)) {
2529
+ matches.push(event);
2530
+ }
2531
+ }
2532
+ return matches;
2533
+ }
2534
+ findLanguageIdsForPath(path) {
2535
+ const languageIds = new Set();
2536
+ const fileName = path.split("/").at(-1) ?? path;
2537
+ for (const extension of this.#lapisExtensionIndex.values()) {
2538
+ for (const entry of extension.contributions) {
2539
+ if (entry.kind !== "languages") {
2540
+ continue;
2541
+ }
2542
+ const contribution = entry.contribution;
2543
+ const normalizedExtensions = (contribution.extensions ?? []).map((it) => (it.startsWith(".") ? it : `.${it}`));
2544
+ if (normalizedExtensions.some((suffix) => path.toLowerCase().endsWith(suffix.toLowerCase())) ||
2545
+ (contribution.filenames ?? []).includes(fileName)) {
2546
+ if (typeof contribution.id === "string" && contribution.id) {
2547
+ languageIds.add(contribution.id);
2548
+ }
2549
+ }
2550
+ }
2551
+ }
2552
+ return [...languageIds];
2553
+ }
2554
+ instantiateSystemExtensionPlugin(pluginId) {
2555
+ const PluginType = this.#systemExtensionPlugins.get(pluginId);
2556
+ const manifest = this.#systemExtensionManifests.get(pluginId);
2557
+ if (!PluginType || !manifest || this.plugins.has(pluginId)) {
2558
+ return this.plugins.get(pluginId) ?? null;
2559
+ }
2560
+ const plugin = new PluginType(this.app);
2561
+ if (!(plugin instanceof Plugin)) {
2562
+ throw new Error(`System extension ${pluginId} does not extend the Plugin base class`);
2563
+ }
2564
+ plugin.manifest.dir ??= dirname(this.getSystemExtensionManifestPath(undefined, pluginId));
2565
+ this.registerPlugin(plugin, {
2566
+ source: "system",
2567
+ basePath: plugin.manifest.dir,
2568
+ required: this.#lapisExtensionIndex.get(pluginId)?.locked ?? false,
2569
+ });
2570
+ return plugin;
2571
+ }
2572
+ createSystemExtensionManifest(registration) {
2573
+ return {
2574
+ id: registration.manifest.id,
2575
+ name: registration.manifest.name,
2576
+ author: registration.manifest.author ?? "Lapis Notes",
2577
+ version: registration.manifest.version,
2578
+ minAppVersion: registration.manifest.minAppVersion ?? "0.0.0",
2579
+ description: registration.manifest.description ?? "",
2580
+ lapis: {
2581
+ ...registration.manifest.lapis,
2582
+ source: "system",
2583
+ enabledByDefault: registration.enabledByDefault ??
2584
+ registration.manifest.lapis.enabledByDefault,
2585
+ locked: registration.locked ?? registration.manifest.lapis.locked,
2586
+ },
2587
+ };
2588
+ }
2589
+ getSystemExtensionManifestPath(basePath, pluginId) {
2590
+ if (basePath) {
2591
+ return normalizePath(joinPath(basePath, "manifest.json"));
2592
+ }
2593
+ return `/.lapis/system-extensions/${pluginId}/manifest.json`;
2594
+ }
2595
+ storeSystemExtensionServiceProviders(pluginId, registrations) {
2596
+ const providers = new Map();
2597
+ for (const registration of registrations) {
2598
+ providers.set(createSystemExtensionServiceProviderKey(registration.service, registration.id), registration);
2599
+ }
2600
+ this.#systemExtensionServiceProviders.set(pluginId, providers);
2601
+ }
2602
+ getSystemExtensionServiceProviderRegistration(pluginId, service, providerId) {
2603
+ return (this.#systemExtensionServiceProviders
2604
+ .get(pluginId)
2605
+ ?.get(createSystemExtensionServiceProviderKey(service, providerId)) ??
2606
+ null);
2607
+ }
2608
+ isManifestOnlyLapisExtension(manifest, classification, hasMainJs) {
2609
+ return (classification === "lapis-extension" &&
2610
+ !hasMainJs &&
2611
+ !hasDeclaredLapisRuntimeCode(manifest));
2612
+ }
2613
+ hasIndexedExtensionCodeEntry(indexedExtension) {
2614
+ return (this.getSelectedRuntimeEntryForIndexedExtension(indexedExtension) !== null);
2615
+ }
2616
+ getSelectedCodeEntry(manifest, classification, hasMainJs) {
2617
+ return (this.selectRuntimeEntryForManifest(manifest, {
2618
+ classification,
2619
+ hasMainJs,
2620
+ })?.path ?? null);
2621
+ }
2622
+ getSelectedCommunityModulePath(manifest, pluginPath, classification, hasMainJs) {
2623
+ const entry = this.getSelectedCodeEntry(manifest, classification, hasMainJs);
2624
+ if (!entry) {
2625
+ return null;
2626
+ }
2627
+ if (entry === "main.js") {
2628
+ return joinPath(pluginPath, entry);
2629
+ }
2630
+ return this.resolveValidatedRuntimeEntryPath(manifest.id, pluginPath, entry);
2631
+ }
2632
+ getSelectedLapisRuntimeEntry(manifest) {
2633
+ const selected = this.selectRuntimeEntryForManifest(manifest, {
2634
+ hasMainJs: false,
2635
+ classification: "lapis-extension",
2636
+ });
2637
+ if (selected?.source === "obsidian-main") {
2638
+ return null;
2639
+ }
2640
+ return selected?.path ?? null;
2641
+ }
2642
+ getHostIdForManifest(manifest) {
2643
+ return (this.#communityPluginHost.hostIdForManifest?.(manifest) ??
2644
+ this.#communityPluginHost.id);
2645
+ }
2646
+ getHostIdForIndexedExtension(indexedExtension) {
2647
+ return isSidecarHost(this.#communityPluginHost.id) &&
2648
+ (indexedExtension.runtime?.desktop ||
2649
+ indexedExtension.runtime?.trustedDesktop ||
2650
+ indexedExtension.requestedCapabilities.length)
2651
+ ? this.#communityPluginHost.id
2652
+ : "renderer";
2653
+ }
2654
+ getSelectedRuntimeEntryForManifest(manifest, classification) {
2655
+ return (this.selectRuntimeEntryForManifest(manifest, {
2656
+ classification,
2657
+ })?.path ?? null);
2658
+ }
2659
+ getSelectedRuntimeEntryForIndexedExtension(indexedExtension) {
2660
+ return (this.selectRuntimeEntryForIndexedExtension(indexedExtension)?.path ?? null);
2661
+ }
2662
+ selectRuntimeEntryForManifest(manifest, options = {}) {
2663
+ const classification = options.classification;
2664
+ const requestedHost = this.getRequestedPluginExecutionHost(manifest);
2665
+ return selectPluginRuntimeEntry({
2666
+ manifest,
2667
+ requestedHost,
2668
+ supportsEsm: this.supportsRendererEsm(requestedHost),
2669
+ supportsNodeEsm: false,
2670
+ hasMainJs: options.hasMainJs ?? classification !== "lapis-extension",
2671
+ });
2672
+ }
2673
+ selectRuntimeEntryForIndexedExtension(indexedExtension) {
2674
+ return selectPluginRuntimeEntry({
2675
+ manifest: {
2676
+ id: indexedExtension.pluginId,
2677
+ name: indexedExtension.name ?? indexedExtension.pluginId,
2678
+ author: "",
2679
+ version: "0.0.0",
2680
+ minAppVersion: "0.0.0",
2681
+ description: "",
2682
+ lapis: {
2683
+ manifestVersion: 1,
2684
+ runtime: indexedExtension.runtime,
2685
+ },
2686
+ },
2687
+ requestedHost: this.getRequestedPluginExecutionHostForHostId(this.getHostIdForIndexedExtension(indexedExtension)),
2688
+ supportsEsm: this.supportsRendererEsm(this.getRequestedPluginExecutionHostForHostId(this.getHostIdForIndexedExtension(indexedExtension))),
2689
+ supportsNodeEsm: false,
2690
+ hasMainJs: indexedExtension.classification !== "lapis-extension",
2691
+ });
2692
+ }
2693
+ supportsRendererEsm(host) {
2694
+ return Boolean(this.#pluginAssetServer) && host !== "electron-sidecar";
2695
+ }
2696
+ getRequestedPluginExecutionHost(manifest) {
2697
+ return this.getRequestedPluginExecutionHostForHostId(this.getHostIdForManifest(manifest));
2698
+ }
2699
+ getRequestedPluginExecutionHostForHostId(hostId) {
2700
+ if (isSidecarHost(hostId)) {
2701
+ return "electron-sidecar";
2702
+ }
2703
+ return getNativeDesktopBridge()?.capabilities?.["plugin-assets"]?.status ===
2704
+ "available"
2705
+ ? "electron-renderer"
2706
+ : "workspace";
2707
+ }
2708
+ resolveValidatedRuntimeEntryPath(pluginId, pluginPath, runtimeEntry) {
2709
+ const normalizedPluginPath = normalizePath(pluginPath);
2710
+ const normalizedEntry = runtimeEntry.trim();
2711
+ if (!normalizedEntry) {
2712
+ throw new Error(`Plugin ${pluginId} is missing a Lapis runtime entry path`);
2713
+ }
2714
+ if (normalizedEntry.startsWith("/")) {
2715
+ throw new Error(`Plugin ${pluginId} declares absolute Lapis runtime entry ${runtimeEntry}`);
2716
+ }
2717
+ if (!/\.(?:[cm]?js)$/u.test(normalizedEntry)) {
2718
+ throw new Error(`Plugin ${pluginId} declares unsupported Lapis runtime entry ${runtimeEntry}`);
2719
+ }
2720
+ const modulePath = normalizePath(joinPath(normalizedPluginPath, normalizedEntry));
2721
+ if (modulePath === normalizedPluginPath ||
2722
+ !modulePath.startsWith(`${normalizedPluginPath}/`)) {
2723
+ throw new Error(`Plugin ${pluginId} declares Lapis runtime entry ${runtimeEntry} outside the plugin root`);
2724
+ }
2725
+ return modulePath;
2726
+ }
2727
+ async pathExists(path) {
2728
+ try {
2729
+ return await this.adapter.exists(path);
2730
+ }
2731
+ catch (error) {
2732
+ if (isMissingPathError(error)) {
2733
+ return false;
2734
+ }
2735
+ throw error;
2736
+ }
2737
+ }
2738
+ }
2739
+ function normalizeStringArray(value) {
2740
+ if (!Array.isArray(value)) {
2741
+ return [];
2742
+ }
2743
+ return [
2744
+ ...new Set(value.filter((it) => typeof it === "string")),
2745
+ ];
2746
+ }
2747
+ function matchesGlob(path, pattern) {
2748
+ const normalizedPattern = pattern.trim();
2749
+ if (!normalizedPattern) {
2750
+ return false;
2751
+ }
2752
+ const escaped = normalizedPattern.replace(/[.+^${}()|[\]\\]/g, "\\$&");
2753
+ const withDoubleStar = escaped.replace(/\*\*/g, "__DOUBLE_STAR__");
2754
+ const regex = new RegExp(`^${withDoubleStar
2755
+ .replace(/\*/g, "[^/]*")
2756
+ .replace(/__DOUBLE_STAR__/g, ".*")
2757
+ .replace(/\?/g, ".")}$`, "u");
2758
+ return regex.test(path);
2759
+ }
2760
+ function isLanguageServiceId(service) {
2761
+ return ["language-service", "lapis.languageService"].includes(service);
2762
+ }
2763
+ function scopeLapisProviderId(pluginId, providerId) {
2764
+ const prefix = `${pluginId}:`;
2765
+ return providerId.startsWith(prefix) ? providerId : `${prefix}${providerId}`;
2766
+ }
2767
+ function createSystemExtensionServiceProviderKey(service, providerId) {
2768
+ return `${service}:${providerId}`;
2769
+ }
2770
+ function normalizeLanguageServiceCapabilities(value) {
2771
+ if (!isRecord(value)) {
2772
+ return {};
2773
+ }
2774
+ return {
2775
+ diagnostics: value.diagnostics === true,
2776
+ completion: value.completion === true,
2777
+ hover: value.hover === true,
2778
+ definition: value.definition === true,
2779
+ codeActions: value.codeActions === true,
2780
+ };
2781
+ }
2782
+ function languageServiceRuntimeFromDeclaration(runtime) {
2783
+ switch (runtime) {
2784
+ case "desktop":
2785
+ return "native";
2786
+ case "browserWorker":
2787
+ return "worker";
2788
+ case "workspace":
2789
+ default:
2790
+ return "in-process";
2791
+ }
2792
+ }
2793
+ function runtimeMatchesHost(runtime, hostId) {
2794
+ if (runtime === hostId) {
2795
+ return true;
2796
+ }
2797
+ if (hostId === "renderer") {
2798
+ return ["renderer", "browser", "pwa", "web"].includes(runtime);
2799
+ }
2800
+ if (isSidecarHost(hostId)) {
2801
+ return [
2802
+ "desktop",
2803
+ "electron",
2804
+ "electron-sidecar",
2805
+ "electron-plugin-sidecar",
2806
+ "plugin-sidecar",
2807
+ ].includes(runtime);
2808
+ }
2809
+ return false;
2810
+ }
2811
+ function isSidecarHost(hostId) {
2812
+ return [
2813
+ "electron-plugin-sidecar",
2814
+ "plugin-sidecar",
2815
+ "electron-sidecar",
2816
+ ].includes(hostId);
2817
+ }
2818
+ function hostPlatformMatchesDependencyPlatform(host, platform) {
2819
+ switch (host) {
2820
+ case "electron-sidecar":
2821
+ return platform === "electron-sidecar";
2822
+ case "electron-renderer":
2823
+ return platform === "electron-renderer" || platform === "web";
2824
+ case "workspace":
2825
+ default:
2826
+ return platform === "web" || platform === "electron-renderer";
2827
+ }
2828
+ }
2829
+ function getPluginAssetUrlMode(url) {
2830
+ if (url.startsWith(`${ELECTRON_PLUGIN_ASSET_SCHEME}://`)) {
2831
+ return "electron";
2832
+ }
2833
+ try {
2834
+ const parsed = new URL(url, "http://lapis.local");
2835
+ if (parsed.pathname.startsWith(`${WEB_PLUGIN_ASSET_ROUTE_PREFIX}/`)) {
2836
+ return "web";
2837
+ }
2838
+ }
2839
+ catch {
2840
+ // Fall through to custom for opaque or host-specific URL shapes.
2841
+ }
2842
+ return "custom";
2843
+ }
2844
+ function getBareDependencySpecifiersFromSources(moduleSources) {
2845
+ return [
2846
+ ...new Set([...moduleSources.values()].flatMap((source) => scanBarePluginDependencySpecifiers(source))),
2847
+ ].sort();
2848
+ }