@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,189 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The `.mkp` pack-install decision logic, merged from
|
|
3
|
+
* `apps/vscode/src/packs/install-pack.ts` and
|
|
4
|
+
* `apps/obsidian/src/packs/install-pack.ts` (survey finding A2: the single
|
|
5
|
+
* biggest duplication finding in the survey — identical function names,
|
|
6
|
+
* identical types, near-identical wording, differing only in that
|
|
7
|
+
* Obsidian refuses a bundled-namespace archive before the consent prompt
|
|
8
|
+
* and VS Code has no such concept).
|
|
9
|
+
*
|
|
10
|
+
* `reservedNamespaces` generalizes Obsidian's `bundledNamespaces`: it
|
|
11
|
+
* defaults to empty, so VS Code's behavior is byte-identical to before
|
|
12
|
+
* (the `'reserved'` outcome branch simply never fires there), and
|
|
13
|
+
* Obsidian's adapter passes its three bundled namespaces
|
|
14
|
+
* (`read`/`dash`/`prep`) through `HostPackSource.reservedNamespaces()`.
|
|
15
|
+
*
|
|
16
|
+
* ORDER IS SECURITY-RELEVANT and preserved exactly: validate the archive,
|
|
17
|
+
* refuse a reserved namespace, ask consent to run the pack's code, ask
|
|
18
|
+
* before replacing an already-installed pack of the same namespace, and
|
|
19
|
+
* only then write anything to disk. Nothing is written until every
|
|
20
|
+
* applicable step has already said yes.
|
|
21
|
+
*/
|
|
22
|
+
import { joinPath } from '../path-util.js';
|
|
23
|
+
import { openPackArchive } from '@markii/pack';
|
|
24
|
+
import { describeArchiveError, writeArchiveContents } from './pack-archive.js';
|
|
25
|
+
import { trustInstalledPack } from './pack-trust-behavior.js';
|
|
26
|
+
function describeThrown(err) {
|
|
27
|
+
return err instanceof Error ? err.message : String(err);
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Never throws: a validation failure, a declined prompt, or a write
|
|
31
|
+
* failure all come back as a structured outcome. Order: validate the
|
|
32
|
+
* archive first (a rejected archive is never even offered the consent
|
|
33
|
+
* prompt), then refuse a reserved namespace, then ask consent to run its
|
|
34
|
+
* code, then, only if a pack of the same namespace is already installed,
|
|
35
|
+
* ask to replace it. Nothing is written until every applicable step has
|
|
36
|
+
* said yes.
|
|
37
|
+
*
|
|
38
|
+
* Every prompt routes through `adapter.prompt` with `kind:
|
|
39
|
+
* 'pack-install-consent'`/`'pack-replace'`, per `HostAdapter`'s contract —
|
|
40
|
+
* a host that can only show a non-blocking surface must deny rather than
|
|
41
|
+
* assume yes.
|
|
42
|
+
*/
|
|
43
|
+
export async function installPackFromArchive(adapter, options) {
|
|
44
|
+
const { archiveBytes, archivePath, installRoot, exists, extractFs, reservedNamespaces, } = options;
|
|
45
|
+
const opened = await openPackArchive(archiveBytes);
|
|
46
|
+
if (!opened.ok) {
|
|
47
|
+
return {
|
|
48
|
+
kind: 'rejected',
|
|
49
|
+
reason: `"${archivePath}" is not a valid pack archive: ${describeArchiveError(opened.error)}`,
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
const packName = opened.archive.manifest.name;
|
|
53
|
+
if (reservedNamespaces.has(packName)) {
|
|
54
|
+
return { kind: 'reserved', packName };
|
|
55
|
+
}
|
|
56
|
+
let consented;
|
|
57
|
+
try {
|
|
58
|
+
consented = await adapter.prompt({
|
|
59
|
+
kind: 'pack-install-consent',
|
|
60
|
+
message: installConsentMessage(packName, adapter.labels),
|
|
61
|
+
allowLabel: 'Install',
|
|
62
|
+
denyLabel: 'Cancel',
|
|
63
|
+
consequential: true,
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
catch (err) {
|
|
67
|
+
return { kind: 'rejected', reason: describeThrown(err) };
|
|
68
|
+
}
|
|
69
|
+
if (!consented) {
|
|
70
|
+
return { kind: 'declined', step: 'consent', packName };
|
|
71
|
+
}
|
|
72
|
+
const targetDir = joinPath(installRoot, packName);
|
|
73
|
+
let alreadyInstalled;
|
|
74
|
+
try {
|
|
75
|
+
alreadyInstalled = await exists(targetDir);
|
|
76
|
+
}
|
|
77
|
+
catch (err) {
|
|
78
|
+
return { kind: 'rejected', reason: describeThrown(err) };
|
|
79
|
+
}
|
|
80
|
+
let replaced = false;
|
|
81
|
+
if (alreadyInstalled) {
|
|
82
|
+
let proceed;
|
|
83
|
+
try {
|
|
84
|
+
proceed = await adapter.prompt({
|
|
85
|
+
kind: 'pack-replace',
|
|
86
|
+
message: installReplaceConfirmMessage(packName),
|
|
87
|
+
allowLabel: 'Replace',
|
|
88
|
+
denyLabel: 'Cancel',
|
|
89
|
+
consequential: true,
|
|
90
|
+
});
|
|
91
|
+
}
|
|
92
|
+
catch (err) {
|
|
93
|
+
return { kind: 'rejected', reason: describeThrown(err) };
|
|
94
|
+
}
|
|
95
|
+
if (!proceed) {
|
|
96
|
+
return { kind: 'declined', step: 'replace', packName };
|
|
97
|
+
}
|
|
98
|
+
replaced = true;
|
|
99
|
+
}
|
|
100
|
+
try {
|
|
101
|
+
await extractFs.removeDirectory(targetDir);
|
|
102
|
+
await writeArchiveContents(opened.archive, targetDir, extractFs);
|
|
103
|
+
}
|
|
104
|
+
catch (err) {
|
|
105
|
+
return {
|
|
106
|
+
kind: 'rejected',
|
|
107
|
+
reason: `could not install pack "${packName}": ${describeThrown(err)}`,
|
|
108
|
+
};
|
|
109
|
+
}
|
|
110
|
+
await trustInstalledPack(adapter, packName, targetDir);
|
|
111
|
+
return { kind: 'installed', packName, installedDir: targetDir, replaced };
|
|
112
|
+
}
|
|
113
|
+
/**
|
|
114
|
+
* The consent prompt's wording (AGENTS.md's product principles: "clean is
|
|
115
|
+
* not silent," and a consent step must say plainly what it authorizes).
|
|
116
|
+
* States outright that the pack's code will run inside the preview, since
|
|
117
|
+
* this is the one place that consent is asked, so it has to say it, not
|
|
118
|
+
* hint at it.
|
|
119
|
+
*
|
|
120
|
+
* CANONICAL WORDING CHOSEN over the two apps' prior copy (report this as a
|
|
121
|
+
* before/after pair): "from someone you trust" (Obsidian's phrasing) over
|
|
122
|
+
* "from a source you trust" (VS Code's), and `labels.previewNoun` ("the
|
|
123
|
+
* Markii preview" for both hosts) in place of VS Code's prior "every
|
|
124
|
+
* Markii preview" — `HostLabels.previewNoun` already gives both hosts the
|
|
125
|
+
* same noun, so the interpolation settles the wording rather than
|
|
126
|
+
* widening `HostLabels` for a distinction the record does not carry.
|
|
127
|
+
*/
|
|
128
|
+
export function installConsentMessage(packName, labels) {
|
|
129
|
+
return `Installing "${packName}" lets its code run inside ${labels.previewNoun}. Only install a pack from someone you trust.`;
|
|
130
|
+
}
|
|
131
|
+
/**
|
|
132
|
+
* The collision confirmation's wording: a namespace already installed,
|
|
133
|
+
* asked before replacing it. CANONICAL WORDING: "removes" (VS Code's prior
|
|
134
|
+
* wording) over Obsidian's prior "deletes" — see `tmp/W11-phase1b.md`'s
|
|
135
|
+
* before/after table for the exact strings this replaces.
|
|
136
|
+
*/
|
|
137
|
+
export function installReplaceConfirmMessage(packName) {
|
|
138
|
+
return `A pack named "${packName}" is already installed. Replacing it removes the installed copy and cannot be undone.`;
|
|
139
|
+
}
|
|
140
|
+
/**
|
|
141
|
+
* The one result message for the install command, matching this host's
|
|
142
|
+
* wording rules (two short sentences, no em dashes or parentheses in the
|
|
143
|
+
* popup; the full detail goes to the diagnostics surface).
|
|
144
|
+
*
|
|
145
|
+
* `reloadsAutomatically` is a per-host BEHAVIOR fact, not a wording noun:
|
|
146
|
+
* Obsidian's install command reloads every open view's packs immediately
|
|
147
|
+
* after a successful install, so its message says so; VS Code's install
|
|
148
|
+
* command does not (an installed pack still needs to be added to
|
|
149
|
+
* `markii.packs` before anything loads it), so it doesn't. Defaults to
|
|
150
|
+
* `false`.
|
|
151
|
+
*/
|
|
152
|
+
export function installPackMessage(outcome, archivePath, labels, reloadsAutomatically = false) {
|
|
153
|
+
if (outcome.kind === 'rejected') {
|
|
154
|
+
return `Markii: could not install a pack from "${archivePath}". Open ${labels.diagnosticsSurface} for details.`;
|
|
155
|
+
}
|
|
156
|
+
if (outcome.kind === 'reserved') {
|
|
157
|
+
return `Markii: "${outcome.packName}" is built in and cannot be installed. Nothing was installed.`;
|
|
158
|
+
}
|
|
159
|
+
if (outcome.kind === 'declined') {
|
|
160
|
+
return outcome.step === 'consent'
|
|
161
|
+
? `Markii: install of "${outcome.packName}" cancelled. Nothing was installed.`
|
|
162
|
+
: `Markii: install of "${outcome.packName}" cancelled. The existing pack was kept.`;
|
|
163
|
+
}
|
|
164
|
+
const reloadSuffix = reloadsAutomatically ? ' Markii packs reloaded.' : '';
|
|
165
|
+
return outcome.replaced
|
|
166
|
+
? `Markii: reinstalled pack "${outcome.packName}".${reloadSuffix}`
|
|
167
|
+
: `Markii: installed pack "${outcome.packName}".${reloadSuffix}`;
|
|
168
|
+
}
|
|
169
|
+
/** The full diagnostics-surface detail for one install attempt (AGENTS.md's "clean is not silent": the other of a failure's two homes). */
|
|
170
|
+
export function installPackDiagnosticLines(outcome, archivePath) {
|
|
171
|
+
if (outcome.kind === 'rejected') {
|
|
172
|
+
return [
|
|
173
|
+
`Install pack from file rejected "${archivePath}": ${outcome.reason}`,
|
|
174
|
+
];
|
|
175
|
+
}
|
|
176
|
+
if (outcome.kind === 'reserved') {
|
|
177
|
+
return [
|
|
178
|
+
`Install pack from file refused "${archivePath}": pack "${outcome.packName}" is a reserved namespace and cannot be installed.`,
|
|
179
|
+
];
|
|
180
|
+
}
|
|
181
|
+
if (outcome.kind === 'declined') {
|
|
182
|
+
return [
|
|
183
|
+
`Install pack from file cancelled for "${archivePath}" (pack "${outcome.packName}") at the ${outcome.step} step; nothing was installed.`,
|
|
184
|
+
];
|
|
185
|
+
}
|
|
186
|
+
return [
|
|
187
|
+
`Install pack from file installed pack "${outcome.packName}" from "${archivePath}" to ${outcome.installedDir}${outcome.replaced ? ' (replacing the previous install)' : ''}.`,
|
|
188
|
+
];
|
|
189
|
+
}
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The shared discovery path behind `loadPacks`, merged from
|
|
3
|
+
* `apps/vscode/src/packs/discover-configured-packs.ts` and
|
|
4
|
+
* `apps/obsidian/src/packs/discover-configured-packs.ts` (survey finding
|
|
5
|
+
* A3) plus the discovery-and-namespace-collection half both apps'
|
|
6
|
+
* `packs/pack-context.ts` also duplicate on top of it.
|
|
7
|
+
*
|
|
8
|
+
* Pack POLICY — which folders are even candidates before any content is
|
|
9
|
+
* read — is irreducibly host-specific (VS Code's resolved `markii.packs`
|
|
10
|
+
* setting merged with its bundled packs; Obsidian's trust-filtered
|
|
11
|
+
* installed folders merged with ITS bundled packs) and stays entirely
|
|
12
|
+
* app-side, behind `HostPackSource.authorizedFolders()`. Everything AFTER
|
|
13
|
+
* that list exists — read each folder's `pack.json`, validate it, drop a
|
|
14
|
+
* namespace collision, engine-gate against this renderer — was already the
|
|
15
|
+
* identical one-line call to `../packs/discover.ts`'s `discoverPacks` in
|
|
16
|
+
* both apps, and that is what this module is.
|
|
17
|
+
*
|
|
18
|
+
* This is the CHEAP catalog path (a pack's manifest only: names,
|
|
19
|
+
* namespaces, component attributes), the same shape both apps' Insert
|
|
20
|
+
* Component command and directive completion already used —
|
|
21
|
+
* deliberately not the heavier `pack-context.ts` composition (which also
|
|
22
|
+
* loads Lua modules and resolves or evaluates a webview registration
|
|
23
|
+
* script), since neither of those is anything `loadPacks`'s cheap-catalog
|
|
24
|
+
* callers need.
|
|
25
|
+
*/
|
|
26
|
+
import type { UnavailableComponent } from '@markii/stdlib';
|
|
27
|
+
import type { DiscoveredPack, PackDirectoryLister, PackFileReader, SkippedPackFolder } from '../packs/discover.js';
|
|
28
|
+
import type { HostAdapter, HostPackSource } from './adapter.js';
|
|
29
|
+
/** The two file-system reads pack discovery needs, injected so this module has no Node dependency. */
|
|
30
|
+
export interface PackDiscoveryIo {
|
|
31
|
+
readonly readFile: PackFileReader;
|
|
32
|
+
readonly listDirectory: PackDirectoryLister;
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Discovery reads derived from the adapter's own `readFile` and
|
|
36
|
+
* `listFolder`. Both are held to discovery's never-rejects contract: an
|
|
37
|
+
* unreadable file is `undefined`, an unreadable folder is `[]`.
|
|
38
|
+
*/
|
|
39
|
+
export declare function packDiscoveryIoFromAdapter(adapter: Pick<HostAdapter, 'readFile' | 'listFolder'>): PackDiscoveryIo;
|
|
40
|
+
export interface LoadPacksResult {
|
|
41
|
+
readonly packs: readonly DiscoveredPack[];
|
|
42
|
+
readonly skipped: readonly SkippedPackFolder[];
|
|
43
|
+
readonly namespaces: readonly string[];
|
|
44
|
+
/**
|
|
45
|
+
* Every component of every installed pack, including a pack dropped whole
|
|
46
|
+
* because it has nothing for this engine, with the engines each one has a
|
|
47
|
+
* view for. A host passes it to an engine that draws NO pack views, such
|
|
48
|
+
* as the static HTML export.
|
|
49
|
+
*/
|
|
50
|
+
readonly components: readonly UnavailableComponent[];
|
|
51
|
+
/**
|
|
52
|
+
* The subset of `components` with no view for this host's engine: the list
|
|
53
|
+
* a host passes to its renderer's `unavailableComponents` option, so a
|
|
54
|
+
* note using one says "no view here" instead of "unknown component".
|
|
55
|
+
*/
|
|
56
|
+
readonly unavailable: readonly UnavailableComponent[];
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* Discovers every pack under `folders`, engine-gated against
|
|
60
|
+
* `engine` (the host's renderer engine id, given by the caller) — the same
|
|
61
|
+
* posture a renderer's pack loader takes for an unsupported `engine`, so
|
|
62
|
+
* neither Insert Component nor completion ever offers a component this
|
|
63
|
+
* renderer could not render.
|
|
64
|
+
*/
|
|
65
|
+
export declare function discoverConfiguredPacks(folders: readonly string[], io: PackDiscoveryIo, engine: string): Promise<LoadPacksResult>;
|
|
66
|
+
/**
|
|
67
|
+
* Runs `discoverConfiguredPacks` over `source.authorizedFolders()`. The
|
|
68
|
+
* caller (`createMarkiiHost`'s `loadPacks`) checks that
|
|
69
|
+
* `adapter.packs` exists before calling this — a host with no `packs`
|
|
70
|
+
* capability at all returns `Unsupported` first and never reaches here.
|
|
71
|
+
*/
|
|
72
|
+
export declare function loadPacksViaAdapter(source: HostPackSource, io: PackDiscoveryIo): Promise<LoadPacksResult>;
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import { componentsWithoutEngineView, declaredPackComponents, discoverPacks, installedNamespaces, } from '../packs/discover.js';
|
|
2
|
+
/**
|
|
3
|
+
* Discovery reads derived from the adapter's own `readFile` and
|
|
4
|
+
* `listFolder`. Both are held to discovery's never-rejects contract: an
|
|
5
|
+
* unreadable file is `undefined`, an unreadable folder is `[]`.
|
|
6
|
+
*/
|
|
7
|
+
export function packDiscoveryIoFromAdapter(adapter) {
|
|
8
|
+
return {
|
|
9
|
+
readFile: async (absolutePath) => {
|
|
10
|
+
try {
|
|
11
|
+
return new TextDecoder().decode(await adapter.readFile(absolutePath));
|
|
12
|
+
}
|
|
13
|
+
catch {
|
|
14
|
+
return undefined;
|
|
15
|
+
}
|
|
16
|
+
},
|
|
17
|
+
listDirectory: async (absoluteDir) => {
|
|
18
|
+
try {
|
|
19
|
+
return [...(await adapter.listFolder(absoluteDir))];
|
|
20
|
+
}
|
|
21
|
+
catch {
|
|
22
|
+
return [];
|
|
23
|
+
}
|
|
24
|
+
},
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Discovers every pack under `folders`, engine-gated against
|
|
29
|
+
* `engine` (the host's renderer engine id, given by the caller) — the same
|
|
30
|
+
* posture a renderer's pack loader takes for an unsupported `engine`, so
|
|
31
|
+
* neither Insert Component nor completion ever offers a component this
|
|
32
|
+
* renderer could not render.
|
|
33
|
+
*/
|
|
34
|
+
export async function discoverConfiguredPacks(folders, io, engine) {
|
|
35
|
+
const result = await discoverPacks(folders, io.readFile, io.listDirectory, engine);
|
|
36
|
+
const components = [
|
|
37
|
+
...result.packs.flatMap((pack) => declaredPackComponents(pack.manifest)),
|
|
38
|
+
...result.skipped.flatMap((entry) => entry.engineUnsupported?.components ?? []),
|
|
39
|
+
];
|
|
40
|
+
return {
|
|
41
|
+
packs: result.packs,
|
|
42
|
+
skipped: result.skipped,
|
|
43
|
+
namespaces: installedNamespaces(result.packs),
|
|
44
|
+
components,
|
|
45
|
+
unavailable: componentsWithoutEngineView(components, engine),
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* Runs `discoverConfiguredPacks` over `source.authorizedFolders()`. The
|
|
50
|
+
* caller (`createMarkiiHost`'s `loadPacks`) checks that
|
|
51
|
+
* `adapter.packs` exists before calling this — a host with no `packs`
|
|
52
|
+
* capability at all returns `Unsupported` first and never reaches here.
|
|
53
|
+
*/
|
|
54
|
+
export async function loadPacksViaAdapter(source, io) {
|
|
55
|
+
const folders = await source.authorizedFolders();
|
|
56
|
+
return discoverConfiguredPacks(folders, io, source.engine);
|
|
57
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import type { HostAdapter } from './adapter.js';
|
|
2
|
+
export type RemovePackOutcome = {
|
|
3
|
+
readonly kind: 'removed';
|
|
4
|
+
readonly namespace: string;
|
|
5
|
+
} | {
|
|
6
|
+
readonly kind: 'not-installed';
|
|
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
|
+
};
|
|
18
|
+
/** 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<RemovePackOutcome>;
|
|
20
|
+
export declare function removePackMessage(outcome: RemovePackOutcome): string;
|
|
21
|
+
export declare function removePackDiagnosticLine(outcome: RemovePackOutcome): string;
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Removing an installed pack: the one shared behavior behind a host's
|
|
3
|
+
* "remove pack" command. The install root holds one folder per namespace
|
|
4
|
+
* (`pack-install.ts`), so removal is a path join, a reserved-namespace
|
|
5
|
+
* check, and `HostPackSource.removeFolder`. Nothing outside the install
|
|
6
|
+
* root is ever touched: the namespace is validated as a pack name (no
|
|
7
|
+
* separators, no dots) before any path is built.
|
|
8
|
+
*/
|
|
9
|
+
import { joinPath } from '../path-util.js';
|
|
10
|
+
import { validatePackName } from '@markii/pack';
|
|
11
|
+
/** Never throws. The caller checks `adapter.packs.installRoot`/`removeFolder` exist first. */
|
|
12
|
+
export async function removePackViaAdapter(adapter, namespace, installRoot, removeFolder, reservedNamespaces) {
|
|
13
|
+
const valid = validatePackName(namespace);
|
|
14
|
+
if (!valid.ok) {
|
|
15
|
+
return {
|
|
16
|
+
kind: 'invalid',
|
|
17
|
+
reason: `"${namespace}" is not a valid pack namespace`,
|
|
18
|
+
};
|
|
19
|
+
}
|
|
20
|
+
if (reservedNamespaces.has(namespace)) {
|
|
21
|
+
return { kind: 'reserved', namespace };
|
|
22
|
+
}
|
|
23
|
+
const folder = joinPath(installRoot, namespace);
|
|
24
|
+
try {
|
|
25
|
+
if (!(await adapter.exists(folder))) {
|
|
26
|
+
return { kind: 'not-installed', namespace };
|
|
27
|
+
}
|
|
28
|
+
await removeFolder(folder);
|
|
29
|
+
}
|
|
30
|
+
catch (err) {
|
|
31
|
+
return {
|
|
32
|
+
kind: 'failed',
|
|
33
|
+
reason: `could not remove pack "${namespace}": ${err instanceof Error ? err.message : String(err)}`,
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
return { kind: 'removed', namespace };
|
|
37
|
+
}
|
|
38
|
+
export function removePackMessage(outcome) {
|
|
39
|
+
switch (outcome.kind) {
|
|
40
|
+
case 'removed':
|
|
41
|
+
return `Markii: removed pack "${outcome.namespace}".`;
|
|
42
|
+
case 'not-installed':
|
|
43
|
+
return `Markii: no pack named "${outcome.namespace}" is installed. Nothing was removed.`;
|
|
44
|
+
case 'reserved':
|
|
45
|
+
return `Markii: "${outcome.namespace}" is built in and cannot be removed. Nothing was removed.`;
|
|
46
|
+
case 'invalid':
|
|
47
|
+
return `Markii: ${outcome.reason}. Nothing was removed.`;
|
|
48
|
+
case 'failed':
|
|
49
|
+
return `Markii: ${outcome.reason}.`;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
export function removePackDiagnosticLine(outcome) {
|
|
53
|
+
switch (outcome.kind) {
|
|
54
|
+
case 'removed':
|
|
55
|
+
return `Remove pack removed "${outcome.namespace}".`;
|
|
56
|
+
case 'not-installed':
|
|
57
|
+
return `Remove pack found no installed pack "${outcome.namespace}"; nothing was removed.`;
|
|
58
|
+
case 'reserved':
|
|
59
|
+
return `Remove pack refused "${outcome.namespace}": a built-in pack cannot be removed.`;
|
|
60
|
+
case 'invalid':
|
|
61
|
+
return `Remove pack refused: ${outcome.reason}.`;
|
|
62
|
+
case 'failed':
|
|
63
|
+
return `Remove pack failed: ${outcome.reason}`;
|
|
64
|
+
}
|
|
65
|
+
}
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import type { PackTrustList } from '../packs/pack-trust.js';
|
|
2
|
+
import type { HostAdapter } from './adapter.js';
|
|
3
|
+
/** One installed pack this device has enabled. */
|
|
4
|
+
export interface EnabledPack {
|
|
5
|
+
readonly namespace: string;
|
|
6
|
+
/** Absolute folder under the install root. */
|
|
7
|
+
readonly folder: string;
|
|
8
|
+
/** The version recorded in the trust list, when the pack declared one. */
|
|
9
|
+
readonly version?: string;
|
|
10
|
+
}
|
|
11
|
+
export interface InstalledPacksListing {
|
|
12
|
+
/** Folders in the install root whose namespace this device trusts. */
|
|
13
|
+
readonly enabled: readonly EnabledPack[];
|
|
14
|
+
/** Folders in the install root this device does not trust: present, never loaded. */
|
|
15
|
+
readonly notEnabled: readonly string[];
|
|
16
|
+
}
|
|
17
|
+
/** Whether this adapter's pack source gates installed packs on the device trust list and can say where they live. */
|
|
18
|
+
export declare function usesPackTrust(adapter: HostAdapter): boolean;
|
|
19
|
+
export declare function readPackTrust(adapter: HostAdapter): PackTrustList;
|
|
20
|
+
/**
|
|
21
|
+
* Pure: splits the namespaces found in the install root by the trust
|
|
22
|
+
* list. A trust entry naming a namespace no longer on disk contributes to
|
|
23
|
+
* neither side: there is nothing to load and nothing to report.
|
|
24
|
+
*/
|
|
25
|
+
export declare function selectEnabledPacks(installRoot: string, onDiskNamespaces: readonly string[], trust: PackTrustList): InstalledPacksListing;
|
|
26
|
+
/** Lists the install root through the adapter and splits it by trust. A host without trust returns everything as neither: callers check `usesPackTrust` first. Never throws. */
|
|
27
|
+
export declare function listInstalledPacks(adapter: HostAdapter): Promise<InstalledPacksListing>;
|
|
28
|
+
/** The folders the trust gate lets load, for `authorizedFolders` to be extended with. Empty for a host that does not use trust. */
|
|
29
|
+
export declare function trustedPackFolders(adapter: HostAdapter): Promise<readonly string[]>;
|
|
30
|
+
/**
|
|
31
|
+
* Called by the install path after an archive was extracted: records the
|
|
32
|
+
* namespace as trusted on this device. A no-op for a host without trust.
|
|
33
|
+
* Never throws; a refused write is reported on the diagnostics surface.
|
|
34
|
+
*/
|
|
35
|
+
export declare function trustInstalledPack(adapter: HostAdapter, namespace: string, installedDir: string): Promise<void>;
|
|
36
|
+
export type EnablePackOutcome = {
|
|
37
|
+
readonly kind: 'enabled';
|
|
38
|
+
readonly namespace: string;
|
|
39
|
+
} | {
|
|
40
|
+
readonly kind: 'declined';
|
|
41
|
+
readonly namespace: string;
|
|
42
|
+
} | {
|
|
43
|
+
readonly kind: 'not-installed';
|
|
44
|
+
readonly namespace: string;
|
|
45
|
+
} | {
|
|
46
|
+
readonly kind: 'reserved';
|
|
47
|
+
readonly namespace: string;
|
|
48
|
+
} | {
|
|
49
|
+
readonly kind: 'invalid';
|
|
50
|
+
readonly reason: string;
|
|
51
|
+
} | {
|
|
52
|
+
readonly kind: 'failed';
|
|
53
|
+
readonly reason: string;
|
|
54
|
+
};
|
|
55
|
+
export type DisablePackOutcome = {
|
|
56
|
+
readonly kind: 'disabled';
|
|
57
|
+
readonly namespace: string;
|
|
58
|
+
} | {
|
|
59
|
+
readonly kind: 'not-enabled';
|
|
60
|
+
readonly namespace: string;
|
|
61
|
+
} | {
|
|
62
|
+
readonly kind: 'invalid';
|
|
63
|
+
readonly reason: string;
|
|
64
|
+
} | {
|
|
65
|
+
readonly kind: 'failed';
|
|
66
|
+
readonly reason: string;
|
|
67
|
+
};
|
|
68
|
+
/** Never throws. The caller checks `usesPackTrust` first. */
|
|
69
|
+
export declare function enablePackViaAdapter(adapter: HostAdapter, namespace: string): Promise<EnablePackOutcome>;
|
|
70
|
+
/** Never throws. The caller checks `usesPackTrust` first. The folder stays on disk. */
|
|
71
|
+
export declare function disablePackViaAdapter(adapter: HostAdapter, namespace: string): Promise<DisablePackOutcome>;
|
|
72
|
+
/** Withdraws a namespace's trust entry after a removal. A no-op for a host without trust. Never throws. */
|
|
73
|
+
export declare function forgetPackTrust(adapter: HostAdapter, namespace: string): Promise<void>;
|
|
74
|
+
export declare function enablePackMessage(outcome: EnablePackOutcome): string;
|
|
75
|
+
export declare function enablePackDiagnosticLine(outcome: EnablePackOutcome): string;
|
|
76
|
+
export declare function disablePackMessage(outcome: DisablePackOutcome): string;
|
|
77
|
+
export declare function disablePackDiagnosticLine(outcome: DisablePackOutcome): string;
|
|
78
|
+
/** One line per folder that is present in the install root but not enabled on this device. Informational, not a failure. */
|
|
79
|
+
export declare function notEnabledPackDiagnosticLine(namespace: string, deviceNoun: string): string;
|