portograph 0.0.0-stage → 0.2.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.
Files changed (446) hide show
  1. package/LICENSE +202 -0
  2. package/README.md +233 -2
  3. package/bin/cli.mjs +256 -0
  4. package/bin/compute.mjs +177 -0
  5. package/bin/font-check.d.mts +4 -0
  6. package/bin/font-check.mjs +58 -0
  7. package/bin/portograph.mjs +23 -0
  8. package/dist/cli.d.cts +109 -0
  9. package/dist/cli.d.ts +109 -0
  10. package/dist/geometry/index.d.cts +14 -0
  11. package/dist/geometry/index.d.ts +14 -0
  12. package/dist/geometry/oriented.d.cts +34 -0
  13. package/dist/geometry/oriented.d.ts +34 -0
  14. package/dist/geometry/planar.d.cts +69 -0
  15. package/dist/geometry/planar.d.ts +69 -0
  16. package/dist/index.cjs +20 -0
  17. package/dist/index.d.cts +24 -0
  18. package/dist/index.d.ts +24 -0
  19. package/dist/index.js +1 -0
  20. package/dist/internal.d.cts +73 -0
  21. package/dist/internal.d.ts +73 -0
  22. package/dist/layout/alignment-targets.d.cts +76 -0
  23. package/dist/layout/alignment-targets.d.ts +76 -0
  24. package/dist/layout/approach.d.cts +13 -0
  25. package/dist/layout/approach.d.ts +13 -0
  26. package/dist/layout/attempt-counters.d.cts +117 -0
  27. package/dist/layout/attempt-counters.d.ts +117 -0
  28. package/dist/layout/attempt-ledger.d.cts +31 -0
  29. package/dist/layout/attempt-ledger.d.ts +31 -0
  30. package/dist/layout/attempt-timings.d.cts +41 -0
  31. package/dist/layout/attempt-timings.d.ts +41 -0
  32. package/dist/layout/bands.d.cts +348 -0
  33. package/dist/layout/bands.d.ts +348 -0
  34. package/dist/layout/bar-width.d.cts +9 -0
  35. package/dist/layout/bar-width.d.ts +9 -0
  36. package/dist/layout/bars.d.cts +26 -0
  37. package/dist/layout/bars.d.ts +26 -0
  38. package/dist/layout/block-checks.d.cts +5 -0
  39. package/dist/layout/block-checks.d.ts +5 -0
  40. package/dist/layout/box-writers.d.cts +53 -0
  41. package/dist/layout/box-writers.d.ts +53 -0
  42. package/dist/layout/bundle-channel.d.cts +8 -0
  43. package/dist/layout/bundle-channel.d.ts +8 -0
  44. package/dist/layout/bundle-geometry.d.cts +163 -0
  45. package/dist/layout/bundle-geometry.d.ts +163 -0
  46. package/dist/layout/bundle-junctions.d.cts +41 -0
  47. package/dist/layout/bundle-junctions.d.ts +41 -0
  48. package/dist/layout/bundle-merge.d.cts +4 -0
  49. package/dist/layout/bundle-merge.d.ts +4 -0
  50. package/dist/layout/bundle-need.d.cts +23 -0
  51. package/dist/layout/bundle-need.d.ts +23 -0
  52. package/dist/layout/bundle-order.d.cts +10 -0
  53. package/dist/layout/bundle-order.d.ts +10 -0
  54. package/dist/layout/bundle-paths.d.cts +33 -0
  55. package/dist/layout/bundle-paths.d.ts +33 -0
  56. package/dist/layout/bundles.d.cts +5 -0
  57. package/dist/layout/bundles.d.ts +5 -0
  58. package/dist/layout/bus-room.d.cts +48 -0
  59. package/dist/layout/bus-room.d.ts +48 -0
  60. package/dist/layout/bus.d.cts +100 -0
  61. package/dist/layout/bus.d.ts +100 -0
  62. package/dist/layout/centre.d.cts +108 -0
  63. package/dist/layout/centre.d.ts +108 -0
  64. package/dist/layout/chord-boxes.d.cts +9 -0
  65. package/dist/layout/chord-boxes.d.ts +9 -0
  66. package/dist/layout/chord-clear.d.cts +32 -0
  67. package/dist/layout/chord-clear.d.ts +32 -0
  68. package/dist/layout/close-up.d.cts +37 -0
  69. package/dist/layout/close-up.d.ts +37 -0
  70. package/dist/layout/column-arrangement.d.cts +37 -0
  71. package/dist/layout/column-arrangement.d.ts +37 -0
  72. package/dist/layout/columns.d.cts +240 -0
  73. package/dist/layout/columns.d.ts +240 -0
  74. package/dist/layout/constraints/catalogue.d.cts +25 -0
  75. package/dist/layout/constraints/catalogue.d.ts +25 -0
  76. package/dist/layout/constraints/evaluate.d.cts +24 -0
  77. package/dist/layout/constraints/evaluate.d.ts +24 -0
  78. package/dist/layout/constraints/index.d.cts +8 -0
  79. package/dist/layout/constraints/index.d.ts +8 -0
  80. package/dist/layout/constraints/separation.d.cts +33 -0
  81. package/dist/layout/constraints/separation.d.ts +33 -0
  82. package/dist/layout/constraints/types.d.cts +65 -0
  83. package/dist/layout/constraints/types.d.ts +65 -0
  84. package/dist/layout/corridor-order.d.cts +61 -0
  85. package/dist/layout/corridor-order.d.ts +61 -0
  86. package/dist/layout/course-bypass.d.cts +63 -0
  87. package/dist/layout/course-bypass.d.ts +63 -0
  88. package/dist/layout/course-gap.d.cts +54 -0
  89. package/dist/layout/course-gap.d.ts +54 -0
  90. package/dist/layout/course-lanes.d.cts +79 -0
  91. package/dist/layout/course-lanes.d.ts +79 -0
  92. package/dist/layout/course-measure.d.cts +114 -0
  93. package/dist/layout/course-measure.d.ts +114 -0
  94. package/dist/layout/course-plan.d.cts +22 -0
  95. package/dist/layout/course-plan.d.ts +22 -0
  96. package/dist/layout/course-rows.d.cts +51 -0
  97. package/dist/layout/course-rows.d.ts +51 -0
  98. package/dist/layout/courses.d.cts +31 -0
  99. package/dist/layout/courses.d.ts +31 -0
  100. package/dist/layout/decide.d.cts +123 -0
  101. package/dist/layout/decide.d.ts +123 -0
  102. package/dist/layout/diagonal-text.d.cts +49 -0
  103. package/dist/layout/diagonal-text.d.ts +49 -0
  104. package/dist/layout/direct-families.d.cts +91 -0
  105. package/dist/layout/direct-families.d.ts +91 -0
  106. package/dist/layout/elk-graph.d.cts +36 -0
  107. package/dist/layout/elk-graph.d.ts +36 -0
  108. package/dist/layout/elk-worker.d.cts +34 -0
  109. package/dist/layout/elk-worker.d.ts +34 -0
  110. package/dist/layout/end-trim.d.cts +41 -0
  111. package/dist/layout/end-trim.d.ts +41 -0
  112. package/dist/layout/face-centre.d.cts +25 -0
  113. package/dist/layout/face-centre.d.ts +25 -0
  114. package/dist/layout/fan-admission.d.cts +47 -0
  115. package/dist/layout/fan-admission.d.ts +47 -0
  116. package/dist/layout/fan-spread.d.cts +43 -0
  117. package/dist/layout/fan-spread.d.ts +43 -0
  118. package/dist/layout/feed-order.d.cts +53 -0
  119. package/dist/layout/feed-order.d.ts +53 -0
  120. package/dist/layout/feed-split.d.cts +34 -0
  121. package/dist/layout/feed-split.d.ts +34 -0
  122. package/dist/layout/flanks.d.cts +21 -0
  123. package/dist/layout/flanks.d.ts +21 -0
  124. package/dist/layout/frame-paths.d.cts +209 -0
  125. package/dist/layout/frame-paths.d.ts +209 -0
  126. package/dist/layout/frames.d.cts +112 -0
  127. package/dist/layout/frames.d.ts +112 -0
  128. package/dist/layout/gap-band.d.cts +10 -0
  129. package/dist/layout/gap-band.d.ts +10 -0
  130. package/dist/layout/gap-plan.d.cts +229 -0
  131. package/dist/layout/gap-plan.d.ts +229 -0
  132. package/dist/layout/gap-room.d.cts +71 -0
  133. package/dist/layout/gap-room.d.ts +71 -0
  134. package/dist/layout/gap-runs.d.cts +95 -0
  135. package/dist/layout/gap-runs.d.ts +95 -0
  136. package/dist/layout/given-back.d.cts +23 -0
  137. package/dist/layout/given-back.d.ts +23 -0
  138. package/dist/layout/grid-chains.d.cts +84 -0
  139. package/dist/layout/grid-chains.d.ts +84 -0
  140. package/dist/layout/grid.d.cts +314 -0
  141. package/dist/layout/grid.d.ts +314 -0
  142. package/dist/layout/group-exit-check.d.cts +36 -0
  143. package/dist/layout/group-exit-check.d.ts +36 -0
  144. package/dist/layout/group-exits.d.cts +53 -0
  145. package/dist/layout/group-exits.d.ts +53 -0
  146. package/dist/layout/group-padding.d.cts +6 -0
  147. package/dist/layout/group-padding.d.ts +6 -0
  148. package/dist/layout/growth-guard.d.cts +25 -0
  149. package/dist/layout/growth-guard.d.ts +25 -0
  150. package/dist/layout/joint-order.d.cts +149 -0
  151. package/dist/layout/joint-order.d.ts +149 -0
  152. package/dist/layout/label-room-guard.d.cts +8 -0
  153. package/dist/layout/label-room-guard.d.ts +8 -0
  154. package/dist/layout/lane-kind.d.cts +11 -0
  155. package/dist/layout/lane-kind.d.ts +11 -0
  156. package/dist/layout/lanes.d.cts +278 -0
  157. package/dist/layout/lanes.d.ts +278 -0
  158. package/dist/layout/legend.d.cts +88 -0
  159. package/dist/layout/legend.d.ts +88 -0
  160. package/dist/layout/lift-judge.d.cts +28 -0
  161. package/dist/layout/lift-judge.d.ts +28 -0
  162. package/dist/layout/lift.d.cts +164 -0
  163. package/dist/layout/lift.d.ts +164 -0
  164. package/dist/layout/link-label-geometry.d.cts +32 -0
  165. package/dist/layout/link-label-geometry.d.ts +32 -0
  166. package/dist/layout/link-labels.d.cts +129 -0
  167. package/dist/layout/link-labels.d.ts +129 -0
  168. package/dist/layout/link-layer.d.cts +6 -0
  169. package/dist/layout/link-layer.d.ts +6 -0
  170. package/dist/layout/move-reservations.d.cts +49 -0
  171. package/dist/layout/move-reservations.d.ts +49 -0
  172. package/dist/layout/mutation.d.cts +32 -0
  173. package/dist/layout/mutation.d.ts +32 -0
  174. package/dist/layout/node-geometry.d.cts +3 -0
  175. package/dist/layout/node-geometry.d.ts +3 -0
  176. package/dist/layout/objective.d.cts +232 -0
  177. package/dist/layout/objective.d.ts +232 -0
  178. package/dist/layout/overlay-nest.d.cts +27 -0
  179. package/dist/layout/overlay-nest.d.ts +27 -0
  180. package/dist/layout/overlay-order.d.cts +140 -0
  181. package/dist/layout/overlay-order.d.ts +140 -0
  182. package/dist/layout/overlay-paths.d.cts +303 -0
  183. package/dist/layout/overlay-paths.d.ts +303 -0
  184. package/dist/layout/overlay-place.d.cts +99 -0
  185. package/dist/layout/overlay-place.d.ts +99 -0
  186. package/dist/layout/overlay-tracks.d.cts +93 -0
  187. package/dist/layout/overlay-tracks.d.ts +93 -0
  188. package/dist/layout/overlay-window.d.cts +83 -0
  189. package/dist/layout/overlay-window.d.ts +83 -0
  190. package/dist/layout/parallel-sets.d.cts +51 -0
  191. package/dist/layout/parallel-sets.d.ts +51 -0
  192. package/dist/layout/parent-stand.d.cts +40 -0
  193. package/dist/layout/parent-stand.d.ts +40 -0
  194. package/dist/layout/path-end.d.cts +34 -0
  195. package/dist/layout/path-end.d.ts +34 -0
  196. package/dist/layout/peers.d.cts +28 -0
  197. package/dist/layout/peers.d.ts +28 -0
  198. package/dist/layout/pill-reach.d.cts +14 -0
  199. package/dist/layout/pill-reach.d.ts +14 -0
  200. package/dist/layout/placement-attempt.d.cts +58 -0
  201. package/dist/layout/placement-attempt.d.ts +58 -0
  202. package/dist/layout/placement-refusal.d.cts +67 -0
  203. package/dist/layout/placement-refusal.d.ts +67 -0
  204. package/dist/layout/port-insets.d.cts +15 -0
  205. package/dist/layout/port-insets.d.ts +15 -0
  206. package/dist/layout/port-side.d.cts +8 -0
  207. package/dist/layout/port-side.d.ts +8 -0
  208. package/dist/layout/postconditions.d.cts +40 -0
  209. package/dist/layout/postconditions.d.ts +40 -0
  210. package/dist/layout/rank-cells.d.cts +24 -0
  211. package/dist/layout/rank-cells.d.ts +24 -0
  212. package/dist/layout/rank-input.d.cts +8 -0
  213. package/dist/layout/rank-input.d.ts +8 -0
  214. package/dist/layout/ranks.d.cts +36 -0
  215. package/dist/layout/ranks.d.ts +36 -0
  216. package/dist/layout/read/arrangement.d.cts +3 -0
  217. package/dist/layout/read/arrangement.d.ts +3 -0
  218. package/dist/layout/read/canton.d.cts +48 -0
  219. package/dist/layout/read/canton.d.ts +48 -0
  220. package/dist/layout/read/chain.d.cts +11 -0
  221. package/dist/layout/read/chain.d.ts +11 -0
  222. package/dist/layout/read/courses.d.cts +67 -0
  223. package/dist/layout/read/courses.d.ts +67 -0
  224. package/dist/layout/read/crossings.d.cts +79 -0
  225. package/dist/layout/read/crossings.d.ts +79 -0
  226. package/dist/layout/read/feeds.d.cts +53 -0
  227. package/dist/layout/read/feeds.d.ts +53 -0
  228. package/dist/layout/read/overlay-straight.d.cts +33 -0
  229. package/dist/layout/read/overlay-straight.d.ts +33 -0
  230. package/dist/layout/read/parent-spread.d.cts +13 -0
  231. package/dist/layout/read/parent-spread.d.ts +13 -0
  232. package/dist/layout/read/tier.d.cts +19 -0
  233. package/dist/layout/read/tier.d.ts +19 -0
  234. package/dist/layout/read/title-edge.d.cts +19 -0
  235. package/dist/layout/read/title-edge.d.ts +19 -0
  236. package/dist/layout/rectangle-index.d.cts +33 -0
  237. package/dist/layout/rectangle-index.d.ts +33 -0
  238. package/dist/layout/region-rebuild.d.cts +9 -0
  239. package/dist/layout/region-rebuild.d.ts +9 -0
  240. package/dist/layout/regions.d.cts +37 -0
  241. package/dist/layout/regions.d.ts +37 -0
  242. package/dist/layout/reservation-checks.d.cts +16 -0
  243. package/dist/layout/reservation-checks.d.ts +16 -0
  244. package/dist/layout/reservations.d.cts +267 -0
  245. package/dist/layout/reservations.d.ts +267 -0
  246. package/dist/layout/row-paths.d.cts +62 -0
  247. package/dist/layout/row-paths.d.ts +62 -0
  248. package/dist/layout/rows.d.cts +5 -0
  249. package/dist/layout/rows.d.ts +5 -0
  250. package/dist/layout/rule-geometry-hook.d.cts +5 -0
  251. package/dist/layout/rule-geometry-hook.d.ts +5 -0
  252. package/dist/layout/run-label-box.d.cts +139 -0
  253. package/dist/layout/run-label-box.d.ts +139 -0
  254. package/dist/layout/side-chords.d.cts +55 -0
  255. package/dist/layout/side-chords.d.ts +55 -0
  256. package/dist/layout/side-hints.d.cts +34 -0
  257. package/dist/layout/side-hints.d.ts +34 -0
  258. package/dist/layout/side-judge.d.cts +65 -0
  259. package/dist/layout/side-judge.d.ts +65 -0
  260. package/dist/layout/side-lanes.d.cts +27 -0
  261. package/dist/layout/side-lanes.d.ts +27 -0
  262. package/dist/layout/side-measure.d.cts +55 -0
  263. package/dist/layout/side-measure.d.ts +55 -0
  264. package/dist/layout/sides-one.d.cts +24 -0
  265. package/dist/layout/sides-one.d.ts +24 -0
  266. package/dist/layout/sides-two.d.cts +159 -0
  267. package/dist/layout/sides-two.d.ts +159 -0
  268. package/dist/layout/size.d.cts +114 -0
  269. package/dist/layout/size.d.ts +114 -0
  270. package/dist/layout/skeleton-geometry.d.cts +38 -0
  271. package/dist/layout/skeleton-geometry.d.ts +38 -0
  272. package/dist/layout/skeleton-graph.d.cts +33 -0
  273. package/dist/layout/skeleton-graph.d.ts +33 -0
  274. package/dist/layout/skeleton.d.cts +21 -0
  275. package/dist/layout/skeleton.d.ts +21 -0
  276. package/dist/layout/solid-blocks.d.cts +24 -0
  277. package/dist/layout/solid-blocks.d.ts +24 -0
  278. package/dist/layout/span-judge.d.cts +13 -0
  279. package/dist/layout/span-judge.d.ts +13 -0
  280. package/dist/layout/span-lanes.d.cts +37 -0
  281. package/dist/layout/span-lanes.d.ts +37 -0
  282. package/dist/layout/span-measure.d.cts +35 -0
  283. package/dist/layout/span-measure.d.ts +35 -0
  284. package/dist/layout/stagger.d.cts +40 -0
  285. package/dist/layout/stagger.d.ts +40 -0
  286. package/dist/layout/title-room.d.cts +50 -0
  287. package/dist/layout/title-room.d.ts +50 -0
  288. package/dist/layout/tolerance.d.cts +60 -0
  289. package/dist/layout/tolerance.d.ts +60 -0
  290. package/dist/layout/trial-copy.d.cts +9 -0
  291. package/dist/layout/trial-copy.d.ts +9 -0
  292. package/dist/layout/trial-findings.d.cts +25 -0
  293. package/dist/layout/trial-findings.d.ts +25 -0
  294. package/dist/layout/trunk.d.cts +69 -0
  295. package/dist/layout/trunk.d.ts +69 -0
  296. package/dist/layout/turn-measure.d.cts +42 -0
  297. package/dist/layout/turn-measure.d.ts +42 -0
  298. package/dist/layout/turn-order.d.cts +83 -0
  299. package/dist/layout/turn-order.d.ts +83 -0
  300. package/dist/migrate/index.d.cts +13 -0
  301. package/dist/migrate/index.d.ts +13 -0
  302. package/dist/model/classes.d.cts +21 -0
  303. package/dist/model/classes.d.ts +21 -0
  304. package/dist/model/document.d.cts +174 -0
  305. package/dist/model/document.d.ts +174 -0
  306. package/dist/model/geometry.d.cts +12 -0
  307. package/dist/model/geometry.d.ts +12 -0
  308. package/dist/model/index.d.cts +5 -0
  309. package/dist/model/index.d.ts +5 -0
  310. package/dist/model/json-equality.d.cts +2 -0
  311. package/dist/model/json-equality.d.ts +2 -0
  312. package/dist/model/layout.d.cts +1142 -0
  313. package/dist/model/layout.d.ts +1142 -0
  314. package/dist/model/link-ids.d.cts +16 -0
  315. package/dist/model/link-ids.d.ts +16 -0
  316. package/dist/model/model.d.cts +26 -0
  317. package/dist/model/model.d.ts +26 -0
  318. package/dist/model/read-cache.d.cts +20 -0
  319. package/dist/model/read-cache.d.ts +20 -0
  320. package/dist/node.cjs +43 -0
  321. package/dist/node.d.cts +6 -0
  322. package/dist/parse/css.d.cts +2 -0
  323. package/dist/parse/css.d.ts +2 -0
  324. package/dist/parse/index.d.cts +23 -0
  325. package/dist/parse/index.d.ts +23 -0
  326. package/dist/parse/link-order.d.cts +24 -0
  327. package/dist/parse/link-order.d.ts +24 -0
  328. package/dist/parse/validation.d.cts +39 -0
  329. package/dist/parse/validation.d.ts +39 -0
  330. package/dist/pipeline/budget.d.cts +28 -0
  331. package/dist/pipeline/budget.d.ts +28 -0
  332. package/dist/pipeline/deadline.d.cts +1 -0
  333. package/dist/pipeline/deadline.d.ts +1 -0
  334. package/dist/pipeline/driver.d.cts +14 -0
  335. package/dist/pipeline/driver.d.ts +14 -0
  336. package/dist/pipeline/env.d.cts +54 -0
  337. package/dist/pipeline/env.d.ts +54 -0
  338. package/dist/pipeline/index.d.cts +17 -0
  339. package/dist/pipeline/index.d.ts +17 -0
  340. package/dist/pipeline/initial.d.cts +3 -0
  341. package/dist/pipeline/initial.d.ts +3 -0
  342. package/dist/pipeline/lift-round.d.cts +9 -0
  343. package/dist/pipeline/lift-round.d.ts +9 -0
  344. package/dist/pipeline/limits.d.cts +16 -0
  345. package/dist/pipeline/limits.d.ts +16 -0
  346. package/dist/pipeline/options.d.cts +85 -0
  347. package/dist/pipeline/options.d.ts +85 -0
  348. package/dist/pipeline/place.d.cts +9 -0
  349. package/dist/pipeline/place.d.ts +9 -0
  350. package/dist/pipeline/publish.d.cts +4 -0
  351. package/dist/pipeline/publish.d.ts +4 -0
  352. package/dist/pipeline/round.d.cts +86 -0
  353. package/dist/pipeline/round.d.ts +86 -0
  354. package/dist/pipeline/rounds.d.cts +85 -0
  355. package/dist/pipeline/rounds.d.ts +85 -0
  356. package/dist/pipeline/state.d.cts +46 -0
  357. package/dist/pipeline/state.d.ts +46 -0
  358. package/dist/pipeline/width-budget.d.cts +6 -0
  359. package/dist/pipeline/width-budget.d.ts +6 -0
  360. package/dist/portograph-avoid-worker.mjs +260 -0
  361. package/dist/portograph.js +43369 -0
  362. package/dist/portograph.umd.cjs +154 -0
  363. package/dist/profiles/network/content.d.cts +29 -0
  364. package/dist/profiles/network/content.d.ts +29 -0
  365. package/dist/profiles/network/icons.d.cts +27 -0
  366. package/dist/profiles/network/icons.d.ts +27 -0
  367. package/dist/profiles/network/snack.d.cts +50 -0
  368. package/dist/profiles/network/snack.d.ts +50 -0
  369. package/dist/profiles/network/theme.d.cts +246 -0
  370. package/dist/profiles/network/theme.d.ts +246 -0
  371. package/dist/profiles/network/themes.d.cts +60 -0
  372. package/dist/profiles/network/themes.d.ts +60 -0
  373. package/dist/profiles/network/tiers.d.cts +8 -0
  374. package/dist/profiles/network/tiers.d.ts +8 -0
  375. package/dist/render/anonymous-end.d.cts +8 -0
  376. package/dist/render/anonymous-end.d.ts +8 -0
  377. package/dist/render/author-css.d.cts +9 -0
  378. package/dist/render/author-css.d.ts +9 -0
  379. package/dist/render/check-options.d.cts +8 -0
  380. package/dist/render/check-options.d.ts +8 -0
  381. package/dist/render/drawio.d.cts +35 -0
  382. package/dist/render/drawio.d.ts +35 -0
  383. package/dist/render/frame-order.d.cts +36 -0
  384. package/dist/render/frame-order.d.ts +36 -0
  385. package/dist/render/index.d.cts +19 -0
  386. package/dist/render/index.d.ts +19 -0
  387. package/dist/render/shared.d.cts +137 -0
  388. package/dist/render/shared.d.ts +137 -0
  389. package/dist/render/svg.d.cts +22 -0
  390. package/dist/render/svg.d.ts +22 -0
  391. package/dist/render/theme.d.cts +81 -0
  392. package/dist/render/theme.d.ts +81 -0
  393. package/dist/render/tooltips.d.cts +11 -0
  394. package/dist/render/tooltips.d.ts +11 -0
  395. package/dist/resolve/fan-family.d.cts +19 -0
  396. package/dist/resolve/fan-family.d.ts +19 -0
  397. package/dist/resolve/index.d.cts +30 -0
  398. package/dist/resolve/index.d.ts +30 -0
  399. package/dist/resolve/mesh-bus.d.cts +19 -0
  400. package/dist/resolve/mesh-bus.d.ts +19 -0
  401. package/dist/resolve/solid-blocks.d.cts +2 -0
  402. package/dist/resolve/solid-blocks.d.ts +2 -0
  403. package/dist/route/containment.d.cts +8 -0
  404. package/dist/route/containment.d.ts +8 -0
  405. package/dist/route/index.d.cts +48 -0
  406. package/dist/route/index.d.ts +48 -0
  407. package/dist/route/obstacles.d.cts +59 -0
  408. package/dist/route/obstacles.d.ts +59 -0
  409. package/dist/route/paths.d.cts +100 -0
  410. package/dist/route/paths.d.ts +100 -0
  411. package/dist/route/plan.d.cts +182 -0
  412. package/dist/route/plan.d.ts +182 -0
  413. package/dist/route/reservations.d.cts +15 -0
  414. package/dist/route/reservations.d.ts +15 -0
  415. package/dist/route/types.d.cts +59 -0
  416. package/dist/route/types.d.ts +59 -0
  417. package/dist/route/validate.d.cts +41 -0
  418. package/dist/route/validate.d.ts +41 -0
  419. package/dist/route/worker.d.cts +73 -0
  420. package/dist/route/worker.d.ts +73 -0
  421. package/dist/score/counts.d.cts +12 -0
  422. package/dist/score/counts.d.ts +12 -0
  423. package/dist/score/gates.d.cts +16 -0
  424. package/dist/score/gates.d.ts +16 -0
  425. package/dist/score/geometry.d.cts +2 -0
  426. package/dist/score/geometry.d.ts +2 -0
  427. package/dist/score/index.d.cts +5 -0
  428. package/dist/score/index.d.ts +5 -0
  429. package/dist/score/metrics.d.cts +7 -0
  430. package/dist/score/metrics.d.ts +7 -0
  431. package/dist/score/model.d.cts +76 -0
  432. package/dist/score/model.d.ts +76 -0
  433. package/dist/score/types.d.cts +135 -0
  434. package/dist/score/types.d.ts +135 -0
  435. package/dist/text/font-metrics.d.cts +9 -0
  436. package/dist/text/font-metrics.d.ts +9 -0
  437. package/dist/text/index.d.cts +32 -0
  438. package/dist/text/index.d.ts +32 -0
  439. package/dist/theme/content.d.cts +33 -0
  440. package/dist/theme/content.d.ts +33 -0
  441. package/dist/theme/geometry.d.cts +81 -0
  442. package/dist/theme/geometry.d.ts +81 -0
  443. package/dist/theme/legend.d.cts +26 -0
  444. package/dist/theme/legend.d.ts +26 -0
  445. package/dist/warm-elk.cjs +43 -0
  446. package/package.json +109 -4
package/bin/cli.mjs ADDED
@@ -0,0 +1,256 @@
1
+ // portograph command line. Plain JavaScript: it loads the built library from
2
+ // dist/ at run time, so `npm run build` must run before the CLI is usable.
3
+ //
4
+ // This process parses arguments, prints diagnostics to stderr and writes artifacts. It does no
5
+ // layout work itself. `layout`, `render` and `score` run in one child process (`compute.mjs`),
6
+ // because that is the only boundary that can really stop them: ELK and libavoid run in worker
7
+ // threads *inside* that child, and `score()` is synchronous, so an in-thread timer or a
8
+ // `Promise.race` could not interrupt either. The parent owns the single absolute deadline and
9
+ // kills the child on expiry, which takes its worker threads with it.
10
+ import { fork } from 'node:child_process';
11
+ import { existsSync, lstatSync, mkdtempSync, renameSync, rmSync, writeFileSync } from 'node:fs';
12
+ import { dirname, join } from 'node:path';
13
+ import * as nodeModule from 'node:module';
14
+ import { fileURLToPath } from 'node:url';
15
+ import { VERDANA_WARNING, verdanaInstalled } from './font-check.mjs';
16
+
17
+ // Node 22.1 and newer keep the compiled code of the library on disk between runs; older Node has no such call. The cache holds compiled code only.
18
+ nodeModule.enableCompileCache?.();
19
+ const require = nodeModule.createRequire(import.meta.url);
20
+ const { version } = require('../package.json');
21
+ const COMPUTE = fileURLToPath(new URL('./compute.mjs', import.meta.url));
22
+
23
+ async function loadLibrary() {
24
+ try {
25
+ return await import('../dist/portograph.js');
26
+ } catch (error) {
27
+ // A missing solver package is also ERR_MODULE_NOT_FOUND; only a missing bundle means "build first".
28
+ if (error && error.code === 'ERR_MODULE_NOT_FOUND' && !existsSync(new URL('../dist/portograph.js', import.meta.url))) {
29
+ throw new Error('portograph: build output missing; run `npm run build` first', { cause: error });
30
+ }
31
+ throw error;
32
+ }
33
+ }
34
+
35
+ /** All of a stream as UTF-8 text; stdin is read here, in the parent, so the child needs no handle on it. */
36
+ async function readAll(stream) {
37
+ const chunks = [];
38
+ for await (const chunk of stream) chunks.push(typeof chunk === 'string' ? Buffer.from(chunk) : chunk);
39
+ return Buffer.concat(chunks).toString('utf8');
40
+ }
41
+
42
+ /** The deadline a command really gets. The environment override exists so tests can prove the
43
+ * termination path with real computational work instead of waiting a minute. */
44
+ function deadlineMs(fallback) {
45
+ const raw = process.env.PORTOGRAPH_COMMAND_DEADLINE_MS;
46
+ if (raw === undefined) return fallback;
47
+ const value = Number(raw);
48
+ if (!Number.isFinite(value) || value <= 0) throw new Error(`portograph: PORTOGRAPH_COMMAND_DEADLINE_MS must be a positive number of milliseconds`);
49
+ return value;
50
+ }
51
+
52
+ /**
53
+ * When the deadline clock starts. A test hook only: `PORTOGRAPH_COMMAND_DEADLINE_FROM=elk-worker`
54
+ * starts the `PORTOGRAPH_COMMAND_DEADLINE_MS` clock when the first ELK worker starts, so a test can
55
+ * prove the kill during real ELK work on a host of any speed. Until then, the shipped deadline holds.
56
+ */
57
+ function deadlinePlan(fallback) {
58
+ const limitMs = deadlineMs(fallback);
59
+ const from = process.env.PORTOGRAPH_COMMAND_DEADLINE_FROM;
60
+ if (from === undefined) return { startMs: limitMs };
61
+ if (from !== 'elk-worker') throw new Error(`portograph: PORTOGRAPH_COMMAND_DEADLINE_FROM must be elk-worker`);
62
+ return { startMs: fallback, elkMs: limitMs };
63
+ }
64
+
65
+ /**
66
+ * Run the compute job in a child process under one absolute deadline.
67
+ *
68
+ * Returns `{ ok: true, result }`, or `{ ok: false, kind, message, diagnostics }` where `kind` is
69
+ * `input` or `processing`. Progress and diagnostics reach `io.err` while the child still runs, so
70
+ * a long command is not silent.
71
+ */
72
+ function compute(request, io, plan, inputText) {
73
+ return new Promise((resolve) => {
74
+ // The child's own budget always counts from the start. The test hook moves only this process's deadline.
75
+ const commandDeadlineAt = Date.now() + plan.startMs;
76
+ let deadlineAt = commandDeadlineAt, limitText = `${plan.startMs} ms`, timer, elkArmed = false;
77
+ let child;
78
+ try {
79
+ child = fork(COMPUTE, [], { stdio: ['ignore', 'pipe', 'pipe', 'ipc'] });
80
+ } catch (error) {
81
+ resolve({ ok: false, kind: 'processing', message: `could not start the compute process: ${error.message}`, diagnostics: [] });
82
+ return;
83
+ }
84
+ // Nothing the child writes to its own streams is ever an artifact. Forward both to stderr so a
85
+ // worker crash is visible without being able to pollute the primary artifact on stdout.
86
+ for (const stream of [child.stdout, child.stderr]) stream?.on('data', (chunk) => io.err.write(chunk));
87
+
88
+ if (process.env.PORTOGRAPH_PROGRESS === '1') io.err.write(`portograph: compute process ${child.pid}\n`);
89
+ let settled = false;
90
+ const finish = (value) => {
91
+ if (settled) return;
92
+ if (value.ok && Date.now() >= deadlineAt) value = { ok: false, kind: 'processing', code: 'COMMAND_DEADLINE',
93
+ message: `the command did not finish within ${limitText}; no artifacts were published` };
94
+ settled = true;
95
+ clearTimeout(timer);
96
+ // Whatever the outcome, this process tree stops here: SIGKILL cannot be caught or delayed,
97
+ // and it takes the child's ELK and libavoid worker threads with it.
98
+ if (child.exitCode !== null || child.signalCode !== null) { resolve(value); return; }
99
+ child.once('exit', () => resolve(value));
100
+ child.kill('SIGKILL');
101
+ };
102
+ const arm = () => {
103
+ clearTimeout(timer);
104
+ timer = setTimeout(() => {
105
+ finish({ ok: false, kind: 'processing', code: 'COMMAND_DEADLINE',
106
+ message: `the command did not finish within ${limitText}; no layout, score or gate result was produced` });
107
+ }, Math.max(1, deadlineAt - Date.now()));
108
+ };
109
+ arm();
110
+
111
+ child.on('message', (message) => {
112
+ if (message.type === 'diagnostic' || message.type === 'progress') io.err.write(`${message.text}\n`);
113
+ else if (message.type === 'result') finish(message.value);
114
+ else if (message.type === 'elk-worker' && plan.elkMs !== undefined && !elkArmed && !settled) {
115
+ elkArmed = true;
116
+ deadlineAt = Date.now() + plan.elkMs;
117
+ limitText = `${plan.elkMs} ms after the ELK worker started`;
118
+ arm();
119
+ }
120
+ });
121
+ child.on('error', (error) => finish({ ok: false, kind: 'processing', message: `compute process failed: ${error.message}` }));
122
+ // A queued result message is delivered before this turn of the loop ends; defer so an exit
123
+ // that follows a successful send is not mistaken for a silent death.
124
+ child.on('exit', (code, signal) => setImmediate(() => finish({ ok: false, kind: 'processing',
125
+ message: `compute process ended without a result (${signal ? `signal ${signal}` : `exit code ${code}`})` })));
126
+ child.send({ type: 'run', request, inputText, deadlineAt: commandDeadlineAt });
127
+ });
128
+ }
129
+
130
+ /** Stage every artifact before replacing any destination; roll back the set on an I/O failure. */
131
+ function publishArtifacts(entries) {
132
+ for (const [path] of entries) {
133
+ if (existsSync(path) && !lstatSync(path).isFile() && !lstatSync(path).isSymbolicLink()) {
134
+ throw new Error(`${path} is not a file`);
135
+ }
136
+ }
137
+ const temporary = mkdtempSync(join(dirname(entries[0][0]), '.portograph-'));
138
+ const installed = [], saved = [];
139
+ let removeTemporary = true;
140
+ try {
141
+ for (const [index, [, contents]] of entries.entries()) writeFileSync(join(temporary, `new-${index}`), contents);
142
+ for (const [index, [path]] of entries.entries()) {
143
+ const backup = join(temporary, `old-${index}`);
144
+ // lstat also sees a dangling symlink, which existsSync deliberately does not.
145
+ try { lstatSync(path); renameSync(path, backup); saved.push([path, backup]); }
146
+ catch (error) { if (error.code !== 'ENOENT') throw error; }
147
+ renameSync(join(temporary, `new-${index}`), path);
148
+ installed.push(path);
149
+ }
150
+ } catch (error) {
151
+ try {
152
+ for (const path of installed.reverse()) rmSync(path, { force: true });
153
+ for (const [path, backup] of saved.reverse()) renameSync(backup, path);
154
+ } catch (rollbackError) {
155
+ removeTemporary = false;
156
+ throw new Error(`${error.message}; rollback failed: ${rollbackError.message}; retained backups in ${temporary}`, { cause: rollbackError });
157
+ }
158
+ throw error;
159
+ } finally {
160
+ if (removeTemporary) rmSync(temporary, { recursive: true, force: true });
161
+ }
162
+ }
163
+
164
+ function writeArtifacts(command, result, io, library) {
165
+ const { EXIT, sidecarPaths, formatStatus } = library;
166
+ const status = (severity, code, message) => io.err.write(`${formatStatus(command.diagnostics, severity, code, message)}\n`);
167
+ if (command.output === undefined) {
168
+ io.out.write(result.primary);
169
+ if (!result.primary.endsWith('\n')) io.out.write('\n');
170
+ } else {
171
+ try {
172
+ if (result.sidecars) {
173
+ const paths = sidecarPaths(command.output);
174
+ publishArtifacts([[command.output, result.primary], [paths.layout, result.sidecars.layout], [paths.score, result.sidecars.score]]);
175
+ status('info', 'WROTE', `wrote ${command.output}, ${paths.layout}, ${paths.score}`);
176
+ } else {
177
+ publishArtifacts([[command.output, result.primary]]);
178
+ status('info', 'WROTE', `wrote ${command.output}`);
179
+ }
180
+ } catch (error) {
181
+ status('error', 'OUTPUT_FAILED', `cannot write ${command.output}: ${error.message}`);
182
+ return EXIT.inputError;
183
+ }
184
+ }
185
+ if (result.passed === false) {
186
+ status('error', 'GATE_FAILED', 'an enforced gate or assertion failed; see the score report');
187
+ return command.allowGateFailures ? EXIT.ok : EXIT.gateFailure;
188
+ }
189
+ return EXIT.ok;
190
+ }
191
+
192
+ /**
193
+ * Run the CLI on `argv` (without the node and script paths).
194
+ * Writes to `io.out` / `io.err` and returns the process exit code.
195
+ */
196
+ export async function main(argv, io = { out: process.stdout, err: process.stderr, in: process.stdin }) {
197
+ const library = await loadLibrary();
198
+ const { COMMAND_DEADLINE_MS, EXIT, formatStatus, helpText, isVerb, parseCommand } = library;
199
+ const [verb, ...rest] = argv;
200
+
201
+ if (verb === undefined || verb === '--help' || verb === '-h') {
202
+ io.out.write(helpText());
203
+ return EXIT.ok;
204
+ }
205
+ if (verb === '--version' || verb === '-V') {
206
+ io.out.write(`${version}\n`);
207
+ return EXIT.ok;
208
+ }
209
+ if (!isVerb(verb)) {
210
+ io.err.write(`portograph: unknown verb '${verb}'\n\n${helpText()}`);
211
+ return EXIT.inputError;
212
+ }
213
+ if (rest.includes('--help') || rest.includes('-h')) {
214
+ io.out.write(helpText());
215
+ return EXIT.ok;
216
+ }
217
+
218
+ let command;
219
+ try {
220
+ command = parseCommand(verb, rest);
221
+ } catch (error) {
222
+ // A caller that asked for JSON gets JSON for a usage error too; the option is read before parsing fails.
223
+ const asJson = rest.includes('--diagnostics=json') || rest.some((argument, index) => argument === '--diagnostics' && rest[index + 1] === 'json');
224
+ if (asJson) io.err.write(`${formatStatus('json', 'error', 'USAGE', error.message)}\n`);
225
+ else io.err.write(`portograph: ${error.message}\n\nRun 'portograph --help' for the full usage.\n`);
226
+ return EXIT.inputError;
227
+ }
228
+
229
+ let plan;
230
+ try {
231
+ plan = deadlinePlan(COMMAND_DEADLINE_MS);
232
+ } catch (error) {
233
+ io.err.write(`${error.message}\n`);
234
+ return EXIT.inputError;
235
+ }
236
+
237
+ let inputText;
238
+ if (command.input === '-') {
239
+ try {
240
+ inputText = await readAll(io.in);
241
+ } catch (error) {
242
+ io.err.write(`${formatStatus(command.diagnostics, 'error', 'STDIN_FAILED', `cannot read stdin: ${error.message}`)}\n`);
243
+ return EXIT.inputError;
244
+ }
245
+ }
246
+
247
+ // Only the CLI looks at the machine's fonts; the library output stays a pure function of the document.
248
+ if ((verb === 'render' || verb === 'layout') && !verdanaInstalled()) {
249
+ io.err.write(`${formatStatus(command.diagnostics, 'warning', 'FONT_MISSING', VERDANA_WARNING, true)}\n`);
250
+ }
251
+
252
+ const outcome = await compute(command, io, plan, inputText);
253
+ if (outcome.ok) return writeArtifacts(command, outcome.result, io, library);
254
+ io.err.write(`${formatStatus(command.diagnostics, 'error', outcome.code ?? (outcome.kind === 'input' ? 'INVALID_INPUT' : 'FAILED'), outcome.message, Boolean(outcome.code))}\n`);
255
+ return outcome.kind === 'input' ? EXIT.inputError : EXIT.processingFailure;
256
+ }
@@ -0,0 +1,177 @@
1
+ // The CLI's compute boundary. One command's whole job runs here, in a child process the parent
2
+ // can really terminate: ELK and libavoid run in worker threads inside this process, and `score()`
3
+ // is a synchronous loop, so nothing inside could interrupt itself. The parent kills this process
4
+ // when its deadline expires, which takes the worker threads with it.
5
+ //
6
+ // This process writes no artifact and no diagnostic of its own. It returns strings to the parent,
7
+ // which owns stdout, stderr and the files.
8
+ import { readFileSync } from 'node:fs';
9
+ import * as nodeModule from 'node:module';
10
+
11
+ // Node 22.1 and newer keep the compiled code of the library and of its packages on disk between runs; older Node has no such call. The cache holds compiled code only.
12
+ nodeModule.enableCompileCache?.();
13
+ const require = nodeModule.createRequire(import.meta.url);
14
+
15
+ function loadLibrary() {
16
+ return import('../dist/portograph.js');
17
+ }
18
+
19
+ /** `input` blames the document, `processing` blames the tool: a deadline, an asset, an internal bug. */
20
+ const failure = (kind, message, code) => ({ ok: false, kind, message, ...(code ? { code } : {}) });
21
+
22
+ function readInput(path, stdinText) {
23
+ if (path === '-') return { ok: true, text: stdinText ?? '' };
24
+ try {
25
+ return { ok: true, text: readFileSync(path, 'utf8') };
26
+ } catch (error) {
27
+ return failure('input', `cannot read ${path}: ${error.message}`);
28
+ }
29
+ }
30
+
31
+ async function run(request, emit) {
32
+ const api = await loadLibrary();
33
+ const { CommandDeadlineError, ElkDeadlineError, RouteDeadlineError, ParseError, ResolveError, createBudget, formatDiagnostic, formatDiagnosticJson, layout,
34
+ drawio, migrateWithDiagnostics, parseWithDiagnostics, render, restoreFinalLayout, score,
35
+ serializeLayoutModel } = api;
36
+
37
+ const budget = createBudget(Math.max(1, request.deadlineAt - Date.now()));
38
+ const printed = new Set();
39
+ const say = (diagnostic, location) => {
40
+ const key = JSON.stringify(diagnostic);
41
+ if (printed.has(key)) return;
42
+ printed.add(key);
43
+ const place = location === undefined ? undefined : { source: label, ...location };
44
+ emit({ type: 'diagnostic', text: (request.command.diagnostics === 'json' ? formatDiagnosticJson : formatDiagnostic)(diagnostic, place) });
45
+ };
46
+ const progress = process.env.PORTOGRAPH_PROGRESS === '1'
47
+ ? (event) => emit({ type: 'progress',
48
+ text: `portograph: ${event.stage} took ${Math.round(event.durationMs)} ms, ${Number.isFinite(event.remainingMs) ? `${Math.round(event.remainingMs)} ms left` : 'no deadline'}` })
49
+ : undefined;
50
+
51
+ const label = request.command.input === '-' ? '<stdin>' : request.command.input;
52
+ const { verb, input, force, allowGateFailures, timings, layers, format, theme, labels, icons } = request.command;
53
+ // One place decides what `render` writes, so the restored-layout path and the from-YAML path can
54
+ // never drift apart on it.
55
+ const primaryArtifact = (model) => format === 'drawio'
56
+ ? drawio(model, { ...(layers ? { layers } : {}), ...(theme ? { theme } : {}), ...(labels ? { labels } : {}), ...(icons ? { icons } : {}),
57
+ onDiagnostic: (diagnostic) => say(diagnostic) })
58
+ : render(model, layers ? { layers } : {});
59
+ // Timings change on every run. The published artifacts leave them out unless `--timings` asks.
60
+ const published = (model) => (timings ? model : { ...model, runtime: { stages: [] } });
61
+ const publishedReport = (report) => {
62
+ if (timings) return report;
63
+ return { snapshot: report.snapshot, runtime: { stages: [] } };
64
+ };
65
+ void allowGateFailures; // the parent applies it to the exit code; the report never changes.
66
+ const source = readInput(input, request.inputText);
67
+ if (source.ok !== true) return source;
68
+ if (verb === 'migrate') {
69
+ const result = migrateWithDiagnostics(source.text, { force });
70
+ for (const diagnostic of result.diagnostics) say(diagnostic);
71
+ return result.ok ? { ok: true, result: { primary: result.yaml } }
72
+ : failure('input', `${label} cannot be migrated`);
73
+ }
74
+
75
+ // `score` reads a serialized layout, never authored YAML. YAML is a superset of JSON, so handing
76
+ // this text to the parser would "succeed" at reinterpreting a layout model as a document.
77
+ let serializedLayout = verb === 'score';
78
+ if (verb === 'render') {
79
+ try {
80
+ const candidate = JSON.parse(source.text);
81
+ serializedLayout = candidate !== null && typeof candidate === 'object'
82
+ && ['document', 'stage', 'freeze', 'tierBands'].some(key => Object.hasOwn(candidate, key));
83
+ } catch { /* Authored YAML, or an input error for the schema parser to explain. */ }
84
+ }
85
+ if (serializedLayout) {
86
+ let model;
87
+ try {
88
+ model = restoreFinalLayout(source.text);
89
+ } catch (error) {
90
+ return failure('input', `${label} is not a final layout: ${error.message}`);
91
+ }
92
+ for (const diagnostic of model.diagnostics) say(diagnostic);
93
+ if (Object.keys(model.nodes).length > 1000 && !force) {
94
+ return failure('input', `${label} has more than 1000 nodes; ${verb} requires --force`);
95
+ }
96
+ const report = score(model, { force });
97
+ if (verb === 'render') return { ok: true, result: {
98
+ primary: primaryArtifact(model),
99
+ sidecars: { layout: `${serializeLayoutModel(published(model))}\n`, score: `${JSON.stringify(publishedReport(report), null, 2)}\n` },
100
+ passed: report.snapshot.passed,
101
+ } };
102
+ return { ok: true, result: { primary: `${JSON.stringify(publishedReport(report), null, 2)}\n`, passed: report.snapshot.passed } };
103
+ }
104
+
105
+ // One ELK worker serves every attempt of this command. It boots on its own thread while the parser runs; a worker that fails to start
106
+ // is reported after the parse, as before, so an input error is never hidden behind an asset error.
107
+ let nodeRuntime;
108
+ let elk;
109
+ let assetError;
110
+ try {
111
+ nodeRuntime = require('../dist/node.cjs');
112
+ // The warm worker is internal to the CLI: it is built here on the worker file of the installed `elkjs`, not exported by `portograph/node`.
113
+ const Worker = require('web-worker');
114
+ const workerUrl = require.resolve('elkjs/lib/elk-worker.min.js');
115
+ elk = require('../dist/warm-elk.cjs').createWarmElk(api.createWorkerElkAttempt, () => new Worker(workerUrl));
116
+ elk.warm();
117
+ } catch (error) {
118
+ assetError = error;
119
+ }
120
+
121
+ const parsed = parseWithDiagnostics(source.text, { force, onDiagnostic: say });
122
+ if (!parsed.ok) return failure('input', `${label} has ${parsed.diagnostics.filter((d) => d.severity === 'error').length} error(s)`);
123
+
124
+ let router;
125
+ try {
126
+ if (assetError) throw assetError;
127
+ router = nodeRuntime.createNodeRouteFactory();
128
+ } catch (error) {
129
+ return failure('processing', `a solver worker or module is unavailable: ${error.message}`, 'WORKER_ASSET');
130
+ }
131
+
132
+ let model;
133
+ try {
134
+ model = await layout(parsed.document, {
135
+ diagnostics: parsed.diagnostics,
136
+ createElkAttempt: () => {
137
+ const attempt = elk.attempt();
138
+ // The test hook of the parent (`PORTOGRAPH_COMMAND_DEADLINE_FROM`) starts its deadline clock here, before the progress line.
139
+ if (process.env.PORTOGRAPH_COMMAND_DEADLINE_FROM === 'elk-worker') emit({ type: 'elk-worker' });
140
+ if (progress) emit({ type: 'progress', text: 'portograph: ELK worker started' });
141
+ return attempt;
142
+ },
143
+ createRouteAttempt: router,
144
+ budget,
145
+ ...(progress ? { onProgress: progress } : {}),
146
+ });
147
+ } catch (error) {
148
+ if (error instanceof ResolveError || error instanceof ParseError) {
149
+ for (const diagnostic of error.diagnostics) say(diagnostic);
150
+ return failure('input', `${label} cannot be laid out`);
151
+ }
152
+ if (error instanceof CommandDeadlineError || error instanceof RouteDeadlineError || error instanceof ElkDeadlineError) return failure('processing', error.message, 'COMMAND_DEADLINE');
153
+ return failure('processing', `layout failed: ${error.message}`, 'PIPELINE_FAILED');
154
+ }
155
+ for (const diagnostic of model.diagnostics) say(diagnostic);
156
+
157
+ const layoutJson = `${serializeLayoutModel(published(model))}\n`;
158
+ if (verb === 'layout') return { ok: true, result: { primary: layoutJson } };
159
+
160
+ const report = score(model, { force });
161
+ return { ok: true, result: {
162
+ primary: primaryArtifact(model),
163
+ sidecars: { layout: layoutJson, score: `${JSON.stringify(publishedReport(report), null, 2)}\n` },
164
+ passed: report.snapshot.passed,
165
+ } };
166
+ }
167
+
168
+ process.on('message', (message) => {
169
+ if (message.type !== 'run') return;
170
+ const emit = (event) => { process.send?.(event); };
171
+ const send = (value) => {
172
+ // Flush the result before exiting, so the parent can never see the exit without the answer.
173
+ process.send?.({ type: 'result', value }, () => { process.exit(0); });
174
+ };
175
+ run({ command: message.request, deadlineAt: message.deadlineAt, inputText: message.inputText }, emit)
176
+ .then(send, (error) => send(failure('processing', `internal error: ${error && error.stack ? error.stack : String(error)}`, 'INTERNAL')));
177
+ });
@@ -0,0 +1,4 @@
1
+ /** Is Verdana installed on this machine? The CLI warns when it is not; see `font-check.mjs`. */
2
+ export declare function fontDirectories(platform?: string, env?: Record<string, string | undefined>, home?: string): string[];
3
+ export declare function verdanaInstalled(directories?: string[]): boolean;
4
+ export declare const VERDANA_WARNING: string;
@@ -0,0 +1,58 @@
1
+ // Is Verdana installed on this machine? The CLI warns when it is not. The library never asks: its output is a
2
+ // pure function of the document, and the text is measured from the checked-in Verdana advances either way.
3
+ // The check is cheap and best effort: it looks in the usual font directories of macOS, Linux and Windows,
4
+ // three levels deep, for a file named like Verdana. A font that only a font server or an application knows is missed.
5
+ import { readdirSync, statSync } from 'node:fs';
6
+ import { homedir } from 'node:os';
7
+ import { join } from 'node:path';
8
+
9
+ const FILE = /^verdana.*\.(ttf|otf|ttc)$/i;
10
+ const DEPTH = 3;
11
+
12
+ /** The usual font directories of the platform. `PORTOGRAPH_FONT_DIRS` (a path-delimited list) replaces them; tests use it. */
13
+ export function fontDirectories(platform = process.platform, env = process.env, home = homedir()) {
14
+ if (env.PORTOGRAPH_FONT_DIRS !== undefined) return env.PORTOGRAPH_FONT_DIRS.split(platform === 'win32' ? ';' : ':').filter(Boolean);
15
+ if (platform === 'darwin') {
16
+ return ['/System/Library/Fonts/Supplemental', '/System/Library/Fonts', '/Library/Fonts', join(home, 'Library/Fonts')];
17
+ }
18
+ if (platform === 'win32') {
19
+ const windows = env.WINDIR ?? env.SystemRoot ?? 'C:\\Windows';
20
+ const local = env.LOCALAPPDATA ?? join(home, 'AppData', 'Local');
21
+ return [join(windows, 'Fonts'), join(local, 'Microsoft', 'Windows', 'Fonts')];
22
+ }
23
+ return ['/usr/share/fonts', '/usr/local/share/fonts', join(home, '.fonts'), join(home, '.local/share/fonts')];
24
+ }
25
+
26
+ function linksToFile(path) {
27
+ try {
28
+ return statSync(path).isFile();
29
+ } catch {
30
+ // A broken link holds no font.
31
+ return false;
32
+ }
33
+ }
34
+
35
+ function hasVerdana(directory, depth) {
36
+ let entries;
37
+ try {
38
+ entries = readdirSync(directory, { withFileTypes: true });
39
+ } catch {
40
+ // An absent or unreadable directory holds no font for this check; "not found" is reported only after all of them.
41
+ return false;
42
+ }
43
+ for (const entry of entries) {
44
+ if (entry.isFile() && FILE.test(entry.name)) return true;
45
+ // A font manager (Homebrew casks, Nix, fontconfig setups) can link a font file into the directory; the link counts when it ends at a file.
46
+ if (entry.isSymbolicLink() && FILE.test(entry.name) && linksToFile(join(directory, entry.name))) return true;
47
+ if (entry.isDirectory() && depth > 1 && hasVerdana(join(directory, entry.name), depth - 1)) return true;
48
+ }
49
+ return false;
50
+ }
51
+
52
+ /** True when a Verdana file sits in one of `directories` (default: the usual ones of this platform). */
53
+ export function verdanaInstalled(directories = fontDirectories()) {
54
+ return directories.some((directory) => hasVerdana(directory, DEPTH));
55
+ }
56
+
57
+ export const VERDANA_WARNING =
58
+ 'Verdana is not installed on this machine. Text is measured from Verdana, so a viewer without it draws text that can overflow its box. Install Verdana (see the README).';
@@ -0,0 +1,23 @@
1
+ #!/usr/bin/env node
2
+ // Executable shim. It carries the .mjs extension because the package is
3
+ // "type": "module" and Node refuses to load an extensionless entry point in that
4
+ // context (ERR_UNKNOWN_FILE_EXTENSION).
5
+ import { main } from './cli.mjs';
6
+
7
+ // A reader that closes early (`portograph layout x.yaml | head`) is not a failure of the tool.
8
+ process.stdout.on('error', (error) => {
9
+ if (error.code === 'EPIPE') process.exit(0);
10
+ throw error;
11
+ });
12
+
13
+ main(process.argv.slice(2)).then(
14
+ (code) => {
15
+ process.exitCode = code;
16
+ },
17
+ (error) => {
18
+ // main() returns 2 for every input error it recognises, so anything that escapes it is the
19
+ // tool failing, not the document: a missing build, an unwritable path, an internal bug.
20
+ process.stderr.write(`${error instanceof Error ? error.message : String(error)}\n`);
21
+ process.exitCode = 3;
22
+ },
23
+ );
package/dist/cli.d.cts ADDED
@@ -0,0 +1,109 @@
1
+ import { Diagnostic, Layer } from './model/index.cjs';
2
+ import { ThemeMode } from './profiles/network/theme.cjs';
3
+ export declare const VERBS: readonly [{
4
+ readonly name: "layout";
5
+ readonly summary: "YAML in, layout JSON out";
6
+ }, {
7
+ readonly name: "render";
8
+ readonly summary: "YAML in, SVG plus layout JSON and score out";
9
+ }, {
10
+ readonly name: "score";
11
+ readonly summary: "layout JSON in, gates and soft score out";
12
+ }, {
13
+ readonly name: "migrate";
14
+ readonly summary: "d3-fd YAML in, schema v2 YAML out";
15
+ }];
16
+ export type Verb = (typeof VERBS)[number]['name'];
17
+ /**
18
+ * Process exit codes.
19
+ *
20
+ * `gateFailure` means a *complete* score report says an enforced gate or assertion failed — the
21
+ * artifacts are still written, because a failing report is the useful output. `inputError` covers
22
+ * usage, unreadable files, invalid YAML or JSON, and the node-count refusal. `processingFailure`
23
+ * covers a command deadline, a missing solver worker or module and any internal pipeline failure:
24
+ * calling one of those an input error would blame the author for the tool's own limit.
25
+ */
26
+ export declare const EXIT: {
27
+ readonly ok: 0;
28
+ readonly gateFailure: 1;
29
+ readonly inputError: 2;
30
+ readonly processingFailure: 3;
31
+ };
32
+ /** Wall milliseconds a whole command may take before its compute boundary is terminated. */
33
+ export declare const COMMAND_DEADLINE_MS = 120000;
34
+ export declare function isVerb(value: string): value is Verb;
35
+ export declare class UsageError extends Error {
36
+ constructor(message: string);
37
+ }
38
+ /** How diagnostics and status lines reach stderr: a text line each, or one JSON object per line. */
39
+ export declare const DIAGNOSTIC_FORMATS: readonly ["text", "json"];
40
+ export type DiagnosticFormat = (typeof DIAGNOSTIC_FORMATS)[number];
41
+ export interface Command {
42
+ verb: Verb;
43
+ /** A path, or `-` for stdin. */
44
+ input: string;
45
+ /** Absent means the primary artifact goes to stdout; so does `-`, which parsing drops. */
46
+ output?: string;
47
+ /** Absent means `text`. */
48
+ diagnostics?: DiagnosticFormat;
49
+ force: boolean;
50
+ allowGateFailures: boolean;
51
+ /** Keep the wall-clock stage timings in the layout and score JSON. Absent means they are left out, so two runs write the same bytes. */
52
+ timings: boolean;
53
+ /** `render` only. Absent means both families draw and `document.overlays` applies. */
54
+ layers?: Layer[];
55
+ /** `render` only. Absent means SVG. */
56
+ format?: Format;
57
+ /** `render --format drawio` only. Absent means `auto`: every colour as a light/dark pair. */
58
+ theme?: ThemeMode | 'auto';
59
+ /** `render --format drawio` only. Absent means `on-run`. */
60
+ labels?: LabelPlacement;
61
+ /** `render --format drawio` only. Absent means `cisco19`. */
62
+ icons?: IconSet;
63
+ }
64
+ export declare function helpText(): string;
65
+ /** The formats `render` can write. The SVG carries all three palettes; a draw.io file carries one. */
66
+ export declare const FORMATS: readonly ["svg", "drawio"];
67
+ export type Format = (typeof FORMATS)[number];
68
+ /** Where a draw.io link label sits; see `DrawioOptions.labels`. */
69
+ export declare const LABEL_PLACEMENTS: readonly ["on-run", "measured"];
70
+ export type LabelPlacement = (typeof LABEL_PLACEMENTS)[number];
71
+ /** Which glyphs a draw.io export draws; see `DrawioOptions.icons`. */
72
+ export declare const ICON_SETS: readonly ["cisco19", "profile"];
73
+ export type IconSet = (typeof ICON_SETS)[number];
74
+ /**
75
+ * Parse one already-validated verb's arguments. Options and the single input path may come in any
76
+ * order, each option at most once, and an option a verb does not accept is a usage error rather
77
+ * than something quietly ignored.
78
+ */
79
+ export declare function parseCommand(verb: Verb, args: readonly string[]): Command;
80
+ export declare function sidecarPaths(output: string): {
81
+ layout: string;
82
+ score: string;
83
+ };
84
+ /** Where a diagnostic points in the authored text, as `parseWithDiagnostics` reports it. */
85
+ export interface DiagnosticPlace {
86
+ source: string;
87
+ line: number;
88
+ column: number;
89
+ }
90
+ /**
91
+ * One diagnostic, one stderr line, built from its structured fields. A parse diagnostic keeps its
92
+ * exact JSONPath and, when the place in the text is known, adds `file:line:col` before it; a later
93
+ * stage names the objects it is about, because those have no source path.
94
+ */
95
+ export declare function formatDiagnostic(diagnostic: Diagnostic & {
96
+ path?: string;
97
+ }, place?: DiagnosticPlace): string;
98
+ /**
99
+ * The same diagnostic as one JSON object, for `--diagnostics json`. The structured fields stay as
100
+ * they are; a place in the authored text is added as `source`, `line` and `column`.
101
+ */
102
+ export declare function formatDiagnosticJson(diagnostic: Diagnostic & {
103
+ path?: string;
104
+ }, place?: DiagnosticPlace): string;
105
+ /**
106
+ * A status line of the command itself (not a diagnostic of the document) in the chosen format.
107
+ * The text form names severity and code only when `showCode` is set, as the coded failures do.
108
+ */
109
+ export declare function formatStatus(format: DiagnosticFormat | undefined, severity: 'info' | 'warning' | 'error', code: string, message: string, showCode?: boolean): string;