@markii/host 0.17.0-next.4 → 0.17.0-next.6
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 +28 -1
- package/dist/browser-worker.classic.js +43 -43
- package/dist/browser-worker.js +43 -43
- package/dist/conformance/host/17-export-linked-set/expected.json +17 -0
- package/dist/conformance/host/17-export-linked-set/files/second.mk.md +3 -0
- package/dist/conformance/host/17-export-linked-set/note.mk.md +4 -0
- package/dist/conformance/host/17-export-linked-set/scenario.json +6 -0
- package/dist/conformance/host/18-last-run-and-grants/expected.json +42 -0
- package/dist/conformance/host/18-last-run-and-grants/note.mk.md +5 -0
- package/dist/conformance/host/18-last-run-and-grants/scenario.json +27 -0
- package/dist/conformance/host/README.md +24 -14
- package/dist/conformance.d.ts +1 -1
- package/dist/conformance.js +1 -1
- package/dist/diagnostics/render-diagnostics.d.ts +1 -1
- package/dist/host/adapter.d.ts +28 -6
- package/dist/host/cascade-export-behavior.d.ts +26 -8
- package/dist/host/cascade-export-behavior.js +64 -1
- package/dist/host/conformance-runner.d.ts +11 -1
- package/dist/host/conformance-runner.js +80 -4
- package/dist/host/create-host.d.ts +250 -44
- package/dist/host/create-host.js +222 -96
- package/dist/host/export-behavior.d.ts +2 -0
- package/dist/host/export-behavior.js +10 -4
- package/dist/host/pack-archive.d.ts +7 -6
- package/dist/host/pack-archive.js +11 -7
- package/dist/host/pack-install.js +9 -7
- package/dist/host/pack-remove.d.ts +7 -18
- package/dist/host/pack-remove.js +2 -0
- package/dist/host/pack-trust-behavior.d.ts +13 -36
- package/dist/host/pack-trust-behavior.js +23 -2
- package/dist/host/run-behavior.d.ts +25 -34
- package/dist/host/run-behavior.js +31 -5
- package/dist/host/terminal-packs.d.ts +1 -1
- package/dist/index.d.ts +8 -10
- package/dist/index.js +1 -1
- package/dist/internal.d.ts +10 -5
- package/dist/internal.js +4 -3
- package/dist/node-worker.js +3 -1
- package/dist/node.d.ts +1 -2
- package/dist/node.js +1 -1
- package/dist/run/bundle-run.d.ts +1 -1
- package/dist/run/bundle-run.js +1 -1
- package/dist/run/node-isolate.d.ts +6 -2
- package/dist/run/node-isolate.js +2 -0
- package/dist/run/run-host.d.ts +2 -2
- package/dist/run/worker-entry-browser.d.ts +6 -1
- package/dist/run/worker-entry-browser.js +62 -5
- package/package.json +11 -11
package/dist/host/create-host.js
CHANGED
|
@@ -1,6 +1,12 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { readPersistedValues } from '../run/run-flow.js';
|
|
2
|
+
import { clearGrantForDocument } from '../run/grant-flow.js';
|
|
3
|
+
import { readLastRunTrace } from '../run/run-trace.js';
|
|
4
|
+
import { staleValuesForRehydration } from '../run/stale-values.js';
|
|
5
|
+
import { runViaAdapter } from './run-behavior.js';
|
|
2
6
|
import { installPackDiagnosticLines, installPackFromArchive, } from './pack-install.js';
|
|
3
7
|
import { loadPacksViaAdapter, packDiscoveryIoFromAdapter, } from './pack-load.js';
|
|
8
|
+
import { exportBaseName } from '../export/note-export.js';
|
|
9
|
+
import { exportLinkedSetViaAdapter, } from './cascade-export-behavior.js';
|
|
4
10
|
import { buildComponentCatalog } from '../insert/component-catalog.js';
|
|
5
11
|
import { formatPackDiagnosticLines, skippedPackFolderLine, } from '../packs/pack-diagnostics.js';
|
|
6
12
|
import { readTerminalPackScripts, } from './terminal-packs.js';
|
|
@@ -13,7 +19,22 @@ function unsupported(adapter, capability, detail) {
|
|
|
13
19
|
adapter.diagnostics(`declined (${capability} unsupported on ${adapter.id}): ${detail}`);
|
|
14
20
|
return { kind: 'unsupported', capability, detail };
|
|
15
21
|
}
|
|
22
|
+
/** Writes one diagnostics line for a rejected adapter primitive and returns the `failed` outcome. Never throws. */
|
|
23
|
+
function failedOutcome(adapter, what, err) {
|
|
24
|
+
const reason = err instanceof Error ? err.message : String(err);
|
|
25
|
+
try {
|
|
26
|
+
adapter.diagnostics(`${what} failed: ${reason}`);
|
|
27
|
+
}
|
|
28
|
+
catch {
|
|
29
|
+
// the diagnostics surface itself is what failed
|
|
30
|
+
}
|
|
31
|
+
return { kind: 'failed', reason };
|
|
32
|
+
}
|
|
16
33
|
export function createMarkiiHost(adapter, options = {}) {
|
|
34
|
+
return createMarkiiHostWithSeams(adapter, options);
|
|
35
|
+
}
|
|
36
|
+
/** `createMarkiiHost` with the test seams. Exported from `@markii/host/internal` only. */
|
|
37
|
+
export function createMarkiiHostWithSeams(adapter, options = {}) {
|
|
17
38
|
const packIo = options.packDiscoveryIo ?? packDiscoveryIoFromAdapter(adapter);
|
|
18
39
|
/**
|
|
19
40
|
* The pack source every load reads: under `trust: 'device-list'` the
|
|
@@ -33,12 +54,22 @@ export function createMarkiiHost(adapter, options = {}) {
|
|
|
33
54
|
};
|
|
34
55
|
}
|
|
35
56
|
/** The pack-aware insert/completion catalog, built fresh from this host's currently authorized folders. Not cached here: `./editor-behavior.js`'s `createCatalogCache` is the caching seam, and a caller wiring a live editor (repeated completion/hover calls per keystroke) is expected to wrap this call in one. */
|
|
36
|
-
async function
|
|
57
|
+
async function readCatalog() {
|
|
37
58
|
if (!adapter.packs)
|
|
38
59
|
return buildComponentCatalog([]);
|
|
39
60
|
const { packs } = await loadPacksViaAdapter(packSource(adapter.packs), packIo);
|
|
40
61
|
return buildComponentCatalog([...(adapter.packs.bundledPacks?.() ?? []), ...packs], adapter.packs.engine);
|
|
41
62
|
}
|
|
63
|
+
/** The catalog for the cache. A rejected pack read writes one diagnostics line and leaves the standard set, so completion and hover still answer. */
|
|
64
|
+
async function currentCatalog() {
|
|
65
|
+
try {
|
|
66
|
+
return await readCatalog();
|
|
67
|
+
}
|
|
68
|
+
catch (err) {
|
|
69
|
+
failedOutcome(adapter, 'read the pack catalog', err);
|
|
70
|
+
return buildComponentCatalog([]);
|
|
71
|
+
}
|
|
72
|
+
}
|
|
42
73
|
/** Completion and hover fire per keystroke, so they read a cached catalog. `insertComponent` stays uncached: it runs once per command. */
|
|
43
74
|
const catalogCache = createCatalogCache(currentCatalog);
|
|
44
75
|
function documentTextFor(editor, request) {
|
|
@@ -74,15 +105,47 @@ export function createMarkiiHost(adapter, options = {}) {
|
|
|
74
105
|
if (!adapter.isolate) {
|
|
75
106
|
return unsupported(adapter, 'isolate', 'no isolate is configured, so scripts cannot run.');
|
|
76
107
|
}
|
|
77
|
-
|
|
78
|
-
|
|
108
|
+
try {
|
|
109
|
+
return await runViaAdapter(adapter, request, options.spawnRun);
|
|
110
|
+
}
|
|
111
|
+
catch (err) {
|
|
112
|
+
// `runViaAdapter` reports a throwing run as `failed` itself. This
|
|
113
|
+
// catches only what threw outside it, so `run` never rejects.
|
|
114
|
+
const reason = err instanceof Error ? err.message : String(err);
|
|
115
|
+
try {
|
|
116
|
+
adapter.diagnostics(`run failed: ${reason}`);
|
|
117
|
+
}
|
|
118
|
+
catch {
|
|
119
|
+
// the diagnostics surface itself is what failed
|
|
120
|
+
}
|
|
121
|
+
return { kind: 'failed', reason };
|
|
122
|
+
}
|
|
123
|
+
},
|
|
124
|
+
lastRun(documentKey) {
|
|
125
|
+
const persistedValues = readPersistedValues(adapter.memento, documentKey);
|
|
126
|
+
const trace = readLastRunTrace(adapter.memento, documentKey);
|
|
127
|
+
return {
|
|
128
|
+
values: staleValuesForRehydration(persistedValues),
|
|
129
|
+
persistedValues,
|
|
130
|
+
...(trace !== undefined ? { trace } : {}),
|
|
131
|
+
};
|
|
132
|
+
},
|
|
133
|
+
forgetGrants(documentKey) {
|
|
134
|
+
return clearGrantForDocument(adapter.memento, documentKey);
|
|
79
135
|
},
|
|
80
136
|
async exportNote(request) {
|
|
81
137
|
const formats = adapter.exports?.formats;
|
|
82
138
|
if (formats === undefined || !formats.has(request.format)) {
|
|
83
139
|
return unsupported(adapter, 'export-format', `export format "${request.format}" is not available on ${adapter.id}.`);
|
|
84
140
|
}
|
|
141
|
+
if (request.targetPath === undefined &&
|
|
142
|
+
adapter.exports?.resolveTarget === undefined) {
|
|
143
|
+
return unsupported(adapter, 'export-format', `export format "${request.format}" has no destination on ${adapter.id}: the host declares no resolveTarget and the request has no targetPath.`);
|
|
144
|
+
}
|
|
85
145
|
const outcome = await exportNoteFileViaAdapter(adapter, request.format, {
|
|
146
|
+
...(request.targetPath !== undefined
|
|
147
|
+
? { targetPath: request.targetPath }
|
|
148
|
+
: {}),
|
|
86
149
|
...(options.htmlEngine !== undefined
|
|
87
150
|
? { engine: options.htmlEngine }
|
|
88
151
|
: {}),
|
|
@@ -116,7 +179,7 @@ export function createMarkiiHost(adapter, options = {}) {
|
|
|
116
179
|
}, options.renderAnsi);
|
|
117
180
|
if (outcome === undefined) {
|
|
118
181
|
adapter.diagnostics(`export declined: format "${request.format}" has no working export path on ${adapter.id} (a cancelled picker, or a missing renderer/PDF seam).`);
|
|
119
|
-
return { kind: '
|
|
182
|
+
return { kind: 'declined' };
|
|
120
183
|
}
|
|
121
184
|
for (const line of exportDiagnosticLines(outcome)) {
|
|
122
185
|
adapter.diagnostics(line);
|
|
@@ -136,6 +199,29 @@ export function createMarkiiHost(adapter, options = {}) {
|
|
|
136
199
|
images: outcome.images,
|
|
137
200
|
};
|
|
138
201
|
},
|
|
202
|
+
async exportLinkedSet(request) {
|
|
203
|
+
// A host offers the linked-set export when it declares `exports` and
|
|
204
|
+
// either names a `resolveLinkedSetTarget` or gets a `targetPath` with
|
|
205
|
+
// the request.
|
|
206
|
+
if (adapter.exports === undefined ||
|
|
207
|
+
(request.targetPath === undefined &&
|
|
208
|
+
adapter.exports.resolveLinkedSetTarget === undefined)) {
|
|
209
|
+
return unsupported(adapter, 'linked-set-export', `the linked-set export is not available on ${adapter.id}.`);
|
|
210
|
+
}
|
|
211
|
+
if (options.htmlEngine === undefined) {
|
|
212
|
+
return unsupported(adapter, 'linked-set-export', 'no HTML export engine was supplied, so the linked-set export cannot render.');
|
|
213
|
+
}
|
|
214
|
+
const outcome = await exportLinkedSetViaAdapter(adapter, {
|
|
215
|
+
...request,
|
|
216
|
+
engine: options.htmlEngine,
|
|
217
|
+
suggestedFileName: `${exportBaseName(request.rootPath)}.zip`,
|
|
218
|
+
});
|
|
219
|
+
if (outcome === undefined) {
|
|
220
|
+
adapter.diagnostics(`linked-set export declined: no archive target was chosen on ${adapter.id}.`);
|
|
221
|
+
return { kind: 'declined' };
|
|
222
|
+
}
|
|
223
|
+
return outcome;
|
|
224
|
+
},
|
|
139
225
|
async installPack(request) {
|
|
140
226
|
if (!adapter.packs?.installRoot) {
|
|
141
227
|
return unsupported(adapter, 'packs', 'this host has no pack-install destination configured.');
|
|
@@ -164,29 +250,34 @@ export function createMarkiiHost(adapter, options = {}) {
|
|
|
164
250
|
return unsupported(adapter, 'packs', 'this host has no pack folders configured.');
|
|
165
251
|
}
|
|
166
252
|
catalogCache.invalidate();
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
if (usesPackTrust(adapter)) {
|
|
177
|
-
for (const namespace of (await listInstalledPacks(adapter))
|
|
178
|
-
.notEnabled) {
|
|
179
|
-
adapter.diagnostics(notEnabledPackDiagnosticLine(namespace, adapter.labels.deviceNoun));
|
|
253
|
+
try {
|
|
254
|
+
const result = await loadPacksViaAdapter(packSource(adapter.packs), packIo);
|
|
255
|
+
for (const line of formatPackDiagnosticLines({
|
|
256
|
+
packs: result.packs,
|
|
257
|
+
skipped: result.skipped,
|
|
258
|
+
engine: adapter.packs.engine,
|
|
259
|
+
cssWarnings: [],
|
|
260
|
+
})) {
|
|
261
|
+
adapter.diagnostics(line);
|
|
180
262
|
}
|
|
263
|
+
if (usesPackTrust(adapter)) {
|
|
264
|
+
for (const namespace of (await listInstalledPacks(adapter))
|
|
265
|
+
.notEnabled) {
|
|
266
|
+
adapter.diagnostics(notEnabledPackDiagnosticLine(namespace, adapter.labels.deviceNoun));
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
return {
|
|
270
|
+
kind: 'loaded',
|
|
271
|
+
namespaces: result.namespaces,
|
|
272
|
+
packs: result.packs,
|
|
273
|
+
skipped: result.skipped,
|
|
274
|
+
components: result.components,
|
|
275
|
+
unavailable: result.unavailable,
|
|
276
|
+
};
|
|
277
|
+
}
|
|
278
|
+
catch (err) {
|
|
279
|
+
return failedOutcome(adapter, 'load packs', err);
|
|
181
280
|
}
|
|
182
|
-
return {
|
|
183
|
-
kind: 'loaded',
|
|
184
|
-
namespaces: result.namespaces,
|
|
185
|
-
packs: result.packs,
|
|
186
|
-
skipped: result.skipped,
|
|
187
|
-
components: result.components,
|
|
188
|
-
unavailable: result.unavailable,
|
|
189
|
-
};
|
|
190
281
|
},
|
|
191
282
|
async loadTerminalPacks() {
|
|
192
283
|
if (!adapter.packs) {
|
|
@@ -196,34 +287,39 @@ export function createMarkiiHost(adapter, options = {}) {
|
|
|
196
287
|
return unsupported(adapter, 'packs', 'this host does not run the terminal engine, so it has no terminal packs.');
|
|
197
288
|
}
|
|
198
289
|
catalogCache.invalidate();
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
290
|
+
try {
|
|
291
|
+
const result = await loadPacksViaAdapter(packSource(adapter.packs), packIo);
|
|
292
|
+
const read = await readTerminalPackScripts(adapter, result.packs);
|
|
293
|
+
for (const line of formatPackDiagnosticLines({
|
|
294
|
+
packs: result.packs,
|
|
295
|
+
skipped: result.skipped,
|
|
296
|
+
engine: 'ansi',
|
|
297
|
+
cssWarnings: [],
|
|
298
|
+
})) {
|
|
299
|
+
adapter.diagnostics(line);
|
|
300
|
+
}
|
|
301
|
+
for (const line of read.problems)
|
|
302
|
+
adapter.diagnostics(line);
|
|
303
|
+
const failureLines = [
|
|
304
|
+
...result.skipped
|
|
305
|
+
.filter((entry) => entry.engineUnsupported === undefined)
|
|
306
|
+
.map(skippedPackFolderLine),
|
|
307
|
+
...read.problems,
|
|
308
|
+
];
|
|
309
|
+
return {
|
|
310
|
+
kind: 'loaded',
|
|
311
|
+
namespaces: result.namespaces,
|
|
312
|
+
packs: result.packs,
|
|
313
|
+
skipped: result.skipped,
|
|
314
|
+
components: result.components,
|
|
315
|
+
unavailable: result.unavailable,
|
|
316
|
+
scripts: read.scripts,
|
|
317
|
+
failureLines,
|
|
318
|
+
};
|
|
319
|
+
}
|
|
320
|
+
catch (err) {
|
|
321
|
+
return failedOutcome(adapter, 'load terminal packs', err);
|
|
208
322
|
}
|
|
209
|
-
for (const line of read.problems)
|
|
210
|
-
adapter.diagnostics(line);
|
|
211
|
-
const failureLines = [
|
|
212
|
-
...result.skipped
|
|
213
|
-
.filter((entry) => entry.engineUnsupported === undefined)
|
|
214
|
-
.map(skippedPackFolderLine),
|
|
215
|
-
...read.problems,
|
|
216
|
-
];
|
|
217
|
-
return {
|
|
218
|
-
kind: 'loaded',
|
|
219
|
-
namespaces: result.namespaces,
|
|
220
|
-
packs: result.packs,
|
|
221
|
-
skipped: result.skipped,
|
|
222
|
-
components: result.components,
|
|
223
|
-
unavailable: result.unavailable,
|
|
224
|
-
scripts: read.scripts,
|
|
225
|
-
failureLines,
|
|
226
|
-
};
|
|
227
323
|
},
|
|
228
324
|
async removePack(namespace) {
|
|
229
325
|
const packs = adapter.packs;
|
|
@@ -245,7 +341,12 @@ export function createMarkiiHost(adapter, options = {}) {
|
|
|
245
341
|
if (!usesPackTrust(adapter)) {
|
|
246
342
|
return unsupported(adapter, 'pack-trust', 'this host does not gate installed packs on a trust list.');
|
|
247
343
|
}
|
|
248
|
-
|
|
344
|
+
try {
|
|
345
|
+
return { kind: 'listed', ...(await listInstalledPacks(adapter)) };
|
|
346
|
+
}
|
|
347
|
+
catch (err) {
|
|
348
|
+
return failedOutcome(adapter, 'list installed packs', err);
|
|
349
|
+
}
|
|
249
350
|
},
|
|
250
351
|
async enablePack(namespace) {
|
|
251
352
|
if (!usesPackTrust(adapter)) {
|
|
@@ -270,17 +371,31 @@ export function createMarkiiHost(adapter, options = {}) {
|
|
|
270
371
|
if (!editor) {
|
|
271
372
|
return unsupported(adapter, 'editor', 'completion is not available: this host has no live editor.');
|
|
272
373
|
}
|
|
273
|
-
|
|
374
|
+
try {
|
|
375
|
+
return completeWith(await catalogCache.get(), request, editor);
|
|
376
|
+
}
|
|
377
|
+
catch (err) {
|
|
378
|
+
return failedOutcome(adapter, 'completion', err);
|
|
379
|
+
}
|
|
274
380
|
},
|
|
275
381
|
completeAtSync(request) {
|
|
276
382
|
const editor = adapter.editor;
|
|
277
383
|
if (!editor) {
|
|
278
384
|
return unsupported(adapter, 'editor', 'completion is not available: this host has no live editor.');
|
|
279
385
|
}
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
386
|
+
try {
|
|
387
|
+
const warm = catalogCache.peek();
|
|
388
|
+
if (warm === undefined) {
|
|
389
|
+
catalogCache.get().catch((err) => {
|
|
390
|
+
failedOutcome(adapter, 'completion catalog', err);
|
|
391
|
+
});
|
|
392
|
+
}
|
|
393
|
+
return completeWith(warm ?? buildComponentCatalog([]), request, editor);
|
|
394
|
+
}
|
|
395
|
+
catch (err) {
|
|
396
|
+
failedOutcome(adapter, 'completion', err);
|
|
397
|
+
return { kind: 'none' };
|
|
398
|
+
}
|
|
284
399
|
},
|
|
285
400
|
invalidateCatalog() {
|
|
286
401
|
catalogCache.invalidate();
|
|
@@ -290,48 +405,59 @@ export function createMarkiiHost(adapter, options = {}) {
|
|
|
290
405
|
if (!editor) {
|
|
291
406
|
return unsupported(adapter, 'editor', 'hover is not available: this host has no live editor.');
|
|
292
407
|
}
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
408
|
+
try {
|
|
409
|
+
const catalog = await catalogCache.get();
|
|
410
|
+
const hover = hoverAtViaEditor({ documentText: () => documentTextFor(editor, request) }, request, catalog);
|
|
411
|
+
if (hover === undefined)
|
|
412
|
+
return { kind: 'none' };
|
|
413
|
+
// The formatted text (summary, attributes, and the usage example),
|
|
414
|
+
// not the bare summary sentence: a component's description alone
|
|
415
|
+
// often never repeats its own directive name (see
|
|
416
|
+
// `./editor-behavior.js`'s `hoverDocumentationText` doc comment),
|
|
417
|
+
// while the usage example always does.
|
|
418
|
+
return {
|
|
419
|
+
kind: 'hover',
|
|
420
|
+
text: formatComponentDocumentation(hover.documentation),
|
|
421
|
+
documentation: hover.documentation,
|
|
422
|
+
start: hover.start,
|
|
423
|
+
end: hover.end,
|
|
424
|
+
};
|
|
425
|
+
}
|
|
426
|
+
catch (err) {
|
|
427
|
+
return failedOutcome(adapter, 'hover', err);
|
|
428
|
+
}
|
|
309
429
|
},
|
|
310
430
|
async insertComponent(request) {
|
|
311
431
|
if (!adapter.editor) {
|
|
312
432
|
return unsupported(adapter, 'editor', 'insert component is not available: this host has no live editor.');
|
|
313
433
|
}
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
434
|
+
try {
|
|
435
|
+
const catalog = await readCatalog();
|
|
436
|
+
const component = catalog.find((entry) => entry.directiveName === request.name);
|
|
437
|
+
if (component === undefined) {
|
|
438
|
+
adapter.diagnostics(`insert component: no catalog entry named "${request.name}".`);
|
|
439
|
+
return { kind: 'not-found', name: request.name };
|
|
440
|
+
}
|
|
441
|
+
const cursor = adapter.editor.cursor();
|
|
442
|
+
const documentText = adapter.editor.documentText();
|
|
443
|
+
const plan = insertComponentPlan(component, documentText, cursor.line, cursor.column);
|
|
444
|
+
const edits = plan.edits.map((edit) => ({
|
|
445
|
+
line: edit.line,
|
|
446
|
+
startColumn: edit.startColumn,
|
|
447
|
+
endColumn: edit.endColumn,
|
|
448
|
+
text: edit.text,
|
|
449
|
+
}));
|
|
450
|
+
const applied = await adapter.editor.applyEdits(edits);
|
|
451
|
+
if (!applied) {
|
|
452
|
+
const reason = 'the host declined to apply the edit';
|
|
453
|
+
adapter.diagnostics(`insert component: the host declined to apply the edit for "${request.name}".`);
|
|
454
|
+
return { kind: 'failed', reason };
|
|
455
|
+
}
|
|
456
|
+
return { kind: 'inserted', cursor: plan.cursor };
|
|
457
|
+
}
|
|
458
|
+
catch (err) {
|
|
459
|
+
return failedOutcome(adapter, 'insert component', err);
|
|
460
|
+
}
|
|
335
461
|
},
|
|
336
462
|
diagnostics(line) {
|
|
337
463
|
adapter.diagnostics(line);
|
|
@@ -70,6 +70,8 @@ export declare function imageEmbedDiagnosticLines(images: EmbeddedImageReport):
|
|
|
70
70
|
export declare function exportDiagnosticLines(outcome: NoteFileExportOutcome): string[];
|
|
71
71
|
/** What `exportHtmlDocumentViaAdapter` needs beyond the note's own text and path. */
|
|
72
72
|
export interface ExportHtmlDocumentRequest {
|
|
73
|
+
/** Where to write the file. When present the adapter's `resolveTarget` is not asked. */
|
|
74
|
+
readonly targetPath?: string;
|
|
73
75
|
/** The static HTML engine. Required for the `html` and `pdf` formats; `exportNoteFileViaAdapter` declines those without it. */
|
|
74
76
|
readonly engine?: HtmlExportEngine;
|
|
75
77
|
readonly notePath: string;
|
|
@@ -148,6 +148,12 @@ export async function buildExportDocument(request) {
|
|
|
148
148
|
images: document.images,
|
|
149
149
|
};
|
|
150
150
|
}
|
|
151
|
+
/** The path an export writes to: the request's own `targetPath`, else whatever the adapter's `resolveTarget` picks, which resolves `undefined` on a cancel. */
|
|
152
|
+
async function exportTarget(adapter, request, format) {
|
|
153
|
+
if (request.targetPath !== undefined)
|
|
154
|
+
return request.targetPath;
|
|
155
|
+
return adapter.exports?.resolveTarget?.(exportDefaultFileName(request.notePath, format), format);
|
|
156
|
+
}
|
|
151
157
|
function reasonOf(error) {
|
|
152
158
|
return error instanceof Error ? error.message : String(error);
|
|
153
159
|
}
|
|
@@ -176,7 +182,7 @@ export async function exportNoteFileViaAdapter(adapter, format, request, renderA
|
|
|
176
182
|
const built = await buildExportDocument({ ...request, engine });
|
|
177
183
|
if (format === 'html') {
|
|
178
184
|
const bytes = new TextEncoder().encode(built.html).byteLength;
|
|
179
|
-
const path = await adapter
|
|
185
|
+
const path = await exportTarget(adapter, request, 'html');
|
|
180
186
|
if (path === undefined)
|
|
181
187
|
return undefined;
|
|
182
188
|
await adapter.writeFile(path, new TextEncoder().encode(built.html));
|
|
@@ -194,7 +200,7 @@ export async function exportNoteFileViaAdapter(adapter, format, request, renderA
|
|
|
194
200
|
const htmlToPdf = adapter.exports?.htmlToPdf;
|
|
195
201
|
if (htmlToPdf === undefined)
|
|
196
202
|
return undefined;
|
|
197
|
-
const path = await adapter
|
|
203
|
+
const path = await exportTarget(adapter, request, 'pdf');
|
|
198
204
|
if (path === undefined)
|
|
199
205
|
return undefined;
|
|
200
206
|
const pdfBytes = await htmlToPdf(built.html);
|
|
@@ -216,7 +222,7 @@ export async function exportNoteFileViaAdapter(adapter, format, request, renderA
|
|
|
216
222
|
if (format === 'md-plain') {
|
|
217
223
|
try {
|
|
218
224
|
const text = mdPlainFromSource(request.text);
|
|
219
|
-
const path = await adapter
|
|
225
|
+
const path = await exportTarget(adapter, request, 'md-plain');
|
|
220
226
|
if (path === undefined)
|
|
221
227
|
return undefined;
|
|
222
228
|
await adapter.writeFile(path, new TextEncoder().encode(text));
|
|
@@ -238,7 +244,7 @@ export async function exportNoteFileViaAdapter(adapter, format, request, renderA
|
|
|
238
244
|
return undefined;
|
|
239
245
|
try {
|
|
240
246
|
const text = await renderAnsi(request.text);
|
|
241
|
-
const path = await adapter
|
|
247
|
+
const path = await exportTarget(adapter, request, 'ansi');
|
|
242
248
|
if (path === undefined)
|
|
243
249
|
return undefined;
|
|
244
250
|
await adapter.writeFile(path, new TextEncoder().encode(text));
|
|
@@ -4,12 +4,13 @@ export declare function describeArchiveError(error: PackArchiveError): string;
|
|
|
4
4
|
/** Writes one file and creates its missing parent folders: `HostAdapter.writeFile`. */
|
|
5
5
|
export type ArchiveFileWriter = (absolutePath: string, bytes: Uint8Array) => Promise<void>;
|
|
6
6
|
/**
|
|
7
|
-
* Writes a validated archive's contents into `destinationDir`: `
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
7
|
+
* Writes a validated archive's contents into `destinationDir`: `webview.js`,
|
|
8
|
+
* `webview.css` and `terminal.js` when the archive has them, `scripts/*`
|
|
9
|
+
* when it ships any, and then `pack.json` (the parsed manifest,
|
|
10
|
+
* re-serialized: `openPackArchive` keeps only the validated manifest
|
|
11
|
+
* fields, not the original bytes, so the written file is a normalized
|
|
12
|
+
* round-trip of the same fields, not a byte-for-byte copy). The manifest is
|
|
13
|
+
* written last, so a failure midway leaves no loadable pack. Does NOT clear `destinationDir` first;
|
|
13
14
|
* a caller that needs a clean re-extract (a reinstall replacing an
|
|
14
15
|
* existing pack) removes the folder itself before this.
|
|
15
16
|
*
|
|
@@ -21,12 +21,13 @@ export function describeArchiveError(error) {
|
|
|
21
21
|
return `invalid pack.json in archive (${error.errors.join('; ')})`;
|
|
22
22
|
}
|
|
23
23
|
/**
|
|
24
|
-
* Writes a validated archive's contents into `destinationDir`: `
|
|
25
|
-
*
|
|
26
|
-
*
|
|
27
|
-
*
|
|
28
|
-
*
|
|
29
|
-
*
|
|
24
|
+
* Writes a validated archive's contents into `destinationDir`: `webview.js`,
|
|
25
|
+
* `webview.css` and `terminal.js` when the archive has them, `scripts/*`
|
|
26
|
+
* when it ships any, and then `pack.json` (the parsed manifest,
|
|
27
|
+
* re-serialized: `openPackArchive` keeps only the validated manifest
|
|
28
|
+
* fields, not the original bytes, so the written file is a normalized
|
|
29
|
+
* round-trip of the same fields, not a byte-for-byte copy). The manifest is
|
|
30
|
+
* written last, so a failure midway leaves no loadable pack. Does NOT clear `destinationDir` first;
|
|
30
31
|
* a caller that needs a clean re-extract (a reinstall replacing an
|
|
31
32
|
* existing pack) removes the folder itself before this.
|
|
32
33
|
*
|
|
@@ -40,7 +41,6 @@ export function describeArchiveError(error) {
|
|
|
40
41
|
*/
|
|
41
42
|
export async function writeArchiveContents(archive, destinationDir, writeFile) {
|
|
42
43
|
const encoder = new TextEncoder();
|
|
43
|
-
await writeFile(joinPath(destinationDir, 'pack.json'), encoder.encode(JSON.stringify(archive.manifest, null, 2) + '\n'));
|
|
44
44
|
await writeFile(joinPath(destinationDir, 'webview.js'), archive.scriptBytes);
|
|
45
45
|
if (archive.stylesheetBytes !== undefined) {
|
|
46
46
|
await writeFile(joinPath(destinationDir, 'webview.css'), archive.stylesheetBytes);
|
|
@@ -56,4 +56,8 @@ export async function writeArchiveContents(archive, destinationDir, writeFile) {
|
|
|
56
56
|
await writeFile(target, archive.scriptModules[name]);
|
|
57
57
|
}
|
|
58
58
|
}
|
|
59
|
+
// The manifest goes last. Discovery treats a folder with no `pack.json`
|
|
60
|
+
// as no pack, so an install that fails partway never leaves a manifest
|
|
61
|
+
// beside a missing script.
|
|
62
|
+
await writeFile(joinPath(destinationDir, 'pack.json'), encoder.encode(JSON.stringify(archive.manifest, null, 2) + '\n'));
|
|
59
63
|
}
|
|
@@ -43,7 +43,7 @@ export async function installPackFromArchive(adapter, options) {
|
|
|
43
43
|
const opened = await openPackArchive(archiveBytes);
|
|
44
44
|
if (!opened.ok) {
|
|
45
45
|
return {
|
|
46
|
-
kind: '
|
|
46
|
+
kind: 'invalid',
|
|
47
47
|
reason: `"${archivePath}" is not a valid pack archive: ${describeArchiveError(opened.error)}`,
|
|
48
48
|
};
|
|
49
49
|
}
|
|
@@ -62,7 +62,7 @@ export async function installPackFromArchive(adapter, options) {
|
|
|
62
62
|
});
|
|
63
63
|
}
|
|
64
64
|
catch (err) {
|
|
65
|
-
return { kind: '
|
|
65
|
+
return { kind: 'failed', reason: describeThrown(err) };
|
|
66
66
|
}
|
|
67
67
|
if (!consented) {
|
|
68
68
|
return { kind: 'declined', step: 'consent', namespace: packName };
|
|
@@ -73,7 +73,7 @@ export async function installPackFromArchive(adapter, options) {
|
|
|
73
73
|
alreadyInstalled = await exists(targetDir);
|
|
74
74
|
}
|
|
75
75
|
catch (err) {
|
|
76
|
-
return { kind: '
|
|
76
|
+
return { kind: 'failed', reason: describeThrown(err) };
|
|
77
77
|
}
|
|
78
78
|
let replaced = false;
|
|
79
79
|
if (alreadyInstalled) {
|
|
@@ -88,7 +88,7 @@ export async function installPackFromArchive(adapter, options) {
|
|
|
88
88
|
});
|
|
89
89
|
}
|
|
90
90
|
catch (err) {
|
|
91
|
-
return { kind: '
|
|
91
|
+
return { kind: 'failed', reason: describeThrown(err) };
|
|
92
92
|
}
|
|
93
93
|
if (!proceed) {
|
|
94
94
|
return { kind: 'declined', step: 'replace', namespace: packName };
|
|
@@ -102,7 +102,7 @@ export async function installPackFromArchive(adapter, options) {
|
|
|
102
102
|
}
|
|
103
103
|
catch (err) {
|
|
104
104
|
return {
|
|
105
|
-
kind: '
|
|
105
|
+
kind: 'failed',
|
|
106
106
|
reason: `could not install pack "${packName}": ${describeThrown(err)}`,
|
|
107
107
|
};
|
|
108
108
|
}
|
|
@@ -153,7 +153,9 @@ export function installReplaceConfirmMessage(packName) {
|
|
|
153
153
|
* `false`.
|
|
154
154
|
*/
|
|
155
155
|
export function installPackMessage(outcome, archivePath, labels, reloadsAutomatically = false) {
|
|
156
|
-
if (outcome.kind === '
|
|
156
|
+
if (outcome.kind === 'invalid' ||
|
|
157
|
+
outcome.kind === 'failed' ||
|
|
158
|
+
outcome.kind === 'unsupported') {
|
|
157
159
|
return `Markii: could not install a pack from "${archivePath}". Open ${labels.diagnosticsSurface} for details.`;
|
|
158
160
|
}
|
|
159
161
|
if (outcome.kind === 'reserved') {
|
|
@@ -171,7 +173,7 @@ export function installPackMessage(outcome, archivePath, labels, reloadsAutomati
|
|
|
171
173
|
}
|
|
172
174
|
/** The full diagnostics-surface detail for one install attempt (AGENTS.md's "clean is not silent": the other of a failure's two homes). */
|
|
173
175
|
export function installPackDiagnosticLines(outcome, archivePath) {
|
|
174
|
-
if (outcome.kind === '
|
|
176
|
+
if (outcome.kind === 'invalid' || outcome.kind === 'failed') {
|
|
175
177
|
return [
|
|
176
178
|
`Install pack from file rejected "${archivePath}": ${outcome.reason}`,
|
|
177
179
|
];
|
|
@@ -1,21 +1,10 @@
|
|
|
1
1
|
import type { HostAdapter } from './adapter.js';
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
readonly namespace: string;
|
|
8
|
-
} | {
|
|
9
|
-
readonly kind: 'reserved';
|
|
10
|
-
readonly namespace: string;
|
|
11
|
-
} | {
|
|
12
|
-
readonly kind: 'invalid';
|
|
13
|
-
readonly reason: string;
|
|
14
|
-
} | {
|
|
15
|
-
readonly kind: 'failed';
|
|
16
|
-
readonly reason: string;
|
|
17
|
-
};
|
|
2
|
+
import type { RemovePackOutcome } from './create-host.js';
|
|
3
|
+
/** What one removal attempt produced, in the shape `createMarkiiHost(...).removePack` returns (minus `unsupported`, which only the host behavior can say). */
|
|
4
|
+
export type RemovePackAttempt = Exclude<RemovePackOutcome, {
|
|
5
|
+
readonly kind: 'unsupported';
|
|
6
|
+
}>;
|
|
18
7
|
/** Never throws. The caller checks `adapter.packs.installRoot`/`removeFolder` exist first. */
|
|
19
|
-
export declare function removePackViaAdapter(adapter: HostAdapter, namespace: string, installRoot: string, removeFolder: (folder: string) => Promise<void>, reservedNamespaces: ReadonlySet<string>): Promise<
|
|
8
|
+
export declare function removePackViaAdapter(adapter: HostAdapter, namespace: string, installRoot: string, removeFolder: (folder: string) => Promise<void>, reservedNamespaces: ReadonlySet<string>): Promise<RemovePackAttempt>;
|
|
20
9
|
export declare function removePackMessage(outcome: RemovePackOutcome): string;
|
|
21
|
-
export declare function removePackDiagnosticLine(outcome:
|
|
10
|
+
export declare function removePackDiagnosticLine(outcome: RemovePackAttempt): string;
|
package/dist/host/pack-remove.js
CHANGED
|
@@ -37,6 +37,8 @@ export async function removePackViaAdapter(adapter, namespace, installRoot, remo
|
|
|
37
37
|
}
|
|
38
38
|
export function removePackMessage(outcome) {
|
|
39
39
|
switch (outcome.kind) {
|
|
40
|
+
case 'unsupported':
|
|
41
|
+
return 'Markii: removing packs is not available on this host. Nothing was removed.';
|
|
40
42
|
case 'removed':
|
|
41
43
|
return `Markii: removed pack "${outcome.namespace}".`;
|
|
42
44
|
case 'not-installed':
|