@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 +2 -0
- package/package.json +4 -3
- package/src/common/findOpenPort.js +58 -0
- package/src/common/hashBytes.js +26 -0
- package/src/dev/changes2.js +38 -0
- package/src/dev/debug2/debug2.js +39 -28
- package/src/dev/debug2/debugChild.js +5 -10
- package/src/dev/debug2/debugCommands.js +7 -25
- package/src/dev/debug2/debugParent.js +113 -158
- package/src/dev/debug2/debugTransform.js +37 -43
- package/src/dev/debug2/expressionTree.js +2 -4
- package/src/dev/debug2/oriEval.js +43 -11
- package/src/dev/dev.js +1 -1
- package/src/dev/help.yaml +18 -0
- package/src/handlers/origamiHandlers.js +1 -0
- package/src/handlers/xml_handler.js +23 -0
- package/src/origami/domNodeToObject.js +93 -0
- package/src/origami/hash.js +17 -0
- package/src/origami/htmlParse.js +22 -0
- package/src/origami/mdHtml.js +1 -0
- package/src/origami/mdOutline.js +17 -5
- package/src/origami/origami.js +5 -1
- package/src/origami/randomFrom.js +15 -0
- package/src/origami/randomsFrom.js +65 -0
- package/src/origami/xmlParse.js +33 -0
- package/src/server/server.js +15 -3
- package/src/origami/htmlDom.js +0 -14
|
@@ -1,12 +1,9 @@
|
|
|
1
|
-
import { OrigamiFileMap } from "@weborigami/language";
|
|
2
1
|
import { fork } from "node:child_process";
|
|
3
2
|
import { EventEmitter } from "node:events";
|
|
4
3
|
import http from "node:http";
|
|
5
|
-
import
|
|
6
|
-
import path from "node:path";
|
|
4
|
+
import { findOpenPort } from "../../common/findOpenPort.js";
|
|
7
5
|
|
|
8
6
|
const PUBLIC_HOST = "127.0.0.1";
|
|
9
|
-
const DEFAULT_PORT = 5000;
|
|
10
7
|
|
|
11
8
|
// Module that loads the server in the child process
|
|
12
9
|
const childModuleUrl = new URL("./debugChild.js", import.meta.url);
|
|
@@ -15,9 +12,6 @@ const childModuleUrl = new URL("./debugChild.js", import.meta.url);
|
|
|
15
12
|
let publicServer;
|
|
16
13
|
let publicOrigin;
|
|
17
14
|
|
|
18
|
-
// The tree of files in the parent path, which we watch for changes
|
|
19
|
-
let tree;
|
|
20
|
-
|
|
21
15
|
// The active child process and port
|
|
22
16
|
/** @typedef {import("node:child_process").ChildProcess} ChildProcess */
|
|
23
17
|
/** @typedef {{ process: ChildProcess, port: number | null }} ChildInfo */
|
|
@@ -44,9 +38,6 @@ let emitter = null;
|
|
|
44
38
|
* whenever files in the parent tree change.
|
|
45
39
|
*
|
|
46
40
|
* Supported `options`:
|
|
47
|
-
* - `debugFilesPath`: path to resources that will be added to the served tree
|
|
48
|
-
* - `enableUnsafeEval`: if true, enables the `!eval` debug command in the child
|
|
49
|
-
* process; default is false
|
|
50
41
|
* - `expression` (required): the Origami expression to evaluate in the child
|
|
51
42
|
* process
|
|
52
43
|
* - `parentPath` (required): the path to the parent tree used for evaluation
|
|
@@ -55,47 +46,32 @@ let emitter = null;
|
|
|
55
46
|
* child server encounters an Origami error while handling a request.
|
|
56
47
|
*
|
|
57
48
|
* @param {Object} options
|
|
58
|
-
* @param {string} options.debugFilesPath
|
|
59
|
-
* @param {boolean} options.enableUnsafeEval
|
|
60
49
|
* @param {string} options.expression
|
|
61
50
|
* @param {string} options.parentPath
|
|
51
|
+
* @param {number} [options.port]
|
|
52
|
+
* @param {boolean} [options.quiet]
|
|
62
53
|
*/
|
|
63
54
|
export default async function debugParent(options) {
|
|
64
55
|
const { parentPath } = options;
|
|
56
|
+
if (parentPath === undefined) {
|
|
57
|
+
throw new Error("Debugger couldn't work out the parent path.");
|
|
58
|
+
}
|
|
65
59
|
|
|
66
|
-
|
|
67
|
-
tree.watch();
|
|
68
|
-
tree.addEventListener?.("change", (event) => {
|
|
69
|
-
// @ts-ignore
|
|
70
|
-
const { filePath } = event.options;
|
|
71
|
-
if (isJavaScriptFile(filePath)) {
|
|
72
|
-
// Need to restart the child process
|
|
73
|
-
console.log("JavaScript file changed, restarting server…");
|
|
74
|
-
startChild(options);
|
|
75
|
-
} else if (isPackageJsonFile(filePath)) {
|
|
76
|
-
// Need to restart the child process
|
|
77
|
-
console.log("package.json changed, restarting server…");
|
|
78
|
-
startChild(options);
|
|
79
|
-
} else {
|
|
80
|
-
// Just have the child reevaluate the expression
|
|
81
|
-
console.log("File changed, reloading site…");
|
|
82
|
-
activeChild?.process.send({ type: "REEVALUATE" });
|
|
83
|
-
}
|
|
84
|
-
});
|
|
85
|
-
|
|
86
|
-
const port = await findOpenPort();
|
|
60
|
+
const port = options.port ?? (await findOpenPort());
|
|
87
61
|
publicOrigin = `http://${PUBLIC_HOST}:${port}`;
|
|
88
62
|
|
|
89
63
|
publicServer = http.createServer(proxyRequest);
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
64
|
+
await new Promise((resolve) =>
|
|
65
|
+
publicServer.listen(port, PUBLIC_HOST, resolve),
|
|
66
|
+
);
|
|
67
|
+
await startChild(options);
|
|
94
68
|
|
|
95
69
|
emitter = Object.assign(new EventEmitter(), {
|
|
96
70
|
close,
|
|
71
|
+
origin: publicOrigin,
|
|
72
|
+
reevaluate,
|
|
73
|
+
restart: () => startChild(options),
|
|
97
74
|
});
|
|
98
|
-
|
|
99
75
|
return emitter;
|
|
100
76
|
}
|
|
101
77
|
|
|
@@ -110,7 +86,6 @@ async function close() {
|
|
|
110
86
|
publicServer.closeAllConnections();
|
|
111
87
|
await closed;
|
|
112
88
|
publicServer = null;
|
|
113
|
-
emitter = null;
|
|
114
89
|
|
|
115
90
|
// Drain and stop any children concurrently
|
|
116
91
|
const children = [pendingChild?.process, activeChild?.process].filter(
|
|
@@ -121,9 +96,9 @@ async function close() {
|
|
|
121
96
|
activeChild = null;
|
|
122
97
|
await Promise.all(children.map(drainAndStopChild));
|
|
123
98
|
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
99
|
+
emitter.emit("close");
|
|
100
|
+
emitter.removeAllListeners();
|
|
101
|
+
emitter = null;
|
|
127
102
|
}
|
|
128
103
|
|
|
129
104
|
/**
|
|
@@ -182,66 +157,6 @@ async function drainAndStopChild(childProcess) {
|
|
|
182
157
|
}, GRACE_MS).unref();
|
|
183
158
|
}
|
|
184
159
|
|
|
185
|
-
// Return the first open port number on or after the given port number.
|
|
186
|
-
async function findOpenPort(startPort = DEFAULT_PORT) {
|
|
187
|
-
for (let port = startPort; port <= 65535; port++) {
|
|
188
|
-
if (await isPortAvailable(port)) {
|
|
189
|
-
return port;
|
|
190
|
-
}
|
|
191
|
-
}
|
|
192
|
-
|
|
193
|
-
throw new Error(`No open port found on or after ${startPort}`);
|
|
194
|
-
}
|
|
195
|
-
|
|
196
|
-
function isJavaScriptFile(filePath) {
|
|
197
|
-
const extname = path.extname(filePath).toLowerCase();
|
|
198
|
-
const jsExtensions = [".cjs", ".js", ".mjs", ".ts"];
|
|
199
|
-
return jsExtensions.includes(extname);
|
|
200
|
-
}
|
|
201
|
-
|
|
202
|
-
function isPackageJsonFile(filePath) {
|
|
203
|
-
return path.basename(filePath).toLowerCase() === "package.json";
|
|
204
|
-
}
|
|
205
|
-
|
|
206
|
-
/**
|
|
207
|
-
* Check whether a port is available on both IPv4 and IPv6 loopback addresses
|
|
208
|
-
* by attempting TCP connections. On macOS, IPv4 and IPv6 port spaces are
|
|
209
|
-
* independent (IPV6_V6ONLY=1 by default), so a server bound to [::]:PORT is
|
|
210
|
-
* invisible to a 127.0.0.1 bind check. Using connect probes on both loopbacks
|
|
211
|
-
* catches servers regardless of which protocol family they listen on. Any
|
|
212
|
-
* connection error (ECONNREFUSED, EADDRNOTAVAIL, etc.) means nothing is
|
|
213
|
-
* listening there, so the function is safe on systems without IPv6.
|
|
214
|
-
*
|
|
215
|
-
* @param {number} port
|
|
216
|
-
* @returns {Promise<boolean>}
|
|
217
|
-
*/
|
|
218
|
-
async function isPortAvailable(port) {
|
|
219
|
-
const [v4, v6] = await Promise.all([
|
|
220
|
-
isPortListening("127.0.0.1", port),
|
|
221
|
-
isPortListening("::1", port),
|
|
222
|
-
]);
|
|
223
|
-
return !v4 && !v6;
|
|
224
|
-
}
|
|
225
|
-
|
|
226
|
-
/**
|
|
227
|
-
* @param {string} host
|
|
228
|
-
* @param {number} port
|
|
229
|
-
* @returns {Promise<boolean>}
|
|
230
|
-
*/
|
|
231
|
-
function isPortListening(host, port) {
|
|
232
|
-
return new Promise((resolve) => {
|
|
233
|
-
const socket = net.createConnection(port, host);
|
|
234
|
-
socket.once("connect", () => {
|
|
235
|
-
socket.destroy();
|
|
236
|
-
resolve(true);
|
|
237
|
-
});
|
|
238
|
-
socket.once("error", () => {
|
|
239
|
-
socket.destroy();
|
|
240
|
-
resolve(false);
|
|
241
|
-
});
|
|
242
|
-
});
|
|
243
|
-
}
|
|
244
|
-
|
|
245
160
|
/**
|
|
246
161
|
* Proxy incoming requests to the active child server, or return a 503 if not
|
|
247
162
|
* ready.
|
|
@@ -322,6 +237,30 @@ function proxyRequest(request, response) {
|
|
|
322
237
|
request.pipe(upstreamRequest);
|
|
323
238
|
}
|
|
324
239
|
|
|
240
|
+
async function reevaluate() {
|
|
241
|
+
if (!activeChild) {
|
|
242
|
+
return;
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
const child = activeChild;
|
|
246
|
+
|
|
247
|
+
// Wait for the next EVALUATED message from the child
|
|
248
|
+
const evaluated = /** @type {Promise<void>} */ (
|
|
249
|
+
new Promise((resolve) => {
|
|
250
|
+
const onMessage = (/** @type {any} */ msg) => {
|
|
251
|
+
if (msg && typeof msg === "object" && msg.type === "EVALUATED") {
|
|
252
|
+
child.process.off("message", onMessage);
|
|
253
|
+
resolve();
|
|
254
|
+
}
|
|
255
|
+
};
|
|
256
|
+
child.process.on("message", onMessage);
|
|
257
|
+
})
|
|
258
|
+
);
|
|
259
|
+
|
|
260
|
+
child.process.send({ type: "REEVALUATE" });
|
|
261
|
+
await evaluated;
|
|
262
|
+
}
|
|
263
|
+
|
|
325
264
|
/**
|
|
326
265
|
* Start a new child process.
|
|
327
266
|
*
|
|
@@ -329,7 +268,8 @@ function proxyRequest(request, response) {
|
|
|
329
268
|
* it becomes active and any previous active child is drained and stopped.
|
|
330
269
|
*/
|
|
331
270
|
function startChild(options) {
|
|
332
|
-
const {
|
|
271
|
+
const { expression, parentPath } = options;
|
|
272
|
+
const quiet = options.quiet ?? false;
|
|
333
273
|
|
|
334
274
|
// Start the child process, passing parent path via an environment variable.
|
|
335
275
|
/** @type {ChildProcess} */
|
|
@@ -339,10 +279,9 @@ function startChild(options) {
|
|
|
339
279
|
stdio: ["inherit", "inherit", "inherit", "ipc"],
|
|
340
280
|
env: {
|
|
341
281
|
...process.env,
|
|
342
|
-
ORIGAMI_DEBUG_FILES_PATH: debugFilesPath,
|
|
343
|
-
ORIGAMI_ENABLE_UNSAFE_EVAL: enableUnsafeEval ? "1" : "0",
|
|
344
282
|
ORIGAMI_EXPRESSION: expression,
|
|
345
283
|
ORIGAMI_PARENT_PATH: parentPath,
|
|
284
|
+
ORIGAMI_QUIET: quiet ? "1" : "0",
|
|
346
285
|
},
|
|
347
286
|
});
|
|
348
287
|
} catch (error) {
|
|
@@ -354,61 +293,77 @@ function startChild(options) {
|
|
|
354
293
|
// This becomes the pending child immediately
|
|
355
294
|
pendingChild = { process: childProcess, port: null };
|
|
356
295
|
|
|
357
|
-
//
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
296
|
+
// Returns a Promise that resolves when the child signals READY, or rejects
|
|
297
|
+
// on FATAL or unexpected exit before ready.
|
|
298
|
+
return /** @type {Promise<void>} */ (
|
|
299
|
+
new Promise((resolve, reject) => {
|
|
300
|
+
// Listen for messages from the child about its status
|
|
301
|
+
childProcess.on("message", (/** @type {any} */ message) => {
|
|
302
|
+
if (!message || typeof message !== "object") {
|
|
303
|
+
return;
|
|
304
|
+
} else if (
|
|
305
|
+
message.type === "READY" &&
|
|
306
|
+
typeof message.port === "number"
|
|
307
|
+
) {
|
|
308
|
+
// Only promote to active if this is still the pending child
|
|
309
|
+
if (pendingChild?.process === childProcess) {
|
|
310
|
+
const previousChild = activeChild;
|
|
311
|
+
|
|
312
|
+
activeChild = pendingChild;
|
|
313
|
+
pendingChild.port = message.port;
|
|
314
|
+
pendingChild = null;
|
|
315
|
+
|
|
316
|
+
// Drain previous child in background (don't wait)
|
|
317
|
+
if (
|
|
318
|
+
previousChild?.process &&
|
|
319
|
+
previousChild.process !== childProcess
|
|
320
|
+
) {
|
|
321
|
+
drainAndStopChild(previousChild.process).catch((err) =>
|
|
322
|
+
console.error("[drain]", err),
|
|
323
|
+
);
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
if (emitter) {
|
|
327
|
+
emitter.emit("ready", { origin: publicOrigin });
|
|
328
|
+
}
|
|
329
|
+
resolve();
|
|
330
|
+
} else {
|
|
331
|
+
// This child was superseded by a newer one, kill it
|
|
332
|
+
// console.log("Child process superseded by newer one, killing it...");
|
|
333
|
+
childProcess.kill("SIGTERM");
|
|
334
|
+
}
|
|
335
|
+
} else if (message.type === "EVALUATED") {
|
|
336
|
+
// Let caller know child has reevaluated the expression (after a file change)
|
|
337
|
+
if (emitter) {
|
|
338
|
+
emitter.emit("evaluated");
|
|
339
|
+
}
|
|
340
|
+
} else if (message.type === "FATAL") {
|
|
341
|
+
// Child couldn't start (import error, etc.)
|
|
342
|
+
// Keep previous active child if any; otherwise we'll serve 500/503.
|
|
343
|
+
console.error("[child fatal]", message.error ?? message);
|
|
344
|
+
if (pendingChild?.process === childProcess) {
|
|
345
|
+
pendingChild = null;
|
|
346
|
+
}
|
|
347
|
+
reject(new Error(message.error ?? "Child server failed to start"));
|
|
377
348
|
}
|
|
349
|
+
});
|
|
378
350
|
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
351
|
+
childProcess.on("exit", (code, signal) => {
|
|
352
|
+
if (activeChild?.process === childProcess) {
|
|
353
|
+
// Active child died unexpectedly.
|
|
354
|
+
activeChild = null;
|
|
355
|
+
}
|
|
356
|
+
if (pendingChild?.process === childProcess) {
|
|
357
|
+
pendingChild = null;
|
|
358
|
+
// Child died before it was ready. If child was ready and resolve()
|
|
359
|
+
// was called, when the child exits, then reject() is a no-op.
|
|
360
|
+
reject(
|
|
361
|
+
new Error(
|
|
362
|
+
`Child exited before ready (code=${code}, signal=${signal})`,
|
|
363
|
+
),
|
|
382
364
|
);
|
|
383
|
-
return;
|
|
384
365
|
}
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
} else {
|
|
389
|
-
// This child was superseded by a newer one, kill it
|
|
390
|
-
// console.log("Child process superseded by newer one, killing it...");
|
|
391
|
-
childProcess.kill("SIGTERM");
|
|
392
|
-
}
|
|
393
|
-
}
|
|
394
|
-
|
|
395
|
-
if (message.type === "FATAL") {
|
|
396
|
-
// Child couldn't start (import error, etc.)
|
|
397
|
-
// Keep previous active child if any; otherwise we'll serve 500/503.
|
|
398
|
-
console.error("[child fatal]", message.error ?? message);
|
|
399
|
-
if (pendingChild?.process === childProcess) {
|
|
400
|
-
pendingChild = null;
|
|
401
|
-
}
|
|
402
|
-
}
|
|
403
|
-
});
|
|
404
|
-
|
|
405
|
-
childProcess.on("exit", (code, signal) => {
|
|
406
|
-
if (activeChild?.process === childProcess) {
|
|
407
|
-
// Active child died unexpectedly.
|
|
408
|
-
activeChild = null;
|
|
409
|
-
}
|
|
410
|
-
if (pendingChild?.process === childProcess) {
|
|
411
|
-
pendingChild = null;
|
|
412
|
-
}
|
|
413
|
-
});
|
|
366
|
+
});
|
|
367
|
+
})
|
|
368
|
+
);
|
|
414
369
|
}
|
|
@@ -9,10 +9,9 @@ import {
|
|
|
9
9
|
scope,
|
|
10
10
|
trailingSlash,
|
|
11
11
|
} from "@weborigami/async-tree";
|
|
12
|
-
import { OrigamiFileMap } from "@weborigami/language";
|
|
13
12
|
import indexPage from "../../origami/indexPage.js";
|
|
14
13
|
import yaml from "../../origami/yaml.js";
|
|
15
|
-
import debugCommands from "./debugCommands.js";
|
|
14
|
+
import * as debugCommands from "./debugCommands.js";
|
|
16
15
|
|
|
17
16
|
/**
|
|
18
17
|
* Transform the given map-based tree to add debugging resources:
|
|
@@ -23,19 +22,29 @@ import debugCommands from "./debugCommands.js";
|
|
|
23
22
|
*
|
|
24
23
|
* Also transform a simple object result to YAML for viewing.
|
|
25
24
|
*
|
|
26
|
-
* @
|
|
27
|
-
* @
|
|
28
|
-
*
|
|
25
|
+
* @typedef {import("@weborigami/async-tree").Maplike} Maplike
|
|
26
|
+
* @typedef {import("@weborigami/async-tree").Packed} Packed
|
|
27
|
+
*
|
|
28
|
+
* @param {Maplike|Packed} input
|
|
29
29
|
*/
|
|
30
|
-
export default function debugTransform(
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
)
|
|
35
|
-
|
|
36
|
-
|
|
30
|
+
export default function debugTransform(input) {
|
|
31
|
+
if (isUnpackable(input)) {
|
|
32
|
+
// If the value isn't a tree, but has a tree attached via an `unpack`
|
|
33
|
+
// method, destructively wrap the unpack method to add this transform.
|
|
34
|
+
const original = input.unpack.bind(input);
|
|
35
|
+
input.unpack = async () => {
|
|
36
|
+
const content = await original();
|
|
37
|
+
if (!Tree.isTraversable(content) || typeof content === "function") {
|
|
38
|
+
return content;
|
|
39
|
+
}
|
|
40
|
+
/** @type {any} */
|
|
41
|
+
let tree = Tree.from(content);
|
|
42
|
+
return debugTransform(tree);
|
|
43
|
+
};
|
|
44
|
+
return input;
|
|
45
|
+
}
|
|
37
46
|
|
|
38
|
-
const
|
|
47
|
+
const source = Tree.from(input, { deep: true });
|
|
39
48
|
|
|
40
49
|
return Object.assign(new AsyncMap(), {
|
|
41
50
|
description: "debug resources",
|
|
@@ -44,22 +53,15 @@ export default function debugTransform(
|
|
|
44
53
|
// Ask the tree if it has the key.
|
|
45
54
|
let value = await source.get(key);
|
|
46
55
|
|
|
47
|
-
if (value === undefined && debugFiles) {
|
|
48
|
-
// Try the debug files
|
|
49
|
-
value = await debugFiles.get(key);
|
|
50
|
-
}
|
|
51
|
-
|
|
52
56
|
if (value === undefined) {
|
|
53
57
|
// Try the defaults and commands
|
|
54
|
-
if (key === "
|
|
55
|
-
return debugFiles;
|
|
56
|
-
} else if (key === "index.html") {
|
|
58
|
+
if (key === "index.html") {
|
|
57
59
|
// Generate an index page for this site
|
|
58
60
|
value = await indexPage(source);
|
|
59
61
|
} else if (key === ".keys.json") {
|
|
60
62
|
value = await jsonKeys.stringify(source);
|
|
61
63
|
} else if (typeof key === "string" && key.startsWith("!")) {
|
|
62
|
-
value = await invokeOrigamiCommand(
|
|
64
|
+
value = await invokeOrigamiCommand(source, key);
|
|
63
65
|
}
|
|
64
66
|
}
|
|
65
67
|
|
|
@@ -72,29 +74,21 @@ export default function debugTransform(
|
|
|
72
74
|
Tree.merge(object, {
|
|
73
75
|
"index.html": yamlText,
|
|
74
76
|
});
|
|
75
|
-
} else if (
|
|
77
|
+
} else if (
|
|
78
|
+
Tree.isMaplike(value) &&
|
|
79
|
+
!Tree.isMap(value) &&
|
|
80
|
+
typeof value !== "function"
|
|
81
|
+
) {
|
|
76
82
|
// Make it a map so we can debug it
|
|
77
83
|
value = Tree.from(value);
|
|
78
84
|
}
|
|
79
85
|
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
value = debugTransform(value
|
|
84
|
-
} else if (value?.unpack) {
|
|
85
|
-
// If the value isn't a tree, but has a tree attached via an `unpack`
|
|
86
|
-
// method, wrap the unpack method to add this transform.
|
|
87
|
-
const original = value.unpack.bind(value);
|
|
88
|
-
value.unpack = async () => {
|
|
89
|
-
const content = await original();
|
|
90
|
-
if (!Tree.isTraversable(content) || typeof content === "function") {
|
|
91
|
-
return content;
|
|
92
|
-
}
|
|
93
|
-
/** @type {any} */
|
|
94
|
-
let tree = Tree.from(content);
|
|
95
|
-
return debugTransform(tree);
|
|
96
|
-
};
|
|
86
|
+
// Ensure this transform is applied to any map result, or any object with
|
|
87
|
+
// an unpack method that returns a map.
|
|
88
|
+
if (Tree.isMap(value) || value?.unpack) {
|
|
89
|
+
value = debugTransform(value);
|
|
97
90
|
}
|
|
91
|
+
|
|
98
92
|
return value;
|
|
99
93
|
},
|
|
100
94
|
|
|
@@ -114,15 +108,15 @@ export default function debugTransform(
|
|
|
114
108
|
});
|
|
115
109
|
}
|
|
116
110
|
|
|
117
|
-
async function invokeOrigamiCommand(
|
|
111
|
+
async function invokeOrigamiCommand(tree, key) {
|
|
118
112
|
// Key is an Origami command; invoke it.
|
|
119
113
|
const commandName = trailingSlash.remove(key.slice(1).trim());
|
|
120
114
|
|
|
121
115
|
// Look for the indicated command
|
|
122
|
-
const command =
|
|
116
|
+
const command = debugCommands[commandName];
|
|
123
117
|
let value;
|
|
124
118
|
if (command) {
|
|
125
|
-
value = await command(tree);
|
|
119
|
+
value = command instanceof Function ? await command(tree) : command;
|
|
126
120
|
} else {
|
|
127
121
|
// Look for command in scope
|
|
128
122
|
const parentScope = await scope(tree);
|
|
@@ -15,13 +15,11 @@ let version = 0;
|
|
|
15
15
|
* resource tree, then transform that tree with debug resources and return it.
|
|
16
16
|
*
|
|
17
17
|
* @param {Object} options
|
|
18
|
-
* @param {string} options.debugFilesPath
|
|
19
|
-
* @param {boolean} options.enableUnsafeEval
|
|
20
18
|
* @param {string} options.expression
|
|
21
19
|
* @param {string} options.parentPath
|
|
22
20
|
*/
|
|
23
21
|
export default async function expressionTree(options) {
|
|
24
|
-
const {
|
|
22
|
+
const { expression, parentPath } = options;
|
|
25
23
|
|
|
26
24
|
const parent = new OrigamiFileMap(parentPath);
|
|
27
25
|
const globals = await projectGlobals(parent);
|
|
@@ -47,7 +45,7 @@ export default async function expressionTree(options) {
|
|
|
47
45
|
setParent(maplike, parent);
|
|
48
46
|
|
|
49
47
|
// Add debugging resources
|
|
50
|
-
const tree = debugTransform(maplike
|
|
48
|
+
const tree = debugTransform(maplike);
|
|
51
49
|
|
|
52
50
|
/** @type {any} */ (tree).version = version++;
|
|
53
51
|
|
|
@@ -1,17 +1,49 @@
|
|
|
1
|
-
import
|
|
1
|
+
import { trailingSlash, Tree } from "@weborigami/async-tree";
|
|
2
|
+
import { evaluate, projectGlobals } from "@weborigami/language";
|
|
3
|
+
import debugTransform from "./debugTransform.js";
|
|
2
4
|
|
|
3
|
-
|
|
4
|
-
let lastResult;
|
|
5
|
+
const mapParentToResult = new WeakMap();
|
|
5
6
|
|
|
7
|
+
/**
|
|
8
|
+
* Return a route that evaluates a key containing an expression.
|
|
9
|
+
*
|
|
10
|
+
* Because this route may be used multiple times to return the resources for a
|
|
11
|
+
* page, we cache the result for each parent and key.
|
|
12
|
+
*
|
|
13
|
+
* To allow multiple evaluations of the same expression, we expect the key to be
|
|
14
|
+
* of the form `<counter>,<expression>`, where the counter is a number that will
|
|
15
|
+
* be used for caching but ignored for evaluation. The debugger increments the
|
|
16
|
+
* counter to force reevaluation of the same expression.
|
|
17
|
+
*/
|
|
6
18
|
export default async function oriEval(parent) {
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
const
|
|
10
|
-
|
|
11
|
-
|
|
19
|
+
const globals = await projectGlobals(parent);
|
|
20
|
+
return async (key) => {
|
|
21
|
+
const normalizedKey = trailingSlash.remove(key);
|
|
22
|
+
let result = mapParentToResult.get(parent);
|
|
23
|
+
if (result?.key === normalizedKey) {
|
|
24
|
+
return result.value;
|
|
12
25
|
}
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
26
|
+
|
|
27
|
+
const regex = /^\d+,(?<expression>.*)$/;
|
|
28
|
+
const match = normalizedKey.match(regex);
|
|
29
|
+
let value;
|
|
30
|
+
if (match) {
|
|
31
|
+
const expression = decodeURIComponent(match.groups.expression);
|
|
32
|
+
value = await evaluate(expression, {
|
|
33
|
+
globals,
|
|
34
|
+
mode: "shell",
|
|
35
|
+
parent,
|
|
36
|
+
});
|
|
37
|
+
if (
|
|
38
|
+
(Tree.isMaplike(value) && typeof value !== "function") ||
|
|
39
|
+
value?.unpack
|
|
40
|
+
) {
|
|
41
|
+
value = debugTransform(value);
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
mapParentToResult.set(parent, { key: normalizedKey, value });
|
|
46
|
+
|
|
47
|
+
return value;
|
|
16
48
|
};
|
|
17
49
|
}
|
package/src/dev/dev.js
CHANGED
|
@@ -6,13 +6,13 @@ export { default as indexPage } from "../origami/indexPage.js";
|
|
|
6
6
|
export { default as yaml } from "../origami/yaml.js";
|
|
7
7
|
export { default as breakpoint } from "./breakpoint.js";
|
|
8
8
|
export { default as changes } from "./changes.js";
|
|
9
|
+
export { default as changes2 } from "./changes2.js";
|
|
9
10
|
export { default as code } from "./code.js";
|
|
10
11
|
export { default as copy } from "./copy.js";
|
|
11
12
|
export { default as audit } from "./crawler/audit.js";
|
|
12
13
|
export { default as crawl } from "./crawler/crawl.js";
|
|
13
14
|
export { default as debug } from "./debug.js";
|
|
14
15
|
export { default as debug2 } from "./debug2/debug2.js";
|
|
15
|
-
export { default as eval } from "./debug2/oriEval.js";
|
|
16
16
|
export { default as explore } from "./explore.js";
|
|
17
17
|
export { default as help } from "./help.js";
|
|
18
18
|
export { default as log } from "./log.js";
|
package/src/dev/help.yaml
CHANGED
|
@@ -68,9 +68,15 @@ Origami:
|
|
|
68
68
|
fetch:
|
|
69
69
|
args: (url, options)
|
|
70
70
|
description: Fetch a resource from a URL with support for extensions
|
|
71
|
+
hash:
|
|
72
|
+
args: (data)
|
|
73
|
+
description: A hex string hash of the data
|
|
71
74
|
htmlEscape:
|
|
72
75
|
args: (text)
|
|
73
76
|
description: Escape HTML entities in the text
|
|
77
|
+
htmlParse:
|
|
78
|
+
args: (html)
|
|
79
|
+
description: Parse HTML into a plain JavaScript object
|
|
74
80
|
image:
|
|
75
81
|
description: Collection of functions for working with images
|
|
76
82
|
indexPage:
|
|
@@ -107,6 +113,12 @@ Origami:
|
|
|
107
113
|
description: POST the given data to the URL
|
|
108
114
|
projectRoot:
|
|
109
115
|
description: The root folder for the current Origami project
|
|
116
|
+
randomFrom:
|
|
117
|
+
args: (data)
|
|
118
|
+
description: Returns a random value based on the data
|
|
119
|
+
randomsFrom:
|
|
120
|
+
args: (data)
|
|
121
|
+
description: Returns a function to produce random values based on the data
|
|
110
122
|
redirect:
|
|
111
123
|
args: (url, options)
|
|
112
124
|
description: Redirect to the given URL
|
|
@@ -139,6 +151,9 @@ Origami:
|
|
|
139
151
|
unpack:
|
|
140
152
|
args: (buffer)
|
|
141
153
|
description: Unpack the buffer into a usable form
|
|
154
|
+
xmlParse:
|
|
155
|
+
args: (xml)
|
|
156
|
+
description: Parse XML into a plain JavaScript object
|
|
142
157
|
yaml:
|
|
143
158
|
args: (obj)
|
|
144
159
|
description: Render the object in YAML format
|
|
@@ -187,6 +202,9 @@ Tree:
|
|
|
187
202
|
clear:
|
|
188
203
|
args: (map)
|
|
189
204
|
description: Remove all values from the map
|
|
205
|
+
combine:
|
|
206
|
+
args: (tree1, tree2, combineFn)
|
|
207
|
+
description: Pairwise application of combineFn to the trees' values
|
|
190
208
|
concat:
|
|
191
209
|
args: (...trees)
|
|
192
210
|
description: Merge trees, renumbering numeric keys
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { default as xml_handler } from "./xml_handler.js";
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { symbols, toString } from "@weborigami/async-tree";
|
|
2
|
+
import xmlParse from "../origami/xmlParse.js";
|
|
3
|
+
|
|
4
|
+
export default {
|
|
5
|
+
mediaType: "application/xml",
|
|
6
|
+
|
|
7
|
+
async unpack(packed, options = {}) {
|
|
8
|
+
const parent = options.parent ?? null;
|
|
9
|
+
const text = toString(packed);
|
|
10
|
+
if (text === null) {
|
|
11
|
+
throw new TypeError("XML handler can only unpack text.");
|
|
12
|
+
}
|
|
13
|
+
const data = await xmlParse(text);
|
|
14
|
+
// Define `parent` as non-enumerable property
|
|
15
|
+
Object.defineProperty(data, symbols.parent, {
|
|
16
|
+
configurable: true,
|
|
17
|
+
enumerable: false,
|
|
18
|
+
value: parent,
|
|
19
|
+
writable: true,
|
|
20
|
+
});
|
|
21
|
+
return data;
|
|
22
|
+
},
|
|
23
|
+
};
|