@markii/host 0.0.0-stage → 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 (193) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +19 -2
  3. package/dist/browser-worker.js +639 -0
  4. package/dist/browser.d.ts +57 -0
  5. package/dist/browser.js +167 -0
  6. package/dist/conformance/host/01-plain-open/expected.json +5 -0
  7. package/dist/conformance/host/01-plain-open/note.mk.md +8 -0
  8. package/dist/conformance/host/01-plain-open/scenario.json +6 -0
  9. package/dist/conformance/host/02-run-manual-grant-accepted/expected.json +16 -0
  10. package/dist/conformance/host/02-run-manual-grant-accepted/note.mk.md +5 -0
  11. package/dist/conformance/host/02-run-manual-grant-accepted/scenario.json +14 -0
  12. package/dist/conformance/host/03-run-manual-grant-refused/expected.json +18 -0
  13. package/dist/conformance/host/03-run-manual-grant-refused/note.mk.md +5 -0
  14. package/dist/conformance/host/03-run-manual-grant-refused/scenario.json +14 -0
  15. package/dist/conformance/host/04-run-auto-write-denied/expected.json +16 -0
  16. package/dist/conformance/host/04-run-auto-write-denied/note.mk.md +10 -0
  17. package/dist/conformance/host/04-run-auto-write-denied/scenario.json +13 -0
  18. package/dist/conformance/host/05-export-html-pack-directive/expected.json +14 -0
  19. package/dist/conformance/host/05-export-html-pack-directive/note.mk.md +8 -0
  20. package/dist/conformance/host/05-export-html-pack-directive/scenario.json +6 -0
  21. package/dist/conformance/host/06-install-pack-then-render/expected.json +18 -0
  22. package/dist/conformance/host/06-install-pack-then-render/files/ana-1.0.0/pack.json +13 -0
  23. package/dist/conformance/host/06-install-pack-then-render/files/ana-1.0.0/webview.js +1 -0
  24. package/dist/conformance/host/06-install-pack-then-render/note.mk.md +5 -0
  25. package/dist/conformance/host/06-install-pack-then-render/scenario.json +15 -0
  26. package/dist/conformance/host/07-complete-in-directive/expected.json +14 -0
  27. package/dist/conformance/host/07-complete-in-directive/note.mk.md +3 -0
  28. package/dist/conformance/host/07-complete-in-directive/scenario.json +6 -0
  29. package/dist/conformance/host/08-hover-standard-component/expected.json +5 -0
  30. package/dist/conformance/host/08-hover-standard-component/note.mk.md +5 -0
  31. package/dist/conformance/host/08-hover-standard-component/scenario.json +6 -0
  32. package/dist/conformance/host/09-insert-skeleton/expected.json +5 -0
  33. package/dist/conformance/host/09-insert-skeleton/note.mk.md +4 -0
  34. package/dist/conformance/host/09-insert-skeleton/scenario.json +6 -0
  35. package/dist/conformance/host/10-scripts-disabled/expected.json +11 -0
  36. package/dist/conformance/host/10-scripts-disabled/note.mk.md +5 -0
  37. package/dist/conformance/host/10-scripts-disabled/scenario.json +9 -0
  38. package/dist/conformance/host/11-complete-closing-fence/expected.json +5 -0
  39. package/dist/conformance/host/11-complete-closing-fence/note.mk.md +5 -0
  40. package/dist/conformance/host/11-complete-closing-fence/scenario.json +12 -0
  41. package/dist/conformance/host/12-complete-nested-container/expected.json +5 -0
  42. package/dist/conformance/host/12-complete-nested-container/note.mk.md +5 -0
  43. package/dist/conformance/host/12-complete-nested-container/scenario.json +12 -0
  44. package/dist/conformance/host/13-install-pack-declined/expected.json +12 -0
  45. package/dist/conformance/host/13-install-pack-declined/files/ana-1.0.0/pack.json +13 -0
  46. package/dist/conformance/host/13-install-pack-declined/files/ana-1.0.0/webview.js +1 -0
  47. package/dist/conformance/host/13-install-pack-declined/note.mk.md +5 -0
  48. package/dist/conformance/host/13-install-pack-declined/scenario.json +10 -0
  49. package/dist/conformance/host/14-remove-pack/expected.json +14 -0
  50. package/dist/conformance/host/14-remove-pack/files/ana-1.0.0/pack.json +13 -0
  51. package/dist/conformance/host/14-remove-pack/files/ana-1.0.0/webview.js +1 -0
  52. package/dist/conformance/host/14-remove-pack/note.mk.md +5 -0
  53. package/dist/conformance/host/14-remove-pack/scenario.json +11 -0
  54. package/dist/conformance/host/15-load-packs-engine-views/expected.json +19 -0
  55. package/dist/conformance/host/15-load-packs-engine-views/files/duo-1.0.0/pack.json +9 -0
  56. package/dist/conformance/host/15-load-packs-engine-views/files/duo-1.0.0/webview.js +1 -0
  57. package/dist/conformance/host/15-load-packs-engine-views/note.mk.md +9 -0
  58. package/dist/conformance/host/15-load-packs-engine-views/scenario.json +10 -0
  59. package/dist/conformance/host/16-pack-trust/expected.json +37 -0
  60. package/dist/conformance/host/16-pack-trust/files/ana-1.0.0/pack.json +12 -0
  61. package/dist/conformance/host/16-pack-trust/files/ana-1.0.0/webview.js +1 -0
  62. package/dist/conformance/host/16-pack-trust/files/sneaky-1.0.0/pack.json +12 -0
  63. package/dist/conformance/host/16-pack-trust/files/sneaky-1.0.0/webview.js +1 -0
  64. package/dist/conformance/host/16-pack-trust/note.mk.md +5 -0
  65. package/dist/conformance/host/16-pack-trust/scenario.json +26 -0
  66. package/dist/conformance/host/README.md +221 -0
  67. package/dist/conformance.d.ts +2 -0
  68. package/dist/conformance.js +5 -0
  69. package/dist/diagnostics/render-diagnostics.d.ts +41 -0
  70. package/dist/diagnostics/render-diagnostics.js +46 -0
  71. package/dist/export/cascade.d.ts +98 -0
  72. package/dist/export/cascade.js +159 -0
  73. package/dist/export/export-zip.d.ts +19 -0
  74. package/dist/export/export-zip.js +38 -0
  75. package/dist/export/image-embed.d.ts +126 -0
  76. package/dist/export/image-embed.js +274 -0
  77. package/dist/export/note-export.d.ts +332 -0
  78. package/dist/export/note-export.js +366 -0
  79. package/dist/export/note-links.d.ts +83 -0
  80. package/dist/export/note-links.js +241 -0
  81. package/dist/glue.wasm +0 -0
  82. package/dist/host/adapter.d.ts +223 -0
  83. package/dist/host/adapter.js +9 -0
  84. package/dist/host/cascade-export-behavior.d.ts +120 -0
  85. package/dist/host/cascade-export-behavior.js +151 -0
  86. package/dist/host/conformance-runner.d.ts +156 -0
  87. package/dist/host/conformance-runner.js +634 -0
  88. package/dist/host/create-host.d.ts +253 -0
  89. package/dist/host/create-host.js +329 -0
  90. package/dist/host/editor-behavior.d.ts +157 -0
  91. package/dist/host/editor-behavior.js +227 -0
  92. package/dist/host/export/md-plain.d.ts +15 -0
  93. package/dist/host/export/md-plain.js +125 -0
  94. package/dist/host/export-behavior.d.ts +113 -0
  95. package/dist/host/export-behavior.js +257 -0
  96. package/dist/host/labels.d.ts +24 -0
  97. package/dist/host/labels.js +32 -0
  98. package/dist/host/pack-archive.d.ts +30 -0
  99. package/dist/host/pack-archive.js +68 -0
  100. package/dist/host/pack-install.d.ts +88 -0
  101. package/dist/host/pack-install.js +189 -0
  102. package/dist/host/pack-load.d.ts +72 -0
  103. package/dist/host/pack-load.js +57 -0
  104. package/dist/host/pack-remove.d.ts +21 -0
  105. package/dist/host/pack-remove.js +65 -0
  106. package/dist/host/pack-trust-behavior.d.ts +79 -0
  107. package/dist/host/pack-trust-behavior.js +262 -0
  108. package/dist/host/refresh-interval.d.ts +39 -0
  109. package/dist/host/refresh-interval.js +61 -0
  110. package/dist/host/run-behavior.d.ts +89 -0
  111. package/dist/host/run-behavior.js +168 -0
  112. package/dist/host/run-diagnostics.d.ts +26 -0
  113. package/dist/host/run-diagnostics.js +43 -0
  114. package/dist/host/script-execution.d.ts +72 -0
  115. package/dist/host/script-execution.js +68 -0
  116. package/dist/host/terminal-packs.d.ts +39 -0
  117. package/dist/host/terminal-packs.js +51 -0
  118. package/dist/html-engine.d.ts +2 -0
  119. package/dist/html-engine.js +29 -0
  120. package/dist/index.d.ts +95 -0
  121. package/dist/index.js +70 -0
  122. package/dist/insert/component-catalog.d.ts +23 -0
  123. package/dist/insert/component-catalog.js +96 -0
  124. package/dist/node-fs.d.ts +14 -0
  125. package/dist/node-fs.js +70 -0
  126. package/dist/node.d.ts +5 -0
  127. package/dist/node.js +13 -0
  128. package/dist/package.json +3 -0
  129. package/dist/packs/discover.d.ts +119 -0
  130. package/dist/packs/discover.js +266 -0
  131. package/dist/packs/pack-diagnostics.d.ts +81 -0
  132. package/dist/packs/pack-diagnostics.js +105 -0
  133. package/dist/packs/pack-paths.d.ts +53 -0
  134. package/dist/packs/pack-paths.js +106 -0
  135. package/dist/packs/pack-render-registry.d.ts +83 -0
  136. package/dist/packs/pack-render-registry.js +182 -0
  137. package/dist/packs/pack-scripts.d.ts +20 -0
  138. package/dist/packs/pack-scripts.js +96 -0
  139. package/dist/packs/pack-trust.d.ts +57 -0
  140. package/dist/packs/pack-trust.js +111 -0
  141. package/dist/packs/prebuilt.d.ts +49 -0
  142. package/dist/packs/prebuilt.js +97 -0
  143. package/dist/path-util.d.ts +24 -0
  144. package/dist/path-util.js +82 -0
  145. package/dist/run/browser-isolate.d.ts +45 -0
  146. package/dist/run/browser-isolate.js +81 -0
  147. package/dist/run/bundle-document.d.ts +29 -0
  148. package/dist/run/bundle-document.js +40 -0
  149. package/dist/run/bundle-run.d.ts +188 -0
  150. package/dist/run/bundle-run.js +232 -0
  151. package/dist/run/grant-flow.d.ts +232 -0
  152. package/dist/run/grant-flow.js +390 -0
  153. package/dist/run/ip-address.d.ts +41 -0
  154. package/dist/run/ip-address.js +255 -0
  155. package/dist/run/isolate.d.ts +57 -0
  156. package/dist/run/isolate.js +22 -0
  157. package/dist/run/lua-resolver.d.ts +21 -0
  158. package/dist/run/lua-resolver.js +63 -0
  159. package/dist/run/net-bridge-worker.d.ts +26 -0
  160. package/dist/run/net-bridge-worker.js +66 -0
  161. package/dist/run/net-bridge.d.ts +56 -0
  162. package/dist/run/net-bridge.js +59 -0
  163. package/dist/run/net-pinning.d.ts +82 -0
  164. package/dist/run/net-pinning.js +137 -0
  165. package/dist/run/net-provider.d.ts +70 -0
  166. package/dist/run/net-provider.js +289 -0
  167. package/dist/run/node-isolate.d.ts +30 -0
  168. package/dist/run/node-isolate.js +117 -0
  169. package/dist/run/run-flow.d.ts +187 -0
  170. package/dist/run/run-flow.js +323 -0
  171. package/dist/run/run-host.d.ts +107 -0
  172. package/dist/run/run-host.js +241 -0
  173. package/dist/run/run-job.d.ts +130 -0
  174. package/dist/run/run-job.js +279 -0
  175. package/dist/run/run-progress.d.ts +55 -0
  176. package/dist/run/run-progress.js +42 -0
  177. package/dist/run/run-trace.d.ts +44 -0
  178. package/dist/run/run-trace.js +36 -0
  179. package/dist/run/script-requirements.d.ts +98 -0
  180. package/dist/run/script-requirements.js +212 -0
  181. package/dist/run/snapshot-storage.d.ts +25 -0
  182. package/dist/run/snapshot-storage.js +31 -0
  183. package/dist/run/stale-values.d.ts +25 -0
  184. package/dist/run/stale-values.js +32 -0
  185. package/dist/run/worker-entry-browser.d.ts +42 -0
  186. package/dist/run/worker-entry-browser.js +77 -0
  187. package/dist/run/worker-entry.d.ts +1 -0
  188. package/dist/run/worker-entry.js +96 -0
  189. package/dist/values-failure.d.ts +17 -0
  190. package/dist/values-failure.js +1 -0
  191. package/dist/values-merge.d.ts +39 -0
  192. package/dist/values-merge.js +33 -0
  193. package/package.json +84 -4
@@ -0,0 +1,98 @@
1
+ import type { NoteLink } from './note-links.js';
2
+ /**
3
+ * How far from the root note a cascade follows links. Four hops covers the
4
+ * shapes people actually build, an index note pointing at sections that
5
+ * point at pages, without turning one command into a whole-vault export.
6
+ */
7
+ export declare const DEFAULT_CASCADE_MAX_DEPTH = 4;
8
+ /**
9
+ * How many notes one cascade may export. A second, independent guard: a
10
+ * shallow graph can still be enormously wide, and an archive of a hundred
11
+ * pages is already past what anyone meant by "this note and what it links
12
+ * to".
13
+ */
14
+ export declare const DEFAULT_CASCADE_MAX_NOTES = 100;
15
+ /** One note the walk reached. */
16
+ export interface CascadeNote {
17
+ /** The note's own path, in whatever form the host resolves and reads. */
18
+ readonly path: string;
19
+ /** The note's full source text. */
20
+ readonly text: string;
21
+ /** Hops from the root note. The root itself is 0. */
22
+ readonly depth: number;
23
+ }
24
+ /** Reads one note's text, or reports that it could not be read. A reader that throws is treated as unreadable. */
25
+ export type CascadeNoteReader = (path: string) => Promise<string | undefined> | string | undefined;
26
+ /**
27
+ * Resolves one link, as written in `fromNotePath`, to the path of a note in
28
+ * this vault or workspace, or `undefined` when it names something that is
29
+ * not a note here. This is where a host puts its own resolution rules: an
30
+ * Obsidian vault resolves a bare note name from anywhere, a folder-relative
31
+ * host resolves against the linking note's folder.
32
+ */
33
+ export type CascadeLinkResolver = (link: NoteLink, fromNotePath: string) => string | undefined;
34
+ /** Why a walk stopped early. */
35
+ export type CascadeTruncation = 'depth' | 'count';
36
+ export interface CascadeWalkOptions {
37
+ /** The note the cascade starts from. */
38
+ readonly rootPath: string;
39
+ readonly readNote: CascadeNoteReader;
40
+ readonly resolveLink: CascadeLinkResolver;
41
+ /** Defaults to `DEFAULT_CASCADE_MAX_DEPTH`. */
42
+ readonly maxDepth?: number;
43
+ /** Defaults to `DEFAULT_CASCADE_MAX_NOTES`. */
44
+ readonly maxNotes?: number;
45
+ }
46
+ export interface CascadeWalkResult {
47
+ /** Every note reached, breadth first, the root note always first. */
48
+ readonly notes: readonly CascadeNote[];
49
+ /** Links that resolved to a note the reader could not read, for the diagnostics surface. */
50
+ readonly unreadable: readonly {
51
+ readonly path: string;
52
+ readonly from: string;
53
+ }[];
54
+ /** Set when a limit stopped the walk before it ran out of links to follow. */
55
+ readonly truncated?: CascadeTruncation;
56
+ }
57
+ /**
58
+ * Walks the note graph out from `rootPath`, breadth first, and returns
59
+ * every note it reached.
60
+ *
61
+ * Breadth first rather than depth first on purpose: when a limit cuts the
62
+ * walk short, what survives is everything CLOSE to the root, which is what
63
+ * a reader of the root note actually needs.
64
+ *
65
+ * Never throws. A note that cannot be read is recorded and skipped, and a
66
+ * root note that cannot be read simply produces an empty walk, which the
67
+ * caller reports as having nothing to export.
68
+ */
69
+ export declare function walkNoteCascade(options: CascadeWalkOptions): Promise<CascadeWalkResult>;
70
+ /**
71
+ * The archive entry name a cascade's own index page uses. Reserved up
72
+ * front with `assignCascadeFileNames`'s `reservedNames`, so a walked note
73
+ * that happens to be called `index` gets a numeric suffix instead of
74
+ * silently colliding with, and overwriting, the index page in the zip.
75
+ */
76
+ export declare const CASCADE_INDEX_FILE_NAME = "index.html";
77
+ /**
78
+ * The file name each note gets inside the archive: its own base name with
79
+ * an `.html` extension.
80
+ *
81
+ * The archive is flat, so two notes in different folders can want the same
82
+ * name. The first one to be reached keeps it, and each later claimant gets
83
+ * a numeric suffix. The walk is breadth first and deterministic, so the
84
+ * same vault always produces the same names.
85
+ *
86
+ * `reservedNames` are claimed before any note, case-insensitively, exactly
87
+ * like an earlier note's claim — a host builds the same archive's index
88
+ * page under one of these names and needs it to never collide with a
89
+ * walked note.
90
+ */
91
+ export declare function assignCascadeFileNames(paths: readonly string[], reservedNames?: Iterable<string>): Map<string, string>;
92
+ /**
93
+ * One note's text with every link to another note IN THIS CASCADE pointed
94
+ * at that note's exported file. A link to anything else, a note outside the
95
+ * walked set or a file that is not a note at all, is left exactly as the
96
+ * author wrote it.
97
+ */
98
+ export declare function rewriteCascadeLinks(note: CascadeNote, fileNames: ReadonlyMap<string, string>, resolveLink: CascadeLinkResolver): string;
@@ -0,0 +1,159 @@
1
+ /**
2
+ * The cascade export's pure half (GitHub issue #28 slice 3, part 2):
3
+ * walking the notes one note links to, deciding what each exported file is
4
+ * called, and rewriting the links between them so the exported set is
5
+ * navigable on its own.
6
+ *
7
+ * PURE AND HOST-NEUTRAL, like `./note-links.ts` beside it. Reading a note
8
+ * and resolving a link target to a real note are injected, because only a
9
+ * host knows what a vault or a workspace contains. Obsidian is the first
10
+ * caller (issue #28 slice 3); a VS Code cascade command would supply its
11
+ * own two functions and reuse everything here unchanged. Nothing in this
12
+ * file imports `node:*`, so it stays available to a browser bundle if a
13
+ * host ever needs it there.
14
+ *
15
+ * BOUNDED BY CONSTRUCTION. A note graph has cycles, and a vault can be
16
+ * enormous, so the walk carries three guards a caller cannot switch off:
17
+ * a visited set, a depth limit, and a note-count limit. Hitting a limit is
18
+ * not a failure. The walk stops, says which limit stopped it, and the host
19
+ * writes that to its diagnostics surface, per AGENTS.md's rule that a
20
+ * quiet outcome still has to be discoverable.
21
+ */
22
+ import { extractNoteLinks, rewriteNoteLinks } from './note-links.js';
23
+ import { exportBaseName } from './note-export.js';
24
+ /**
25
+ * How far from the root note a cascade follows links. Four hops covers the
26
+ * shapes people actually build, an index note pointing at sections that
27
+ * point at pages, without turning one command into a whole-vault export.
28
+ */
29
+ export const DEFAULT_CASCADE_MAX_DEPTH = 4;
30
+ /**
31
+ * How many notes one cascade may export. A second, independent guard: a
32
+ * shallow graph can still be enormously wide, and an archive of a hundred
33
+ * pages is already past what anyone meant by "this note and what it links
34
+ * to".
35
+ */
36
+ export const DEFAULT_CASCADE_MAX_NOTES = 100;
37
+ /**
38
+ * Walks the note graph out from `rootPath`, breadth first, and returns
39
+ * every note it reached.
40
+ *
41
+ * Breadth first rather than depth first on purpose: when a limit cuts the
42
+ * walk short, what survives is everything CLOSE to the root, which is what
43
+ * a reader of the root note actually needs.
44
+ *
45
+ * Never throws. A note that cannot be read is recorded and skipped, and a
46
+ * root note that cannot be read simply produces an empty walk, which the
47
+ * caller reports as having nothing to export.
48
+ */
49
+ export async function walkNoteCascade(options) {
50
+ const maxDepth = options.maxDepth ?? DEFAULT_CASCADE_MAX_DEPTH;
51
+ const maxNotes = options.maxNotes ?? DEFAULT_CASCADE_MAX_NOTES;
52
+ const notes = [];
53
+ const unreadable = [];
54
+ const visited = new Set();
55
+ let truncated;
56
+ const read = async (path) => {
57
+ try {
58
+ return await options.readNote(path);
59
+ }
60
+ catch {
61
+ return undefined;
62
+ }
63
+ };
64
+ const rootText = await read(options.rootPath);
65
+ if (rootText === undefined) {
66
+ return { notes: [], unreadable: [{ path: options.rootPath, from: '' }] };
67
+ }
68
+ visited.add(options.rootPath);
69
+ notes.push({ path: options.rootPath, text: rootText, depth: 0 });
70
+ for (let cursor = 0; cursor < notes.length; cursor += 1) {
71
+ const current = notes[cursor];
72
+ if (current.depth >= maxDepth) {
73
+ // Only a note that actually has an unfollowed link proves the depth
74
+ // limit changed the outcome, so the flag is set below rather than
75
+ // here.
76
+ const hasLink = extractNoteLinks(current.text).some((link) => options.resolveLink(link, current.path) !== undefined);
77
+ if (hasLink)
78
+ truncated ??= 'depth';
79
+ continue;
80
+ }
81
+ for (const link of extractNoteLinks(current.text)) {
82
+ const resolved = options.resolveLink(link, current.path);
83
+ if (resolved === undefined)
84
+ continue;
85
+ if (visited.has(resolved))
86
+ continue;
87
+ visited.add(resolved);
88
+ if (notes.length >= maxNotes) {
89
+ truncated ??= 'count';
90
+ continue;
91
+ }
92
+ const text = await read(resolved);
93
+ if (text === undefined) {
94
+ unreadable.push({ path: resolved, from: current.path });
95
+ continue;
96
+ }
97
+ notes.push({ path: resolved, text, depth: current.depth + 1 });
98
+ }
99
+ }
100
+ return {
101
+ notes,
102
+ unreadable,
103
+ ...(truncated !== undefined ? { truncated } : {}),
104
+ };
105
+ }
106
+ /**
107
+ * The archive entry name a cascade's own index page uses. Reserved up
108
+ * front with `assignCascadeFileNames`'s `reservedNames`, so a walked note
109
+ * that happens to be called `index` gets a numeric suffix instead of
110
+ * silently colliding with, and overwriting, the index page in the zip.
111
+ */
112
+ export const CASCADE_INDEX_FILE_NAME = 'index.html';
113
+ /**
114
+ * The file name each note gets inside the archive: its own base name with
115
+ * an `.html` extension.
116
+ *
117
+ * The archive is flat, so two notes in different folders can want the same
118
+ * name. The first one to be reached keeps it, and each later claimant gets
119
+ * a numeric suffix. The walk is breadth first and deterministic, so the
120
+ * same vault always produces the same names.
121
+ *
122
+ * `reservedNames` are claimed before any note, case-insensitively, exactly
123
+ * like an earlier note's claim — a host builds the same archive's index
124
+ * page under one of these names and needs it to never collide with a
125
+ * walked note.
126
+ */
127
+ export function assignCascadeFileNames(paths, reservedNames = []) {
128
+ const names = new Map();
129
+ const taken = new Set();
130
+ for (const reserved of reservedNames) {
131
+ taken.add(reserved.toLowerCase());
132
+ }
133
+ for (const path of paths) {
134
+ const base = exportBaseName(path);
135
+ let candidate = `${base}.html`;
136
+ let counter = 2;
137
+ while (taken.has(candidate.toLowerCase())) {
138
+ candidate = `${base}-${String(counter)}.html`;
139
+ counter += 1;
140
+ }
141
+ taken.add(candidate.toLowerCase());
142
+ names.set(path, candidate);
143
+ }
144
+ return names;
145
+ }
146
+ /**
147
+ * One note's text with every link to another note IN THIS CASCADE pointed
148
+ * at that note's exported file. A link to anything else, a note outside the
149
+ * walked set or a file that is not a note at all, is left exactly as the
150
+ * author wrote it.
151
+ */
152
+ export function rewriteCascadeLinks(note, fileNames, resolveLink) {
153
+ return rewriteNoteLinks(note.text, (link) => {
154
+ const resolved = resolveLink(link, note.path);
155
+ if (resolved === undefined)
156
+ return undefined;
157
+ return fileNames.get(resolved);
158
+ });
159
+ }
@@ -0,0 +1,19 @@
1
+ /** One file in a cascade archive. */
2
+ export interface ExportArchiveEntry {
3
+ /** The file's name inside the archive. Flat: no directories. */
4
+ readonly name: string;
5
+ /** The file's UTF-8 text. */
6
+ readonly text: string;
7
+ }
8
+ /**
9
+ * The archive bytes for `entries`.
10
+ *
11
+ * Deflate level 6, the ordinary default: exported HTML embeds base64 image
12
+ * data, which compresses poorly, while the markup around it compresses
13
+ * well, and level 6 is the balance every archiver picked for the same
14
+ * reason.
15
+ *
16
+ * `mtime` is deliberately not set, so the same set of notes always produces
17
+ * byte-identical output and a re-export is diffable.
18
+ */
19
+ export declare function zipExportArchive(entries: readonly ExportArchiveEntry[]): Uint8Array;
@@ -0,0 +1,38 @@
1
+ /**
2
+ * Packing a cascade export into one archive (GitHub issue #28 slice 3,
3
+ * part 2).
4
+ *
5
+ * WHY NOT `@markii/bundle`. That package's zip module is about `.mkz`
6
+ * bundles: reading one safely (entry-size guards, CRC checks, a path jail)
7
+ * and writing one back out with a manifest. A cascade archive is not a
8
+ * bundle. It has no manifest, no scripts, and no bundle semantics at all,
9
+ * so borrowing `exportZipBundle` would mean claiming a format this is not.
10
+ * What both share is `fflate`, an approved dependency, so this writes the
11
+ * archive directly and stays a dozen lines.
12
+ *
13
+ * Host-neutral and free of `node:*`: `fflate` and `TextEncoder` work in
14
+ * every environment either host runs in.
15
+ */
16
+ import { zipSync } from 'fflate';
17
+ /**
18
+ * The archive bytes for `entries`.
19
+ *
20
+ * Deflate level 6, the ordinary default: exported HTML embeds base64 image
21
+ * data, which compresses poorly, while the markup around it compresses
22
+ * well, and level 6 is the balance every archiver picked for the same
23
+ * reason.
24
+ *
25
+ * `mtime` is deliberately not set, so the same set of notes always produces
26
+ * byte-identical output and a re-export is diffable.
27
+ */
28
+ export function zipExportArchive(entries) {
29
+ const encoder = new TextEncoder();
30
+ // A null-prototype object, matching how `@markii/bundle`'s own zip
31
+ // writing guards `zipSync`'s bracket assignment against a file named
32
+ // `__proto__`.
33
+ const files = Object.create(null);
34
+ for (const entry of entries) {
35
+ files[entry.name] = encoder.encode(entry.text);
36
+ }
37
+ return zipSync(files, { level: 6 });
38
+ }
@@ -0,0 +1,126 @@
1
+ /**
2
+ * Embedding a note's local images into its exported HTML (GitHub issue #28
3
+ * slice 3, part 1), so an exported file is genuinely self-contained: one
4
+ * document a user can mail, archive, or open on a machine that has never
5
+ * seen the vault, with its pictures still in it.
6
+ *
7
+ * WHERE THIS SITS. `./note-export.ts` composes an already-rendered body
8
+ * string into the export page. This module rewrites that body BEFORE the
9
+ * composition, turning every embeddable `<img src>` into a `data:` URI. It
10
+ * works on the rendered string rather than on the note's AST on purpose:
11
+ * the body is whatever engine rendered it, React or `@markii/html`, and a
12
+ * pack component's own images have to be embedded too. A string rewrite is
13
+ * the one place both engines and every component meet.
14
+ *
15
+ * WHAT STAYS A URL. Anything with a scheme, so `http:` and `https:` sources
16
+ * stay live URLs exactly as authored, and a `data:` URI is already
17
+ * embedded. A protocol-relative `//host/x.png` is a remote source too. Only
18
+ * a genuinely local, relative path is a candidate.
19
+ *
20
+ * THE HOST SEAM. This package knows nothing about vaults, workspace
21
+ * folders, or filesystems, so reading the bytes is injected: a host hands
22
+ * in an `ExportImageReader` that resolves a source the way its own preview
23
+ * resolves one and reads it, or reports that it could not. That keeps
24
+ * `@markii/host` free of host APIs while the size cap, the MIME rules, the
25
+ * base64 encoding, and the reporting stay in ONE place, shared by every
26
+ * host.
27
+ *
28
+ * NOTHING HERE EVER FAILS AN EXPORT. An image that is too large, of an
29
+ * unknown type, missing, or unreadable keeps the source the author wrote
30
+ * and is recorded in the returned report. The host turns that report into
31
+ * diagnostics lines: per AGENTS.md, a skipped image is a quiet fact on the
32
+ * diagnostics surface, never a notice and never a failed export.
33
+ */
34
+ /**
35
+ * The per-image ceiling for embedding, 2 MiB. An image above it keeps its
36
+ * original path rather than inflating the exported file by a third of its
37
+ * own size in base64. Exported so a host with a cheap size check can skip
38
+ * reading such a file at all and report `oversize` straight away.
39
+ */
40
+ export declare const MAX_EMBEDDED_IMAGE_BYTES: number;
41
+ /** Every extension `mimeTypeForImageSrc` recognizes, lowercase and without the dot. Exported for a host that wants to name them in its own documentation or tests. */
42
+ export declare const EMBEDDABLE_IMAGE_EXTENSIONS: readonly string[];
43
+ /**
44
+ * The MIME type for one image source, read from its extension, or
45
+ * `undefined` when the extension is missing or unrecognized. Any `?query`
46
+ * or `#fragment` is ignored first, so `logo.png?v=2` still reads as a PNG.
47
+ */
48
+ export declare function mimeTypeForImageSrc(src: string): string | undefined;
49
+ /**
50
+ * True for a source this will try to embed: a non-empty, local, relative
51
+ * path. A scheme of any kind, a protocol-relative `//host/...`, and an
52
+ * empty or fragment-only value are all left exactly as authored.
53
+ */
54
+ export declare function isEmbeddableImageSrc(src: string): boolean;
55
+ /** What a host's image reader came back with for one source. */
56
+ export type ExportImageResult = {
57
+ /** The file's bytes, ready to embed. */
58
+ readonly kind: 'bytes';
59
+ readonly bytes: Uint8Array;
60
+ } | {
61
+ /** The host measured the file and it is over `MAX_EMBEDDED_IMAGE_BYTES`, so it never read it. */
62
+ readonly kind: 'oversize';
63
+ readonly byteLength: number;
64
+ } | {
65
+ /** The source did not resolve to a readable file, or reading it failed. */
66
+ readonly kind: 'unreadable';
67
+ /** The verbatim reason, for the diagnostics surface only. */
68
+ readonly detail?: string;
69
+ };
70
+ /**
71
+ * Reads one image source for an export. A host resolves the source the way
72
+ * its own preview resolves one, applies its own jail, and reads the bytes.
73
+ * It is expected not to throw; one that does is caught by
74
+ * `embedImagesInHtml` and treated as `unreadable`, so an export is never
75
+ * lost to a bad path.
76
+ */
77
+ export type ExportImageReader = (src: string) => ExportImageResult | Promise<ExportImageResult>;
78
+ /** Why one image kept its original source instead of being embedded. Only outcomes worth a diagnostics line appear here; a remote URL is not a skip, it is the documented behavior. */
79
+ export type ImageSkipReason = 'unsupported-type' | 'too-large' | 'unreadable';
80
+ /** One image that was not embedded, and why. */
81
+ export interface SkippedImage {
82
+ /** The source exactly as it appears in the note. */
83
+ readonly src: string;
84
+ readonly reason: ImageSkipReason;
85
+ /** The file's size, for a `too-large` skip. */
86
+ readonly byteLength?: number;
87
+ /** The verbatim reason, for an `unreadable` skip. Diagnostics only. */
88
+ readonly detail?: string;
89
+ }
90
+ /** What one pass of image embedding did, for the host's diagnostics surface. */
91
+ export interface EmbeddedImageReport {
92
+ /** The sources that became `data:` URIs, in document order. */
93
+ readonly embedded: readonly string[];
94
+ /** How many bytes of image data the file gained, before base64 expansion. */
95
+ readonly embeddedBytes: number;
96
+ /** Every local image that kept its original source, and why. */
97
+ readonly skipped: readonly SkippedImage[];
98
+ /** How many sources were left alone because they are remote or already embedded. Never a skip; counted only so a host can say the file still reaches the network. */
99
+ readonly remote: number;
100
+ }
101
+ /** An empty report, for an export that embedded nothing because no host reader was offered. */
102
+ export declare const EMPTY_IMAGE_REPORT: EmbeddedImageReport;
103
+ /**
104
+ * Base64 for a byte array, hand-rolled so this module needs neither
105
+ * `Buffer` (Node only) nor `btoa` (which cannot take bytes without a
106
+ * lossy string round trip) and stays environment-free, matching how this
107
+ * repo hand-rolls its manifest validation and `uses:` reader rather than
108
+ * taking a dependency for something this small.
109
+ */
110
+ export declare function encodeBase64(bytes: Uint8Array): string;
111
+ /** One image's bytes as a `data:` URI. Base64 output is alphanumeric plus `+/=`, so it is always safe inside a double-quoted HTML attribute with no further escaping. */
112
+ export declare function toImageDataUri(bytes: Uint8Array, mimeType: string): string;
113
+ /**
114
+ * Rewrites every embeddable local `<img src>` in `html` into a `data:` URI,
115
+ * using `readImage` to fetch each source's bytes, and reports what happened
116
+ * to every image it saw.
117
+ *
118
+ * Each distinct source is read at most once, so a note that shows the same
119
+ * picture five times reads and encodes it once and embeds the same URI five
120
+ * times. Never throws: a reader that throws is recorded as `unreadable` and
121
+ * that one image keeps its original source.
122
+ */
123
+ export declare function embedImagesInHtml(html: string, readImage: ExportImageReader): Promise<{
124
+ html: string;
125
+ report: EmbeddedImageReport;
126
+ }>;