@weborigami/origami 0.6.15 → 0.6.17

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/main.js CHANGED
@@ -1,7 +1,9 @@
1
1
  export { default as documentObject } from "./src/common/documentObject.js";
2
2
  export * from "./src/common/serialize.js";
3
3
  export { default as debugParent } from "./src/dev/debug2/debugParent.js";
4
+ export { default as debugTransform } from "./src/dev/debug2/debugTransform.js";
4
5
  export * as Dev from "./src/dev/dev.js";
6
+ export * from "./src/handlers/origamiHandlers.js";
5
7
  export * as Origami from "./src/origami/origami.js";
6
8
  export { default as origamiHighlightDefinition } from "./src/origami/origamiHighlightDefinition.js";
7
9
  export { default as constructResponse } from "./src/server/constructResponse.js";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@weborigami/origami",
3
- "version": "0.6.15",
3
+ "version": "0.6.17",
4
4
  "description": "Web Origami language, CLI, framework, and server",
5
5
  "type": "module",
6
6
  "repository": {
@@ -18,9 +18,9 @@
18
18
  },
19
19
  "dependencies": {
20
20
  "@hpcc-js/wasm-graphviz": "^1.21.0",
21
- "@weborigami/async-tree": "0.6.15",
21
+ "@weborigami/async-tree": "0.6.17",
22
22
  "@weborigami/json-feed-to-rss": "1.0.1",
23
- "@weborigami/language": "0.6.15",
23
+ "@weborigami/language": "0.6.17",
24
24
  "css-tree": "3.1.0",
25
25
  "highlight.js": "11.11.1",
26
26
  "jsdom": "28.1.0",
@@ -29,6 +29,7 @@
29
29
  "marked-highlight": "2.2.3",
30
30
  "marked-smartypants": "1.1.11",
31
31
  "sharp": "0.34.5",
32
+ "whatwg-mimetype": "5.0.0",
32
33
  "yaml": "2.8.2"
33
34
  },
34
35
  "scripts": {
@@ -0,0 +1,58 @@
1
+ import net from "node:net";
2
+
3
+ const DEFAULT_PORT = 5000;
4
+
5
+ /**
6
+ * Return the first open port number on or after the given port number.
7
+ *
8
+ * @param {number} startPort
9
+ * @returns {Promise<number>}
10
+ */
11
+ export async function findOpenPort(startPort = DEFAULT_PORT) {
12
+ for (let port = startPort; port <= 65535; port++) {
13
+ if (await isPortAvailable(port)) {
14
+ return port;
15
+ }
16
+ }
17
+
18
+ throw new Error(`No open port found on or after ${startPort}`);
19
+ }
20
+
21
+ /**
22
+ * Check whether a port is available on both IPv4 and IPv6 loopback addresses
23
+ * by attempting TCP connections. On macOS, IPv4 and IPv6 port spaces are
24
+ * independent (IPV6_V6ONLY=1 by default), so a server bound to [::]:PORT is
25
+ * invisible to a 127.0.0.1 bind check. Using connect probes on both loopbacks
26
+ * catches servers regardless of which protocol family they listen on. Any
27
+ * connection error (ECONNREFUSED, EADDRNOTAVAIL, etc.) means nothing is
28
+ * listening there, so the function is safe on systems without IPv6.
29
+ *
30
+ * @param {number} port
31
+ * @returns {Promise<boolean>}
32
+ */
33
+ async function isPortAvailable(port) {
34
+ const [v4, v6] = await Promise.all([
35
+ isPortListening("127.0.0.1", port),
36
+ isPortListening("::1", port),
37
+ ]);
38
+ return !v4 && !v6;
39
+ }
40
+
41
+ /**
42
+ * @param {string} host
43
+ * @param {number} port
44
+ * @returns {Promise<boolean>}
45
+ */
46
+ function isPortListening(host, port) {
47
+ return new Promise((resolve) => {
48
+ const socket = net.createConnection(port, host);
49
+ socket.once("connect", () => {
50
+ socket.destroy();
51
+ resolve(true);
52
+ });
53
+ socket.once("error", () => {
54
+ socket.destroy();
55
+ resolve(false);
56
+ });
57
+ });
58
+ }
@@ -0,0 +1,26 @@
1
+ import { toString } from "@weborigami/async-tree";
2
+ import { createHash } from "node:crypto";
3
+
4
+ /**
5
+ * Given data, return a 256-bit hash of that data. The data can be a string or a
6
+ * Uint8Array.
7
+ *
8
+ * @typedef {import("@weborigami/async-tree").Stringlike} Stringlike
9
+ *
10
+ * @param {Uint8Array|Stringlike} data
11
+ */
12
+ export default function hashBytes(data) {
13
+ let bytes;
14
+ if (data instanceof Uint8Array) {
15
+ bytes = data;
16
+ } else {
17
+ const text = toString(data);
18
+ if (!text) {
19
+ throw new TypeError("Data must be a string or Uint8Array");
20
+ }
21
+ bytes = new TextEncoder().encode(text);
22
+ }
23
+
24
+ const hash = createHash("sha256").update(bytes).digest();
25
+ return hash;
26
+ }
@@ -0,0 +1,38 @@
1
+ import { args, isStringlike, toString, Tree } from "@weborigami/async-tree";
2
+
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").Maplike} Maplike
8
+ *
9
+ * @param {Maplike} oldMaplike
10
+ * @param {Maplike} newMaplike
11
+ */
12
+ export default async function changes(oldMaplike, newMaplike) {
13
+ const oldTree = await args.map(oldMaplike, "Dev.changes", {
14
+ deep: true,
15
+ position: 1,
16
+ });
17
+ const newTree = await args.map(newMaplike, "Dev.changes", {
18
+ deep: true,
19
+ position: 2,
20
+ });
21
+
22
+ const combination = await Tree.combine(oldTree, newTree, compare);
23
+ return combination;
24
+ }
25
+
26
+ function compare(oldValue, newValue) {
27
+ if (oldValue !== undefined && newValue === undefined) {
28
+ return "deleted";
29
+ } else if (oldValue === undefined && newValue !== undefined) {
30
+ return "added";
31
+ } else if (isStringlike(oldValue) && isStringlike(newValue)) {
32
+ const oldText = toString(oldValue);
33
+ const newText = toString(newValue);
34
+ return oldText === newText ? undefined : "changed";
35
+ } else {
36
+ return oldValue === newValue ? undefined : "changed";
37
+ }
38
+ }
@@ -1,4 +1,5 @@
1
- import { execute } from "@weborigami/language";
1
+ import { OrigamiFileMap } from "@weborigami/language";
2
+ import path from "node:path";
2
3
  import debugParent from "./debugParent.js";
3
4
 
4
5
  /**
@@ -9,25 +10,13 @@ import debugParent from "./debugParent.js";
9
10
  * extract the source code of the expression to be debugged. (If it were
10
11
  * evaluated, the function will be called with the result of the expression.)
11
12
  *
12
- * The `options` argument can include:
13
- * - `enableUnsafeEval`: if true, enables the `!eval` debug command in the child
14
- * process; default is false
15
- * - `debugFilesPath`: path to resources that will be added to the served tree
16
- *
17
13
  * @typedef {import("@weborigami/language").RuntimeState} RuntimeState
18
14
  * @typedef {import("@weborigami/language").AnnotatedCode} AnnotatedCode
19
15
  *
20
16
  * @param {AnnotatedCode} code
21
- * @param {any | RuntimeState} options
22
17
  * @param {RuntimeState} state
23
18
  */
24
- export default async function debug2(code, options, state) {
25
- if (state === undefined) {
26
- // Options were omitted; shift arguments
27
- state = options;
28
- options = [];
29
- }
30
-
19
+ export default async function debug2(code, state) {
31
20
  if (
32
21
  !(code instanceof Array) ||
33
22
  code.source === undefined ||
@@ -47,23 +36,45 @@ export default async function debug2(code, options, state) {
47
36
  throw new Error("Dev.debug2 couldn't work out the parent path.");
48
37
  }
49
38
 
50
- // Need to evaluate options object
51
- if (options.length > 0) {
52
- options = await execute(options, state);
53
- } else {
54
- options = {};
55
- }
56
-
57
- // @ts-ignore
58
- const enableUnsafeEval = options.enableUnsafeEval ?? false;
59
- const debugFilesPath = options.debugFilesPath ?? "";
60
-
61
- await debugParent({
62
- debugFilesPath,
63
- enableUnsafeEval,
39
+ // Start the debug server
40
+ const server = await debugParent({
64
41
  expression,
65
42
  parentPath,
66
43
  });
44
+
45
+ // Watch the parent files for changes
46
+ const tree = new OrigamiFileMap(parentPath);
47
+ tree.watch();
48
+ tree.addEventListener?.("change", async (event) => {
49
+ // @ts-ignore
50
+ const { filePath } = event.options;
51
+ if (isJavaScriptFile(filePath)) {
52
+ // Need to restart the child process
53
+ console.log("JavaScript file changed, restarting server…");
54
+ await server.restart();
55
+ } else if (path.basename(filePath) === "package.json") {
56
+ // Need to restart the child process
57
+ console.log("package.json changed, restarting server…");
58
+ await server.restart();
59
+ } else {
60
+ // Just have the child reevaluate the expression
61
+ console.log("File changed, reloading site…");
62
+ await server.reevaluate();
63
+ }
64
+ });
65
+
66
+ // When server closes, stop watching for file changes
67
+ server.on("close", () => {
68
+ tree.unwatch();
69
+ });
70
+
71
+ console.log(`Server running at ${server.origin}. Press Ctrl+C to stop.`);
67
72
  }
68
73
  debug2.needsState = true;
69
74
  debug2.unevaluatedArgs = true;
75
+
76
+ function isJavaScriptFile(filePath) {
77
+ const extname = path.extname(filePath).toLowerCase();
78
+ const jsExtensions = [".cjs", ".js", ".mjs", ".ts"];
79
+ return jsExtensions.includes(extname);
80
+ }
@@ -22,13 +22,6 @@ function fail(message) {
22
22
  process.exit(1);
23
23
  }
24
24
 
25
- /** @type {string} */
26
- const debugFilesPath = process.env.ORIGAMI_DEBUG_FILES_PATH ?? "";
27
-
28
- /** @type {boolean} */
29
- // @ts-ignore
30
- const enableUnsafeEval = process.env.ORIGAMI_ENABLE_UNSAFE_EVAL === "1";
31
-
32
25
  /** @type {string} */
33
26
  // @ts-ignore
34
27
  const expression = process.env.ORIGAMI_EXPRESSION;
@@ -43,6 +36,8 @@ if (parentPath === undefined) {
43
36
  fail("Missing Origami parent");
44
37
  }
45
38
 
39
+ const quiet = process.env.ORIGAMI_QUIET === "1";
40
+
46
41
  // An indirect pointer to the tree of resources;
47
42
  let treeHandle = {};
48
43
 
@@ -50,7 +45,7 @@ let treeHandle = {};
50
45
  await evaluateExpression();
51
46
 
52
47
  // Serve the tree of resources
53
- const listener = requestListener(treeHandle);
48
+ const listener = requestListener(treeHandle, { quiet });
54
49
  const server = http.createServer(listener);
55
50
 
56
51
  // Track live connections so we can drain/close cleanly.
@@ -96,10 +91,8 @@ function beginDrain() {
96
91
 
97
92
  async function evaluateExpression() {
98
93
  const tree = await expressionTree({
99
- debugFilesPath,
100
94
  expression,
101
95
  parentPath,
102
- enableUnsafeEval,
103
96
  });
104
97
  if (!tree) {
105
98
  fail("Dev.debug2: expression did not evaluate to a maplike resource tree");
@@ -118,6 +111,8 @@ async function evaluateExpression() {
118
111
  } catch {
119
112
  // Ignore errors.
120
113
  }
114
+
115
+ process.send?.({ type: "EVALUATED" });
121
116
  }
122
117
 
123
118
  function maybeFinishDrain() {
@@ -1,29 +1,11 @@
1
1
  // Subset of commands made available via debugTransform
2
2
 
3
3
  import { Tree } from "@weborigami/async-tree";
4
+ export const keys = Tree.keys;
5
+ export const json = Tree.json;
4
6
 
5
- import index from "../../origami/indexPage.js";
6
- import yaml from "../../origami/yaml.js";
7
- import explore from "../explore.js";
8
- import svg from "../svg.js";
9
- import version from "../version.js";
10
- import oriEval from "./oriEval.js";
11
-
12
- export default function debugCommands(enableUnsafeEval = false) {
13
- return Object.assign(
14
- {
15
- keys: Tree.keys,
16
- json: Tree.json,
17
- index,
18
- yaml,
19
- explore,
20
- svg,
21
- version,
22
- },
23
- enableUnsafeEval
24
- ? {
25
- eval: oriEval,
26
- }
27
- : {},
28
- );
29
- }
7
+ export { default as index } from "../../origami/indexPage.js";
8
+ export { default as yaml } from "../../origami/yaml.js";
9
+ export { default as explore } from "../explore.js";
10
+ export { default as svg } from "../svg.js";
11
+ export { default as version } from "../version.js";