@weborigami/origami 0.0.41 → 0.0.43
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/buildExports.js +1 -1
- package/exports/exports.js +32 -34
- package/package.json +4 -4
- package/src/builtins/@arrows.js +1 -1
- package/src/builtins/@builtins.js +2 -5
- package/src/builtins/@cache.js +1 -1
- package/src/builtins/{@tree/concat.js → @concat.js} +3 -3
- package/src/builtins/@copy.js +2 -2
- package/src/builtins/{@tree/count.js → @count.js} +3 -3
- package/src/builtins/@crawl.js +1 -1
- package/src/builtins/@debug.js +1 -1
- package/src/builtins/{@tree/defineds.js → @defineds.js} +2 -2
- package/src/builtins/@document.js +1 -1
- package/src/builtins/{@tree/exceptions.js → @exceptions.js} +4 -4
- package/src/builtins/@explore.js +2 -5
- package/src/builtins/@files.js +1 -1
- package/src/builtins/@filter.js +1 -1
- package/src/builtins/{@tree/first.js → @first.js} +3 -3
- package/src/builtins/{@tree/fn.js → @fnTree.js} +7 -7
- package/src/builtins/@globs.js +1 -1
- package/src/builtins/{@tree/groupBy.js → @groupBy.js} +6 -6
- package/src/builtins/@help.js +1 -1
- package/src/builtins/@http.js +1 -1
- package/src/builtins/@https.js +1 -1
- package/src/builtins/@if.js +1 -1
- package/src/builtins/@image/format.js +36 -2
- package/src/builtins/@image/resize.js +32 -2
- package/src/builtins/@index.js +1 -1
- package/src/builtins/@inherited.js +1 -1
- package/src/builtins/@inline.js +2 -2
- package/src/builtins/{@tree/inners.js → @inners.js} +3 -3
- package/src/builtins/@invoke.js +1 -1
- package/src/builtins/{@tree/isAsyncTree.js → @isAsyncTree.js} +1 -1
- package/src/builtins/@json.js +1 -1
- package/src/builtins/{@tree/keys.js → @keys.js} +3 -3
- package/src/builtins/{@tree/keysJson.js → @keysJson.js} +3 -3
- package/src/builtins/@loaders/ori.js +18 -18
- package/src/builtins/@map.js +13 -1
- package/src/builtins/@match.js +1 -1
- package/src/builtins/{@tree/merge.js → @merge.js} +2 -2
- package/src/builtins/{@tree/mergeDeep.js → @mergeDeep.js} +3 -3
- package/src/builtins/@once.js +1 -1
- package/src/builtins/@ori.js +1 -1
- package/src/builtins/@pack.js +1 -1
- package/src/builtins/{@tree/parent.js → @parent.js} +3 -3
- package/src/builtins/{@tree/paths.js → @paths.js} +3 -3
- package/src/builtins/@perf.js +18 -0
- package/src/builtins/{@tree/plain.js → @plain.js} +3 -3
- package/src/builtins/@project.js +1 -1
- package/src/builtins/@redirect.js +8 -0
- package/src/builtins/{@tree/reverse.js → @reverse.js} +3 -3
- package/src/builtins/@rss.js +1 -1
- package/src/builtins/@scope/extend.js +6 -6
- package/src/builtins/@scope/get.js +1 -1
- package/src/builtins/@scope/set.js +4 -4
- package/src/builtins/@serve.js +1 -1
- package/src/builtins/{@tree/setDeep.js → @setDeep.js} +1 -1
- package/src/builtins/{@tree/shuffle.js → @shuffle.js} +5 -5
- package/src/builtins/{@tree/sitemap.js → @sitemap.js} +8 -8
- package/src/builtins/{@tree/sort.js → @sort.js} +4 -4
- package/src/builtins/{@tree/sortBy.js → @sortBy.js} +6 -6
- package/src/builtins/{@tree/static.js → @static.js} +5 -5
- package/src/builtins/@svg.js +2 -2
- package/src/builtins/{@tree/table.js → @table.js} +3 -3
- package/src/builtins/{@tree/take.js → @take.js} +3 -3
- package/src/builtins/{@tree/from.js → @tree.js} +3 -3
- package/src/builtins/@treeHttp.js +1 -1
- package/src/builtins/@treeHttps.js +1 -1
- package/src/builtins/@unpack.js +1 -1
- package/src/builtins/{@tree/values.js → @values.js} +3 -3
- package/src/builtins/{@tree/valuesDeep.js → @valuesDeep.js} +3 -3
- package/src/builtins/@watch.js +1 -1
- package/src/builtins/@with.js +1 -1
- package/src/builtins/@yaml.js +1 -1
- package/src/builtins/{@tree/map.d.ts → map.d.ts} +1 -1
- package/src/cli/cli.js +2 -15
- package/src/common/ExplorableSiteTransform.js +6 -4
- package/src/misc/assertScopeIsDefined.js +2 -2
- package/src/misc/explore.ori +4 -4
- package/src/misc/getTreeArgument.js +16 -6
- package/src/{builtins/@tree/dot.js → misc/treeDot.js} +8 -8
- package/src/server/constructResponse.js +126 -0
- package/src/server/mediaTypes.js +0 -16
- package/src/server/server.js +62 -131
- package/src/builtins/@tree/flowSvg.js +0 -55
- package/src/builtins/@tree/fromJson.js +0 -6
- package/src/builtins/@tree/fromYaml.js +0 -24
- package/src/builtins/@tree/nextKey.js +0 -29
- package/src/builtins/@tree/previousKey.js +0 -29
package/src/builtins/@svg.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import graphviz from "graphviz-wasm";
|
|
2
2
|
import getTreeArgument from "../misc/getTreeArgument.js";
|
|
3
|
-
import dot from "
|
|
3
|
+
import dot from "../misc/treeDot.js";
|
|
4
4
|
|
|
5
5
|
let graphvizLoaded = false;
|
|
6
6
|
|
|
@@ -20,7 +20,7 @@ export default async function svg(treelike, options = {}) {
|
|
|
20
20
|
await graphviz.loadWASM();
|
|
21
21
|
graphvizLoaded = true;
|
|
22
22
|
}
|
|
23
|
-
const tree = await getTreeArgument(this, arguments, treelike);
|
|
23
|
+
const tree = await getTreeArgument(this, arguments, treelike, "@svg");
|
|
24
24
|
const dotText = await dot.call(this, tree, options);
|
|
25
25
|
if (dotText === undefined) {
|
|
26
26
|
return undefined;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { Tree } from "@weborigami/async-tree";
|
|
2
|
-
import getTreeArgument from "
|
|
2
|
+
import getTreeArgument from "../misc/getTreeArgument.js";
|
|
3
3
|
|
|
4
4
|
/**
|
|
5
5
|
* @typedef {import("@weborigami/types").AsyncTree} AsyncTree
|
|
@@ -8,7 +8,7 @@ import getTreeArgument from "../../misc/getTreeArgument.js";
|
|
|
8
8
|
* @param {Treelike} treelike
|
|
9
9
|
*/
|
|
10
10
|
export default async function table(treelike) {
|
|
11
|
-
const tree = await getTreeArgument(this, arguments, treelike);
|
|
11
|
+
const tree = await getTreeArgument(this, arguments, treelike, "@table");
|
|
12
12
|
const firstValue = await valueForFirstKey(tree);
|
|
13
13
|
if (Tree.isAsyncTree(firstValue)) {
|
|
14
14
|
return fullTable(tree, firstValue);
|
|
@@ -65,5 +65,5 @@ async function valueForFirstKey(tree) {
|
|
|
65
65
|
}
|
|
66
66
|
|
|
67
67
|
table.usage =
|
|
68
|
-
"table <tree>\tFormat the tree's top level as a tab-delimited table";
|
|
68
|
+
"@table <tree>\tFormat the tree's top level as a tab-delimited table";
|
|
69
69
|
table.documentation = "https://weborigami.org/cli/builtins.html#table";
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { Scope } from "@weborigami/language";
|
|
2
|
-
import getTreeArgument from "
|
|
2
|
+
import getTreeArgument from "../misc/getTreeArgument.js";
|
|
3
3
|
|
|
4
4
|
/**
|
|
5
5
|
* Given a tree, take the first n items from it.
|
|
@@ -11,7 +11,7 @@ import getTreeArgument from "../../misc/getTreeArgument.js";
|
|
|
11
11
|
* @param {number} n
|
|
12
12
|
*/
|
|
13
13
|
export default async function take(treelike, n) {
|
|
14
|
-
const tree = await getTreeArgument(this, arguments, treelike);
|
|
14
|
+
const tree = await getTreeArgument(this, arguments, treelike, "@take");
|
|
15
15
|
|
|
16
16
|
/** @type {AsyncTree} */
|
|
17
17
|
let takeTree = {
|
|
@@ -30,5 +30,5 @@ export default async function take(treelike, n) {
|
|
|
30
30
|
return takeTree;
|
|
31
31
|
}
|
|
32
32
|
|
|
33
|
-
take.usage =
|
|
33
|
+
take.usage = `@take tree, n\tReturn the first n items from tree`;
|
|
34
34
|
take.documentation = "https://weborigami.org/cli/builtins.html#take";
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { Scope } from "@weborigami/language";
|
|
2
|
-
import getTreeArgument from "
|
|
2
|
+
import getTreeArgument from "../misc/getTreeArgument.js";
|
|
3
3
|
|
|
4
4
|
/**
|
|
5
5
|
* Cast the indicated treelike to a tree.
|
|
@@ -10,10 +10,10 @@ import getTreeArgument from "../../misc/getTreeArgument.js";
|
|
|
10
10
|
* @param {Treelike} [treelike]
|
|
11
11
|
*/
|
|
12
12
|
export default async function tree(treelike) {
|
|
13
|
-
let tree = await getTreeArgument(this, arguments, treelike);
|
|
13
|
+
let tree = await getTreeArgument(this, arguments, treelike, "@tree");
|
|
14
14
|
tree = Scope.treeWithScope(tree, this);
|
|
15
15
|
return tree;
|
|
16
16
|
}
|
|
17
17
|
|
|
18
|
-
tree.usage =
|
|
18
|
+
tree.usage = `@tree <treelike>\tConvert JSON, YAML, function, or plain object to a tree`;
|
|
19
19
|
tree.documentation = "https://weborigami.org/cli/builtins.html#tree";
|
|
@@ -11,7 +11,7 @@ import assertScopeIsDefined from "../misc/assertScopeIsDefined.js";
|
|
|
11
11
|
* @param {...string|Symbol} keys
|
|
12
12
|
*/
|
|
13
13
|
export default function treeHttp(host, ...keys) {
|
|
14
|
-
assertScopeIsDefined(this);
|
|
14
|
+
assertScopeIsDefined(this, "treeHttp");
|
|
15
15
|
return ops.treeHttp.call(this, host, ...keys);
|
|
16
16
|
}
|
|
17
17
|
|
|
@@ -11,7 +11,7 @@ import assertScopeIsDefined from "../misc/assertScopeIsDefined.js";
|
|
|
11
11
|
* @param {...string|Symbol} keys
|
|
12
12
|
*/
|
|
13
13
|
export default function treeHttps(host, ...keys) {
|
|
14
|
-
assertScopeIsDefined(this);
|
|
14
|
+
assertScopeIsDefined(this, "treeHttps");
|
|
15
15
|
return ops.treeHttps.call(this, host, ...keys);
|
|
16
16
|
}
|
|
17
17
|
|
package/src/builtins/@unpack.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { Tree } from "@weborigami/async-tree";
|
|
2
|
-
import getTreeArgument from "
|
|
2
|
+
import getTreeArgument from "../misc/getTreeArgument.js";
|
|
3
3
|
|
|
4
4
|
/**
|
|
5
5
|
* Return the interior nodes of the tree.
|
|
@@ -10,9 +10,9 @@ import getTreeArgument from "../../misc/getTreeArgument.js";
|
|
|
10
10
|
* @param {Treelike} [treelike]
|
|
11
11
|
*/
|
|
12
12
|
export default async function values(treelike) {
|
|
13
|
-
const tree = await getTreeArgument(this, arguments, treelike);
|
|
13
|
+
const tree = await getTreeArgument(this, arguments, treelike, "@values");
|
|
14
14
|
return Tree.values(tree);
|
|
15
15
|
}
|
|
16
16
|
|
|
17
|
-
values.usage =
|
|
17
|
+
values.usage = `@values <tree>\tThe top-level values in the tree`;
|
|
18
18
|
values.documentation = "https://weborigami.org/cli/builtins.html#values";
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { Tree } from "@weborigami/async-tree";
|
|
2
|
-
import getTreeArgument from "
|
|
2
|
+
import getTreeArgument from "../misc/getTreeArgument.js";
|
|
3
3
|
|
|
4
4
|
/**
|
|
5
5
|
* Return the in-order exterior values of a tree as a flat array.
|
|
@@ -10,10 +10,10 @@ import getTreeArgument from "../../misc/getTreeArgument.js";
|
|
|
10
10
|
* @param {Treelike} [treelike]
|
|
11
11
|
*/
|
|
12
12
|
export default async function valuesDeep(treelike) {
|
|
13
|
-
const tree = await getTreeArgument(this, arguments, treelike);
|
|
13
|
+
const tree = await getTreeArgument(this, arguments, treelike, "@valuesDeep");
|
|
14
14
|
return Tree.mapReduce(tree, null, async (values) => values.flat());
|
|
15
15
|
}
|
|
16
16
|
|
|
17
|
-
valuesDeep.usage =
|
|
17
|
+
valuesDeep.usage = `@valuesDeep <tree>\tThe in-order tree values as a flat array`;
|
|
18
18
|
valuesDeep.documentation =
|
|
19
19
|
"https://weborigami.org/cli/builtins.html#valuesDeep";
|
package/src/builtins/@watch.js
CHANGED
|
@@ -16,7 +16,7 @@ import getTreeArgument from "../misc/getTreeArgument.js";
|
|
|
16
16
|
*/
|
|
17
17
|
export default async function watch(treelike, fn) {
|
|
18
18
|
/** @type {any} */
|
|
19
|
-
const container = await getTreeArgument(this, arguments, treelike);
|
|
19
|
+
const container = await getTreeArgument(this, arguments, treelike, "@watch");
|
|
20
20
|
|
|
21
21
|
// Watch the indicated tree.
|
|
22
22
|
await /** @type {any} */ (container).watch?.();
|
package/src/builtins/@with.js
CHANGED
|
@@ -14,7 +14,7 @@ import assertScopeIsDefined from "../misc/assertScopeIsDefined.js";
|
|
|
14
14
|
* @param {Invocable} invocable
|
|
15
15
|
*/
|
|
16
16
|
export default function withTree(treelike, invocable) {
|
|
17
|
-
assertScopeIsDefined(this);
|
|
17
|
+
assertScopeIsDefined(this, "with");
|
|
18
18
|
const tree = Tree.from(treelike);
|
|
19
19
|
const fn = toFunction(invocable);
|
|
20
20
|
const scope = new Scope(tree, this);
|
package/src/builtins/@yaml.js
CHANGED
|
@@ -10,7 +10,7 @@ import assertScopeIsDefined from "../misc/assertScopeIsDefined.js";
|
|
|
10
10
|
* @param {any} [obj]
|
|
11
11
|
*/
|
|
12
12
|
export default async function toYaml(obj) {
|
|
13
|
-
assertScopeIsDefined(this);
|
|
13
|
+
assertScopeIsDefined(this, "yaml");
|
|
14
14
|
// A fragment of the logic from getTreeArgument.js
|
|
15
15
|
if (arguments.length > 0 && obj === undefined) {
|
|
16
16
|
throw new Error(
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { KeyFn, Treelike, ValueKeyFn } from "@weborigami/async-tree";
|
|
2
2
|
import { AsyncTree } from "@weborigami/types";
|
|
3
|
-
import { TreelikeTransform } from "
|
|
3
|
+
import { TreelikeTransform } from "../../index.ts";
|
|
4
4
|
|
|
5
5
|
type TreeMapOptions = {
|
|
6
6
|
deep?: boolean;
|
package/src/cli/cli.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
3
|
import { ObjectTree, Tree } from "@weborigami/async-tree";
|
|
4
|
-
import { Scope } from "@weborigami/language";
|
|
4
|
+
import { Scope, formatError } from "@weborigami/language";
|
|
5
5
|
import path from "node:path";
|
|
6
6
|
import process, { stdout } from "node:process";
|
|
7
7
|
import ori from "../builtins/@ori.js";
|
|
@@ -75,19 +75,6 @@ while (args[0] === "") {
|
|
|
75
75
|
try {
|
|
76
76
|
await main(...args);
|
|
77
77
|
} catch (/** @type {any} */ error) {
|
|
78
|
-
|
|
79
|
-
if (!error.cause && !error.stack) {
|
|
80
|
-
console.error(error.message);
|
|
81
|
-
} else {
|
|
82
|
-
while (error.cause) {
|
|
83
|
-
console.error(error.message);
|
|
84
|
-
error = error.cause;
|
|
85
|
-
}
|
|
86
|
-
}
|
|
87
|
-
if (error.stack) {
|
|
88
|
-
// Display stack trace for root cause, under the theory that that's the most
|
|
89
|
-
// useful place to look for the problem.
|
|
90
|
-
console.error(error.stack);
|
|
91
|
-
}
|
|
78
|
+
console.error(formatError(error));
|
|
92
79
|
process.exitCode = 1;
|
|
93
80
|
}
|
|
@@ -37,10 +37,12 @@ export default function ExplorableSiteTransform(Base) {
|
|
|
37
37
|
if (value === undefined) {
|
|
38
38
|
// The tree doesn't have the key; try the defaults.
|
|
39
39
|
const scope = Scope.getScope(this);
|
|
40
|
-
if (
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
40
|
+
if (scope) {
|
|
41
|
+
if (key === "index.html") {
|
|
42
|
+
value = await index.call(scope, this);
|
|
43
|
+
} else if (key === ".keys.json") {
|
|
44
|
+
value = await keysJson.stringify(this);
|
|
45
|
+
}
|
|
44
46
|
}
|
|
45
47
|
}
|
|
46
48
|
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
export default function assertScopeIsDefined(scope) {
|
|
1
|
+
export default function assertScopeIsDefined(scope, methodName) {
|
|
2
2
|
if (scope === undefined) {
|
|
3
3
|
throw new Error(
|
|
4
|
-
|
|
4
|
+
`${methodName} must be called with a scope. If you don't want to pass a scope, invoke with: ${methodName}.call(null)`
|
|
5
5
|
);
|
|
6
6
|
}
|
|
7
7
|
}
|
package/src/misc/explore.ori
CHANGED
|
@@ -4,8 +4,8 @@
|
|
|
4
4
|
<meta charset="utf-8" />
|
|
5
5
|
<meta name="viewport" content="width=device-width,initial-scale=1" />
|
|
6
6
|
<title>Web Origami Explorer</title>
|
|
7
|
-
<style
|
|
8
|
-
<script
|
|
7
|
+
<style>${ explore.css }</style>
|
|
8
|
+
<script>${ explore.js.inline }</script>
|
|
9
9
|
</head>
|
|
10
10
|
<body>
|
|
11
11
|
<nav>
|
|
@@ -18,10 +18,10 @@
|
|
|
18
18
|
</div>
|
|
19
19
|
{{ @map(=`
|
|
20
20
|
<ul>
|
|
21
|
-
<h2
|
|
21
|
+
<h2>${ _/name }</h2>
|
|
22
22
|
{{ @map(=`
|
|
23
23
|
<li>
|
|
24
|
-
<a href="./!@explore
|
|
24
|
+
<a href="./!@explore/${ _ }" target="frame">${ _ }</a>
|
|
25
25
|
</li>
|
|
26
26
|
`)(_/keys) }}
|
|
27
27
|
</ul>
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { Tree } from "@weborigami/async-tree";
|
|
2
|
+
import { isTreelike } from "@weborigami/async-tree/src/Tree.js";
|
|
2
3
|
import assertScopeIsDefined from "./assertScopeIsDefined.js";
|
|
3
4
|
|
|
4
5
|
/**
|
|
@@ -16,26 +17,35 @@ import assertScopeIsDefined from "./assertScopeIsDefined.js";
|
|
|
16
17
|
* @param {AsyncTree|null} scope
|
|
17
18
|
* @param {IArguments} args
|
|
18
19
|
* @param {Treelike|undefined} treelike
|
|
20
|
+
* @param {string} methodName
|
|
19
21
|
* @returns {Promise<AsyncTree>}
|
|
20
22
|
*/
|
|
21
|
-
export default async function getTreeArgument(
|
|
23
|
+
export default async function getTreeArgument(
|
|
24
|
+
scope,
|
|
25
|
+
args,
|
|
26
|
+
treelike,
|
|
27
|
+
methodName
|
|
28
|
+
) {
|
|
22
29
|
assertScopeIsDefined(scope);
|
|
23
30
|
|
|
24
31
|
if (treelike !== undefined) {
|
|
25
|
-
|
|
32
|
+
if (isTreelike(treelike)) {
|
|
33
|
+
return Tree.from(treelike);
|
|
34
|
+
}
|
|
35
|
+
throw new Error(
|
|
36
|
+
`${methodName}: The first argument must be a tree, like an array, object, or files.`
|
|
37
|
+
);
|
|
26
38
|
}
|
|
27
39
|
|
|
28
40
|
if (args.length === 0) {
|
|
29
41
|
if (!scope) {
|
|
30
42
|
// Should never happen because assertScopeIsDefined throws an exception.
|
|
31
43
|
throw new Error(
|
|
32
|
-
|
|
44
|
+
`${methodName} was called with no tree argument and no scope.`
|
|
33
45
|
);
|
|
34
46
|
}
|
|
35
47
|
return scope.get("@current");
|
|
36
48
|
}
|
|
37
49
|
|
|
38
|
-
throw new Error(
|
|
39
|
-
"An Origami tree function was called with an initial argument, but its value is undefined."
|
|
40
|
-
);
|
|
50
|
+
throw new Error(`${methodName}: The first argument was undefined.`);
|
|
41
51
|
}
|
|
@@ -1,11 +1,7 @@
|
|
|
1
1
|
import { Tree, isPlainObject, isStringLike } from "@weborigami/async-tree";
|
|
2
2
|
import { extname } from "@weborigami/language";
|
|
3
|
-
import * as serialize from "
|
|
4
|
-
import {
|
|
5
|
-
hasNonPrintableCharacters,
|
|
6
|
-
keySymbol,
|
|
7
|
-
} from "../../common/utilities.js";
|
|
8
|
-
import getTreeArgument from "../../misc/getTreeArgument.js";
|
|
3
|
+
import * as serialize from "../common/serialize.js";
|
|
4
|
+
import { hasNonPrintableCharacters, keySymbol } from "../common/utilities.js";
|
|
9
5
|
|
|
10
6
|
/**
|
|
11
7
|
* Render a tree in DOT format.
|
|
@@ -19,7 +15,7 @@ import getTreeArgument from "../../misc/getTreeArgument.js";
|
|
|
19
15
|
* @param {PlainObject} [options]
|
|
20
16
|
*/
|
|
21
17
|
export default async function dot(treelike, options = {}) {
|
|
22
|
-
const tree =
|
|
18
|
+
const tree = Tree.from(treelike);
|
|
23
19
|
const rootLabel = tree[keySymbol] ?? "";
|
|
24
20
|
const treeArcs = await statements(tree, "", rootLabel, options);
|
|
25
21
|
return `digraph g {
|
|
@@ -158,7 +154,11 @@ async function statements(tree, nodePath, nodeLabel, options) {
|
|
|
158
154
|
for (const key in nodes) {
|
|
159
155
|
const node = nodes[key];
|
|
160
156
|
const icon = node.isError ? "⚠️ " : "";
|
|
161
|
-
|
|
157
|
+
// GraphViz has trouble rendering DOT nodes whose labels contain ellipsis
|
|
158
|
+
// characters, so we map those to three periods. GraphViz appears to turn
|
|
159
|
+
// those back into ellipsis characters when rendering the graph.
|
|
160
|
+
const text = node.label.replace(/…/g, "...");
|
|
161
|
+
const label = `label="${icon}${text}"`;
|
|
162
162
|
const color = node.isError ? `; color="red"` : "";
|
|
163
163
|
const fill = node.isError ? `; fillcolor="#FFF4F4"` : "";
|
|
164
164
|
const destPath = nodePath ? `${nodePath}/${key}` : key;
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
import {
|
|
2
|
+
SiteTree,
|
|
3
|
+
Tree,
|
|
4
|
+
isPlainObject,
|
|
5
|
+
isStringLike,
|
|
6
|
+
} from "@weborigami/async-tree";
|
|
7
|
+
import { extname } from "@weborigami/language";
|
|
8
|
+
import * as serialize from "../common/serialize.js";
|
|
9
|
+
import { toString } from "../common/utilities.js";
|
|
10
|
+
import { mediaTypeForExtension } from "./mediaTypes.js";
|
|
11
|
+
|
|
12
|
+
const TypedArray = Object.getPrototypeOf(Uint8Array);
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Given a resource that was returned from a route, construct an appropriate
|
|
16
|
+
* HTTP Response indicating what should be sent to the client. Return null
|
|
17
|
+
* if the resource is not a valid response.
|
|
18
|
+
*
|
|
19
|
+
* @param {import("node:http").IncomingMessage} request
|
|
20
|
+
* @param {any} resource
|
|
21
|
+
* @returns {Promise<Response|null>}
|
|
22
|
+
*/
|
|
23
|
+
export default async function constructResponse(request, resource) {
|
|
24
|
+
if (resource instanceof Response) {
|
|
25
|
+
// Already a Response, return as is.
|
|
26
|
+
return resource;
|
|
27
|
+
} else if (!resource) {
|
|
28
|
+
return null;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
// Determine media type, what data we'll send, and encoding.
|
|
32
|
+
const url = new URL(request.url ?? "", `https://${request.headers.host}`);
|
|
33
|
+
const extension = extname(url.pathname).toLowerCase();
|
|
34
|
+
let mediaType = extension ? mediaTypeForExtension[extension] : undefined;
|
|
35
|
+
|
|
36
|
+
if (
|
|
37
|
+
mediaType === undefined &&
|
|
38
|
+
!url.pathname.endsWith("/") &&
|
|
39
|
+
(Tree.isAsyncTree(resource) ||
|
|
40
|
+
isPlainObject(resource) ||
|
|
41
|
+
resource instanceof Array)
|
|
42
|
+
) {
|
|
43
|
+
// Redirect to an index page for the result.
|
|
44
|
+
const Location = `${request.url}/`;
|
|
45
|
+
return new Response("ok", {
|
|
46
|
+
headers: {
|
|
47
|
+
Location,
|
|
48
|
+
},
|
|
49
|
+
status: 307,
|
|
50
|
+
});
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
// If the request is for a JSON or YAML result, and the resource we got
|
|
54
|
+
// isn't yet a string or Buffer, convert the resource to JSON or YAML now.
|
|
55
|
+
if (
|
|
56
|
+
(mediaType === "application/json" || mediaType === "text/yaml") &&
|
|
57
|
+
!isStringLike(resource)
|
|
58
|
+
) {
|
|
59
|
+
const tree = Tree.from(resource);
|
|
60
|
+
resource =
|
|
61
|
+
mediaType === "text/yaml"
|
|
62
|
+
? await serialize.toYaml(tree)
|
|
63
|
+
: await serialize.toJson(tree);
|
|
64
|
+
} else if (
|
|
65
|
+
mediaType === undefined &&
|
|
66
|
+
(isPlainObject(resource) || resource instanceof Array)
|
|
67
|
+
) {
|
|
68
|
+
// The resource is data, try showing it as YAML.
|
|
69
|
+
const tree = Tree.from(resource);
|
|
70
|
+
resource = await serialize.toYaml(tree);
|
|
71
|
+
mediaType = "text/yaml";
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
let body;
|
|
75
|
+
if (mediaType) {
|
|
76
|
+
body = SiteTree.mediaTypeIsText(mediaType) ? toString(resource) : resource;
|
|
77
|
+
} else {
|
|
78
|
+
body = textOrObject(resource);
|
|
79
|
+
// Infer media type.
|
|
80
|
+
mediaType =
|
|
81
|
+
typeof body !== "string"
|
|
82
|
+
? "application/octet-stream"
|
|
83
|
+
: body.trimStart().startsWith("<")
|
|
84
|
+
? "text/html"
|
|
85
|
+
: "text/plain";
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
// Assume text is encoded in UTF-8.
|
|
89
|
+
if (SiteTree.mediaTypeIsText(mediaType)) {
|
|
90
|
+
mediaType += "; charset=utf-8";
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
// If we didn't get back some kind of data that response.write() accepts,
|
|
94
|
+
// assume it was an error.
|
|
95
|
+
const validResponse = typeof body === "string" || body instanceof TypedArray;
|
|
96
|
+
if (!validResponse) {
|
|
97
|
+
const typeName = body?.constructor?.name ?? typeof body;
|
|
98
|
+
console.error(
|
|
99
|
+
`A served tree must return a string or a TypedArray (such as a Buffer) but returned an instance of ${typeName}.`
|
|
100
|
+
);
|
|
101
|
+
return null;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
return new Response(body, {
|
|
105
|
+
headers: {
|
|
106
|
+
"Content-Type": mediaType,
|
|
107
|
+
},
|
|
108
|
+
});
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* Convert to a string if we can, but leave objects that convert to something
|
|
113
|
+
* like "[object Object]" alone.
|
|
114
|
+
*
|
|
115
|
+
* @param {any} object
|
|
116
|
+
*/
|
|
117
|
+
function textOrObject(object) {
|
|
118
|
+
if (object instanceof ArrayBuffer) {
|
|
119
|
+
// Convert to Buffer.
|
|
120
|
+
return Buffer.from(object);
|
|
121
|
+
} else if (object instanceof TypedArray) {
|
|
122
|
+
// Return typed arrays as is.
|
|
123
|
+
return object;
|
|
124
|
+
}
|
|
125
|
+
return toString(object);
|
|
126
|
+
}
|
package/src/server/mediaTypes.js
CHANGED
|
@@ -79,19 +79,3 @@ export const mediaTypeForExtension = {
|
|
|
79
79
|
".yaml": "text/yaml", // Not official
|
|
80
80
|
".zip": "application/zip",
|
|
81
81
|
};
|
|
82
|
-
|
|
83
|
-
export const mediaTypeIsText = {
|
|
84
|
-
"application/json": true,
|
|
85
|
-
"application/ld+json": true,
|
|
86
|
-
"application/vnd.oasis.opendocument.text": true,
|
|
87
|
-
"application/x-httpd-php": true,
|
|
88
|
-
"application/x-sh": true,
|
|
89
|
-
"application/xhtml+xml": true,
|
|
90
|
-
"text/css": true,
|
|
91
|
-
"text/csv": true,
|
|
92
|
-
"text/html": true,
|
|
93
|
-
"text/javascript": true,
|
|
94
|
-
"text/plain": true,
|
|
95
|
-
"text/yaml": true,
|
|
96
|
-
"text/yml": true,
|
|
97
|
-
};
|