@takodotid/circuit 0.34.3 → 0.35.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 +13 -14
- package/dist/chunks/index-20kcjxjg.js +174 -0
- package/dist/chunks/index-2r4bbhac.js +977 -0
- package/dist/chunks/index-n7w3tg5y.js +149 -0
- package/dist/chunks/new-w3zys5y4.js +172 -0
- package/dist/chunks/server-gzsf5me5.js +259 -0
- package/dist/cli/index.js +11345 -9024
- package/dist/helpers/index.js +594 -0
- package/dist/index.js +9 -28
- package/dist/types/core/{addresses.d.ts → address-space.d.ts} +23 -7
- package/dist/types/core/address.d.ts +44 -0
- package/dist/types/core/cabling.d.ts +41 -0
- package/dist/types/core/cross-checks.d.ts +28 -0
- package/dist/types/core/define.d.ts +25 -3
- package/dist/types/core/project-files.d.ts +12 -0
- package/dist/types/core/schema-docs.d.ts +13 -4
- package/dist/types/core/secrets.d.ts +62 -21
- package/dist/types/core/{graph.d.ts → topology.d.ts} +32 -12
- package/dist/types/core/utils.d.ts +8 -0
- package/dist/types/core/validator.d.ts +19 -0
- package/dist/types/helpers/bgp.d.ts +97 -0
- package/dist/types/helpers/checks.d.ts +32 -0
- package/dist/types/helpers/exchange.d.ts +49 -0
- package/dist/types/helpers/filters.d.ts +51 -0
- package/dist/types/helpers/index.d.ts +10 -0
- package/dist/types/helpers/route-server.d.ts +52 -0
- package/dist/types/index.d.ts +5 -3
- package/dist/types/integrations/librenms.d.ts +60 -0
- package/dist/types/integrations/netbox.d.ts +89 -0
- package/dist/types/integrations/peeringdb.d.ts +24 -0
- package/dist/types/integrations/prometheus.d.ts +24 -0
- package/dist/types/integrations/registries.d.ts +8 -0
- package/dist/types/platforms/bgp-wait.d.ts +18 -0
- package/dist/types/platforms/catalog.d.ts +109 -0
- package/dist/types/platforms/derive.d.ts +17 -0
- package/dist/types/platforms/fortios/firewall.d.ts +45 -0
- package/dist/types/platforms/fortios/index.d.ts +134 -0
- package/dist/types/platforms/fortios/names.d.ts +52 -0
- package/dist/types/platforms/fortios/plan.d.ts +104 -0
- package/dist/types/platforms/fortios/render.d.ts +75 -0
- package/dist/types/platforms/fortios/routing.d.ts +56 -0
- package/dist/types/platforms/fortios/tree.d.ts +37 -0
- package/dist/types/platforms/index.d.ts +13 -0
- package/dist/types/platforms/junos/bgp.d.ts +44 -0
- package/dist/types/platforms/junos/index.d.ts +114 -0
- package/dist/types/platforms/junos/render.d.ts +154 -0
- package/dist/types/platforms/line-diff.d.ts +67 -0
- package/dist/types/platforms/platform.d.ts +120 -0
- package/dist/types/platforms/raisecom-ros/index.d.ts +71 -0
- package/dist/types/platforms/raisecom-ros/render.d.ts +41 -0
- package/dist/types/platforms/routeros/export.d.ts +59 -0
- package/dist/types/platforms/routeros/index.d.ts +96 -0
- package/dist/types/{adapters/devices → platforms}/routeros/models.d.ts +4 -0
- package/dist/types/platforms/routeros/plan.d.ts +54 -0
- package/dist/types/platforms/routeros/render.d.ts +240 -0
- package/dist/types/platforms/vrp/index.d.ts +83 -0
- package/dist/types/platforms/vrp/render.d.ts +96 -0
- package/dist/types/schema/common.d.ts +12 -6
- package/dist/types/schema/index.d.ts +1 -0
- package/dist/types/schema/interfaces.d.ts +14 -6
- package/dist/types/schema/policy.d.ts +18 -10
- package/dist/types/schema/routing.d.ts +14 -1
- package/dist/types/schema/system.d.ts +16 -6
- package/dist/types/schema/time-zones.d.ts +2 -0
- package/dist/types/transport/console.d.ts +79 -23
- package/dist/types/transport/path-check.d.ts +32 -0
- package/dist/types/transport/ssh.d.ts +17 -15
- package/package.json +17 -11
- package/src/cli/graph-format.ts +91 -0
- package/src/cli/index.ts +1074 -484
- package/src/cli/new.ts +208 -175
- package/src/cli/rename.ts +97 -91
- package/src/core/address-space.ts +320 -0
- package/src/core/address.ts +202 -0
- package/src/core/cabling.ts +154 -0
- package/src/core/cross-checks.ts +364 -0
- package/src/core/define.ts +26 -3
- package/src/core/project-files.ts +35 -0
- package/src/core/schema-docs.ts +138 -130
- package/src/core/secrets.ts +170 -76
- package/src/core/topology.ts +656 -0
- package/src/core/utils.ts +37 -0
- package/src/core/validator.ts +407 -0
- package/src/helpers/bgp.ts +301 -0
- package/src/helpers/checks.ts +178 -0
- package/src/helpers/exchange.ts +117 -0
- package/src/helpers/filters.ts +107 -0
- package/src/helpers/index.ts +12 -0
- package/src/helpers/route-server.ts +212 -0
- package/src/index.ts +6 -3
- package/src/integrations/librenms.ts +291 -0
- package/src/integrations/netbox.ts +620 -0
- package/src/integrations/peeringdb.ts +173 -0
- package/src/integrations/prometheus.ts +90 -0
- package/src/integrations/registries.ts +25 -0
- package/src/platforms/bgp-wait.ts +41 -0
- package/src/platforms/catalog.ts +69 -0
- package/src/platforms/derive.ts +117 -0
- package/src/{adapters/devices → platforms}/fortios/TRAPS.md +6 -0
- package/src/platforms/fortios/firewall.ts +377 -0
- package/src/platforms/fortios/index.ts +1060 -0
- package/src/platforms/fortios/names.ts +130 -0
- package/src/platforms/fortios/plan.ts +929 -0
- package/src/platforms/fortios/render.ts +779 -0
- package/src/platforms/fortios/routing.ts +476 -0
- package/src/platforms/fortios/tree.ts +129 -0
- package/src/platforms/index.ts +36 -0
- package/src/{adapters/devices → platforms}/junos/TRAPS.md +8 -0
- package/src/platforms/junos/bgp.ts +293 -0
- package/src/platforms/junos/index.ts +1006 -0
- package/src/platforms/junos/render.ts +967 -0
- package/src/platforms/line-diff.ts +244 -0
- package/src/platforms/platform.ts +122 -0
- package/src/platforms/raisecom-ros/TRAPS.md +40 -0
- package/src/platforms/raisecom-ros/index.ts +731 -0
- package/src/platforms/raisecom-ros/render.ts +328 -0
- package/src/{adapters/devices → platforms}/routeros/TRAPS.md +16 -2
- package/src/platforms/routeros/export.ts +342 -0
- package/src/platforms/routeros/index.ts +636 -0
- package/src/{adapters/devices → platforms}/routeros/models.ts +2 -0
- package/src/platforms/routeros/plan.ts +444 -0
- package/src/platforms/routeros/render.ts +1483 -0
- package/src/platforms/vrp/TRAPS.md +45 -0
- package/src/platforms/vrp/index.ts +652 -0
- package/src/platforms/vrp/render.ts +615 -0
- package/src/schema/common.ts +15 -6
- package/src/schema/index.ts +1 -0
- package/src/schema/interfaces.ts +15 -5
- package/src/schema/policy.ts +22 -10
- package/src/schema/routing.ts +16 -1
- package/src/schema/system.ts +16 -6
- package/src/schema/time-zones.ts +482 -0
- package/src/studio/data.ts +172 -168
- package/src/studio/git.ts +95 -90
- package/src/studio/public/app.js +161 -150
- package/src/studio/public/compare.js +219 -212
- package/src/studio/public/diff.js +77 -75
- package/src/studio/public/dom.js +118 -108
- package/src/studio/public/evaluate.js +584 -545
- package/src/studio/public/export.js +120 -118
- package/src/studio/public/graph.js +1343 -1266
- package/src/studio/public/net.js +115 -109
- package/src/studio/public/tables.js +1135 -1083
- package/src/studio/public/tools.js +561 -529
- package/src/studio/server.ts +202 -196
- package/src/transport/console.ts +363 -271
- package/src/transport/path-check.ts +91 -0
- package/src/transport/ssh.ts +98 -94
- package/templates/colocation/checks.ts +11 -6
- package/templates/colocation/edge-01.ts +9 -9
- package/templates/colocation/routing.ts +2 -2
- package/templates/colocation/tenancy.ts +3 -3
- package/templates/colocation/tor-01.ts +3 -3
- package/templates/edge-router/checks.ts +4 -4
- package/templates/edge-router/edge-01.ts +5 -5
- package/templates/edge-router/routing.ts +2 -2
- package/templates/project/AGENTS.md +5 -1
- package/templates/single-site/checks.ts +2 -2
- package/dist/chunks/index-47debt16.js +0 -956
- package/dist/chunks/index-v8zgr7wm.js +0 -128
- package/dist/chunks/new-ny89s7y7.js +0 -166
- package/dist/chunks/server-tjbb3rqc.js +0 -251
- package/dist/presets/index.js +0 -394
- package/dist/types/adapters/devices/catalog.d.ts +0 -86
- package/dist/types/adapters/devices/derive.d.ts +0 -7
- package/dist/types/adapters/devices/fortios/firewall.d.ts +0 -23
- package/dist/types/adapters/devices/fortios/index.d.ts +0 -58
- package/dist/types/adapters/devices/fortios/names.d.ts +0 -48
- package/dist/types/adapters/devices/fortios/plan.d.ts +0 -28
- package/dist/types/adapters/devices/fortios/render.d.ts +0 -38
- package/dist/types/adapters/devices/fortios/routing.d.ts +0 -32
- package/dist/types/adapters/devices/fortios/tree.d.ts +0 -33
- package/dist/types/adapters/devices/index.d.ts +0 -3
- package/dist/types/adapters/devices/junos/bgp.d.ts +0 -11
- package/dist/types/adapters/devices/junos/index.d.ts +0 -42
- package/dist/types/adapters/devices/junos/render.d.ts +0 -52
- package/dist/types/adapters/devices/lines.d.ts +0 -56
- package/dist/types/adapters/devices/raisecom-ros/index.d.ts +0 -28
- package/dist/types/adapters/devices/routeros/export.d.ts +0 -39
- package/dist/types/adapters/devices/routeros/index.d.ts +0 -30
- package/dist/types/adapters/devices/routeros/plan.d.ts +0 -4
- package/dist/types/adapters/devices/routeros/render.d.ts +0 -100
- package/dist/types/adapters/devices/types.d.ts +0 -40
- package/dist/types/adapters/devices/vrp/index.d.ts +0 -27
- package/dist/types/adapters/registries/index.d.ts +0 -3
- package/dist/types/adapters/registries/librenms.d.ts +0 -15
- package/dist/types/adapters/registries/peeringdb.d.ts +0 -16
- package/dist/types/adapters/registries/prometheus.d.ts +0 -16
- package/dist/types/core/across.d.ts +0 -4
- package/dist/types/core/addr.d.ts +0 -34
- package/dist/types/core/files.d.ts +0 -6
- package/dist/types/core/merge.d.ts +0 -4
- package/dist/types/core/time.d.ts +0 -2
- package/dist/types/core/validate.d.ts +0 -2
- package/dist/types/presets/index.d.ts +0 -183
- package/src/adapters/devices/catalog.ts +0 -41
- package/src/adapters/devices/derive.ts +0 -59
- package/src/adapters/devices/fortios/firewall.ts +0 -362
- package/src/adapters/devices/fortios/index.ts +0 -923
- package/src/adapters/devices/fortios/names.ts +0 -115
- package/src/adapters/devices/fortios/plan.ts +0 -888
- package/src/adapters/devices/fortios/render.ts +0 -768
- package/src/adapters/devices/fortios/routing.ts +0 -444
- package/src/adapters/devices/fortios/tree.ts +0 -121
- package/src/adapters/devices/index.ts +0 -9
- package/src/adapters/devices/junos/bgp.ts +0 -269
- package/src/adapters/devices/junos/index.ts +0 -862
- package/src/adapters/devices/junos/render.ts +0 -872
- package/src/adapters/devices/lines.ts +0 -212
- package/src/adapters/devices/raisecom-ros/TRAPS.md +0 -26
- package/src/adapters/devices/raisecom-ros/index.ts +0 -700
- package/src/adapters/devices/routeros/export.ts +0 -321
- package/src/adapters/devices/routeros/index.ts +0 -346
- package/src/adapters/devices/routeros/plan.ts +0 -398
- package/src/adapters/devices/routeros/render.ts +0 -1490
- package/src/adapters/devices/types.ts +0 -38
- package/src/adapters/devices/vrp/TRAPS.md +0 -26
- package/src/adapters/devices/vrp/index.ts +0 -865
- package/src/adapters/registries/index.ts +0 -19
- package/src/adapters/registries/librenms.ts +0 -92
- package/src/adapters/registries/peeringdb.ts +0 -169
- package/src/adapters/registries/prometheus.ts +0 -71
- package/src/cli/graph.ts +0 -89
- package/src/core/across.ts +0 -350
- package/src/core/addr.ts +0 -174
- package/src/core/addresses.ts +0 -316
- package/src/core/files.ts +0 -32
- package/src/core/graph.ts +0 -640
- package/src/core/merge.ts +0 -19
- package/src/core/time.ts +0 -15
- package/src/core/validate.ts +0 -348
- package/src/presets/index.ts +0 -579
- /package/dist/types/{adapters/devices → platforms}/fortios/models.d.ts +0 -0
- /package/dist/types/{adapters/devices → platforms}/junos/models.d.ts +0 -0
- /package/dist/types/{adapters/devices → platforms}/raisecom-ros/models.d.ts +0 -0
- /package/dist/types/{adapters/devices → platforms}/vrp/models.d.ts +0 -0
- /package/src/{adapters/devices → platforms}/fortios/models.ts +0 -0
- /package/src/{adapters/devices → platforms}/junos/models.ts +0 -0
- /package/src/{adapters/devices → platforms}/raisecom-ros/models.ts +0 -0
- /package/src/{adapters/devices → platforms}/vrp/models.ts +0 -0
|
@@ -1,128 +0,0 @@
|
|
|
1
|
-
// src/core/addr.ts
|
|
2
|
-
var familyOf2 = (address) => address.includes(":") ? "ipv6" : "ipv4";
|
|
3
|
-
var bitsOf = (family) => family === "ipv4" ? 32 : 128;
|
|
4
|
-
function parse(address) {
|
|
5
|
-
if (!address.includes(":")) {
|
|
6
|
-
const octets = address.split(".");
|
|
7
|
-
const valid = octets.length === 4 && octets.every((octet) => /^\d{1,3}$/.test(octet) && Number(octet) <= 255);
|
|
8
|
-
if (!valid)
|
|
9
|
-
return null;
|
|
10
|
-
return octets.reduce((value, octet) => value << 8n | BigInt(octet), 0n);
|
|
11
|
-
}
|
|
12
|
-
const [before, after, extra] = address.split("::");
|
|
13
|
-
if (extra !== undefined)
|
|
14
|
-
return null;
|
|
15
|
-
const groupsOf = (text) => text ? text.split(":") : [];
|
|
16
|
-
const leading = groupsOf(before);
|
|
17
|
-
const trailing = groupsOf(after);
|
|
18
|
-
const zeros = after === undefined ? 0 : 8 - leading.length - trailing.length;
|
|
19
|
-
const groups = [...leading, ...Array(zeros).fill("0"), ...trailing];
|
|
20
|
-
const valid = groups.length === 8 && groups.every((group) => /^[0-9a-fA-F]{1,4}$/.test(group));
|
|
21
|
-
if (!valid)
|
|
22
|
-
return null;
|
|
23
|
-
return groups.reduce((value, group) => value << 16n | BigInt(parseInt(group, 16)), 0n);
|
|
24
|
-
}
|
|
25
|
-
function split(cidr) {
|
|
26
|
-
const [address, lengthText] = cidr.split("/");
|
|
27
|
-
const family = familyOf2(address);
|
|
28
|
-
const length = Number(lengthText);
|
|
29
|
-
const valid = parse(address) !== null && lengthText !== undefined && Number.isInteger(length) && length >= 0 && length <= bitsOf(family);
|
|
30
|
-
if (!valid)
|
|
31
|
-
throw new Error(`'${cidr}' is not an address with a length`);
|
|
32
|
-
return { addr: address, len: length, family };
|
|
33
|
-
}
|
|
34
|
-
var isAddress2 = (text) => parse(text) !== null;
|
|
35
|
-
function isCidr2(text) {
|
|
36
|
-
try {
|
|
37
|
-
split(text);
|
|
38
|
-
return true;
|
|
39
|
-
} catch {
|
|
40
|
-
return false;
|
|
41
|
-
}
|
|
42
|
-
}
|
|
43
|
-
var mask = (length, family) => {
|
|
44
|
-
const all = (1n << BigInt(bitsOf(family))) - 1n;
|
|
45
|
-
const hostBits = (1n << BigInt(bitsOf(family) - length)) - 1n;
|
|
46
|
-
return all ^ hostBits;
|
|
47
|
-
};
|
|
48
|
-
function format(value, family) {
|
|
49
|
-
if (family === "ipv4") {
|
|
50
|
-
return [24n, 16n, 8n, 0n].map((shift) => String(value >> shift & 255n)).join(".");
|
|
51
|
-
}
|
|
52
|
-
const groups = Array.from({ length: 8 }, (_, index) => (value >> BigInt(112 - index * 16) & 0xffffn).toString(16));
|
|
53
|
-
let longestStart = -1;
|
|
54
|
-
let longestLength = 0;
|
|
55
|
-
for (let start = 0;start < 8; ) {
|
|
56
|
-
let end = start;
|
|
57
|
-
while (end < 8 && groups[end] === "0")
|
|
58
|
-
end++;
|
|
59
|
-
const runLength = end - start;
|
|
60
|
-
if (runLength > longestLength && runLength > 1) {
|
|
61
|
-
longestStart = start;
|
|
62
|
-
longestLength = runLength;
|
|
63
|
-
}
|
|
64
|
-
start = end === start ? start + 1 : end;
|
|
65
|
-
}
|
|
66
|
-
if (longestStart < 0)
|
|
67
|
-
return groups.join(":");
|
|
68
|
-
const head = groups.slice(0, longestStart).join(":");
|
|
69
|
-
const tail = groups.slice(longestStart + longestLength).join(":");
|
|
70
|
-
return `${head}::${tail}`;
|
|
71
|
-
}
|
|
72
|
-
function network(cidr) {
|
|
73
|
-
const { addr, len, family } = split(cidr);
|
|
74
|
-
return `${format(parse(addr) & mask(len, family), family)}/${len}`;
|
|
75
|
-
}
|
|
76
|
-
function contains2(outer, inner) {
|
|
77
|
-
const outerParts = split(outer);
|
|
78
|
-
const [innerAddress, innerLengthText] = inner.split("/");
|
|
79
|
-
if (familyOf2(innerAddress) !== outerParts.family)
|
|
80
|
-
return false;
|
|
81
|
-
const innerLength = innerLengthText === undefined ? bitsOf(outerParts.family) : Number(innerLengthText);
|
|
82
|
-
if (innerLength < outerParts.len)
|
|
83
|
-
return false;
|
|
84
|
-
const outerMask = mask(outerParts.len, outerParts.family);
|
|
85
|
-
return (parse(innerAddress) & outerMask) === (parse(outerParts.addr) & outerMask);
|
|
86
|
-
}
|
|
87
|
-
var formatAddress = (value, family) => format(value, family);
|
|
88
|
-
function rangeOf(cidr) {
|
|
89
|
-
const { addr, len, family } = split(cidr);
|
|
90
|
-
const hostBits = mask(len, family) ^ (1n << BigInt(bitsOf(family))) - 1n;
|
|
91
|
-
const first = parse(addr) & mask(len, family);
|
|
92
|
-
return { first, last: first | hostBits, family };
|
|
93
|
-
}
|
|
94
|
-
function prefixesBetween(first, last, family) {
|
|
95
|
-
const bits = bitsOf(family);
|
|
96
|
-
const prefixes = [];
|
|
97
|
-
for (let start = first;start <= last; ) {
|
|
98
|
-
let size = 0;
|
|
99
|
-
while (size < bits && start % (1n << BigInt(size + 1)) === 0n && start + (1n << BigInt(size + 1)) - 1n <= last)
|
|
100
|
-
size++;
|
|
101
|
-
prefixes.push(`${format(start, family)}/${bits - size}`);
|
|
102
|
-
start += 1n << BigInt(size);
|
|
103
|
-
}
|
|
104
|
-
return prefixes;
|
|
105
|
-
}
|
|
106
|
-
var netmask = (length) => format(mask(length, "ipv4"), "ipv4");
|
|
107
|
-
var wildcard = (length) => format(mask(length, "ipv4") ^ 0xffffffffn, "ipv4");
|
|
108
|
-
var host2 = (cidr) => cidr.split("/")[0];
|
|
109
|
-
var NOT_GLOBAL_V4 = [
|
|
110
|
-
"0.0.0.0/8",
|
|
111
|
-
"10.0.0.0/8",
|
|
112
|
-
"100.64.0.0/10",
|
|
113
|
-
"127.0.0.0/8",
|
|
114
|
-
"169.254.0.0/16",
|
|
115
|
-
"172.16.0.0/12",
|
|
116
|
-
"192.0.2.0/24",
|
|
117
|
-
"192.168.0.0/16",
|
|
118
|
-
"198.18.0.0/15",
|
|
119
|
-
"198.51.100.0/24",
|
|
120
|
-
"203.0.113.0/24"
|
|
121
|
-
];
|
|
122
|
-
function isGlobal2(address) {
|
|
123
|
-
if (familyOf2(address) === "ipv6")
|
|
124
|
-
return contains2("2000::/3", address) && !contains2("2001:db8::/32", address);
|
|
125
|
-
return !NOT_GLOBAL_V4.some((prefix) => contains2(prefix, address));
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
export { familyOf2, parse, split, isAddress2, isCidr2, network, contains2, formatAddress, rangeOf, prefixesBetween, netmask, wildcard, host2, isGlobal2 };
|
|
@@ -1,166 +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 { createInterface } from "node:readline/promises";
|
|
6
|
-
import { fileURLToPath } from "node:url";
|
|
7
|
-
function packageRoot() {
|
|
8
|
-
let directory = dirname(fileURLToPath(import.meta.url));
|
|
9
|
-
while (!existsSync(join(directory, "templates"))) {
|
|
10
|
-
const parent = dirname(directory);
|
|
11
|
-
if (parent === directory)
|
|
12
|
-
throw new Error("Circuit's templates are missing from its package");
|
|
13
|
-
directory = parent;
|
|
14
|
-
}
|
|
15
|
-
return directory;
|
|
16
|
-
}
|
|
17
|
-
var ROOT = packageRoot();
|
|
18
|
-
var TEMPLATES = join(ROOT, "templates");
|
|
19
|
-
var circuit = JSON.parse(readFileSync(join(ROOT, "package.json"), "utf8"));
|
|
20
|
-
var MANAGERS = {
|
|
21
|
-
npm: { install: "npm install", run: "npx circuit", types: "node" },
|
|
22
|
-
pnpm: { install: "pnpm install", run: "pnpm circuit", types: "node" },
|
|
23
|
-
bun: { install: "bun install", run: "bun circuit", types: "bun" }
|
|
24
|
-
};
|
|
25
|
-
function detectManager() {
|
|
26
|
-
const agent = process.env.npm_config_user_agent ?? "";
|
|
27
|
-
if (agent.startsWith("pnpm/"))
|
|
28
|
-
return "pnpm";
|
|
29
|
-
if (agent.startsWith("bun/") || process.versions.bun)
|
|
30
|
-
return "bun";
|
|
31
|
-
return "npm";
|
|
32
|
-
}
|
|
33
|
-
var PATTERNS = {
|
|
34
|
-
"single-site": { title: "A single site", fits: "a home, an office or an internal network: a router and a switch, no BGP", asn: false },
|
|
35
|
-
"edge-router": { title: "An edge router", fits: "your own AS number, with an IP transit and an internet exchange", asn: true },
|
|
36
|
-
colocation: {
|
|
37
|
-
title: "Colocation with tenants",
|
|
38
|
-
fits: "a provider whose customers each get their own VLANs, addresses and ports",
|
|
39
|
-
asn: true
|
|
40
|
-
}
|
|
41
|
-
};
|
|
42
|
-
var EXAMPLE_ASN = "64500";
|
|
43
|
-
var USAGE = `usage: circuit new [directory] [options]
|
|
44
|
-
|
|
45
|
-
--pattern <name> single-site, edge-router or colocation
|
|
46
|
-
--asn <number> your AS number, for edge-router and colocation
|
|
47
|
-
--1password write .env with 1Password references instead of empty values
|
|
48
|
-
--use <manager> npm, pnpm or bun; the one that ran this by default
|
|
49
|
-
--no-install do not install the dependencies`;
|
|
50
|
-
function fail(message) {
|
|
51
|
-
console.error(message);
|
|
52
|
-
process.exit(1);
|
|
53
|
-
}
|
|
54
|
-
function option(args, name) {
|
|
55
|
-
const at = args.indexOf(`--${name}`);
|
|
56
|
-
if (at >= 0)
|
|
57
|
-
return args[at + 1];
|
|
58
|
-
return args.find((arg) => arg.startsWith(`--${name}=`))?.slice(name.length + 3);
|
|
59
|
-
}
|
|
60
|
-
function filesIn(directory) {
|
|
61
|
-
return readdirSync(directory, { recursive: true, withFileTypes: true }).filter((entry) => entry.isFile()).map((entry) => relative(directory, join(entry.parentPath, entry.name)));
|
|
62
|
-
}
|
|
63
|
-
async function create(args) {
|
|
64
|
-
if (args.includes("--help") || args.includes("-h")) {
|
|
65
|
-
console.log(USAGE);
|
|
66
|
-
return;
|
|
67
|
-
}
|
|
68
|
-
const valued = new Set(["--pattern", "--asn", "--use"]);
|
|
69
|
-
const positional = args.filter((arg, index) => !arg.startsWith("--") && !valued.has(args[index - 1] ?? ""));
|
|
70
|
-
const interactive = process.stdin.isTTY === true;
|
|
71
|
-
const terminal = interactive ? createInterface({ input: process.stdin, output: process.stdout }) : undefined;
|
|
72
|
-
const ask = async (question, fallback) => {
|
|
73
|
-
if (!terminal)
|
|
74
|
-
return fallback;
|
|
75
|
-
const answer = (await terminal.question(`${question} (${fallback}): `)).trim();
|
|
76
|
-
return answer || fallback;
|
|
77
|
-
};
|
|
78
|
-
const directory = positional[0] ?? await ask("Directory for the project", "my-network");
|
|
79
|
-
const target = resolve(process.cwd(), directory);
|
|
80
|
-
if (existsSync(target) && readdirSync(target).length)
|
|
81
|
-
fail(`${target} is not empty`);
|
|
82
|
-
let pattern = option(args, "pattern");
|
|
83
|
-
if (!pattern) {
|
|
84
|
-
const names = Object.keys(PATTERNS);
|
|
85
|
-
if (terminal) {
|
|
86
|
-
console.log(`
|
|
87
|
-
Which is closest to your network?`);
|
|
88
|
-
names.forEach((name, index) => console.log(` ${index + 1}. ${PATTERNS[name].title}: ${PATTERNS[name].fits}`));
|
|
89
|
-
}
|
|
90
|
-
pattern = names[Number(await ask("Number", "1")) - 1];
|
|
91
|
-
}
|
|
92
|
-
if (!pattern || !(pattern in PATTERNS))
|
|
93
|
-
fail(`no pattern ${pattern}; there are ${Object.keys(PATTERNS).join(", ")}`);
|
|
94
|
-
let asn = option(args, "asn");
|
|
95
|
-
if (!asn && PATTERNS[pattern].asn) {
|
|
96
|
-
asn = await ask("Your AS number, or press enter to keep the documentation example", EXAMPLE_ASN);
|
|
97
|
-
}
|
|
98
|
-
if (asn && !/^\d+$/.test(asn))
|
|
99
|
-
fail(`${asn} is not an AS number`);
|
|
100
|
-
const manager = option(args, "use") ?? detectManager();
|
|
101
|
-
if (!(manager in MANAGERS))
|
|
102
|
-
fail(`no package manager ${manager}; there are ${Object.keys(MANAGERS).join(", ")}`);
|
|
103
|
-
const { install, run, types } = MANAGERS[manager];
|
|
104
|
-
const onePassword = args.includes("--1password") || (await ask("Keep the secrets in 1Password? y or n", "n")).toLowerCase().startsWith("y");
|
|
105
|
-
terminal?.close();
|
|
106
|
-
cpSync(join(TEMPLATES, pattern), target, { recursive: true });
|
|
107
|
-
cpSync(join(TEMPLATES, "project"), target, { recursive: true });
|
|
108
|
-
renameSync(join(target, "gitignore"), join(target, ".gitignore"));
|
|
109
|
-
for (const file of ["AGENTS.md", "README.md", "tsconfig.json"]) {
|
|
110
|
-
const path = join(target, file);
|
|
111
|
-
const text = readFileSync(path, "utf8").replaceAll("npx circuit", run).replaceAll("npm install", install).replaceAll('"types": ["node"]', `"types": ["${types}"]`);
|
|
112
|
-
writeFileSync(path, text);
|
|
113
|
-
}
|
|
114
|
-
const sources = filesIn(target).filter((file) => file.endsWith(".ts"));
|
|
115
|
-
const secrets = new Set;
|
|
116
|
-
for (const file of sources) {
|
|
117
|
-
const path = join(target, file);
|
|
118
|
-
let text = readFileSync(path, "utf8");
|
|
119
|
-
if (asn && asn !== EXAMPLE_ASN)
|
|
120
|
-
text = text.replaceAll(new RegExp(`\\b${EXAMPLE_ASN}\\b`, "g"), asn);
|
|
121
|
-
for (const [, name] of text.matchAll(/secret\("([A-Z0-9_]+)"\)/g))
|
|
122
|
-
secrets.add(name);
|
|
123
|
-
writeFileSync(path, text);
|
|
124
|
-
}
|
|
125
|
-
const env = [
|
|
126
|
-
"# Secrets for Circuit. Never commit this file.",
|
|
127
|
-
"# A value is the secret itself, or a 1Password reference, op://vault/item/field, read with the 1Password CLI.",
|
|
128
|
-
...[...secrets].sort().map((name) => onePassword ? `${name}=op://Network/${name}/password` : `${name}=`),
|
|
129
|
-
""
|
|
130
|
-
];
|
|
131
|
-
writeFileSync(join(target, ".env"), env.join(`
|
|
132
|
-
`));
|
|
133
|
-
const name = basename(target).toLowerCase().replace(/[^a-z0-9-]+/g, "-");
|
|
134
|
-
const packageJson = {
|
|
135
|
-
name,
|
|
136
|
-
private: true,
|
|
137
|
-
type: "module",
|
|
138
|
-
dependencies: { "@takodotid/circuit": circuit.version },
|
|
139
|
-
devDependencies: { [`@types/${types}`]: circuit.devDependencies[`@types/${types}`] }
|
|
140
|
-
};
|
|
141
|
-
writeFileSync(join(target, "package.json"), JSON.stringify(packageJson, null, 4) + `
|
|
142
|
-
`);
|
|
143
|
-
if (!args.includes("--no-install")) {
|
|
144
|
-
const result = spawnSync(install, { cwd: target, stdio: "inherit", shell: true });
|
|
145
|
-
if (result.status !== 0)
|
|
146
|
-
console.log(`${install} failed; run it again in the project.`);
|
|
147
|
-
}
|
|
148
|
-
const steps = [
|
|
149
|
-
`cd ${directory}`,
|
|
150
|
-
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`,
|
|
151
|
-
"Change the names, addresses and ports in the device files to your own.",
|
|
152
|
-
...PATTERNS[pattern].asn && (!asn || asn === EXAMPLE_ASN) ? ["Put your own AS number in routing.ts."] : [],
|
|
153
|
-
`${run} validate`,
|
|
154
|
-
`${run} snapshot, then ${run} diff, to see what would change on each device`
|
|
155
|
-
];
|
|
156
|
-
console.log(`
|
|
157
|
-
Created ${directory} from the pattern "${PATTERNS[pattern].title}".
|
|
158
|
-
|
|
159
|
-
Next:`);
|
|
160
|
-
steps.forEach((step, index) => console.log(` ${index + 1}. ${step}`));
|
|
161
|
-
console.log(`
|
|
162
|
-
What each file does: https://circuit.tako.id/patterns/${pattern}`);
|
|
163
|
-
}
|
|
164
|
-
export {
|
|
165
|
-
create
|
|
166
|
-
};
|
|
@@ -1,251 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
packageRoot
|
|
3
|
-
} from "./index-47debt16.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
|
-
var environment = () => Object.fromEntries(Object.entries(process.env).filter(([name]) => !name.startsWith("GIT_")));
|
|
16
|
-
function git(cwd, args) {
|
|
17
|
-
return new Promise((done, fail) => {
|
|
18
|
-
execFile("git", args, { cwd, env: environment(), maxBuffer: 64 * 1024 * 1024 }, (error, stdout, stderr) => {
|
|
19
|
-
if (error)
|
|
20
|
-
fail(new Error((stderr || error.message).trim()));
|
|
21
|
-
else
|
|
22
|
-
done(stdout);
|
|
23
|
-
});
|
|
24
|
-
});
|
|
25
|
-
}
|
|
26
|
-
var FORMAT = "--format=%H%x1f%h%x1f%an%x1f%aI%x1f%s";
|
|
27
|
-
function commitsOf(text) {
|
|
28
|
-
return text.split(`
|
|
29
|
-
`).filter(Boolean).map((line) => {
|
|
30
|
-
const [commit = "", short = "", author = "", date = "", subject = ""] = line.split("\x1F");
|
|
31
|
-
return { commit, short, author, date, subject };
|
|
32
|
-
});
|
|
33
|
-
}
|
|
34
|
-
async function gitTop(directory) {
|
|
35
|
-
try {
|
|
36
|
-
return (await git(directory, ["rev-parse", "--show-toplevel"])).trim();
|
|
37
|
-
} catch {
|
|
38
|
-
return;
|
|
39
|
-
}
|
|
40
|
-
}
|
|
41
|
-
async function history(directory, files, limit = 50) {
|
|
42
|
-
if (!files.length)
|
|
43
|
-
return [];
|
|
44
|
-
return commitsOf(await git(directory, ["log", `-n${limit}`, FORMAT, "--", ...files]));
|
|
45
|
-
}
|
|
46
|
-
async function references(directory) {
|
|
47
|
-
const names = (await git(directory, [
|
|
48
|
-
"for-each-ref",
|
|
49
|
-
"--count=100",
|
|
50
|
-
"--sort=-committerdate",
|
|
51
|
-
"--format=%(refname:short)",
|
|
52
|
-
"refs/heads",
|
|
53
|
-
"refs/remotes",
|
|
54
|
-
"refs/tags"
|
|
55
|
-
])).split(`
|
|
56
|
-
`).filter((name) => name && !name.endsWith("/HEAD"));
|
|
57
|
-
return { names, commits: commitsOf(await git(directory, ["log", "-n30", FORMAT])) };
|
|
58
|
-
}
|
|
59
|
-
async function commitOf(directory, name) {
|
|
60
|
-
if (!/^[A-Za-z0-9][\w./@^~-]*$/.test(name))
|
|
61
|
-
return;
|
|
62
|
-
try {
|
|
63
|
-
const [commit] = commitsOf(await git(directory, ["log", "-n1", FORMAT, `${name}^{commit}`, "--"]));
|
|
64
|
-
return commit;
|
|
65
|
-
} catch {
|
|
66
|
-
return;
|
|
67
|
-
}
|
|
68
|
-
}
|
|
69
|
-
async function networkAt(configDirectory, configPath, commit, gather) {
|
|
70
|
-
const top = await gitTop(configDirectory);
|
|
71
|
-
if (!top)
|
|
72
|
-
throw new Error("the config is not in a git repository");
|
|
73
|
-
const cache = join(configDirectory, ".circuit", "cache");
|
|
74
|
-
mkdirSync(cache, { recursive: true });
|
|
75
|
-
writeFileSync(join(cache, ".gitignore"), `# Written by Circuit Studio: checkouts of other commits, removed once read.
|
|
76
|
-
*
|
|
77
|
-
`);
|
|
78
|
-
await git(top, ["worktree", "prune"]);
|
|
79
|
-
const checkout = join(cache, commit.slice(0, 12));
|
|
80
|
-
await git(top, ["worktree", "add", "--detach", "--force", checkout, commit]);
|
|
81
|
-
try {
|
|
82
|
-
const prefix = (await git(configDirectory, ["rev-parse", "--show-prefix"])).trim();
|
|
83
|
-
return await gather(join(checkout, prefix, basename(configPath)));
|
|
84
|
-
} finally {
|
|
85
|
-
await git(top, ["worktree", "remove", "--force", checkout]).catch(() => {});
|
|
86
|
-
}
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
// src/studio/server.ts
|
|
90
|
-
var isLoopback = (host) => host === "localhost" || host === "::1" || /^127\.\d+\.\d+\.\d+$/.test(host);
|
|
91
|
-
function hostAllowed(header, port, allowHosts) {
|
|
92
|
-
if (!header)
|
|
93
|
-
return false;
|
|
94
|
-
if (header === `127.0.0.1:${port}` || header === `localhost:${port}` || header === `[::1]:${port}`)
|
|
95
|
-
return true;
|
|
96
|
-
const name = header.replace(/:\d+$/, "").toLowerCase();
|
|
97
|
-
return allowHosts.some((allowed) => {
|
|
98
|
-
const wanted = allowed.toLowerCase();
|
|
99
|
-
return wanted === header.toLowerCase() || wanted === name;
|
|
100
|
-
});
|
|
101
|
-
}
|
|
102
|
-
var IGNORED = /(^|[\\/])(node_modules|\.git|\.circuit[\\/]cache)([\\/]|$)/;
|
|
103
|
-
var CONTENT_TYPES = {
|
|
104
|
-
".html": "text/html; charset=utf-8",
|
|
105
|
-
".css": "text/css; charset=utf-8",
|
|
106
|
-
".js": "text/javascript; charset=utf-8",
|
|
107
|
-
".svg": "image/svg+xml",
|
|
108
|
-
".woff2": "font/woff2"
|
|
109
|
-
};
|
|
110
|
-
function gather(configPath) {
|
|
111
|
-
const cli = process.argv[1];
|
|
112
|
-
const args = [...process.execArgv, cli, "studio", "--json", `--config=${configPath}`];
|
|
113
|
-
return new Promise((done) => {
|
|
114
|
-
execFile2(process.execPath, args, { maxBuffer: 256 * 1024 * 1024 }, (error, stdout, stderr) => {
|
|
115
|
-
if (!error)
|
|
116
|
-
return done(stdout);
|
|
117
|
-
const message = (stderr || error.message).trim();
|
|
118
|
-
done(JSON.stringify({ error: message }));
|
|
119
|
-
});
|
|
120
|
-
});
|
|
121
|
-
}
|
|
122
|
-
function openBrowser(url) {
|
|
123
|
-
const [command, args] = process.platform === "win32" ? ["cmd", ["/c", "start", "", url]] : process.platform === "darwin" ? ["open", [url]] : ["xdg-open", [url]];
|
|
124
|
-
const child = spawn(command, args, { stdio: "ignore", detached: true });
|
|
125
|
-
child.on("error", () => {});
|
|
126
|
-
child.unref();
|
|
127
|
-
}
|
|
128
|
-
async function studio(options) {
|
|
129
|
-
const { configPath, root, port } = options;
|
|
130
|
-
const installed = packageRoot();
|
|
131
|
-
if (!installed)
|
|
132
|
-
throw new Error("cannot find where Circuit is installed, so Studio has no page to serve");
|
|
133
|
-
const pages = join2(installed, "src", "studio", "public");
|
|
134
|
-
let data = await gather(configPath);
|
|
135
|
-
const past = new Map;
|
|
136
|
-
const answer = async (response, work) => {
|
|
137
|
-
let body;
|
|
138
|
-
try {
|
|
139
|
-
const result = await work();
|
|
140
|
-
body = typeof result === "string" ? result : JSON.stringify(result);
|
|
141
|
-
} catch (error) {
|
|
142
|
-
body = JSON.stringify({ error: error.message });
|
|
143
|
-
}
|
|
144
|
-
response.writeHead(200, { "content-type": "application/json", "cache-control": "no-store" }).end(body);
|
|
145
|
-
};
|
|
146
|
-
const listeners = new Set;
|
|
147
|
-
const tell = (message) => {
|
|
148
|
-
for (const listener of listeners)
|
|
149
|
-
listener.write(`data: ${message}
|
|
150
|
-
|
|
151
|
-
`);
|
|
152
|
-
};
|
|
153
|
-
const server = createServer((request, response) => {
|
|
154
|
-
response.setHeader("x-robots-tag", "noindex, nofollow, noarchive");
|
|
155
|
-
if (!hostAllowed(request.headers.host, port, options.allowHosts)) {
|
|
156
|
-
response.writeHead(403).end();
|
|
157
|
-
return;
|
|
158
|
-
}
|
|
159
|
-
const url = new URL(request.url ?? "/", "http://studio");
|
|
160
|
-
const path = url.pathname;
|
|
161
|
-
if (path === "/history") {
|
|
162
|
-
answer(response, async () => {
|
|
163
|
-
const name = url.searchParams.get("device") ?? "";
|
|
164
|
-
const files = JSON.parse(data).files?.[name];
|
|
165
|
-
if (!files)
|
|
166
|
-
throw new Error(`no device ${name}`);
|
|
167
|
-
return history(root, [files.source, files.snapshot].filter((file) => Boolean(file)));
|
|
168
|
-
});
|
|
169
|
-
return;
|
|
170
|
-
}
|
|
171
|
-
if (path === "/refs") {
|
|
172
|
-
answer(response, () => references(root));
|
|
173
|
-
return;
|
|
174
|
-
}
|
|
175
|
-
if (path === "/compare") {
|
|
176
|
-
answer(response, async () => {
|
|
177
|
-
const asked = url.searchParams.get("ref") ?? "";
|
|
178
|
-
const commit = await commitOf(root, asked);
|
|
179
|
-
if (!commit)
|
|
180
|
-
throw new Error(`${asked} names no commit here`);
|
|
181
|
-
if (!past.has(commit.commit))
|
|
182
|
-
past.set(commit.commit, networkAt(root, configPath, commit.commit, gather));
|
|
183
|
-
const network = await past.get(commit.commit);
|
|
184
|
-
if (network.startsWith('{"error"'))
|
|
185
|
-
past.delete(commit.commit);
|
|
186
|
-
return `{"commit":${JSON.stringify(commit)},"network":${network}}`;
|
|
187
|
-
});
|
|
188
|
-
return;
|
|
189
|
-
}
|
|
190
|
-
if (path === "/robots.txt") {
|
|
191
|
-
response.writeHead(200, { "content-type": "text/plain; charset=utf-8" }).end(`User-agent: *
|
|
192
|
-
Disallow: /
|
|
193
|
-
`);
|
|
194
|
-
return;
|
|
195
|
-
}
|
|
196
|
-
if (path === "/data") {
|
|
197
|
-
response.writeHead(200, { "content-type": "application/json", "cache-control": "no-store" }).end(data);
|
|
198
|
-
return;
|
|
199
|
-
}
|
|
200
|
-
if (path === "/events") {
|
|
201
|
-
response.writeHead(200, {
|
|
202
|
-
"content-type": "text/event-stream",
|
|
203
|
-
"cache-control": "no-store",
|
|
204
|
-
connection: "keep-alive",
|
|
205
|
-
"x-accel-buffering": "no"
|
|
206
|
-
});
|
|
207
|
-
response.write(`: connected
|
|
208
|
-
|
|
209
|
-
`);
|
|
210
|
-
listeners.add(response);
|
|
211
|
-
request.on("close", () => listeners.delete(response));
|
|
212
|
-
return;
|
|
213
|
-
}
|
|
214
|
-
const name = path === "/" ? "index.html" : path.slice(1);
|
|
215
|
-
const type = CONTENT_TYPES[extname(name)];
|
|
216
|
-
const file = join2(pages, name);
|
|
217
|
-
if (!/^[a-z-]+\.[a-z0-9]+$/.test(name) || !type || !existsSync(file)) {
|
|
218
|
-
response.writeHead(404).end();
|
|
219
|
-
return;
|
|
220
|
-
}
|
|
221
|
-
response.writeHead(200, { "content-type": type, "cache-control": "no-store" }).end(readFileSync(file));
|
|
222
|
-
});
|
|
223
|
-
let timer;
|
|
224
|
-
watch(root, { recursive: true }, (_event, filename) => {
|
|
225
|
-
if (filename && IGNORED.test(filename))
|
|
226
|
-
return;
|
|
227
|
-
clearTimeout(timer);
|
|
228
|
-
timer = setTimeout(async () => {
|
|
229
|
-
data = await gather(configPath);
|
|
230
|
-
tell("changed");
|
|
231
|
-
console.log(`${new Date().toLocaleTimeString()} ${filename ?? "a file"} changed, read again`);
|
|
232
|
-
}, 300);
|
|
233
|
-
});
|
|
234
|
-
watch(pages, () => tell("page"));
|
|
235
|
-
server.on("error", (error) => {
|
|
236
|
-
const message = error.code === "EADDRINUSE" ? `port ${port} is in use; choose another with --port=N` : error.message;
|
|
237
|
-
console.error(message);
|
|
238
|
-
process.exit(1);
|
|
239
|
-
});
|
|
240
|
-
server.listen(port, options.host, () => {
|
|
241
|
-
const local = isLoopback(options.host);
|
|
242
|
-
const url = local ? `http://127.0.0.1:${port}` : `http://${options.allowHosts[0] ?? options.host}`;
|
|
243
|
-
console.log(`Circuit Studio at ${url}, listening on ${options.host}:${port}, watching ${root}. Stop it with Ctrl+C.`);
|
|
244
|
-
if (options.open && local)
|
|
245
|
-
openBrowser(url);
|
|
246
|
-
});
|
|
247
|
-
}
|
|
248
|
-
export {
|
|
249
|
-
isLoopback,
|
|
250
|
-
studio
|
|
251
|
-
};
|