@weborigami/origami 0.6.9 → 0.6.11

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.
Files changed (43) hide show
  1. package/package.json +3 -3
  2. package/src/cli/cli.js +1 -1
  3. package/src/dev/changes.js +14 -8
  4. package/src/dev/copy.js +8 -19
  5. package/src/dev/crawler/crawlResources.js +3 -3
  6. package/src/dev/crawler/getSiteArgument.js +2 -2
  7. package/src/dev/debug.js +2 -2
  8. package/src/dev/explore.js +2 -2
  9. package/src/dev/serve.js +9 -5
  10. package/src/dev/svg.js +2 -2
  11. package/src/dev/treeDot.js +6 -6
  12. package/src/dev/watch.js +8 -5
  13. package/src/origami/basename.js +2 -1
  14. package/src/origami/csv.js +3 -2
  15. package/src/origami/document.js +12 -3
  16. package/src/origami/fetch.js +3 -1
  17. package/src/origami/htmlDom.js +7 -0
  18. package/src/origami/htmlEscape.js +3 -2
  19. package/src/origami/image/format.js +6 -1
  20. package/src/origami/image/resize.js +6 -1
  21. package/src/origami/indexPage.js +7 -3
  22. package/src/origami/inline.js +4 -3
  23. package/src/origami/json.js +1 -7
  24. package/src/origami/jsonKeys.js +2 -7
  25. package/src/origami/jsonParse.js +7 -2
  26. package/src/origami/mdHtml.js +6 -4
  27. package/src/origami/mdOutline.js +3 -1
  28. package/src/origami/once.js +3 -1
  29. package/src/origami/ori.js +11 -14
  30. package/src/origami/origami.js +2 -2
  31. package/src/origami/pack.js +1 -0
  32. package/src/origami/post.js +3 -1
  33. package/src/origami/redirect.js +9 -0
  34. package/src/origami/repeat.js +11 -2
  35. package/src/origami/rss.js +2 -2
  36. package/src/origami/shell.js +9 -2
  37. package/src/origami/sitemap.js +3 -3
  38. package/src/origami/slug.js +12 -2
  39. package/src/origami/static.js +2 -7
  40. package/src/origami/unpack.js +3 -1
  41. package/src/origami/yaml.js +1 -7
  42. package/src/origami/yamlParse.js +7 -2
  43. package/src/server/server.js +2 -2
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@weborigami/origami",
3
- "version": "0.6.9",
3
+ "version": "0.6.11",
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.9",
20
+ "@weborigami/async-tree": "0.6.11",
21
21
  "@weborigami/json-feed-to-rss": "1.0.1",
22
- "@weborigami/language": "0.6.9",
22
+ "@weborigami/language": "0.6.11",
23
23
  "css-tree": "3.1.0",
24
24
  "graphviz-wasm": "3.0.2",
25
25
  "highlight.js": "11.11.1",
package/src/cli/cli.js CHANGED
@@ -61,6 +61,6 @@ while (args[0] === "") {
61
61
  try {
62
62
  await main(...args);
63
63
  } catch (/** @type {any} */ error) {
64
- console.error(formatError(error));
64
+ console.error(await formatError(error));
65
65
  process.exitCode = 1;
66
66
  }
@@ -1,4 +1,10 @@
1
- import { getTreeArgument, trailingSlash, Tree } from "@weborigami/async-tree";
1
+ import {
2
+ args,
3
+ isStringlike,
4
+ toString,
5
+ trailingSlash,
6
+ Tree,
7
+ } from "@weborigami/async-tree";
2
8
 
3
9
  /**
4
10
  * Given an old tree and a new tree, return a tree of changes indicated
@@ -10,13 +16,13 @@ import { getTreeArgument, trailingSlash, Tree } from "@weborigami/async-tree";
10
16
  * @param {Maplike} newMaplike
11
17
  */
12
18
  export default async function changes(oldMaplike, newMaplike) {
13
- const oldTree = await getTreeArgument(oldMaplike, "changes", {
19
+ const oldTree = await args.map(oldMaplike, "Dev.changes", {
14
20
  deep: true,
15
- position: 0,
21
+ position: 1,
16
22
  });
17
- const newTree = await getTreeArgument(newMaplike, "changes", {
23
+ const newTree = await args.map(newMaplike, "Dev.changes", {
18
24
  deep: true,
19
- position: 1,
25
+ position: 2,
20
26
  });
21
27
 
22
28
  const oldKeys = await Tree.keys(oldTree);
@@ -44,9 +50,9 @@ export default async function changes(oldMaplike, newMaplike) {
44
50
  result ??= {};
45
51
  result[oldKey] = treeChanges;
46
52
  }
47
- } else if (oldValue?.toString && newValue?.toString) {
48
- const oldText = oldValue.toString();
49
- const newText = newValue.toString();
53
+ } else if (isStringlike(oldValue) && isStringlike(newValue)) {
54
+ const oldText = toString(oldValue);
55
+ const newText = toString(newValue);
50
56
  if (oldText !== newText) {
51
57
  result ??= {};
52
58
  result[oldKey] = "changed";
package/src/dev/copy.js CHANGED
@@ -1,10 +1,4 @@
1
- import {
2
- AsyncMap,
3
- getTreeArgument,
4
- SyncMap,
5
- Tree,
6
- } from "@weborigami/async-tree";
7
- import { formatError } from "@weborigami/language";
1
+ import { args, AsyncMap, SyncMap, Tree } from "@weborigami/async-tree";
8
2
  import process, { stdout } from "node:process";
9
3
 
10
4
  /**
@@ -14,8 +8,8 @@ import process, { stdout } from "node:process";
14
8
  * @param {Maplike} target
15
9
  */
16
10
  export default async function copy(source, target) {
17
- const sourceTree = await getTreeArgument(source, "copy", { position: 0 });
18
- let targetTree = await getTreeArgument(target, "copy", { position: 1 });
11
+ const sourceTree = await args.map(source, "Dev.copy", { position: 1 });
12
+ let targetTree = await args.map(target, "Dev.copy", { position: 2 });
19
13
 
20
14
  let progressTree;
21
15
  if (stdout.isTTY) {
@@ -64,17 +58,12 @@ function showSetProgress(source, counts) {
64
58
  set(key, value) {
65
59
  counts.total++;
66
60
  showProgress();
67
- try {
68
- const setResult = source.set(key, value);
69
- return awaitIfPromise(setResult, () => {
70
- counts.copied++;
71
- showProgress();
72
- return progressTree;
73
- });
74
- } catch (/** @type {any} */ error) {
75
- console.error(formatError(error));
61
+ const setResult = source.set(key, value);
62
+ return awaitIfPromise(setResult, () => {
63
+ counts.copied++;
64
+ showProgress();
76
65
  return progressTree;
77
- }
66
+ });
78
67
  },
79
68
  });
80
69
 
@@ -37,7 +37,7 @@ export default async function* crawlResources(tree, baseUrl) {
37
37
  while (true) {
38
38
  // Get the latest array of promises that haven't been resolved yet.
39
39
  const promises = Object.values(promisesForPaths).filter(
40
- (promise) => promise !== null
40
+ (promise) => promise !== null,
41
41
  );
42
42
 
43
43
  if (promises.length === 0) {
@@ -136,7 +136,7 @@ async function processPath(tree, path, baseUrl) {
136
136
  value = await Tree.traverse(tree, ...keys);
137
137
  normalizedKeys = keys.slice();
138
138
  normalizedPath = path;
139
- if (Tree.isMaplike(value)) {
139
+ if (value && Tree.isMaplike(value)) {
140
140
  // Path is actually a directory. See if we can get the empty string or
141
141
  // "index.html".
142
142
  value =
@@ -196,7 +196,7 @@ async function processPath(tree, path, baseUrl) {
196
196
  value,
197
197
  key,
198
198
  baseUrl,
199
- normalizedPath
199
+ normalizedPath,
200
200
  );
201
201
 
202
202
  return {
@@ -1,4 +1,4 @@
1
- import { getTreeArgument, SiteMap } from "@weborigami/async-tree";
1
+ import { args, SiteMap } from "@weborigami/async-tree";
2
2
 
3
3
  /**
4
4
  * Return a site: if it's a tree, return the tree; if it's a string, create a
@@ -8,5 +8,5 @@ export default async function getSiteArgument(site, command) {
8
8
  if (typeof site === "string") {
9
9
  return new SiteMap(site);
10
10
  }
11
- return getTreeArgument(site, command);
11
+ return args.map(site, command);
12
12
  }
package/src/dev/debug.js CHANGED
@@ -1,4 +1,4 @@
1
- import { getTreeArgument, Tree } from "@weborigami/async-tree";
1
+ import { args, Tree } from "@weborigami/async-tree";
2
2
  import { isTransformApplied, transformObject } from "../common/utilities.js";
3
3
  import ExplorableSiteTransform from "./ExplorableSiteTransform.js";
4
4
  import OriCommandTransform from "./OriCommandTransform.js";
@@ -13,7 +13,7 @@ import OriCommandTransform from "./OriCommandTransform.js";
13
13
  * @returns {Promise<Map|AsyncMap>}
14
14
  */
15
15
  export default async function debug(maplike) {
16
- let tree = await getTreeArgument(maplike, "debug");
16
+ let tree = await args.map(maplike, "Dev.debug");
17
17
 
18
18
  if (!isTransformApplied(DebugTransform, tree)) {
19
19
  tree = transformObject(DebugTransform, tree);
@@ -1,4 +1,4 @@
1
- import { getTreeArgument, Tree } from "@weborigami/async-tree";
1
+ import { args, Tree } from "@weborigami/async-tree";
2
2
  import { Handlers, OrigamiFileMap } from "@weborigami/language";
3
3
  import path from "node:path";
4
4
  import { fileURLToPath } from "node:url";
@@ -11,7 +11,7 @@ let templatePromise;
11
11
  * Display a debug/explore page for the current tree.
12
12
  */
13
13
  export default async function explore(maplike) {
14
- const tree = await getTreeArgument(maplike, "explore");
14
+ const tree = await args.map(maplike, "Dev.explore");
15
15
 
16
16
  // Construct the template page
17
17
  const scope = await Tree.scope(tree);
package/src/dev/serve.js CHANGED
@@ -1,4 +1,4 @@
1
- import { getTreeArgument } from "@weborigami/async-tree";
1
+ import { args } from "@weborigami/async-tree";
2
2
  import http from "node:http";
3
3
  import { createServer } from "node:net";
4
4
  import process from "node:process";
@@ -17,7 +17,11 @@ const defaultPort = 5000;
17
17
  * @param {number} [port]
18
18
  */
19
19
  export default async function serve(maplike, port) {
20
- let tree = await getTreeArgument(maplike, "serve");
20
+ let tree = await args.map(maplike, "Dev.serve");
21
+ port =
22
+ port !== undefined
23
+ ? args.number(port, "Dev.serve", { position: 2 })
24
+ : undefined;
21
25
 
22
26
  if (!isTransformApplied(ExplorableSiteTransform, tree)) {
23
27
  tree = transformObject(ExplorableSiteTransform, tree);
@@ -36,7 +40,7 @@ export default async function serve(maplike, port) {
36
40
  // @ts-ignore
37
41
  http.createServer(requestListener(tree)).listen(port, undefined, () => {
38
42
  console.log(
39
- `Server running at http://localhost:${port}. Press Ctrl+C to stop.`
43
+ `Server running at http://localhost:${port}. Press Ctrl+C to stop.`,
40
44
  );
41
45
  });
42
46
  }
@@ -48,9 +52,9 @@ function findOpenPort(port) {
48
52
  return new Promise((resolve, reject) =>
49
53
  server
50
54
  .on("error", (/** @type {any} */ error) =>
51
- error.code === "EADDRINUSE" ? server.listen(++port) : reject(error)
55
+ error.code === "EADDRINUSE" ? server.listen(++port) : reject(error),
52
56
  )
53
57
  .on("listening", () => server.close(() => resolve(port)))
54
- .listen(port)
58
+ .listen(port),
55
59
  );
56
60
  }
package/src/dev/svg.js CHANGED
@@ -1,4 +1,4 @@
1
- import { getTreeArgument } from "@weborigami/async-tree";
1
+ import { args } from "@weborigami/async-tree";
2
2
  import graphviz from "graphviz-wasm";
3
3
 
4
4
  import dot from "./treeDot.js";
@@ -19,7 +19,7 @@ export default async function svg(maplike, options = {}) {
19
19
  await graphviz.loadWASM();
20
20
  graphvizLoaded = true;
21
21
  }
22
- const tree = await getTreeArgument(maplike, "svg", { deep: true });
22
+ const tree = await args.map(maplike, "Dev.svg", { deep: true });
23
23
  const dotText = await dot(tree, options);
24
24
  if (dotText === undefined) {
25
25
  return undefined;
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  Tree,
3
- getTreeArgument,
3
+ args,
4
4
  isPlainObject,
5
5
  isStringlike,
6
6
  toString,
@@ -19,7 +19,7 @@ import { getDescriptor } from "../common/utilities.js";
19
19
  * @param {PlainObject} [options]
20
20
  */
21
21
  export default async function dot(maplike, options = {}) {
22
- const tree = await getTreeArgument(maplike, "treeDot", { deep: true });
22
+ const tree = await args.map(maplike, "Dev.treeDot", { deep: true });
23
23
  const rootLabel = getDescriptor(tree) ?? "";
24
24
  const treeArcs = await statements(tree, "", rootLabel, options);
25
25
  return `digraph g {
@@ -43,7 +43,7 @@ async function statements(tree, nodePath, nodeLabel, options) {
43
43
  const url = createLinks ? `; URL="${rootUrl}"` : "";
44
44
  const rootLabel = nodeLabel ? `; xlabel="${nodeLabel}"` : "";
45
45
  result.push(
46
- ` "${nodePath}" [shape=circle${rootLabel}; label=""; color=gray40; width=0.15${url}];`
46
+ ` "${nodePath}" [shape=circle${rootLabel}; label=""; color=gray40; width=0.15${url}];`,
47
47
  );
48
48
 
49
49
  // Draw edges and collect labels for the nodes they lead to.
@@ -63,7 +63,7 @@ async function statements(tree, nodePath, nodeLabel, options) {
63
63
  value =
64
64
  error.name && error.message
65
65
  ? `${error.name}: ${error.message}`
66
- : error.name ?? error.message ?? error;
66
+ : (error.name ?? error.message ?? error);
67
67
  }
68
68
 
69
69
  const expandable =
@@ -76,8 +76,8 @@ async function statements(tree, nodePath, nodeLabel, options) {
76
76
  const label = isStringlike(value)
77
77
  ? toString(value)
78
78
  : value !== undefined
79
- ? await serialize.toYaml(value)
80
- : "";
79
+ ? await serialize.toYaml(value)
80
+ : "";
81
81
  if (value === undefined) {
82
82
  isError = true;
83
83
  }
package/src/dev/watch.js CHANGED
@@ -1,4 +1,4 @@
1
- import { ConstantMap, getTreeArgument, Tree } from "@weborigami/async-tree";
1
+ import { args, ConstantMap, Tree } from "@weborigami/async-tree";
2
2
  import { formatError, moduleCache } from "@weborigami/language";
3
3
 
4
4
  /**
@@ -13,7 +13,10 @@ import { formatError, moduleCache } from "@weborigami/language";
13
13
  * @returns {Promise<Map|AsyncMap>}
14
14
  */
15
15
  export default async function watch(maplike, fn) {
16
- const container = await getTreeArgument(maplike, "watch");
16
+ const container = await args.map(maplike, "Dev.watch");
17
+ const treeFn = fn
18
+ ? args.invocable(fn, "Dev.watch", { position: 2 })
19
+ : undefined;
17
20
 
18
21
  // Watch the indicated tree.
19
22
  /** @type {any} */ (container).watch?.();
@@ -23,7 +26,7 @@ export default async function watch(maplike, fn) {
23
26
  }
24
27
 
25
28
  // The caller supplied a function to reevaluate whenever the tree changes.
26
- let tree = await evaluateTree(container, fn);
29
+ let tree = await evaluateTree(container, treeFn);
27
30
 
28
31
  // We want to return a stable reference to the tree, so we'll use a prototype
29
32
  // chain that will always point to the latest tree. We'll extend the tree's
@@ -33,7 +36,7 @@ export default async function watch(maplike, fn) {
33
36
 
34
37
  // Reevaluate the function whenever the tree changes.
35
38
  /** @type {any} */ (container).addEventListener?.("change", async () => {
36
- const tree = await evaluateTree(container, fn);
39
+ const tree = await evaluateTree(container, treeFn);
37
40
  moduleCache.resetTimestamp();
38
41
  updateIndirectPointer(handle, tree);
39
42
  });
@@ -48,7 +51,7 @@ async function evaluateTree(parent, fn) {
48
51
  try {
49
52
  result = await fn();
50
53
  } catch (/** @type {any} */ error) {
51
- message = formatError(error);
54
+ message = await formatError(error);
52
55
  }
53
56
  tree = result ? Tree.from(result, { parent }) : undefined;
54
57
  if (tree) {
@@ -1,6 +1,7 @@
1
- import { extension } from "@weborigami/async-tree";
1
+ import { args, extension } from "@weborigami/async-tree";
2
2
 
3
3
  export default function basename(key) {
4
+ key = args.string(key, "Origami.basename");
4
5
  const ext = extension.extname(key);
5
6
  return ext ? key.slice(0, -ext.length) : key;
6
7
  }
@@ -3,8 +3,9 @@ import { isUnpackable, toPlainValue } from "@weborigami/async-tree";
3
3
  /**
4
4
  * Render the object as text in CSV format.
5
5
  *
6
- * The object should a maplike object such as an array. The output will include
7
- * a header row with field names taken from the first item in the tree/array.
6
+ * The object should be a maplike object such as an array. The output will
7
+ * include a header row with field names taken from the first item in the
8
+ * tree/array.
8
9
  *
9
10
  * @param {any} object
10
11
  */
@@ -1,11 +1,20 @@
1
+ import { args, isPlainObject, isUnpackable } from "@weborigami/async-tree";
1
2
  import documentObject from "../common/documentObject.js";
2
3
 
3
4
  /**
4
5
  * @typedef {import("@weborigami/async-tree").Stringlike} Stringlike
6
+ * @typedef {import("@weborigami/async-tree").PlainObject} PlainObject
5
7
  *
6
- * @param {Stringlike} text
8
+ * @param {Stringlike|PlainObject} input
7
9
  * @param {any} [data]
8
10
  */
9
- export default async function documentBuiltin(text, data) {
10
- return documentObject(text, data);
11
+ export default async function documentBuiltin(input, data) {
12
+ if (isUnpackable(input)) {
13
+ // Unpack the input first, might already be a document object.
14
+ input = await input.unpack();
15
+ }
16
+ input = isPlainObject(input)
17
+ ? input
18
+ : args.stringlike(input, "Origami.document");
19
+ return documentObject(input, data);
11
20
  }
@@ -1,3 +1,4 @@
1
+ import { args } from "@weborigami/async-tree";
1
2
  import { handleExtension } from "@weborigami/language";
2
3
 
3
4
  /**
@@ -5,6 +6,7 @@ import { handleExtension } from "@weborigami/language";
5
6
  * with an unpack() method if the resource has a known file extension.
6
7
  */
7
8
  export default async function fetchBuiltin(resource, options, state) {
9
+ resource = args.string(resource, "Origami.fetch");
8
10
  const response = await fetch(resource, options);
9
11
  if (!response.ok) {
10
12
  return undefined;
@@ -15,6 +17,6 @@ export default async function fetchBuiltin(resource, options, state) {
15
17
  const url = new URL(resource);
16
18
  const key = url.pathname;
17
19
 
18
- return handleExtension(value, key, state.container);
20
+ return handleExtension(value, key, state?.parent);
19
21
  }
20
22
  fetchBuiltin.needsState = true;
@@ -1,6 +1,13 @@
1
+ import { args } from "@weborigami/async-tree";
1
2
  import loadJsDom from "../common/loadJsDom.js";
2
3
 
4
+ /**
5
+ * Return the DOM for the given HTML string.
6
+ *
7
+ * @param {import("@weborigami/async-tree").Stringlike} html
8
+ */
3
9
  export default async function htmlDom(html) {
10
+ html = args.stringlike(html, "Origami.htmlDom");
4
11
  const { JSDOM } = await loadJsDom();
5
12
  const dom = JSDOM.fragment(html);
6
13
  return dom;
@@ -1,4 +1,4 @@
1
- import { toString } from "@weborigami/async-tree";
1
+ import { args, toString } from "@weborigami/async-tree";
2
2
  import loadJsDom from "../common/loadJsDom.js";
3
3
 
4
4
  /**
@@ -7,8 +7,9 @@ import loadJsDom from "../common/loadJsDom.js";
7
7
  * @param {import("@weborigami/async-tree").Stringlike} html
8
8
  */
9
9
  export default async function htmlEscape(html) {
10
+ const text = args.stringlike(html, "Origami.htmlEscape");
10
11
  const { JSDOM } = await loadJsDom();
11
12
  const div = JSDOM.fragment("<div></div>").firstChild;
12
- div.textContent = toString(html);
13
+ div.textContent = toString(text);
13
14
  return div.innerHTML;
14
15
  }
@@ -1,4 +1,4 @@
1
- import sharp from "sharp";
1
+ let sharp;
2
2
 
3
3
  /**
4
4
  * Return the image in a different format.
@@ -9,6 +9,11 @@ import sharp from "sharp";
9
9
  * @param {any} options
10
10
  */
11
11
  export default async function imageFormat(input, format, options) {
12
+ if (!sharp) {
13
+ // Dynamic import to avoid loading Sharp until needed
14
+ sharp = (await import("sharp")).default;
15
+ }
16
+
12
17
  if (!(input instanceof Uint8Array || input instanceof ArrayBuffer)) {
13
18
  return undefined;
14
19
  }
@@ -1,4 +1,4 @@
1
- import sharp from "sharp";
1
+ let sharp;
2
2
 
3
3
  /**
4
4
  * Resize an image.
@@ -7,6 +7,11 @@ import sharp from "sharp";
7
7
  * @param {import("sharp").ResizeOptions} options
8
8
  */
9
9
  export default async function resize(input, options) {
10
+ if (!sharp) {
11
+ // Dynamic import to avoid loading Sharp until needed
12
+ sharp = (await import("sharp")).default;
13
+ }
14
+
10
15
  if (!(input instanceof Uint8Array || input instanceof ArrayBuffer)) {
11
16
  return undefined;
12
17
  }
@@ -1,4 +1,4 @@
1
- import { getTreeArgument, Tree } from "@weborigami/async-tree";
1
+ import { args, Tree } from "@weborigami/async-tree";
2
2
  import { getDescriptor } from "../common/utilities.js";
3
3
 
4
4
  /**
@@ -10,12 +10,16 @@ import { getDescriptor } from "../common/utilities.js";
10
10
  * @param {string} [basePath]
11
11
  */
12
12
  export default async function indexPage(maplike, basePath) {
13
- const tree = await getTreeArgument(maplike, "svg");
13
+ const tree = await args.map(maplike, "Origami.indexPage");
14
+ if (basePath !== undefined) {
15
+ basePath = args.string(basePath, "Origami.indexPage", { position: 2 });
16
+ }
17
+
14
18
  const treeKeys = await Tree.keys(tree);
15
19
 
16
20
  // Skip system-ish files that start with a period. Also skip `index.html`.
17
21
  const filtered = treeKeys.filter(
18
- (key) => !(key.startsWith?.(".") || key === "index.html")
22
+ (key) => !(key.startsWith?.(".") || key === "index.html"),
19
23
  );
20
24
 
21
25
  const links = [];
@@ -6,7 +6,6 @@ import documentObject from "../common/documentObject.js";
6
6
  * Inline any Origami expressions found inside ${...} placeholders in the input
7
7
  * text.
8
8
  *
9
- *
10
9
  * @param {any} input
11
10
  */
12
11
  export default async function inline(input, options = {}) {
@@ -17,7 +16,9 @@ export default async function inline(input, options = {}) {
17
16
  const inputIsDocument = input._body !== undefined;
18
17
  const origami = inputIsDocument ? input._body : toString(input);
19
18
  if (origami === null) {
20
- return undefined;
19
+ throw new Error(
20
+ "Origami.inline: The provided input couldn't be treated as text.",
21
+ );
21
22
  }
22
23
 
23
24
  const parent =
@@ -29,7 +30,7 @@ export default async function inline(input, options = {}) {
29
30
  if (inputIsDocument) {
30
31
  // Collect all document properties except the body
31
32
  front = Object.fromEntries(
32
- Object.entries(input).filter(([key]) => key !== "_body")
33
+ Object.entries(input).filter(([key]) => key !== "_body"),
33
34
  );
34
35
  } else {
35
36
  front = null;
@@ -3,15 +3,9 @@ import { isUnpackable, toPlainValue } from "@weborigami/async-tree";
3
3
  /**
4
4
  * Render the given object in JSON format.
5
5
  *
6
- *
7
- * @param {any} [obj]
6
+ * @param {any} obj
8
7
  */
9
8
  export default async function json(obj) {
10
- if (arguments.length > 0 && obj === undefined) {
11
- throw new Error(
12
- "An Origami function was called with an initial argument, but its value is undefined."
13
- );
14
- }
15
9
  if (obj === undefined) {
16
10
  return undefined;
17
11
  }
@@ -1,9 +1,4 @@
1
- import {
2
- AsyncMap,
3
- Tree,
4
- getTreeArgument,
5
- jsonKeys,
6
- } from "@weborigami/async-tree";
1
+ import { AsyncMap, Tree, args, jsonKeys } from "@weborigami/async-tree";
7
2
 
8
3
  /**
9
4
  * Expose .keys.json for a tree.
@@ -14,7 +9,7 @@ import {
14
9
  * @returns {Promise<AsyncMap>}
15
10
  */
16
11
  export default async function jsonKeysBuiltin(maplike) {
17
- const source = await getTreeArgument(maplike, "jsonKeys");
12
+ const source = await args.map(maplike, "Origami.jsonKeys");
18
13
  return jsonKeysMap(source);
19
14
  }
20
15
 
@@ -1,6 +1,11 @@
1
- import { toString } from "@weborigami/async-tree";
1
+ import { args } from "@weborigami/async-tree";
2
2
 
3
+ /**
4
+ * Parse the input as JSON.
5
+ *
6
+ * @param {import("@weborigami/async-tree").Stringlike} input
7
+ */
3
8
  export default async function jsonParse(input) {
4
- const text = toString(input);
9
+ const text = args.stringlike(input, "Origami.jsonParse");
5
10
  return text ? JSON.parse(text) : undefined;
6
11
  }
@@ -25,7 +25,7 @@ marked.use(
25
25
  markedSmartypants(),
26
26
  {
27
27
  gfm: true, // Use GitHub-flavored markdown.
28
- }
28
+ },
29
29
  );
30
30
 
31
31
  /**
@@ -35,8 +35,8 @@ marked.use(
35
35
  */
36
36
  export default async function mdHtml(input) {
37
37
  if (input == null) {
38
- const error = new TypeError("mdHtml: The input is not defined.");
39
- /** @type {any} */ (error).position = 0;
38
+ const error = new TypeError("Origami.mdHtml: The input is not defined.");
39
+ /** @type {any} */ (error).position = 1;
40
40
  throw error;
41
41
  }
42
42
  if (isUnpackable(input)) {
@@ -45,7 +45,9 @@ export default async function mdHtml(input) {
45
45
  const inputIsDocument = typeof input === "object" && "_body" in input;
46
46
  const markdown = inputIsDocument ? input._body : toString(input);
47
47
  if (markdown === null) {
48
- throw new Error("mdHtml: The provided input couldn't be treated as text.");
48
+ throw new Error(
49
+ "Origami.mdHtml: The provided input couldn't be treated as text.",
50
+ );
49
51
  }
50
52
  const html = marked.parse(markdown);
51
53
  return inputIsDocument ? documentObject(html, input) : html;
@@ -8,7 +8,9 @@ export default async function mdOutline(input) {
8
8
  const inputIsDocument = typeof input === "object" && "_body" in input;
9
9
  const markdown = inputIsDocument ? input._body : toString(input);
10
10
  if (markdown === null) {
11
- throw new Error("mdHtml: The provided input couldn't be treated as text.");
11
+ throw new Error(
12
+ "Origami.mdOutline: The provided input couldn't be treated as text.",
13
+ );
12
14
  }
13
15
 
14
16
  // Call the marked lexer to parse the markdown into tokens
@@ -1,13 +1,15 @@
1
+ import { args } from "@weborigami/async-tree";
2
+
1
3
  const fnPromiseMap = new Map();
2
4
  const codePromiseMap = new Map();
3
5
 
4
6
  /**
5
7
  * Evaluate the given function only once and cache the result.
6
8
  *
7
- *
8
9
  * @param {Function} fn
9
10
  */
10
11
  export default async function once(fn) {
12
+ fn = args.fn(fn, "Origami.once");
11
13
  const code = /** @type {any} */ (fn).code;
12
14
  if (code) {
13
15
  // Origami function, cache by code
@@ -1,9 +1,4 @@
1
- import {
2
- Tree,
3
- getRealmObjectPrototype,
4
- isStringlike,
5
- toString,
6
- } from "@weborigami/async-tree";
1
+ import { Tree, args, getRealmObjectPrototype } from "@weborigami/async-tree";
7
2
  import { compile, projectGlobals } from "@weborigami/language";
8
3
  import { toYaml } from "../common/serialize.js";
9
4
  import * as dev from "../dev/dev.js";
@@ -14,19 +9,13 @@ const TypedArray = Object.getPrototypeOf(Uint8Array);
14
9
  * Parse an Origami expression, evaluate it in the context of a tree (provided
15
10
  * by `this`), and return the result as text.
16
11
  *
17
- *
18
- * @param {string} expression
12
+ * @param {import("@weborigami/async-tree").Stringlike} expression
19
13
  */
20
14
  export default async function ori(expression, options = {}) {
21
15
  const parent = options.parent ?? null;
22
16
  const formatResult = options.formatResult ?? true;
23
17
 
24
- // In case expression has come from a file, cast it to a string.
25
- if (!isStringlike(expression)) {
26
- throw new TypeError("ori: The expression is not text.");
27
- }
28
- // @ts-ignore
29
- expression = toString(expression);
18
+ expression = args.stringlike(expression, "Origami.ori");
30
19
 
31
20
  // Add Dev builtins as top-level globals
32
21
  const globals = {
@@ -65,6 +54,14 @@ async function format(result) {
65
54
  return result;
66
55
  }
67
56
 
57
+ if (result instanceof Response) {
58
+ if (!result.ok) {
59
+ console.warn(`Response not OK: ${result.status} ${result.statusText}`);
60
+ return undefined;
61
+ }
62
+ return await result.arrayBuffer();
63
+ }
64
+
68
65
  /** @type {string|String|undefined} */
69
66
  let text;
70
67
 
@@ -1,10 +1,10 @@
1
1
  export { extension } from "@weborigami/async-tree";
2
2
  export { default as help } from "../dev/help.js"; // Alias
3
- export { default as document } from "./document.js";
4
- // export { default as htmlDom } from "./htmlDom.js";
5
3
  export { default as basename } from "./basename.js";
6
4
  export { default as csv } from "./csv.js";
5
+ export { default as document } from "./document.js";
7
6
  export { default as fetch } from "./fetch.js";
7
+ export { default as htmlDom } from "./htmlDom.js";
8
8
  export { default as htmlEscape } from "./htmlEscape.js";
9
9
  export { default as format } from "./image/format.js";
10
10
  export * as image from "./image/image.js";
@@ -1,4 +1,5 @@
1
1
  /**
2
+ * Invoke the `pack` method of an object, if it exists.
2
3
  *
3
4
  * @param {any} obj
4
5
  */
@@ -1,4 +1,5 @@
1
1
  import {
2
+ args,
2
3
  isStringlike,
3
4
  isUnpackable,
4
5
  toPlainValue,
@@ -13,6 +14,7 @@ import {
13
14
  * @param {any} data
14
15
  */
15
16
  export default async function post(url, data) {
17
+ url = args.string(url, "Origami.post");
16
18
  let body;
17
19
  let headers;
18
20
  if (isUnpackable(data)) {
@@ -39,7 +41,7 @@ export default async function post(url, data) {
39
41
  });
40
42
  if (!response.ok) {
41
43
  throw new Error(
42
- `Failed to POST to ${url}. Error ${response.status}: ${response.statusText}`
44
+ `Failed to POST to ${url}. Error ${response.status}: ${response.statusText}`,
43
45
  );
44
46
  }
45
47
  return response.arrayBuffer();
@@ -1,4 +1,13 @@
1
+ import { args } from "@weborigami/async-tree";
2
+
3
+ /**
4
+ * Generate a live or static redirect response.
5
+ *
6
+ * @param {string} url
7
+ * @param {{ permanent?: boolean }} [options]
8
+ */
1
9
  export default function redirect(url, options = { permanent: false }) {
10
+ url = args.string(url, "Origami.redirect");
2
11
  const response = new Response("ok", {
3
12
  headers: {
4
13
  Location: url,
@@ -1,5 +1,14 @@
1
- export default async function repeat(count, content) {
1
+ import { args } from "@weborigami/async-tree";
2
+
3
+ /**
4
+ * Return an array of a given length, filled with the given value.
5
+ *
6
+ * @param {number} count
7
+ * @param {any} value
8
+ */
9
+ export default async function repeat(count, value) {
10
+ count = args.number(count, "Origami.repeat");
2
11
  const array = new Array(count);
3
- array.fill(content);
12
+ array.fill(value);
4
13
  return array;
5
14
  }
@@ -1,4 +1,4 @@
1
- import { getTreeArgument, Tree } from "@weborigami/async-tree";
1
+ import { args, Tree } from "@weborigami/async-tree";
2
2
  import jsonFeedToRss from "@weborigami/json-feed-to-rss";
3
3
 
4
4
  /**
@@ -8,7 +8,7 @@ import jsonFeedToRss from "@weborigami/json-feed-to-rss";
8
8
  * @param {any} options
9
9
  */
10
10
  export default async function rss(jsonFeed, options = {}) {
11
- const tree = await getTreeArgument(jsonFeed, "rss");
11
+ const tree = await args.map(jsonFeed, "Origami.rss");
12
12
  const jsonFeedPlain = await Tree.plain(tree);
13
13
  return jsonFeedToRss(jsonFeedPlain, options);
14
14
  }
@@ -1,13 +1,20 @@
1
+ import { args } from "@weborigami/async-tree";
1
2
  import { exec as callbackExec } from "node:child_process";
2
3
  import util from "node:util";
3
4
  const exec = util.promisify(callbackExec);
4
5
 
6
+ /**
7
+ * Return the standard output of invoking the given shell command.
8
+ *
9
+ * @param {string} command
10
+ */
5
11
  export default async function shell(command) {
12
+ command = args.string(command, "Origami.shell");
6
13
  try {
7
14
  const { stdout } = await exec(command);
8
15
  return stdout;
9
- } catch (err) {
10
- console.error(err);
16
+ } catch (/** @type {any} */ error) {
17
+ console.error(error.stderr);
11
18
  return undefined;
12
19
  }
13
20
  }
@@ -1,4 +1,4 @@
1
- import { getTreeArgument, Tree } from "@weborigami/async-tree";
1
+ import { args, Tree } from "@weborigami/async-tree";
2
2
  import { ori_handler } from "@weborigami/language/src/handlers/handlers.js";
3
3
 
4
4
  const templateText = `(urls) => \`<?xml version="1.0" encoding="UTF-8"?>
@@ -18,12 +18,12 @@ const templateText = `(urls) => \`<?xml version="1.0" encoding="UTF-8"?>
18
18
  * @returns {Promise<string>}
19
19
  */
20
20
  export default async function sitemap(maplike, options = {}) {
21
- const tree = await getTreeArgument(maplike, "sitemap");
21
+ const tree = await args.map(maplike, "Origami.sitemap");
22
22
 
23
23
  // We're only interested in keys that end in .html or with no extension.
24
24
  const filtered = await Tree.filter(
25
25
  tree,
26
- (value, key) => key.endsWith?.(".html") || !key.includes?.(".")
26
+ (value, key) => key.endsWith?.(".html") || !key.includes?.("."),
27
27
  );
28
28
 
29
29
  const treePaths = await Tree.paths(filtered, options);
@@ -1,5 +1,15 @@
1
- export default function slug(filename) {
2
- let slug = filename.toLowerCase();
1
+ import { args } from "@weborigami/async-tree";
2
+
3
+ /**
4
+ * Return a slug version of the given text: lowercase, spaces replaced by
5
+ * dashes, and special characters removed.
6
+ *
7
+ * @param {string} text
8
+ */
9
+ export default function slug(text) {
10
+ text = args.string(text, "Origami.slug");
11
+
12
+ let slug = text.toLowerCase();
3
13
 
4
14
  // Convert spaces to dashes
5
15
  slug = slug.replace(/\s+/g, "-");
@@ -1,9 +1,4 @@
1
- import {
2
- AsyncMap,
3
- Tree,
4
- getTreeArgument,
5
- jsonKeys,
6
- } from "@weborigami/async-tree";
1
+ import { AsyncMap, Tree, args, jsonKeys } from "@weborigami/async-tree";
7
2
  import indexPage from "./indexPage.js";
8
3
 
9
4
  /**
@@ -15,7 +10,7 @@ import indexPage from "./indexPage.js";
15
10
  * @returns {Promise<AsyncMap>}
16
11
  */
17
12
  export default async function staticBuiltin(maplike) {
18
- const source = await getTreeArgument(maplike, "static");
13
+ const source = await args.map(maplike, "Origami.static");
19
14
  return staticMap(source);
20
15
  }
21
16
 
@@ -6,7 +6,9 @@
6
6
  */
7
7
  export default function unpack(obj) {
8
8
  if (obj == null) {
9
- throw new ReferenceError("Cannot unpack null or undefined value");
9
+ throw new ReferenceError(
10
+ "Origami.unpack: Cannot unpack a null or undefined value",
11
+ );
10
12
  }
11
13
  return obj.unpack?.() ?? obj;
12
14
  }
@@ -4,15 +4,9 @@ import YAML from "yaml";
4
4
  /**
5
5
  * Render the object as text in YAML format.
6
6
  *
7
- * @param {any} [obj]
7
+ * @param {any} obj
8
8
  */
9
9
  export default async function yamlBuiltin(obj) {
10
- // A fragment of the logic from getTreeArgument.js
11
- if (arguments.length > 0 && obj === undefined) {
12
- throw new Error(
13
- "An Origami function was called with an initial argument, but its value is undefined."
14
- );
15
- }
16
10
  if (obj === undefined) {
17
11
  return undefined;
18
12
  }
@@ -1,7 +1,12 @@
1
- import { toString } from "@weborigami/async-tree";
1
+ import { args } from "@weborigami/async-tree";
2
2
  import * as serialize from "../common/serialize.js";
3
3
 
4
+ /**
5
+ * Parse the given YAML text and return the resulting value.
6
+ *
7
+ * @param {import("@weborigami/async-tree").Stringlike} input
8
+ */
4
9
  export default async function yamlParse(input) {
5
- const text = toString(input);
10
+ const text = args.stringlike(input, "Origami.yamlParse");
6
11
  return text ? serialize.parseYaml(text) : undefined;
7
12
  }
@@ -119,8 +119,8 @@ export function requestListener(maplike) {
119
119
  * Construct a page in response in the given error, and also show the error in
120
120
  * the console.
121
121
  */
122
- export function respondWithError(response, error) {
123
- let message = formatError(error);
122
+ export async function respondWithError(response, error) {
123
+ let message = await formatError(error);
124
124
  // Remove ANSI escape codes from the message.
125
125
  message = message.replace(/\x1b\[[0-9;]*m/g, "");
126
126
  // Prevent HTML in the error message from being interpreted as HTML.