@weborigami/origami 0.6.0 → 0.6.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/package.json +4 -4
- package/src/common/serialize.js +4 -10
- package/src/dev/copy.js +71 -55
- package/src/dev/crawler/audit.js +3 -7
- package/src/dev/crawler/crawl.js +4 -8
- package/src/dev/crawler/getSiteArgument.js +12 -0
- package/src/dev/help.yaml +15 -0
- package/src/origami/htmlEscape.js +14 -0
- package/src/origami/inline.js +2 -1
- package/src/origami/jsonKeys.js +3 -1
- package/src/origami/ori.js +11 -0
- package/src/origami/origami.js +1 -0
- package/src/origami/sitemap.js +1 -1
- package/src/origami/unpack.js +4 -1
- package/src/server/server.js +2 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@weborigami/origami",
|
|
3
|
-
"version": "0.6.
|
|
3
|
+
"version": "0.6.2",
|
|
4
4
|
"description": "Web Origami language, CLI, framework, and server",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"repository": {
|
|
@@ -17,9 +17,9 @@
|
|
|
17
17
|
"typescript": "5.9.3"
|
|
18
18
|
},
|
|
19
19
|
"dependencies": {
|
|
20
|
-
"@weborigami/async-tree": "0.6.
|
|
21
|
-
"@weborigami/json-feed-to-rss": "1.0.
|
|
22
|
-
"@weborigami/language": "0.6.
|
|
20
|
+
"@weborigami/async-tree": "0.6.2",
|
|
21
|
+
"@weborigami/json-feed-to-rss": "1.0.1",
|
|
22
|
+
"@weborigami/language": "0.6.2",
|
|
23
23
|
"css-tree": "3.1.0",
|
|
24
24
|
"graphviz-wasm": "3.0.2",
|
|
25
25
|
"highlight.js": "11.11.1",
|
package/src/common/serialize.js
CHANGED
|
@@ -5,12 +5,7 @@
|
|
|
5
5
|
* @typedef {import("@weborigami/async-tree").SyncOrAsyncMap} SyncOrAsyncMap
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
|
-
import {
|
|
9
|
-
castArraylike,
|
|
10
|
-
SyncMap,
|
|
11
|
-
toPlainValue,
|
|
12
|
-
trailingSlash,
|
|
13
|
-
} from "@weborigami/async-tree";
|
|
8
|
+
import { castArraylike, toPlainValue } from "@weborigami/async-tree";
|
|
14
9
|
import * as YAMLModule from "yaml";
|
|
15
10
|
|
|
16
11
|
// The "yaml" package doesn't seem to provide a default export that the browser can
|
|
@@ -26,10 +21,9 @@ export function parseYaml(text) {
|
|
|
26
21
|
return YAML.parse(text);
|
|
27
22
|
}
|
|
28
23
|
|
|
29
|
-
function reduceToMap(
|
|
30
|
-
//
|
|
31
|
-
|
|
32
|
-
return castArraylike(keys, values, (entries) => new SyncMap(entries));
|
|
24
|
+
function reduceToMap(map) {
|
|
25
|
+
// createFn parameter returns as map as is
|
|
26
|
+
return castArraylike(map, (result) => result);
|
|
33
27
|
}
|
|
34
28
|
|
|
35
29
|
/**
|
package/src/dev/copy.js
CHANGED
|
@@ -1,7 +1,11 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import {
|
|
2
|
+
AsyncMap,
|
|
3
|
+
getTreeArgument,
|
|
4
|
+
SyncMap,
|
|
5
|
+
Tree,
|
|
6
|
+
} from "@weborigami/async-tree";
|
|
2
7
|
import { formatError } from "@weborigami/language";
|
|
3
8
|
import process, { stdout } from "node:process";
|
|
4
|
-
import { transformObject } from "../common/utilities.js";
|
|
5
9
|
|
|
6
10
|
/**
|
|
7
11
|
* @typedef {import("@weborigami/async-tree").Maplike} Maplike
|
|
@@ -13,75 +17,87 @@ export default async function copy(source, target) {
|
|
|
13
17
|
const sourceTree = await getTreeArgument(source, "copy", { position: 0 });
|
|
14
18
|
let targetTree = await getTreeArgument(target, "copy", { position: 1 });
|
|
15
19
|
|
|
20
|
+
let progressTree;
|
|
16
21
|
if (stdout.isTTY) {
|
|
17
|
-
targetTree
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
countCopied = 0;
|
|
22
|
+
progressTree = showSetProgress(targetTree, {
|
|
23
|
+
copied: 0,
|
|
24
|
+
total: 0,
|
|
25
|
+
});
|
|
26
|
+
} else {
|
|
27
|
+
progressTree = targetTree;
|
|
24
28
|
}
|
|
25
29
|
|
|
26
|
-
await Tree.assign(
|
|
30
|
+
await Tree.assign(progressTree, sourceTree);
|
|
27
31
|
|
|
28
32
|
if (stdout.isTTY) {
|
|
29
33
|
process.stdout.clearLine(0);
|
|
30
34
|
process.stdout.cursorTo(0);
|
|
31
|
-
copyRoot = null;
|
|
32
|
-
countFiles = null;
|
|
33
|
-
countCopied = null;
|
|
34
35
|
}
|
|
35
36
|
}
|
|
36
37
|
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
38
|
+
// Wrap the source tree to show progress on set() operations. Handle both sync
|
|
39
|
+
// and async trees. All child trees will share the same counts object.
|
|
40
|
+
function showSetProgress(source, counts) {
|
|
41
|
+
function showProgress() {
|
|
42
|
+
process.stdout.clearLine(0);
|
|
43
|
+
process.stdout.cursorTo(0);
|
|
44
|
+
process.stdout.write(`Copied ${counts.copied} of ${counts.total}`);
|
|
45
|
+
}
|
|
40
46
|
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
countFiles++;
|
|
45
|
-
copyRoot.showProgress();
|
|
46
|
-
let result;
|
|
47
|
-
try {
|
|
48
|
-
result = await super.set(...args);
|
|
49
|
-
countCopied++;
|
|
50
|
-
} catch (/** @type {any} */ error) {
|
|
51
|
-
console.error(formatError(error));
|
|
52
|
-
}
|
|
53
|
-
copyRoot.showProgress();
|
|
54
|
-
return result;
|
|
55
|
-
}
|
|
47
|
+
const isSync = source instanceof Map;
|
|
48
|
+
const MapClass = isSync ? SyncMap : AsyncMap;
|
|
49
|
+
const iteratorKey = isSync ? Symbol.iterator : Symbol.asyncIterator;
|
|
56
50
|
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
51
|
+
const progressTree = Object.assign(new MapClass(), {
|
|
52
|
+
delete: source.delete.bind(source),
|
|
53
|
+
keys: source.keys.bind(source),
|
|
54
|
+
[iteratorKey]: source[iteratorKey].bind(source),
|
|
55
|
+
|
|
56
|
+
// Wrap get() to apply progress tracking
|
|
57
|
+
get(key) {
|
|
58
|
+
return awaitIfPromise(source.get(key), (value) => {
|
|
59
|
+
return Tree.isMap(value) ? showSetProgress(value, counts) : value;
|
|
60
|
+
});
|
|
61
|
+
},
|
|
64
62
|
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
copyRoot.showProgress();
|
|
70
|
-
let result;
|
|
63
|
+
// Wrap set() to show progress
|
|
64
|
+
set(key, value) {
|
|
65
|
+
counts.total++;
|
|
66
|
+
showProgress();
|
|
71
67
|
try {
|
|
72
|
-
|
|
73
|
-
|
|
68
|
+
const setResult = source.set(key, value);
|
|
69
|
+
return awaitIfPromise(setResult, () => {
|
|
70
|
+
counts.copied++;
|
|
71
|
+
showProgress();
|
|
72
|
+
return progressTree;
|
|
73
|
+
});
|
|
74
74
|
} catch (/** @type {any} */ error) {
|
|
75
75
|
console.error(formatError(error));
|
|
76
|
+
return progressTree;
|
|
76
77
|
}
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
78
|
+
},
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
if (typeof source.child === "function") {
|
|
82
|
+
// @ts-ignore
|
|
83
|
+
progressTree.child = async function (key) {
|
|
84
|
+
counts.total++;
|
|
85
|
+
showProgress();
|
|
86
|
+
const childResult = source.child(key);
|
|
87
|
+
return awaitIfPromise(childResult, (child) => {
|
|
88
|
+
counts.copied++;
|
|
89
|
+
showProgress();
|
|
90
|
+
return showSetProgress(child, counts);
|
|
91
|
+
});
|
|
92
|
+
};
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
return progressTree;
|
|
96
|
+
}
|
|
80
97
|
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
};
|
|
98
|
+
// Helper function that awaits a value if it's a Promise, then gives it to the
|
|
99
|
+
// function; otherwise calls the function directly. This helps us write code
|
|
100
|
+
// that can handle both sync and async values.
|
|
101
|
+
function awaitIfPromise(value, fn) {
|
|
102
|
+
return value instanceof Promise ? value.then(fn) : fn(value);
|
|
87
103
|
}
|
package/src/dev/crawler/audit.js
CHANGED
|
@@ -1,10 +1,6 @@
|
|
|
1
|
-
import {
|
|
2
|
-
getTreeArgument,
|
|
3
|
-
pathFromKeys,
|
|
4
|
-
symbols,
|
|
5
|
-
Tree,
|
|
6
|
-
} from "@weborigami/async-tree";
|
|
1
|
+
import { pathFromKeys, symbols, Tree } from "@weborigami/async-tree";
|
|
7
2
|
import crawlResources from "./crawlResources.js";
|
|
3
|
+
import getSiteArgument from "./getSiteArgument.js";
|
|
8
4
|
import { getBaseUrl } from "./utilities.js";
|
|
9
5
|
|
|
10
6
|
/**
|
|
@@ -17,7 +13,7 @@ import { getBaseUrl } from "./utilities.js";
|
|
|
17
13
|
* @param {string} [baseHref]
|
|
18
14
|
*/
|
|
19
15
|
export default async function audit(maplike, baseHref) {
|
|
20
|
-
const tree = await
|
|
16
|
+
const tree = await getSiteArgument(maplike, "audit");
|
|
21
17
|
const baseUrl = getBaseUrl(baseHref, maplike);
|
|
22
18
|
|
|
23
19
|
let errors = {};
|
package/src/dev/crawler/crawl.js
CHANGED
|
@@ -1,10 +1,6 @@
|
|
|
1
|
-
import {
|
|
2
|
-
DeepObjectMap,
|
|
3
|
-
Tree,
|
|
4
|
-
getTreeArgument,
|
|
5
|
-
keysFromPath,
|
|
6
|
-
} from "@weborigami/async-tree";
|
|
1
|
+
import { ObjectMap, Tree, keysFromPath } from "@weborigami/async-tree";
|
|
7
2
|
import crawlResources from "./crawlResources.js";
|
|
3
|
+
import getSiteArgument from "./getSiteArgument.js";
|
|
8
4
|
import { addValueToObject, getBaseUrl } from "./utilities.js";
|
|
9
5
|
|
|
10
6
|
/**
|
|
@@ -23,7 +19,7 @@ import { addValueToObject, getBaseUrl } from "./utilities.js";
|
|
|
23
19
|
* @returns {Promise<AsyncMap>}
|
|
24
20
|
*/
|
|
25
21
|
export default async function crawlBuiltin(maplike, baseHref) {
|
|
26
|
-
const tree = await
|
|
22
|
+
const tree = await getSiteArgument(maplike, "crawl");
|
|
27
23
|
const baseUrl = getBaseUrl(baseHref, maplike);
|
|
28
24
|
|
|
29
25
|
const cache = {};
|
|
@@ -53,7 +49,7 @@ export default async function crawlBuiltin(maplike, baseHref) {
|
|
|
53
49
|
// for something already, that's better than a function that will get that
|
|
54
50
|
// value.
|
|
55
51
|
const result = Tree.deepMerge(
|
|
56
|
-
new
|
|
52
|
+
new ObjectMap(cache, { deep: true }),
|
|
57
53
|
await Tree.invokeFunctions(resources)
|
|
58
54
|
);
|
|
59
55
|
return result;
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { getTreeArgument, SiteMap } from "@weborigami/async-tree";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Return a site: if it's a tree, return the tree; if it's a string, create a
|
|
5
|
+
* tree for that URL.
|
|
6
|
+
*/
|
|
7
|
+
export default async function getSiteArgument(site, command) {
|
|
8
|
+
if (typeof site === "string") {
|
|
9
|
+
return new SiteMap(site);
|
|
10
|
+
}
|
|
11
|
+
return getTreeArgument(site, command);
|
|
12
|
+
}
|
package/src/dev/help.yaml
CHANGED
|
@@ -68,6 +68,9 @@ Origami:
|
|
|
68
68
|
fetch:
|
|
69
69
|
args: (url, options)
|
|
70
70
|
description: Fetch a resource from a URL with support for extensions
|
|
71
|
+
htmlEscape:
|
|
72
|
+
args: (text)
|
|
73
|
+
description: Escape HTML entities in the text
|
|
71
74
|
image:
|
|
72
75
|
description: Collection of functions for working with images
|
|
73
76
|
indexPage:
|
|
@@ -178,6 +181,9 @@ Tree:
|
|
|
178
181
|
calendar:
|
|
179
182
|
args: (options)
|
|
180
183
|
description: Return a tree structure for years/months/days
|
|
184
|
+
child:
|
|
185
|
+
args: (tree, key)
|
|
186
|
+
description: Returns the indicated child node, creating it if necessary
|
|
181
187
|
clear:
|
|
182
188
|
args: (map)
|
|
183
189
|
description: Remove all values from the map
|
|
@@ -235,6 +241,9 @@ Tree:
|
|
|
235
241
|
inners:
|
|
236
242
|
args: (tree)
|
|
237
243
|
description: The tree's interior nodes
|
|
244
|
+
invokeFunctions:
|
|
245
|
+
args: (map)
|
|
246
|
+
description: Getting a map value invokes it if it's a function
|
|
238
247
|
isMap:
|
|
239
248
|
args: (object)
|
|
240
249
|
description: True if object is a map
|
|
@@ -283,6 +292,9 @@ Tree:
|
|
|
283
292
|
plain:
|
|
284
293
|
args: (tree)
|
|
285
294
|
description: Render the tree as a plain JavaScript object
|
|
295
|
+
reduce:
|
|
296
|
+
args: (tree, reduceFn)
|
|
297
|
+
description: Reduce the tree to a single value
|
|
286
298
|
regExpKeys:
|
|
287
299
|
args: (tree)
|
|
288
300
|
description: A tree whose keys are regular expression strings
|
|
@@ -295,6 +307,9 @@ Tree:
|
|
|
295
307
|
scope:
|
|
296
308
|
args: (tree)
|
|
297
309
|
description: A merged view of the tree and its ancestors
|
|
310
|
+
set:
|
|
311
|
+
args: (map, key, value)
|
|
312
|
+
description: Set the value for the key in the map
|
|
298
313
|
shuffle:
|
|
299
314
|
args: (map)
|
|
300
315
|
description: Shuffle the keys of the map
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { toString } from "@weborigami/async-tree";
|
|
2
|
+
import loadJsDom from "../common/loadJsDom.js";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Escapes HTML entities in a string.
|
|
6
|
+
*
|
|
7
|
+
* @param {import("@weborigami/async-tree").Stringlike} html
|
|
8
|
+
*/
|
|
9
|
+
export default async function htmlEscape(html) {
|
|
10
|
+
const { JSDOM } = await loadJsDom();
|
|
11
|
+
const div = JSDOM.fragment("<div></div>").firstChild;
|
|
12
|
+
div.textContent = toString(html);
|
|
13
|
+
return div.innerHTML;
|
|
14
|
+
}
|
package/src/origami/inline.js
CHANGED
|
@@ -9,7 +9,7 @@ import documentObject from "../common/documentObject.js";
|
|
|
9
9
|
*
|
|
10
10
|
* @param {any} input
|
|
11
11
|
*/
|
|
12
|
-
export default async function inline(input) {
|
|
12
|
+
export default async function inline(input, options = {}) {
|
|
13
13
|
// Get the input text and any attached front matter.
|
|
14
14
|
if (isUnpackable(input)) {
|
|
15
15
|
input = await input.unpack();
|
|
@@ -21,6 +21,7 @@ export default async function inline(input) {
|
|
|
21
21
|
}
|
|
22
22
|
|
|
23
23
|
const parent =
|
|
24
|
+
options.parent ??
|
|
24
25
|
/** @type {any} */ (input).parent ??
|
|
25
26
|
/** @type {any} */ (input)[symbols.parent];
|
|
26
27
|
|
package/src/origami/jsonKeys.js
CHANGED
package/src/origami/ori.js
CHANGED
|
@@ -48,6 +48,17 @@ export default async function ori(expression, options = {}) {
|
|
|
48
48
|
// Execute
|
|
49
49
|
let result = await fn();
|
|
50
50
|
|
|
51
|
+
// if (result === undefined) {
|
|
52
|
+
// // Was the code a path traversal?
|
|
53
|
+
// const wasTraversal =
|
|
54
|
+
// fn.code[0] === ops.unpack ||
|
|
55
|
+
// (fn.code[0] instanceof Array && fn.code[0][0] === ops.scope);
|
|
56
|
+
// if (wasTraversal) {
|
|
57
|
+
// // Yes, probably an error
|
|
58
|
+
// console.warn(`ori: warning: undefined ${highlightError(expression)}`);
|
|
59
|
+
// }
|
|
60
|
+
// }
|
|
61
|
+
|
|
51
62
|
// If result was a function, execute it.
|
|
52
63
|
if (typeof result === "function") {
|
|
53
64
|
result = await result();
|
package/src/origami/origami.js
CHANGED
|
@@ -14,6 +14,7 @@ export { default as static } from "../origami/static.js";
|
|
|
14
14
|
export { default as basename } from "./basename.js";
|
|
15
15
|
export { default as csv } from "./csv.js";
|
|
16
16
|
export { default as fetch } from "./fetch.js";
|
|
17
|
+
export { default as htmlEscape } from "./htmlEscape.js";
|
|
17
18
|
export { default as format } from "./image/format.js";
|
|
18
19
|
export * as image from "./image/image.js";
|
|
19
20
|
export { default as resize } from "./image/resize.js";
|
package/src/origami/sitemap.js
CHANGED
|
@@ -14,7 +14,7 @@ const templateText = `(urls) => \`<?xml version="1.0" encoding="UTF-8"?>
|
|
|
14
14
|
* @typedef {import("@weborigami/async-tree").Maplike} Maplike
|
|
15
15
|
*
|
|
16
16
|
* @param {Maplike} maplike
|
|
17
|
-
* @param {{
|
|
17
|
+
* @param {{ base?: string }} options
|
|
18
18
|
* @returns {Promise<string>}
|
|
19
19
|
*/
|
|
20
20
|
export default async function sitemap(maplike, options = {}) {
|
package/src/origami/unpack.js
CHANGED
package/src/server/server.js
CHANGED
|
@@ -121,6 +121,8 @@ export function requestListener(maplike) {
|
|
|
121
121
|
*/
|
|
122
122
|
function respondWithError(response, error) {
|
|
123
123
|
let message = formatError(error);
|
|
124
|
+
// Remove ANSI escape codes from the message.
|
|
125
|
+
message = message.replace(/\x1b\[[0-9;]*m/g, "");
|
|
124
126
|
// Prevent HTML in the error message from being interpreted as HTML.
|
|
125
127
|
message = message.replace(/</g, "<").replace(/>/g, ">");
|
|
126
128
|
const html = `<!DOCTYPE html>
|