@takodotid/circuit 0.43.1 → 1.0.0-rc.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/README.md +115 -37
- package/package.json +21 -58
- package/src/checks/across.ts +402 -0
- package/src/checks/built-in.ts +256 -0
- package/src/checks/device.ts +436 -0
- package/src/checks/index.ts +53 -0
- package/src/cli/advisories.ts +108 -0
- package/src/cli/app.ts +71 -0
- package/src/cli/editor-types.generated.ts +30 -0
- package/src/cli/editor-types.ts +69 -0
- package/src/cli/format.ts +124 -0
- package/src/cli/git-hooks.ts +79 -0
- package/src/cli/index.ts +350 -1170
- package/src/cli/migrate.ts +554 -0
- package/src/cli/project-commands.ts +656 -0
- package/src/cli/releases.ts +374 -0
- package/src/cli/remember.ts +339 -0
- package/src/cli/rename.ts +27 -33
- package/src/cli/secrets-password.ts +60 -0
- package/src/cli/secrets.ts +258 -0
- package/src/cli/setup.ts +300 -0
- package/src/cli/signing.ts +23 -0
- package/src/cli/text-editor.ts +268 -0
- package/src/cli/ui.ts +298 -0
- package/src/cli/version.ts +2 -0
- package/src/cli/versions.ts +158 -0
- package/src/config/address.ts +102 -0
- package/src/config/circuit.d.ts +30 -0
- package/src/config/common.ts +65 -0
- package/src/config/communities.ts +479 -0
- package/src/config/define.ts +684 -0
- package/src/config/device.ts +164 -0
- package/src/config/firewall.ts +150 -0
- package/src/config/index.ts +22 -0
- package/src/{schema → config}/interfaces.ts +121 -85
- package/src/config/keys.ts +434 -0
- package/src/config/management.ts +61 -0
- package/src/config/merge.ts +335 -0
- package/src/config/models.ts +70 -0
- package/src/config/monitoring.ts +64 -0
- package/src/config/network.ts +180 -0
- package/src/config/presets.ts +149 -0
- package/src/config/routing.ts +401 -0
- package/src/config/services.ts +51 -0
- package/src/config/switching.ts +84 -0
- package/src/config/system.ts +103 -0
- package/src/{schema → config}/time-zones.ts +1 -1
- package/src/core/address.ts +1 -1
- package/src/core/cabling.ts +8 -43
- package/src/core/graph.ts +40 -0
- package/src/{integrations → core}/registries.ts +5 -3
- package/src/core/secrets-file.ts +539 -0
- package/src/core/secrets.ts +83 -64
- package/src/core/topology.ts +103 -146
- package/src/core/utils.ts +12 -1
- package/src/model.ts +83 -0
- package/src/platforms/derive.ts +64 -37
- package/src/platforms/finding.ts +9 -0
- package/src/platforms/fortios/firewall.ts +76 -64
- package/src/platforms/fortios/index.ts +189 -125
- package/src/platforms/fortios/names.ts +51 -18
- package/src/platforms/fortios/render.ts +69 -62
- package/src/platforms/fortios/routing.ts +89 -58
- package/src/platforms/fortios/vdoms.ts +47 -16
- package/src/platforms/junos/TRAPS.md +2 -2
- package/src/platforms/junos/bgp.ts +63 -42
- package/src/platforms/junos/index.ts +122 -89
- package/src/platforms/junos/render.ts +139 -104
- package/src/platforms/platform.ts +10 -8
- package/src/platforms/raisecom-ros/TRAPS.md +1 -1
- package/src/platforms/raisecom-ros/index.ts +89 -70
- package/src/platforms/raisecom-ros/render.ts +49 -45
- package/src/platforms/routeros/TRAPS.md +3 -3
- package/src/platforms/routeros/index.ts +121 -60
- package/src/platforms/routeros/render.ts +214 -195
- package/src/platforms/vrp/TRAPS.md +1 -1
- package/src/platforms/vrp/index.ts +74 -50
- package/src/platforms/vrp/render.ts +67 -56
- package/src/project/project.ts +725 -0
- package/src/studio/app/env.d.ts +9 -0
- package/src/studio/app/index.html +18 -0
- package/src/studio/app/src/App.vue +95 -0
- package/src/studio/app/src/components/AddressesView.vue +38 -0
- package/src/studio/app/src/components/CablesView.vue +29 -0
- package/src/studio/app/src/components/CompareView.vue +160 -0
- package/src/studio/app/src/components/DataTable.vue +49 -0
- package/src/studio/app/src/components/DeviceView.vue +237 -0
- package/src/studio/app/src/components/DevicesView.vue +32 -0
- package/src/studio/app/src/components/Faceplate.vue +75 -0
- package/src/studio/app/src/components/GraphView.vue +282 -0
- package/src/studio/app/src/components/LineDiff.vue +24 -0
- package/src/studio/app/src/components/ProblemsView.vue +42 -0
- package/src/studio/app/src/components/SearchView.vue +72 -0
- package/src/studio/app/src/components/SecretsView.vue +112 -0
- package/src/studio/app/src/components/SessionsView.vue +35 -0
- package/src/studio/app/src/components/Settings.vue +95 -0
- package/src/studio/app/src/components/ToolsView.vue +118 -0
- package/src/studio/app/src/components/VlansView.vue +50 -0
- package/src/studio/app/src/lib/csv.ts +19 -0
- package/src/studio/{public/diff.js → app/src/lib/diff.ts} +40 -35
- package/src/studio/app/src/lib/format.ts +38 -0
- package/src/studio/app/src/lib/net.ts +68 -0
- package/src/studio/app/src/lib/secrets-crypto.ts +35 -0
- package/src/studio/app/src/lib/store.ts +71 -0
- package/src/studio/app/src/lib/tools.ts +217 -0
- package/src/studio/app/src/main.ts +9 -0
- package/src/studio/{public → app/src}/style.css +14 -4
- package/src/studio/app/tsconfig.json +17 -0
- package/src/studio/app/vite.config.ts +16 -0
- package/src/studio/assets.generated.ts +3 -0
- package/src/studio/data.ts +96 -188
- package/src/studio/git.ts +27 -43
- package/src/studio/server.ts +196 -107
- package/src/studio/types.ts +141 -0
- package/src/transport/console.ts +3 -3
- package/src/transport/ssh.ts +1 -1
- package/dist/chunks/index-3b343xva.js +0 -177
- package/dist/chunks/index-6wb41q67.js +0 -62
- package/dist/chunks/index-n7w3tg5y.js +0 -149
- package/dist/chunks/index-yzw86rc8.js +0 -921
- package/dist/chunks/new-w3zys5y4.js +0 -172
- package/dist/chunks/server-cxr9ynxc.js +0 -259
- package/dist/cli/index.js +0 -12922
- package/dist/helpers/index.js +0 -881
- package/dist/index.js +0 -28
- package/dist/types/core/address-space.d.ts +0 -62
- package/dist/types/core/address.d.ts +0 -44
- package/dist/types/core/cabling.d.ts +0 -41
- package/dist/types/core/cross-checks.d.ts +0 -28
- package/dist/types/core/define.d.ts +0 -79
- package/dist/types/core/project-files.d.ts +0 -12
- package/dist/types/core/schema-docs.d.ts +0 -25
- package/dist/types/core/secrets.d.ts +0 -62
- package/dist/types/core/topology.d.ts +0 -96
- package/dist/types/core/utils.d.ts +0 -10
- package/dist/types/core/validator.d.ts +0 -23
- package/dist/types/helpers/bgp.d.ts +0 -195
- package/dist/types/helpers/cables.d.ts +0 -69
- package/dist/types/helpers/checks.d.ts +0 -36
- package/dist/types/helpers/exchange.d.ts +0 -55
- package/dist/types/helpers/filters.d.ts +0 -51
- package/dist/types/helpers/index.d.ts +0 -14
- package/dist/types/helpers/route-server.d.ts +0 -52
- package/dist/types/helpers/upstreams.d.ts +0 -192
- package/dist/types/index.d.ts +0 -10
- package/dist/types/integrations/librenms.d.ts +0 -60
- package/dist/types/integrations/netbox.d.ts +0 -89
- package/dist/types/integrations/peeringdb.d.ts +0 -24
- package/dist/types/integrations/prometheus.d.ts +0 -24
- package/dist/types/integrations/registries.d.ts +0 -8
- package/dist/types/platforms/bgp-wait.d.ts +0 -24
- package/dist/types/platforms/catalog.d.ts +0 -109
- package/dist/types/platforms/derive.d.ts +0 -17
- package/dist/types/platforms/fortios/firewall.d.ts +0 -47
- package/dist/types/platforms/fortios/index.d.ts +0 -140
- package/dist/types/platforms/fortios/models.d.ts +0 -14
- package/dist/types/platforms/fortios/names.d.ts +0 -52
- package/dist/types/platforms/fortios/plan.d.ts +0 -114
- package/dist/types/platforms/fortios/render.d.ts +0 -77
- package/dist/types/platforms/fortios/routing.d.ts +0 -56
- package/dist/types/platforms/fortios/tree.d.ts +0 -37
- package/dist/types/platforms/fortios/vdoms.d.ts +0 -50
- package/dist/types/platforms/index.d.ts +0 -13
- package/dist/types/platforms/junos/bgp.d.ts +0 -44
- package/dist/types/platforms/junos/index.d.ts +0 -114
- package/dist/types/platforms/junos/models.d.ts +0 -14
- package/dist/types/platforms/junos/render.d.ts +0 -154
- package/dist/types/platforms/line-diff.d.ts +0 -67
- package/dist/types/platforms/platform.d.ts +0 -135
- package/dist/types/platforms/raisecom-ros/index.d.ts +0 -71
- package/dist/types/platforms/raisecom-ros/models.d.ts +0 -14
- package/dist/types/platforms/raisecom-ros/render.d.ts +0 -41
- package/dist/types/platforms/routeros/export.d.ts +0 -59
- package/dist/types/platforms/routeros/index.d.ts +0 -116
- package/dist/types/platforms/routeros/models.d.ts +0 -18
- package/dist/types/platforms/routeros/plan.d.ts +0 -58
- package/dist/types/platforms/routeros/render.d.ts +0 -250
- package/dist/types/platforms/vrp/index.d.ts +0 -83
- package/dist/types/platforms/vrp/models.d.ts +0 -12
- package/dist/types/platforms/vrp/render.d.ts +0 -96
- package/dist/types/schema/common.d.ts +0 -30
- package/dist/types/schema/device.d.ts +0 -126
- package/dist/types/schema/firewall.d.ts +0 -131
- package/dist/types/schema/index.d.ts +0 -8
- package/dist/types/schema/interfaces.d.ts +0 -205
- package/dist/types/schema/policy.d.ts +0 -107
- package/dist/types/schema/routing.d.ts +0 -161
- package/dist/types/schema/system.d.ts +0 -198
- package/dist/types/schema/time-zones.d.ts +0 -2
- package/dist/types/transport/console.d.ts +0 -115
- package/dist/types/transport/path-check.d.ts +0 -32
- package/dist/types/transport/ssh.d.ts +0 -27
- package/src/cli/graph-format.ts +0 -91
- package/src/cli/new.ts +0 -234
- package/src/core/address-space.ts +0 -320
- package/src/core/cross-checks.ts +0 -369
- package/src/core/define.ts +0 -124
- package/src/core/project-files.ts +0 -35
- package/src/core/schema-docs.ts +0 -174
- package/src/core/validator.ts +0 -508
- package/src/helpers/bgp.ts +0 -473
- package/src/helpers/cables.ts +0 -74
- package/src/helpers/checks.ts +0 -201
- package/src/helpers/exchange.ts +0 -123
- package/src/helpers/filters.ts +0 -107
- package/src/helpers/index.ts +0 -25
- package/src/helpers/route-server.ts +0 -212
- package/src/helpers/upstreams.ts +0 -369
- package/src/index.ts +0 -13
- package/src/integrations/librenms.ts +0 -291
- package/src/integrations/netbox.ts +0 -620
- package/src/integrations/peeringdb.ts +0 -173
- package/src/integrations/prometheus.ts +0 -90
- package/src/schema/common.ts +0 -41
- package/src/schema/device.ts +0 -145
- package/src/schema/firewall.ts +0 -140
- package/src/schema/index.ts +0 -8
- package/src/schema/policy.ts +0 -115
- package/src/schema/routing.ts +0 -173
- package/src/schema/system.ts +0 -203
- package/src/studio/public/app.js +0 -186
- package/src/studio/public/compare.js +0 -250
- package/src/studio/public/dom.js +0 -138
- package/src/studio/public/evaluate.js +0 -687
- package/src/studio/public/export.js +0 -130
- package/src/studio/public/graph.js +0 -1541
- package/src/studio/public/index.html +0 -83
- package/src/studio/public/net.js +0 -134
- package/src/studio/public/tables.js +0 -1289
- package/src/studio/public/tools.js +0 -610
- package/templates/colocation/checks.ts +0 -53
- package/templates/colocation/circuit.config.ts +0 -9
- package/templates/colocation/edge-01.ts +0 -133
- package/templates/colocation/routing.ts +0 -13
- package/templates/colocation/tenancy.ts +0 -76
- package/templates/colocation/tenants/acme.ts +0 -19
- package/templates/colocation/tenants/index.ts +0 -6
- package/templates/colocation/tor-01.ts +0 -33
- package/templates/edge-router/checks.ts +0 -20
- package/templates/edge-router/circuit.config.ts +0 -8
- package/templates/edge-router/edge-01.ts +0 -110
- package/templates/edge-router/routing.ts +0 -13
- package/templates/project/AGENTS.md +0 -51
- package/templates/project/CLAUDE.md +0 -1
- package/templates/project/README.md +0 -16
- package/templates/project/gitignore +0 -5
- package/templates/project/tsconfig.json +0 -13
- package/templates/single-site/checks.ts +0 -26
- package/templates/single-site/circuit.config.ts +0 -8
- package/templates/single-site/router.ts +0 -86
- package/templates/single-site/site.ts +0 -18
- package/templates/single-site/switch.ts +0 -34
- /package/src/studio/{public → app/public}/favicon.svg +0 -0
- /package/src/studio/{public → app/public}/special-gothic-latin-ext.woff2 +0 -0
- /package/src/studio/{public → app/public}/special-gothic-latin.woff2 +0 -0
- /package/src/studio/{public → app/public}/special-gothic-license.txt +0 -0
|
@@ -1,172 +0,0 @@
|
|
|
1
|
-
// src/cli/new.ts
|
|
2
|
-
import { spawnSync } from "node:child_process";
|
|
3
|
-
import { cpSync, existsSync, readdirSync, readFileSync, renameSync, writeFileSync } from "node:fs";
|
|
4
|
-
import { basename, dirname, join, relative, resolve } from "node:path";
|
|
5
|
-
import { fileURLToPath } from "node:url";
|
|
6
|
-
import * as prompts from "@clack/prompts";
|
|
7
|
-
|
|
8
|
-
class Scaffold {
|
|
9
|
-
static ROOT = Scaffold.packageRoot();
|
|
10
|
-
static TEMPLATES = join(Scaffold.ROOT, "templates");
|
|
11
|
-
static CIRCUIT = JSON.parse(readFileSync(join(Scaffold.ROOT, "package.json"), "utf8"));
|
|
12
|
-
static MANAGERS = {
|
|
13
|
-
npm: { install: "npm install", run: "npx circuit", types: "node" },
|
|
14
|
-
pnpm: { install: "pnpm install", run: "pnpm circuit", types: "node" },
|
|
15
|
-
bun: { install: "bun install", run: "bun circuit", types: "bun" }
|
|
16
|
-
};
|
|
17
|
-
static PATTERNS = {
|
|
18
|
-
"single-site": {
|
|
19
|
-
title: "A single site",
|
|
20
|
-
fits: "a home, an office or an internal network: a router and a switch, no BGP",
|
|
21
|
-
asn: false
|
|
22
|
-
},
|
|
23
|
-
"edge-router": { title: "An edge router", fits: "your own AS number, with an IP transit and an internet exchange", asn: true },
|
|
24
|
-
colocation: {
|
|
25
|
-
title: "Colocation with tenants",
|
|
26
|
-
fits: "a provider whose customers each get their own VLANs, addresses and ports",
|
|
27
|
-
asn: true
|
|
28
|
-
}
|
|
29
|
-
};
|
|
30
|
-
static EXAMPLE_ASN = "64500";
|
|
31
|
-
static packageRoot() {
|
|
32
|
-
let directory = dirname(fileURLToPath(import.meta.url));
|
|
33
|
-
while (!existsSync(join(directory, "templates"))) {
|
|
34
|
-
const parent = dirname(directory);
|
|
35
|
-
if (parent === directory)
|
|
36
|
-
throw new Error("Circuit's templates are missing from its package");
|
|
37
|
-
directory = parent;
|
|
38
|
-
}
|
|
39
|
-
return directory;
|
|
40
|
-
}
|
|
41
|
-
static detectManager() {
|
|
42
|
-
const agent = process.env.npm_config_user_agent ?? "";
|
|
43
|
-
if (agent.startsWith("pnpm/"))
|
|
44
|
-
return "pnpm";
|
|
45
|
-
if (agent.startsWith("bun/") || process.versions.bun)
|
|
46
|
-
return "bun";
|
|
47
|
-
return "npm";
|
|
48
|
-
}
|
|
49
|
-
static fail(message) {
|
|
50
|
-
console.error(message);
|
|
51
|
-
process.exit(1);
|
|
52
|
-
}
|
|
53
|
-
static filesIn(directory) {
|
|
54
|
-
return readdirSync(directory, { recursive: true, withFileTypes: true }).filter((entry) => entry.isFile()).map((entry) => relative(directory, join(entry.parentPath, entry.name)));
|
|
55
|
-
}
|
|
56
|
-
static notEmpty(directory) {
|
|
57
|
-
const target = resolve(process.cwd(), directory);
|
|
58
|
-
return existsSync(target) && readdirSync(target).length ? `${target} is not empty` : undefined;
|
|
59
|
-
}
|
|
60
|
-
static async ask(question, fallback) {
|
|
61
|
-
if (process.stdin.isTTY !== true)
|
|
62
|
-
return fallback;
|
|
63
|
-
const answer = await question();
|
|
64
|
-
if (prompts.isCancel(answer)) {
|
|
65
|
-
prompts.cancel("Nothing created.");
|
|
66
|
-
process.exit(1);
|
|
67
|
-
}
|
|
68
|
-
return answer;
|
|
69
|
-
}
|
|
70
|
-
static async create(directoryArgument, options) {
|
|
71
|
-
const directory = directoryArgument ?? await Scaffold.ask(() => prompts.text({
|
|
72
|
-
message: "Directory for the project",
|
|
73
|
-
placeholder: "my-network",
|
|
74
|
-
defaultValue: "my-network",
|
|
75
|
-
validate: (value) => Scaffold.notEmpty(value || "my-network")
|
|
76
|
-
}), "my-network");
|
|
77
|
-
const notEmpty = Scaffold.notEmpty(directory);
|
|
78
|
-
if (notEmpty)
|
|
79
|
-
Scaffold.fail(notEmpty);
|
|
80
|
-
const target = resolve(process.cwd(), directory);
|
|
81
|
-
const names = Object.keys(Scaffold.PATTERNS);
|
|
82
|
-
const pattern = options.pattern ?? await Scaffold.ask(() => prompts.select({
|
|
83
|
-
message: "Which is closest to your network?",
|
|
84
|
-
options: names.map((name) => ({
|
|
85
|
-
value: name,
|
|
86
|
-
label: Scaffold.PATTERNS[name].title,
|
|
87
|
-
hint: Scaffold.PATTERNS[name].fits
|
|
88
|
-
}))
|
|
89
|
-
}), names[0]);
|
|
90
|
-
if (!Object.hasOwn(Scaffold.PATTERNS, pattern))
|
|
91
|
-
Scaffold.fail(`no pattern ${pattern}; there are ${names.join(", ")}`);
|
|
92
|
-
const chosen = Scaffold.PATTERNS[pattern];
|
|
93
|
-
let asn = options.asn;
|
|
94
|
-
if (!asn && chosen.asn) {
|
|
95
|
-
asn = await Scaffold.ask(() => prompts.text({
|
|
96
|
-
message: "Your AS number, or press enter to keep the documentation example",
|
|
97
|
-
placeholder: Scaffold.EXAMPLE_ASN,
|
|
98
|
-
defaultValue: Scaffold.EXAMPLE_ASN,
|
|
99
|
-
validate: (value) => !value || /^\d+$/.test(value) ? undefined : `${value} is not an AS number`
|
|
100
|
-
}), Scaffold.EXAMPLE_ASN);
|
|
101
|
-
}
|
|
102
|
-
if (asn && !/^\d+$/.test(asn))
|
|
103
|
-
Scaffold.fail(`${asn} is not an AS number`);
|
|
104
|
-
const manager = options.use ?? Scaffold.detectManager();
|
|
105
|
-
if (!Object.hasOwn(Scaffold.MANAGERS, manager)) {
|
|
106
|
-
Scaffold.fail(`no package manager ${manager}; there are ${Object.keys(Scaffold.MANAGERS).join(", ")}`);
|
|
107
|
-
}
|
|
108
|
-
const { install, run, types } = Scaffold.MANAGERS[manager];
|
|
109
|
-
const onePassword = options.onePassword || await Scaffold.ask(() => prompts.confirm({ message: "Keep the secrets in 1Password?", initialValue: false }), false);
|
|
110
|
-
cpSync(join(Scaffold.TEMPLATES, pattern), target, { recursive: true });
|
|
111
|
-
cpSync(join(Scaffold.TEMPLATES, "project"), target, { recursive: true });
|
|
112
|
-
renameSync(join(target, "gitignore"), join(target, ".gitignore"));
|
|
113
|
-
for (const file of ["AGENTS.md", "README.md", "tsconfig.json"]) {
|
|
114
|
-
const path = join(target, file);
|
|
115
|
-
const text = readFileSync(path, "utf8").replaceAll("npx circuit", run).replaceAll("npm install", install).replaceAll('"types": ["node"]', `"types": ["${types}"]`);
|
|
116
|
-
writeFileSync(path, text);
|
|
117
|
-
}
|
|
118
|
-
const sources = Scaffold.filesIn(target).filter((file) => file.endsWith(".ts"));
|
|
119
|
-
const secrets = new Set;
|
|
120
|
-
for (const file of sources) {
|
|
121
|
-
const path = join(target, file);
|
|
122
|
-
let text = readFileSync(path, "utf8");
|
|
123
|
-
if (asn && asn !== Scaffold.EXAMPLE_ASN)
|
|
124
|
-
text = text.replaceAll(new RegExp(`\\b${Scaffold.EXAMPLE_ASN}\\b`, "g"), asn);
|
|
125
|
-
for (const [, name] of text.matchAll(/secret\("([A-Z0-9_]+)"\)/g))
|
|
126
|
-
secrets.add(name);
|
|
127
|
-
writeFileSync(path, text);
|
|
128
|
-
}
|
|
129
|
-
const env = [
|
|
130
|
-
"# Secrets for Circuit. Never commit this file.",
|
|
131
|
-
"# A value is the secret itself, or a 1Password reference, op://vault/item/field, read with the 1Password CLI.",
|
|
132
|
-
"# Put quotes around a value that has a # in it: without them, # starts a comment.",
|
|
133
|
-
...[...secrets].sort().map((name) => onePassword ? `${name}=op://Network/${name}/password` : `${name}=`),
|
|
134
|
-
""
|
|
135
|
-
];
|
|
136
|
-
writeFileSync(join(target, ".env"), env.join(`
|
|
137
|
-
`));
|
|
138
|
-
const name = basename(target).toLowerCase().replace(/[^a-z0-9-]+/g, "-");
|
|
139
|
-
const packageJson = {
|
|
140
|
-
name,
|
|
141
|
-
private: true,
|
|
142
|
-
type: "module",
|
|
143
|
-
dependencies: { "@takodotid/circuit": Scaffold.CIRCUIT.version },
|
|
144
|
-
devDependencies: { [`@types/${types}`]: Scaffold.CIRCUIT.devDependencies[`@types/${types}`] }
|
|
145
|
-
};
|
|
146
|
-
writeFileSync(join(target, "package.json"), JSON.stringify(packageJson, null, 4) + `
|
|
147
|
-
`);
|
|
148
|
-
if (options.install) {
|
|
149
|
-
const result = spawnSync(install, { cwd: target, stdio: "inherit", shell: true });
|
|
150
|
-
if (result.status !== 0)
|
|
151
|
-
console.log(`${install} failed; run it again in the project.`);
|
|
152
|
-
}
|
|
153
|
-
const steps = [
|
|
154
|
-
`cd ${directory}`,
|
|
155
|
-
onePassword ? `Point each line of .env at the item that holds it in 1Password, then run: ${run} secrets` : `Fill in each password in .env, then run: ${run} secrets`,
|
|
156
|
-
"Change the names, addresses and ports in the device files to your own.",
|
|
157
|
-
...chosen.asn && (!asn || asn === Scaffold.EXAMPLE_ASN) ? ["Put your own AS number in routing.ts."] : [],
|
|
158
|
-
`${run} validate`,
|
|
159
|
-
`${run} snapshot, then ${run} diff, to see what would change on each device`
|
|
160
|
-
];
|
|
161
|
-
console.log(`
|
|
162
|
-
Created ${directory} from the pattern "${chosen.title}".
|
|
163
|
-
|
|
164
|
-
Next:`);
|
|
165
|
-
steps.forEach((step, index) => console.log(` ${index + 1}. ${step}`));
|
|
166
|
-
console.log(`
|
|
167
|
-
What each file does: https://circuit.tako.id/patterns/${pattern}`);
|
|
168
|
-
}
|
|
169
|
-
}
|
|
170
|
-
export {
|
|
171
|
-
Scaffold
|
|
172
|
-
};
|
|
@@ -1,259 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
StudioModel
|
|
3
|
-
} from "./index-yzw86rc8.js";
|
|
4
|
-
|
|
5
|
-
// src/studio/server.ts
|
|
6
|
-
import { execFile as execFile2, spawn } from "node:child_process";
|
|
7
|
-
import { existsSync, readFileSync, watch } from "node:fs";
|
|
8
|
-
import { createServer } from "node:http";
|
|
9
|
-
import { extname, join as join2 } from "node:path";
|
|
10
|
-
|
|
11
|
-
// src/studio/git.ts
|
|
12
|
-
import { execFile } from "node:child_process";
|
|
13
|
-
import { mkdirSync, writeFileSync } from "node:fs";
|
|
14
|
-
import { basename, join } from "node:path";
|
|
15
|
-
|
|
16
|
-
class Git {
|
|
17
|
-
static FORMAT = "--format=%H%x1f%h%x1f%an%x1f%aI%x1f%s";
|
|
18
|
-
static environment() {
|
|
19
|
-
return Object.fromEntries(Object.entries(process.env).filter(([name]) => !name.startsWith("GIT_")));
|
|
20
|
-
}
|
|
21
|
-
static run(cwd, args) {
|
|
22
|
-
return new Promise((done, fail) => {
|
|
23
|
-
execFile("git", args, { cwd, env: Git.environment(), maxBuffer: 64 * 1024 * 1024 }, (error, stdout, stderr) => {
|
|
24
|
-
if (error)
|
|
25
|
-
fail(new Error((stderr || error.message).trim()));
|
|
26
|
-
else
|
|
27
|
-
done(stdout);
|
|
28
|
-
});
|
|
29
|
-
});
|
|
30
|
-
}
|
|
31
|
-
static commitsOf(text) {
|
|
32
|
-
return text.split(`
|
|
33
|
-
`).filter(Boolean).map((line) => {
|
|
34
|
-
const [commit = "", short = "", author = "", date = "", subject = ""] = line.split("\x1F");
|
|
35
|
-
return { commit, short, author, date, subject };
|
|
36
|
-
});
|
|
37
|
-
}
|
|
38
|
-
static async top(directory) {
|
|
39
|
-
try {
|
|
40
|
-
return (await Git.run(directory, ["rev-parse", "--show-toplevel"])).trim();
|
|
41
|
-
} catch {
|
|
42
|
-
return;
|
|
43
|
-
}
|
|
44
|
-
}
|
|
45
|
-
static async history(directory, files, limit = 50) {
|
|
46
|
-
if (!files.length)
|
|
47
|
-
return [];
|
|
48
|
-
return Git.commitsOf(await Git.run(directory, ["log", `-n${limit}`, Git.FORMAT, "--", ...files]));
|
|
49
|
-
}
|
|
50
|
-
static async references(directory) {
|
|
51
|
-
const names = (await Git.run(directory, [
|
|
52
|
-
"for-each-ref",
|
|
53
|
-
"--count=100",
|
|
54
|
-
"--sort=-committerdate",
|
|
55
|
-
"--format=%(refname:short)",
|
|
56
|
-
"refs/heads",
|
|
57
|
-
"refs/remotes",
|
|
58
|
-
"refs/tags"
|
|
59
|
-
])).split(`
|
|
60
|
-
`).filter((name) => name && !name.endsWith("/HEAD"));
|
|
61
|
-
return { names, commits: Git.commitsOf(await Git.run(directory, ["log", "-n30", Git.FORMAT])) };
|
|
62
|
-
}
|
|
63
|
-
static async commitOf(directory, name) {
|
|
64
|
-
if (!/^[A-Za-z0-9][\w./@^~-]*$/.test(name))
|
|
65
|
-
return;
|
|
66
|
-
try {
|
|
67
|
-
const [commit] = Git.commitsOf(await Git.run(directory, ["log", "-n1", Git.FORMAT, `${name}^{commit}`, "--"]));
|
|
68
|
-
return commit;
|
|
69
|
-
} catch {
|
|
70
|
-
return;
|
|
71
|
-
}
|
|
72
|
-
}
|
|
73
|
-
static async networkAt(configDirectory, configPath, commit, gather) {
|
|
74
|
-
const top = await Git.top(configDirectory);
|
|
75
|
-
if (!top)
|
|
76
|
-
throw new Error("the config is not in a git repository");
|
|
77
|
-
const cache = join(configDirectory, ".circuit", "cache");
|
|
78
|
-
mkdirSync(cache, { recursive: true });
|
|
79
|
-
writeFileSync(join(cache, ".gitignore"), `# Written by Circuit Studio: checkouts of other commits, removed once read.
|
|
80
|
-
*
|
|
81
|
-
`);
|
|
82
|
-
await Git.run(top, ["worktree", "prune"]);
|
|
83
|
-
const checkout = join(cache, commit.slice(0, 12));
|
|
84
|
-
await Git.run(top, ["-c", "core.longpaths=true", "worktree", "add", "--detach", "--force", checkout, commit]);
|
|
85
|
-
try {
|
|
86
|
-
const prefix = (await Git.run(configDirectory, ["rev-parse", "--show-prefix"])).trim();
|
|
87
|
-
return await gather(join(checkout, prefix, basename(configPath)));
|
|
88
|
-
} finally {
|
|
89
|
-
await Git.run(top, ["-c", "core.longpaths=true", "worktree", "remove", "--force", checkout]).catch(() => {});
|
|
90
|
-
}
|
|
91
|
-
}
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
// src/studio/server.ts
|
|
95
|
-
class StudioServer {
|
|
96
|
-
static IGNORED = /(^|[\\/])(node_modules|\.git|\.circuit[\\/]cache)([\\/]|$)/;
|
|
97
|
-
static CONTENT_TYPES = {
|
|
98
|
-
".html": "text/html; charset=utf-8",
|
|
99
|
-
".css": "text/css; charset=utf-8",
|
|
100
|
-
".js": "text/javascript; charset=utf-8",
|
|
101
|
-
".svg": "image/svg+xml",
|
|
102
|
-
".woff2": "font/woff2"
|
|
103
|
-
};
|
|
104
|
-
static isLoopback(host) {
|
|
105
|
-
return host === "localhost" || host === "::1" || /^127\.\d+\.\d+\.\d+$/.test(host);
|
|
106
|
-
}
|
|
107
|
-
static hostAllowed(header, port, allowHosts) {
|
|
108
|
-
if (!header)
|
|
109
|
-
return false;
|
|
110
|
-
if (header === `127.0.0.1:${port}` || header === `localhost:${port}` || header === `[::1]:${port}`)
|
|
111
|
-
return true;
|
|
112
|
-
const name = header.replace(/:\d+$/, "").toLowerCase();
|
|
113
|
-
return allowHosts.some((allowed) => {
|
|
114
|
-
const wanted = allowed.toLowerCase();
|
|
115
|
-
return wanted === header.toLowerCase() || wanted === name;
|
|
116
|
-
});
|
|
117
|
-
}
|
|
118
|
-
static gather(configPath) {
|
|
119
|
-
const cli = process.argv[1];
|
|
120
|
-
const args = [...process.execArgv, cli, "studio", "--json", `--config=${configPath}`];
|
|
121
|
-
return new Promise((done) => {
|
|
122
|
-
execFile2(process.execPath, args, { maxBuffer: 256 * 1024 * 1024 }, (error, stdout, stderr) => {
|
|
123
|
-
if (!error)
|
|
124
|
-
return done(stdout);
|
|
125
|
-
const message = (stderr || error.message).trim();
|
|
126
|
-
done(JSON.stringify({ error: message }));
|
|
127
|
-
});
|
|
128
|
-
});
|
|
129
|
-
}
|
|
130
|
-
static openBrowser(url) {
|
|
131
|
-
const [command, args] = process.platform === "win32" ? ["cmd", ["/c", "start", "", url]] : process.platform === "darwin" ? ["open", [url]] : ["xdg-open", [url]];
|
|
132
|
-
const child = spawn(command, args, { stdio: "ignore", detached: true });
|
|
133
|
-
child.on("error", () => {});
|
|
134
|
-
child.unref();
|
|
135
|
-
}
|
|
136
|
-
static async start(options) {
|
|
137
|
-
const { configPath, root, port } = options;
|
|
138
|
-
const installed = StudioModel.packageRoot();
|
|
139
|
-
if (!installed)
|
|
140
|
-
throw new Error("cannot find where Circuit is installed, so Studio has no page to serve");
|
|
141
|
-
const pages = join2(installed, "src", "studio", "public");
|
|
142
|
-
let data = await StudioServer.gather(configPath);
|
|
143
|
-
const past = new Map;
|
|
144
|
-
const answer = async (response, work) => {
|
|
145
|
-
let body;
|
|
146
|
-
try {
|
|
147
|
-
const result = await work();
|
|
148
|
-
body = typeof result === "string" ? result : JSON.stringify(result);
|
|
149
|
-
} catch (error) {
|
|
150
|
-
body = JSON.stringify({ error: error.message });
|
|
151
|
-
}
|
|
152
|
-
response.writeHead(200, { "content-type": "application/json", "cache-control": "no-store" }).end(body);
|
|
153
|
-
};
|
|
154
|
-
const listeners = new Set;
|
|
155
|
-
const tell = (message) => {
|
|
156
|
-
for (const listener of listeners)
|
|
157
|
-
listener.write(`data: ${message}
|
|
158
|
-
|
|
159
|
-
`);
|
|
160
|
-
};
|
|
161
|
-
const server = createServer((request, response) => {
|
|
162
|
-
response.setHeader("x-robots-tag", "noindex, nofollow, noarchive");
|
|
163
|
-
if (!StudioServer.hostAllowed(request.headers.host, port, options.allowHosts)) {
|
|
164
|
-
response.writeHead(403).end();
|
|
165
|
-
return;
|
|
166
|
-
}
|
|
167
|
-
const url = new URL(request.url ?? "/", "http://studio");
|
|
168
|
-
const path = url.pathname;
|
|
169
|
-
if (path === "/history") {
|
|
170
|
-
answer(response, async () => {
|
|
171
|
-
const name = url.searchParams.get("device") ?? "";
|
|
172
|
-
const files = JSON.parse(data).files?.[name];
|
|
173
|
-
if (!files)
|
|
174
|
-
throw new Error(`no device ${name}`);
|
|
175
|
-
return Git.history(root, [files.source, files.snapshot].filter((file) => Boolean(file)));
|
|
176
|
-
});
|
|
177
|
-
return;
|
|
178
|
-
}
|
|
179
|
-
if (path === "/refs") {
|
|
180
|
-
answer(response, () => Git.references(root));
|
|
181
|
-
return;
|
|
182
|
-
}
|
|
183
|
-
if (path === "/compare") {
|
|
184
|
-
answer(response, async () => {
|
|
185
|
-
const asked = url.searchParams.get("ref") ?? "";
|
|
186
|
-
const commit = await Git.commitOf(root, asked);
|
|
187
|
-
if (!commit)
|
|
188
|
-
throw new Error(`${asked} names no commit here`);
|
|
189
|
-
if (!past.has(commit.commit))
|
|
190
|
-
past.set(commit.commit, Git.networkAt(root, configPath, commit.commit, StudioServer.gather));
|
|
191
|
-
const network = await past.get(commit.commit);
|
|
192
|
-
if (network.startsWith('{"error"'))
|
|
193
|
-
past.delete(commit.commit);
|
|
194
|
-
return `{"commit":${JSON.stringify(commit)},"network":${network}}`;
|
|
195
|
-
});
|
|
196
|
-
return;
|
|
197
|
-
}
|
|
198
|
-
if (path === "/robots.txt") {
|
|
199
|
-
response.writeHead(200, { "content-type": "text/plain; charset=utf-8" }).end(`User-agent: *
|
|
200
|
-
Disallow: /
|
|
201
|
-
`);
|
|
202
|
-
return;
|
|
203
|
-
}
|
|
204
|
-
if (path === "/data") {
|
|
205
|
-
response.writeHead(200, { "content-type": "application/json", "cache-control": "no-store" }).end(data);
|
|
206
|
-
return;
|
|
207
|
-
}
|
|
208
|
-
if (path === "/events") {
|
|
209
|
-
response.writeHead(200, {
|
|
210
|
-
"content-type": "text/event-stream",
|
|
211
|
-
"cache-control": "no-store",
|
|
212
|
-
connection: "keep-alive",
|
|
213
|
-
"x-accel-buffering": "no"
|
|
214
|
-
});
|
|
215
|
-
response.write(`: connected
|
|
216
|
-
|
|
217
|
-
`);
|
|
218
|
-
listeners.add(response);
|
|
219
|
-
request.on("close", () => listeners.delete(response));
|
|
220
|
-
return;
|
|
221
|
-
}
|
|
222
|
-
const name = path === "/" ? "index.html" : path.slice(1);
|
|
223
|
-
const type = StudioServer.CONTENT_TYPES[extname(name)];
|
|
224
|
-
const file = join2(pages, name);
|
|
225
|
-
if (!/^[a-z-]+\.[a-z0-9]+$/.test(name) || !type || !existsSync(file)) {
|
|
226
|
-
response.writeHead(404).end();
|
|
227
|
-
return;
|
|
228
|
-
}
|
|
229
|
-
response.writeHead(200, { "content-type": type, "cache-control": "no-store" }).end(readFileSync(file));
|
|
230
|
-
});
|
|
231
|
-
let timer;
|
|
232
|
-
watch(root, { recursive: true }, (_event, filename) => {
|
|
233
|
-
if (filename && StudioServer.IGNORED.test(filename))
|
|
234
|
-
return;
|
|
235
|
-
clearTimeout(timer);
|
|
236
|
-
timer = setTimeout(async () => {
|
|
237
|
-
data = await StudioServer.gather(configPath);
|
|
238
|
-
tell("changed");
|
|
239
|
-
console.log(`${new Date().toLocaleTimeString()} ${filename ?? "a file"} changed, read again`);
|
|
240
|
-
}, 300);
|
|
241
|
-
});
|
|
242
|
-
watch(pages, () => tell("page"));
|
|
243
|
-
server.on("error", (error) => {
|
|
244
|
-
const message = error.code === "EADDRINUSE" ? `port ${port} is in use; choose another with --port=N` : error.message;
|
|
245
|
-
console.error(message);
|
|
246
|
-
process.exit(1);
|
|
247
|
-
});
|
|
248
|
-
server.listen(port, options.host, () => {
|
|
249
|
-
const local = StudioServer.isLoopback(options.host);
|
|
250
|
-
const url = local ? `http://127.0.0.1:${port}` : `http://${options.allowHosts[0] ?? options.host}`;
|
|
251
|
-
console.log(`Circuit Studio at ${url}, listening on ${options.host}:${port}, watching ${root}. Stop it with Ctrl+C.`);
|
|
252
|
-
if (options.open && local)
|
|
253
|
-
StudioServer.openBrowser(url);
|
|
254
|
-
});
|
|
255
|
-
}
|
|
256
|
-
}
|
|
257
|
-
export {
|
|
258
|
-
StudioServer
|
|
259
|
-
};
|