@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.
Files changed (256) hide show
  1. package/README.md +115 -37
  2. package/package.json +21 -58
  3. package/src/checks/across.ts +402 -0
  4. package/src/checks/built-in.ts +256 -0
  5. package/src/checks/device.ts +436 -0
  6. package/src/checks/index.ts +53 -0
  7. package/src/cli/advisories.ts +108 -0
  8. package/src/cli/app.ts +71 -0
  9. package/src/cli/editor-types.generated.ts +30 -0
  10. package/src/cli/editor-types.ts +69 -0
  11. package/src/cli/format.ts +124 -0
  12. package/src/cli/git-hooks.ts +79 -0
  13. package/src/cli/index.ts +350 -1170
  14. package/src/cli/migrate.ts +554 -0
  15. package/src/cli/project-commands.ts +656 -0
  16. package/src/cli/releases.ts +374 -0
  17. package/src/cli/remember.ts +339 -0
  18. package/src/cli/rename.ts +27 -33
  19. package/src/cli/secrets-password.ts +60 -0
  20. package/src/cli/secrets.ts +258 -0
  21. package/src/cli/setup.ts +300 -0
  22. package/src/cli/signing.ts +23 -0
  23. package/src/cli/text-editor.ts +268 -0
  24. package/src/cli/ui.ts +298 -0
  25. package/src/cli/version.ts +2 -0
  26. package/src/cli/versions.ts +158 -0
  27. package/src/config/address.ts +102 -0
  28. package/src/config/circuit.d.ts +30 -0
  29. package/src/config/common.ts +65 -0
  30. package/src/config/communities.ts +479 -0
  31. package/src/config/define.ts +684 -0
  32. package/src/config/device.ts +164 -0
  33. package/src/config/firewall.ts +150 -0
  34. package/src/config/index.ts +22 -0
  35. package/src/{schema → config}/interfaces.ts +121 -85
  36. package/src/config/keys.ts +434 -0
  37. package/src/config/management.ts +61 -0
  38. package/src/config/merge.ts +335 -0
  39. package/src/config/models.ts +70 -0
  40. package/src/config/monitoring.ts +64 -0
  41. package/src/config/network.ts +180 -0
  42. package/src/config/presets.ts +149 -0
  43. package/src/config/routing.ts +401 -0
  44. package/src/config/services.ts +51 -0
  45. package/src/config/switching.ts +84 -0
  46. package/src/config/system.ts +103 -0
  47. package/src/{schema → config}/time-zones.ts +1 -1
  48. package/src/core/address.ts +1 -1
  49. package/src/core/cabling.ts +8 -43
  50. package/src/core/graph.ts +40 -0
  51. package/src/{integrations → core}/registries.ts +5 -3
  52. package/src/core/secrets-file.ts +539 -0
  53. package/src/core/secrets.ts +83 -64
  54. package/src/core/topology.ts +103 -146
  55. package/src/core/utils.ts +12 -1
  56. package/src/model.ts +83 -0
  57. package/src/platforms/derive.ts +64 -37
  58. package/src/platforms/finding.ts +9 -0
  59. package/src/platforms/fortios/firewall.ts +76 -64
  60. package/src/platforms/fortios/index.ts +189 -125
  61. package/src/platforms/fortios/names.ts +51 -18
  62. package/src/platforms/fortios/render.ts +69 -62
  63. package/src/platforms/fortios/routing.ts +89 -58
  64. package/src/platforms/fortios/vdoms.ts +47 -16
  65. package/src/platforms/junos/TRAPS.md +2 -2
  66. package/src/platforms/junos/bgp.ts +63 -42
  67. package/src/platforms/junos/index.ts +122 -89
  68. package/src/platforms/junos/render.ts +139 -104
  69. package/src/platforms/platform.ts +10 -8
  70. package/src/platforms/raisecom-ros/TRAPS.md +1 -1
  71. package/src/platforms/raisecom-ros/index.ts +89 -70
  72. package/src/platforms/raisecom-ros/render.ts +49 -45
  73. package/src/platforms/routeros/TRAPS.md +3 -3
  74. package/src/platforms/routeros/index.ts +121 -60
  75. package/src/platforms/routeros/render.ts +214 -195
  76. package/src/platforms/vrp/TRAPS.md +1 -1
  77. package/src/platforms/vrp/index.ts +74 -50
  78. package/src/platforms/vrp/render.ts +67 -56
  79. package/src/project/project.ts +725 -0
  80. package/src/studio/app/env.d.ts +9 -0
  81. package/src/studio/app/index.html +18 -0
  82. package/src/studio/app/src/App.vue +95 -0
  83. package/src/studio/app/src/components/AddressesView.vue +38 -0
  84. package/src/studio/app/src/components/CablesView.vue +29 -0
  85. package/src/studio/app/src/components/CompareView.vue +160 -0
  86. package/src/studio/app/src/components/DataTable.vue +49 -0
  87. package/src/studio/app/src/components/DeviceView.vue +237 -0
  88. package/src/studio/app/src/components/DevicesView.vue +32 -0
  89. package/src/studio/app/src/components/Faceplate.vue +75 -0
  90. package/src/studio/app/src/components/GraphView.vue +282 -0
  91. package/src/studio/app/src/components/LineDiff.vue +24 -0
  92. package/src/studio/app/src/components/ProblemsView.vue +42 -0
  93. package/src/studio/app/src/components/SearchView.vue +72 -0
  94. package/src/studio/app/src/components/SecretsView.vue +112 -0
  95. package/src/studio/app/src/components/SessionsView.vue +35 -0
  96. package/src/studio/app/src/components/Settings.vue +95 -0
  97. package/src/studio/app/src/components/ToolsView.vue +118 -0
  98. package/src/studio/app/src/components/VlansView.vue +50 -0
  99. package/src/studio/app/src/lib/csv.ts +19 -0
  100. package/src/studio/{public/diff.js → app/src/lib/diff.ts} +40 -35
  101. package/src/studio/app/src/lib/format.ts +38 -0
  102. package/src/studio/app/src/lib/net.ts +68 -0
  103. package/src/studio/app/src/lib/secrets-crypto.ts +35 -0
  104. package/src/studio/app/src/lib/store.ts +71 -0
  105. package/src/studio/app/src/lib/tools.ts +217 -0
  106. package/src/studio/app/src/main.ts +9 -0
  107. package/src/studio/{public → app/src}/style.css +14 -4
  108. package/src/studio/app/tsconfig.json +17 -0
  109. package/src/studio/app/vite.config.ts +16 -0
  110. package/src/studio/assets.generated.ts +3 -0
  111. package/src/studio/data.ts +96 -188
  112. package/src/studio/git.ts +27 -43
  113. package/src/studio/server.ts +196 -107
  114. package/src/studio/types.ts +141 -0
  115. package/src/transport/console.ts +3 -3
  116. package/src/transport/ssh.ts +1 -1
  117. package/dist/chunks/index-3b343xva.js +0 -177
  118. package/dist/chunks/index-6wb41q67.js +0 -62
  119. package/dist/chunks/index-n7w3tg5y.js +0 -149
  120. package/dist/chunks/index-yzw86rc8.js +0 -921
  121. package/dist/chunks/new-w3zys5y4.js +0 -172
  122. package/dist/chunks/server-cxr9ynxc.js +0 -259
  123. package/dist/cli/index.js +0 -12922
  124. package/dist/helpers/index.js +0 -881
  125. package/dist/index.js +0 -28
  126. package/dist/types/core/address-space.d.ts +0 -62
  127. package/dist/types/core/address.d.ts +0 -44
  128. package/dist/types/core/cabling.d.ts +0 -41
  129. package/dist/types/core/cross-checks.d.ts +0 -28
  130. package/dist/types/core/define.d.ts +0 -79
  131. package/dist/types/core/project-files.d.ts +0 -12
  132. package/dist/types/core/schema-docs.d.ts +0 -25
  133. package/dist/types/core/secrets.d.ts +0 -62
  134. package/dist/types/core/topology.d.ts +0 -96
  135. package/dist/types/core/utils.d.ts +0 -10
  136. package/dist/types/core/validator.d.ts +0 -23
  137. package/dist/types/helpers/bgp.d.ts +0 -195
  138. package/dist/types/helpers/cables.d.ts +0 -69
  139. package/dist/types/helpers/checks.d.ts +0 -36
  140. package/dist/types/helpers/exchange.d.ts +0 -55
  141. package/dist/types/helpers/filters.d.ts +0 -51
  142. package/dist/types/helpers/index.d.ts +0 -14
  143. package/dist/types/helpers/route-server.d.ts +0 -52
  144. package/dist/types/helpers/upstreams.d.ts +0 -192
  145. package/dist/types/index.d.ts +0 -10
  146. package/dist/types/integrations/librenms.d.ts +0 -60
  147. package/dist/types/integrations/netbox.d.ts +0 -89
  148. package/dist/types/integrations/peeringdb.d.ts +0 -24
  149. package/dist/types/integrations/prometheus.d.ts +0 -24
  150. package/dist/types/integrations/registries.d.ts +0 -8
  151. package/dist/types/platforms/bgp-wait.d.ts +0 -24
  152. package/dist/types/platforms/catalog.d.ts +0 -109
  153. package/dist/types/platforms/derive.d.ts +0 -17
  154. package/dist/types/platforms/fortios/firewall.d.ts +0 -47
  155. package/dist/types/platforms/fortios/index.d.ts +0 -140
  156. package/dist/types/platforms/fortios/models.d.ts +0 -14
  157. package/dist/types/platforms/fortios/names.d.ts +0 -52
  158. package/dist/types/platforms/fortios/plan.d.ts +0 -114
  159. package/dist/types/platforms/fortios/render.d.ts +0 -77
  160. package/dist/types/platforms/fortios/routing.d.ts +0 -56
  161. package/dist/types/platforms/fortios/tree.d.ts +0 -37
  162. package/dist/types/platforms/fortios/vdoms.d.ts +0 -50
  163. package/dist/types/platforms/index.d.ts +0 -13
  164. package/dist/types/platforms/junos/bgp.d.ts +0 -44
  165. package/dist/types/platforms/junos/index.d.ts +0 -114
  166. package/dist/types/platforms/junos/models.d.ts +0 -14
  167. package/dist/types/platforms/junos/render.d.ts +0 -154
  168. package/dist/types/platforms/line-diff.d.ts +0 -67
  169. package/dist/types/platforms/platform.d.ts +0 -135
  170. package/dist/types/platforms/raisecom-ros/index.d.ts +0 -71
  171. package/dist/types/platforms/raisecom-ros/models.d.ts +0 -14
  172. package/dist/types/platforms/raisecom-ros/render.d.ts +0 -41
  173. package/dist/types/platforms/routeros/export.d.ts +0 -59
  174. package/dist/types/platforms/routeros/index.d.ts +0 -116
  175. package/dist/types/platforms/routeros/models.d.ts +0 -18
  176. package/dist/types/platforms/routeros/plan.d.ts +0 -58
  177. package/dist/types/platforms/routeros/render.d.ts +0 -250
  178. package/dist/types/platforms/vrp/index.d.ts +0 -83
  179. package/dist/types/platforms/vrp/models.d.ts +0 -12
  180. package/dist/types/platforms/vrp/render.d.ts +0 -96
  181. package/dist/types/schema/common.d.ts +0 -30
  182. package/dist/types/schema/device.d.ts +0 -126
  183. package/dist/types/schema/firewall.d.ts +0 -131
  184. package/dist/types/schema/index.d.ts +0 -8
  185. package/dist/types/schema/interfaces.d.ts +0 -205
  186. package/dist/types/schema/policy.d.ts +0 -107
  187. package/dist/types/schema/routing.d.ts +0 -161
  188. package/dist/types/schema/system.d.ts +0 -198
  189. package/dist/types/schema/time-zones.d.ts +0 -2
  190. package/dist/types/transport/console.d.ts +0 -115
  191. package/dist/types/transport/path-check.d.ts +0 -32
  192. package/dist/types/transport/ssh.d.ts +0 -27
  193. package/src/cli/graph-format.ts +0 -91
  194. package/src/cli/new.ts +0 -234
  195. package/src/core/address-space.ts +0 -320
  196. package/src/core/cross-checks.ts +0 -369
  197. package/src/core/define.ts +0 -124
  198. package/src/core/project-files.ts +0 -35
  199. package/src/core/schema-docs.ts +0 -174
  200. package/src/core/validator.ts +0 -508
  201. package/src/helpers/bgp.ts +0 -473
  202. package/src/helpers/cables.ts +0 -74
  203. package/src/helpers/checks.ts +0 -201
  204. package/src/helpers/exchange.ts +0 -123
  205. package/src/helpers/filters.ts +0 -107
  206. package/src/helpers/index.ts +0 -25
  207. package/src/helpers/route-server.ts +0 -212
  208. package/src/helpers/upstreams.ts +0 -369
  209. package/src/index.ts +0 -13
  210. package/src/integrations/librenms.ts +0 -291
  211. package/src/integrations/netbox.ts +0 -620
  212. package/src/integrations/peeringdb.ts +0 -173
  213. package/src/integrations/prometheus.ts +0 -90
  214. package/src/schema/common.ts +0 -41
  215. package/src/schema/device.ts +0 -145
  216. package/src/schema/firewall.ts +0 -140
  217. package/src/schema/index.ts +0 -8
  218. package/src/schema/policy.ts +0 -115
  219. package/src/schema/routing.ts +0 -173
  220. package/src/schema/system.ts +0 -203
  221. package/src/studio/public/app.js +0 -186
  222. package/src/studio/public/compare.js +0 -250
  223. package/src/studio/public/dom.js +0 -138
  224. package/src/studio/public/evaluate.js +0 -687
  225. package/src/studio/public/export.js +0 -130
  226. package/src/studio/public/graph.js +0 -1541
  227. package/src/studio/public/index.html +0 -83
  228. package/src/studio/public/net.js +0 -134
  229. package/src/studio/public/tables.js +0 -1289
  230. package/src/studio/public/tools.js +0 -610
  231. package/templates/colocation/checks.ts +0 -53
  232. package/templates/colocation/circuit.config.ts +0 -9
  233. package/templates/colocation/edge-01.ts +0 -133
  234. package/templates/colocation/routing.ts +0 -13
  235. package/templates/colocation/tenancy.ts +0 -76
  236. package/templates/colocation/tenants/acme.ts +0 -19
  237. package/templates/colocation/tenants/index.ts +0 -6
  238. package/templates/colocation/tor-01.ts +0 -33
  239. package/templates/edge-router/checks.ts +0 -20
  240. package/templates/edge-router/circuit.config.ts +0 -8
  241. package/templates/edge-router/edge-01.ts +0 -110
  242. package/templates/edge-router/routing.ts +0 -13
  243. package/templates/project/AGENTS.md +0 -51
  244. package/templates/project/CLAUDE.md +0 -1
  245. package/templates/project/README.md +0 -16
  246. package/templates/project/gitignore +0 -5
  247. package/templates/project/tsconfig.json +0 -13
  248. package/templates/single-site/checks.ts +0 -26
  249. package/templates/single-site/circuit.config.ts +0 -8
  250. package/templates/single-site/router.ts +0 -86
  251. package/templates/single-site/site.ts +0 -18
  252. package/templates/single-site/switch.ts +0 -34
  253. /package/src/studio/{public → app/public}/favicon.svg +0 -0
  254. /package/src/studio/{public → app/public}/special-gothic-latin-ext.woff2 +0 -0
  255. /package/src/studio/{public → app/public}/special-gothic-latin.woff2 +0 -0
  256. /package/src/studio/{public → app/public}/special-gothic-license.txt +0 -0
@@ -0,0 +1,158 @@
1
+ // Each project pins its Circuit in `.circuit/version`, committed. The installed `circuit`, the manager, runs that version for every command in the project, kept once in ~/.circuit/versions/<version>/, so two projects on different versions never clash and the manager updating itself changes no project. A version not here yet is downloaded once the person says yes, signed and checked; one a security advisory affects never runs. Circuit remembers the projects it ran in, so a kept version no project pins any more is removed.
2
+
3
+ import { spawnSync } from "node:child_process";
4
+ import { chmodSync, existsSync, mkdirSync, readdirSync, readFileSync, renameSync, rmSync, writeFileSync } from "node:fs";
5
+ import { join, resolve } from "node:path";
6
+ import { Releases } from "./releases";
7
+ import { Ui } from "./ui";
8
+ import { VERSION } from "./version";
9
+
10
+ export class Versions {
11
+ /** The file at a project's root that names its Circuit version. */
12
+ static readonly FILE = ".circuit/version";
13
+
14
+ /** Set in the environment of a pinned Circuit this one started, so it doesn't start another. */
15
+ private static readonly RUNNING = "CIRCUIT_PINNED";
16
+
17
+ /** The version a project pins, or undefined when it pins none. */
18
+ static pinned(root: string): string | undefined {
19
+ const path = join(root, Versions.FILE);
20
+ if (!existsSync(path)) return undefined;
21
+
22
+ const version = readFileSync(path, "utf8").trim();
23
+ return version || undefined;
24
+ }
25
+
26
+ static pin(root: string, version: string): void {
27
+ mkdirSync(join(root, ".circuit"), { recursive: true });
28
+ writeFileSync(join(root, Versions.FILE), `${version}\n`);
29
+ }
30
+
31
+ /** The projects Circuit ran in on this machine, by folder. */
32
+ private static projectsFile(): string {
33
+ return join(Releases.home(), "projects.json");
34
+ }
35
+
36
+ /** The projects Circuit remembers. */
37
+ static known(): string[] {
38
+ try {
39
+ const read = JSON.parse(readFileSync(Versions.projectsFile(), "utf8")) as { projects?: unknown };
40
+ return Array.isArray(read.projects) ? read.projects.filter((root): root is string => typeof root === "string") : [];
41
+ } catch {
42
+ return [];
43
+ }
44
+ }
45
+
46
+ private static save(projects: readonly string[]): void {
47
+ try {
48
+ mkdirSync(Releases.home(), { recursive: true });
49
+ writeFileSync(Versions.projectsFile(), `${JSON.stringify({ projects }, null, 4)}\n`);
50
+ } catch {}
51
+ }
52
+
53
+ /** Remember a project, so the version it pins is kept. */
54
+ static remember(root: string): void {
55
+ const path = resolve(root);
56
+ const known = Versions.known();
57
+ if (!known.includes(path)) Versions.save([...known, path]);
58
+ }
59
+
60
+ /**
61
+ * Remove each kept version no project pins any more: a project is one Circuit ran in that is still there, and its version is what its
62
+ * `.circuit/version` says now. The app's own version is never kept apart: the app runs it. Gives back the versions removed.
63
+ */
64
+ static prune(app: string = VERSION): string[] {
65
+ const projects = Versions.known().filter((root) => existsSync(join(root, "circuit.ts")));
66
+ Versions.save(projects);
67
+
68
+ const folder = join(Releases.home(), "versions");
69
+ if (!existsSync(folder)) return [];
70
+
71
+ const used = new Set(projects.map((root) => Versions.pinned(root)));
72
+ const removed: string[] = [];
73
+ for (const version of readdirSync(folder)) {
74
+ if (used.has(version) && version !== app) continue;
75
+ // One still running in another terminal is locked on Windows: it goes on a later run.
76
+ try {
77
+ rmSync(join(folder, version), { recursive: true, force: true });
78
+ removed.push(version);
79
+ } catch {}
80
+ }
81
+ return removed;
82
+ }
83
+
84
+ /** Where a version is kept once downloaded. */
85
+ static path(version: string): string {
86
+ return join(Releases.home(), "versions", version, process.platform === "win32" ? "circuit.exe" : "circuit");
87
+ }
88
+
89
+ /** A version's binary: downloaded, signed and checked, once the person says yes, when it is not here yet. Without a person to ask, such as in CI, it is downloaded. */
90
+ static async ensure(version: string): Promise<string> {
91
+ const path = Versions.path(version);
92
+ if (existsSync(path)) return path;
93
+
94
+ if (
95
+ Ui.interactive &&
96
+ !(await Ui.confirm(`This project runs Circuit ${version}, which is not on this machine yet. Download it?`, true))
97
+ )
98
+ Ui.fail(`Not run. This project needs Circuit ${version}; or move it to another version with circuit update.`);
99
+
100
+ const bytes = await Ui.step(`Downloading Circuit ${version}`, () => Releases.fetchBinary(version));
101
+ mkdirSync(join(path, ".."), { recursive: true });
102
+ writeFileSync(`${path}.part`, bytes);
103
+ if (process.platform !== "win32") chmodSync(`${path}.part`, 0o755);
104
+ renameSync(`${path}.part`, path);
105
+
106
+ return path;
107
+ }
108
+
109
+ /**
110
+ * Before a command in a project: a version a security advisory affects never runs, so the person moves the project to a fixed one
111
+ * first. Then, when the project pins another version than this one, run that version with these arguments, and end with its exit
112
+ * code. Nothing to do when this Circuit is the one to run.
113
+ */
114
+ static async run(root: string, argv: string[]): Promise<void> {
115
+ if (process.env[Versions.RUNNING] || !Releases.binary()) return;
116
+
117
+ let pinned = Versions.pinned(root) ?? VERSION;
118
+ const { advisories } = await Ui.wait("Checking the project's Circuit for security problems", () => Releases.advisories());
119
+ const affected = Releases.affecting(advisories, pinned);
120
+ if (affected.length) {
121
+ if (!Ui.interactive)
122
+ Ui.fail(
123
+ `This project runs Circuit ${pinned}, which has a security problem: ${affected.map((advisory) => `${advisory.summary} (${advisory.url})`).join("; ")}. Fixed in ${[...new Set(affected.flatMap((advisory) => advisory.patched))].join(" or ")}. Run circuit update in a terminal.`
124
+ );
125
+ pinned =
126
+ (await Versions.updateProject(root, false)) ?? Ui.fail("Not run: the project stays on a version with a security problem.");
127
+ }
128
+ if (pinned === VERSION) return;
129
+
130
+ const binary = await Versions.ensure(pinned);
131
+ const result = spawnSync(binary, argv, { stdio: "inherit", env: { ...process.env, [Versions.RUNNING]: "1" } });
132
+ if (result.error) Ui.fail(`Circuit ${pinned} could not start: ${result.error.message}`);
133
+ process.exit(result.status ?? 1);
134
+ }
135
+
136
+ /** Whether this Circuit was started by the manager to run a project's version, so it leaves checking and updating to it. */
137
+ static started(): boolean {
138
+ return Boolean(process.env[Versions.RUNNING]);
139
+ }
140
+
141
+ /** Move a project to a version the person picks. Gives back the version it moved to. */
142
+ static async updateProject(root: string, confirmed: boolean): Promise<string | undefined> {
143
+ const current = Versions.pinned(root) ?? VERSION;
144
+ const target = await Releases.pick(current, confirmed, "This project");
145
+ if (!target) return undefined;
146
+
147
+ const binary = target === VERSION ? (Releases.binary() ?? (await Versions.ensure(target))) : await Versions.ensure(target);
148
+ Versions.pin(root, target);
149
+ Versions.remember(root);
150
+
151
+ // The new version writes its own editor types and hook.
152
+ spawnSync(binary, ["setup", root], { stdio: "inherit", env: { ...process.env, [Versions.RUNNING]: "1" } });
153
+ Ui.done(`This project runs Circuit ${target} now. Commit ${Versions.FILE}.`);
154
+
155
+ Versions.prune();
156
+ return target;
157
+ }
158
+ }
@@ -0,0 +1,102 @@
1
+ // `address`, a global beside `presets`: standard IP math for a project's own checks, IPv4 prefixes by RFC 4632 and IPv6 by RFC 4291,
2
+ // each address written back in its standard form (RFC 5952 for IPv6). Each helper takes an address alone, a /32 or a /128, or with its length.
3
+
4
+ import type { Family, IP, Prefix } from "./common";
5
+
6
+ /** An address alone, such as `10.0.0.1`, or with its length, such as `10.0.0.0/24` or `10.0.0.1/24`. */
7
+ export type AddressOrPrefix = IP | Prefix;
8
+
9
+ /** An address or prefix as numbers: the address written, and the first and last address of its range. */
10
+ function parse(text: string) {
11
+ const bad = () => new Error(`address: ${JSON.stringify(text)} is not an IPv4 or IPv6 address, alone or with its length.`);
12
+ const [ip = "", length, extra] = text.split("/");
13
+ const v6 = ip.includes(":");
14
+ const bits = v6 ? 128 : 32;
15
+ const len = length === undefined ? bits : /^\d{1,3}$/.test(length) ? Number(length) : -1;
16
+ if (extra !== undefined || len < 0 || len > bits) throw bad();
17
+
18
+ let value = 0n;
19
+ if (v6) {
20
+ // An IPv4 address may end an IPv6 one, `::ffff:192.0.2.1` (RFC 4291, 2.2): it is the last two groups.
21
+ let text6 = ip;
22
+ const tail = /(\d+\.\d+\.\d+\.\d+)$/.exec(ip);
23
+ if (tail) {
24
+ const v4 = parse(tail[1]!).address;
25
+ text6 = `${ip.slice(0, -tail[1]!.length)}${(v4 >> 16n).toString(16)}:${(v4 & 0xffffn).toString(16)}`;
26
+ }
27
+ const halves = text6.split("::");
28
+ const groups = (s: string) => (s === "" ? [] : s.split(":"));
29
+ const head = groups(halves[0]!);
30
+ const rest = halves.length === 2 ? groups(halves[1]!) : [];
31
+ const zeros = 8 - head.length - rest.length;
32
+ // `::` stands for one or more groups of zeros.
33
+ if (halves.length > 2 || (halves.length === 2 ? zeros < 1 : zeros !== 0)) throw bad();
34
+ const all = [...head, ...Array<string>(halves.length === 2 ? zeros : 0).fill("0"), ...rest];
35
+ if (all.some((g) => !/^[0-9a-f]{1,4}$/i.test(g))) throw bad();
36
+ for (const g of all) value = (value << 16n) + BigInt(parseInt(g, 16));
37
+ } else {
38
+ const parts = ip.split(".");
39
+ if (parts.length !== 4 || parts.some((p) => !/^\d{1,3}$/.test(p) || Number(p) > 255)) throw bad();
40
+ for (const p of parts) value = (value << 8n) + BigInt(p);
41
+ }
42
+ const size = 1n << BigInt(bits - len);
43
+ const first = value - (value % size);
44
+ return { family: (v6 ? "ipv6" : "ipv4") as Family, address: value, first, last: first + size - 1n };
45
+ }
46
+
47
+ /** A number written as an address of `family`: dotted for IPv4; for IPv6 lowercase, no leading zeros, the longest run of two or more zero groups as `::`, the first if two tie (RFC 5952). */
48
+ function format(family: Family, n: bigint): IP {
49
+ if (family === "ipv4") return [24n, 16n, 8n, 0n].map((s) => String((n >> s) & 255n)).join(".") as IP;
50
+ const groups = [112n, 96n, 80n, 64n, 48n, 32n, 16n, 0n].map((s) => ((n >> s) & 0xffffn).toString(16));
51
+ let best = { at: -1, length: 1 };
52
+ for (let i = 0; i < 8; i++) {
53
+ let j = i;
54
+ while (j < 8 && groups[j] === "0") j++;
55
+ if (j - i > best.length) best = { at: i, length: j - i };
56
+ }
57
+ if (best.at < 0) return groups.join(":") as IP;
58
+ return `${groups.slice(0, best.at).join(":")}::${groups.slice(best.at + best.length).join(":")}` as IP;
59
+ }
60
+
61
+ /**
62
+ * IP math for your own checks, by RFC 4632 (IPv4) and RFC 4291 (IPv6). Each takes an address alone (a /32 or a /128) or with its
63
+ * length. An address it can't read is an error naming it.
64
+ */
65
+ export const address = {
66
+ /** Whether `inner` lies wholly inside `outer`, of the same family: `address.contains("10.0.0.0/8", "10.1.0.0/16")` is true. */
67
+ contains(outer: AddressOrPrefix, inner: AddressOrPrefix): boolean {
68
+ const a = parse(outer);
69
+ const b = parse(inner);
70
+ return a.family === b.family && a.first <= b.first && b.last <= a.last;
71
+ },
72
+
73
+ /** Whether `a` and `b` share any address: one contains the other. */
74
+ overlaps(a: AddressOrPrefix, b: AddressOrPrefix): boolean {
75
+ const x = parse(a);
76
+ const y = parse(b);
77
+ return x.family === y.family && x.first <= y.last && y.first <= x.last;
78
+ },
79
+
80
+ /** `ipv4` or `ipv6`. */
81
+ family(x: AddressOrPrefix): Family {
82
+ return parse(x).family;
83
+ },
84
+
85
+ /** The address without its length, in its standard form: `address.host("2001:db8:0:0::33/64")` is `2001:db8::33`. */
86
+ host(x: AddressOrPrefix): IP {
87
+ const p = parse(x);
88
+ return format(p.family, p.address);
89
+ },
90
+
91
+ /** The first address of a prefix, its network address: `address.first("192.0.2.33/27")` is `192.0.2.32`. */
92
+ first(x: AddressOrPrefix): IP {
93
+ const p = parse(x);
94
+ return format(p.family, p.first);
95
+ },
96
+
97
+ /** The last address of a prefix: `address.last("192.0.2.32/27")` is `192.0.2.63`. */
98
+ last(x: AddressOrPrefix): IP {
99
+ const p = parse(x);
100
+ return format(p.family, p.last);
101
+ },
102
+ };
@@ -0,0 +1,30 @@
1
+ // Circuit's functions, `presets` and `address`, declared as globals, so a project's files use them with no import. The project's tsconfig.json
2
+ // includes this file (Circuit writes both). Each is the same as index.ts gives, which Circuit puts on `globalThis` when it reads a project.
3
+
4
+ import type * as Circuit from "./index";
5
+
6
+ declare global {
7
+ /**
8
+ * Declare a network, in `circuit.ts` at the project's root: `export const as64500 = circuit({ asn, prefixes, sites, checks, integrations })`, named after its AS. Each
9
+ * device is `as64500.device({ name, include: [sources], ... })` in its own file under `sites/`, typed from the network. Several networks: one `circuit()` each.
10
+ */
11
+ const circuit: typeof Circuit.circuit;
12
+ /** A part written in one file and used in another: `part("device", {...})`, or a rule list such as `part("filter", [...])`. */
13
+ const part: typeof Circuit.part;
14
+ /** Items made into one list or one map, by the field it lands in. */
15
+ const merge: typeof Circuit.merge;
16
+ /** A value written once and kept as written. */
17
+ const literal: typeof Circuit.literal;
18
+ /** A value read by name when a device is applied. */
19
+ const secret: typeof Circuit.secret;
20
+ /** A value read from a file when a device is applied. */
21
+ const secretFile: typeof Circuit.secretFile;
22
+ /** An address set Circuit fills with every BGP neighbor address of the device. */
23
+ const bgpNeighbors: typeof Circuit.bgpNeighbors;
24
+ /** Values every network writes the same way, each from a public standard. */
25
+ const presets: typeof Circuit.presets;
26
+ /** IP math for your own checks (RFC 4632, RFC 4291): `address.contains`, `.overlaps`, `.family`, `.host`, `.first`, `.last`. */
27
+ const address: typeof Circuit.address;
28
+ }
29
+
30
+ export {};
@@ -0,0 +1,65 @@
1
+ // Shapes every group shares: secrets, addresses, ordered rules, lists in a match.
2
+
3
+ /**
4
+ * A value kept out of the repository and resolved only when a device is applied.
5
+ *
6
+ * `secret("NAME")` reads a secret from the network's secrets file, `.circuit/secrets`, set with `circuit secrets`, or from the environment; a value written as `op://vault/item/field` is read from 1Password. `secretFile("path")` reads a file, relative to the project root, for a value that spans lines such as a private key in PEM. Keep that file out of git.
7
+ */
8
+ export type Secret =
9
+ | {
10
+ /** Name of the secret, as `circuit secrets set NAME` sets it. */
11
+ readonly secret: string;
12
+ }
13
+ | {
14
+ /** Path of the file with the value, relative to the project root. */
15
+ readonly secret_file: string;
16
+ };
17
+
18
+ /** An IPv4 address, four numbers with dots between them, for example `10.0.0.1`. The type checks the shape; `circuit validate` checks each number is 0 to 255. */
19
+ export type IPv4 = `${number}.${number}.${number}.${number}`;
20
+
21
+ /** An IPv6 address, groups with colons between them, for example `2001:db8::1`. The type only checks there is a colon; `circuit validate` checks the rest. */
22
+ export type IPv6 = `${string}:${string}`;
23
+
24
+ /** An address without a length, IPv4 or IPv6, for example `10.0.0.1` or `2001:db8::1`. */
25
+ export type IP = IPv4 | IPv6;
26
+
27
+ /** A network with its length, for example `10.0.0.0/24` or `2001:db8::/32`. */
28
+ export type Prefix = `${IP}/${number}`;
29
+
30
+ /** An interface address with its length, for example `10.0.0.1/24` or `2001:db8::1/64`. */
31
+ export type InterfaceAddress = `${IP}/${number}`;
32
+
33
+ /**
34
+ * One address, alone or with its length: `10.0.0.1` or `10.0.0.1/24`. A field that names one host takes either, so an address written
35
+ * once with its length (on a site, for an interface) is used as it is. Circuit writes the address alone there.
36
+ */
37
+ export type HostAddress = IP | InterfaceAddress;
38
+
39
+ /** A MAC address, six pairs of hex digits with colons between them, for example `02:00:00:00:00:01`. */
40
+ export type MacAddress = `${string}:${string}:${string}:${string}:${string}:${string}`;
41
+
42
+ /** An address family. */
43
+ export type Family = "ipv4" | "ipv6";
44
+
45
+ /** Bits per second, for example `2G`, `500M` or `64k`. */
46
+ export type Rate = `${number}${"k" | "M" | "G"}`;
47
+
48
+ /** A log severity, most severe first. A threshold includes everything above it. */
49
+ export type Severity = "emergency" | "alert" | "critical" | "error" | "warning" | "notice" | "info" | "debug";
50
+
51
+ /** A TCP or UDP port number, 1 to 65535. */
52
+ export type PortNumber = number;
53
+
54
+ /**
55
+ * Rules where order decides: the first runs first. An ordered list is written whole in one place, the device or one part, and
56
+ * places rules from other files where they go with `merge`: `merge([own_rule, tenants.rules.router_input, last_rule])`. The same list from two places
57
+ * is an error naming both.
58
+ */
59
+ export type Ordered<Rule> = readonly Rule[];
60
+
61
+ /** One value, or a list of them. In a rule's match, a list matches any of them: Circuit writes one rule for each, where the platform has no list of its own. */
62
+ export type OneOrMore<T> = T | readonly T[];
63
+
64
+ /** Entries keyed by name, such as VLANs or policies. A name written here is one the device declares: what its other fields may refer to. */
65
+ export type Named<K extends string, V> = { [N in K]?: V };