@markii/host 0.17.0-next.3 → 0.17.0-next.5
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 +34 -1
- package/dist/browser-worker.classic.js +640 -0
- package/dist/browser-worker.js +19 -19
- package/dist/browser.d.ts +1 -1
- package/dist/browser.js +10 -3
- package/dist/conformance/host/05-export-html-pack-directive/expected.json +2 -0
- package/dist/conformance/host/06-install-pack-then-render/expected.json +6 -1
- 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/README.md +17 -12
- package/dist/diagnostics/render-diagnostics.d.ts +1 -1
- package/dist/export/note-export.d.ts +1 -1
- package/dist/host/adapter.d.ts +13 -3
- package/dist/host/cascade-export-behavior.d.ts +24 -8
- package/dist/host/cascade-export-behavior.js +61 -1
- package/dist/host/conformance-runner.d.ts +3 -4
- package/dist/host/conformance-runner.js +58 -8
- package/dist/host/create-host.d.ts +65 -8
- package/dist/host/create-host.js +54 -23
- package/dist/host/export-behavior.d.ts +2 -1
- package/dist/host/export-behavior.js +1 -1
- package/dist/host/labels.d.ts +5 -14
- package/dist/host/labels.js +8 -22
- package/dist/host/pack-archive.d.ts +4 -10
- package/dist/host/pack-archive.js +11 -14
- package/dist/host/pack-install.d.ts +10 -21
- package/dist/host/pack-install.js +22 -16
- package/dist/host/pack-trust-behavior.js +1 -1
- package/dist/host/run-behavior.d.ts +4 -0
- package/dist/host/run-behavior.js +9 -1
- package/dist/host/terminal-packs.d.ts +1 -1
- package/dist/html-engine.js +4 -4
- package/dist/index.d.ts +19 -81
- package/dist/index.js +19 -62
- package/dist/internal.d.ts +28 -0
- package/dist/internal.js +31 -0
- package/dist/node-fs.d.ts +0 -3
- package/dist/node-fs.js +1 -21
- package/dist/node-worker.d.ts +8 -0
- package/dist/node-worker.js +36 -15
- package/dist/node.d.ts +1 -2
- package/dist/node.js +4 -3
- package/dist/packs/discover.d.ts +2 -0
- package/dist/packs/discover.js +1 -0
- package/dist/run/bundle-run.d.ts +1 -1
- package/dist/run/bundle-run.js +1 -1
- package/dist/run/net-bridge.js +7 -5
- package/dist/run/net-pinning.d.ts +2 -0
- package/dist/run/net-pinning.js +17 -3
- package/dist/run/net-provider.js +23 -12
- package/dist/run/net-refusal.d.ts +4 -0
- package/dist/run/net-refusal.js +24 -0
- package/package.json +28 -12
|
@@ -18,9 +18,11 @@ import type { StoredValue } from '@markii/runtime';
|
|
|
18
18
|
import type { OnDiagnostic, UnavailableComponent } from '@markii/stdlib';
|
|
19
19
|
import type { CompletionItem, ComponentDocumentation, LineColumn } from '@markii/stdlib/editor';
|
|
20
20
|
import { type RunViaAdapterRequest, type RunViaAdapterResult } from './run-behavior.js';
|
|
21
|
-
import { type ArchiveExtractFs } from './pack-archive.js';
|
|
22
21
|
import { type PackDiscoveryIo } from './pack-load.js';
|
|
23
|
-
import type { HtmlExportEngine } from '../export/note-export.js';
|
|
22
|
+
import type { ExportBodyRenderer, ExportPackStylesheet, ExportRenderInfo, HtmlExportEngine, StaticExportReason } from '../export/note-export.js';
|
|
23
|
+
import type { EmbeddedImageReport, ExportImageReader } from '../export/image-embed.js';
|
|
24
|
+
import type { CascadeLinkResolver } from '../export/cascade.js';
|
|
25
|
+
import { type CascadeExportOutcome } from './cascade-export-behavior.js';
|
|
24
26
|
import type { DiscoveredPack, SkippedPackFolder } from '../packs/discover.js';
|
|
25
27
|
import { type TerminalPackScript } from './terminal-packs.js';
|
|
26
28
|
import { type RemovePackOutcome } from './pack-remove.js';
|
|
@@ -28,7 +30,7 @@ import type { DisablePackOutcome, EnablePackOutcome, InstalledPacksListing } fro
|
|
|
28
30
|
/** The shared decline shape every behavior returns when the adapter lacks the capability it needs. */
|
|
29
31
|
export interface Unsupported {
|
|
30
32
|
readonly kind: 'unsupported';
|
|
31
|
-
readonly capability: 'isolate' | 'editor' | 'packs' | 'pack-trust' | 'export-format';
|
|
33
|
+
readonly capability: 'isolate' | 'editor' | 'packs' | 'pack-trust' | 'export-format' | 'linked-set-export';
|
|
32
34
|
readonly detail?: string;
|
|
33
35
|
}
|
|
34
36
|
export interface OpenRequest {
|
|
@@ -61,11 +63,31 @@ export interface ExportRequest {
|
|
|
61
63
|
readonly unavailableComponents?: readonly UnavailableComponent[];
|
|
62
64
|
/** Receives the static engine's render diagnostics, such as the `no-engine-view` event. */
|
|
63
65
|
readonly onDiagnostic?: OnDiagnostic;
|
|
66
|
+
/** The host's own render of the body (a React pass bound to its loaded packs). Omitted means the static engine renders, and `staticReason` says why. Used by the `html` and `pdf` formats. */
|
|
67
|
+
readonly renderBody?: ExportBodyRenderer;
|
|
68
|
+
/** Why the static engine renders when `renderBody` is omitted. Defaults to `no-packs`. */
|
|
69
|
+
readonly staticReason?: StaticExportReason;
|
|
70
|
+
/** The loaded packs' stylesheets, embedded only when `renderBody` ran. */
|
|
71
|
+
readonly packStylesheets?: readonly ExportPackStylesheet[];
|
|
72
|
+
/** How many loaded packs contributed to the render. Diagnostics only. */
|
|
73
|
+
readonly packCount?: number;
|
|
74
|
+
/** Reads one of the note's local images so the export embeds it as a `data:` URI. Omitted leaves every image source as written. */
|
|
75
|
+
readonly embedImages?: ExportImageReader;
|
|
76
|
+
/** Hides the collapsed script marker in the exported file. Defaults to `false`. */
|
|
77
|
+
readonly hideScriptBlocks?: boolean;
|
|
64
78
|
}
|
|
65
79
|
export type ExportOutcome = {
|
|
66
80
|
readonly kind: 'exported';
|
|
67
81
|
readonly path: string;
|
|
68
82
|
readonly bytes: number;
|
|
83
|
+
/** How many last-run values were baked into the file. */
|
|
84
|
+
readonly valueCount: number;
|
|
85
|
+
/** `true` when the note contains a script fence. */
|
|
86
|
+
readonly hasScripts?: boolean;
|
|
87
|
+
/** Which engine rendered the body, and why when it was the static one. */
|
|
88
|
+
readonly render: ExportRenderInfo;
|
|
89
|
+
/** What image embedding did. */
|
|
90
|
+
readonly images: EmbeddedImageReport;
|
|
69
91
|
} | {
|
|
70
92
|
readonly kind: 'failed';
|
|
71
93
|
readonly reason: string;
|
|
@@ -74,6 +96,41 @@ export type ExportOutcome = {
|
|
|
74
96
|
| {
|
|
75
97
|
readonly kind: 'cancelled';
|
|
76
98
|
} | Unsupported;
|
|
99
|
+
export interface ExportLinkedSetRequest {
|
|
100
|
+
/** The root note's path, in whatever form `adapter.readFile` and `resolveLink` understand. */
|
|
101
|
+
readonly rootPath: string;
|
|
102
|
+
/** Resolves one link in a note to the path of a note this host can export, or `undefined` to leave the link as written. */
|
|
103
|
+
readonly resolveLink: CascadeLinkResolver;
|
|
104
|
+
/** Reads one note's persisted last-run values, keyed by that note's own path. An empty object exports that note's standard empty states. */
|
|
105
|
+
readonly readValues: (notePath: string) => Record<string, StoredValue>;
|
|
106
|
+
/** The host's own render of each note's body. Omitted renders every note with the static engine, and `staticReason` says why. */
|
|
107
|
+
readonly renderBody?: ExportBodyRenderer;
|
|
108
|
+
/** Why the static engine renders when `renderBody` is omitted. Defaults to `no-packs`. */
|
|
109
|
+
readonly staticReason?: StaticExportReason;
|
|
110
|
+
/** The loaded packs' stylesheets, embedded only when `renderBody` ran. */
|
|
111
|
+
readonly packStylesheets?: readonly ExportPackStylesheet[];
|
|
112
|
+
/** How many loaded packs contributed to the render. Diagnostics only. */
|
|
113
|
+
readonly packCount?: number;
|
|
114
|
+
/** Installed pack components the static engine cannot draw. */
|
|
115
|
+
readonly unavailableComponents?: readonly UnavailableComponent[];
|
|
116
|
+
/** Receives the static engine's render diagnostics for every note. */
|
|
117
|
+
readonly onDiagnostic?: OnDiagnostic;
|
|
118
|
+
/** Builds the image reader for one note, bound to that note's own folder. Omitted embeds no images. */
|
|
119
|
+
readonly embedImagesFor?: (notePath: string) => ExportImageReader;
|
|
120
|
+
/** Hides the collapsed script marker in every exported note. Defaults to `false`. */
|
|
121
|
+
readonly hideScriptBlocks?: boolean;
|
|
122
|
+
}
|
|
123
|
+
export type ExportLinkedSetOutcome = Extract<CascadeExportOutcome, {
|
|
124
|
+
kind: 'exported';
|
|
125
|
+
}> | {
|
|
126
|
+
readonly kind: 'failed';
|
|
127
|
+
readonly path?: string;
|
|
128
|
+
readonly reason: string;
|
|
129
|
+
}
|
|
130
|
+
/** The host's picker was cancelled, so nothing was written. */
|
|
131
|
+
| {
|
|
132
|
+
readonly kind: 'cancelled';
|
|
133
|
+
} | Unsupported;
|
|
77
134
|
export interface InstallPackRequest {
|
|
78
135
|
readonly archiveBytes: Uint8Array;
|
|
79
136
|
/** The archive's own path or display name, used only in wording. Defaults to a generic phrase when omitted. */
|
|
@@ -82,6 +139,8 @@ export interface InstallPackRequest {
|
|
|
82
139
|
export type InstallPackOutcome = {
|
|
83
140
|
readonly kind: 'installed';
|
|
84
141
|
readonly namespace: string;
|
|
142
|
+
/** The folder the pack now lives in, under the adapter's install root. */
|
|
143
|
+
readonly installedDir: string;
|
|
85
144
|
readonly replaced: boolean;
|
|
86
145
|
} | {
|
|
87
146
|
readonly kind: 'declined';
|
|
@@ -196,6 +255,8 @@ export interface MarkiiHost {
|
|
|
196
255
|
open(request: OpenRequest): Promise<OpenOutcome>;
|
|
197
256
|
run(request: RunRequest): Promise<RunOutcome>;
|
|
198
257
|
exportNote(request: ExportRequest): Promise<ExportOutcome>;
|
|
258
|
+
/** Exports a note and the notes it links to as one zip archive of HTML files. */
|
|
259
|
+
exportLinkedSet(request: ExportLinkedSetRequest): Promise<ExportLinkedSetOutcome>;
|
|
199
260
|
installPack(request: InstallPackRequest): Promise<InstallPackOutcome>;
|
|
200
261
|
loadPacks(): Promise<LoadPacksOutcome>;
|
|
201
262
|
/**
|
|
@@ -237,15 +298,11 @@ export interface MarkiiHost {
|
|
|
237
298
|
*/
|
|
238
299
|
export interface MarkiiHostOptions {
|
|
239
300
|
readonly spawnRun?: (options: SpawnRunRequest) => Promise<RunResult>;
|
|
240
|
-
/** The
|
|
241
|
-
readonly archiveExtractFs?: ArchiveExtractFs;
|
|
242
|
-
/** The static HTML engine behind the `html` and `pdf` exports (`createHtmlExportEngine()` from `@markii/host/node`). Without it those formats decline, like `renderAnsi` for `ansi`. */
|
|
301
|
+
/** The static HTML engine behind the `html` and `pdf` exports (`createHtmlExportEngine()` from `@markii/host/html-engine`). Without it those formats decline, like `renderAnsi` for `ansi`. */
|
|
243
302
|
readonly htmlEngine?: HtmlExportEngine;
|
|
244
303
|
/** Pack discovery's file reads. Defaults to reads through the adapter's own `readFile` and `listFolder`. */
|
|
245
304
|
readonly packDiscoveryIo?: PackDiscoveryIo;
|
|
246
305
|
/** `@markii/ansi`'s renderer, injected because this package never depends on `@markii/ansi` (a platform renderer, never a `@markii/host` dependency). A host declaring `'ansi'` in `adapter.exports.formats` without supplying this gets `Unsupported` at export time. */
|
|
247
306
|
readonly renderAnsi?: (text: string) => Promise<string>;
|
|
248
|
-
/** Composes a pack-aware render registry (webview registration, in-process evaluation, ...). Reserved for a future slice that renders through `exportNote`'s `html` path with packs; unused today, since `buildExportDocument`'s default (no `renderBody`) already produces a correct, pack-free static export. */
|
|
249
|
-
readonly buildPackRegistration?: unknown;
|
|
250
307
|
}
|
|
251
308
|
export declare function createMarkiiHost(adapter: HostAdapter, options?: MarkiiHostOptions): MarkiiHost;
|
package/dist/host/create-host.js
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import { runViaAdapter, } from './run-behavior.js';
|
|
2
2
|
import { installPackDiagnosticLines, installPackFromArchive, } from './pack-install.js';
|
|
3
3
|
import { loadPacksViaAdapter, packDiscoveryIoFromAdapter, } from './pack-load.js';
|
|
4
|
+
import { exportBaseName } from '../export/note-export.js';
|
|
5
|
+
import { exportLinkedSetViaAdapter, } from './cascade-export-behavior.js';
|
|
4
6
|
import { buildComponentCatalog } from '../insert/component-catalog.js';
|
|
5
7
|
import { formatPackDiagnosticLines, skippedPackFolderLine, } from '../packs/pack-diagnostics.js';
|
|
6
8
|
import { readTerminalPackScripts, } from './terminal-packs.js';
|
|
@@ -95,6 +97,24 @@ export function createMarkiiHost(adapter, options = {}) {
|
|
|
95
97
|
...(request.onDiagnostic !== undefined
|
|
96
98
|
? { onDiagnostic: request.onDiagnostic }
|
|
97
99
|
: {}),
|
|
100
|
+
...(request.renderBody !== undefined
|
|
101
|
+
? { renderBody: request.renderBody }
|
|
102
|
+
: {}),
|
|
103
|
+
...(request.staticReason !== undefined
|
|
104
|
+
? { staticReason: request.staticReason }
|
|
105
|
+
: {}),
|
|
106
|
+
...(request.packStylesheets !== undefined
|
|
107
|
+
? { packStylesheets: request.packStylesheets }
|
|
108
|
+
: {}),
|
|
109
|
+
...(request.packCount !== undefined
|
|
110
|
+
? { packCount: request.packCount }
|
|
111
|
+
: {}),
|
|
112
|
+
...(request.embedImages !== undefined
|
|
113
|
+
? { embedImages: request.embedImages }
|
|
114
|
+
: {}),
|
|
115
|
+
...(request.hideScriptBlocks !== undefined
|
|
116
|
+
? { hideScriptBlocks: request.hideScriptBlocks }
|
|
117
|
+
: {}),
|
|
98
118
|
}, options.renderAnsi);
|
|
99
119
|
if (outcome === undefined) {
|
|
100
120
|
adapter.diagnostics(`export declined: format "${request.format}" has no working export path on ${adapter.id} (a cancelled picker, or a missing renderer/PDF seam).`);
|
|
@@ -106,47 +126,58 @@ export function createMarkiiHost(adapter, options = {}) {
|
|
|
106
126
|
if (outcome.kind === 'failed') {
|
|
107
127
|
return { kind: 'failed', reason: outcome.reason };
|
|
108
128
|
}
|
|
109
|
-
return {
|
|
129
|
+
return {
|
|
130
|
+
kind: 'exported',
|
|
131
|
+
path: outcome.path,
|
|
132
|
+
bytes: outcome.bytes,
|
|
133
|
+
valueCount: outcome.valueCount,
|
|
134
|
+
...(outcome.hasScripts !== undefined
|
|
135
|
+
? { hasScripts: outcome.hasScripts }
|
|
136
|
+
: {}),
|
|
137
|
+
render: outcome.render,
|
|
138
|
+
images: outcome.images,
|
|
139
|
+
};
|
|
140
|
+
},
|
|
141
|
+
async exportLinkedSet(request) {
|
|
142
|
+
if (adapter.exports?.resolveLinkedSetTarget === undefined) {
|
|
143
|
+
return unsupported(adapter, 'linked-set-export', `the linked-set export is not available on ${adapter.id}.`);
|
|
144
|
+
}
|
|
145
|
+
if (options.htmlEngine === undefined) {
|
|
146
|
+
return unsupported(adapter, 'linked-set-export', 'no HTML export engine was supplied, so the linked-set export cannot render.');
|
|
147
|
+
}
|
|
148
|
+
const outcome = await exportLinkedSetViaAdapter(adapter, {
|
|
149
|
+
...request,
|
|
150
|
+
engine: options.htmlEngine,
|
|
151
|
+
suggestedFileName: `${exportBaseName(request.rootPath)}.zip`,
|
|
152
|
+
});
|
|
153
|
+
if (outcome === undefined) {
|
|
154
|
+
adapter.diagnostics(`linked-set export declined: no archive target was chosen on ${adapter.id}.`);
|
|
155
|
+
return { kind: 'cancelled' };
|
|
156
|
+
}
|
|
157
|
+
return outcome;
|
|
110
158
|
},
|
|
111
159
|
async installPack(request) {
|
|
112
160
|
if (!adapter.packs?.installRoot) {
|
|
113
161
|
return unsupported(adapter, 'packs', 'this host has no pack-install destination configured.');
|
|
114
162
|
}
|
|
115
163
|
const archivePath = request.archivePath ?? 'the selected archive';
|
|
116
|
-
const
|
|
117
|
-
if (
|
|
118
|
-
return unsupported(adapter, 'packs', 'this host
|
|
164
|
+
const removeFolder = adapter.packs.removeFolder;
|
|
165
|
+
if (removeFolder === undefined) {
|
|
166
|
+
return unsupported(adapter, 'packs', 'this host has no way to remove an installed pack folder.');
|
|
119
167
|
}
|
|
120
168
|
const outcome = await installPackFromArchive(adapter, {
|
|
121
169
|
archiveBytes: request.archiveBytes,
|
|
122
170
|
archivePath,
|
|
123
171
|
installRoot: adapter.packs.installRoot(),
|
|
124
172
|
exists: (path) => adapter.exists(path),
|
|
125
|
-
|
|
173
|
+
removeFolder: (path) => removeFolder.call(adapter.packs, path),
|
|
126
174
|
reservedNamespaces: adapter.packs.reservedNamespaces(),
|
|
127
175
|
});
|
|
128
176
|
catalogCache.invalidate();
|
|
129
177
|
for (const line of installPackDiagnosticLines(outcome, archivePath)) {
|
|
130
178
|
adapter.diagnostics(line);
|
|
131
179
|
}
|
|
132
|
-
|
|
133
|
-
return {
|
|
134
|
-
kind: 'installed',
|
|
135
|
-
namespace: outcome.packName,
|
|
136
|
-
replaced: outcome.replaced,
|
|
137
|
-
};
|
|
138
|
-
}
|
|
139
|
-
if (outcome.kind === 'declined') {
|
|
140
|
-
return {
|
|
141
|
-
kind: 'declined',
|
|
142
|
-
step: outcome.step,
|
|
143
|
-
namespace: outcome.packName,
|
|
144
|
-
};
|
|
145
|
-
}
|
|
146
|
-
if (outcome.kind === 'reserved') {
|
|
147
|
-
return { kind: 'reserved', namespace: outcome.packName };
|
|
148
|
-
}
|
|
149
|
-
return { kind: 'rejected', reason: outcome.reason };
|
|
180
|
+
return outcome;
|
|
150
181
|
},
|
|
151
182
|
async loadPacks() {
|
|
152
183
|
if (!adapter.packs) {
|
|
@@ -30,6 +30,7 @@ import type { OnDiagnostic, UnavailableComponent } from '@markii/stdlib';
|
|
|
30
30
|
import type { HtmlExportEngine, ExportBodyRenderer, ExportPackStylesheet, ExportRenderInfo, StaticExportReason } from '../export/note-export.js';
|
|
31
31
|
import type { EmbeddedImageReport, ExportImageReader } from '../export/image-embed.js';
|
|
32
32
|
import type { HostAdapter, HostExportFormat, HostLabels } from './adapter.js';
|
|
33
|
+
import type { ExportOutcome } from './create-host.js';
|
|
33
34
|
/** 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. */
|
|
34
35
|
export declare function exportDefaultFileName(notePath: string, format: HostExportFormat): string;
|
|
35
36
|
/** What a `'html'` or `'pdf'` export attempt produced. Every shape except `failed` means the host got a file, and every shape but `failed` carries the same diagnostics-facing detail so the two hosts' wording can share it. */
|
|
@@ -58,7 +59,7 @@ export declare function fileNameOf(path: string): string;
|
|
|
58
59
|
* host's own diagnostics surface (`labels.diagnosticsSurface`), never at
|
|
59
60
|
* a stack trace — the verbatim reason goes there instead.
|
|
60
61
|
*/
|
|
61
|
-
export declare function exportResultMessage(outcome:
|
|
62
|
+
export declare function exportResultMessage(outcome: ExportOutcome, labels: HostLabels): string;
|
|
62
63
|
/** 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. */
|
|
63
64
|
export declare function renderEngineDiagnosticLine(render: ExportRenderInfo): string;
|
|
64
65
|
/** `bytes`, formatted as a human-readable size (`"1.2 KB"`, `"512 bytes"`). */
|
|
@@ -23,7 +23,7 @@ export function fileNameOf(path) {
|
|
|
23
23
|
* a stack trace — the verbatim reason goes there instead.
|
|
24
24
|
*/
|
|
25
25
|
export function exportResultMessage(outcome, labels) {
|
|
26
|
-
if (outcome.kind
|
|
26
|
+
if (outcome.kind !== 'exported') {
|
|
27
27
|
return `Markii: could not export this note. Open ${labels.diagnosticsSurface} for details.`;
|
|
28
28
|
}
|
|
29
29
|
const name = fileNameOf(outcome.path);
|
package/dist/host/labels.d.ts
CHANGED
|
@@ -1,20 +1,11 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
3
|
-
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
* lets one template serve every host.
|
|
2
|
+
* The shared wording templates a host's shared behaviors interpolate
|
|
3
|
+
* `HostLabels` into live beside each behavior. This module holds the one
|
|
4
|
+
* label sentence that has no per-host noun. Each app owns its own
|
|
5
|
+
* `HostLabels` record and passes it as `HostAdapter.labels`.
|
|
7
6
|
*
|
|
8
|
-
* Pure and dependency-free
|
|
9
|
-
* `../index.ts` so VS Code's Node-free webview bundle can read it too.
|
|
7
|
+
* Pure and dependency-free.
|
|
10
8
|
*/
|
|
11
|
-
import type { HostLabels } from './adapter.js';
|
|
12
|
-
/** 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). */
|
|
13
|
-
export declare const VSCODE_LABELS: HostLabels;
|
|
14
|
-
/** The Obsidian plugin's labels, taken from its current wording (`apps/obsidian/src/script-execution.ts`, docs/integration.md's host checklist). */
|
|
15
|
-
export declare const OBSIDIAN_LABELS: HostLabels;
|
|
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 declare const CLI_LABELS: HostLabels;
|
|
18
9
|
/**
|
|
19
10
|
* The quiet marker a rendered note shows when its `uses:` list names packs
|
|
20
11
|
* this host has not installed (docs/packs.md). One sentence for every host;
|
package/dist/host/labels.js
CHANGED
|
@@ -1,25 +1,11 @@
|
|
|
1
|
-
/**
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
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
|
-
};
|
|
1
|
+
/**
|
|
2
|
+
* The shared wording templates a host's shared behaviors interpolate
|
|
3
|
+
* `HostLabels` into live beside each behavior. This module holds the one
|
|
4
|
+
* label sentence that has no per-host noun. Each app owns its own
|
|
5
|
+
* `HostLabels` record and passes it as `HostAdapter.labels`.
|
|
6
|
+
*
|
|
7
|
+
* Pure and dependency-free.
|
|
8
|
+
*/
|
|
23
9
|
/**
|
|
24
10
|
* The quiet marker a rendered note shows when its `uses:` list names packs
|
|
25
11
|
* this host has not installed (docs/packs.md). One sentence for every host;
|
|
@@ -1,14 +1,8 @@
|
|
|
1
1
|
import type { PackArchiveContents, PackArchiveError } from '@markii/pack';
|
|
2
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
3
|
export declare function describeArchiveError(error: PackArchiveError): string;
|
|
4
|
-
/**
|
|
5
|
-
export
|
|
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
|
-
}
|
|
4
|
+
/** Writes one file and creates its missing parent folders: `HostAdapter.writeFile`. */
|
|
5
|
+
export type ArchiveFileWriter = (absolutePath: string, bytes: Uint8Array) => Promise<void>;
|
|
12
6
|
/**
|
|
13
7
|
* Writes a validated archive's contents into `destinationDir`: `pack.json`
|
|
14
8
|
* (the parsed manifest, re-serialized: `openPackArchive` keeps only the
|
|
@@ -17,7 +11,7 @@ export interface ArchiveExtractFs {
|
|
|
17
11
|
* copy), `webview.js`, `webview.css` and `terminal.js` when the archive has them, and
|
|
18
12
|
* `scripts/*` when it ships any. Does NOT clear `destinationDir` first;
|
|
19
13
|
* a caller that needs a clean re-extract (a reinstall replacing an
|
|
20
|
-
* existing pack)
|
|
14
|
+
* existing pack) removes the folder itself before this.
|
|
21
15
|
*
|
|
22
16
|
* PATH JAIL: `@markii/pack`'s `openPackArchive` has already rejected any
|
|
23
17
|
* entry whose name escapes the archive (`../`, an absolute path) or
|
|
@@ -27,4 +21,4 @@ export interface ArchiveExtractFs {
|
|
|
27
21
|
* `webview.css`, or a `scripts/`-relative Lua module name), never from
|
|
28
22
|
* anything read back out of the zip's raw entry names.
|
|
29
23
|
*/
|
|
30
|
-
export declare function writeArchiveContents(archive: PackArchiveContents, destinationDir: string,
|
|
24
|
+
export declare function writeArchiveContents(archive: PackArchiveContents, destinationDir: string, writeFile: ArchiveFileWriter): Promise<void>;
|
|
@@ -1,18 +1,17 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* `.mkp` pack archive handling, merged from `apps/vscode/src/packs/archive-packs.ts`
|
|
3
3
|
* and `apps/obsidian/src/packs/archive-packs.ts`. It exports
|
|
4
|
-
* `describeArchiveError`, `writeArchiveContents` and `
|
|
4
|
+
* `describeArchiveError`, `writeArchiveContents` and `ArchiveFileWriter`.
|
|
5
5
|
*
|
|
6
6
|
* This module bridges `@markii/pack`'s in-memory archive reader
|
|
7
7
|
* (`openPackArchive`, never modified here) into the write a pack install
|
|
8
8
|
* needs: extracting a validated archive's contents onto disk, under
|
|
9
9
|
* whichever install root the calling host names (`HostPackSource.installRoot`).
|
|
10
10
|
*
|
|
11
|
-
* Environment-free
|
|
12
|
-
*
|
|
13
|
-
* and is exported from `@markii/host/node`.
|
|
11
|
+
* Environment-free: the write goes through the host adapter's `writeFile`,
|
|
12
|
+
* which creates any missing parent folder.
|
|
14
13
|
*/
|
|
15
|
-
import {
|
|
14
|
+
import { joinPath } from '../path-util.js';
|
|
16
15
|
/** 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. */
|
|
17
16
|
export function describeArchiveError(error) {
|
|
18
17
|
if (error.kind === 'zip')
|
|
@@ -29,7 +28,7 @@ export function describeArchiveError(error) {
|
|
|
29
28
|
* copy), `webview.js`, `webview.css` and `terminal.js` when the archive has them, and
|
|
30
29
|
* `scripts/*` when it ships any. Does NOT clear `destinationDir` first;
|
|
31
30
|
* a caller that needs a clean re-extract (a reinstall replacing an
|
|
32
|
-
* existing pack)
|
|
31
|
+
* existing pack) removes the folder itself before this.
|
|
33
32
|
*
|
|
34
33
|
* PATH JAIL: `@markii/pack`'s `openPackArchive` has already rejected any
|
|
35
34
|
* entry whose name escapes the archive (`../`, an absolute path) or
|
|
@@ -39,24 +38,22 @@ export function describeArchiveError(error) {
|
|
|
39
38
|
* `webview.css`, or a `scripts/`-relative Lua module name), never from
|
|
40
39
|
* anything read back out of the zip's raw entry names.
|
|
41
40
|
*/
|
|
42
|
-
export async function writeArchiveContents(archive, destinationDir,
|
|
43
|
-
await fs.makeDirectory(destinationDir);
|
|
41
|
+
export async function writeArchiveContents(archive, destinationDir, writeFile) {
|
|
44
42
|
const encoder = new TextEncoder();
|
|
45
|
-
await
|
|
46
|
-
await
|
|
43
|
+
await writeFile(joinPath(destinationDir, 'pack.json'), encoder.encode(JSON.stringify(archive.manifest, null, 2) + '\n'));
|
|
44
|
+
await writeFile(joinPath(destinationDir, 'webview.js'), archive.scriptBytes);
|
|
47
45
|
if (archive.stylesheetBytes !== undefined) {
|
|
48
|
-
await
|
|
46
|
+
await writeFile(joinPath(destinationDir, 'webview.css'), archive.stylesheetBytes);
|
|
49
47
|
}
|
|
50
48
|
if (archive.terminalScriptBytes !== undefined) {
|
|
51
|
-
await
|
|
49
|
+
await writeFile(joinPath(destinationDir, 'terminal.js'), archive.terminalScriptBytes);
|
|
52
50
|
}
|
|
53
51
|
const moduleNames = Object.keys(archive.scriptModules);
|
|
54
52
|
if (moduleNames.length > 0) {
|
|
55
53
|
const scriptsDir = joinPath(destinationDir, 'scripts');
|
|
56
54
|
for (const name of moduleNames) {
|
|
57
55
|
const target = joinPath(scriptsDir, name);
|
|
58
|
-
await
|
|
59
|
-
await fs.writeFile(target, archive.scriptModules[name]);
|
|
56
|
+
await writeFile(target, archive.scriptModules[name]);
|
|
60
57
|
}
|
|
61
58
|
}
|
|
62
59
|
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import type { ArchiveExtractFs } from './pack-archive.js';
|
|
2
1
|
import type { HostAdapter, HostLabels } from './adapter.js';
|
|
2
|
+
import type { InstallPackOutcome as HostInstallPackOutcome } from './create-host.js';
|
|
3
3
|
/** Whether a directory already exists under the install root, injected so this module needs no real disk to test. */
|
|
4
4
|
export type PackDirectoryExists = (absolutePath: string) => Promise<boolean>;
|
|
5
5
|
export interface InstallPackFromArchiveOptions {
|
|
@@ -9,7 +9,8 @@ export interface InstallPackFromArchiveOptions {
|
|
|
9
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
10
|
readonly installRoot: string;
|
|
11
11
|
readonly exists: PackDirectoryExists;
|
|
12
|
-
|
|
12
|
+
/** Removes the installed folder before a replacing install (`HostPackSource.removeFolder`). Called only when the namespace is already installed. */
|
|
13
|
+
readonly removeFolder: (absolutePath: string) => Promise<void>;
|
|
13
14
|
/**
|
|
14
15
|
* Namespaces this install may never claim (`HostPackSource.reservedNamespaces()`).
|
|
15
16
|
* Empty for a host with no reserved set (VS Code). Checked before the
|
|
@@ -17,22 +18,10 @@ export interface InstallPackFromArchiveOptions {
|
|
|
17
18
|
*/
|
|
18
19
|
readonly reservedNamespaces: ReadonlySet<string>;
|
|
19
20
|
}
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
readonly
|
|
23
|
-
|
|
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
|
-
};
|
|
21
|
+
/** What one install attempt produced, in the shape `createMarkiiHost(...).installPack` returns (minus `unsupported`, which only the host behavior can say). */
|
|
22
|
+
export type InstallAttemptOutcome = Exclude<HostInstallPackOutcome, {
|
|
23
|
+
readonly kind: 'unsupported';
|
|
24
|
+
}>;
|
|
36
25
|
/**
|
|
37
26
|
* Never throws: a validation failure, a declined prompt, or a write
|
|
38
27
|
* failure all come back as a structured outcome. Order: validate the
|
|
@@ -47,7 +36,7 @@ export type InstallPackOutcome = {
|
|
|
47
36
|
* a host that can only show a non-blocking surface must deny rather than
|
|
48
37
|
* assume yes.
|
|
49
38
|
*/
|
|
50
|
-
export declare function installPackFromArchive(adapter: HostAdapter, options: InstallPackFromArchiveOptions): Promise<
|
|
39
|
+
export declare function installPackFromArchive(adapter: HostAdapter, options: InstallPackFromArchiveOptions): Promise<InstallAttemptOutcome>;
|
|
51
40
|
/**
|
|
52
41
|
* The consent prompt's wording (AGENTS.md's product principles: "clean is
|
|
53
42
|
* not silent," and a consent step must say plainly what it authorizes).
|
|
@@ -82,6 +71,6 @@ export declare function installReplaceConfirmMessage(packName: string): string;
|
|
|
82
71
|
* `markii.packs` before anything loads it), so it doesn't. Defaults to
|
|
83
72
|
* `false`.
|
|
84
73
|
*/
|
|
85
|
-
export declare function installPackMessage(outcome:
|
|
74
|
+
export declare function installPackMessage(outcome: HostInstallPackOutcome, archivePath: string, labels: HostLabels, reloadsAutomatically?: boolean): string;
|
|
86
75
|
/** The full diagnostics-surface detail for one install attempt (AGENTS.md's "clean is not silent": the other of a failure's two homes). */
|
|
87
|
-
export declare function installPackDiagnosticLines(outcome:
|
|
76
|
+
export declare function installPackDiagnosticLines(outcome: InstallAttemptOutcome, archivePath: string): string[];
|
|
@@ -39,7 +39,7 @@ function describeThrown(err) {
|
|
|
39
39
|
* assume yes.
|
|
40
40
|
*/
|
|
41
41
|
export async function installPackFromArchive(adapter, options) {
|
|
42
|
-
const { archiveBytes, archivePath, installRoot, exists,
|
|
42
|
+
const { archiveBytes, archivePath, installRoot, exists, removeFolder, reservedNamespaces, } = options;
|
|
43
43
|
const opened = await openPackArchive(archiveBytes);
|
|
44
44
|
if (!opened.ok) {
|
|
45
45
|
return {
|
|
@@ -49,7 +49,7 @@ export async function installPackFromArchive(adapter, options) {
|
|
|
49
49
|
}
|
|
50
50
|
const packName = opened.archive.manifest.name;
|
|
51
51
|
if (reservedNamespaces.has(packName)) {
|
|
52
|
-
return { kind: 'reserved', packName };
|
|
52
|
+
return { kind: 'reserved', namespace: packName };
|
|
53
53
|
}
|
|
54
54
|
let consented;
|
|
55
55
|
try {
|
|
@@ -65,7 +65,7 @@ export async function installPackFromArchive(adapter, options) {
|
|
|
65
65
|
return { kind: 'rejected', reason: describeThrown(err) };
|
|
66
66
|
}
|
|
67
67
|
if (!consented) {
|
|
68
|
-
return { kind: 'declined', step: 'consent', packName };
|
|
68
|
+
return { kind: 'declined', step: 'consent', namespace: packName };
|
|
69
69
|
}
|
|
70
70
|
const targetDir = joinPath(installRoot, packName);
|
|
71
71
|
let alreadyInstalled;
|
|
@@ -91,13 +91,14 @@ export async function installPackFromArchive(adapter, options) {
|
|
|
91
91
|
return { kind: 'rejected', reason: describeThrown(err) };
|
|
92
92
|
}
|
|
93
93
|
if (!proceed) {
|
|
94
|
-
return { kind: 'declined', step: 'replace', packName };
|
|
94
|
+
return { kind: 'declined', step: 'replace', namespace: packName };
|
|
95
95
|
}
|
|
96
96
|
replaced = true;
|
|
97
97
|
}
|
|
98
98
|
try {
|
|
99
|
-
|
|
100
|
-
|
|
99
|
+
if (alreadyInstalled)
|
|
100
|
+
await removeFolder(targetDir);
|
|
101
|
+
await writeArchiveContents(opened.archive, targetDir, (path, bytes) => adapter.writeFile(path, bytes));
|
|
101
102
|
}
|
|
102
103
|
catch (err) {
|
|
103
104
|
return {
|
|
@@ -106,7 +107,12 @@ export async function installPackFromArchive(adapter, options) {
|
|
|
106
107
|
};
|
|
107
108
|
}
|
|
108
109
|
await trustInstalledPack(adapter, packName, targetDir);
|
|
109
|
-
return {
|
|
110
|
+
return {
|
|
111
|
+
kind: 'installed',
|
|
112
|
+
namespace: packName,
|
|
113
|
+
installedDir: targetDir,
|
|
114
|
+
replaced,
|
|
115
|
+
};
|
|
110
116
|
}
|
|
111
117
|
/**
|
|
112
118
|
* The consent prompt's wording (AGENTS.md's product principles: "clean is
|
|
@@ -147,21 +153,21 @@ export function installReplaceConfirmMessage(packName) {
|
|
|
147
153
|
* `false`.
|
|
148
154
|
*/
|
|
149
155
|
export function installPackMessage(outcome, archivePath, labels, reloadsAutomatically = false) {
|
|
150
|
-
if (outcome.kind === 'rejected') {
|
|
156
|
+
if (outcome.kind === 'rejected' || outcome.kind === 'unsupported') {
|
|
151
157
|
return `Markii: could not install a pack from "${archivePath}". Open ${labels.diagnosticsSurface} for details.`;
|
|
152
158
|
}
|
|
153
159
|
if (outcome.kind === 'reserved') {
|
|
154
|
-
return `Markii: "${outcome.
|
|
160
|
+
return `Markii: "${outcome.namespace}" is built in and cannot be installed. Nothing was installed.`;
|
|
155
161
|
}
|
|
156
162
|
if (outcome.kind === 'declined') {
|
|
157
163
|
return outcome.step === 'consent'
|
|
158
|
-
? `Markii: install of "${outcome.
|
|
159
|
-
: `Markii: install of "${outcome.
|
|
164
|
+
? `Markii: install of "${outcome.namespace}" cancelled. Nothing was installed.`
|
|
165
|
+
: `Markii: install of "${outcome.namespace}" cancelled. The existing pack was kept.`;
|
|
160
166
|
}
|
|
161
167
|
const reloadSuffix = reloadsAutomatically ? ' Markii packs reloaded.' : '';
|
|
162
168
|
return outcome.replaced
|
|
163
|
-
? `Markii: reinstalled pack "${outcome.
|
|
164
|
-
: `Markii: installed pack "${outcome.
|
|
169
|
+
? `Markii: reinstalled pack "${outcome.namespace}".${reloadSuffix}`
|
|
170
|
+
: `Markii: installed pack "${outcome.namespace}".${reloadSuffix}`;
|
|
165
171
|
}
|
|
166
172
|
/** The full diagnostics-surface detail for one install attempt (AGENTS.md's "clean is not silent": the other of a failure's two homes). */
|
|
167
173
|
export function installPackDiagnosticLines(outcome, archivePath) {
|
|
@@ -172,15 +178,15 @@ export function installPackDiagnosticLines(outcome, archivePath) {
|
|
|
172
178
|
}
|
|
173
179
|
if (outcome.kind === 'reserved') {
|
|
174
180
|
return [
|
|
175
|
-
`Install pack from file refused "${archivePath}": pack "${outcome.
|
|
181
|
+
`Install pack from file refused "${archivePath}": pack "${outcome.namespace}" is a reserved namespace and cannot be installed.`,
|
|
176
182
|
];
|
|
177
183
|
}
|
|
178
184
|
if (outcome.kind === 'declined') {
|
|
179
185
|
return [
|
|
180
|
-
`Install pack from file cancelled for "${archivePath}" (pack "${outcome.
|
|
186
|
+
`Install pack from file cancelled for "${archivePath}" (pack "${outcome.namespace}") at the ${outcome.step} step; nothing was installed.`,
|
|
181
187
|
];
|
|
182
188
|
}
|
|
183
189
|
return [
|
|
184
|
-
`Install pack from file installed pack "${outcome.
|
|
190
|
+
`Install pack from file installed pack "${outcome.namespace}" from "${archivePath}" to ${outcome.installedDir}${outcome.replaced ? ' (replacing the previous install)' : ''}.`,
|
|
185
191
|
];
|
|
186
192
|
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* The shared pack-trust behavior (docs/packs.md, docs/
|
|
2
|
+
* The shared pack-trust behavior (docs/packs.md, docs/hosts.md's host
|
|
3
3
|
* checklist). A host that declares `HostPackSource.trust: 'device-list'`
|
|
4
4
|
* loads an installed pack only when this device's trust list names its
|
|
5
5
|
* namespace. A folder that sits in the install root without being named
|
|
@@ -74,6 +74,10 @@ export interface RunViaAdapterRequest {
|
|
|
74
74
|
/** A run's result plus the `RunTrace` it just recorded, so a host can show its in-note marker from the same value the shared path persisted. */
|
|
75
75
|
export type RunViaAdapterResult = RunOnceResult & {
|
|
76
76
|
readonly trace: RunTrace;
|
|
77
|
+
/** How many of this run's values came back with the value store's `fresh` status. */
|
|
78
|
+
readonly freshCount: number;
|
|
79
|
+
/** How many of this run's values came back with the value store's `error` status. */
|
|
80
|
+
readonly errorCount: number;
|
|
77
81
|
};
|
|
78
82
|
/**
|
|
79
83
|
* Runs one manual, auto, or scheduled pass of `request.text`'s scripts,
|
|
@@ -185,5 +185,13 @@ export async function runViaAdapter(adapter, request, overrideSpawnRun) {
|
|
|
185
185
|
// must never turn a successful (or already-reported) run into a thrown
|
|
186
186
|
// error.
|
|
187
187
|
await writeLastRunTrace(adapter.memento, request.documentKey, trace).then(undefined, () => { });
|
|
188
|
-
|
|
188
|
+
let freshCount = 0;
|
|
189
|
+
let errorCount = 0;
|
|
190
|
+
for (const value of Object.values(result.values)) {
|
|
191
|
+
if (value.status === 'fresh')
|
|
192
|
+
freshCount += 1;
|
|
193
|
+
else if (value.status === 'error')
|
|
194
|
+
errorCount += 1;
|
|
195
|
+
}
|
|
196
|
+
return { ...result, trace, freshCount, errorCount };
|
|
189
197
|
}
|