@markii/host 0.0.1 → 0.17.0-next.2

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 (192) hide show
  1. package/README.md +18 -2
  2. package/dist/browser-worker.js +639 -0
  3. package/dist/browser.d.ts +57 -0
  4. package/dist/browser.js +167 -0
  5. package/dist/conformance/host/01-plain-open/expected.json +5 -0
  6. package/dist/conformance/host/01-plain-open/note.mk.md +8 -0
  7. package/dist/conformance/host/01-plain-open/scenario.json +6 -0
  8. package/dist/conformance/host/02-run-manual-grant-accepted/expected.json +16 -0
  9. package/dist/conformance/host/02-run-manual-grant-accepted/note.mk.md +5 -0
  10. package/dist/conformance/host/02-run-manual-grant-accepted/scenario.json +14 -0
  11. package/dist/conformance/host/03-run-manual-grant-refused/expected.json +18 -0
  12. package/dist/conformance/host/03-run-manual-grant-refused/note.mk.md +5 -0
  13. package/dist/conformance/host/03-run-manual-grant-refused/scenario.json +14 -0
  14. package/dist/conformance/host/04-run-auto-write-denied/expected.json +16 -0
  15. package/dist/conformance/host/04-run-auto-write-denied/note.mk.md +10 -0
  16. package/dist/conformance/host/04-run-auto-write-denied/scenario.json +13 -0
  17. package/dist/conformance/host/05-export-html-pack-directive/expected.json +14 -0
  18. package/dist/conformance/host/05-export-html-pack-directive/note.mk.md +8 -0
  19. package/dist/conformance/host/05-export-html-pack-directive/scenario.json +6 -0
  20. package/dist/conformance/host/06-install-pack-then-render/expected.json +18 -0
  21. package/dist/conformance/host/06-install-pack-then-render/files/ana-1.0.0/pack.json +13 -0
  22. package/dist/conformance/host/06-install-pack-then-render/files/ana-1.0.0/webview.js +1 -0
  23. package/dist/conformance/host/06-install-pack-then-render/note.mk.md +5 -0
  24. package/dist/conformance/host/06-install-pack-then-render/scenario.json +15 -0
  25. package/dist/conformance/host/07-complete-in-directive/expected.json +14 -0
  26. package/dist/conformance/host/07-complete-in-directive/note.mk.md +3 -0
  27. package/dist/conformance/host/07-complete-in-directive/scenario.json +6 -0
  28. package/dist/conformance/host/08-hover-standard-component/expected.json +5 -0
  29. package/dist/conformance/host/08-hover-standard-component/note.mk.md +5 -0
  30. package/dist/conformance/host/08-hover-standard-component/scenario.json +6 -0
  31. package/dist/conformance/host/09-insert-skeleton/expected.json +5 -0
  32. package/dist/conformance/host/09-insert-skeleton/note.mk.md +4 -0
  33. package/dist/conformance/host/09-insert-skeleton/scenario.json +6 -0
  34. package/dist/conformance/host/10-scripts-disabled/expected.json +11 -0
  35. package/dist/conformance/host/10-scripts-disabled/note.mk.md +5 -0
  36. package/dist/conformance/host/10-scripts-disabled/scenario.json +9 -0
  37. package/dist/conformance/host/11-complete-closing-fence/expected.json +5 -0
  38. package/dist/conformance/host/11-complete-closing-fence/note.mk.md +5 -0
  39. package/dist/conformance/host/11-complete-closing-fence/scenario.json +12 -0
  40. package/dist/conformance/host/12-complete-nested-container/expected.json +5 -0
  41. package/dist/conformance/host/12-complete-nested-container/note.mk.md +5 -0
  42. package/dist/conformance/host/12-complete-nested-container/scenario.json +12 -0
  43. package/dist/conformance/host/13-install-pack-declined/expected.json +12 -0
  44. package/dist/conformance/host/13-install-pack-declined/files/ana-1.0.0/pack.json +13 -0
  45. package/dist/conformance/host/13-install-pack-declined/files/ana-1.0.0/webview.js +1 -0
  46. package/dist/conformance/host/13-install-pack-declined/note.mk.md +5 -0
  47. package/dist/conformance/host/13-install-pack-declined/scenario.json +10 -0
  48. package/dist/conformance/host/14-remove-pack/expected.json +14 -0
  49. package/dist/conformance/host/14-remove-pack/files/ana-1.0.0/pack.json +13 -0
  50. package/dist/conformance/host/14-remove-pack/files/ana-1.0.0/webview.js +1 -0
  51. package/dist/conformance/host/14-remove-pack/note.mk.md +5 -0
  52. package/dist/conformance/host/14-remove-pack/scenario.json +11 -0
  53. package/dist/conformance/host/15-load-packs-engine-views/expected.json +19 -0
  54. package/dist/conformance/host/15-load-packs-engine-views/files/duo-1.0.0/pack.json +9 -0
  55. package/dist/conformance/host/15-load-packs-engine-views/files/duo-1.0.0/webview.js +1 -0
  56. package/dist/conformance/host/15-load-packs-engine-views/note.mk.md +9 -0
  57. package/dist/conformance/host/15-load-packs-engine-views/scenario.json +10 -0
  58. package/dist/conformance/host/16-pack-trust/expected.json +37 -0
  59. package/dist/conformance/host/16-pack-trust/files/ana-1.0.0/pack.json +12 -0
  60. package/dist/conformance/host/16-pack-trust/files/ana-1.0.0/webview.js +1 -0
  61. package/dist/conformance/host/16-pack-trust/files/sneaky-1.0.0/pack.json +12 -0
  62. package/dist/conformance/host/16-pack-trust/files/sneaky-1.0.0/webview.js +1 -0
  63. package/dist/conformance/host/16-pack-trust/note.mk.md +5 -0
  64. package/dist/conformance/host/16-pack-trust/scenario.json +26 -0
  65. package/dist/conformance/host/README.md +221 -0
  66. package/dist/conformance.d.ts +2 -0
  67. package/dist/conformance.js +5 -0
  68. package/dist/diagnostics/render-diagnostics.d.ts +41 -0
  69. package/dist/diagnostics/render-diagnostics.js +46 -0
  70. package/dist/export/cascade.d.ts +98 -0
  71. package/dist/export/cascade.js +159 -0
  72. package/dist/export/export-zip.d.ts +19 -0
  73. package/dist/export/export-zip.js +38 -0
  74. package/dist/export/image-embed.d.ts +126 -0
  75. package/dist/export/image-embed.js +274 -0
  76. package/dist/export/note-export.d.ts +332 -0
  77. package/dist/export/note-export.js +366 -0
  78. package/dist/export/note-links.d.ts +83 -0
  79. package/dist/export/note-links.js +241 -0
  80. package/dist/glue.wasm +0 -0
  81. package/dist/host/adapter.d.ts +223 -0
  82. package/dist/host/adapter.js +9 -0
  83. package/dist/host/cascade-export-behavior.d.ts +120 -0
  84. package/dist/host/cascade-export-behavior.js +151 -0
  85. package/dist/host/conformance-runner.d.ts +156 -0
  86. package/dist/host/conformance-runner.js +634 -0
  87. package/dist/host/create-host.d.ts +253 -0
  88. package/dist/host/create-host.js +329 -0
  89. package/dist/host/editor-behavior.d.ts +157 -0
  90. package/dist/host/editor-behavior.js +227 -0
  91. package/dist/host/export/md-plain.d.ts +15 -0
  92. package/dist/host/export/md-plain.js +125 -0
  93. package/dist/host/export-behavior.d.ts +113 -0
  94. package/dist/host/export-behavior.js +257 -0
  95. package/dist/host/labels.d.ts +24 -0
  96. package/dist/host/labels.js +32 -0
  97. package/dist/host/pack-archive.d.ts +30 -0
  98. package/dist/host/pack-archive.js +68 -0
  99. package/dist/host/pack-install.d.ts +88 -0
  100. package/dist/host/pack-install.js +189 -0
  101. package/dist/host/pack-load.d.ts +72 -0
  102. package/dist/host/pack-load.js +57 -0
  103. package/dist/host/pack-remove.d.ts +21 -0
  104. package/dist/host/pack-remove.js +65 -0
  105. package/dist/host/pack-trust-behavior.d.ts +79 -0
  106. package/dist/host/pack-trust-behavior.js +262 -0
  107. package/dist/host/refresh-interval.d.ts +39 -0
  108. package/dist/host/refresh-interval.js +61 -0
  109. package/dist/host/run-behavior.d.ts +89 -0
  110. package/dist/host/run-behavior.js +168 -0
  111. package/dist/host/run-diagnostics.d.ts +26 -0
  112. package/dist/host/run-diagnostics.js +43 -0
  113. package/dist/host/script-execution.d.ts +72 -0
  114. package/dist/host/script-execution.js +68 -0
  115. package/dist/host/terminal-packs.d.ts +39 -0
  116. package/dist/host/terminal-packs.js +51 -0
  117. package/dist/html-engine.d.ts +2 -0
  118. package/dist/html-engine.js +29 -0
  119. package/dist/index.d.ts +95 -0
  120. package/dist/index.js +70 -0
  121. package/dist/insert/component-catalog.d.ts +23 -0
  122. package/dist/insert/component-catalog.js +96 -0
  123. package/dist/node-fs.d.ts +14 -0
  124. package/dist/node-fs.js +70 -0
  125. package/dist/node.d.ts +5 -0
  126. package/dist/node.js +13 -0
  127. package/dist/package.json +3 -0
  128. package/dist/packs/discover.d.ts +119 -0
  129. package/dist/packs/discover.js +266 -0
  130. package/dist/packs/pack-diagnostics.d.ts +81 -0
  131. package/dist/packs/pack-diagnostics.js +105 -0
  132. package/dist/packs/pack-paths.d.ts +53 -0
  133. package/dist/packs/pack-paths.js +106 -0
  134. package/dist/packs/pack-render-registry.d.ts +83 -0
  135. package/dist/packs/pack-render-registry.js +182 -0
  136. package/dist/packs/pack-scripts.d.ts +20 -0
  137. package/dist/packs/pack-scripts.js +96 -0
  138. package/dist/packs/pack-trust.d.ts +57 -0
  139. package/dist/packs/pack-trust.js +111 -0
  140. package/dist/packs/prebuilt.d.ts +49 -0
  141. package/dist/packs/prebuilt.js +97 -0
  142. package/dist/path-util.d.ts +24 -0
  143. package/dist/path-util.js +82 -0
  144. package/dist/run/browser-isolate.d.ts +45 -0
  145. package/dist/run/browser-isolate.js +81 -0
  146. package/dist/run/bundle-document.d.ts +29 -0
  147. package/dist/run/bundle-document.js +40 -0
  148. package/dist/run/bundle-run.d.ts +188 -0
  149. package/dist/run/bundle-run.js +232 -0
  150. package/dist/run/grant-flow.d.ts +232 -0
  151. package/dist/run/grant-flow.js +390 -0
  152. package/dist/run/ip-address.d.ts +41 -0
  153. package/dist/run/ip-address.js +255 -0
  154. package/dist/run/isolate.d.ts +57 -0
  155. package/dist/run/isolate.js +22 -0
  156. package/dist/run/lua-resolver.d.ts +21 -0
  157. package/dist/run/lua-resolver.js +63 -0
  158. package/dist/run/net-bridge-worker.d.ts +26 -0
  159. package/dist/run/net-bridge-worker.js +66 -0
  160. package/dist/run/net-bridge.d.ts +56 -0
  161. package/dist/run/net-bridge.js +59 -0
  162. package/dist/run/net-pinning.d.ts +82 -0
  163. package/dist/run/net-pinning.js +137 -0
  164. package/dist/run/net-provider.d.ts +70 -0
  165. package/dist/run/net-provider.js +289 -0
  166. package/dist/run/node-isolate.d.ts +30 -0
  167. package/dist/run/node-isolate.js +117 -0
  168. package/dist/run/run-flow.d.ts +187 -0
  169. package/dist/run/run-flow.js +323 -0
  170. package/dist/run/run-host.d.ts +107 -0
  171. package/dist/run/run-host.js +241 -0
  172. package/dist/run/run-job.d.ts +130 -0
  173. package/dist/run/run-job.js +279 -0
  174. package/dist/run/run-progress.d.ts +55 -0
  175. package/dist/run/run-progress.js +42 -0
  176. package/dist/run/run-trace.d.ts +44 -0
  177. package/dist/run/run-trace.js +36 -0
  178. package/dist/run/script-requirements.d.ts +98 -0
  179. package/dist/run/script-requirements.js +212 -0
  180. package/dist/run/snapshot-storage.d.ts +25 -0
  181. package/dist/run/snapshot-storage.js +31 -0
  182. package/dist/run/stale-values.d.ts +25 -0
  183. package/dist/run/stale-values.js +32 -0
  184. package/dist/run/worker-entry-browser.d.ts +42 -0
  185. package/dist/run/worker-entry-browser.js +77 -0
  186. package/dist/run/worker-entry.d.ts +1 -0
  187. package/dist/run/worker-entry.js +96 -0
  188. package/dist/values-failure.d.ts +17 -0
  189. package/dist/values-failure.js +1 -0
  190. package/dist/values-merge.d.ts +39 -0
  191. package/dist/values-merge.js +33 -0
  192. package/package.json +77 -4
@@ -0,0 +1,70 @@
1
+ /**
2
+ * The Node-backed implementations of the file-system seams the
3
+ * environment-free half of this package injects: pack discovery's file
4
+ * reader and directory lister, the pack-scripts reader, and the `.mkp`
5
+ * extraction filesystem. Reached only through `@markii/host/node`; nothing
6
+ * in the main entry imports this module.
7
+ */
8
+ import { mkdir, readFile, readdir, rm, writeFile } from 'node:fs/promises';
9
+ /** The real, Node-backed `PackDirectoryLister`. Never rejects: an unreadable, missing or non-directory path resolves `[]`. */
10
+ export function createNodeDirectoryLister() {
11
+ return async (absoluteDir) => {
12
+ try {
13
+ const entries = await readdir(absoluteDir, { withFileTypes: true });
14
+ return entries.map((entry) => ({
15
+ name: entry.name,
16
+ isDirectory: entry.isDirectory(),
17
+ }));
18
+ }
19
+ catch {
20
+ return [];
21
+ }
22
+ };
23
+ }
24
+ /** The real, Node-backed `PackFileReader`. Never rejects: an unreadable or missing path resolves `undefined`. */
25
+ export function createNodeFileReader() {
26
+ return async (absolutePath) => {
27
+ try {
28
+ return await readFile(absolutePath, 'utf8');
29
+ }
30
+ catch {
31
+ return undefined;
32
+ }
33
+ };
34
+ }
35
+ /** The real, Node-backed `PackScriptsReader`. */
36
+ export function createNodeReader() {
37
+ const listDirectory = createNodeDirectoryLister();
38
+ const readText = createNodeFileReader();
39
+ return {
40
+ readDirectory: (absoluteDir) => listDirectory(absoluteDir),
41
+ readFile: (absolutePath) => readText(absolutePath),
42
+ };
43
+ }
44
+ /** The real, Node-backed `ArchiveExtractFs`. */
45
+ export function createNodeArchiveExtractFs() {
46
+ return {
47
+ removeDirectory: async (absolutePath) => {
48
+ try {
49
+ await rm(absolutePath, { recursive: true, force: true });
50
+ }
51
+ catch {
52
+ // Quiet: the write that follows fails loudly on its own if the
53
+ // path is genuinely unusable.
54
+ }
55
+ },
56
+ makeDirectory: async (absolutePath) => {
57
+ await mkdir(absolutePath, { recursive: true });
58
+ },
59
+ writeFile: async (absolutePath, bytes) => {
60
+ await writeFile(absolutePath, bytes);
61
+ },
62
+ };
63
+ }
64
+ /** Pack discovery's reads over the real file system. */
65
+ export function createNodePackDiscoveryIo() {
66
+ return {
67
+ readFile: createNodeFileReader(),
68
+ listDirectory: createNodeDirectoryLister(),
69
+ };
70
+ }
package/dist/node.d.ts ADDED
@@ -0,0 +1,5 @@
1
+ export type { SpawnRunOptions } from './run/run-host.js';
2
+ export { defaultWorkerPath, spawnRunInThread, workerThreadIsolate, } from './run/node-isolate.js';
3
+ export { createNetProvider } from './run/net-provider.js';
4
+ export { createNodeArchiveExtractFs, createNodeDirectoryLister, createNodeFileReader, createNodePackDiscoveryIo, createNodeReader as createNodePackScriptsReader, } from './node-fs.js';
5
+ export { createHtmlExportEngine } from './html-engine.js';
package/dist/node.js ADDED
@@ -0,0 +1,13 @@
1
+ // @markii/host/node: the half of this package that needs Node, or that
2
+ // carries a heavy engine dependency the main entry must not.
3
+ //
4
+ // The main entry (`./index.ts`) is environment-neutral. This entry adds:
5
+ // - the worker_threads isolate and the pinned network provider (`node:*`);
6
+ // - the Node-backed file-system seams the neutral code takes by injection;
7
+ // - the `@markii/html`-backed static export engine;
8
+ //
9
+ // A Node host imports from both entries. No browser bundle imports this one.
10
+ export { defaultWorkerPath, spawnRunInThread, workerThreadIsolate, } from './run/node-isolate.js';
11
+ export { createNetProvider } from './run/net-provider.js';
12
+ export { createNodeArchiveExtractFs, createNodeDirectoryLister, createNodeFileReader, createNodePackDiscoveryIo, createNodeReader as createNodePackScriptsReader, } from './node-fs.js';
13
+ export { createHtmlExportEngine } from './html-engine.js';
@@ -0,0 +1,3 @@
1
+ {
2
+ "type": "module"
3
+ }
@@ -0,0 +1,119 @@
1
+ import type { PackManifest } from '@markii/pack';
2
+ import type { UnavailableComponent } from '@markii/stdlib';
3
+ /**
4
+ * What a valid pack that declares nothing for this host's engine carries on
5
+ * its skipped entry: its namespace, and every component it declares with
6
+ * the engines each one does ship a view for, so a note that uses one can say
7
+ * "no view here" rather than "unknown component".
8
+ */
9
+ export interface EngineUnsupportedPack {
10
+ readonly packName: string;
11
+ readonly components: readonly UnavailableComponent[];
12
+ }
13
+ /**
14
+ * Every component a pack declares, as `{ directive, pack, engines }` with
15
+ * the engine ids sorted. The shape a host hands a renderer's
16
+ * `unavailableComponents` option after filtering.
17
+ */
18
+ export declare function declaredPackComponents(manifest: Pick<PackManifest, 'name' | 'components' | 'engine'>): UnavailableComponent[];
19
+ /** The components, from `declared`, that ship no view for `engine`. */
20
+ export declare function componentsWithoutEngineView(declared: readonly UnavailableComponent[], engine: string): UnavailableComponent[];
21
+ /** Reads a file's UTF-8 text, or resolves `undefined` if it does not exist / cannot be read. Never rejects for an ordinary "not found" — injected so this module needs no real filesystem to test. */
22
+ export type PackFileReader = (absolutePath: string) => Promise<string | undefined>;
23
+ /** Lists one directory's immediate entries, or `[]` if it does not exist / cannot be read / is not a directory. Never rejects — injected (like `PackFileReader`) so the one-level parent-folder scan below is testable without real disk. */
24
+ export type PackDirectoryLister = (absoluteDir: string) => Promise<ReadonlyArray<{
25
+ name: string;
26
+ isDirectory: boolean;
27
+ }>>;
28
+ /** One successfully discovered, validated pack. */
29
+ export interface DiscoveredPack {
30
+ /** The configured folder this pack was discovered in (absolute path, as given). */
31
+ readonly folder: string;
32
+ readonly manifest: PackManifest;
33
+ /** Local component name -> absolute path to its declared source file (`manifest.components[name]`, resolved against `folder`). Informational: what a host consumes them for (bundling, `import()`, or locating the pack's prebuilt registration script) is that host's own concern. */
34
+ readonly componentPaths: Readonly<Record<string, string>>;
35
+ /** Engine id -> (local component name -> absolute source path), for every engine any component declares a source for. `componentPaths` is the `react` slice of this. A terminal build (`ansi`) reads its slice here. */
36
+ readonly componentPathsByEngine?: Readonly<Record<string, Readonly<Record<string, string>>>>;
37
+ /** Absolute path to this pack's `scripts/` directory (docs/packs.md: shared Lua modules travel here). The directory need not actually exist — a pack with no shared Lua simply has nothing under it. */
38
+ readonly scriptsDir: string;
39
+ /**
40
+ * Absolute path to this pack's prebuilt registration script (`webview.js`,
41
+ * sibling to `pack.json`) — a HOST-SPECIFIC CONVENTION layered on top of
42
+ * the neutral pack contract (`@markii/pack`'s `PackManifest.components`
43
+ * only names pack-relative SOURCE paths for a bundler, per
44
+ * docs/packs.md; it says nothing about a prebuilt browser artifact).
45
+ * Named `scriptPath`, not `webviewScriptPath`: this field is read by
46
+ * every host that loads a pack's compiled registration script, not only
47
+ * a host with an actual webview (e.g. an Obsidian plugin evaluates it
48
+ * in-process — see that host's `pack-runtime.ts`), and this package has
49
+ * no webview of its own. The on-disk filename convention (`webview.js`)
50
+ * is unchanged — a script authored for one host still works for another,
51
+ * since the compiled-artifact contract itself
52
+ * (`window.__markiiRegisterPack`/lazy `window.__markiiReact`) is
53
+ * host-neutral (see `./pack-build.ts`'s top doc comment). The file need
54
+ * not actually exist — a pack with no such script simply never
55
+ * contributes anything to a host's registry, and every directive under
56
+ * its namespace falls through to the standard unknown-component
57
+ * fallback (the same engine-gating posture `@markii/react`'s `loadPack`
58
+ * already takes for an unsupported `engine`).
59
+ */
60
+ readonly scriptPath: string;
61
+ /**
62
+ * Absolute path to this pack's emitted stylesheet, when one exists — a
63
+ * SIBLING of `scriptPath`'s compiled output (`./pack-build.ts`, same
64
+ * cache directory, same content-hash base name, `.css` in place of
65
+ * `.js`). `undefined` here always: discovery itself never scans for one,
66
+ * only a host's own pack-context composition knows whether a build
67
+ * actually produced a stylesheet — it overrides this field on the
68
+ * `DiscoveredPack` copy it uses once `buildPackRegistrationScript`'s
69
+ * outcome carries a `stylesheetPath`. A pack that ships its own prebuilt
70
+ * `webview.js` (no compile step at all) has no established
71
+ * sibling-stylesheet convention and never gets one here either.
72
+ */
73
+ readonly stylesheetPath?: string;
74
+ }
75
+ /** One folder that did not produce a usable pack, and why — developer-facing only, never shown as page content. */
76
+ export interface SkippedPackFolder {
77
+ readonly folder: string;
78
+ readonly reason: string;
79
+ /** Set when the pack is valid but declares nothing for this host's renderer engine (an expected state, not a failure). `packName` is its namespace. */
80
+ readonly engineUnsupported?: EngineUnsupportedPack;
81
+ }
82
+ export interface DiscoverPacksResult {
83
+ /** Every pack that validated AND whose namespace does not collide with another discovered pack. Empty when every configured folder was invalid or every valid pack collided. */
84
+ readonly packs: readonly DiscoveredPack[];
85
+ /** Namespaces that appeared more than once across the configured folders (docs/packs.md: "Installing two packs with the same namespace is rejected at install time") — every pack sharing a colliding namespace is excluded from `packs`, not just the second one, so install stays all-or-nothing per namespace. */
86
+ readonly collisions: readonly string[];
87
+ /** Folders that produced no pack, with a short reason each (missing/unreadable `pack.json`, malformed JSON, failed manifest validation). */
88
+ readonly skipped: readonly SkippedPackFolder[];
89
+ }
90
+ /**
91
+ * Discovers and validates a pack in each of `folders`, in order, then
92
+ * removes (and reports via `collisions`) every pack whose namespace repeats
93
+ * across the set. Duplicate folder entries in `folders` collapse to one
94
+ * discovery attempt each (`Set`), so a repeated setting entry can never
95
+ * itself manufacture a spurious collision.
96
+ *
97
+ * `supportedEngine`, when given, additionally skips (rather than
98
+ * discovers) any pack whose manifest names a different `engine` — see
99
+ * `checkEngine`. Omitted (the default), a pack's engine is not checked
100
+ * here at all, matching this function's behavior before this parameter
101
+ * existed; a caller that only lists packs for a purpose engine-agnostic
102
+ * (e.g. `@markii/pack`'s own tooling) is unaffected.
103
+ *
104
+ * ONE-LEVEL PARENT-FOLDER SCAN: when a configured folder has no `pack.json`
105
+ * of its own (not "has one that fails validation" — see `ManifestAttempt`'s
106
+ * doc comment), its immediate subfolders are probed the same way, and each
107
+ * one that DOES have a valid `pack.json` counts as its own discovered pack
108
+ * (`folder` set to the child path). This lets a user configure one parent
109
+ * (`packs`) that holds several pack folders (`packs/pack1`, `packs/pack2`)
110
+ * instead of listing each one in the host's setting. Exactly one level
111
+ * deep, never recursive: a grandchild's `pack.json` is never found this
112
+ * way. A parent with neither its own manifest nor any child manifest is
113
+ * skipped exactly as before ("no readable pack.json"), and a child that
114
+ * DOES have a `pack.json` but fails validation is skipped individually, by
115
+ * its own (child) folder path.
116
+ */
117
+ export declare function discoverPacks(folders: readonly string[], readFile: PackFileReader, listDirectory: PackDirectoryLister, supportedEngine?: string): Promise<DiscoverPacksResult>;
118
+ /** The discovered packs' namespaces — what `resolveUses` (`@markii/pack`) checks a note's `uses:` declaration against. */
119
+ export declare function installedNamespaces(packs: readonly DiscoveredPack[]): string[];
@@ -0,0 +1,266 @@
1
+ /**
2
+ * Node-side, host-UI-free discovery of installed component packs
3
+ * (docs/packs.md, GitHub issue #3 slice 5). Given the folders named by a
4
+ * host's own pack-folder setting (VS Code's `markii.packs`, this plugin's
5
+ * device-local pack-folder list, or any future host's equivalent), reads
6
+ * each folder's `pack.json`, validates it with `@markii/pack`'s
7
+ * `parsePackManifest`, and returns a model of what was found: the manifest
8
+ * plus absolute, host-resolved paths to the pack-relative locations a
9
+ * later step needs (each component's declared source, and the pack's
10
+ * `scripts/` directory for shared Lua modules).
11
+ *
12
+ * Lives in `@markii/host` (not any one app) because it is pure logic over
13
+ * paths, manifests, and strings — no `vscode`, no `obsidian`, no React, and
14
+ * no knowledge of which host is calling it. `apps/vscode/src/packs/`'s
15
+ * `apps/obsidian/src/packs/` copies of this file (pre-dating this hoist)
16
+ * differed only in the field names below (`scriptPath`/`stylesheetPath` —
17
+ * see those fields' own doc comments for why the neutral names won).
18
+ *
19
+ * Filesystem access is injected (`readFile`) rather than imported directly,
20
+ * so this whole module is testable with plain in-memory fakes.
21
+ *
22
+ * Cleanliness (AGENTS.md): a folder with no `pack.json`, malformed JSON, or
23
+ * a manifest that fails validation is QUIETLY skipped — never a thrown
24
+ * error or a dump in the UI. A caller that wants to surface *why* a pack
25
+ * failed to load can read `DiscoverPacksResult.skipped`, which is optional,
26
+ * developer-facing detail (a host's own diagnostics surface), never
27
+ * something shown as page content.
28
+ */
29
+ import { joinPath } from '../path-util.js';
30
+ import { parsePackManifest } from '@markii/pack';
31
+ import { composeDirectiveName, detectNamespaceCollisions, packComponents, packComponentsForEngine, packDeclaredEngines, packSupportsEngine, } from '@markii/pack';
32
+ /**
33
+ * Every component a pack declares, as `{ directive, pack, engines }` with
34
+ * the engine ids sorted. The shape a host hands a renderer's
35
+ * `unavailableComponents` option after filtering.
36
+ */
37
+ export function declaredPackComponents(manifest) {
38
+ const declared = [];
39
+ for (const listing of packComponents(manifest)) {
40
+ const composed = composeDirectiveName(manifest.name, listing.localName);
41
+ if (!composed.ok)
42
+ continue;
43
+ declared.push({
44
+ directive: composed.name,
45
+ pack: manifest.name,
46
+ engines: Object.keys(listing.sources).sort(),
47
+ });
48
+ }
49
+ return declared;
50
+ }
51
+ /** The components, from `declared`, that ship no view for `engine`. */
52
+ export function componentsWithoutEngineView(declared, engine) {
53
+ return declared.filter((component) => !component.engines.includes(engine));
54
+ }
55
+ /** A sane upper bound on how many immediate children of one configured folder the one-level parent-folder scan (`discoverPacks`) will probe for a `pack.json` — defense in depth against a pathological folder (thousands of entries, a symlink farm) turning "load the installed packs" into an unbounded scan. Matches the spirit of `./pack-scripts.ts`'s `MAX_SCRIPT_FILES_PER_PACK`. */
56
+ const MAX_CHILD_FOLDERS_PER_PARENT = 200;
57
+ /**
58
+ * The engine whose source paths fill `DiscoveredPack.componentPaths`. A
59
+ * data-field name from the pack contract, not a renderer dependency.
60
+ */
61
+ const COMPONENT_PATHS_ENGINE = 'react';
62
+ /** `folder`/`pack.json`, joined with the host path separator. */
63
+ function manifestPathFor(folder) {
64
+ return joinPath(folder, 'pack.json');
65
+ }
66
+ async function tryLoadPackAt(folder, readFile) {
67
+ const manifestPath = manifestPathFor(folder);
68
+ let text;
69
+ try {
70
+ text = await readFile(manifestPath);
71
+ }
72
+ catch {
73
+ text = undefined;
74
+ }
75
+ if (text === undefined) {
76
+ return { kind: 'missing' };
77
+ }
78
+ const result = parsePackManifest(text);
79
+ if (!result.ok) {
80
+ return {
81
+ kind: 'invalid',
82
+ reason: `invalid pack.json (${result.errors.join('; ')})`,
83
+ };
84
+ }
85
+ // Read the manifest's components map only through @markii/pack's
86
+ // packComponents() — the one accessor that normalizes both the string
87
+ // shorthand and the object form (source/description/kind), so this
88
+ // module never re-implements that walk or its Object.hasOwn guarding.
89
+ const componentPaths = {};
90
+ for (const listing of packComponentsForEngine(result.manifest, COMPONENT_PATHS_ENGINE)) {
91
+ componentPaths[listing.localName] = joinPath(folder, listing.source);
92
+ }
93
+ const componentPathsByEngine = {};
94
+ for (const listing of packComponents(result.manifest)) {
95
+ for (const [engine, source] of Object.entries(listing.sources)) {
96
+ const slice = (componentPathsByEngine[engine] ??= {});
97
+ slice[listing.localName] = joinPath(folder, source);
98
+ }
99
+ }
100
+ return {
101
+ kind: 'found',
102
+ pack: {
103
+ folder,
104
+ manifest: result.manifest,
105
+ componentPaths,
106
+ componentPathsByEngine,
107
+ scriptsDir: joinPath(folder, 'scripts'),
108
+ scriptPath: joinPath(folder, 'webview.js'),
109
+ },
110
+ };
111
+ }
112
+ /**
113
+ * Discovers and validates a pack in each of `folders`, in order, then
114
+ * removes (and reports via `collisions`) every pack whose namespace repeats
115
+ * across the set. Duplicate folder entries in `folders` collapse to one
116
+ * discovery attempt each (`Set`), so a repeated setting entry can never
117
+ * itself manufacture a spurious collision.
118
+ *
119
+ * ONE-LEVEL PARENT-FOLDER SCAN: when a configured folder has no `pack.json`
120
+ * of its own (not "has one that fails validation" — see `ManifestAttempt`'s
121
+ * doc comment), its immediate subfolders are probed the same way, and each
122
+ * one that DOES have a valid `pack.json` counts as its own discovered pack
123
+ * (`folder` set to the child path). This lets a user configure one parent
124
+ * (`packs`) that holds several pack folders (`packs/pack1`, `packs/pack2`)
125
+ * instead of listing each one in the host's setting. Exactly one level
126
+ * deep, never recursive: a grandchild's `pack.json` is never found this
127
+ * way. A parent with neither its own manifest nor any child manifest is
128
+ * skipped exactly as before ("no readable pack.json"), and a child that
129
+ * DOES have a `pack.json` but fails validation is skipped individually, by
130
+ * its own (child) folder path.
131
+ */
132
+ /**
133
+ * When a `ManifestAttempt` succeeded (`kind: 'found'`) but the manifest's
134
+ * `engine` does not match `supportedEngine` (batch 7 #46), downgrades it to
135
+ * a `SkippedPackFolder` instead of a `DiscoveredPack` — same effect
136
+ * `@markii/react`'s `loadPack` already has for an unsupported engine
137
+ * (nothing registered for that namespace, unknown-component fallback for
138
+ * every directive under it), but recorded here too so it also drops out of
139
+ * a host's Insert Component / completion catalog (`discoverConfiguredPacks`
140
+ * consumers) instead of offering a snippet for a component nothing can
141
+ * ever render, and so the skip reaches the SAME diagnostics line every
142
+ * other skip does — never silent.
143
+ */
144
+ function checkEngine(pack, supportedEngine) {
145
+ if (supportedEngine === undefined ||
146
+ packSupportsEngine(pack.manifest, supportedEngine)) {
147
+ return { kind: 'found', pack };
148
+ }
149
+ return {
150
+ kind: 'invalid',
151
+ reason: `pack engine "${packDeclaredEngines(pack.manifest)}" is not supported here (expected "${supportedEngine}"); its components fall back to the unknown-component box`,
152
+ engineUnsupported: {
153
+ packName: pack.manifest.name,
154
+ components: declaredPackComponents(pack.manifest),
155
+ },
156
+ };
157
+ }
158
+ /**
159
+ * Discovers and validates a pack in each of `folders`, in order, then
160
+ * removes (and reports via `collisions`) every pack whose namespace repeats
161
+ * across the set. Duplicate folder entries in `folders` collapse to one
162
+ * discovery attempt each (`Set`), so a repeated setting entry can never
163
+ * itself manufacture a spurious collision.
164
+ *
165
+ * `supportedEngine`, when given, additionally skips (rather than
166
+ * discovers) any pack whose manifest names a different `engine` — see
167
+ * `checkEngine`. Omitted (the default), a pack's engine is not checked
168
+ * here at all, matching this function's behavior before this parameter
169
+ * existed; a caller that only lists packs for a purpose engine-agnostic
170
+ * (e.g. `@markii/pack`'s own tooling) is unaffected.
171
+ *
172
+ * ONE-LEVEL PARENT-FOLDER SCAN: when a configured folder has no `pack.json`
173
+ * of its own (not "has one that fails validation" — see `ManifestAttempt`'s
174
+ * doc comment), its immediate subfolders are probed the same way, and each
175
+ * one that DOES have a valid `pack.json` counts as its own discovered pack
176
+ * (`folder` set to the child path). This lets a user configure one parent
177
+ * (`packs`) that holds several pack folders (`packs/pack1`, `packs/pack2`)
178
+ * instead of listing each one in the host's setting. Exactly one level
179
+ * deep, never recursive: a grandchild's `pack.json` is never found this
180
+ * way. A parent with neither its own manifest nor any child manifest is
181
+ * skipped exactly as before ("no readable pack.json"), and a child that
182
+ * DOES have a `pack.json` but fails validation is skipped individually, by
183
+ * its own (child) folder path.
184
+ */
185
+ export async function discoverPacks(folders, readFile, listDirectory, supportedEngine) {
186
+ const uniqueFolders = [...new Set(folders)];
187
+ const found = [];
188
+ const skipped = [];
189
+ for (const folder of uniqueFolders) {
190
+ const rawAttempt = await tryLoadPackAt(folder, readFile);
191
+ const attempt = rawAttempt.kind === 'found'
192
+ ? checkEngine(rawAttempt.pack, supportedEngine)
193
+ : rawAttempt;
194
+ if (attempt.kind === 'found') {
195
+ found.push(attempt.pack);
196
+ continue;
197
+ }
198
+ if (attempt.kind === 'invalid') {
199
+ skipped.push({
200
+ folder,
201
+ reason: attempt.reason,
202
+ ...(attempt.engineUnsupported
203
+ ? { engineUnsupported: attempt.engineUnsupported }
204
+ : {}),
205
+ });
206
+ continue;
207
+ }
208
+ // 'missing': this folder itself has no pack.json — try its immediate
209
+ // children, one level only.
210
+ let entries = [];
211
+ try {
212
+ entries = await listDirectory(folder);
213
+ }
214
+ catch {
215
+ entries = [];
216
+ }
217
+ let anyChildManifest = false;
218
+ for (const entry of entries.slice(0, MAX_CHILD_FOLDERS_PER_PARENT)) {
219
+ if (!entry.isDirectory)
220
+ continue;
221
+ const childFolder = joinPath(folder, entry.name);
222
+ const rawChildAttempt = await tryLoadPackAt(childFolder, readFile);
223
+ const childAttempt = rawChildAttempt.kind === 'found'
224
+ ? checkEngine(rawChildAttempt.pack, supportedEngine)
225
+ : rawChildAttempt;
226
+ if (childAttempt.kind === 'found') {
227
+ found.push(childAttempt.pack);
228
+ anyChildManifest = true;
229
+ }
230
+ else if (childAttempt.kind === 'invalid') {
231
+ skipped.push({
232
+ folder: childFolder,
233
+ reason: childAttempt.reason,
234
+ ...(childAttempt.engineUnsupported
235
+ ? { engineUnsupported: childAttempt.engineUnsupported }
236
+ : {}),
237
+ });
238
+ anyChildManifest = true;
239
+ }
240
+ // 'missing' for a child: an ordinary non-pack subfolder, not worth
241
+ // reporting individually.
242
+ }
243
+ if (!anyChildManifest) {
244
+ skipped.push({ folder, reason: 'no readable pack.json' });
245
+ }
246
+ }
247
+ const collisionNames = new Set(detectNamespaceCollisions(found.map((pack) => pack.manifest.name)).map((collision) => collision.namespace));
248
+ const packs = found.filter((pack) => !collisionNames.has(pack.manifest.name));
249
+ for (const pack of found) {
250
+ if (collisionNames.has(pack.manifest.name)) {
251
+ skipped.push({
252
+ folder: pack.folder,
253
+ reason: `pack namespace "${pack.manifest.name}" collides with another configured pack and was not installed`,
254
+ });
255
+ }
256
+ }
257
+ return {
258
+ packs,
259
+ collisions: [...collisionNames],
260
+ skipped,
261
+ };
262
+ }
263
+ /** The discovered packs' namespaces — what `resolveUses` (`@markii/pack`) checks a note's `uses:` declaration against. */
264
+ export function installedNamespaces(packs) {
265
+ return packs.map((pack) => pack.manifest.name);
266
+ }
@@ -0,0 +1,81 @@
1
+ import type { PackManifest } from '@markii/pack';
2
+ /** The minimal shape of one loaded pack this module needs — a structural subset of `./discover.ts`'s `DiscoveredPack`. */
3
+ export interface PackDiagnosticsPack {
4
+ readonly manifest: Pick<PackManifest, 'name' | 'engine' | 'components'>;
5
+ }
6
+ /** The minimal shape of one skipped folder this module needs — a structural subset of `./discover.ts`'s `SkippedPackFolder`. */
7
+ export interface PackDiagnosticsSkippedFolder {
8
+ readonly folder: string;
9
+ readonly reason: string;
10
+ }
11
+ export interface PackDiagnosticsContext {
12
+ /** Every validated, non-colliding discovered pack. */
13
+ readonly packs: readonly PackDiagnosticsPack[];
14
+ /** The renderer engine whose components the "Loaded pack" line counts. Absent counts every declared component. */
15
+ readonly engine?: string;
16
+ /** Configured folders that produced no usable pack, and why. */
17
+ readonly skipped: readonly PackDiagnosticsSkippedFolder[];
18
+ /** Already-formatted lines, one per relative pack-folder-setting entry (host-specific wording — see this module's top doc comment). Defaults to none. */
19
+ readonly relativeEntryLines?: readonly string[];
20
+ /** Already-formatted informational lines, one per pack whose prebuilt webview.js shadows component sources on disk (host-specific wording, since each host names its own build command). Informational, never a failure: shipping both is a supported state. */
21
+ readonly prebuiltShadowLines?: readonly string[];
22
+ /** Pack CSS authoring warnings against every built pack's emitted stylesheet. Warnings only, developer-facing. */
23
+ readonly cssWarnings: readonly string[];
24
+ /** One line per malformed pack registration, dropped rather than installed (`./pack-render-registry.ts`'s `BuildRenderRegistryResult.invalidReasons`). Omitted (or empty) contributes nothing — a host that never validates registrations on this side (e.g. VS Code's webview validates them separately, in the browser) simply has none to report yet. */
25
+ readonly invalidRegistrationReasons?: readonly string[];
26
+ /** Namespaces shared by two or more registered packs (`./pack-render-registry.ts`'s `BuildRenderRegistryResult.collisions`). When non-empty, contributes one summary line. */
27
+ readonly registrationCollisions?: readonly string[];
28
+ /** Composed directive names two DIFFERENT packs both claimed (`./pack-render-registry.ts`'s `BuildRenderRegistryResult.duplicateComposedNames`) — the first pack keeps the name, and each entry here names both the pack that kept it and the pack whose component was skipped. Omitted (or empty) contributes nothing; this is expected to stay empty under ordinary pack composition (see that module's doc comment). */
29
+ readonly duplicateComposedNames?: readonly {
30
+ readonly composedName: string;
31
+ readonly keptPack: string;
32
+ readonly skippedPack: string;
33
+ }[];
34
+ /**
35
+ * One entry per validated, non-colliding pack whose declared engine
36
+ * `@markii/react`'s `loadPack` cannot run (`./pack-render-registry.ts`'s
37
+ * `BuildRenderRegistryResult.droppedEngines`, batch 7 #46). Omitted (or
38
+ * empty) contributes nothing — the ordinary case where every registered
39
+ * pack's engine is supported.
40
+ */
41
+ readonly droppedEngines?: readonly {
42
+ name: string;
43
+ engine: string;
44
+ }[];
45
+ }
46
+ /** The one line for a folder `discoverPacks` could not turn into a usable pack. */
47
+ export declare function skippedPackFolderLine(entry: PackDiagnosticsSkippedFolder): string;
48
+ /**
49
+ * The full set of diagnostic lines for one pack-loading outcome: loaded
50
+ * packs first (the confirmation that the setting is working at all), then
51
+ * every skipped folder with its reason, then relative-entry lines, then
52
+ * prebuilt-shadow lines, then pack CSS lint warnings, then any
53
+ * invalid-registration, namespace-collision, or duplicate-composed-name
54
+ * lines. Empty when nothing is configured at all — the caller decides
55
+ * whether an empty result is worth writing anything to its own
56
+ * diagnostics surface.
57
+ */
58
+ export declare function formatPackDiagnosticLines(context: PackDiagnosticsContext): string[];
59
+ /** How many configured folders failed to produce a usable pack — what a preview's quiet marker counts. */
60
+ export declare function skippedPackCount(context: {
61
+ readonly skipped: readonly PackDiagnosticsSkippedFolder[];
62
+ }): number;
63
+ /**
64
+ * One informational line for a pack whose prebuilt `webview.js` shadows
65
+ * component sources still present in the same folder — survey finding A9:
66
+ * `apps/vscode/src/packs/pack-diagnostics.ts` and
67
+ * `apps/obsidian/src/packs/pack-diagnostics.ts` each defined this exact
68
+ * sentence shape, differing only in the tail clause naming how a user
69
+ * would rebuild the prebuilt artifact from those sources (VS Code's own
70
+ * Export Pack command versus, from Obsidian, VS Code's Export Pack command
71
+ * by name, since Obsidian has no build command of its own).
72
+ *
73
+ * Never a failure: shipping both the built artifact and its sources is a
74
+ * supported distribution shape. `rebuildInstruction` is the one thing that
75
+ * differs per host — a short clause completing "Edits to them take effect
76
+ * only after you delete webview.js, or ...".
77
+ */
78
+ export declare function prebuiltShadowLine(pack: {
79
+ readonly name: string;
80
+ readonly folder: string;
81
+ }, rebuildInstruction: string): string;