@takodotid/circuit 0.18.5 → 0.19.0
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/README.md +18 -0
- package/dist/chunks/{index-ex1wcst7.js → index-dczpxzzm.js} +1 -1
- package/dist/chunks/studio-femhydvd.js +764 -0
- package/dist/cli/index.js +546 -8
- package/dist/index.js +1 -1
- package/dist/presets/index.js +1 -1
- package/dist/types/core/graph.d.ts +44 -0
- package/package.json +1 -1
- package/src/cli/graph.ts +103 -0
- package/src/cli/index.ts +69 -0
- package/src/cli/studio-page.ts +876 -0
- package/src/cli/studio.ts +126 -0
- package/src/core/graph.ts +613 -0
- package/templates/project/AGENTS.md +10 -9
- package/templates/project/README.md +1 -0
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
// `circuit studio`: the graph in the browser, on this machine only. Each time a file beside the config changes, the graph is made again by `circuit graph` in a new process, so the config is always read fresh.
|
|
2
|
+
|
|
3
|
+
import { execFile, spawn } from "node:child_process";
|
|
4
|
+
import { watch } from "node:fs";
|
|
5
|
+
import { createServer, type ServerResponse } from "node:http";
|
|
6
|
+
import { page, STYLE } from "./studio-page";
|
|
7
|
+
|
|
8
|
+
export type StudioOptions = {
|
|
9
|
+
/** The config file. */
|
|
10
|
+
configPath: string;
|
|
11
|
+
/** The directory watched for changes: the config's own. */
|
|
12
|
+
root: string;
|
|
13
|
+
port: number;
|
|
14
|
+
/** Open the browser once the server listens. */
|
|
15
|
+
open: boolean;
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
const HTML = `<!doctype html>
|
|
19
|
+
<html lang="en">
|
|
20
|
+
<head>
|
|
21
|
+
<meta charset="utf-8">
|
|
22
|
+
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
23
|
+
<title>Circuit studio</title>
|
|
24
|
+
<style>${STYLE}</style>
|
|
25
|
+
</head>
|
|
26
|
+
<body>
|
|
27
|
+
<script>(${page.toString()})()</script>
|
|
28
|
+
</body>
|
|
29
|
+
</html>
|
|
30
|
+
`;
|
|
31
|
+
|
|
32
|
+
/** Directories whose changes never change the graph. */
|
|
33
|
+
const IGNORED = /(^|[\\/])(node_modules|\.git)([\\/]|$)/;
|
|
34
|
+
|
|
35
|
+
/** The graph as JSON, from `circuit graph` run as a new process, or what stopped it. */
|
|
36
|
+
function makeGraph(configPath: string): Promise<string> {
|
|
37
|
+
const cli = process.argv[1]!;
|
|
38
|
+
const args = [...process.execArgv, cli, "graph", "--format=json", "--layer=all", `--config=${configPath}`];
|
|
39
|
+
|
|
40
|
+
return new Promise((done) => {
|
|
41
|
+
execFile(process.execPath, args, { maxBuffer: 256 * 1024 * 1024 }, (error, stdout, stderr) => {
|
|
42
|
+
if (!error) return done(stdout);
|
|
43
|
+
|
|
44
|
+
const message = (stderr || error.message).trim();
|
|
45
|
+
done(JSON.stringify({ error: message }));
|
|
46
|
+
});
|
|
47
|
+
});
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/** Open the address in the browser. A machine without one only misses the convenience. */
|
|
51
|
+
function openBrowser(url: string): void {
|
|
52
|
+
const [command, args] =
|
|
53
|
+
process.platform === "win32"
|
|
54
|
+
? ["cmd", ["/c", "start", "", url]]
|
|
55
|
+
: process.platform === "darwin"
|
|
56
|
+
? ["open", [url]]
|
|
57
|
+
: ["xdg-open", [url]];
|
|
58
|
+
|
|
59
|
+
const child = spawn(command, args, { stdio: "ignore", detached: true });
|
|
60
|
+
child.on("error", () => {});
|
|
61
|
+
child.unref();
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
export async function studio(options: StudioOptions): Promise<void> {
|
|
65
|
+
const { configPath, root, port } = options;
|
|
66
|
+
|
|
67
|
+
let graph = await makeGraph(configPath);
|
|
68
|
+
const listeners = new Set<ServerResponse>();
|
|
69
|
+
|
|
70
|
+
// Only this machine's own names are answered, so a web page elsewhere cannot read the graph through a name it points here.
|
|
71
|
+
const allowedHosts = new Set([`127.0.0.1:${port}`, `localhost:${port}`]);
|
|
72
|
+
|
|
73
|
+
const server = createServer((request, response) => {
|
|
74
|
+
if (!allowedHosts.has(request.headers.host ?? "")) {
|
|
75
|
+
response.writeHead(403).end();
|
|
76
|
+
return;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
if (request.url === "/") {
|
|
80
|
+
response.writeHead(200, { "content-type": "text/html; charset=utf-8" }).end(HTML);
|
|
81
|
+
return;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
if (request.url === "/graph") {
|
|
85
|
+
response.writeHead(200, { "content-type": "application/json", "cache-control": "no-store" }).end(graph);
|
|
86
|
+
return;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
if (request.url === "/events") {
|
|
90
|
+
response.writeHead(200, { "content-type": "text/event-stream", "cache-control": "no-store", connection: "keep-alive" });
|
|
91
|
+
response.write(": connected\n\n");
|
|
92
|
+
|
|
93
|
+
listeners.add(response);
|
|
94
|
+
request.on("close", () => listeners.delete(response));
|
|
95
|
+
return;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
response.writeHead(404).end();
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
// Several files often change at once, such as on a save or a checkout: the graph is made once they settle.
|
|
102
|
+
let timer: NodeJS.Timeout | undefined;
|
|
103
|
+
|
|
104
|
+
watch(root, { recursive: true }, (_event, filename) => {
|
|
105
|
+
if (filename && IGNORED.test(filename)) return;
|
|
106
|
+
|
|
107
|
+
clearTimeout(timer);
|
|
108
|
+
timer = setTimeout(async () => {
|
|
109
|
+
graph = await makeGraph(configPath);
|
|
110
|
+
for (const listener of listeners) listener.write("data: changed\n\n");
|
|
111
|
+
console.log(`${new Date().toLocaleTimeString()} ${filename ?? "a file"} changed, graph made again`);
|
|
112
|
+
}, 300);
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
server.on("error", (error: NodeJS.ErrnoException) => {
|
|
116
|
+
const message = error.code === "EADDRINUSE" ? `port ${port} is in use; choose another with --port=N` : error.message;
|
|
117
|
+
console.error(message);
|
|
118
|
+
process.exit(1);
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
server.listen(port, "127.0.0.1", () => {
|
|
122
|
+
const url = `http://127.0.0.1:${port}`;
|
|
123
|
+
console.log(`Circuit studio at ${url}, watching ${root}. Stop it with Ctrl+C.`);
|
|
124
|
+
if (options.open) openBrowser(url);
|
|
125
|
+
});
|
|
126
|
+
}
|