@silverbulletmd/silverbullet 2.5.3 → 2.6.1
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 +4 -5
- package/client/asset_bundle/bundle.ts +3 -9
- package/client/data/datastore.ts +4 -5
- package/client/markdown_parser/constants.ts +3 -2
- package/client/plugos/hooks/code_widget.ts +3 -5
- package/client/plugos/hooks/command.ts +8 -8
- package/client/plugos/hooks/document_editor.ts +10 -12
- package/client/plugos/hooks/event.ts +33 -36
- package/client/plugos/hooks/mq.ts +17 -17
- package/client/plugos/hooks/plug_namespace.ts +3 -5
- package/client/plugos/hooks/slash_command.ts +12 -27
- package/client/plugos/hooks/syscall.ts +3 -3
- package/client/plugos/manifest_cache.ts +22 -15
- package/client/plugos/plug.ts +2 -5
- package/client/plugos/plug_compile.ts +67 -65
- package/client/plugos/protocol.ts +28 -28
- package/client/plugos/proxy_fetch.ts +7 -6
- package/client/plugos/sandboxes/worker_sandbox.ts +16 -15
- package/client/plugos/syscalls/asset.ts +1 -3
- package/client/plugos/syscalls/code_widget.ts +1 -3
- package/client/plugos/syscalls/config.ts +1 -5
- package/client/plugos/syscalls/datastore.ts +1 -1
- package/client/plugos/syscalls/editor.ts +63 -60
- package/client/plugos/syscalls/event.ts +9 -12
- package/client/plugos/syscalls/fetch.ts +30 -22
- package/client/plugos/syscalls/index.ts +10 -1
- package/client/plugos/syscalls/jsonschema.ts +72 -32
- package/client/plugos/syscalls/language.ts +9 -5
- package/client/plugos/syscalls/markdown.ts +29 -7
- package/client/plugos/syscalls/mq.ts +3 -11
- package/client/plugos/syscalls/service_registry.ts +1 -4
- package/client/plugos/syscalls/shell.ts +2 -5
- package/client/plugos/syscalls/sync.ts +69 -60
- package/client/plugos/syscalls/system.ts +2 -3
- package/client/plugos/system.ts +4 -10
- package/client/plugos/worker_runtime.ts +4 -3
- package/client/space_lua/aggregates.ts +632 -59
- package/client/space_lua/ast.ts +21 -9
- package/client/space_lua/ast_narrow.ts +4 -2
- package/client/space_lua/eval.ts +842 -536
- package/client/space_lua/labels.ts +6 -11
- package/client/space_lua/liq_null.ts +6 -0
- package/client/space_lua/numeric.ts +5 -8
- package/client/space_lua/parse.ts +290 -169
- package/client/space_lua/query_collection.ts +213 -149
- package/client/space_lua/render_lua_markdown.ts +369 -0
- package/client/space_lua/rp.ts +5 -4
- package/client/space_lua/runtime.ts +245 -142
- package/client/space_lua/stdlib/format.ts +34 -20
- package/client/space_lua/stdlib/js.ts +3 -7
- package/client/space_lua/stdlib/load.ts +1 -3
- package/client/space_lua/stdlib/math.ts +15 -14
- package/client/space_lua/stdlib/net.ts +25 -15
- package/client/space_lua/stdlib/os.ts +76 -85
- package/client/space_lua/stdlib/pattern.ts +28 -35
- package/client/space_lua/stdlib/prng.ts +15 -12
- package/client/space_lua/stdlib/space_lua.ts +16 -17
- package/client/space_lua/stdlib/string.ts +7 -17
- package/client/space_lua/stdlib/string_pack.ts +23 -19
- package/client/space_lua/stdlib/table.ts +5 -9
- package/client/space_lua/stdlib.ts +20 -30
- package/client/space_lua/tonumber.ts +79 -40
- package/client/space_lua/util.ts +14 -10
- package/dist/plug-compile.js +44 -41
- package/package.json +24 -22
- package/plug-api/lib/async.ts +19 -6
- package/plug-api/lib/crypto.ts +5 -6
- package/plug-api/lib/dates.ts +15 -7
- package/plug-api/lib/json.ts +10 -4
- package/plug-api/lib/ref.ts +18 -18
- package/plug-api/lib/resolve.ts +7 -11
- package/plug-api/lib/tags.ts +13 -4
- package/plug-api/lib/transclusion.ts +6 -17
- package/plug-api/lib/tree.ts +115 -43
- package/plug-api/lib/yaml.ts +25 -15
- package/plug-api/syscalls/asset.ts +1 -1
- package/plug-api/syscalls/config.ts +1 -4
- package/plug-api/syscalls/editor.ts +14 -14
- package/plug-api/syscalls/jsonschema.ts +1 -3
- package/plug-api/syscalls/lua.ts +3 -9
- package/plug-api/syscalls/mq.ts +1 -4
- package/plug-api/syscalls/shell.ts +4 -1
- package/plug-api/syscalls/space.ts +3 -10
- package/plug-api/syscalls/system.ts +1 -4
- package/plug-api/syscalls/yaml.ts +2 -6
- package/plug-api/types/client.ts +16 -1
- package/plug-api/types/event.ts +6 -4
- package/plug-api/types/manifest.ts +8 -9
- package/plugs/builtin_plugs.ts +2 -2
- package/dist/worker_runtime_bundle.js +0 -233
package/plug-api/lib/async.ts
CHANGED
|
@@ -1,6 +1,9 @@
|
|
|
1
|
-
export function throttle(
|
|
1
|
+
export function throttle(
|
|
2
|
+
func: () => void,
|
|
3
|
+
limit: number,
|
|
4
|
+
): (() => void) & { flush(): void } {
|
|
2
5
|
let timer: any = null;
|
|
3
|
-
|
|
6
|
+
const throttled = () => {
|
|
4
7
|
if (!timer) {
|
|
5
8
|
timer = setTimeout(() => {
|
|
6
9
|
func();
|
|
@@ -8,6 +11,15 @@ export function throttle(func: () => void, limit: number): () => void {
|
|
|
8
11
|
}, limit);
|
|
9
12
|
}
|
|
10
13
|
};
|
|
14
|
+
// Immediately execute any pending call and cancel the timer
|
|
15
|
+
throttled.flush = () => {
|
|
16
|
+
if (timer) {
|
|
17
|
+
clearTimeout(timer);
|
|
18
|
+
timer = null;
|
|
19
|
+
func();
|
|
20
|
+
}
|
|
21
|
+
};
|
|
22
|
+
return throttled;
|
|
11
23
|
}
|
|
12
24
|
|
|
13
25
|
export function throttleImmediately(
|
|
@@ -38,7 +50,7 @@ export function timeout(ms: number): Promise<never> {
|
|
|
38
50
|
return new Promise((_resolve, reject) =>
|
|
39
51
|
setTimeout(() => {
|
|
40
52
|
reject(new Error("timeout"));
|
|
41
|
-
}, ms)
|
|
53
|
+
}, ms),
|
|
42
54
|
);
|
|
43
55
|
}
|
|
44
56
|
|
|
@@ -58,7 +70,7 @@ export class PromiseQueue {
|
|
|
58
70
|
return new Promise((resolve, reject) => {
|
|
59
71
|
this.queue.push({ fn, resolve, reject });
|
|
60
72
|
if (!this.processing) {
|
|
61
|
-
this.process();
|
|
73
|
+
void this.process();
|
|
62
74
|
}
|
|
63
75
|
});
|
|
64
76
|
}
|
|
@@ -79,7 +91,7 @@ export class PromiseQueue {
|
|
|
79
91
|
reject(error);
|
|
80
92
|
}
|
|
81
93
|
|
|
82
|
-
this.process(); // Continue processing the next promise in the queue
|
|
94
|
+
void this.process(); // Continue processing the next promise in the queue
|
|
83
95
|
}
|
|
84
96
|
}
|
|
85
97
|
|
|
@@ -105,7 +117,8 @@ export async function batchRequests<I, O>(
|
|
|
105
117
|
const batchResults = await Promise.all(batches.map(fn));
|
|
106
118
|
// Flatten the results
|
|
107
119
|
for (const batchResult of batchResults) {
|
|
108
|
-
if (Array.isArray(batchResult)) {
|
|
120
|
+
if (Array.isArray(batchResult)) {
|
|
121
|
+
// If fn returns an array, collect them
|
|
109
122
|
results.push(...batchResult);
|
|
110
123
|
}
|
|
111
124
|
}
|
package/plug-api/lib/crypto.ts
CHANGED
|
@@ -42,9 +42,8 @@ export async function hashSHA256(
|
|
|
42
42
|
): Promise<string> {
|
|
43
43
|
// Transform the string into an ArrayBuffer
|
|
44
44
|
const encoder = new TextEncoder();
|
|
45
|
-
const data: Uint8Array =
|
|
46
|
-
? encoder.encode(message)
|
|
47
|
-
: message;
|
|
45
|
+
const data: Uint8Array =
|
|
46
|
+
typeof message === "string" ? encoder.encode(message) : message;
|
|
48
47
|
|
|
49
48
|
// Generate the hash
|
|
50
49
|
const hashBuffer = await globalThis.crypto.subtle.digest(
|
|
@@ -53,9 +52,9 @@ export async function hashSHA256(
|
|
|
53
52
|
);
|
|
54
53
|
|
|
55
54
|
// Transform the hash into a hex string
|
|
56
|
-
return Array.from(new Uint8Array(hashBuffer))
|
|
57
|
-
b.toString(16).padStart(2, "0")
|
|
58
|
-
|
|
55
|
+
return Array.from(new Uint8Array(hashBuffer))
|
|
56
|
+
.map((b) => b.toString(16).padStart(2, "0"))
|
|
57
|
+
.join("");
|
|
59
58
|
}
|
|
60
59
|
|
|
61
60
|
/**
|
package/plug-api/lib/dates.ts
CHANGED
|
@@ -3,11 +3,19 @@ export function niceDate(d: Date): string {
|
|
|
3
3
|
}
|
|
4
4
|
|
|
5
5
|
export function localDateString(d: Date): string {
|
|
6
|
-
return
|
|
7
|
-
|
|
8
|
-
"-" +
|
|
9
|
-
|
|
10
|
-
"
|
|
11
|
-
|
|
12
|
-
"
|
|
6
|
+
return (
|
|
7
|
+
d.getFullYear() +
|
|
8
|
+
"-" +
|
|
9
|
+
String(d.getMonth() + 1).padStart(2, "0") +
|
|
10
|
+
"-" +
|
|
11
|
+
String(d.getDate()).padStart(2, "0") +
|
|
12
|
+
"T" +
|
|
13
|
+
String(d.getHours()).padStart(2, "0") +
|
|
14
|
+
":" +
|
|
15
|
+
String(d.getMinutes()).padStart(2, "0") +
|
|
16
|
+
":" +
|
|
17
|
+
String(d.getSeconds()).padStart(2, "0") +
|
|
18
|
+
"." +
|
|
19
|
+
String(d.getMilliseconds()).padStart(3, "0")
|
|
20
|
+
);
|
|
13
21
|
}
|
package/plug-api/lib/json.ts
CHANGED
|
@@ -52,11 +52,17 @@ export function deepEqual(a: any, b: any): boolean {
|
|
|
52
52
|
export function cleanStringDate(d: Date): string {
|
|
53
53
|
// If no significant time, return a date string only
|
|
54
54
|
if (
|
|
55
|
-
d.getUTCHours() === 0 &&
|
|
55
|
+
d.getUTCHours() === 0 &&
|
|
56
|
+
d.getUTCMinutes() === 0 &&
|
|
57
|
+
d.getUTCSeconds() === 0
|
|
56
58
|
) {
|
|
57
|
-
return
|
|
58
|
-
|
|
59
|
-
|
|
59
|
+
return (
|
|
60
|
+
d.getUTCFullYear() +
|
|
61
|
+
"-" +
|
|
62
|
+
String(d.getUTCMonth() + 1).padStart(2, "0") +
|
|
63
|
+
"-" +
|
|
64
|
+
String(d.getUTCDate()).padStart(2, "0")
|
|
65
|
+
);
|
|
60
66
|
} else {
|
|
61
67
|
return d.toISOString();
|
|
62
68
|
}
|
package/plug-api/lib/ref.ts
CHANGED
|
@@ -77,8 +77,9 @@ export function isValidName(name: string): boolean {
|
|
|
77
77
|
// If the name, parses as a link and doesn't provide any other info we can be
|
|
78
78
|
// sure it was only parsed as a path and that the path then conforms to all
|
|
79
79
|
// the requirements
|
|
80
|
-
return
|
|
81
|
-
encodeRef(ref) === name
|
|
80
|
+
return (
|
|
81
|
+
!!ref && !ref.details && !ref.meta && name !== "" && encodeRef(ref) === name
|
|
82
|
+
);
|
|
82
83
|
}
|
|
83
84
|
|
|
84
85
|
/**
|
|
@@ -206,23 +207,22 @@ export function getOffsetFromHeader(
|
|
|
206
207
|
parseTree: ParseTree,
|
|
207
208
|
header: string,
|
|
208
209
|
): number {
|
|
209
|
-
const node = findNodeMatching(
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
return renderToText(subTree)
|
|
210
|
+
const node = findNodeMatching(parseTree, (subTree) => {
|
|
211
|
+
if (!subTree.type || !subTree.type.startsWith("ATXHeading")) {
|
|
212
|
+
return false;
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
const mark = findNodeOfType(subTree, "HeaderMark");
|
|
216
|
+
if (!mark || mark.from === undefined || mark.to === undefined) {
|
|
217
|
+
return false;
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
return (
|
|
221
|
+
renderToText(subTree)
|
|
222
222
|
.slice(mark.to - mark.from)
|
|
223
|
-
.trimStart() === header.trim()
|
|
224
|
-
|
|
225
|
-
);
|
|
223
|
+
.trimStart() === header.trim()
|
|
224
|
+
);
|
|
225
|
+
});
|
|
226
226
|
|
|
227
227
|
if (!node) {
|
|
228
228
|
return -1;
|
package/plug-api/lib/resolve.ts
CHANGED
|
@@ -4,9 +4,11 @@ import type { Path } from "@silverbulletmd/silverbullet/lib/ref";
|
|
|
4
4
|
* Determines wether a url points into the world wide web or to the local SB instance
|
|
5
5
|
*/
|
|
6
6
|
export function isLocalURL(url: string): boolean {
|
|
7
|
-
return
|
|
7
|
+
return (
|
|
8
|
+
!url.includes("://") &&
|
|
8
9
|
!url.startsWith("mailto:") &&
|
|
9
|
-
!url.startsWith("tel:")
|
|
10
|
+
!url.startsWith("tel:")
|
|
11
|
+
);
|
|
10
12
|
}
|
|
11
13
|
|
|
12
14
|
/**
|
|
@@ -22,10 +24,7 @@ export function fileName(name: string | Path): string | Path {
|
|
|
22
24
|
return name.split("/").pop()!;
|
|
23
25
|
}
|
|
24
26
|
|
|
25
|
-
const builtinPrefixes = [
|
|
26
|
-
"tag:",
|
|
27
|
-
"search:",
|
|
28
|
-
];
|
|
27
|
+
const builtinPrefixes = ["tag:", "search:"];
|
|
29
28
|
|
|
30
29
|
/**
|
|
31
30
|
* Builtin pages are pages which SB should automatically consider as existing
|
|
@@ -51,11 +50,8 @@ export function resolveMarkdownLink(
|
|
|
51
50
|
if (relative.startsWith("/")) {
|
|
52
51
|
return relative.slice(1);
|
|
53
52
|
} else {
|
|
54
|
-
const splitAbsolute = absolute
|
|
55
|
-
|
|
56
|
-
.slice(0, -1);
|
|
57
|
-
const splitRelative = relative
|
|
58
|
-
.split("/");
|
|
53
|
+
const splitAbsolute = absolute.split("/").slice(0, -1);
|
|
54
|
+
const splitRelative = relative.split("/");
|
|
59
55
|
|
|
60
56
|
while (splitRelative && splitRelative[0] === "..") {
|
|
61
57
|
splitAbsolute.pop();
|
package/plug-api/lib/tags.ts
CHANGED
|
@@ -1,15 +1,24 @@
|
|
|
1
|
+
/** Check if a tag marks a page as a meta page (exact "meta" or "meta/..." subtag) */
|
|
2
|
+
export function isMetaTag(tag: string): boolean {
|
|
3
|
+
return tag === "meta" || tag.startsWith("meta/");
|
|
4
|
+
}
|
|
5
|
+
|
|
1
6
|
/** Extract the name from hashtag text, removing # prefix and <angle brackets> if necessary */
|
|
2
7
|
export function extractHashtag(text: string): string {
|
|
3
|
-
if (text[0] !== "#") {
|
|
8
|
+
if (text[0] !== "#") {
|
|
9
|
+
// you shouldn't call this function at all
|
|
4
10
|
console.error("extractHashtag called on already clean string", text);
|
|
5
11
|
return text;
|
|
6
12
|
} else if (text[1] === "<") {
|
|
7
|
-
if (text.slice(-1) !== ">") {
|
|
13
|
+
if (text.slice(-1) !== ">") {
|
|
14
|
+
// this is malformed: #<name but maybe we're trying to autocomplete
|
|
8
15
|
return text.slice(2);
|
|
9
|
-
} else {
|
|
16
|
+
} else {
|
|
17
|
+
// this is correct #<name>
|
|
10
18
|
return text.slice(2, -1);
|
|
11
19
|
}
|
|
12
|
-
} else {
|
|
20
|
+
} else {
|
|
21
|
+
// this is just #name
|
|
13
22
|
return text.slice(1);
|
|
14
23
|
}
|
|
15
24
|
}
|
|
@@ -1,7 +1,3 @@
|
|
|
1
|
-
import {
|
|
2
|
-
isLocalURL,
|
|
3
|
-
resolveMarkdownLink,
|
|
4
|
-
} from "@silverbulletmd/silverbullet/lib/resolve";
|
|
5
1
|
import {
|
|
6
2
|
mdLinkRegex,
|
|
7
3
|
wikiLinkRegex,
|
|
@@ -34,9 +30,10 @@ export type ContentDimensions = {
|
|
|
34
30
|
* Parse an alias, possibly containing dimensions into an object
|
|
35
31
|
* @example "alias", "alias|100", "alias|100x200", "100", "100x200"
|
|
36
32
|
*/
|
|
37
|
-
export function parseDimensionFromAlias(
|
|
38
|
-
|
|
39
|
-
|
|
33
|
+
export function parseDimensionFromAlias(text: string): {
|
|
34
|
+
alias: string;
|
|
35
|
+
dimension?: ContentDimensions;
|
|
36
|
+
} {
|
|
40
37
|
let alias: string;
|
|
41
38
|
let dim: ContentDimensions | undefined;
|
|
42
39
|
if (text.includes("|")) {
|
|
@@ -71,10 +68,8 @@ export function parseDimensionFromAlias(
|
|
|
71
68
|
* Parses a transclusion of the type `![[]]` or `![]()`
|
|
72
69
|
* @param text
|
|
73
70
|
*/
|
|
74
|
-
export function parseTransclusion(
|
|
75
|
-
|
|
76
|
-
): Transclusion | null {
|
|
77
|
-
let url, alias ;
|
|
71
|
+
export function parseTransclusion(text: string): Transclusion | null {
|
|
72
|
+
let url, alias;
|
|
78
73
|
let linktype: LinkType = "markdownlink";
|
|
79
74
|
// TODO: Take in the tree and use tree nodes to get url and alias (Applies to all regex uses)
|
|
80
75
|
mdLinkRegex.lastIndex = 0;
|
|
@@ -83,12 +78,6 @@ export function parseTransclusion(
|
|
|
83
78
|
if ((match = mdLinkRegex.exec(text)) && match.groups) {
|
|
84
79
|
({ url, title: alias } = match.groups);
|
|
85
80
|
|
|
86
|
-
if (isLocalURL(url)) {
|
|
87
|
-
url = resolveMarkdownLink(
|
|
88
|
-
client.currentName(),
|
|
89
|
-
decodeURI(url),
|
|
90
|
-
);
|
|
91
|
-
}
|
|
92
81
|
linktype = "markdownlink";
|
|
93
82
|
} else if ((match = wikiLinkRegex.exec(text)) && match.groups) {
|
|
94
83
|
({ stringRef: url, alias } = match.groups);
|
package/plug-api/lib/tree.ts
CHANGED
|
@@ -1,5 +1,3 @@
|
|
|
1
|
-
import { deepClone } from "@silverbulletmd/silverbullet/lib/json";
|
|
2
|
-
|
|
3
1
|
export type ParseTree = {
|
|
4
2
|
type?: string; // undefined === text node
|
|
5
3
|
from?: number;
|
|
@@ -21,7 +19,7 @@ export function addParentPointers(tree: ParseTree) {
|
|
|
21
19
|
}
|
|
22
20
|
|
|
23
21
|
export function removeParentPointers(tree: ParseTree) {
|
|
24
|
-
|
|
22
|
+
tree.parent = undefined;
|
|
25
23
|
if (!tree.children) {
|
|
26
24
|
return;
|
|
27
25
|
}
|
|
@@ -51,38 +49,53 @@ export function collectNodesOfType(
|
|
|
51
49
|
return collectNodesMatching(tree, (n) => n.type === nodeType);
|
|
52
50
|
}
|
|
53
51
|
|
|
54
|
-
|
|
52
|
+
function collectNodesMatchingInternal(
|
|
55
53
|
tree: ParseTree,
|
|
56
54
|
matchFn: (tree: ParseTree) => boolean,
|
|
57
|
-
|
|
55
|
+
results: ParseTree[],
|
|
56
|
+
): void {
|
|
58
57
|
if (matchFn(tree)) {
|
|
59
|
-
|
|
58
|
+
results.push(tree);
|
|
59
|
+
return;
|
|
60
60
|
}
|
|
61
|
-
let results: ParseTree[] = [];
|
|
62
61
|
if (tree.children) {
|
|
63
62
|
for (const child of tree.children) {
|
|
64
|
-
|
|
63
|
+
collectNodesMatchingInternal(child, matchFn, results);
|
|
65
64
|
}
|
|
66
65
|
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
export function collectNodesMatching(
|
|
69
|
+
tree: ParseTree,
|
|
70
|
+
matchFn: (tree: ParseTree) => boolean,
|
|
71
|
+
): ParseTree[] {
|
|
72
|
+
const results: ParseTree[] = [];
|
|
73
|
+
collectNodesMatchingInternal(tree, matchFn, results);
|
|
67
74
|
return results;
|
|
68
75
|
}
|
|
69
76
|
|
|
70
|
-
|
|
77
|
+
async function collectNodesMatchingAsyncInternal(
|
|
71
78
|
tree: ParseTree,
|
|
72
79
|
matchFn: (tree: ParseTree) => Promise<boolean>,
|
|
73
|
-
|
|
80
|
+
results: ParseTree[],
|
|
81
|
+
): Promise<void> {
|
|
74
82
|
if (await matchFn(tree)) {
|
|
75
|
-
|
|
83
|
+
results.push(tree);
|
|
84
|
+
return;
|
|
76
85
|
}
|
|
77
|
-
let results: ParseTree[] = [];
|
|
78
86
|
if (tree.children) {
|
|
79
87
|
for (const child of tree.children) {
|
|
80
|
-
|
|
81
|
-
...results,
|
|
82
|
-
...await collectNodesMatchingAsync(child, matchFn),
|
|
83
|
-
];
|
|
88
|
+
await collectNodesMatchingAsyncInternal(child, matchFn, results);
|
|
84
89
|
}
|
|
85
90
|
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
export async function collectNodesMatchingAsync(
|
|
94
|
+
tree: ParseTree,
|
|
95
|
+
matchFn: (tree: ParseTree) => Promise<boolean>,
|
|
96
|
+
): Promise<ParseTree[]> {
|
|
97
|
+
const results: ParseTree[] = [];
|
|
98
|
+
await collectNodesMatchingAsyncInternal(tree, matchFn, results);
|
|
86
99
|
return results;
|
|
87
100
|
}
|
|
88
101
|
|
|
@@ -92,19 +105,22 @@ export function replaceNodesMatching(
|
|
|
92
105
|
substituteFn: (tree: ParseTree) => ParseTree | null | undefined,
|
|
93
106
|
) {
|
|
94
107
|
if (tree?.children) {
|
|
95
|
-
|
|
96
|
-
|
|
108
|
+
let i = 0;
|
|
109
|
+
while (i < tree.children.length) {
|
|
110
|
+
const child = tree.children[i];
|
|
97
111
|
const subst = substituteFn(child);
|
|
98
112
|
if (subst !== undefined) {
|
|
99
|
-
const pos = tree.children.indexOf(child);
|
|
100
113
|
if (subst) {
|
|
101
|
-
tree.children
|
|
114
|
+
tree.children[i] = subst;
|
|
115
|
+
i++;
|
|
102
116
|
} else {
|
|
103
117
|
// null = delete
|
|
104
|
-
tree.children.splice(
|
|
118
|
+
tree.children.splice(i, 1);
|
|
119
|
+
// don't increment i — next child shifted into this position
|
|
105
120
|
}
|
|
106
121
|
} else {
|
|
107
122
|
replaceNodesMatching(child, substituteFn);
|
|
123
|
+
i++;
|
|
108
124
|
}
|
|
109
125
|
}
|
|
110
126
|
}
|
|
@@ -115,19 +131,21 @@ export async function replaceNodesMatchingAsync(
|
|
|
115
131
|
substituteFn: (tree: ParseTree) => Promise<ParseTree | null | undefined>,
|
|
116
132
|
) {
|
|
117
133
|
if (tree.children) {
|
|
118
|
-
|
|
119
|
-
|
|
134
|
+
let i = 0;
|
|
135
|
+
while (i < tree.children.length) {
|
|
136
|
+
const child = tree.children[i];
|
|
120
137
|
const subst = await substituteFn(child);
|
|
121
138
|
if (subst !== undefined) {
|
|
122
|
-
const pos = tree.children.indexOf(child);
|
|
123
139
|
if (subst) {
|
|
124
|
-
tree.children
|
|
140
|
+
tree.children[i] = subst;
|
|
141
|
+
i++;
|
|
125
142
|
} else {
|
|
126
143
|
// null = delete
|
|
127
|
-
tree.children.splice(
|
|
144
|
+
tree.children.splice(i, 1);
|
|
128
145
|
}
|
|
129
146
|
} else {
|
|
130
147
|
await replaceNodesMatchingAsync(child, substituteFn);
|
|
148
|
+
i++;
|
|
131
149
|
}
|
|
132
150
|
}
|
|
133
151
|
}
|
|
@@ -137,36 +155,88 @@ export function findNodeMatching(
|
|
|
137
155
|
tree: ParseTree,
|
|
138
156
|
matchFn: (tree: ParseTree) => boolean,
|
|
139
157
|
): ParseTree | null {
|
|
140
|
-
|
|
158
|
+
if (matchFn(tree)) {
|
|
159
|
+
return tree;
|
|
160
|
+
}
|
|
161
|
+
if (tree.children) {
|
|
162
|
+
for (const child of tree.children) {
|
|
163
|
+
const result = findNodeMatching(child, matchFn);
|
|
164
|
+
if (result) {
|
|
165
|
+
return result;
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
return null;
|
|
141
170
|
}
|
|
142
171
|
|
|
143
172
|
export function findNodeOfType(
|
|
144
173
|
tree: ParseTree,
|
|
145
174
|
nodeType: string,
|
|
146
175
|
): ParseTree | null {
|
|
147
|
-
|
|
176
|
+
if (tree.type === nodeType) {
|
|
177
|
+
return tree;
|
|
178
|
+
}
|
|
179
|
+
if (tree.children) {
|
|
180
|
+
for (const child of tree.children) {
|
|
181
|
+
const result = findNodeOfType(child, nodeType);
|
|
182
|
+
if (result) {
|
|
183
|
+
return result;
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
return null;
|
|
148
188
|
}
|
|
149
189
|
|
|
150
190
|
export function traverseTree(
|
|
151
191
|
tree: ParseTree,
|
|
152
|
-
// Return value = should stop traversal?
|
|
192
|
+
// Return value = should stop traversal into children?
|
|
153
193
|
matchFn: (tree: ParseTree) => boolean,
|
|
154
194
|
): void {
|
|
155
|
-
|
|
156
|
-
|
|
195
|
+
if (matchFn(tree)) {
|
|
196
|
+
return;
|
|
197
|
+
}
|
|
198
|
+
if (tree.children) {
|
|
199
|
+
for (const child of tree.children) {
|
|
200
|
+
traverseTree(child, matchFn);
|
|
201
|
+
}
|
|
202
|
+
}
|
|
157
203
|
}
|
|
158
204
|
|
|
159
205
|
export async function traverseTreeAsync(
|
|
160
206
|
tree: ParseTree,
|
|
161
|
-
// Return value = should stop traversal?
|
|
207
|
+
// Return value = should stop traversal into children?
|
|
162
208
|
matchFn: (tree: ParseTree) => Promise<boolean>,
|
|
163
209
|
): Promise<void> {
|
|
164
|
-
|
|
165
|
-
|
|
210
|
+
if (await matchFn(tree)) {
|
|
211
|
+
return;
|
|
212
|
+
}
|
|
213
|
+
if (tree.children) {
|
|
214
|
+
for (const child of tree.children) {
|
|
215
|
+
await traverseTreeAsync(child, matchFn);
|
|
216
|
+
}
|
|
217
|
+
}
|
|
166
218
|
}
|
|
167
219
|
|
|
168
220
|
export function cloneTree(tree: ParseTree): ParseTree {
|
|
169
|
-
|
|
221
|
+
if (tree.text !== undefined) {
|
|
222
|
+
return {
|
|
223
|
+
from: tree.from,
|
|
224
|
+
to: tree.to,
|
|
225
|
+
text: tree.text,
|
|
226
|
+
};
|
|
227
|
+
}
|
|
228
|
+
const clone: ParseTree = {
|
|
229
|
+
type: tree.type,
|
|
230
|
+
from: tree.from,
|
|
231
|
+
to: tree.to,
|
|
232
|
+
};
|
|
233
|
+
if (tree.children) {
|
|
234
|
+
clone.children = new Array(tree.children.length);
|
|
235
|
+
for (let i = 0; i < tree.children.length; i++) {
|
|
236
|
+
clone.children[i] = cloneTree(tree.children[i]);
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
return clone;
|
|
170
240
|
}
|
|
171
241
|
|
|
172
242
|
// Finds non-text node at position
|
|
@@ -243,21 +313,23 @@ export function renderToText(tree?: ParseTree): string {
|
|
|
243
313
|
if (!tree) {
|
|
244
314
|
return "";
|
|
245
315
|
}
|
|
246
|
-
const pieces: string[] = [];
|
|
247
316
|
if (tree.text !== undefined) {
|
|
248
317
|
return tree.text;
|
|
249
318
|
}
|
|
250
|
-
|
|
251
|
-
|
|
319
|
+
const children = tree.children!;
|
|
320
|
+
if (children.length === 1) {
|
|
321
|
+
return renderToText(children[0]);
|
|
322
|
+
}
|
|
323
|
+
let result = "";
|
|
324
|
+
for (const child of children) {
|
|
325
|
+
result += renderToText(child);
|
|
252
326
|
}
|
|
253
|
-
return
|
|
327
|
+
return result;
|
|
254
328
|
}
|
|
255
329
|
|
|
256
330
|
export function cleanTree(tree: ParseTree, omitTrimmable = true): ParseTree {
|
|
257
331
|
if (tree.type === "⚠") {
|
|
258
|
-
throw new Error(
|
|
259
|
-
`Parse error at pos ${tree.from}`,
|
|
260
|
-
);
|
|
332
|
+
throw new Error(`Parse error at pos ${tree.from}`);
|
|
261
333
|
}
|
|
262
334
|
if (tree.text !== undefined) {
|
|
263
335
|
return tree;
|
|
@@ -272,7 +344,7 @@ export function cleanTree(tree: ParseTree, omitTrimmable = true): ParseTree {
|
|
|
272
344
|
if (node.type && node.type !== "Comment") {
|
|
273
345
|
ast.children!.push(cleanTree(node, omitTrimmable));
|
|
274
346
|
}
|
|
275
|
-
if (node.text && (omitTrimmable && node.text.trim() || !omitTrimmable)) {
|
|
347
|
+
if (node.text && ((omitTrimmable && node.text.trim()) || !omitTrimmable)) {
|
|
276
348
|
ast.children!.push(node);
|
|
277
349
|
}
|
|
278
350
|
}
|
package/plug-api/lib/yaml.ts
CHANGED
|
@@ -25,7 +25,9 @@ function serializeToYamlScalar(
|
|
|
25
25
|
// Simple strings without special chars/meaning don't need quotes
|
|
26
26
|
return value;
|
|
27
27
|
} else if (
|
|
28
|
-
typeof value === "number" ||
|
|
28
|
+
typeof value === "number" ||
|
|
29
|
+
typeof value === "boolean" ||
|
|
30
|
+
value === null
|
|
29
31
|
) {
|
|
30
32
|
return String(value);
|
|
31
33
|
}
|
|
@@ -46,10 +48,15 @@ function serializeToYamlValue(
|
|
|
46
48
|
// Determine indentation for list items (base + 2 spaces)
|
|
47
49
|
const itemIndentation = `${baseIndentation} `;
|
|
48
50
|
// Format each item recursively, preceded by '- ' marker
|
|
49
|
-
return
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
51
|
+
return (
|
|
52
|
+
"\n" +
|
|
53
|
+
value
|
|
54
|
+
.map(
|
|
55
|
+
(item) =>
|
|
56
|
+
`${itemIndentation}- ${serializeToYamlValue(item, itemIndentation)}`,
|
|
57
|
+
)
|
|
58
|
+
.join("\n")
|
|
59
|
+
);
|
|
53
60
|
// Note: serializeToYamlValue is used recursively here to handle nested arrays/objects if needed in future
|
|
54
61
|
// However, the current `applyMinimalSetKeyPatches` only handles top-level keys.
|
|
55
62
|
} else if (typeof value === "object" && value !== null) {
|
|
@@ -58,22 +65,25 @@ function serializeToYamlValue(
|
|
|
58
65
|
const itemIndentation = `${baseIndentation} `;
|
|
59
66
|
const entries = Object.entries(value);
|
|
60
67
|
if (entries.length === 0) return "{}"; // Flow style empty objects
|
|
61
|
-
return
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
68
|
+
return (
|
|
69
|
+
"\n" +
|
|
70
|
+
entries
|
|
71
|
+
.map(
|
|
72
|
+
([key, val]) =>
|
|
73
|
+
`${itemIndentation}${key}: ${serializeToYamlValue(
|
|
74
|
+
val,
|
|
75
|
+
itemIndentation,
|
|
76
|
+
)}`,
|
|
77
|
+
)
|
|
78
|
+
.join("\n")
|
|
79
|
+
);
|
|
67
80
|
} else {
|
|
68
81
|
// Handle scalars using the dedicated function
|
|
69
82
|
return serializeToYamlScalar(value);
|
|
70
83
|
}
|
|
71
84
|
}
|
|
72
85
|
|
|
73
|
-
export function applyPatches(
|
|
74
|
-
yamlString: string,
|
|
75
|
-
patches: YamlPatch[],
|
|
76
|
-
): string {
|
|
86
|
+
export function applyPatches(yamlString: string, patches: YamlPatch[]): string {
|
|
77
87
|
let currentYaml = yamlString;
|
|
78
88
|
|
|
79
89
|
for (const patch of patches) {
|
|
@@ -15,7 +15,7 @@ export async function readAsset(
|
|
|
15
15
|
name: string,
|
|
16
16
|
encoding: "utf8" | "dataurl" = "utf8",
|
|
17
17
|
): Promise<string> {
|
|
18
|
-
const dataUrl = await syscall("asset.readAsset", plugName, name) as string;
|
|
18
|
+
const dataUrl = (await syscall("asset.readAsset", plugName, name)) as string;
|
|
19
19
|
switch (encoding) {
|
|
20
20
|
case "utf8":
|
|
21
21
|
return new TextDecoder().decode(base64DecodeDataUrl(dataUrl));
|
|
@@ -31,10 +31,7 @@ export function set<T>(
|
|
|
31
31
|
/**
|
|
32
32
|
* Inserts a config value into an array
|
|
33
33
|
*/
|
|
34
|
-
export function insert<T>(
|
|
35
|
-
path: string | string[],
|
|
36
|
-
value: T,
|
|
37
|
-
): Promise<void> {
|
|
34
|
+
export function insert<T>(path: string | string[], value: T): Promise<void> {
|
|
38
35
|
return syscall("config.insert", path, value);
|
|
39
36
|
}
|
|
40
37
|
|