@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
@@ -2,12 +2,9 @@
2
2
 
3
3
  import { Address } from "../../core/address";
4
4
  import { Secrets } from "../../core/secrets";
5
- import type { Device, Family, Neighbor, NeighborSettings, PolicyRule } from "../../schema";
5
+ import type { Device, Family, Neighbor, NeighborSettings, PolicyRule, Prefix, PrefixSet, RouteRule } from "../../model";
6
6
  import { JunosRender, type JunosStatements } from "./render";
7
7
 
8
- type AnyRule = PolicyRule<string, string>;
9
- type AnyNeighbor = Neighbor<string, string, string>;
10
-
11
8
  /** BGP sessions, the policies they use, prefix sets and RPKI servers, added to one device's statements. */
12
9
  export class JunosBgp {
13
10
  /** The policy a session uses when it states none, so that it learns or announces nothing. */
@@ -41,11 +38,23 @@ export class JunosBgp {
41
38
  }
42
39
 
43
40
  /** A neighbor's settings with its group's beneath them. */
44
- static settingsOf(device: Device, neighbor: AnyNeighbor): NeighborSettings<string, string> {
45
- const group = neighbor.group ? device.routing?.bgp?.groups?.[neighbor.group] : undefined;
41
+ static settingsOf(device: Device, neighbor: Neighbor): NeighborSettings {
42
+ const group = neighbor.neighbor_group ? device.routing?.bgp?.neighbor_groups?.[neighbor.neighbor_group] : undefined;
46
43
  return { ...group, ...neighbor };
47
44
  }
48
45
 
46
+ /** A prefix set's prefixes, written as a list or as a map of named prefixes. A set fetched by `refresh` has none here. */
47
+ static prefixesOf(set: PrefixSet): Prefix[] {
48
+ if (!("prefixes" in set)) return [];
49
+
50
+ return Array.isArray(set.prefixes) ? [...(set.prefixes as readonly Prefix[])] : Object.values(set.prefixes);
51
+ }
52
+
53
+ /** A rule on routes: neither a call nor a communities rule. */
54
+ static isRouteRule(rule: PolicyRule): rule is RouteRule {
55
+ return !("call" in rule && rule.call !== undefined) && !("communities" in rule && rule.communities !== undefined);
56
+ }
57
+
49
58
  /** A name made of a value's words, for a community or a path-length list: `64500:*` becomes `64500-any`. */
50
59
  private static slug(value: string): string {
51
60
  return value.replace(/\*/g, "any").replace(/[^A-Za-z0-9]+/g, "-");
@@ -57,13 +66,14 @@ export class JunosBgp {
57
66
  }
58
67
 
59
68
  /** A term's name: its place, then the rule's description so a reader sees it on the device. */
60
- private static termName(label: string, rule: AnyRule): string {
69
+ private static termName(label: string, rule: RouteRule): string {
61
70
  return JunosRender.quote(rule.description ? `${label} ${rule.description}` : label);
62
71
  }
63
72
 
64
73
  private sessions(): void {
65
74
  const bgp = this.device.routing?.bgp;
66
- if (!bgp) return;
75
+ // Without our AS there is no BGP to write; `unsupported()` names it.
76
+ if (!bgp || bgp.asn === undefined) return;
67
77
 
68
78
  this.out.set("routing-options autonomous-system", bgp.asn);
69
79
 
@@ -71,35 +81,37 @@ export class JunosBgp {
71
81
  let rejectAll = false;
72
82
  let exports = false;
73
83
 
74
- // One group per session: a group holds one type, internal or external, and the config's groups are only shared settings.
84
+ // One group per session: a group holds one type, internal or external, and the config's neighbor groups are only shared settings.
75
85
  for (const [name, neighbor] of Object.entries(bgp.neighbors ?? {})) {
86
+ if (!neighbor) continue;
87
+
76
88
  const settings = JunosBgp.settingsOf(this.device, neighbor);
77
89
  const group = `protocols bgp group ${name}`;
78
90
 
79
91
  this.out.set(group, "type", settings.remote_as === bgp.asn ? "internal" : "external");
80
92
  this.out.set(group, "peer-as", settings.remote_as!);
81
93
  if (settings.description) this.out.set(group, "description", JunosRender.quote(settings.description));
82
- if (settings.local_address) this.out.set(group, "local-address", settings.local_address);
94
+ if (settings.local_address) this.out.set(group, "local-address", Address.host(settings.local_address));
83
95
  if (settings.multihop) this.out.set(group, "multihop");
84
96
  if (settings.passive) this.out.set(group, "passive");
85
97
  if (settings.hold_time !== undefined) this.out.set(group, "hold-time", settings.hold_time);
86
98
  if (settings.password) this.out.set(group, "authentication-key", Secrets.marker(settings.password));
87
99
  if (settings.bfd) JunosRender.bfd(this.out, group, this.device);
88
100
 
89
- this.out.set(group, "import", settings.import ?? JunosBgp.REJECT_ALL);
101
+ this.out.set(group, "import", settings.import_policy ?? JunosBgp.REJECT_ALL);
90
102
 
91
- if (settings.export) {
103
+ if (settings.export_policy) {
92
104
  exports = true;
93
105
  this.out.set(group, "export", JunosBgp.ORIGIN);
94
- this.out.set(group, "export", settings.export);
106
+ this.out.set(group, "export", settings.export_policy);
95
107
  } else this.out.set(group, "export", JunosBgp.REJECT_ALL);
96
108
 
97
- for (const policy of [settings.import, settings.export]) {
109
+ for (const policy of [settings.import_policy, settings.export_policy]) {
98
110
  if (policy) used.add(policy);
99
111
  else rejectAll = true;
100
112
  }
101
113
 
102
- for (const family of settings.families ?? [Address.family(neighbor.address)]) {
114
+ for (const family of settings.address_families ?? [Address.family(neighbor.address)]) {
103
115
  const nlri = `${group} family ${JunosBgp.FAMILY[family]} unicast`;
104
116
  this.out.set(nlri);
105
117
 
@@ -126,18 +138,22 @@ export class JunosBgp {
126
138
  this.out.set(statement, "term bgp from protocol bgp");
127
139
  this.out.set(statement, "term bgp then next policy");
128
140
 
129
- const networks = this.device.routing?.bgp?.networks ?? [];
130
- for (const prefix of networks) this.out.set(statement, "term networks from route-filter", prefix, "exact");
131
- if (networks.length) this.out.set(statement, "term networks then next policy");
141
+ // The term keeps its 0.x name, `networks`, so a device that holds it is not rewritten.
142
+ const originated = this.device.routing?.bgp?.originated_prefixes ?? [];
143
+ for (const prefix of originated) this.out.set(statement, "term networks from route-filter", prefix, "exact");
144
+ if (originated.length) this.out.set(statement, "term networks then next policy");
132
145
 
133
146
  this.out.set(statement, "term others then reject");
134
147
  }
135
148
 
136
149
  private prefixSets(): void {
137
- for (const [name, set] of Object.entries(this.device.prefix_sets ?? {})) {
150
+ for (const [name, set] of Object.entries(this.device.routing?.prefix_sets ?? {})) {
151
+ if (!set) continue;
152
+
138
153
  const list = `policy-options prefix-list ${name}`;
154
+ const prefixes = JunosBgp.prefixesOf(set);
139
155
 
140
- if ("prefixes" in set && set.prefixes.length) for (const prefix of set.prefixes) this.out.set(list, prefix);
156
+ if (prefixes.length) for (const prefix of prefixes) this.out.set(list, prefix);
141
157
  else this.out.set(list);
142
158
 
143
159
  if ("source" in set) this.out.note(`${JunosBgp.GENERATED}${list}`);
@@ -151,31 +167,33 @@ export class JunosBgp {
151
167
  }
152
168
 
153
169
  /** The family a rule is limited to, by its match: a family, a prefix, or a prefix set of one family. */
154
- private familyOfRule(rule: AnyRule): Family | undefined {
155
- const match = rule.match;
170
+ private familyOfRule(rule: RouteRule): Family | undefined {
171
+ const match = rule.if;
156
172
  if (match?.family) return match.family;
157
173
 
158
174
  if (match?.prefix) return Address.family(Address.host(match.prefix));
159
175
 
160
- const set = match?.prefix_set ? this.device.prefix_sets?.[match.prefix_set] : undefined;
176
+ const set = match?.prefix_set ? this.device.routing?.prefix_sets?.[match.prefix_set] : undefined;
161
177
  if (set && "source" in set) return set.source.family;
162
178
 
163
- if (set?.prefixes[0]) return Address.family(Address.host(set.prefixes[0]));
179
+ const first = set && JunosBgp.prefixesOf(set)[0];
180
+ if (first) return Address.family(Address.host(first));
164
181
 
165
182
  return undefined;
166
183
  }
167
184
 
168
185
  /** A policy's rules with each call replaced by the rules it calls, in order. A called policy that decides nothing hands back to the caller, as it does on every platform. */
169
- private flatten(policy: string, label: string, seen: string[]): { label: string; rule: AnyRule }[] {
170
- const rules = this.device.policies?.[policy] ?? [];
171
- const flat: { label: string; rule: AnyRule }[] = [];
186
+ private flatten(policy: string, label: string, seen: string[]): { label: string; rule: RouteRule }[] {
187
+ const rules = this.device.routing?.policies?.[policy] ?? [];
188
+ const flat: { label: string; rule: RouteRule }[] = [];
172
189
 
173
190
  rules.forEach((rule, index) => {
174
191
  const here = label ? `${label}.${index + 1}` : `${index + 1}`;
175
192
 
176
- if (rule.call && !seen.includes(rule.call)) flat.push(...this.flatten(rule.call, here, [...seen, rule.call]));
193
+ if ("call" in rule && rule.call && !seen.includes(rule.call)) flat.push(...this.flatten(rule.call, here, [...seen, rule.call]));
177
194
 
178
- if (rule.match || rule.set || rule.action) flat.push({ label: here, rule });
195
+ // A communities rule is not written here; `unsupported()` names it.
196
+ if (JunosBgp.isRouteRule(rule) && (rule.if || rule.set || rule.then)) flat.push({ label: here, rule });
179
197
  });
180
198
 
181
199
  return flat;
@@ -189,7 +207,7 @@ export class JunosBgp {
189
207
  for (const { label, rule } of flat) {
190
208
  const family = this.familyOfRule(rule);
191
209
  // A next hop only means something to routes of its own family, and one term cannot filter prefixes of both, so a rule for both families splits into a term for each.
192
- const split = !family && (rule.set?.next_hop || rule.match?.prefix_length) ? (["ipv4", "ipv6"] as const) : [family];
210
+ const split = !family && (rule.set?.next_hop || rule.if?.prefix_length) ? (["ipv4", "ipv6"] as const) : [family];
193
211
 
194
212
  for (const part of split) {
195
213
  const name =
@@ -201,12 +219,12 @@ export class JunosBgp {
201
219
  }
202
220
 
203
221
  const last = flat.at(-1)?.rule;
204
- const decides = last !== undefined && !last.match && last.action !== undefined;
222
+ const decides = last !== undefined && !last.if && last.then !== undefined;
205
223
  if (!decides) this.out.set(statement, "term circuit-default then reject");
206
224
  }
207
225
 
208
- private term(term: string, rule: AnyRule, family: Family | undefined, splitFamily: boolean): void {
209
- const match = rule.match ?? {};
226
+ private term(term: string, rule: RouteRule, family: Family | undefined, splitFamily: boolean): void {
227
+ const match = rule.if ?? {};
210
228
  const set = rule.set ?? {};
211
229
  const from: (string | number)[][] = [];
212
230
  const then: (string | number)[][] = [];
@@ -232,9 +250,9 @@ export class JunosBgp {
232
250
  from.push(["as-path", name]);
233
251
  }
234
252
 
235
- if (match.through_as !== undefined) {
236
- const name = `circuit-through-${match.through_as}`;
237
- this.out.set(`policy-options as-path ${name}`, JunosRender.quote(`.* ${match.through_as} .*`));
253
+ if (match.as_path_contains !== undefined) {
254
+ const name = `circuit-through-${match.as_path_contains}`;
255
+ this.out.set(`policy-options as-path ${name}`, JunosRender.quote(`.* ${match.as_path_contains} .*`));
238
256
  from.push(["as-path", name]);
239
257
  }
240
258
 
@@ -242,7 +260,7 @@ export class JunosBgp {
242
260
 
243
261
  // Members of one named community must all be present, so a rule matching both kinds names them together.
244
262
  const wanted = [
245
- ...(match.community ? [JunosBgp.member(match.community, false)] : []),
263
+ ...(match.standard_community ? [JunosBgp.member(match.standard_community, false)] : []),
246
264
  ...(match.large_community ? [JunosBgp.member(match.large_community, true)] : []),
247
265
  ];
248
266
 
@@ -252,22 +270,25 @@ export class JunosBgp {
252
270
  if (set.med !== undefined) then.push(["metric", set.med]);
253
271
 
254
272
  if (set.prepend !== undefined) {
255
- const asn = this.device.routing!.bgp!.asn;
273
+ const asn = this.device.routing!.bgp!.asn!;
256
274
  then.push(["as-path-prepend", JunosRender.quote(Array(set.prepend).fill(asn).join(" "))]);
257
275
  }
258
276
 
259
277
  // Replacing communities of one kind is removing every one of that kind, then adding the new ones.
260
278
  const removed = [
261
- ...(set.communities ? ["*:*"] : (set.remove_communities ?? [])).map((pattern) => JunosBgp.member(pattern, false)),
279
+ ...(set.standard_communities ? ["*:*"] : (set.remove_standard_communities ?? [])).map((pattern) =>
280
+ JunosBgp.member(pattern, false)
281
+ ),
262
282
  ...(set.large_communities ? ["*:*:*"] : (set.remove_large_communities ?? [])).map((pattern) => JunosBgp.member(pattern, true)),
263
283
  ];
264
284
 
265
285
  const added = [
266
- ...(set.communities ?? set.add_communities ?? []).map((value) => JunosBgp.member(value, false)),
286
+ ...(set.standard_communities ?? set.add_standard_communities ?? []).map((value) => JunosBgp.member(value, false)),
267
287
  ...(set.large_communities ?? set.add_large_communities ?? []).map((value) => JunosBgp.member(value, true)),
268
288
  ];
269
289
 
270
- if (set.communities && set.add_communities) added.push(...set.add_communities.map((value) => JunosBgp.member(value, false)));
290
+ if (set.standard_communities && set.add_standard_communities)
291
+ added.push(...set.add_standard_communities.map((value) => JunosBgp.member(value, false)));
271
292
  if (set.large_communities && set.add_large_communities)
272
293
  added.push(...set.add_large_communities.map((value) => JunosBgp.member(value, true)));
273
294
 
@@ -276,7 +297,7 @@ export class JunosBgp {
276
297
 
277
298
  if (set.next_hop && (!family || Address.family(set.next_hop) === family)) then.push(["next-hop", set.next_hop]);
278
299
  if (set.blackhole) then.push(["next-hop discard"]);
279
- if (rule.action) then.push([rule.action]);
300
+ if (rule.then) then.push([rule.then]);
280
301
 
281
302
  for (const words of from) this.out.set(term, "from", ...words);
282
303
  for (const words of then) this.out.set(term, "then", ...words);