@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.
- package/README.md +18 -2
- package/dist/browser-worker.js +639 -0
- package/dist/browser.d.ts +57 -0
- package/dist/browser.js +167 -0
- package/dist/conformance/host/01-plain-open/expected.json +5 -0
- package/dist/conformance/host/01-plain-open/note.mk.md +8 -0
- package/dist/conformance/host/01-plain-open/scenario.json +6 -0
- package/dist/conformance/host/02-run-manual-grant-accepted/expected.json +16 -0
- package/dist/conformance/host/02-run-manual-grant-accepted/note.mk.md +5 -0
- package/dist/conformance/host/02-run-manual-grant-accepted/scenario.json +14 -0
- package/dist/conformance/host/03-run-manual-grant-refused/expected.json +18 -0
- package/dist/conformance/host/03-run-manual-grant-refused/note.mk.md +5 -0
- package/dist/conformance/host/03-run-manual-grant-refused/scenario.json +14 -0
- package/dist/conformance/host/04-run-auto-write-denied/expected.json +16 -0
- package/dist/conformance/host/04-run-auto-write-denied/note.mk.md +10 -0
- package/dist/conformance/host/04-run-auto-write-denied/scenario.json +13 -0
- package/dist/conformance/host/05-export-html-pack-directive/expected.json +14 -0
- package/dist/conformance/host/05-export-html-pack-directive/note.mk.md +8 -0
- package/dist/conformance/host/05-export-html-pack-directive/scenario.json +6 -0
- package/dist/conformance/host/06-install-pack-then-render/expected.json +18 -0
- package/dist/conformance/host/06-install-pack-then-render/files/ana-1.0.0/pack.json +13 -0
- package/dist/conformance/host/06-install-pack-then-render/files/ana-1.0.0/webview.js +1 -0
- package/dist/conformance/host/06-install-pack-then-render/note.mk.md +5 -0
- package/dist/conformance/host/06-install-pack-then-render/scenario.json +15 -0
- package/dist/conformance/host/07-complete-in-directive/expected.json +14 -0
- package/dist/conformance/host/07-complete-in-directive/note.mk.md +3 -0
- package/dist/conformance/host/07-complete-in-directive/scenario.json +6 -0
- package/dist/conformance/host/08-hover-standard-component/expected.json +5 -0
- package/dist/conformance/host/08-hover-standard-component/note.mk.md +5 -0
- package/dist/conformance/host/08-hover-standard-component/scenario.json +6 -0
- package/dist/conformance/host/09-insert-skeleton/expected.json +5 -0
- package/dist/conformance/host/09-insert-skeleton/note.mk.md +4 -0
- package/dist/conformance/host/09-insert-skeleton/scenario.json +6 -0
- package/dist/conformance/host/10-scripts-disabled/expected.json +11 -0
- package/dist/conformance/host/10-scripts-disabled/note.mk.md +5 -0
- package/dist/conformance/host/10-scripts-disabled/scenario.json +9 -0
- package/dist/conformance/host/11-complete-closing-fence/expected.json +5 -0
- package/dist/conformance/host/11-complete-closing-fence/note.mk.md +5 -0
- package/dist/conformance/host/11-complete-closing-fence/scenario.json +12 -0
- package/dist/conformance/host/12-complete-nested-container/expected.json +5 -0
- package/dist/conformance/host/12-complete-nested-container/note.mk.md +5 -0
- package/dist/conformance/host/12-complete-nested-container/scenario.json +12 -0
- package/dist/conformance/host/13-install-pack-declined/expected.json +12 -0
- package/dist/conformance/host/13-install-pack-declined/files/ana-1.0.0/pack.json +13 -0
- package/dist/conformance/host/13-install-pack-declined/files/ana-1.0.0/webview.js +1 -0
- package/dist/conformance/host/13-install-pack-declined/note.mk.md +5 -0
- package/dist/conformance/host/13-install-pack-declined/scenario.json +10 -0
- package/dist/conformance/host/14-remove-pack/expected.json +14 -0
- package/dist/conformance/host/14-remove-pack/files/ana-1.0.0/pack.json +13 -0
- package/dist/conformance/host/14-remove-pack/files/ana-1.0.0/webview.js +1 -0
- package/dist/conformance/host/14-remove-pack/note.mk.md +5 -0
- package/dist/conformance/host/14-remove-pack/scenario.json +11 -0
- package/dist/conformance/host/15-load-packs-engine-views/expected.json +19 -0
- package/dist/conformance/host/15-load-packs-engine-views/files/duo-1.0.0/pack.json +9 -0
- package/dist/conformance/host/15-load-packs-engine-views/files/duo-1.0.0/webview.js +1 -0
- package/dist/conformance/host/15-load-packs-engine-views/note.mk.md +9 -0
- package/dist/conformance/host/15-load-packs-engine-views/scenario.json +10 -0
- package/dist/conformance/host/16-pack-trust/expected.json +37 -0
- package/dist/conformance/host/16-pack-trust/files/ana-1.0.0/pack.json +12 -0
- package/dist/conformance/host/16-pack-trust/files/ana-1.0.0/webview.js +1 -0
- package/dist/conformance/host/16-pack-trust/files/sneaky-1.0.0/pack.json +12 -0
- package/dist/conformance/host/16-pack-trust/files/sneaky-1.0.0/webview.js +1 -0
- package/dist/conformance/host/16-pack-trust/note.mk.md +5 -0
- package/dist/conformance/host/16-pack-trust/scenario.json +26 -0
- package/dist/conformance/host/README.md +221 -0
- package/dist/conformance.d.ts +2 -0
- package/dist/conformance.js +5 -0
- package/dist/diagnostics/render-diagnostics.d.ts +41 -0
- package/dist/diagnostics/render-diagnostics.js +46 -0
- package/dist/export/cascade.d.ts +98 -0
- package/dist/export/cascade.js +159 -0
- package/dist/export/export-zip.d.ts +19 -0
- package/dist/export/export-zip.js +38 -0
- package/dist/export/image-embed.d.ts +126 -0
- package/dist/export/image-embed.js +274 -0
- package/dist/export/note-export.d.ts +332 -0
- package/dist/export/note-export.js +366 -0
- package/dist/export/note-links.d.ts +83 -0
- package/dist/export/note-links.js +241 -0
- package/dist/glue.wasm +0 -0
- package/dist/host/adapter.d.ts +223 -0
- package/dist/host/adapter.js +9 -0
- package/dist/host/cascade-export-behavior.d.ts +120 -0
- package/dist/host/cascade-export-behavior.js +151 -0
- package/dist/host/conformance-runner.d.ts +156 -0
- package/dist/host/conformance-runner.js +634 -0
- package/dist/host/create-host.d.ts +253 -0
- package/dist/host/create-host.js +329 -0
- package/dist/host/editor-behavior.d.ts +157 -0
- package/dist/host/editor-behavior.js +227 -0
- package/dist/host/export/md-plain.d.ts +15 -0
- package/dist/host/export/md-plain.js +125 -0
- package/dist/host/export-behavior.d.ts +113 -0
- package/dist/host/export-behavior.js +257 -0
- package/dist/host/labels.d.ts +24 -0
- package/dist/host/labels.js +32 -0
- package/dist/host/pack-archive.d.ts +30 -0
- package/dist/host/pack-archive.js +68 -0
- package/dist/host/pack-install.d.ts +88 -0
- package/dist/host/pack-install.js +189 -0
- package/dist/host/pack-load.d.ts +72 -0
- package/dist/host/pack-load.js +57 -0
- package/dist/host/pack-remove.d.ts +21 -0
- package/dist/host/pack-remove.js +65 -0
- package/dist/host/pack-trust-behavior.d.ts +79 -0
- package/dist/host/pack-trust-behavior.js +262 -0
- package/dist/host/refresh-interval.d.ts +39 -0
- package/dist/host/refresh-interval.js +61 -0
- package/dist/host/run-behavior.d.ts +89 -0
- package/dist/host/run-behavior.js +168 -0
- package/dist/host/run-diagnostics.d.ts +26 -0
- package/dist/host/run-diagnostics.js +43 -0
- package/dist/host/script-execution.d.ts +72 -0
- package/dist/host/script-execution.js +68 -0
- package/dist/host/terminal-packs.d.ts +39 -0
- package/dist/host/terminal-packs.js +51 -0
- package/dist/html-engine.d.ts +2 -0
- package/dist/html-engine.js +29 -0
- package/dist/index.d.ts +95 -0
- package/dist/index.js +70 -0
- package/dist/insert/component-catalog.d.ts +23 -0
- package/dist/insert/component-catalog.js +96 -0
- package/dist/node-fs.d.ts +14 -0
- package/dist/node-fs.js +70 -0
- package/dist/node.d.ts +5 -0
- package/dist/node.js +13 -0
- package/dist/package.json +3 -0
- package/dist/packs/discover.d.ts +119 -0
- package/dist/packs/discover.js +266 -0
- package/dist/packs/pack-diagnostics.d.ts +81 -0
- package/dist/packs/pack-diagnostics.js +105 -0
- package/dist/packs/pack-paths.d.ts +53 -0
- package/dist/packs/pack-paths.js +106 -0
- package/dist/packs/pack-render-registry.d.ts +83 -0
- package/dist/packs/pack-render-registry.js +182 -0
- package/dist/packs/pack-scripts.d.ts +20 -0
- package/dist/packs/pack-scripts.js +96 -0
- package/dist/packs/pack-trust.d.ts +57 -0
- package/dist/packs/pack-trust.js +111 -0
- package/dist/packs/prebuilt.d.ts +49 -0
- package/dist/packs/prebuilt.js +97 -0
- package/dist/path-util.d.ts +24 -0
- package/dist/path-util.js +82 -0
- package/dist/run/browser-isolate.d.ts +45 -0
- package/dist/run/browser-isolate.js +81 -0
- package/dist/run/bundle-document.d.ts +29 -0
- package/dist/run/bundle-document.js +40 -0
- package/dist/run/bundle-run.d.ts +188 -0
- package/dist/run/bundle-run.js +232 -0
- package/dist/run/grant-flow.d.ts +232 -0
- package/dist/run/grant-flow.js +390 -0
- package/dist/run/ip-address.d.ts +41 -0
- package/dist/run/ip-address.js +255 -0
- package/dist/run/isolate.d.ts +57 -0
- package/dist/run/isolate.js +22 -0
- package/dist/run/lua-resolver.d.ts +21 -0
- package/dist/run/lua-resolver.js +63 -0
- package/dist/run/net-bridge-worker.d.ts +26 -0
- package/dist/run/net-bridge-worker.js +66 -0
- package/dist/run/net-bridge.d.ts +56 -0
- package/dist/run/net-bridge.js +59 -0
- package/dist/run/net-pinning.d.ts +82 -0
- package/dist/run/net-pinning.js +137 -0
- package/dist/run/net-provider.d.ts +70 -0
- package/dist/run/net-provider.js +289 -0
- package/dist/run/node-isolate.d.ts +30 -0
- package/dist/run/node-isolate.js +117 -0
- package/dist/run/run-flow.d.ts +187 -0
- package/dist/run/run-flow.js +323 -0
- package/dist/run/run-host.d.ts +107 -0
- package/dist/run/run-host.js +241 -0
- package/dist/run/run-job.d.ts +130 -0
- package/dist/run/run-job.js +279 -0
- package/dist/run/run-progress.d.ts +55 -0
- package/dist/run/run-progress.js +42 -0
- package/dist/run/run-trace.d.ts +44 -0
- package/dist/run/run-trace.js +36 -0
- package/dist/run/script-requirements.d.ts +98 -0
- package/dist/run/script-requirements.js +212 -0
- package/dist/run/snapshot-storage.d.ts +25 -0
- package/dist/run/snapshot-storage.js +31 -0
- package/dist/run/stale-values.d.ts +25 -0
- package/dist/run/stale-values.js +32 -0
- package/dist/run/worker-entry-browser.d.ts +42 -0
- package/dist/run/worker-entry-browser.js +77 -0
- package/dist/run/worker-entry.d.ts +1 -0
- package/dist/run/worker-entry.js +96 -0
- package/dist/values-failure.d.ts +17 -0
- package/dist/values-failure.js +1 -0
- package/dist/values-merge.d.ts +39 -0
- package/dist/values-merge.js +33 -0
- package/package.json +77 -4
|
@@ -0,0 +1,257 @@
|
|
|
1
|
+
import { buildNoteExport, exportedFileName, noteHasScripts, } from '../export/note-export.js';
|
|
2
|
+
import { EMPTY_IMAGE_REPORT, MAX_EMBEDDED_IMAGE_BYTES, } from '../export/image-embed.js';
|
|
3
|
+
import { mdPlainFromSource } from './export/md-plain.js';
|
|
4
|
+
/** The file name an export lands under, by format: the note's own base name with the format's own extension, so the export lands beside the note unless the host's picker says otherwise. */
|
|
5
|
+
export function exportDefaultFileName(notePath, format) {
|
|
6
|
+
const extension = {
|
|
7
|
+
html: '.html',
|
|
8
|
+
pdf: '.pdf',
|
|
9
|
+
ansi: '.txt',
|
|
10
|
+
'md-plain': '.md',
|
|
11
|
+
};
|
|
12
|
+
return exportedFileName(notePath, extension[format]);
|
|
13
|
+
}
|
|
14
|
+
/** The last path segment of a `/`-separated or `\`-separated path, for naming a file in a message. */
|
|
15
|
+
export function fileNameOf(path) {
|
|
16
|
+
const segments = path.split(/[/\\]/);
|
|
17
|
+
return segments[segments.length - 1] ?? path;
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* The short result message for a written or failed html/pdf export. A
|
|
21
|
+
* success names the file. A failure says what failed and points at this
|
|
22
|
+
* host's own diagnostics surface (`labels.diagnosticsSurface`), never at
|
|
23
|
+
* a stack trace — the verbatim reason goes there instead.
|
|
24
|
+
*/
|
|
25
|
+
export function exportResultMessage(outcome, labels) {
|
|
26
|
+
if (outcome.kind === 'failed') {
|
|
27
|
+
return `Markii: could not export this note. Open ${labels.diagnosticsSurface} for details.`;
|
|
28
|
+
}
|
|
29
|
+
const name = fileNameOf(outcome.path);
|
|
30
|
+
return outcome.valueCount === 0 && outcome.hasScripts === true
|
|
31
|
+
? `Markii: exported ${name}. The note has no stored script values, so data-bound components show their empty states.`
|
|
32
|
+
: `Markii: exported ${name}.`;
|
|
33
|
+
}
|
|
34
|
+
/** One diagnostic line describing HOW an export's body was rendered: the pack-vs-static distinction, kept off the result message and put here instead. */
|
|
35
|
+
export function renderEngineDiagnosticLine(render) {
|
|
36
|
+
if (render.engine === 'react') {
|
|
37
|
+
const packs = render.packCount === 1 ? '1 pack' : `${String(render.packCount)} packs`;
|
|
38
|
+
const stylesheets = render.stylesheetCount === 1
|
|
39
|
+
? '1 stylesheet'
|
|
40
|
+
: `${String(render.stylesheetCount)} stylesheets`;
|
|
41
|
+
return `Rendered through the preview's React engine, with ${packs} and ${stylesheets} embedded.`;
|
|
42
|
+
}
|
|
43
|
+
if (render.reason === 'no-packs') {
|
|
44
|
+
return 'Rendered with the static engine because no pack components are loaded, which matches what the preview shows.';
|
|
45
|
+
}
|
|
46
|
+
if (render.reason === 'no-renderer') {
|
|
47
|
+
return 'Rendered with the static engine because a preview panel could not be opened for this note. Pack components exported as labeled boxes; open the preview and export again to include them.';
|
|
48
|
+
}
|
|
49
|
+
if (render.reason === 'timeout') {
|
|
50
|
+
const detail = render.detail ? ` Detail: ${render.detail}` : '';
|
|
51
|
+
return `Rendered with the static engine because the preview did not answer in time. Pack components exported as labeled boxes.${detail}`;
|
|
52
|
+
}
|
|
53
|
+
const detail = render.detail ? ` Detail: ${render.detail}` : '';
|
|
54
|
+
return `Rendered with the static engine because the preview could not render the note. Pack components exported as labeled boxes.${detail}`;
|
|
55
|
+
}
|
|
56
|
+
/** One skipped image's diagnostics line: which file, and why it kept its original source instead of being embedded. */
|
|
57
|
+
function skippedImageLine(skipped) {
|
|
58
|
+
if (skipped.reason === 'too-large') {
|
|
59
|
+
const size = skipped.byteLength !== undefined
|
|
60
|
+
? formatByteSize(skipped.byteLength)
|
|
61
|
+
: 'an unknown size';
|
|
62
|
+
return `Skipped ${skipped.src}: ${size}, over the ${formatByteSize(MAX_EMBEDDED_IMAGE_BYTES)} embed limit.`;
|
|
63
|
+
}
|
|
64
|
+
if (skipped.reason === 'unsupported-type') {
|
|
65
|
+
return `Skipped ${skipped.src}: this file type is not supported for embedding.`;
|
|
66
|
+
}
|
|
67
|
+
return `Skipped ${skipped.src}: ${skipped.detail ?? 'the file could not be read'}.`;
|
|
68
|
+
}
|
|
69
|
+
/** `bytes`, formatted as a human-readable size (`"1.2 KB"`, `"512 bytes"`). */
|
|
70
|
+
export function formatByteSize(bytes) {
|
|
71
|
+
if (bytes < 1024)
|
|
72
|
+
return bytes === 1 ? '1 byte' : `${String(bytes)} bytes`;
|
|
73
|
+
const kb = bytes / 1024;
|
|
74
|
+
if (kb < 1024)
|
|
75
|
+
return `${kb.toFixed(1)} KB`;
|
|
76
|
+
return `${(kb / 1024).toFixed(1)} MB`;
|
|
77
|
+
}
|
|
78
|
+
/** The image lines for `exportDiagnosticLines`: how many images were embedded and how many bytes they added, one line per skipped image, and a remote-source count when any sources still reach the network. Empty when a note has no images at all. */
|
|
79
|
+
export function imageEmbedDiagnosticLines(images) {
|
|
80
|
+
const lines = [];
|
|
81
|
+
if (images.embedded.length > 0) {
|
|
82
|
+
const count = images.embedded.length === 1
|
|
83
|
+
? '1 image'
|
|
84
|
+
: `${String(images.embedded.length)} images`;
|
|
85
|
+
lines.push(`Embedded ${count}, adding ${formatByteSize(images.embeddedBytes)} to the exported file.`);
|
|
86
|
+
}
|
|
87
|
+
for (const skipped of images.skipped) {
|
|
88
|
+
lines.push(skippedImageLine(skipped));
|
|
89
|
+
}
|
|
90
|
+
if (images.remote > 0) {
|
|
91
|
+
const line = images.remote === 1
|
|
92
|
+
? '1 image source still points at a remote URL and needs network access to load.'
|
|
93
|
+
: `${String(images.remote)} image sources still point at remote URLs and need network access to load.`;
|
|
94
|
+
lines.push(line);
|
|
95
|
+
}
|
|
96
|
+
return lines;
|
|
97
|
+
}
|
|
98
|
+
/** The full diagnostics-surface detail for one html/pdf export attempt. */
|
|
99
|
+
export function exportDiagnosticLines(outcome) {
|
|
100
|
+
if (outcome.kind === 'failed') {
|
|
101
|
+
const where = outcome.path ? ` to ${outcome.path}` : '';
|
|
102
|
+
return [`Export failed${where}: ${outcome.reason}`];
|
|
103
|
+
}
|
|
104
|
+
return [
|
|
105
|
+
`Export wrote ${outcome.path}: ${String(outcome.bytes)} bytes, ${String(outcome.valueCount)} stored values baked in.`,
|
|
106
|
+
renderEngineDiagnosticLine(outcome.render),
|
|
107
|
+
...imageEmbedDiagnosticLines(outcome.images),
|
|
108
|
+
];
|
|
109
|
+
}
|
|
110
|
+
/** Builds the standalone HTML document `html` and `pdf` both export, via the shared, already-host-neutral `buildNoteExport`. Never throws: a failing `renderBody` degrades to the static engine rather than propagating. */
|
|
111
|
+
export async function buildExportDocument(request) {
|
|
112
|
+
const values = request.values ?? {};
|
|
113
|
+
const document = await buildNoteExport({
|
|
114
|
+
engine: request.engine,
|
|
115
|
+
text: request.text,
|
|
116
|
+
fileName: request.notePath,
|
|
117
|
+
values,
|
|
118
|
+
...(request.renderBody !== undefined
|
|
119
|
+
? { renderBody: request.renderBody }
|
|
120
|
+
: {}),
|
|
121
|
+
...(request.staticReason !== undefined
|
|
122
|
+
? { staticReason: request.staticReason }
|
|
123
|
+
: {}),
|
|
124
|
+
...(request.packStylesheets !== undefined
|
|
125
|
+
? { packStylesheets: request.packStylesheets }
|
|
126
|
+
: {}),
|
|
127
|
+
...(request.packCount !== undefined
|
|
128
|
+
? { packCount: request.packCount }
|
|
129
|
+
: {}),
|
|
130
|
+
...(request.embedImages !== undefined
|
|
131
|
+
? { embedImages: request.embedImages }
|
|
132
|
+
: {}),
|
|
133
|
+
...(request.hideScriptBlocks !== undefined
|
|
134
|
+
? { hideScriptBlocks: request.hideScriptBlocks }
|
|
135
|
+
: {}),
|
|
136
|
+
...(request.unavailableComponents !== undefined
|
|
137
|
+
? { unavailableComponents: request.unavailableComponents }
|
|
138
|
+
: {}),
|
|
139
|
+
...(request.onDiagnostic !== undefined
|
|
140
|
+
? { onDiagnostic: request.onDiagnostic }
|
|
141
|
+
: {}),
|
|
142
|
+
});
|
|
143
|
+
return {
|
|
144
|
+
html: document.html,
|
|
145
|
+
valueCount: document.valueCount,
|
|
146
|
+
hasScripts: noteHasScripts(request.text),
|
|
147
|
+
render: document.render,
|
|
148
|
+
images: document.images,
|
|
149
|
+
};
|
|
150
|
+
}
|
|
151
|
+
function reasonOf(error) {
|
|
152
|
+
return error instanceof Error ? error.message : String(error);
|
|
153
|
+
}
|
|
154
|
+
/**
|
|
155
|
+
* Exports one note as `format`, gated on `adapter.exports?.formats`. A
|
|
156
|
+
* format the host has not declared returns `undefined` immediately
|
|
157
|
+
* (`createMarkiiHost`'s `exportNote` turns that into the shared
|
|
158
|
+
* `Unsupported` outcome and writes its own diagnostics line, matching
|
|
159
|
+
* every other capability-gated behavior).
|
|
160
|
+
*
|
|
161
|
+
* `renderAnsi` is required to actually produce an `'ansi'` outcome — a
|
|
162
|
+
* host that offers the `'ansi'` FORMAT capability but passes no renderer
|
|
163
|
+
* is a configuration bug on that host's part, not a Markii user's problem
|
|
164
|
+
* to see, so it also resolves to `undefined` here (never a thrown error)
|
|
165
|
+
* and the caller's `Unsupported` fallback covers it.
|
|
166
|
+
*/
|
|
167
|
+
export async function exportNoteFileViaAdapter(adapter, format, request, renderAnsi) {
|
|
168
|
+
const formats = adapter.exports?.formats;
|
|
169
|
+
if (formats === undefined || !formats.has(format))
|
|
170
|
+
return undefined;
|
|
171
|
+
if (format === 'html' || format === 'pdf') {
|
|
172
|
+
const engine = request.engine;
|
|
173
|
+
if (engine === undefined)
|
|
174
|
+
return undefined;
|
|
175
|
+
try {
|
|
176
|
+
const built = await buildExportDocument({ ...request, engine });
|
|
177
|
+
if (format === 'html') {
|
|
178
|
+
const bytes = new TextEncoder().encode(built.html).byteLength;
|
|
179
|
+
const path = await adapter.exports?.resolveTarget(exportDefaultFileName(request.notePath, 'html'), 'html');
|
|
180
|
+
if (path === undefined)
|
|
181
|
+
return undefined;
|
|
182
|
+
await adapter.writeFile(path, new TextEncoder().encode(built.html));
|
|
183
|
+
return {
|
|
184
|
+
kind: 'written',
|
|
185
|
+
path,
|
|
186
|
+
bytes,
|
|
187
|
+
valueCount: built.valueCount,
|
|
188
|
+
hasScripts: built.hasScripts,
|
|
189
|
+
render: built.render,
|
|
190
|
+
images: built.images,
|
|
191
|
+
};
|
|
192
|
+
}
|
|
193
|
+
// 'pdf': the seam only. Degradation policy stays app-side.
|
|
194
|
+
const htmlToPdf = adapter.exports?.htmlToPdf;
|
|
195
|
+
if (htmlToPdf === undefined)
|
|
196
|
+
return undefined;
|
|
197
|
+
const path = await adapter.exports?.resolveTarget(exportDefaultFileName(request.notePath, 'pdf'), 'pdf');
|
|
198
|
+
if (path === undefined)
|
|
199
|
+
return undefined;
|
|
200
|
+
const pdfBytes = await htmlToPdf(built.html);
|
|
201
|
+
await adapter.writeFile(path, pdfBytes);
|
|
202
|
+
return {
|
|
203
|
+
kind: 'written',
|
|
204
|
+
path,
|
|
205
|
+
bytes: pdfBytes.byteLength,
|
|
206
|
+
valueCount: built.valueCount,
|
|
207
|
+
hasScripts: built.hasScripts,
|
|
208
|
+
render: built.render,
|
|
209
|
+
images: built.images,
|
|
210
|
+
};
|
|
211
|
+
}
|
|
212
|
+
catch (error) {
|
|
213
|
+
return { kind: 'failed', reason: reasonOf(error) };
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
if (format === 'md-plain') {
|
|
217
|
+
try {
|
|
218
|
+
const text = mdPlainFromSource(request.text);
|
|
219
|
+
const path = await adapter.exports?.resolveTarget(exportDefaultFileName(request.notePath, 'md-plain'), 'md-plain');
|
|
220
|
+
if (path === undefined)
|
|
221
|
+
return undefined;
|
|
222
|
+
await adapter.writeFile(path, new TextEncoder().encode(text));
|
|
223
|
+
return {
|
|
224
|
+
kind: 'written',
|
|
225
|
+
path,
|
|
226
|
+
bytes: new TextEncoder().encode(text).byteLength,
|
|
227
|
+
valueCount: 0,
|
|
228
|
+
render: { engine: 'static', reason: 'no-packs' },
|
|
229
|
+
images: EMPTY_IMAGE_REPORT,
|
|
230
|
+
};
|
|
231
|
+
}
|
|
232
|
+
catch (error) {
|
|
233
|
+
return { kind: 'failed', reason: reasonOf(error) };
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
// format === 'ansi'
|
|
237
|
+
if (renderAnsi === undefined)
|
|
238
|
+
return undefined;
|
|
239
|
+
try {
|
|
240
|
+
const text = await renderAnsi(request.text);
|
|
241
|
+
const path = await adapter.exports?.resolveTarget(exportDefaultFileName(request.notePath, 'ansi'), 'ansi');
|
|
242
|
+
if (path === undefined)
|
|
243
|
+
return undefined;
|
|
244
|
+
await adapter.writeFile(path, new TextEncoder().encode(text));
|
|
245
|
+
return {
|
|
246
|
+
kind: 'written',
|
|
247
|
+
path,
|
|
248
|
+
bytes: new TextEncoder().encode(text).byteLength,
|
|
249
|
+
valueCount: 0,
|
|
250
|
+
render: { engine: 'static', reason: 'no-packs' },
|
|
251
|
+
images: EMPTY_IMAGE_REPORT,
|
|
252
|
+
};
|
|
253
|
+
}
|
|
254
|
+
catch (error) {
|
|
255
|
+
return { kind: 'failed', reason: reasonOf(error) };
|
|
256
|
+
}
|
|
257
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `HostLabels`: the wording nouns every shared behavior module
|
|
3
|
+
* (`./script-execution.ts`, `./refresh-interval.ts`) interpolates, and the
|
|
4
|
+
* one concrete record per app this repo ships. Each app's `HostAdapter`
|
|
5
|
+
* imports its record rather than inventing its own strings, which is what
|
|
6
|
+
* collapses the survey's near-identical wording pairs (`tmp/W11-survey.md`,
|
|
7
|
+
* section A4) into one template.
|
|
8
|
+
*
|
|
9
|
+
* Pure and dependency-free: re-exported from `../browser.ts` as well as
|
|
10
|
+
* `../index.ts` so VS Code's Node-free webview bundle can read it too.
|
|
11
|
+
*/
|
|
12
|
+
import type { HostLabels } from './adapter.js';
|
|
13
|
+
/** The VS Code extension's labels, taken from its current wording (`apps/vscode/src/script-execution.ts`, `apps/vscode/src/extension.ts`'s output channel). */
|
|
14
|
+
export declare const VSCODE_LABELS: HostLabels;
|
|
15
|
+
/** The Obsidian plugin's labels, taken from its current wording (`apps/obsidian/src/script-execution.ts`, docs/integration.md's host checklist). */
|
|
16
|
+
export declare const OBSIDIAN_LABELS: HostLabels;
|
|
17
|
+
/** The `markii` command line tool's labels: it has no persistent preview panel, and its diagnostics surface is stderr, gated behind `--verbose` for anything short of a failure (`apps/cli/src/main.ts`). */
|
|
18
|
+
export declare const CLI_LABELS: HostLabels;
|
|
19
|
+
/**
|
|
20
|
+
* The quiet marker a rendered note shows when its `uses:` list names packs
|
|
21
|
+
* this host has not installed (docs/packs.md). One sentence for every host;
|
|
22
|
+
* the tooltip beside it, which says how to install, stays host-specific.
|
|
23
|
+
*/
|
|
24
|
+
export declare function packsNotInstalledLabel(missing: readonly string[]): string;
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
/** The VS Code extension's labels, taken from its current wording (`apps/vscode/src/script-execution.ts`, `apps/vscode/src/extension.ts`'s output channel). */
|
|
2
|
+
export const VSCODE_LABELS = {
|
|
3
|
+
appNoun: 'extension',
|
|
4
|
+
previewNoun: 'the Markii preview',
|
|
5
|
+
diagnosticsSurface: 'the Markii output channel',
|
|
6
|
+
deviceNoun: 'this device',
|
|
7
|
+
settingName: 'markii.scriptsDisabled',
|
|
8
|
+
};
|
|
9
|
+
/** The Obsidian plugin's labels, taken from its current wording (`apps/obsidian/src/script-execution.ts`, docs/integration.md's host checklist). */
|
|
10
|
+
export const OBSIDIAN_LABELS = {
|
|
11
|
+
appNoun: 'plugin',
|
|
12
|
+
previewNoun: 'the Markii preview',
|
|
13
|
+
diagnosticsSurface: 'the Markii diagnostics',
|
|
14
|
+
deviceNoun: 'this device',
|
|
15
|
+
};
|
|
16
|
+
/** The `markii` command line tool's labels: it has no persistent preview panel, and its diagnostics surface is stderr, gated behind `--verbose` for anything short of a failure (`apps/cli/src/main.ts`). */
|
|
17
|
+
export const CLI_LABELS = {
|
|
18
|
+
appNoun: 'command line tool',
|
|
19
|
+
previewNoun: 'the rendered note',
|
|
20
|
+
diagnosticsSurface: 'stderr',
|
|
21
|
+
deviceNoun: 'this device',
|
|
22
|
+
};
|
|
23
|
+
/**
|
|
24
|
+
* The quiet marker a rendered note shows when its `uses:` list names packs
|
|
25
|
+
* this host has not installed (docs/packs.md). One sentence for every host;
|
|
26
|
+
* the tooltip beside it, which says how to install, stays host-specific.
|
|
27
|
+
*/
|
|
28
|
+
export function packsNotInstalledLabel(missing) {
|
|
29
|
+
return missing.length === 1
|
|
30
|
+
? `Pack not installed: ${missing[0]}`
|
|
31
|
+
: `Packs not installed: ${missing.join(', ')}`;
|
|
32
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import type { PackArchiveContents, PackArchiveError } from '@markii/pack';
|
|
2
|
+
/** One line describing why `openPackArchive` rejected an archive, for a host's install-diagnostics wording. Passes `kind: 'zip'`'s and `kind: 'missing-entry'`'s message through verbatim (already specific); summarizes a manifest failure plainly. */
|
|
3
|
+
export declare function describeArchiveError(error: PackArchiveError): string;
|
|
4
|
+
/** The filesystem write seam `writeArchiveContents` needs (`./pack-install.ts`). Deliberately narrow: extracting an archive only ever creates files under a directory it first clears, it never reads back or deletes anything else. */
|
|
5
|
+
export interface ArchiveExtractFs {
|
|
6
|
+
/** Removes `absolutePath` and everything under it, or resolves quietly if it does not exist. Never rejects: a directory that fails to clear is treated the same as a fresh one, and the write that follows fails loudly on its own if the path is genuinely unusable. */
|
|
7
|
+
readonly removeDirectory: (absolutePath: string) => Promise<void>;
|
|
8
|
+
/** Creates a directory and any missing parents. May reject on a genuine I/O failure. */
|
|
9
|
+
readonly makeDirectory: (absolutePath: string) => Promise<void>;
|
|
10
|
+
readonly writeFile: (absolutePath: string, bytes: Uint8Array) => Promise<void>;
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* Writes a validated archive's contents into `destinationDir`: `pack.json`
|
|
14
|
+
* (the parsed manifest, re-serialized: `openPackArchive` keeps only the
|
|
15
|
+
* validated manifest fields, not the original bytes, so the written file
|
|
16
|
+
* is a normalized round-trip of the same fields, not a byte-for-byte
|
|
17
|
+
* copy), `webview.js`, `webview.css` and `terminal.js` when the archive has them, and
|
|
18
|
+
* `scripts/*` when it ships any. Does NOT clear `destinationDir` first;
|
|
19
|
+
* a caller that needs a clean re-extract (a reinstall replacing an
|
|
20
|
+
* existing pack) calls `fs.removeDirectory` itself before this.
|
|
21
|
+
*
|
|
22
|
+
* PATH JAIL: `@markii/pack`'s `openPackArchive` has already rejected any
|
|
23
|
+
* entry whose name escapes the archive (`../`, an absolute path) or
|
|
24
|
+
* exceeds its size caps before this function ever sees `archive` — every
|
|
25
|
+
* path it writes here is built from `destinationDir` joined with a plain,
|
|
26
|
+
* already-validated relative name (`pack.json`, `webview.js`,
|
|
27
|
+
* `webview.css`, or a `scripts/`-relative Lua module name), never from
|
|
28
|
+
* anything read back out of the zip's raw entry names.
|
|
29
|
+
*/
|
|
30
|
+
export declare function writeArchiveContents(archive: PackArchiveContents, destinationDir: string, fs: ArchiveExtractFs): Promise<void>;
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `.mkp` pack archive handling, merged from `apps/vscode/src/packs/archive-packs.ts`
|
|
3
|
+
* and `apps/obsidian/src/packs/archive-packs.ts` (survey finding A8). The
|
|
4
|
+
* orchestrator diffed the two files directly (not through the survey's
|
|
5
|
+
* earlier, unverified guess) and found them functionally byte-identical:
|
|
6
|
+
* the same three exports (`describeArchiveError`, `writeArchiveContents`,
|
|
7
|
+
* `ArchiveExtractFs`), the same implementation, the same path-jail
|
|
8
|
+
* reasoning. They differed only in doc-comment wording (which host's
|
|
9
|
+
* install command they described) and one em dash in a comment, which
|
|
10
|
+
* carries no behavior. See `tmp/W11-phase1b.md` for the exact diff output.
|
|
11
|
+
*
|
|
12
|
+
* This module bridges `@markii/pack`'s in-memory archive reader
|
|
13
|
+
* (`openPackArchive`, never modified here) into the write a pack install
|
|
14
|
+
* needs: extracting a validated archive's contents onto disk, under
|
|
15
|
+
* whichever install root the calling host names (`HostPackSource.installRoot`).
|
|
16
|
+
*
|
|
17
|
+
* Environment-free (`ArchiveExtractFs` is an injected seam). The real
|
|
18
|
+
* Node-backed one, `createNodeArchiveExtractFs`, lives in `../node-fs.ts`
|
|
19
|
+
* and is exported from `@markii/host/node`.
|
|
20
|
+
*/
|
|
21
|
+
import { dirnamePath, joinPath } from '../path-util.js';
|
|
22
|
+
/** One line describing why `openPackArchive` rejected an archive, for a host's install-diagnostics wording. Passes `kind: 'zip'`'s and `kind: 'missing-entry'`'s message through verbatim (already specific); summarizes a manifest failure plainly. */
|
|
23
|
+
export function describeArchiveError(error) {
|
|
24
|
+
if (error.kind === 'zip')
|
|
25
|
+
return error.message;
|
|
26
|
+
if (error.kind === 'missing-entry')
|
|
27
|
+
return error.message;
|
|
28
|
+
return `invalid pack.json in archive (${error.errors.join('; ')})`;
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Writes a validated archive's contents into `destinationDir`: `pack.json`
|
|
32
|
+
* (the parsed manifest, re-serialized: `openPackArchive` keeps only the
|
|
33
|
+
* validated manifest fields, not the original bytes, so the written file
|
|
34
|
+
* is a normalized round-trip of the same fields, not a byte-for-byte
|
|
35
|
+
* copy), `webview.js`, `webview.css` and `terminal.js` when the archive has them, and
|
|
36
|
+
* `scripts/*` when it ships any. Does NOT clear `destinationDir` first;
|
|
37
|
+
* a caller that needs a clean re-extract (a reinstall replacing an
|
|
38
|
+
* existing pack) calls `fs.removeDirectory` itself before this.
|
|
39
|
+
*
|
|
40
|
+
* PATH JAIL: `@markii/pack`'s `openPackArchive` has already rejected any
|
|
41
|
+
* entry whose name escapes the archive (`../`, an absolute path) or
|
|
42
|
+
* exceeds its size caps before this function ever sees `archive` — every
|
|
43
|
+
* path it writes here is built from `destinationDir` joined with a plain,
|
|
44
|
+
* already-validated relative name (`pack.json`, `webview.js`,
|
|
45
|
+
* `webview.css`, or a `scripts/`-relative Lua module name), never from
|
|
46
|
+
* anything read back out of the zip's raw entry names.
|
|
47
|
+
*/
|
|
48
|
+
export async function writeArchiveContents(archive, destinationDir, fs) {
|
|
49
|
+
await fs.makeDirectory(destinationDir);
|
|
50
|
+
const encoder = new TextEncoder();
|
|
51
|
+
await fs.writeFile(joinPath(destinationDir, 'pack.json'), encoder.encode(JSON.stringify(archive.manifest, null, 2) + '\n'));
|
|
52
|
+
await fs.writeFile(joinPath(destinationDir, 'webview.js'), archive.scriptBytes);
|
|
53
|
+
if (archive.stylesheetBytes !== undefined) {
|
|
54
|
+
await fs.writeFile(joinPath(destinationDir, 'webview.css'), archive.stylesheetBytes);
|
|
55
|
+
}
|
|
56
|
+
if (archive.terminalScriptBytes !== undefined) {
|
|
57
|
+
await fs.writeFile(joinPath(destinationDir, 'terminal.js'), archive.terminalScriptBytes);
|
|
58
|
+
}
|
|
59
|
+
const moduleNames = Object.keys(archive.scriptModules);
|
|
60
|
+
if (moduleNames.length > 0) {
|
|
61
|
+
const scriptsDir = joinPath(destinationDir, 'scripts');
|
|
62
|
+
for (const name of moduleNames) {
|
|
63
|
+
const target = joinPath(scriptsDir, name);
|
|
64
|
+
await fs.makeDirectory(dirnamePath(target));
|
|
65
|
+
await fs.writeFile(target, archive.scriptModules[name]);
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
}
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
import type { ArchiveExtractFs } from './pack-archive.js';
|
|
2
|
+
import type { HostAdapter, HostLabels } from './adapter.js';
|
|
3
|
+
/** Whether a directory already exists under the install root, injected so this module needs no real disk to test. */
|
|
4
|
+
export type PackDirectoryExists = (absolutePath: string) => Promise<boolean>;
|
|
5
|
+
export interface InstallPackFromArchiveOptions {
|
|
6
|
+
readonly archiveBytes: Uint8Array;
|
|
7
|
+
/** The `.mkp` file's own path, used only in a rejection's diagnostic wording. Never read again once `archiveBytes` is in hand. */
|
|
8
|
+
readonly archivePath: string;
|
|
9
|
+
/** This host's own pack-install directory (`HostPackSource.installRoot()`). Each installed pack gets a subdirectory named by its namespace, since two packs can never share one (docs/packs.md's collision rule), which makes "is this namespace already installed" a plain directory check. */
|
|
10
|
+
readonly installRoot: string;
|
|
11
|
+
readonly exists: PackDirectoryExists;
|
|
12
|
+
readonly extractFs: ArchiveExtractFs;
|
|
13
|
+
/**
|
|
14
|
+
* Namespaces this install may never claim (`HostPackSource.reservedNamespaces()`).
|
|
15
|
+
* Empty for a host with no reserved set (VS Code). Checked before the
|
|
16
|
+
* consent prompt — a reserved namespace cannot be shadowed by an install.
|
|
17
|
+
*/
|
|
18
|
+
readonly reservedNamespaces: ReadonlySet<string>;
|
|
19
|
+
}
|
|
20
|
+
export type InstallPackOutcome = {
|
|
21
|
+
readonly kind: 'installed';
|
|
22
|
+
readonly packName: string;
|
|
23
|
+
readonly installedDir: string;
|
|
24
|
+
readonly replaced: boolean;
|
|
25
|
+
} | {
|
|
26
|
+
readonly kind: 'declined';
|
|
27
|
+
readonly step: 'consent' | 'replace';
|
|
28
|
+
readonly packName: string;
|
|
29
|
+
} | {
|
|
30
|
+
readonly kind: 'reserved';
|
|
31
|
+
readonly packName: string;
|
|
32
|
+
} | {
|
|
33
|
+
readonly kind: 'rejected';
|
|
34
|
+
readonly reason: string;
|
|
35
|
+
};
|
|
36
|
+
/**
|
|
37
|
+
* Never throws: a validation failure, a declined prompt, or a write
|
|
38
|
+
* failure all come back as a structured outcome. Order: validate the
|
|
39
|
+
* archive first (a rejected archive is never even offered the consent
|
|
40
|
+
* prompt), then refuse a reserved namespace, then ask consent to run its
|
|
41
|
+
* code, then, only if a pack of the same namespace is already installed,
|
|
42
|
+
* ask to replace it. Nothing is written until every applicable step has
|
|
43
|
+
* said yes.
|
|
44
|
+
*
|
|
45
|
+
* Every prompt routes through `adapter.prompt` with `kind:
|
|
46
|
+
* 'pack-install-consent'`/`'pack-replace'`, per `HostAdapter`'s contract —
|
|
47
|
+
* a host that can only show a non-blocking surface must deny rather than
|
|
48
|
+
* assume yes.
|
|
49
|
+
*/
|
|
50
|
+
export declare function installPackFromArchive(adapter: HostAdapter, options: InstallPackFromArchiveOptions): Promise<InstallPackOutcome>;
|
|
51
|
+
/**
|
|
52
|
+
* The consent prompt's wording (AGENTS.md's product principles: "clean is
|
|
53
|
+
* not silent," and a consent step must say plainly what it authorizes).
|
|
54
|
+
* States outright that the pack's code will run inside the preview, since
|
|
55
|
+
* this is the one place that consent is asked, so it has to say it, not
|
|
56
|
+
* hint at it.
|
|
57
|
+
*
|
|
58
|
+
* CANONICAL WORDING CHOSEN over the two apps' prior copy (report this as a
|
|
59
|
+
* before/after pair): "from someone you trust" (Obsidian's phrasing) over
|
|
60
|
+
* "from a source you trust" (VS Code's), and `labels.previewNoun` ("the
|
|
61
|
+
* Markii preview" for both hosts) in place of VS Code's prior "every
|
|
62
|
+
* Markii preview" — `HostLabels.previewNoun` already gives both hosts the
|
|
63
|
+
* same noun, so the interpolation settles the wording rather than
|
|
64
|
+
* widening `HostLabels` for a distinction the record does not carry.
|
|
65
|
+
*/
|
|
66
|
+
export declare function installConsentMessage(packName: string, labels: HostLabels): string;
|
|
67
|
+
/**
|
|
68
|
+
* The collision confirmation's wording: a namespace already installed,
|
|
69
|
+
* asked before replacing it. CANONICAL WORDING: "removes" (VS Code's prior
|
|
70
|
+
* wording) over Obsidian's prior "deletes" — see `tmp/W11-phase1b.md`'s
|
|
71
|
+
* before/after table for the exact strings this replaces.
|
|
72
|
+
*/
|
|
73
|
+
export declare function installReplaceConfirmMessage(packName: string): string;
|
|
74
|
+
/**
|
|
75
|
+
* The one result message for the install command, matching this host's
|
|
76
|
+
* wording rules (two short sentences, no em dashes or parentheses in the
|
|
77
|
+
* popup; the full detail goes to the diagnostics surface).
|
|
78
|
+
*
|
|
79
|
+
* `reloadsAutomatically` is a per-host BEHAVIOR fact, not a wording noun:
|
|
80
|
+
* Obsidian's install command reloads every open view's packs immediately
|
|
81
|
+
* after a successful install, so its message says so; VS Code's install
|
|
82
|
+
* command does not (an installed pack still needs to be added to
|
|
83
|
+
* `markii.packs` before anything loads it), so it doesn't. Defaults to
|
|
84
|
+
* `false`.
|
|
85
|
+
*/
|
|
86
|
+
export declare function installPackMessage(outcome: InstallPackOutcome, archivePath: string, labels: HostLabels, reloadsAutomatically?: boolean): string;
|
|
87
|
+
/** The full diagnostics-surface detail for one install attempt (AGENTS.md's "clean is not silent": the other of a failure's two homes). */
|
|
88
|
+
export declare function installPackDiagnosticLines(outcome: InstallPackOutcome, archivePath: string): string[];
|