@weborigami/origami 0.0.65 → 0.0.66-beta.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/exports/PathTransform.js +3 -1
- package/exports/exports.js +3 -0
- package/package.json +7 -7
- package/src/builtins/@cache.js +3 -1
- package/src/builtins/@changes.js +13 -5
- package/src/builtins/@concat.js +1 -1
- package/src/builtins/@copy.js +3 -3
- package/src/builtins/@crawl.js +13 -25
- package/src/builtins/@debug.js +6 -10
- package/src/builtins/@filter.js +8 -4
- package/src/builtins/@inners.js +6 -9
- package/src/builtins/@jsonKeys.js +1 -1
- package/src/builtins/@mapFn.js +1 -1
- package/src/builtins/@mdHtml.js +1 -1
- package/src/builtins/@paginateFn.js +3 -1
- package/src/builtins/@post.js +45 -0
- package/src/builtins/@serve.js +1 -4
- package/src/builtins/@setDeep.js +9 -2
- package/src/builtins/@slash.js +1 -0
- package/src/builtins/@static.js +6 -4
- package/src/builtins/@watch.js +16 -25
- package/src/common/CommandModulesTransform.js +2 -0
- package/src/common/ExplorableSiteTransform.js +8 -11
- package/src/common/FilterTree.js +10 -11
- package/src/common/GlobTree.js +14 -13
- package/src/misc/treeDot.js +3 -2
- package/src/server/constructResponse.js +10 -10
- package/src/server/parsePostData.js +35 -0
- package/src/server/server.js +14 -2
package/exports/PathTransform.js
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { trailingSlash } from "@weborigami/async-tree";
|
|
2
|
+
|
|
1
3
|
export default function PathTransform(Base) {
|
|
2
4
|
return class Path extends Base {
|
|
3
5
|
async get(key) {
|
|
@@ -6,7 +8,7 @@ export default function PathTransform(Base) {
|
|
|
6
8
|
// @ts-ignore
|
|
7
9
|
const path = this[PathTransform.pathKey]
|
|
8
10
|
? // @ts-ignore
|
|
9
|
-
`${this[PathTransform.pathKey]}
|
|
11
|
+
`${trailingSlash.add(this[PathTransform.pathKey])}${key}`
|
|
10
12
|
: key;
|
|
11
13
|
value[PathTransform.pathKey] = path;
|
|
12
14
|
}
|
package/exports/exports.js
CHANGED
|
@@ -72,6 +72,7 @@ export { default as paginateFn } from "../src/builtins/@paginateFn.js";
|
|
|
72
72
|
export { default as parent } from "../src/builtins/@parent.js";
|
|
73
73
|
export { default as perf } from "../src/builtins/@perf.js";
|
|
74
74
|
export { default as plain } from "../src/builtins/@plain.js";
|
|
75
|
+
export { default as post } from "../src/builtins/@post.js";
|
|
75
76
|
export { default as project } from "../src/builtins/@project.js";
|
|
76
77
|
export { default as redirect } from "../src/builtins/@redirect.js";
|
|
77
78
|
export { default as regexMatch } from "../src/builtins/@regexMatch.js";
|
|
@@ -84,6 +85,7 @@ export { default as setDeep } from "../src/builtins/@setDeep.js";
|
|
|
84
85
|
export { default as shell } from "../src/builtins/@shell.js";
|
|
85
86
|
export { default as shuffle } from "../src/builtins/@shuffle.js";
|
|
86
87
|
export { default as sitemap } from "../src/builtins/@sitemap.js";
|
|
88
|
+
export * from "../src/builtins/@slash.js";
|
|
87
89
|
export { default as slug } from "../src/builtins/@slug.js";
|
|
88
90
|
export { default as sort } from "../src/builtins/@sort.js";
|
|
89
91
|
export { default as sortFn } from "../src/builtins/@sortFn.js";
|
|
@@ -139,4 +141,5 @@ export { default as origamiHighlightDefinition } from "../src/misc/origamiHighli
|
|
|
139
141
|
export { default as treeDot } from "../src/misc/treeDot.js";
|
|
140
142
|
export { default as constructResponse } from "../src/server/constructResponse.js";
|
|
141
143
|
export * from "../src/server/mediaTypes.js";
|
|
144
|
+
export { default as parsePostData } from "../src/server/parsePostData.js";
|
|
142
145
|
export * from "../src/server/server.js";
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@weborigami/origami",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.66-beta.2",
|
|
4
4
|
"description": "Web Origami language, CLI, framework, and server",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"repository": {
|
|
@@ -13,17 +13,17 @@
|
|
|
13
13
|
"main": "./exports/exports.js",
|
|
14
14
|
"types": "./index.ts",
|
|
15
15
|
"devDependencies": {
|
|
16
|
-
"@types/node": "22.
|
|
17
|
-
"typescript": "5.
|
|
16
|
+
"@types/node": "22.7.4",
|
|
17
|
+
"typescript": "5.6.2"
|
|
18
18
|
},
|
|
19
19
|
"dependencies": {
|
|
20
|
-
"@weborigami/async-tree": "0.0.
|
|
21
|
-
"@weborigami/language": "0.0.
|
|
22
|
-
"@weborigami/types": "0.0.
|
|
20
|
+
"@weborigami/async-tree": "0.0.66-beta.2",
|
|
21
|
+
"@weborigami/language": "0.0.66-beta.2",
|
|
22
|
+
"@weborigami/types": "0.0.66-beta.2",
|
|
23
23
|
"exif-parser": "0.1.12",
|
|
24
24
|
"graphviz-wasm": "3.0.2",
|
|
25
25
|
"highlight.js": "11.10.0",
|
|
26
|
-
"marked": "14.1.
|
|
26
|
+
"marked": "14.1.2",
|
|
27
27
|
"marked-gfm-heading-id": "4.1.0",
|
|
28
28
|
"marked-highlight": "2.1.4",
|
|
29
29
|
"marked-smartypants": "1.1.8",
|
package/src/builtins/@cache.js
CHANGED
|
@@ -19,7 +19,9 @@ export default async function cacheBuiltin(
|
|
|
19
19
|
) {
|
|
20
20
|
assertTreeIsDefined(this, "cache");
|
|
21
21
|
/** @type {any} */
|
|
22
|
-
const cacheTree = cacheTreelike
|
|
22
|
+
const cacheTree = cacheTreelike
|
|
23
|
+
? Tree.from(cacheTreelike, { parent: this })
|
|
24
|
+
: undefined;
|
|
23
25
|
const result = cache(sourceTreelike, cacheTree, filterTreelike);
|
|
24
26
|
return result;
|
|
25
27
|
}
|
package/src/builtins/@changes.js
CHANGED
|
@@ -1,10 +1,18 @@
|
|
|
1
1
|
import { Tree } from "@weborigami/async-tree";
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
3
|
+
/**
|
|
4
|
+
* Given an old tree and a new tree, return a tree of changes indicated
|
|
5
|
+
* by the values: "added", "changed", or "deleted".
|
|
6
|
+
*
|
|
7
|
+
* @typedef {import("@weborigami/async-tree").Treelike} Treelike
|
|
8
|
+
*
|
|
9
|
+
* @this {import("@weborigami/types").AsyncTree|null}
|
|
10
|
+
* @param {Treelike} oldTreelike
|
|
11
|
+
* @param {Treelike} newTreelike
|
|
12
|
+
*/
|
|
5
13
|
export default async function changes(oldTreelike, newTreelike) {
|
|
6
|
-
const oldTree = Tree.from(oldTreelike, { deep: true });
|
|
7
|
-
const newTree = Tree.from(newTreelike, { deep: true });
|
|
14
|
+
const oldTree = Tree.from(oldTreelike, { deep: true, parent: this });
|
|
15
|
+
const newTree = Tree.from(newTreelike, { deep: true, parent: this });
|
|
8
16
|
|
|
9
17
|
const oldKeys = Array.from(await oldTree.keys());
|
|
10
18
|
const newKeys = Array.from(await newTree.keys());
|
|
@@ -21,7 +29,7 @@ export default async function changes(oldTreelike, newTreelike) {
|
|
|
21
29
|
const newValue = await newTree.get(key);
|
|
22
30
|
|
|
23
31
|
if (Tree.isAsyncTree(oldValue) && Tree.isAsyncTree(newValue)) {
|
|
24
|
-
const treeChanges = await changes(oldValue, newValue);
|
|
32
|
+
const treeChanges = await changes.call(this, oldValue, newValue);
|
|
25
33
|
if (Object.keys(treeChanges).length > 0) {
|
|
26
34
|
result[key] = treeChanges;
|
|
27
35
|
}
|
package/src/builtins/@concat.js
CHANGED
|
@@ -12,7 +12,7 @@ import assertTreeIsDefined from "../misc/assertTreeIsDefined.js";
|
|
|
12
12
|
*/
|
|
13
13
|
export default async function concat(...args) {
|
|
14
14
|
assertTreeIsDefined(this, "concat");
|
|
15
|
-
const tree = args.length === 0 ? this : Tree.from(args);
|
|
15
|
+
const tree = args.length === 0 ? this : Tree.from(args, { parent: this });
|
|
16
16
|
return ops.concat.call(this, tree);
|
|
17
17
|
}
|
|
18
18
|
|
package/src/builtins/@copy.js
CHANGED
|
@@ -16,8 +16,8 @@ import setDeep from "./@setDeep.js";
|
|
|
16
16
|
export default async function copy(source, target) {
|
|
17
17
|
assertTreeIsDefined(this, "copy");
|
|
18
18
|
// const start = performance.now();
|
|
19
|
-
const sourceTree = Tree.from(source);
|
|
20
|
-
/** @type {any} */ let targetTree = Tree.from(target);
|
|
19
|
+
const sourceTree = Tree.from(source, { parent: this });
|
|
20
|
+
/** @type {any} */ let targetTree = Tree.from(target, { parent: this });
|
|
21
21
|
|
|
22
22
|
if (stdout.isTTY) {
|
|
23
23
|
targetTree = transformObject(ProgressTransform, targetTree);
|
|
@@ -26,7 +26,7 @@ export default async function copy(source, target) {
|
|
|
26
26
|
countCopied = 0;
|
|
27
27
|
}
|
|
28
28
|
|
|
29
|
-
await setDeep(targetTree, sourceTree);
|
|
29
|
+
await setDeep.call(this, targetTree, sourceTree);
|
|
30
30
|
|
|
31
31
|
if (stdout.isTTY) {
|
|
32
32
|
process.stdout.clearLine(0);
|
package/src/builtins/@crawl.js
CHANGED
|
@@ -4,6 +4,7 @@ import {
|
|
|
4
4
|
deepMerge,
|
|
5
5
|
isPlainObject,
|
|
6
6
|
keysFromPath,
|
|
7
|
+
trailingSlash,
|
|
7
8
|
} from "@weborigami/async-tree";
|
|
8
9
|
import { InvokeFunctionsTransform, extname } from "@weborigami/language";
|
|
9
10
|
import * as utilities from "../common/utilities.js";
|
|
@@ -34,7 +35,7 @@ export default async function crawl(treelike, baseHref) {
|
|
|
34
35
|
const tree =
|
|
35
36
|
typeof treelike === "string"
|
|
36
37
|
? treeHttps.call(this, treelike)
|
|
37
|
-
: Tree.from(treelike);
|
|
38
|
+
: Tree.from(treelike, { parent: this });
|
|
38
39
|
|
|
39
40
|
if (baseHref === undefined) {
|
|
40
41
|
// Ask tree or original treelike if it has an `href` property we can use as
|
|
@@ -75,7 +76,7 @@ export default async function crawl(treelike, baseHref) {
|
|
|
75
76
|
for (const resourcePath of resourcePaths) {
|
|
76
77
|
const resourceKeys = adjustKeys(keysFromPath(resourcePath));
|
|
77
78
|
const fn = () => {
|
|
78
|
-
return traverse(tree, ...resourceKeys);
|
|
79
|
+
return Tree.traverse(tree, ...resourceKeys);
|
|
79
80
|
};
|
|
80
81
|
addValueToObject(resources, resourceKeys, fn);
|
|
81
82
|
}
|
|
@@ -100,18 +101,19 @@ export default async function crawl(treelike, baseHref) {
|
|
|
100
101
|
}
|
|
101
102
|
|
|
102
103
|
// For indexing and storage purposes, treat a path that ends in a trailing slash
|
|
103
|
-
//
|
|
104
|
+
// as if it ends in index.html.
|
|
104
105
|
function adjustKeys(keys) {
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
adjustedKeys[adjustedKeys.length - 1] = "index.html";
|
|
106
|
+
if (keys.length > 0 && !trailingSlash.has(keys.at(-1))) {
|
|
107
|
+
return keys;
|
|
108
108
|
}
|
|
109
|
+
const adjustedKeys = keys.slice();
|
|
110
|
+
adjustedKeys.push("index.html");
|
|
109
111
|
return adjustedKeys;
|
|
110
112
|
}
|
|
111
113
|
|
|
112
114
|
function addValueToObject(object, keys, value) {
|
|
113
115
|
for (let i = 0, current = object; i < keys.length; i++) {
|
|
114
|
-
const key = keys[i];
|
|
116
|
+
const key = trailingSlash.remove(keys[i]);
|
|
115
117
|
if (i === keys.length - 1) {
|
|
116
118
|
// Write out value
|
|
117
119
|
if (isPlainObject(current[key])) {
|
|
@@ -150,7 +152,7 @@ async function* crawlPaths(tree, baseUrl) {
|
|
|
150
152
|
const promisesForPaths = {};
|
|
151
153
|
|
|
152
154
|
// Seed the promise dictionary with robots.txt and the root path.
|
|
153
|
-
const initialPaths = ["/robots.txt", ""];
|
|
155
|
+
const initialPaths = ["/robots.txt", "/"];
|
|
154
156
|
initialPaths.forEach((path) => {
|
|
155
157
|
promisesForPaths[path] = processPath(tree, path, baseUrl);
|
|
156
158
|
});
|
|
@@ -468,14 +470,13 @@ async function processPath(tree, path, baseUrl) {
|
|
|
468
470
|
}
|
|
469
471
|
|
|
470
472
|
// Convert path to keys
|
|
471
|
-
|
|
472
|
-
let keys = path === "" ? [""] : keysFromPath(path);
|
|
473
|
+
const keys = keysFromPath(path);
|
|
473
474
|
|
|
474
475
|
// Traverse tree to get value.
|
|
475
|
-
let value = await traverse(tree, ...keys);
|
|
476
|
+
let value = await Tree.traverse(tree, ...keys);
|
|
476
477
|
if (Tree.isAsyncTree(value)) {
|
|
477
478
|
// Path is actually a directory; see if it has an index.html
|
|
478
|
-
value = await traverse(value, "index.html");
|
|
479
|
+
value = await Tree.traverse(value, "index.html");
|
|
479
480
|
}
|
|
480
481
|
|
|
481
482
|
const adjustedKeys = adjustKeys(keys);
|
|
@@ -508,18 +509,5 @@ async function processPath(tree, path, baseUrl) {
|
|
|
508
509
|
};
|
|
509
510
|
}
|
|
510
511
|
|
|
511
|
-
async function traverse(tree, ...keys) {
|
|
512
|
-
if (tree.resolve && keys.length > 1) {
|
|
513
|
-
// Tree like SiteTree that supports resolve() method
|
|
514
|
-
const lastKey = keys.pop();
|
|
515
|
-
const path = keys.join("/");
|
|
516
|
-
const resolved = tree.resolve(path);
|
|
517
|
-
return resolved.get(lastKey);
|
|
518
|
-
} else {
|
|
519
|
-
// Regular async tree
|
|
520
|
-
return Tree.traverse(tree, ...keys);
|
|
521
|
-
}
|
|
522
|
-
}
|
|
523
|
-
|
|
524
512
|
crawl.usage = `@crawl <tree>\tCrawl a tree`;
|
|
525
513
|
crawl.documentation = "https://weborigami.org/language/@crawl.html";
|
package/src/builtins/@debug.js
CHANGED
|
@@ -39,12 +39,11 @@ function DebugTransform(Base) {
|
|
|
39
39
|
const parent = this;
|
|
40
40
|
|
|
41
41
|
// Since this transform is for diagnostic purposes, cast any treelike
|
|
42
|
-
// result to a tree so we can debug the result too.
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
}
|
|
42
|
+
// result to a tree so we can debug the result too. (Don't do this for
|
|
43
|
+
// functions, as that can be undesirable, e.g., when writing functions
|
|
44
|
+
// that handle POST requests.)
|
|
45
|
+
if (Tree.isTreelike(value) && typeof value !== "function") {
|
|
46
|
+
value = Tree.from(value, { parent });
|
|
48
47
|
if (!isTransformApplied(DebugTransform, value)) {
|
|
49
48
|
value = transformObject(DebugTransform, value);
|
|
50
49
|
}
|
|
@@ -58,10 +57,7 @@ function DebugTransform(Base) {
|
|
|
58
57
|
return content;
|
|
59
58
|
}
|
|
60
59
|
/** @type {any} */
|
|
61
|
-
let tree = Tree.from(content);
|
|
62
|
-
if (!tree.parent) {
|
|
63
|
-
tree.parent = parent;
|
|
64
|
-
}
|
|
60
|
+
let tree = Tree.from(content, { parent });
|
|
65
61
|
if (!isTransformApplied(DebugTransform, tree)) {
|
|
66
62
|
tree = transformObject(DebugTransform, tree);
|
|
67
63
|
}
|
package/src/builtins/@filter.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { Tree } from "@weborigami/async-tree";
|
|
1
2
|
import FilterTree from "../common/FilterTree.js";
|
|
2
3
|
import assertTreeIsDefined from "../misc/assertTreeIsDefined.js";
|
|
3
4
|
|
|
@@ -7,12 +8,15 @@ import assertTreeIsDefined from "../misc/assertTreeIsDefined.js";
|
|
|
7
8
|
* @typedef {import("@weborigami/types").AsyncTree} AsyncTree
|
|
8
9
|
* @typedef {import("@weborigami/async-tree").Treelike} Treelike
|
|
9
10
|
* @this {AsyncTree|null}
|
|
10
|
-
* @param {Treelike}
|
|
11
|
-
* @param {Treelike}
|
|
11
|
+
* @param {Treelike} sourceTreelike
|
|
12
|
+
* @param {Treelike} filterTreelike
|
|
12
13
|
*/
|
|
13
|
-
export default async function filter(
|
|
14
|
+
export default async function filter(sourceTreelike, filterTreelike) {
|
|
14
15
|
assertTreeIsDefined(this, "filter");
|
|
15
|
-
const
|
|
16
|
+
const sourceTree = Tree.from(sourceTreelike, { parent: this });
|
|
17
|
+
const filterTree = Tree.from(filterTreelike, { deep: true, parent: this });
|
|
18
|
+
const result = new FilterTree(sourceTree, filterTree);
|
|
19
|
+
result.parent = this;
|
|
16
20
|
return result;
|
|
17
21
|
}
|
|
18
22
|
|
package/src/builtins/@inners.js
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
|
-
import { Tree } from "@weborigami/async-tree";
|
|
1
|
+
import { trailingSlash, Tree } from "@weborigami/async-tree";
|
|
2
2
|
import getTreeArgument from "../misc/getTreeArgument.js";
|
|
3
3
|
|
|
4
4
|
/**
|
|
5
|
-
* Return the
|
|
5
|
+
* Return the interior nodes of the tree. This relies on subtree keys having
|
|
6
|
+
* trailing slashes.
|
|
6
7
|
*
|
|
7
8
|
* @typedef {import("@weborigami/types").AsyncTree} AsyncTree
|
|
8
9
|
* @typedef {import("@weborigami/async-tree").Treelike} Treelike
|
|
@@ -19,12 +20,8 @@ export default async function inners(treelike) {
|
|
|
19
20
|
},
|
|
20
21
|
|
|
21
22
|
async keys() {
|
|
22
|
-
const
|
|
23
|
-
|
|
24
|
-
if (await Tree.isKeyForSubtree(tree, key)) {
|
|
25
|
-
subtreeKeys.push(key);
|
|
26
|
-
}
|
|
27
|
-
}
|
|
23
|
+
const keys = [...(await tree.keys())];
|
|
24
|
+
const subtreeKeys = keys.filter(trailingSlash.has);
|
|
28
25
|
return subtreeKeys;
|
|
29
26
|
},
|
|
30
27
|
};
|
|
@@ -32,5 +29,5 @@ export default async function inners(treelike) {
|
|
|
32
29
|
return result;
|
|
33
30
|
}
|
|
34
31
|
|
|
35
|
-
inners.usage = `@inners <tree>\tThe
|
|
32
|
+
inners.usage = `@inners <tree>\tThe interior nodes of the tree`;
|
|
36
33
|
inners.documentation = "https://weborigami.org/cli/builtins.html#inners";
|
|
@@ -28,7 +28,7 @@ function KeysJsonTransform(Base) {
|
|
|
28
28
|
if (value === undefined && key === ".keys.json") {
|
|
29
29
|
value = await jsonKeys.stringify(this);
|
|
30
30
|
} else if (Tree.isTreelike(value)) {
|
|
31
|
-
const tree = Tree.from(value, { deep: true });
|
|
31
|
+
const tree = Tree.from(value, { deep: true, parent: this });
|
|
32
32
|
value = transformObject(KeysJsonTransform, tree);
|
|
33
33
|
}
|
|
34
34
|
return value;
|
package/src/builtins/@mapFn.js
CHANGED
|
@@ -91,7 +91,7 @@ export default function mapFnBuiltin(operation) {
|
|
|
91
91
|
);
|
|
92
92
|
return resultKey;
|
|
93
93
|
}
|
|
94
|
-
const keyFns = cachedKeyFunctions(scopedKeyFn);
|
|
94
|
+
const keyFns = cachedKeyFunctions(scopedKeyFn, deep);
|
|
95
95
|
extendedKeyFn = keyFns.key;
|
|
96
96
|
extendedInverseKeyFn = keyFns.inverseKey;
|
|
97
97
|
} else {
|
package/src/builtins/@mdHtml.js
CHANGED
|
@@ -38,7 +38,7 @@ marked.use(
|
|
|
38
38
|
* @param {StringLike|UnpackableStringlike} input
|
|
39
39
|
*/
|
|
40
40
|
export default async function mdHtml(input) {
|
|
41
|
-
if (
|
|
41
|
+
if (input == null) {
|
|
42
42
|
const error = new TypeError("@mdHtml: The input is not defined.");
|
|
43
43
|
/** @type {any} */ (error).position = 0;
|
|
44
44
|
throw error;
|
|
@@ -11,11 +11,13 @@ import { Tree } from "@weborigami/async-tree";
|
|
|
11
11
|
* @param {number} [size=10]
|
|
12
12
|
*/
|
|
13
13
|
export default function paginateFn(size = 10) {
|
|
14
|
+
const parent = this;
|
|
15
|
+
|
|
14
16
|
/**
|
|
15
17
|
* @param {Treelike} [treelike]
|
|
16
18
|
*/
|
|
17
19
|
return async function (treelike) {
|
|
18
|
-
const tree = Tree.from(treelike);
|
|
20
|
+
const tree = Tree.from(treelike, { parent });
|
|
19
21
|
const keys = Array.from(await tree.keys());
|
|
20
22
|
const pageCount = Math.ceil(keys.length / size);
|
|
21
23
|
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import {
|
|
2
|
+
isStringLike,
|
|
3
|
+
isUnpackable,
|
|
4
|
+
toPlainValue,
|
|
5
|
+
toString,
|
|
6
|
+
Tree,
|
|
7
|
+
} from "@weborigami/async-tree";
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* @this {import("@weborigami/types").AsyncTree|null}
|
|
11
|
+
* @param {string} url
|
|
12
|
+
* @param {any} data
|
|
13
|
+
*/
|
|
14
|
+
export default async function post(url, data) {
|
|
15
|
+
let body;
|
|
16
|
+
let headers;
|
|
17
|
+
if (isUnpackable(data)) {
|
|
18
|
+
data = await data.unpack();
|
|
19
|
+
}
|
|
20
|
+
if (Tree.isTreelike(data)) {
|
|
21
|
+
const value = await toPlainValue(data);
|
|
22
|
+
body = JSON.stringify(value);
|
|
23
|
+
headers = {
|
|
24
|
+
"Content-Type": "application/json",
|
|
25
|
+
};
|
|
26
|
+
} else if (isStringLike(data)) {
|
|
27
|
+
body = toString(data);
|
|
28
|
+
headers = {
|
|
29
|
+
"Content-Type": "text/plain",
|
|
30
|
+
};
|
|
31
|
+
} else {
|
|
32
|
+
body = data;
|
|
33
|
+
}
|
|
34
|
+
const response = await fetch(url, {
|
|
35
|
+
method: "POST",
|
|
36
|
+
body,
|
|
37
|
+
headers,
|
|
38
|
+
});
|
|
39
|
+
if (!response.ok) {
|
|
40
|
+
throw new Error(
|
|
41
|
+
`Failed to POST to ${url}. Error ${response.status}: ${response.statusText}`
|
|
42
|
+
);
|
|
43
|
+
}
|
|
44
|
+
return response.arrayBuffer();
|
|
45
|
+
}
|
package/src/builtins/@serve.js
CHANGED
|
@@ -25,10 +25,7 @@ export default async function serve(treelike, port) {
|
|
|
25
25
|
assertTreeIsDefined(this, "serve");
|
|
26
26
|
let tree;
|
|
27
27
|
if (treelike) {
|
|
28
|
-
tree = Tree.from(treelike);
|
|
29
|
-
|
|
30
|
-
// TODO: Instead of applying ExplorableSiteTransform, apply a transform
|
|
31
|
-
// that just maps the empty string to index.html.
|
|
28
|
+
tree = Tree.from(treelike, { parent: this });
|
|
32
29
|
if (!isTransformApplied(ExplorableSiteTransform, tree)) {
|
|
33
30
|
tree = transformObject(ExplorableSiteTransform, tree);
|
|
34
31
|
}
|
package/src/builtins/@setDeep.js
CHANGED
|
@@ -1,8 +1,15 @@
|
|
|
1
1
|
import { Tree } from "@weborigami/async-tree";
|
|
2
2
|
|
|
3
|
+
/**
|
|
4
|
+
* @typedef {import("@weborigami/async-tree").Treelike} Treelike
|
|
5
|
+
*
|
|
6
|
+
* @this {import("@weborigami/types").AsyncTree|null}
|
|
7
|
+
* @param {Treelike} target
|
|
8
|
+
* @param {Treelike} source
|
|
9
|
+
*/
|
|
3
10
|
export default async function setDeep(target, source) {
|
|
4
|
-
const targetTree = Tree.from(target);
|
|
5
|
-
const sourceTree = Tree.from(source);
|
|
11
|
+
const targetTree = Tree.from(target, { parent: this });
|
|
12
|
+
const sourceTree = Tree.from(source, { parent: this });
|
|
6
13
|
await applyUpdates(sourceTree, targetTree);
|
|
7
14
|
}
|
|
8
15
|
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { trailingSlash as default } from "@weborigami/async-tree";
|
package/src/builtins/@static.js
CHANGED
|
@@ -14,6 +14,7 @@ import index from "./@index.js";
|
|
|
14
14
|
export default async function staticTree(treelike) {
|
|
15
15
|
const tree = await getTreeArgument(this, arguments, treelike, "@static");
|
|
16
16
|
const result = transformObject(StaticTransform, tree);
|
|
17
|
+
result.parent = this;
|
|
17
18
|
return result;
|
|
18
19
|
}
|
|
19
20
|
|
|
@@ -22,11 +23,12 @@ function StaticTransform(Base) {
|
|
|
22
23
|
async get(key) {
|
|
23
24
|
let value = await super.get(key);
|
|
24
25
|
if (value === undefined && key === "index.html") {
|
|
25
|
-
value = index.call(this, this);
|
|
26
|
+
value = await index.call(this, this);
|
|
26
27
|
} else if (value === undefined && key === ".keys.json") {
|
|
27
|
-
value = jsonKeys.stringify(this);
|
|
28
|
-
} else if (Tree.
|
|
29
|
-
|
|
28
|
+
value = await jsonKeys.stringify(this);
|
|
29
|
+
} else if (Tree.isTreelike(value)) {
|
|
30
|
+
const tree = Tree.from(value, { parent: this });
|
|
31
|
+
value = transformObject(StaticTransform, tree);
|
|
30
32
|
}
|
|
31
33
|
return value;
|
|
32
34
|
}
|
package/src/builtins/@watch.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { Tree } from "@weborigami/async-tree";
|
|
2
|
+
import { formatError } from "@weborigami/language";
|
|
2
3
|
import ConstantTree from "../common/ConstantTree.js";
|
|
3
4
|
import getTreeArgument from "../misc/getTreeArgument.js";
|
|
4
5
|
|
|
@@ -42,16 +43,16 @@ export default async function watch(treelike, fn) {
|
|
|
42
43
|
return handle;
|
|
43
44
|
}
|
|
44
45
|
|
|
45
|
-
async function evaluateTree(
|
|
46
|
+
async function evaluateTree(parent, fn) {
|
|
46
47
|
let tree;
|
|
47
48
|
let message;
|
|
48
49
|
let result;
|
|
49
50
|
try {
|
|
50
|
-
result = await fn.call(
|
|
51
|
-
} catch (error) {
|
|
52
|
-
message =
|
|
51
|
+
result = await fn.call(parent);
|
|
52
|
+
} catch (/** @type {any} */ error) {
|
|
53
|
+
message = formatError(error);
|
|
53
54
|
}
|
|
54
|
-
tree = result ? Tree.from(result) : undefined;
|
|
55
|
+
tree = result ? Tree.from(result, { parent }) : undefined;
|
|
55
56
|
if (tree) {
|
|
56
57
|
return tree;
|
|
57
58
|
}
|
|
@@ -60,33 +61,23 @@ async function evaluateTree(container, fn) {
|
|
|
60
61
|
}
|
|
61
62
|
console.warn(message);
|
|
62
63
|
tree = new ConstantTree(message);
|
|
63
|
-
tree.parent =
|
|
64
|
+
tree.parent = parent;
|
|
64
65
|
return tree;
|
|
65
66
|
}
|
|
66
67
|
|
|
67
|
-
function messageForError(error) {
|
|
68
|
-
let message = "";
|
|
69
|
-
// Work up to the root cause, displaying intermediate messages as we go up.
|
|
70
|
-
while (error.cause) {
|
|
71
|
-
message += error.message + `\n`;
|
|
72
|
-
error = error.cause;
|
|
73
|
-
}
|
|
74
|
-
if (error.name) {
|
|
75
|
-
message += `${error.name}: `;
|
|
76
|
-
}
|
|
77
|
-
message += error.message;
|
|
78
|
-
return message;
|
|
79
|
-
}
|
|
80
|
-
|
|
81
68
|
// Update an indirect pointer to a target.
|
|
82
69
|
function updateIndirectPointer(indirect, target) {
|
|
83
70
|
// Clean the pointer of any named properties or symbols that have been set
|
|
84
71
|
// directly on it.
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
72
|
+
try {
|
|
73
|
+
for (const key of Object.getOwnPropertyNames(indirect)) {
|
|
74
|
+
delete indirect[key];
|
|
75
|
+
}
|
|
76
|
+
for (const key of Object.getOwnPropertySymbols(indirect)) {
|
|
77
|
+
delete indirect[key];
|
|
78
|
+
}
|
|
79
|
+
} catch {
|
|
80
|
+
// Ignore errors.
|
|
90
81
|
}
|
|
91
82
|
|
|
92
83
|
Object.setPrototypeOf(indirect, target);
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { trailingSlash } from "@weborigami/async-tree";
|
|
1
2
|
import path from "node:path";
|
|
2
3
|
|
|
3
4
|
/**
|
|
@@ -18,6 +19,7 @@ export default function CommandsModulesTransform(Base) {
|
|
|
18
19
|
}
|
|
19
20
|
|
|
20
21
|
// See if we have a JS module for the requested key.
|
|
22
|
+
key = trailingSlash.remove(key);
|
|
21
23
|
if (key === undefined || key.endsWith?.(".js")) {
|
|
22
24
|
return undefined;
|
|
23
25
|
}
|
|
@@ -25,11 +25,6 @@ import { isTransformApplied, transformObject } from "../common/utilities.js";
|
|
|
25
25
|
export default function ExplorableSiteTransform(Base) {
|
|
26
26
|
return class ExplorableSite extends Base {
|
|
27
27
|
async get(key) {
|
|
28
|
-
// The empty string key represents "index.html".
|
|
29
|
-
if (key === "") {
|
|
30
|
-
key = "index.html";
|
|
31
|
-
}
|
|
32
|
-
|
|
33
28
|
// Ask the tree if it has the key.
|
|
34
29
|
let value = await super.get(key);
|
|
35
30
|
|
|
@@ -49,11 +44,6 @@ export default function ExplorableSiteTransform(Base) {
|
|
|
49
44
|
if (!isTransformApplied(ExplorableSiteTransform, value)) {
|
|
50
45
|
value = transformObject(ExplorableSiteTransform, value);
|
|
51
46
|
}
|
|
52
|
-
|
|
53
|
-
if (key.endsWith?.("/")) {
|
|
54
|
-
// Instead of return the tree directly, return an index for it.
|
|
55
|
-
value = await index.call(this, value);
|
|
56
|
-
}
|
|
57
47
|
} else if (value?.unpack) {
|
|
58
48
|
// If the value isn't a tree, but has a tree attached via an `unpack`
|
|
59
49
|
// method, wrap the unpack method to add this transform.
|
|
@@ -61,7 +51,8 @@ export default function ExplorableSiteTransform(Base) {
|
|
|
61
51
|
const parent = this;
|
|
62
52
|
value.unpack = async () => {
|
|
63
53
|
const content = await original();
|
|
64
|
-
|
|
54
|
+
// See function notes at @debug
|
|
55
|
+
if (!Tree.isTraversable(content) || typeof content === "function") {
|
|
65
56
|
return content;
|
|
66
57
|
}
|
|
67
58
|
/** @type {any} */
|
|
@@ -77,5 +68,11 @@ export default function ExplorableSiteTransform(Base) {
|
|
|
77
68
|
}
|
|
78
69
|
return value;
|
|
79
70
|
}
|
|
71
|
+
|
|
72
|
+
// If this value is given to the server, the server will call this pack()
|
|
73
|
+
// method. We respond with the index page.
|
|
74
|
+
async pack() {
|
|
75
|
+
return this.get("index.html");
|
|
76
|
+
}
|
|
80
77
|
};
|
|
81
78
|
}
|
package/src/common/FilterTree.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { trailingSlash, Tree } from "@weborigami/async-tree";
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
4
|
* @typedef {import("@weborigami/types").AsyncTree} AsyncTree
|
|
@@ -7,9 +7,8 @@ import { DeepObjectTree, Tree, isPlainObject } from "@weborigami/async-tree";
|
|
|
7
7
|
export default class FilterTree {
|
|
8
8
|
constructor(tree, filter) {
|
|
9
9
|
this.tree = Tree.from(tree);
|
|
10
|
-
this.filter =
|
|
11
|
-
|
|
12
|
-
: Tree.from(filter);
|
|
10
|
+
this.filter = Tree.from(filter, { deep: true });
|
|
11
|
+
this.parent = null;
|
|
13
12
|
}
|
|
14
13
|
|
|
15
14
|
async get(key) {
|
|
@@ -41,7 +40,7 @@ export default class FilterTree {
|
|
|
41
40
|
// must be a tree too.
|
|
42
41
|
const match =
|
|
43
42
|
(!isFilterValueTree && filterValue) ||
|
|
44
|
-
(isFilterValueTree &&
|
|
43
|
+
(isFilterValueTree && trailingSlash.has(key));
|
|
45
44
|
if (match) {
|
|
46
45
|
keys.add(key);
|
|
47
46
|
}
|
|
@@ -50,12 +49,12 @@ export default class FilterTree {
|
|
|
50
49
|
// Also include any keys in the filter that are found in the tree. This
|
|
51
50
|
// lets the filter "pull" values from a tree that, e.g., is defined by a
|
|
52
51
|
// function without an explicit domain.
|
|
53
|
-
for (const key of await this.filter.keys()) {
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
}
|
|
52
|
+
// for (const key of await this.filter.keys()) {
|
|
53
|
+
// const value = await this.tree.get(key);
|
|
54
|
+
// if (value !== undefined) {
|
|
55
|
+
// keys.add(key);
|
|
56
|
+
// }
|
|
57
|
+
// }
|
|
59
58
|
|
|
60
59
|
return keys;
|
|
61
60
|
}
|
package/src/common/GlobTree.js
CHANGED
|
@@ -1,10 +1,4 @@
|
|
|
1
|
-
import {
|
|
2
|
-
DeepObjectTree,
|
|
3
|
-
ObjectTree,
|
|
4
|
-
Tree,
|
|
5
|
-
isPlainObject,
|
|
6
|
-
merge,
|
|
7
|
-
} from "@weborigami/async-tree";
|
|
1
|
+
import { ObjectTree, Tree, merge, trailingSlash } from "@weborigami/async-tree";
|
|
8
2
|
|
|
9
3
|
const globstar = "**";
|
|
10
4
|
|
|
@@ -14,15 +8,17 @@ const globstar = "**";
|
|
|
14
8
|
*/
|
|
15
9
|
export default class GlobTree {
|
|
16
10
|
constructor(globs) {
|
|
17
|
-
this.globs =
|
|
18
|
-
? new DeepObjectTree(globs)
|
|
19
|
-
: Tree.from(globs);
|
|
11
|
+
this.globs = Tree.from(globs, { deep: true });
|
|
20
12
|
}
|
|
21
13
|
|
|
22
14
|
async get(key) {
|
|
23
15
|
if (typeof key !== "string") {
|
|
24
16
|
return undefined;
|
|
25
17
|
}
|
|
18
|
+
|
|
19
|
+
// Remove trailing slash if it exists
|
|
20
|
+
key = trailingSlash.remove(key);
|
|
21
|
+
|
|
26
22
|
let value = await matchGlobs(this.globs, key);
|
|
27
23
|
if (Tree.isAsyncTree(value)) {
|
|
28
24
|
value = Reflect.construct(this.constructor, [value]);
|
|
@@ -35,8 +31,8 @@ export default class GlobTree {
|
|
|
35
31
|
}
|
|
36
32
|
}
|
|
37
33
|
|
|
34
|
+
// Convert the glob to a regular expression
|
|
38
35
|
function matchGlob(glob, text) {
|
|
39
|
-
// Convert the glob to a regular expression
|
|
40
36
|
const regexText = glob
|
|
41
37
|
// Escape special regex characters
|
|
42
38
|
.replace(/[+?^${}()|\[\]\\]/g, "\\$&")
|
|
@@ -49,10 +45,15 @@ function matchGlob(glob, text) {
|
|
|
49
45
|
|
|
50
46
|
async function matchGlobs(globs, text) {
|
|
51
47
|
let value;
|
|
52
|
-
for (
|
|
48
|
+
for (let glob of await globs.keys()) {
|
|
53
49
|
if (typeof glob !== "string") {
|
|
54
50
|
continue;
|
|
55
|
-
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
// Remove trailing slash if it exists
|
|
54
|
+
glob = trailingSlash.remove(glob);
|
|
55
|
+
|
|
56
|
+
if (glob !== globstar && matchGlob(glob, text)) {
|
|
56
57
|
value = await globs.get(glob);
|
|
57
58
|
if (value !== undefined) {
|
|
58
59
|
break;
|
package/src/misc/treeDot.js
CHANGED
|
@@ -3,6 +3,7 @@ import {
|
|
|
3
3
|
isPlainObject,
|
|
4
4
|
isStringLike,
|
|
5
5
|
toString,
|
|
6
|
+
trailingSlash,
|
|
6
7
|
} from "@weborigami/async-tree";
|
|
7
8
|
import * as serialize from "../common/serialize.js";
|
|
8
9
|
import { keySymbol } from "../common/utilities.js";
|
|
@@ -49,7 +50,7 @@ async function statements(tree, nodePath, nodeLabel, options) {
|
|
|
49
50
|
// Draw edges and collect labels for the nodes they lead to.
|
|
50
51
|
let nodes = new Map();
|
|
51
52
|
for (const key of await tree.keys()) {
|
|
52
|
-
const destPath = nodePath ? `${nodePath}
|
|
53
|
+
const destPath = nodePath ? `${trailingSlash.add(nodePath)}${key}` : key;
|
|
53
54
|
const arc = ` "${nodePath}" -> "${destPath}" [label="${key}"];`;
|
|
54
55
|
result.push(arc);
|
|
55
56
|
|
|
@@ -163,7 +164,7 @@ async function statements(tree, nodePath, nodeLabel, options) {
|
|
|
163
164
|
const label = `label="${icon}${text}"`;
|
|
164
165
|
const color = node.isError ? `; color="red"` : "";
|
|
165
166
|
const fill = node.isError ? `; fillcolor="#FFF4F4"` : "";
|
|
166
|
-
const destPath = nodePath ? `${nodePath}
|
|
167
|
+
const destPath = nodePath ? `${trailingSlash.add(nodePath)}${key}` : key;
|
|
167
168
|
const url = createLinks ? `; URL="${destPath}"` : "";
|
|
168
169
|
result.push(` "${destPath}" [${label}${color}${fill}${url}];`);
|
|
169
170
|
}
|
|
@@ -32,16 +32,6 @@ export default async function constructResponse(request, resource) {
|
|
|
32
32
|
// Determine media type, what data we'll send, and encoding.
|
|
33
33
|
const url = new URL(request.url ?? "", `https://${request.headers.host}`);
|
|
34
34
|
|
|
35
|
-
let mediaType;
|
|
36
|
-
if (resource.mediaType) {
|
|
37
|
-
// Resource indicates its own media type.
|
|
38
|
-
mediaType = resource.mediaType;
|
|
39
|
-
} else {
|
|
40
|
-
// Infer expected media type from file extension on request URL.
|
|
41
|
-
const extension = extname(url.pathname).toLowerCase();
|
|
42
|
-
mediaType = extension ? mediaTypeForExtension[extension] : undefined;
|
|
43
|
-
}
|
|
44
|
-
|
|
45
35
|
if (!url.pathname.endsWith("/") && Tree.isTreelike(resource)) {
|
|
46
36
|
// Treelike resource: redirect to its index page.
|
|
47
37
|
const Location = `${url.pathname}/`;
|
|
@@ -57,6 +47,16 @@ export default async function constructResponse(request, resource) {
|
|
|
57
47
|
resource = await resource.pack();
|
|
58
48
|
}
|
|
59
49
|
|
|
50
|
+
let mediaType;
|
|
51
|
+
if (resource.mediaType) {
|
|
52
|
+
// Resource indicates its own media type.
|
|
53
|
+
mediaType = resource.mediaType;
|
|
54
|
+
} else {
|
|
55
|
+
// Infer expected media type from file extension on request URL.
|
|
56
|
+
const extension = extname(url.pathname).toLowerCase();
|
|
57
|
+
mediaType = extension ? mediaTypeForExtension[extension] : undefined;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
60
|
if (
|
|
61
61
|
(mediaType === "application/json" || mediaType === "text/yaml") &&
|
|
62
62
|
!isStringLike(resource)
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { toString } from "@weborigami/async-tree";
|
|
2
|
+
|
|
3
|
+
export default async function parsePostData(request) {
|
|
4
|
+
const data = await getPostData(request);
|
|
5
|
+
const type = request.headers["content-type"];
|
|
6
|
+
switch (type) {
|
|
7
|
+
case "application/json":
|
|
8
|
+
return JSON.parse(data);
|
|
9
|
+
|
|
10
|
+
case "application/x-www-form-urlencoded":
|
|
11
|
+
const params = new URLSearchParams(data);
|
|
12
|
+
return Object.fromEntries(params);
|
|
13
|
+
|
|
14
|
+
case "text/plain":
|
|
15
|
+
return toString(data);
|
|
16
|
+
|
|
17
|
+
default:
|
|
18
|
+
return data;
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function getPostData(request) {
|
|
23
|
+
return new Promise((resolve, reject) => {
|
|
24
|
+
let body = "";
|
|
25
|
+
request.on("data", (chunk) => {
|
|
26
|
+
body += chunk.toString();
|
|
27
|
+
});
|
|
28
|
+
request.on("end", () => {
|
|
29
|
+
resolve(body);
|
|
30
|
+
});
|
|
31
|
+
request.on("error", (error) => {
|
|
32
|
+
reject(error);
|
|
33
|
+
});
|
|
34
|
+
});
|
|
35
|
+
}
|
package/src/server/server.js
CHANGED
|
@@ -2,6 +2,7 @@ import { ObjectTree, Tree, keysFromPath } from "@weborigami/async-tree";
|
|
|
2
2
|
import { formatError } from "@weborigami/language";
|
|
3
3
|
import { ServerResponse } from "node:http";
|
|
4
4
|
import constructResponse from "./constructResponse.js";
|
|
5
|
+
import parsePostData from "./parsePostData.js";
|
|
5
6
|
|
|
6
7
|
/**
|
|
7
8
|
* Copy a constructed response to a ServerResponse. Return true if the response
|
|
@@ -81,13 +82,16 @@ export async function handleRequest(request, response, tree) {
|
|
|
81
82
|
? extendTreeScopeWithParams(tree, url)
|
|
82
83
|
: tree;
|
|
83
84
|
|
|
85
|
+
const data = request.method === "POST" ? await parsePostData(request) : null;
|
|
86
|
+
|
|
84
87
|
// Ask the tree for the resource with those keys.
|
|
85
88
|
let resource;
|
|
86
89
|
try {
|
|
87
90
|
resource = await Tree.traverse(extendedTree, ...keys);
|
|
88
91
|
// If resource is a function, invoke to get the object we want to return.
|
|
92
|
+
// For a POST request, pass the data to the function.
|
|
89
93
|
if (typeof resource === "function") {
|
|
90
|
-
resource = await resource();
|
|
94
|
+
resource = data ? await resource(data) : await resource();
|
|
91
95
|
}
|
|
92
96
|
} catch (/** @type {any} */ error) {
|
|
93
97
|
respondWithError(response, error);
|
|
@@ -111,8 +115,16 @@ function keysFromUrl(url) {
|
|
|
111
115
|
const parts = url.pathname.split(/\/!/);
|
|
112
116
|
|
|
113
117
|
// Split everything before the first command by slashes and decode those.
|
|
114
|
-
|
|
118
|
+
let path = parts.shift();
|
|
119
|
+
if (parts.length > 0) {
|
|
120
|
+
// HACK: Add back trailing slash that was removed by split
|
|
121
|
+
path += "/";
|
|
122
|
+
}
|
|
115
123
|
const pathKeys = keysFromPath(path).map((key) => decodeURIComponent(key));
|
|
124
|
+
if (parts.length > 0 && pathKeys.at(-1) === "") {
|
|
125
|
+
// HACK part 2: Remove empty string that was added for trailing slash
|
|
126
|
+
pathKeys.pop();
|
|
127
|
+
}
|
|
116
128
|
|
|
117
129
|
// If there are no commands, and the path ends with a trailing slash, the
|
|
118
130
|
// final key will be an empty string. Change that to "index.html".
|