@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,57 @@
1
+ /**
2
+ * The DEVICE-LOCAL pack trust list: which installed-pack namespaces this
3
+ * device authorizes to load (docs/packs.md). Every namespace named here
4
+ * has its pack code evaluated at host trust, so the list authorizes code
5
+ * execution and must live in device-local state (the adapter's
6
+ * `memento`), never in anything that travels with a vault, a workspace
7
+ * file, or a sync service.
8
+ *
9
+ * A pack folder can sit in the install root without being trusted: it
10
+ * arrived through a file sync, or a user copied it there by hand. Such a
11
+ * folder is never loaded until its namespace is added here, exactly like
12
+ * a freshly installed one, and the consent prompt is asked either way.
13
+ * The trust list, not the presence of a folder, decides whether code
14
+ * runs. `../host/pack-trust-behavior.ts` enforces that for every host
15
+ * that declares `trust: 'device-list'`.
16
+ *
17
+ * Pure and environment-free, so the shape and its hostile-input
18
+ * normalization are unit-testable with no host in sight.
19
+ */
20
+ /** The memento key this shape lives under. It is the key Obsidian has always stored this list under, so an existing device keeps its trust list. */
21
+ export declare const PACK_TRUST_STORAGE_KEY = "markii:installedPacks";
22
+ /** A sane upper bound on how many namespaces this list may hold : a real device installs a handful of packs; this only exists to bound a hostile or corrupt stored value. */
23
+ export declare const MAX_TRUSTED_PACKS = 200;
24
+ /** One namespace this device trusts, and the version it was installed at (the manifest's own `version` field, or `undefined` when the pack declares none). */
25
+ export interface PackTrustEntry {
26
+ readonly namespace: string;
27
+ readonly version?: string;
28
+ }
29
+ export interface PackTrustList {
30
+ readonly entries: readonly PackTrustEntry[];
31
+ }
32
+ export declare const DEFAULT_PACK_TRUST_LIST: PackTrustList;
33
+ /**
34
+ * Normalizes whatever `app.loadLocalStorage(PACK_TRUST_STORAGE_KEY)` handed
35
+ * back into a well-formed `PackTrustList` : hostile-shape-guarded the same
36
+ * way every other device-local shape in this plugin is, since this is also
37
+ * hand-editable, foreign-version-shaped data in principle. A malformed
38
+ * entry is dropped rather than failing the whole read; duplicates are
39
+ * removed by namespace, first occurrence wins; the list is bounded by
40
+ * `MAX_TRUSTED_PACKS`.
41
+ */
42
+ export declare function normalizePackTrustList(data: unknown): PackTrustList;
43
+ /** Whether `namespace` is trusted on this device. */
44
+ export declare function isPackTrusted(list: PackTrustList, namespace: string): boolean;
45
+ /**
46
+ * Adds (or replaces) `namespace`'s trust entry, recording `version` when
47
+ * given. Used both by a fresh install and by "Enable" for a pack folder
48
+ * that was already present on disk. Returns the same list unchanged (never
49
+ * a new object) when the entry is already present with the same version,
50
+ * so a caller can skip a write that would change nothing.
51
+ */
52
+ export declare function trustPack(list: PackTrustList, namespace: string, version?: string): PackTrustList;
53
+ /**
54
+ * Removes `namespace`'s trust entry. Returns the same list unchanged when
55
+ * it was not present, so a caller can skip a no-op write.
56
+ */
57
+ export declare function untrustPack(list: PackTrustList, namespace: string): PackTrustList;
@@ -0,0 +1,111 @@
1
+ /**
2
+ * The DEVICE-LOCAL pack trust list: which installed-pack namespaces this
3
+ * device authorizes to load (docs/packs.md). Every namespace named here
4
+ * has its pack code evaluated at host trust, so the list authorizes code
5
+ * execution and must live in device-local state (the adapter's
6
+ * `memento`), never in anything that travels with a vault, a workspace
7
+ * file, or a sync service.
8
+ *
9
+ * A pack folder can sit in the install root without being trusted: it
10
+ * arrived through a file sync, or a user copied it there by hand. Such a
11
+ * folder is never loaded until its namespace is added here, exactly like
12
+ * a freshly installed one, and the consent prompt is asked either way.
13
+ * The trust list, not the presence of a folder, decides whether code
14
+ * runs. `../host/pack-trust-behavior.ts` enforces that for every host
15
+ * that declares `trust: 'device-list'`.
16
+ *
17
+ * Pure and environment-free, so the shape and its hostile-input
18
+ * normalization are unit-testable with no host in sight.
19
+ */
20
+ /** The memento key this shape lives under. It is the key Obsidian has always stored this list under, so an existing device keeps its trust list. */
21
+ export const PACK_TRUST_STORAGE_KEY = 'markii:installedPacks';
22
+ /** A sane upper bound on how many namespaces this list may hold : a real device installs a handful of packs; this only exists to bound a hostile or corrupt stored value. */
23
+ export const MAX_TRUSTED_PACKS = 200;
24
+ export const DEFAULT_PACK_TRUST_LIST = { entries: [] };
25
+ function isPlainObject(value) {
26
+ return typeof value === 'object' && value !== null && !Array.isArray(value);
27
+ }
28
+ /** Normalizes one raw entry, or `undefined` when it does not have a usable `namespace`. A non-string, empty, or whitespace-only `namespace` is dropped rather than crashing the whole read; a non-string `version` is dropped (the entry survives, just without one). */
29
+ function normalizeEntry(raw) {
30
+ if (!isPlainObject(raw))
31
+ return undefined;
32
+ const namespace = raw.namespace;
33
+ if (typeof namespace !== 'string')
34
+ return undefined;
35
+ const trimmed = namespace.trim();
36
+ if (trimmed.length === 0)
37
+ return undefined;
38
+ const version = raw.version;
39
+ return typeof version === 'string' && version.trim().length > 0
40
+ ? { namespace: trimmed, version: version.trim() }
41
+ : { namespace: trimmed };
42
+ }
43
+ /**
44
+ * Normalizes whatever `app.loadLocalStorage(PACK_TRUST_STORAGE_KEY)` handed
45
+ * back into a well-formed `PackTrustList` : hostile-shape-guarded the same
46
+ * way every other device-local shape in this plugin is, since this is also
47
+ * hand-editable, foreign-version-shaped data in principle. A malformed
48
+ * entry is dropped rather than failing the whole read; duplicates are
49
+ * removed by namespace, first occurrence wins; the list is bounded by
50
+ * `MAX_TRUSTED_PACKS`.
51
+ */
52
+ export function normalizePackTrustList(data) {
53
+ if (!isPlainObject(data))
54
+ return { ...DEFAULT_PACK_TRUST_LIST };
55
+ const rawEntries = data.entries;
56
+ if (!Array.isArray(rawEntries))
57
+ return { ...DEFAULT_PACK_TRUST_LIST };
58
+ const seen = new Set();
59
+ const entries = [];
60
+ for (const rawEntry of rawEntries) {
61
+ if (entries.length >= MAX_TRUSTED_PACKS)
62
+ break;
63
+ const entry = normalizeEntry(rawEntry);
64
+ if (!entry)
65
+ continue;
66
+ if (seen.has(entry.namespace))
67
+ continue;
68
+ seen.add(entry.namespace);
69
+ entries.push(entry);
70
+ }
71
+ return { entries };
72
+ }
73
+ /** Whether `namespace` is trusted on this device. */
74
+ export function isPackTrusted(list, namespace) {
75
+ return list.entries.some((entry) => entry.namespace === namespace);
76
+ }
77
+ /**
78
+ * Adds (or replaces) `namespace`'s trust entry, recording `version` when
79
+ * given. Used both by a fresh install and by "Enable" for a pack folder
80
+ * that was already present on disk. Returns the same list unchanged (never
81
+ * a new object) when the entry is already present with the same version,
82
+ * so a caller can skip a write that would change nothing.
83
+ */
84
+ export function trustPack(list, namespace, version) {
85
+ const trimmed = namespace.trim();
86
+ if (trimmed.length === 0)
87
+ return list;
88
+ const existing = list.entries.find((entry) => entry.namespace === trimmed);
89
+ if (existing && existing.version === version)
90
+ return list;
91
+ const withoutExisting = list.entries.filter((entry) => entry.namespace !== trimmed);
92
+ const next = [
93
+ ...withoutExisting,
94
+ version !== undefined
95
+ ? { namespace: trimmed, version }
96
+ : { namespace: trimmed },
97
+ ];
98
+ return { entries: next.slice(0, MAX_TRUSTED_PACKS) };
99
+ }
100
+ /**
101
+ * Removes `namespace`'s trust entry. Returns the same list unchanged when
102
+ * it was not present, so a caller can skip a no-op write.
103
+ */
104
+ export function untrustPack(list, namespace) {
105
+ if (!list.entries.some((entry) => entry.namespace === namespace)) {
106
+ return list;
107
+ }
108
+ return {
109
+ entries: list.entries.filter((entry) => entry.namespace !== namespace),
110
+ };
111
+ }
@@ -0,0 +1,49 @@
1
+ import type { DiscoveredPack } from './discover.js';
2
+ export declare const PREBUILT_SCRIPT_FILENAME = "webview.js";
3
+ export declare const PREBUILT_STYLESHEET_FILENAME = "webview.css";
4
+ /** The terminal engine's prebuilt registration module, beside `webview.js` (docs/packs.md). Optional: a pack with no terminal views never has one. */
5
+ export declare const PREBUILT_TERMINAL_SCRIPT_FILENAME = "terminal.js";
6
+ /** Absolute path of a pack folder's prebuilt terminal script. No existence check. */
7
+ export declare function prebuiltTerminalScriptPathFor(folder: string): string;
8
+ /**
9
+ * The `webview.js` an export writes for a pack that has terminal views but
10
+ * no React ones: a registration with no components, so the archive keeps
11
+ * its required `webview.js` and a React host loads the pack as having
12
+ * nothing for it (every directive falls back).
13
+ */
14
+ export declare function emptyWebviewScript(manifestJson: string): string;
15
+ /** Absolute path of a pack folder's prebuilt registration script. No existence check. */
16
+ export declare function prebuiltScriptPathFor(folder: string): string;
17
+ /** Absolute path of a pack folder's prebuilt sibling stylesheet. No existence check. */
18
+ export declare function prebuiltStylesheetPathFor(folder: string): string;
19
+ /** Whether an absolute path exists. Injected so this module needs no real disk to test; may be sync or async. */
20
+ export type PackPathExists = (absolutePath: string) => boolean | Promise<boolean>;
21
+ export interface PrebuiltPackResolution {
22
+ /** Absolute path to the pack's prebuilt webview.js (it exists). */
23
+ readonly scriptPath: string;
24
+ /** Absolute path to the sibling webview.css, present only when that file exists. */
25
+ readonly stylesheetPath?: string;
26
+ /**
27
+ * Absolute paths of the manifest-declared component sources that are
28
+ * present on disk and therefore shadowed by the prebuilt script. Empty
29
+ * when the pack ships no sources (the ordinary distribution shape:
30
+ * `webview.js` alone, docs/packs.md's "This is the form to ship when
31
+ * other people will use your pack").
32
+ */
33
+ readonly shadowedComponentSources: readonly string[];
34
+ }
35
+ /**
36
+ * Resolves the prebuilt artifacts of one discovered pack, or `undefined`
37
+ * when the pack ships no `webview.js` at all (the from-source path, which a
38
+ * host compiles via `./pack-build.ts` instead).
39
+ *
40
+ * `pack.scriptPath` (already `folder/webview.js` from `./discover.ts`) is
41
+ * used as the script path rather than recomputed, but the stylesheet path
42
+ * is derived from the script path's own directory so the two always stay
43
+ * siblings even if a caller ever hands this a `DiscoveredPack` copy whose
44
+ * `folder` and `scriptPath` were computed independently.
45
+ *
46
+ * Never throws: an `exists` that throws is treated as "does not exist" for
47
+ * every path this function checks.
48
+ */
49
+ export declare function resolvePrebuiltPack(pack: Pick<DiscoveredPack, 'folder' | 'componentPaths' | 'scriptPath'>, exists: PackPathExists): Promise<PrebuiltPackResolution | undefined>;
@@ -0,0 +1,97 @@
1
+ /**
2
+ * The prebuilt-pack convention (docs/packs.md's "Two ways to run a pack:
3
+ * prebuilt and from source"): a pack that ships a compiled `webview.js`
4
+ * sibling to its `pack.json` is used as-is, no compiler runs. This module
5
+ * adds the second half of that convention (issue #15): an OPTIONAL sibling
6
+ * `webview.css`, injected/removed by a host's own pack-context composition
7
+ * keyed by namespace exactly like a compiled pack's own emitted stylesheet
8
+ * (`./pack-build.ts`'s `stylesheetPath`). A prebuilt pack with no
9
+ * `webview.css` simply has no stylesheet — never an error, matching
10
+ * `./discover.ts`'s "the file need not actually exist" posture for every
11
+ * other pack artifact.
12
+ *
13
+ * Also detects the SHADOWING case docs/packs.md names: "When a pack folder
14
+ * holds both a `webview.js` and sources, the prebuilt script wins and the
15
+ * sources are ignored." `resolvePrebuiltPack` reports exactly which of the
16
+ * manifest-declared component sources are present on disk, so a host's own
17
+ * diagnostics surface can name them (host-specific wording; this module
18
+ * only supplies the facts).
19
+ *
20
+ * Filesystem access is injected (`PackPathExists`), not imported directly,
21
+ * so this module is testable with plain in-memory fakes — matching every
22
+ * other module in this directory.
23
+ */
24
+ import { dirnamePath, joinPath } from '../path-util.js';
25
+ export const PREBUILT_SCRIPT_FILENAME = 'webview.js';
26
+ export const PREBUILT_STYLESHEET_FILENAME = 'webview.css';
27
+ /** The terminal engine's prebuilt registration module, beside `webview.js` (docs/packs.md). Optional: a pack with no terminal views never has one. */
28
+ export const PREBUILT_TERMINAL_SCRIPT_FILENAME = 'terminal.js';
29
+ /** Absolute path of a pack folder's prebuilt terminal script. No existence check. */
30
+ export function prebuiltTerminalScriptPathFor(folder) {
31
+ return joinPath(folder, PREBUILT_TERMINAL_SCRIPT_FILENAME);
32
+ }
33
+ /**
34
+ * The `webview.js` an export writes for a pack that has terminal views but
35
+ * no React ones: a registration with no components, so the archive keeps
36
+ * its required `webview.js` and a React host loads the pack as having
37
+ * nothing for it (every directive falls back).
38
+ */
39
+ export function emptyWebviewScript(manifestJson) {
40
+ return [
41
+ "if (typeof window !== 'undefined' && typeof window.__markiiRegisterPack === 'function') {",
42
+ ` window.__markiiRegisterPack(${JSON.stringify(manifestJson)}, {});`,
43
+ '}',
44
+ '',
45
+ ].join('\n');
46
+ }
47
+ /** Absolute path of a pack folder's prebuilt registration script. No existence check. */
48
+ export function prebuiltScriptPathFor(folder) {
49
+ return joinPath(folder, PREBUILT_SCRIPT_FILENAME);
50
+ }
51
+ /** Absolute path of a pack folder's prebuilt sibling stylesheet. No existence check. */
52
+ export function prebuiltStylesheetPathFor(folder) {
53
+ return joinPath(folder, PREBUILT_STYLESHEET_FILENAME);
54
+ }
55
+ /** Calls `exists`, treating a thrown error the same as "does not exist" — an unreadable path is not proof a file is absent, but this module's contract (like every other filesystem-adjacent module in this directory) is to degrade quietly rather than surface a raw I/O error here. */
56
+ async function existsQuietly(exists, absolutePath) {
57
+ try {
58
+ return await exists(absolutePath);
59
+ }
60
+ catch {
61
+ return false;
62
+ }
63
+ }
64
+ /**
65
+ * Resolves the prebuilt artifacts of one discovered pack, or `undefined`
66
+ * when the pack ships no `webview.js` at all (the from-source path, which a
67
+ * host compiles via `./pack-build.ts` instead).
68
+ *
69
+ * `pack.scriptPath` (already `folder/webview.js` from `./discover.ts`) is
70
+ * used as the script path rather than recomputed, but the stylesheet path
71
+ * is derived from the script path's own directory so the two always stay
72
+ * siblings even if a caller ever hands this a `DiscoveredPack` copy whose
73
+ * `folder` and `scriptPath` were computed independently.
74
+ *
75
+ * Never throws: an `exists` that throws is treated as "does not exist" for
76
+ * every path this function checks.
77
+ */
78
+ export async function resolvePrebuiltPack(pack, exists) {
79
+ const scriptExists = await existsQuietly(exists, pack.scriptPath);
80
+ if (!scriptExists)
81
+ return undefined;
82
+ const stylesheetPath = joinPath(dirnamePath(pack.scriptPath), PREBUILT_STYLESHEET_FILENAME);
83
+ const stylesheetExists = await existsQuietly(exists, stylesheetPath);
84
+ const shadowedComponentSources = [];
85
+ for (const sourcePath of Object.values(pack.componentPaths)) {
86
+ if (sourcePath === undefined)
87
+ continue;
88
+ if (await existsQuietly(exists, sourcePath)) {
89
+ shadowedComponentSources.push(sourcePath);
90
+ }
91
+ }
92
+ return {
93
+ scriptPath: pack.scriptPath,
94
+ ...(stylesheetExists ? { stylesheetPath } : {}),
95
+ shadowedComponentSources,
96
+ };
97
+ }
@@ -0,0 +1,24 @@
1
+ /**
2
+ * Small, environment-free path helpers for the paths the host layer hands to
3
+ * a `HostAdapter` (`readFile`, `writeFile`, `listFolder`). They are adapter
4
+ * paths, not OS paths: this layer never touches a file system itself, so it
5
+ * must not import `node:path`.
6
+ *
7
+ * They stay correct for a Windows host, where the VS Code adapter does hand
8
+ * over `C:\Users\ana\packs` style strings (from `os.homedir()`, a
9
+ * `globalStorageUri.fsPath`, or a hand-written setting). The separator is
10
+ * taken from the base path: a base that uses backslashes and no forward
11
+ * slashes is a Windows path, joined and split with backslashes (forward
12
+ * slashes inside later segments are accepted as separators too, as
13
+ * `path.win32` does). Any other base is posix.
14
+ */
15
+ /** True for a rooted path: `/x`, `\x`, `\\server\share`, or `C:\x` / `C:/x`. */
16
+ export declare function isAbsolutePath(path: string): boolean;
17
+ /**
18
+ * Joins `base` and `parts` with the separator `base` uses, collapsing `.`,
19
+ * `..` and repeated separators the way `path.join` does. An empty result is
20
+ * `.`.
21
+ */
22
+ export declare function joinPath(base: string, ...parts: string[]): string;
23
+ /** The parent folder of `path`, `path.dirname`-style (a root's parent is itself). */
24
+ export declare function dirnamePath(path: string): string;
@@ -0,0 +1,82 @@
1
+ /**
2
+ * Small, environment-free path helpers for the paths the host layer hands to
3
+ * a `HostAdapter` (`readFile`, `writeFile`, `listFolder`). They are adapter
4
+ * paths, not OS paths: this layer never touches a file system itself, so it
5
+ * must not import `node:path`.
6
+ *
7
+ * They stay correct for a Windows host, where the VS Code adapter does hand
8
+ * over `C:\Users\ana\packs` style strings (from `os.homedir()`, a
9
+ * `globalStorageUri.fsPath`, or a hand-written setting). The separator is
10
+ * taken from the base path: a base that uses backslashes and no forward
11
+ * slashes is a Windows path, joined and split with backslashes (forward
12
+ * slashes inside later segments are accepted as separators too, as
13
+ * `path.win32` does). Any other base is posix.
14
+ */
15
+ const DRIVE_PREFIX = /^[A-Za-z]:/;
16
+ function separatorOf(path) {
17
+ return path.includes('\\') && !path.includes('/') ? '\\' : '/';
18
+ }
19
+ /** True for a rooted path: `/x`, `\x`, `\\server\share`, or `C:\x` / `C:/x`. */
20
+ export function isAbsolutePath(path) {
21
+ return (path.startsWith('/') ||
22
+ path.startsWith('\\') ||
23
+ /^[A-Za-z]:[\\/]/.test(path));
24
+ }
25
+ function parse(path, sep) {
26
+ const splitter = sep === '\\' ? /[\\/]+/ : /\/+/;
27
+ let root = '';
28
+ let rest = path;
29
+ if (sep === '\\') {
30
+ const drive = DRIVE_PREFIX.exec(rest);
31
+ if (drive) {
32
+ root = drive[0];
33
+ rest = rest.slice(2);
34
+ }
35
+ if (/^[\\/]{2}[^\\/]/.test(path) && root === '') {
36
+ root = '\\\\';
37
+ rest = rest.replace(/^[\\/]+/, '');
38
+ }
39
+ else if (/^[\\/]/.test(rest)) {
40
+ root += '\\';
41
+ }
42
+ }
43
+ else if (rest.startsWith('/')) {
44
+ root = '/';
45
+ }
46
+ const segments = [];
47
+ for (const part of rest.split(splitter)) {
48
+ if (part === '' || part === '.')
49
+ continue;
50
+ if (part === '..') {
51
+ const last = segments[segments.length - 1];
52
+ if (last !== undefined && last !== '..')
53
+ segments.pop();
54
+ else if (root === '')
55
+ segments.push('..');
56
+ continue;
57
+ }
58
+ segments.push(part);
59
+ }
60
+ return { root, segments };
61
+ }
62
+ function format({ root, segments }, sep) {
63
+ const body = segments.join(sep);
64
+ if (body === '')
65
+ return root === '' ? '.' : root;
66
+ return root + body;
67
+ }
68
+ /**
69
+ * Joins `base` and `parts` with the separator `base` uses, collapsing `.`,
70
+ * `..` and repeated separators the way `path.join` does. An empty result is
71
+ * `.`.
72
+ */
73
+ export function joinPath(base, ...parts) {
74
+ const sep = separatorOf(base);
75
+ return format(parse([base, ...parts].filter((p) => p !== '').join(sep), sep), sep);
76
+ }
77
+ /** The parent folder of `path`, `path.dirname`-style (a root's parent is itself). */
78
+ export function dirnamePath(path) {
79
+ const sep = separatorOf(path);
80
+ const parsed = parse(path, sep);
81
+ return format({ root: parsed.root, segments: parsed.segments.slice(0, -1) }, sep);
82
+ }
@@ -0,0 +1,45 @@
1
+ /**
2
+ * A `RunIsolate` backed by a Web Worker, for hosts whose runtime cannot
3
+ * create a worker thread (see `./isolate.ts`).
4
+ *
5
+ * Two details are load-bearing.
6
+ *
7
+ * The worker is started from a BLOB URL built from the bundle's own bytes,
8
+ * read off disk by the host. Loading a worker from a `file://` URL is
9
+ * refused by Chromium, and an Electron app's custom protocols are not
10
+ * something this package can assume, so reading the file the host already
11
+ * has and handing it over as a blob sidesteps the question entirely.
12
+ * `glue.wasm` travels the same way, because wasmoon fetches it by URL and
13
+ * there is no `__dirname` inside a worker to find it beside the bundle.
14
+ *
15
+ * Network is served by the HOST, not the worker: `serveNetRequests` wires
16
+ * every `markii:net-request` to the caller's own `NetProvider`, which is
17
+ * the same pinned one a worker-thread isolate runs. The isolate has no
18
+ * network stack of its own, so the allowlist cannot be bypassed from
19
+ * inside it.
20
+ *
21
+ * What this CANNOT do, stated plainly because it is a real difference: a
22
+ * Web Worker takes no `maxOldGenerationSizeMb`, so `options`'s heap cap is
23
+ * accepted and ignored. Lua's own memory limit and the external watchdog
24
+ * still apply; a runaway JavaScript-side allocation inside the isolate is
25
+ * bounded only by the browser engine, and exhausting it can take the host
26
+ * renderer down. docs/security.md records this.
27
+ */
28
+ import type { NetProvider } from '@markii/lua';
29
+ import type { RunIsolate, SpawnIsolateOptions } from './isolate.js';
30
+ /** The Web Worker surface this uses, declared structurally so the package needs no DOM lib. */
31
+ export interface WorkerLike {
32
+ postMessage: (message: unknown) => void;
33
+ terminate: () => void;
34
+ addEventListener: (type: 'message' | 'error' | 'messageerror', listener: (event: unknown) => void) => void;
35
+ }
36
+ export interface BrowserIsolateOptions {
37
+ /** Creates the worker. Injected so a test never needs a real one, and so a host can decide how it mints the URL. */
38
+ createWorker: (entryPath: string) => WorkerLike;
39
+ /** The host's own pinned provider. Every `net.*` call the script makes is answered by this, in the host, never in the isolate. */
40
+ netProvider: (netAllowlist: string[], maxFetchBytes: number, netPolicy: unknown) => NetProvider;
41
+ /** Where the worker should fetch wasmoon's `glue.wasm`; sent along with the job. */
42
+ wasmUri?: string;
43
+ }
44
+ /** Builds the spawner a host passes as `SpawnRunOptions.spawnIsolate`. */
45
+ export declare function createBrowserIsolate(options: BrowserIsolateOptions): (spawn: SpawnIsolateOptions) => RunIsolate;
@@ -0,0 +1,81 @@
1
+ import { isNetBridgeRequest, serveNetRequest, } from './net-bridge.js';
2
+ /** Pulls the fields the host needs to build the same provider the worker would have built for itself. */
3
+ function netArgsFrom(job) {
4
+ const j = (job ?? {});
5
+ return {
6
+ allowlist: Array.isArray(j.netAllowlist)
7
+ ? j.netAllowlist.filter((h) => typeof h === 'string')
8
+ : [],
9
+ maxFetchBytes: typeof j.limits?.maxFetchBytes === 'number'
10
+ ? j.limits.maxFetchBytes
11
+ : 2 * 1024 * 1024,
12
+ // Fail closed, exactly as the worker does with an absent policy.
13
+ policy: j.netPolicy ?? { allowRestrictedAddresses: false },
14
+ };
15
+ }
16
+ /** Builds the spawner a host passes as `SpawnRunOptions.spawnIsolate`. */
17
+ export function createBrowserIsolate(options) {
18
+ return (spawn) => {
19
+ const worker = options.createWorker(spawn.entryPath);
20
+ let provider;
21
+ let alive = true;
22
+ const messageListeners = [];
23
+ const errorListeners = [];
24
+ worker.addEventListener('message', (event) => {
25
+ const data = event?.data;
26
+ if (isNetBridgeRequest(data)) {
27
+ if (!provider)
28
+ return;
29
+ void serveNetRequest(data, provider, (reply) => {
30
+ // A reply posted to a worker that is already gone would throw and
31
+ // take out this handler; the run is over either way.
32
+ if (alive)
33
+ worker.postMessage(reply);
34
+ });
35
+ return;
36
+ }
37
+ for (const listener of messageListeners)
38
+ listener(data);
39
+ });
40
+ worker.addEventListener('error', (event) => {
41
+ const message = event?.message ?? 'worker error';
42
+ for (const listener of errorListeners) {
43
+ listener(new Error(String(message)));
44
+ }
45
+ });
46
+ // A message that could not be deserialized is a failed run, not silence.
47
+ worker.addEventListener('messageerror', () => {
48
+ for (const listener of errorListeners) {
49
+ listener(new Error('worker sent an undeserializable message'));
50
+ }
51
+ });
52
+ return {
53
+ send: (job) => {
54
+ const { allowlist, maxFetchBytes, policy } = netArgsFrom(job);
55
+ provider = options.netProvider(allowlist, maxFetchBytes, policy);
56
+ worker.postMessage({
57
+ ...job,
58
+ ...(options.wasmUri !== undefined
59
+ ? { wasmUri: options.wasmUri }
60
+ : {}),
61
+ });
62
+ },
63
+ kill: () => {
64
+ if (!alive)
65
+ return;
66
+ alive = false;
67
+ worker.terminate();
68
+ },
69
+ onMessage: (listener) => {
70
+ messageListeners.push(listener);
71
+ },
72
+ onError: (listener) => {
73
+ errorListeners.push(listener);
74
+ },
75
+ // A Web Worker has no exit event: `terminate()` is immediate and
76
+ // silent. `spawnRun`'s watchdog path settles on its own timer rather
77
+ // than waiting for one, so there is nothing to report here.
78
+ onExit: () => { },
79
+ };
80
+ };
81
+ }
@@ -0,0 +1,29 @@
1
+ /**
2
+ * Where a bundle's note lives and what a bundle is called on disk. Every
3
+ * host that opens a `.mkz` (the VS Code preview, the CLI) asks these
4
+ * questions, so the answers live here once.
5
+ */
6
+ import type { BundleManifest } from '@markii/bundle';
7
+ /** The conventional document path inside a bundle when the manifest does not name one (docs/bundles.md). */
8
+ export declare const DEFAULT_BUNDLE_DOCUMENT_PATH = "note.mk.md";
9
+ /** The current bundle extension; new bundles are always written with this one. */
10
+ export declare const BUNDLE_EXTENSION = ".mkz";
11
+ /** The legacy bundle extension, still recognized. */
12
+ export declare const LEGACY_BUNDLE_EXTENSION = ".mkbundle";
13
+ /**
14
+ * True when `name` ends with `.mkz` or the legacy `.mkbundle`
15
+ * (case-insensitively) AND has a non-empty base name before the extension.
16
+ */
17
+ export declare function isBundleName(name: string): boolean;
18
+ /**
19
+ * The bundle-relative path of the document to open: the manifest's own
20
+ * `document` field when it names one, normalized through the same path jail
21
+ * every other bundle path goes through so a manifest cannot point outside
22
+ * the bundle; otherwise the conventional `note.mk.md`.
23
+ *
24
+ * Returns `undefined` when a `document` field is present but not a usable
25
+ * bundle-relative path (wrong type, or rejected by `normalizeBundlePath`,
26
+ * including any path with a `..` segment). That is a manifest authoring
27
+ * error, not something to ignore silently.
28
+ */
29
+ export declare function resolveBundleDocumentPath(manifest: BundleManifest): string | undefined;
@@ -0,0 +1,40 @@
1
+ import { normalizeBundlePath } from '@markii/bundle';
2
+ /** The conventional document path inside a bundle when the manifest does not name one (docs/bundles.md). */
3
+ export const DEFAULT_BUNDLE_DOCUMENT_PATH = 'note.mk.md';
4
+ /** The current bundle extension; new bundles are always written with this one. */
5
+ export const BUNDLE_EXTENSION = '.mkz';
6
+ /** The legacy bundle extension, still recognized. */
7
+ export const LEGACY_BUNDLE_EXTENSION = '.mkbundle';
8
+ /**
9
+ * True when `name` ends with `.mkz` or the legacy `.mkbundle`
10
+ * (case-insensitively) AND has a non-empty base name before the extension.
11
+ */
12
+ export function isBundleName(name) {
13
+ const lower = name.toLowerCase();
14
+ for (const extension of [BUNDLE_EXTENSION, LEGACY_BUNDLE_EXTENSION]) {
15
+ if (lower.endsWith(extension) && lower.length > extension.length) {
16
+ return true;
17
+ }
18
+ }
19
+ return false;
20
+ }
21
+ /**
22
+ * The bundle-relative path of the document to open: the manifest's own
23
+ * `document` field when it names one, normalized through the same path jail
24
+ * every other bundle path goes through so a manifest cannot point outside
25
+ * the bundle; otherwise the conventional `note.mk.md`.
26
+ *
27
+ * Returns `undefined` when a `document` field is present but not a usable
28
+ * bundle-relative path (wrong type, or rejected by `normalizeBundlePath`,
29
+ * including any path with a `..` segment). That is a manifest authoring
30
+ * error, not something to ignore silently.
31
+ */
32
+ export function resolveBundleDocumentPath(manifest) {
33
+ const raw = manifest.document;
34
+ if (raw === undefined)
35
+ return DEFAULT_BUNDLE_DOCUMENT_PATH;
36
+ if (typeof raw !== 'string')
37
+ return undefined;
38
+ const normalized = normalizeBundlePath(raw);
39
+ return normalized.ok ? normalized.path : undefined;
40
+ }