@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,57 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The DEVICE-LOCAL pack trust list: which installed-pack namespaces this
|
|
3
|
+
* device authorizes to load (docs/packs.md). Every namespace named here
|
|
4
|
+
* has its pack code evaluated at host trust, so the list authorizes code
|
|
5
|
+
* execution and must live in device-local state (the adapter's
|
|
6
|
+
* `memento`), never in anything that travels with a vault, a workspace
|
|
7
|
+
* file, or a sync service.
|
|
8
|
+
*
|
|
9
|
+
* A pack folder can sit in the install root without being trusted: it
|
|
10
|
+
* arrived through a file sync, or a user copied it there by hand. Such a
|
|
11
|
+
* folder is never loaded until its namespace is added here, exactly like
|
|
12
|
+
* a freshly installed one, and the consent prompt is asked either way.
|
|
13
|
+
* The trust list, not the presence of a folder, decides whether code
|
|
14
|
+
* runs. `../host/pack-trust-behavior.ts` enforces that for every host
|
|
15
|
+
* that declares `trust: 'device-list'`.
|
|
16
|
+
*
|
|
17
|
+
* Pure and environment-free, so the shape and its hostile-input
|
|
18
|
+
* normalization are unit-testable with no host in sight.
|
|
19
|
+
*/
|
|
20
|
+
/** The memento key this shape lives under. It is the key Obsidian has always stored this list under, so an existing device keeps its trust list. */
|
|
21
|
+
export declare const PACK_TRUST_STORAGE_KEY = "markii:installedPacks";
|
|
22
|
+
/** A sane upper bound on how many namespaces this list may hold : a real device installs a handful of packs; this only exists to bound a hostile or corrupt stored value. */
|
|
23
|
+
export declare const MAX_TRUSTED_PACKS = 200;
|
|
24
|
+
/** One namespace this device trusts, and the version it was installed at (the manifest's own `version` field, or `undefined` when the pack declares none). */
|
|
25
|
+
export interface PackTrustEntry {
|
|
26
|
+
readonly namespace: string;
|
|
27
|
+
readonly version?: string;
|
|
28
|
+
}
|
|
29
|
+
export interface PackTrustList {
|
|
30
|
+
readonly entries: readonly PackTrustEntry[];
|
|
31
|
+
}
|
|
32
|
+
export declare const DEFAULT_PACK_TRUST_LIST: PackTrustList;
|
|
33
|
+
/**
|
|
34
|
+
* Normalizes whatever `app.loadLocalStorage(PACK_TRUST_STORAGE_KEY)` handed
|
|
35
|
+
* back into a well-formed `PackTrustList` : hostile-shape-guarded the same
|
|
36
|
+
* way every other device-local shape in this plugin is, since this is also
|
|
37
|
+
* hand-editable, foreign-version-shaped data in principle. A malformed
|
|
38
|
+
* entry is dropped rather than failing the whole read; duplicates are
|
|
39
|
+
* removed by namespace, first occurrence wins; the list is bounded by
|
|
40
|
+
* `MAX_TRUSTED_PACKS`.
|
|
41
|
+
*/
|
|
42
|
+
export declare function normalizePackTrustList(data: unknown): PackTrustList;
|
|
43
|
+
/** Whether `namespace` is trusted on this device. */
|
|
44
|
+
export declare function isPackTrusted(list: PackTrustList, namespace: string): boolean;
|
|
45
|
+
/**
|
|
46
|
+
* Adds (or replaces) `namespace`'s trust entry, recording `version` when
|
|
47
|
+
* given. Used both by a fresh install and by "Enable" for a pack folder
|
|
48
|
+
* that was already present on disk. Returns the same list unchanged (never
|
|
49
|
+
* a new object) when the entry is already present with the same version,
|
|
50
|
+
* so a caller can skip a write that would change nothing.
|
|
51
|
+
*/
|
|
52
|
+
export declare function trustPack(list: PackTrustList, namespace: string, version?: string): PackTrustList;
|
|
53
|
+
/**
|
|
54
|
+
* Removes `namespace`'s trust entry. Returns the same list unchanged when
|
|
55
|
+
* it was not present, so a caller can skip a no-op write.
|
|
56
|
+
*/
|
|
57
|
+
export declare function untrustPack(list: PackTrustList, namespace: string): PackTrustList;
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The DEVICE-LOCAL pack trust list: which installed-pack namespaces this
|
|
3
|
+
* device authorizes to load (docs/packs.md). Every namespace named here
|
|
4
|
+
* has its pack code evaluated at host trust, so the list authorizes code
|
|
5
|
+
* execution and must live in device-local state (the adapter's
|
|
6
|
+
* `memento`), never in anything that travels with a vault, a workspace
|
|
7
|
+
* file, or a sync service.
|
|
8
|
+
*
|
|
9
|
+
* A pack folder can sit in the install root without being trusted: it
|
|
10
|
+
* arrived through a file sync, or a user copied it there by hand. Such a
|
|
11
|
+
* folder is never loaded until its namespace is added here, exactly like
|
|
12
|
+
* a freshly installed one, and the consent prompt is asked either way.
|
|
13
|
+
* The trust list, not the presence of a folder, decides whether code
|
|
14
|
+
* runs. `../host/pack-trust-behavior.ts` enforces that for every host
|
|
15
|
+
* that declares `trust: 'device-list'`.
|
|
16
|
+
*
|
|
17
|
+
* Pure and environment-free, so the shape and its hostile-input
|
|
18
|
+
* normalization are unit-testable with no host in sight.
|
|
19
|
+
*/
|
|
20
|
+
/** The memento key this shape lives under. It is the key Obsidian has always stored this list under, so an existing device keeps its trust list. */
|
|
21
|
+
export const PACK_TRUST_STORAGE_KEY = 'markii:installedPacks';
|
|
22
|
+
/** A sane upper bound on how many namespaces this list may hold : a real device installs a handful of packs; this only exists to bound a hostile or corrupt stored value. */
|
|
23
|
+
export const MAX_TRUSTED_PACKS = 200;
|
|
24
|
+
export const DEFAULT_PACK_TRUST_LIST = { entries: [] };
|
|
25
|
+
function isPlainObject(value) {
|
|
26
|
+
return typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
27
|
+
}
|
|
28
|
+
/** Normalizes one raw entry, or `undefined` when it does not have a usable `namespace`. A non-string, empty, or whitespace-only `namespace` is dropped rather than crashing the whole read; a non-string `version` is dropped (the entry survives, just without one). */
|
|
29
|
+
function normalizeEntry(raw) {
|
|
30
|
+
if (!isPlainObject(raw))
|
|
31
|
+
return undefined;
|
|
32
|
+
const namespace = raw.namespace;
|
|
33
|
+
if (typeof namespace !== 'string')
|
|
34
|
+
return undefined;
|
|
35
|
+
const trimmed = namespace.trim();
|
|
36
|
+
if (trimmed.length === 0)
|
|
37
|
+
return undefined;
|
|
38
|
+
const version = raw.version;
|
|
39
|
+
return typeof version === 'string' && version.trim().length > 0
|
|
40
|
+
? { namespace: trimmed, version: version.trim() }
|
|
41
|
+
: { namespace: trimmed };
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* Normalizes whatever `app.loadLocalStorage(PACK_TRUST_STORAGE_KEY)` handed
|
|
45
|
+
* back into a well-formed `PackTrustList` : hostile-shape-guarded the same
|
|
46
|
+
* way every other device-local shape in this plugin is, since this is also
|
|
47
|
+
* hand-editable, foreign-version-shaped data in principle. A malformed
|
|
48
|
+
* entry is dropped rather than failing the whole read; duplicates are
|
|
49
|
+
* removed by namespace, first occurrence wins; the list is bounded by
|
|
50
|
+
* `MAX_TRUSTED_PACKS`.
|
|
51
|
+
*/
|
|
52
|
+
export function normalizePackTrustList(data) {
|
|
53
|
+
if (!isPlainObject(data))
|
|
54
|
+
return { ...DEFAULT_PACK_TRUST_LIST };
|
|
55
|
+
const rawEntries = data.entries;
|
|
56
|
+
if (!Array.isArray(rawEntries))
|
|
57
|
+
return { ...DEFAULT_PACK_TRUST_LIST };
|
|
58
|
+
const seen = new Set();
|
|
59
|
+
const entries = [];
|
|
60
|
+
for (const rawEntry of rawEntries) {
|
|
61
|
+
if (entries.length >= MAX_TRUSTED_PACKS)
|
|
62
|
+
break;
|
|
63
|
+
const entry = normalizeEntry(rawEntry);
|
|
64
|
+
if (!entry)
|
|
65
|
+
continue;
|
|
66
|
+
if (seen.has(entry.namespace))
|
|
67
|
+
continue;
|
|
68
|
+
seen.add(entry.namespace);
|
|
69
|
+
entries.push(entry);
|
|
70
|
+
}
|
|
71
|
+
return { entries };
|
|
72
|
+
}
|
|
73
|
+
/** Whether `namespace` is trusted on this device. */
|
|
74
|
+
export function isPackTrusted(list, namespace) {
|
|
75
|
+
return list.entries.some((entry) => entry.namespace === namespace);
|
|
76
|
+
}
|
|
77
|
+
/**
|
|
78
|
+
* Adds (or replaces) `namespace`'s trust entry, recording `version` when
|
|
79
|
+
* given. Used both by a fresh install and by "Enable" for a pack folder
|
|
80
|
+
* that was already present on disk. Returns the same list unchanged (never
|
|
81
|
+
* a new object) when the entry is already present with the same version,
|
|
82
|
+
* so a caller can skip a write that would change nothing.
|
|
83
|
+
*/
|
|
84
|
+
export function trustPack(list, namespace, version) {
|
|
85
|
+
const trimmed = namespace.trim();
|
|
86
|
+
if (trimmed.length === 0)
|
|
87
|
+
return list;
|
|
88
|
+
const existing = list.entries.find((entry) => entry.namespace === trimmed);
|
|
89
|
+
if (existing && existing.version === version)
|
|
90
|
+
return list;
|
|
91
|
+
const withoutExisting = list.entries.filter((entry) => entry.namespace !== trimmed);
|
|
92
|
+
const next = [
|
|
93
|
+
...withoutExisting,
|
|
94
|
+
version !== undefined
|
|
95
|
+
? { namespace: trimmed, version }
|
|
96
|
+
: { namespace: trimmed },
|
|
97
|
+
];
|
|
98
|
+
return { entries: next.slice(0, MAX_TRUSTED_PACKS) };
|
|
99
|
+
}
|
|
100
|
+
/**
|
|
101
|
+
* Removes `namespace`'s trust entry. Returns the same list unchanged when
|
|
102
|
+
* it was not present, so a caller can skip a no-op write.
|
|
103
|
+
*/
|
|
104
|
+
export function untrustPack(list, namespace) {
|
|
105
|
+
if (!list.entries.some((entry) => entry.namespace === namespace)) {
|
|
106
|
+
return list;
|
|
107
|
+
}
|
|
108
|
+
return {
|
|
109
|
+
entries: list.entries.filter((entry) => entry.namespace !== namespace),
|
|
110
|
+
};
|
|
111
|
+
}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import type { DiscoveredPack } from './discover.js';
|
|
2
|
+
export declare const PREBUILT_SCRIPT_FILENAME = "webview.js";
|
|
3
|
+
export declare const PREBUILT_STYLESHEET_FILENAME = "webview.css";
|
|
4
|
+
/** The terminal engine's prebuilt registration module, beside `webview.js` (docs/packs.md). Optional: a pack with no terminal views never has one. */
|
|
5
|
+
export declare const PREBUILT_TERMINAL_SCRIPT_FILENAME = "terminal.js";
|
|
6
|
+
/** Absolute path of a pack folder's prebuilt terminal script. No existence check. */
|
|
7
|
+
export declare function prebuiltTerminalScriptPathFor(folder: string): string;
|
|
8
|
+
/**
|
|
9
|
+
* The `webview.js` an export writes for a pack that has terminal views but
|
|
10
|
+
* no React ones: a registration with no components, so the archive keeps
|
|
11
|
+
* its required `webview.js` and a React host loads the pack as having
|
|
12
|
+
* nothing for it (every directive falls back).
|
|
13
|
+
*/
|
|
14
|
+
export declare function emptyWebviewScript(manifestJson: string): string;
|
|
15
|
+
/** Absolute path of a pack folder's prebuilt registration script. No existence check. */
|
|
16
|
+
export declare function prebuiltScriptPathFor(folder: string): string;
|
|
17
|
+
/** Absolute path of a pack folder's prebuilt sibling stylesheet. No existence check. */
|
|
18
|
+
export declare function prebuiltStylesheetPathFor(folder: string): string;
|
|
19
|
+
/** Whether an absolute path exists. Injected so this module needs no real disk to test; may be sync or async. */
|
|
20
|
+
export type PackPathExists = (absolutePath: string) => boolean | Promise<boolean>;
|
|
21
|
+
export interface PrebuiltPackResolution {
|
|
22
|
+
/** Absolute path to the pack's prebuilt webview.js (it exists). */
|
|
23
|
+
readonly scriptPath: string;
|
|
24
|
+
/** Absolute path to the sibling webview.css, present only when that file exists. */
|
|
25
|
+
readonly stylesheetPath?: string;
|
|
26
|
+
/**
|
|
27
|
+
* Absolute paths of the manifest-declared component sources that are
|
|
28
|
+
* present on disk and therefore shadowed by the prebuilt script. Empty
|
|
29
|
+
* when the pack ships no sources (the ordinary distribution shape:
|
|
30
|
+
* `webview.js` alone, docs/packs.md's "This is the form to ship when
|
|
31
|
+
* other people will use your pack").
|
|
32
|
+
*/
|
|
33
|
+
readonly shadowedComponentSources: readonly string[];
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* Resolves the prebuilt artifacts of one discovered pack, or `undefined`
|
|
37
|
+
* when the pack ships no `webview.js` at all (the from-source path, which a
|
|
38
|
+
* host compiles via `./pack-build.ts` instead).
|
|
39
|
+
*
|
|
40
|
+
* `pack.scriptPath` (already `folder/webview.js` from `./discover.ts`) is
|
|
41
|
+
* used as the script path rather than recomputed, but the stylesheet path
|
|
42
|
+
* is derived from the script path's own directory so the two always stay
|
|
43
|
+
* siblings even if a caller ever hands this a `DiscoveredPack` copy whose
|
|
44
|
+
* `folder` and `scriptPath` were computed independently.
|
|
45
|
+
*
|
|
46
|
+
* Never throws: an `exists` that throws is treated as "does not exist" for
|
|
47
|
+
* every path this function checks.
|
|
48
|
+
*/
|
|
49
|
+
export declare function resolvePrebuiltPack(pack: Pick<DiscoveredPack, 'folder' | 'componentPaths' | 'scriptPath'>, exists: PackPathExists): Promise<PrebuiltPackResolution | undefined>;
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The prebuilt-pack convention (docs/packs.md's "Two ways to run a pack:
|
|
3
|
+
* prebuilt and from source"): a pack that ships a compiled `webview.js`
|
|
4
|
+
* sibling to its `pack.json` is used as-is, no compiler runs. This module
|
|
5
|
+
* adds the second half of that convention (issue #15): an OPTIONAL sibling
|
|
6
|
+
* `webview.css`, injected/removed by a host's own pack-context composition
|
|
7
|
+
* keyed by namespace exactly like a compiled pack's own emitted stylesheet
|
|
8
|
+
* (`./pack-build.ts`'s `stylesheetPath`). A prebuilt pack with no
|
|
9
|
+
* `webview.css` simply has no stylesheet — never an error, matching
|
|
10
|
+
* `./discover.ts`'s "the file need not actually exist" posture for every
|
|
11
|
+
* other pack artifact.
|
|
12
|
+
*
|
|
13
|
+
* Also detects the SHADOWING case docs/packs.md names: "When a pack folder
|
|
14
|
+
* holds both a `webview.js` and sources, the prebuilt script wins and the
|
|
15
|
+
* sources are ignored." `resolvePrebuiltPack` reports exactly which of the
|
|
16
|
+
* manifest-declared component sources are present on disk, so a host's own
|
|
17
|
+
* diagnostics surface can name them (host-specific wording; this module
|
|
18
|
+
* only supplies the facts).
|
|
19
|
+
*
|
|
20
|
+
* Filesystem access is injected (`PackPathExists`), not imported directly,
|
|
21
|
+
* so this module is testable with plain in-memory fakes — matching every
|
|
22
|
+
* other module in this directory.
|
|
23
|
+
*/
|
|
24
|
+
import { dirnamePath, joinPath } from '../path-util.js';
|
|
25
|
+
export const PREBUILT_SCRIPT_FILENAME = 'webview.js';
|
|
26
|
+
export const PREBUILT_STYLESHEET_FILENAME = 'webview.css';
|
|
27
|
+
/** The terminal engine's prebuilt registration module, beside `webview.js` (docs/packs.md). Optional: a pack with no terminal views never has one. */
|
|
28
|
+
export const PREBUILT_TERMINAL_SCRIPT_FILENAME = 'terminal.js';
|
|
29
|
+
/** Absolute path of a pack folder's prebuilt terminal script. No existence check. */
|
|
30
|
+
export function prebuiltTerminalScriptPathFor(folder) {
|
|
31
|
+
return joinPath(folder, PREBUILT_TERMINAL_SCRIPT_FILENAME);
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* The `webview.js` an export writes for a pack that has terminal views but
|
|
35
|
+
* no React ones: a registration with no components, so the archive keeps
|
|
36
|
+
* its required `webview.js` and a React host loads the pack as having
|
|
37
|
+
* nothing for it (every directive falls back).
|
|
38
|
+
*/
|
|
39
|
+
export function emptyWebviewScript(manifestJson) {
|
|
40
|
+
return [
|
|
41
|
+
"if (typeof window !== 'undefined' && typeof window.__markiiRegisterPack === 'function') {",
|
|
42
|
+
` window.__markiiRegisterPack(${JSON.stringify(manifestJson)}, {});`,
|
|
43
|
+
'}',
|
|
44
|
+
'',
|
|
45
|
+
].join('\n');
|
|
46
|
+
}
|
|
47
|
+
/** Absolute path of a pack folder's prebuilt registration script. No existence check. */
|
|
48
|
+
export function prebuiltScriptPathFor(folder) {
|
|
49
|
+
return joinPath(folder, PREBUILT_SCRIPT_FILENAME);
|
|
50
|
+
}
|
|
51
|
+
/** Absolute path of a pack folder's prebuilt sibling stylesheet. No existence check. */
|
|
52
|
+
export function prebuiltStylesheetPathFor(folder) {
|
|
53
|
+
return joinPath(folder, PREBUILT_STYLESHEET_FILENAME);
|
|
54
|
+
}
|
|
55
|
+
/** Calls `exists`, treating a thrown error the same as "does not exist" — an unreadable path is not proof a file is absent, but this module's contract (like every other filesystem-adjacent module in this directory) is to degrade quietly rather than surface a raw I/O error here. */
|
|
56
|
+
async function existsQuietly(exists, absolutePath) {
|
|
57
|
+
try {
|
|
58
|
+
return await exists(absolutePath);
|
|
59
|
+
}
|
|
60
|
+
catch {
|
|
61
|
+
return false;
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* Resolves the prebuilt artifacts of one discovered pack, or `undefined`
|
|
66
|
+
* when the pack ships no `webview.js` at all (the from-source path, which a
|
|
67
|
+
* host compiles via `./pack-build.ts` instead).
|
|
68
|
+
*
|
|
69
|
+
* `pack.scriptPath` (already `folder/webview.js` from `./discover.ts`) is
|
|
70
|
+
* used as the script path rather than recomputed, but the stylesheet path
|
|
71
|
+
* is derived from the script path's own directory so the two always stay
|
|
72
|
+
* siblings even if a caller ever hands this a `DiscoveredPack` copy whose
|
|
73
|
+
* `folder` and `scriptPath` were computed independently.
|
|
74
|
+
*
|
|
75
|
+
* Never throws: an `exists` that throws is treated as "does not exist" for
|
|
76
|
+
* every path this function checks.
|
|
77
|
+
*/
|
|
78
|
+
export async function resolvePrebuiltPack(pack, exists) {
|
|
79
|
+
const scriptExists = await existsQuietly(exists, pack.scriptPath);
|
|
80
|
+
if (!scriptExists)
|
|
81
|
+
return undefined;
|
|
82
|
+
const stylesheetPath = joinPath(dirnamePath(pack.scriptPath), PREBUILT_STYLESHEET_FILENAME);
|
|
83
|
+
const stylesheetExists = await existsQuietly(exists, stylesheetPath);
|
|
84
|
+
const shadowedComponentSources = [];
|
|
85
|
+
for (const sourcePath of Object.values(pack.componentPaths)) {
|
|
86
|
+
if (sourcePath === undefined)
|
|
87
|
+
continue;
|
|
88
|
+
if (await existsQuietly(exists, sourcePath)) {
|
|
89
|
+
shadowedComponentSources.push(sourcePath);
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
return {
|
|
93
|
+
scriptPath: pack.scriptPath,
|
|
94
|
+
...(stylesheetExists ? { stylesheetPath } : {}),
|
|
95
|
+
shadowedComponentSources,
|
|
96
|
+
};
|
|
97
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Small, environment-free path helpers for the paths the host layer hands to
|
|
3
|
+
* a `HostAdapter` (`readFile`, `writeFile`, `listFolder`). They are adapter
|
|
4
|
+
* paths, not OS paths: this layer never touches a file system itself, so it
|
|
5
|
+
* must not import `node:path`.
|
|
6
|
+
*
|
|
7
|
+
* They stay correct for a Windows host, where the VS Code adapter does hand
|
|
8
|
+
* over `C:\Users\ana\packs` style strings (from `os.homedir()`, a
|
|
9
|
+
* `globalStorageUri.fsPath`, or a hand-written setting). The separator is
|
|
10
|
+
* taken from the base path: a base that uses backslashes and no forward
|
|
11
|
+
* slashes is a Windows path, joined and split with backslashes (forward
|
|
12
|
+
* slashes inside later segments are accepted as separators too, as
|
|
13
|
+
* `path.win32` does). Any other base is posix.
|
|
14
|
+
*/
|
|
15
|
+
/** True for a rooted path: `/x`, `\x`, `\\server\share`, or `C:\x` / `C:/x`. */
|
|
16
|
+
export declare function isAbsolutePath(path: string): boolean;
|
|
17
|
+
/**
|
|
18
|
+
* Joins `base` and `parts` with the separator `base` uses, collapsing `.`,
|
|
19
|
+
* `..` and repeated separators the way `path.join` does. An empty result is
|
|
20
|
+
* `.`.
|
|
21
|
+
*/
|
|
22
|
+
export declare function joinPath(base: string, ...parts: string[]): string;
|
|
23
|
+
/** The parent folder of `path`, `path.dirname`-style (a root's parent is itself). */
|
|
24
|
+
export declare function dirnamePath(path: string): string;
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Small, environment-free path helpers for the paths the host layer hands to
|
|
3
|
+
* a `HostAdapter` (`readFile`, `writeFile`, `listFolder`). They are adapter
|
|
4
|
+
* paths, not OS paths: this layer never touches a file system itself, so it
|
|
5
|
+
* must not import `node:path`.
|
|
6
|
+
*
|
|
7
|
+
* They stay correct for a Windows host, where the VS Code adapter does hand
|
|
8
|
+
* over `C:\Users\ana\packs` style strings (from `os.homedir()`, a
|
|
9
|
+
* `globalStorageUri.fsPath`, or a hand-written setting). The separator is
|
|
10
|
+
* taken from the base path: a base that uses backslashes and no forward
|
|
11
|
+
* slashes is a Windows path, joined and split with backslashes (forward
|
|
12
|
+
* slashes inside later segments are accepted as separators too, as
|
|
13
|
+
* `path.win32` does). Any other base is posix.
|
|
14
|
+
*/
|
|
15
|
+
const DRIVE_PREFIX = /^[A-Za-z]:/;
|
|
16
|
+
function separatorOf(path) {
|
|
17
|
+
return path.includes('\\') && !path.includes('/') ? '\\' : '/';
|
|
18
|
+
}
|
|
19
|
+
/** True for a rooted path: `/x`, `\x`, `\\server\share`, or `C:\x` / `C:/x`. */
|
|
20
|
+
export function isAbsolutePath(path) {
|
|
21
|
+
return (path.startsWith('/') ||
|
|
22
|
+
path.startsWith('\\') ||
|
|
23
|
+
/^[A-Za-z]:[\\/]/.test(path));
|
|
24
|
+
}
|
|
25
|
+
function parse(path, sep) {
|
|
26
|
+
const splitter = sep === '\\' ? /[\\/]+/ : /\/+/;
|
|
27
|
+
let root = '';
|
|
28
|
+
let rest = path;
|
|
29
|
+
if (sep === '\\') {
|
|
30
|
+
const drive = DRIVE_PREFIX.exec(rest);
|
|
31
|
+
if (drive) {
|
|
32
|
+
root = drive[0];
|
|
33
|
+
rest = rest.slice(2);
|
|
34
|
+
}
|
|
35
|
+
if (/^[\\/]{2}[^\\/]/.test(path) && root === '') {
|
|
36
|
+
root = '\\\\';
|
|
37
|
+
rest = rest.replace(/^[\\/]+/, '');
|
|
38
|
+
}
|
|
39
|
+
else if (/^[\\/]/.test(rest)) {
|
|
40
|
+
root += '\\';
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
else if (rest.startsWith('/')) {
|
|
44
|
+
root = '/';
|
|
45
|
+
}
|
|
46
|
+
const segments = [];
|
|
47
|
+
for (const part of rest.split(splitter)) {
|
|
48
|
+
if (part === '' || part === '.')
|
|
49
|
+
continue;
|
|
50
|
+
if (part === '..') {
|
|
51
|
+
const last = segments[segments.length - 1];
|
|
52
|
+
if (last !== undefined && last !== '..')
|
|
53
|
+
segments.pop();
|
|
54
|
+
else if (root === '')
|
|
55
|
+
segments.push('..');
|
|
56
|
+
continue;
|
|
57
|
+
}
|
|
58
|
+
segments.push(part);
|
|
59
|
+
}
|
|
60
|
+
return { root, segments };
|
|
61
|
+
}
|
|
62
|
+
function format({ root, segments }, sep) {
|
|
63
|
+
const body = segments.join(sep);
|
|
64
|
+
if (body === '')
|
|
65
|
+
return root === '' ? '.' : root;
|
|
66
|
+
return root + body;
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* Joins `base` and `parts` with the separator `base` uses, collapsing `.`,
|
|
70
|
+
* `..` and repeated separators the way `path.join` does. An empty result is
|
|
71
|
+
* `.`.
|
|
72
|
+
*/
|
|
73
|
+
export function joinPath(base, ...parts) {
|
|
74
|
+
const sep = separatorOf(base);
|
|
75
|
+
return format(parse([base, ...parts].filter((p) => p !== '').join(sep), sep), sep);
|
|
76
|
+
}
|
|
77
|
+
/** The parent folder of `path`, `path.dirname`-style (a root's parent is itself). */
|
|
78
|
+
export function dirnamePath(path) {
|
|
79
|
+
const sep = separatorOf(path);
|
|
80
|
+
const parsed = parse(path, sep);
|
|
81
|
+
return format({ root: parsed.root, segments: parsed.segments.slice(0, -1) }, sep);
|
|
82
|
+
}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A `RunIsolate` backed by a Web Worker, for hosts whose runtime cannot
|
|
3
|
+
* create a worker thread (see `./isolate.ts`).
|
|
4
|
+
*
|
|
5
|
+
* Two details are load-bearing.
|
|
6
|
+
*
|
|
7
|
+
* The worker is started from a BLOB URL built from the bundle's own bytes,
|
|
8
|
+
* read off disk by the host. Loading a worker from a `file://` URL is
|
|
9
|
+
* refused by Chromium, and an Electron app's custom protocols are not
|
|
10
|
+
* something this package can assume, so reading the file the host already
|
|
11
|
+
* has and handing it over as a blob sidesteps the question entirely.
|
|
12
|
+
* `glue.wasm` travels the same way, because wasmoon fetches it by URL and
|
|
13
|
+
* there is no `__dirname` inside a worker to find it beside the bundle.
|
|
14
|
+
*
|
|
15
|
+
* Network is served by the HOST, not the worker: `serveNetRequests` wires
|
|
16
|
+
* every `markii:net-request` to the caller's own `NetProvider`, which is
|
|
17
|
+
* the same pinned one a worker-thread isolate runs. The isolate has no
|
|
18
|
+
* network stack of its own, so the allowlist cannot be bypassed from
|
|
19
|
+
* inside it.
|
|
20
|
+
*
|
|
21
|
+
* What this CANNOT do, stated plainly because it is a real difference: a
|
|
22
|
+
* Web Worker takes no `maxOldGenerationSizeMb`, so `options`'s heap cap is
|
|
23
|
+
* accepted and ignored. Lua's own memory limit and the external watchdog
|
|
24
|
+
* still apply; a runaway JavaScript-side allocation inside the isolate is
|
|
25
|
+
* bounded only by the browser engine, and exhausting it can take the host
|
|
26
|
+
* renderer down. docs/security.md records this.
|
|
27
|
+
*/
|
|
28
|
+
import type { NetProvider } from '@markii/lua';
|
|
29
|
+
import type { RunIsolate, SpawnIsolateOptions } from './isolate.js';
|
|
30
|
+
/** The Web Worker surface this uses, declared structurally so the package needs no DOM lib. */
|
|
31
|
+
export interface WorkerLike {
|
|
32
|
+
postMessage: (message: unknown) => void;
|
|
33
|
+
terminate: () => void;
|
|
34
|
+
addEventListener: (type: 'message' | 'error' | 'messageerror', listener: (event: unknown) => void) => void;
|
|
35
|
+
}
|
|
36
|
+
export interface BrowserIsolateOptions {
|
|
37
|
+
/** Creates the worker. Injected so a test never needs a real one, and so a host can decide how it mints the URL. */
|
|
38
|
+
createWorker: (entryPath: string) => WorkerLike;
|
|
39
|
+
/** The host's own pinned provider. Every `net.*` call the script makes is answered by this, in the host, never in the isolate. */
|
|
40
|
+
netProvider: (netAllowlist: string[], maxFetchBytes: number, netPolicy: unknown) => NetProvider;
|
|
41
|
+
/** Where the worker should fetch wasmoon's `glue.wasm`; sent along with the job. */
|
|
42
|
+
wasmUri?: string;
|
|
43
|
+
}
|
|
44
|
+
/** Builds the spawner a host passes as `SpawnRunOptions.spawnIsolate`. */
|
|
45
|
+
export declare function createBrowserIsolate(options: BrowserIsolateOptions): (spawn: SpawnIsolateOptions) => RunIsolate;
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import { isNetBridgeRequest, serveNetRequest, } from './net-bridge.js';
|
|
2
|
+
/** Pulls the fields the host needs to build the same provider the worker would have built for itself. */
|
|
3
|
+
function netArgsFrom(job) {
|
|
4
|
+
const j = (job ?? {});
|
|
5
|
+
return {
|
|
6
|
+
allowlist: Array.isArray(j.netAllowlist)
|
|
7
|
+
? j.netAllowlist.filter((h) => typeof h === 'string')
|
|
8
|
+
: [],
|
|
9
|
+
maxFetchBytes: typeof j.limits?.maxFetchBytes === 'number'
|
|
10
|
+
? j.limits.maxFetchBytes
|
|
11
|
+
: 2 * 1024 * 1024,
|
|
12
|
+
// Fail closed, exactly as the worker does with an absent policy.
|
|
13
|
+
policy: j.netPolicy ?? { allowRestrictedAddresses: false },
|
|
14
|
+
};
|
|
15
|
+
}
|
|
16
|
+
/** Builds the spawner a host passes as `SpawnRunOptions.spawnIsolate`. */
|
|
17
|
+
export function createBrowserIsolate(options) {
|
|
18
|
+
return (spawn) => {
|
|
19
|
+
const worker = options.createWorker(spawn.entryPath);
|
|
20
|
+
let provider;
|
|
21
|
+
let alive = true;
|
|
22
|
+
const messageListeners = [];
|
|
23
|
+
const errorListeners = [];
|
|
24
|
+
worker.addEventListener('message', (event) => {
|
|
25
|
+
const data = event?.data;
|
|
26
|
+
if (isNetBridgeRequest(data)) {
|
|
27
|
+
if (!provider)
|
|
28
|
+
return;
|
|
29
|
+
void serveNetRequest(data, provider, (reply) => {
|
|
30
|
+
// A reply posted to a worker that is already gone would throw and
|
|
31
|
+
// take out this handler; the run is over either way.
|
|
32
|
+
if (alive)
|
|
33
|
+
worker.postMessage(reply);
|
|
34
|
+
});
|
|
35
|
+
return;
|
|
36
|
+
}
|
|
37
|
+
for (const listener of messageListeners)
|
|
38
|
+
listener(data);
|
|
39
|
+
});
|
|
40
|
+
worker.addEventListener('error', (event) => {
|
|
41
|
+
const message = event?.message ?? 'worker error';
|
|
42
|
+
for (const listener of errorListeners) {
|
|
43
|
+
listener(new Error(String(message)));
|
|
44
|
+
}
|
|
45
|
+
});
|
|
46
|
+
// A message that could not be deserialized is a failed run, not silence.
|
|
47
|
+
worker.addEventListener('messageerror', () => {
|
|
48
|
+
for (const listener of errorListeners) {
|
|
49
|
+
listener(new Error('worker sent an undeserializable message'));
|
|
50
|
+
}
|
|
51
|
+
});
|
|
52
|
+
return {
|
|
53
|
+
send: (job) => {
|
|
54
|
+
const { allowlist, maxFetchBytes, policy } = netArgsFrom(job);
|
|
55
|
+
provider = options.netProvider(allowlist, maxFetchBytes, policy);
|
|
56
|
+
worker.postMessage({
|
|
57
|
+
...job,
|
|
58
|
+
...(options.wasmUri !== undefined
|
|
59
|
+
? { wasmUri: options.wasmUri }
|
|
60
|
+
: {}),
|
|
61
|
+
});
|
|
62
|
+
},
|
|
63
|
+
kill: () => {
|
|
64
|
+
if (!alive)
|
|
65
|
+
return;
|
|
66
|
+
alive = false;
|
|
67
|
+
worker.terminate();
|
|
68
|
+
},
|
|
69
|
+
onMessage: (listener) => {
|
|
70
|
+
messageListeners.push(listener);
|
|
71
|
+
},
|
|
72
|
+
onError: (listener) => {
|
|
73
|
+
errorListeners.push(listener);
|
|
74
|
+
},
|
|
75
|
+
// A Web Worker has no exit event: `terminate()` is immediate and
|
|
76
|
+
// silent. `spawnRun`'s watchdog path settles on its own timer rather
|
|
77
|
+
// than waiting for one, so there is nothing to report here.
|
|
78
|
+
onExit: () => { },
|
|
79
|
+
};
|
|
80
|
+
};
|
|
81
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Where a bundle's note lives and what a bundle is called on disk. Every
|
|
3
|
+
* host that opens a `.mkz` (the VS Code preview, the CLI) asks these
|
|
4
|
+
* questions, so the answers live here once.
|
|
5
|
+
*/
|
|
6
|
+
import type { BundleManifest } from '@markii/bundle';
|
|
7
|
+
/** The conventional document path inside a bundle when the manifest does not name one (docs/bundles.md). */
|
|
8
|
+
export declare const DEFAULT_BUNDLE_DOCUMENT_PATH = "note.mk.md";
|
|
9
|
+
/** The current bundle extension; new bundles are always written with this one. */
|
|
10
|
+
export declare const BUNDLE_EXTENSION = ".mkz";
|
|
11
|
+
/** The legacy bundle extension, still recognized. */
|
|
12
|
+
export declare const LEGACY_BUNDLE_EXTENSION = ".mkbundle";
|
|
13
|
+
/**
|
|
14
|
+
* True when `name` ends with `.mkz` or the legacy `.mkbundle`
|
|
15
|
+
* (case-insensitively) AND has a non-empty base name before the extension.
|
|
16
|
+
*/
|
|
17
|
+
export declare function isBundleName(name: string): boolean;
|
|
18
|
+
/**
|
|
19
|
+
* The bundle-relative path of the document to open: the manifest's own
|
|
20
|
+
* `document` field when it names one, normalized through the same path jail
|
|
21
|
+
* every other bundle path goes through so a manifest cannot point outside
|
|
22
|
+
* the bundle; otherwise the conventional `note.mk.md`.
|
|
23
|
+
*
|
|
24
|
+
* Returns `undefined` when a `document` field is present but not a usable
|
|
25
|
+
* bundle-relative path (wrong type, or rejected by `normalizeBundlePath`,
|
|
26
|
+
* including any path with a `..` segment). That is a manifest authoring
|
|
27
|
+
* error, not something to ignore silently.
|
|
28
|
+
*/
|
|
29
|
+
export declare function resolveBundleDocumentPath(manifest: BundleManifest): string | undefined;
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { normalizeBundlePath } from '@markii/bundle';
|
|
2
|
+
/** The conventional document path inside a bundle when the manifest does not name one (docs/bundles.md). */
|
|
3
|
+
export const DEFAULT_BUNDLE_DOCUMENT_PATH = 'note.mk.md';
|
|
4
|
+
/** The current bundle extension; new bundles are always written with this one. */
|
|
5
|
+
export const BUNDLE_EXTENSION = '.mkz';
|
|
6
|
+
/** The legacy bundle extension, still recognized. */
|
|
7
|
+
export const LEGACY_BUNDLE_EXTENSION = '.mkbundle';
|
|
8
|
+
/**
|
|
9
|
+
* True when `name` ends with `.mkz` or the legacy `.mkbundle`
|
|
10
|
+
* (case-insensitively) AND has a non-empty base name before the extension.
|
|
11
|
+
*/
|
|
12
|
+
export function isBundleName(name) {
|
|
13
|
+
const lower = name.toLowerCase();
|
|
14
|
+
for (const extension of [BUNDLE_EXTENSION, LEGACY_BUNDLE_EXTENSION]) {
|
|
15
|
+
if (lower.endsWith(extension) && lower.length > extension.length) {
|
|
16
|
+
return true;
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
return false;
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* The bundle-relative path of the document to open: the manifest's own
|
|
23
|
+
* `document` field when it names one, normalized through the same path jail
|
|
24
|
+
* every other bundle path goes through so a manifest cannot point outside
|
|
25
|
+
* the bundle; otherwise the conventional `note.mk.md`.
|
|
26
|
+
*
|
|
27
|
+
* Returns `undefined` when a `document` field is present but not a usable
|
|
28
|
+
* bundle-relative path (wrong type, or rejected by `normalizeBundlePath`,
|
|
29
|
+
* including any path with a `..` segment). That is a manifest authoring
|
|
30
|
+
* error, not something to ignore silently.
|
|
31
|
+
*/
|
|
32
|
+
export function resolveBundleDocumentPath(manifest) {
|
|
33
|
+
const raw = manifest.document;
|
|
34
|
+
if (raw === undefined)
|
|
35
|
+
return DEFAULT_BUNDLE_DOCUMENT_PATH;
|
|
36
|
+
if (typeof raw !== 'string')
|
|
37
|
+
return undefined;
|
|
38
|
+
const normalized = normalizeBundlePath(raw);
|
|
39
|
+
return normalized.ok ? normalized.path : undefined;
|
|
40
|
+
}
|