@shortlink-org/portolan 0.1.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 (826) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +387 -0
  3. package/catalog/enum_test.go +46 -0
  4. package/catalog/model.go +932 -0
  5. package/catalog/roundtrip_test.go +185 -0
  6. package/catalog/via_test.go +38 -0
  7. package/cli/portolan.mjs +368 -0
  8. package/go.mod +14 -0
  9. package/go.sum +14 -0
  10. package/index.html +13 -0
  11. package/internal/commands/cargo.go +154 -0
  12. package/internal/commands/commands.go +143 -0
  13. package/internal/commands/commands_test.go +323 -0
  14. package/internal/commands/gradle.go +77 -0
  15. package/internal/commands/justfile.go +82 -0
  16. package/internal/commands/makefile.go +106 -0
  17. package/internal/commands/maven.go +93 -0
  18. package/internal/commands/packagejson.go +159 -0
  19. package/internal/commands/pyproject.go +169 -0
  20. package/internal/commands/taskfile.go +101 -0
  21. package/internal/commands/testdata/estate/.cargo/config.toml +7 -0
  22. package/internal/commands/testdata/estate/Makefile +33 -0
  23. package/internal/commands/testdata/estate/Taskfile.yml +21 -0
  24. package/internal/commands/testdata/estate/build.gradle.kts +21 -0
  25. package/internal/commands/testdata/estate/justfile +20 -0
  26. package/internal/commands/testdata/estate/package.json +13 -0
  27. package/internal/commands/testdata/estate/pom.xml +29 -0
  28. package/internal/commands/testdata/estate/pyproject.toml +27 -0
  29. package/internal/commands/testdata/estate/xtask/src/main.rs +27 -0
  30. package/internal/commands/testdata/golden/commands.json +305 -0
  31. package/internal/gohttp/analyze.go +2500 -0
  32. package/internal/gohttp/endpoints.go +1067 -0
  33. package/internal/gohttp/roots.go +320 -0
  34. package/internal/gohttp/typed.go +143 -0
  35. package/internal/goscan/constants.go +85 -0
  36. package/internal/goscan/goscan_test.go +227 -0
  37. package/internal/goscan/names.go +52 -0
  38. package/internal/goscan/parse_test.go +11 -0
  39. package/internal/goscan/source.go +36 -0
  40. package/internal/goscan/tree.go +155 -0
  41. package/internal/goscan/types.go +99 -0
  42. package/internal/wsdl/ids.go +127 -0
  43. package/internal/wsdl/ids_test.go +21 -0
  44. package/internal/wsdl/model.go +62 -0
  45. package/internal/wsdl/parse.go +920 -0
  46. package/internal/wsdl/parse_test.go +133 -0
  47. package/package.json +117 -0
  48. package/plugin/describe.go +107 -0
  49. package/plugin/describe_test.go +114 -0
  50. package/plugin/protocol.go +117 -0
  51. package/plugin/schematest/schematest.go +126 -0
  52. package/plugins/README.md +1160 -0
  53. package/plugins/extract-adr/describe.go +19 -0
  54. package/plugins/extract-adr/describe_test.go +11 -0
  55. package/plugins/extract-adr/extract.go +153 -0
  56. package/plugins/extract-adr/extract_test.go +350 -0
  57. package/plugins/extract-adr/history.go +99 -0
  58. package/plugins/extract-adr/main.go +65 -0
  59. package/plugins/extract-adr/options.schema.json +26 -0
  60. package/plugins/extract-adr/parse.go +642 -0
  61. package/plugins/extract-adr/parse_test.go +405 -0
  62. package/plugins/extract-asyncapi/describe.go +19 -0
  63. package/plugins/extract-asyncapi/describe_test.go +11 -0
  64. package/plugins/extract-asyncapi/extract.go +315 -0
  65. package/plugins/extract-asyncapi/extract_test.go +197 -0
  66. package/plugins/extract-asyncapi/main.go +48 -0
  67. package/plugins/extract-asyncapi/options.schema.json +26 -0
  68. package/plugins/extract-asyncapi/spec.go +151 -0
  69. package/plugins/extract-celery/README.md +169 -0
  70. package/plugins/extract-celery/extract.py +346 -0
  71. package/plugins/extract-celery/extract_test.py +192 -0
  72. package/plugins/extract-celery/main.py +66 -0
  73. package/plugins/extract-celery/options.py +31 -0
  74. package/plugins/extract-celery/options.schema.json +31 -0
  75. package/plugins/extract-celery/producers.py +156 -0
  76. package/plugins/extract-celery/routes_test.py +106 -0
  77. package/plugins/extract-commands/describe.go +19 -0
  78. package/plugins/extract-commands/describe_test.go +11 -0
  79. package/plugins/extract-commands/extract.go +79 -0
  80. package/plugins/extract-commands/extract_test.go +92 -0
  81. package/plugins/extract-commands/main.go +55 -0
  82. package/plugins/extract-commands/options.schema.json +20 -0
  83. package/plugins/extract-csr/avro.go +258 -0
  84. package/plugins/extract-csr/describe.go +19 -0
  85. package/plugins/extract-csr/describe_test.go +11 -0
  86. package/plugins/extract-csr/extract.go +338 -0
  87. package/plugins/extract-csr/extract_test.go +374 -0
  88. package/plugins/extract-csr/jsonschema.go +343 -0
  89. package/plugins/extract-csr/lock.go +25 -0
  90. package/plugins/extract-csr/main.go +84 -0
  91. package/plugins/extract-csr/options.schema.json +43 -0
  92. package/plugins/extract-csr/subject.go +102 -0
  93. package/plugins/extract-django/README.md +386 -0
  94. package/plugins/extract-django/apps.py +100 -0
  95. package/plugins/extract-django/auth.py +302 -0
  96. package/plugins/extract-django/auth_test.py +188 -0
  97. package/plugins/extract-django/clients.py +121 -0
  98. package/plugins/extract-django/contracts.py +458 -0
  99. package/plugins/extract-django/database.py +119 -0
  100. package/plugins/extract-django/domain.py +318 -0
  101. package/plugins/extract-django/events.py +86 -0
  102. package/plugins/extract-django/extract.py +601 -0
  103. package/plugins/extract-django/extract_test.py +397 -0
  104. package/plugins/extract-django/flows.py +1278 -0
  105. package/plugins/extract-django/flows_test.py +338 -0
  106. package/plugins/extract-django/ids.py +42 -0
  107. package/plugins/extract-django/ids_test.py +30 -0
  108. package/plugins/extract-django/lifecycle.py +222 -0
  109. package/plugins/extract-django/lifecycle_test.py +76 -0
  110. package/plugins/extract-django/main.py +66 -0
  111. package/plugins/extract-django/openapi.py +229 -0
  112. package/plugins/extract-django/openapi_test.py +60 -0
  113. package/plugins/extract-django/operations.py +96 -0
  114. package/plugins/extract-django/options.py +61 -0
  115. package/plugins/extract-django/options.schema.json +126 -0
  116. package/plugins/extract-django/routing.py +214 -0
  117. package/plugins/extract-django/routing_test.py +198 -0
  118. package/plugins/extract-django/serializers.py +558 -0
  119. package/plugins/extract-django/store.py +434 -0
  120. package/plugins/extract-django/transport.py +242 -0
  121. package/plugins/extract-flows/describe.go +19 -0
  122. package/plugins/extract-flows/describe_test.go +11 -0
  123. package/plugins/extract-flows/extract.go +91 -0
  124. package/plugins/extract-flows/main.go +45 -0
  125. package/plugins/extract-flows/options.schema.json +16 -0
  126. package/plugins/extract-flows/parse.go +593 -0
  127. package/plugins/extract-flows/parse_test.go +204 -0
  128. package/plugins/extract-glossary/describe.go +19 -0
  129. package/plugins/extract-glossary/describe_test.go +11 -0
  130. package/plugins/extract-glossary/extract.go +115 -0
  131. package/plugins/extract-glossary/extract_test.go +220 -0
  132. package/plugins/extract-glossary/main.go +59 -0
  133. package/plugins/extract-glossary/options.schema.json +21 -0
  134. package/plugins/extract-glossary/parse.go +214 -0
  135. package/plugins/extract-glossary/parse_test.go +203 -0
  136. package/plugins/extract-go/aggregate.go +214 -0
  137. package/plugins/extract-go/client.go +409 -0
  138. package/plugins/extract-go/client_test.go +266 -0
  139. package/plugins/extract-go/describe.go +19 -0
  140. package/plugins/extract-go/describe_test.go +11 -0
  141. package/plugins/extract-go/enum.go +195 -0
  142. package/plugins/extract-go/enum_test.go +82 -0
  143. package/plugins/extract-go/event.go +99 -0
  144. package/plugins/extract-go/extract.go +191 -0
  145. package/plugins/extract-go/extract_test.go +261 -0
  146. package/plugins/extract-go/flow.go +1441 -0
  147. package/plugins/extract-go/flow_test.go +609 -0
  148. package/plugins/extract-go/httpclient.go +174 -0
  149. package/plugins/extract-go/httpclient_test.go +296 -0
  150. package/plugins/extract-go/ids.go +92 -0
  151. package/plugins/extract-go/layout.go +225 -0
  152. package/plugins/extract-go/layout_test.go +125 -0
  153. package/plugins/extract-go/lifecycle.go +324 -0
  154. package/plugins/extract-go/lifecycle_test.go +108 -0
  155. package/plugins/extract-go/main.go +79 -0
  156. package/plugins/extract-go/operation.go +157 -0
  157. package/plugins/extract-go/options.schema.json +66 -0
  158. package/plugins/extract-go/source.go +316 -0
  159. package/plugins/extract-go/transport.go +321 -0
  160. package/plugins/extract-go/transport_test.go +145 -0
  161. package/plugins/extract-go/wiring.go +434 -0
  162. package/plugins/extract-go-nats/describe.go +19 -0
  163. package/plugins/extract-go-nats/describe_test.go +11 -0
  164. package/plugins/extract-go-nats/extract.go +177 -0
  165. package/plugins/extract-go-nats/extract_test.go +389 -0
  166. package/plugins/extract-go-nats/index.go +379 -0
  167. package/plugins/extract-go-nats/main.go +42 -0
  168. package/plugins/extract-go-nats/options.schema.json +21 -0
  169. package/plugins/extract-go-nats/resolve.go +161 -0
  170. package/plugins/extract-go-nats/sites.go +224 -0
  171. package/plugins/extract-graphql/describe.go +19 -0
  172. package/plugins/extract-graphql/describe_test.go +11 -0
  173. package/plugins/extract-graphql/extract.go +433 -0
  174. package/plugins/extract-graphql/extract_test.go +256 -0
  175. package/plugins/extract-graphql/ids.go +49 -0
  176. package/plugins/extract-graphql/lex.go +237 -0
  177. package/plugins/extract-graphql/main.go +51 -0
  178. package/plugins/extract-graphql/options.schema.json +31 -0
  179. package/plugins/extract-graphql/parse.go +621 -0
  180. package/plugins/extract-graphql/parse_test.go +122 -0
  181. package/plugins/extract-http-clients/describe.go +19 -0
  182. package/plugins/extract-http-clients/describe_test.go +11 -0
  183. package/plugins/extract-http-clients/extract.go +705 -0
  184. package/plugins/extract-http-clients/extract_test.go +1263 -0
  185. package/plugins/extract-http-clients/main.go +42 -0
  186. package/plugins/extract-http-clients/options.schema.json +33 -0
  187. package/plugins/extract-java/README.md +190 -0
  188. package/plugins/extract-java/build/org/portolan/extract/Catalog.class +0 -0
  189. package/plugins/extract-java/build/org/portolan/extract/Clients$1.class +0 -0
  190. package/plugins/extract-java/build/org/portolan/extract/Clients$2.class +0 -0
  191. package/plugins/extract-java/build/org/portolan/extract/Clients$Call.class +0 -0
  192. package/plugins/extract-java/build/org/portolan/extract/Clients$Client.class +0 -0
  193. package/plugins/extract-java/build/org/portolan/extract/Clients$Route.class +0 -0
  194. package/plugins/extract-java/build/org/portolan/extract/Clients.class +0 -0
  195. package/plugins/extract-java/build/org/portolan/extract/Domain$Aggregate.class +0 -0
  196. package/plugins/extract-java/build/org/portolan/extract/Domain.class +0 -0
  197. package/plugins/extract-java/build/org/portolan/extract/Events$Found.class +0 -0
  198. package/plugins/extract-java/build/org/portolan/extract/Events.class +0 -0
  199. package/plugins/extract-java/build/org/portolan/extract/Extract.class +0 -0
  200. package/plugins/extract-java/build/org/portolan/extract/Flows$1.class +0 -0
  201. package/plugins/extract-java/build/org/portolan/extract/Flows$Binding.class +0 -0
  202. package/plugins/extract-java/build/org/portolan/extract/Flows$Draft.class +0 -0
  203. package/plugins/extract-java/build/org/portolan/extract/Flows$Frame.class +0 -0
  204. package/plugins/extract-java/build/org/portolan/extract/Flows$Options.class +0 -0
  205. package/plugins/extract-java/build/org/portolan/extract/Flows.class +0 -0
  206. package/plugins/extract-java/build/org/portolan/extract/Ids.class +0 -0
  207. package/plugins/extract-java/build/org/portolan/extract/Json$Reader.class +0 -0
  208. package/plugins/extract-java/build/org/portolan/extract/Json.class +0 -0
  209. package/plugins/extract-java/build/org/portolan/extract/Lifecycle$1.class +0 -0
  210. package/plugins/extract-java/build/org/portolan/extract/Lifecycle$Move.class +0 -0
  211. package/plugins/extract-java/build/org/portolan/extract/Lifecycle.class +0 -0
  212. package/plugins/extract-java/build/org/portolan/extract/Main.class +0 -0
  213. package/plugins/extract-java/build/org/portolan/extract/OpenApi$Operation.class +0 -0
  214. package/plugins/extract-java/build/org/portolan/extract/OpenApi$Spec.class +0 -0
  215. package/plugins/extract-java/build/org/portolan/extract/OpenApi.class +0 -0
  216. package/plugins/extract-java/build/org/portolan/extract/Operations$1.class +0 -0
  217. package/plugins/extract-java/build/org/portolan/extract/Operations$UseCase.class +0 -0
  218. package/plugins/extract-java/build/org/portolan/extract/Operations.class +0 -0
  219. package/plugins/extract-java/build/org/portolan/extract/Proto$Rpc.class +0 -0
  220. package/plugins/extract-java/build/org/portolan/extract/Proto$Service.class +0 -0
  221. package/plugins/extract-java/build/org/portolan/extract/Proto.class +0 -0
  222. package/plugins/extract-java/build/org/portolan/extract/Protocol$Builder.class +0 -0
  223. package/plugins/extract-java/build/org/portolan/extract/Protocol$Input.class +0 -0
  224. package/plugins/extract-java/build/org/portolan/extract/Protocol$Options.class +0 -0
  225. package/plugins/extract-java/build/org/portolan/extract/Protocol.class +0 -0
  226. package/plugins/extract-java/build/org/portolan/extract/Source$Project.class +0 -0
  227. package/plugins/extract-java/build/org/portolan/extract/Source$Unit.class +0 -0
  228. package/plugins/extract-java/build/org/portolan/extract/Source.class +0 -0
  229. package/plugins/extract-java/build/org/portolan/extract/Transport$Endpoint.class +0 -0
  230. package/plugins/extract-java/build/org/portolan/extract/Transport.class +0 -0
  231. package/plugins/extract-java/build/org/portolan/extract/Yaml$Line.class +0 -0
  232. package/plugins/extract-java/build/org/portolan/extract/Yaml$Reader.class +0 -0
  233. package/plugins/extract-java/build/org/portolan/extract/Yaml.class +0 -0
  234. package/plugins/extract-java/options.schema.json +70 -0
  235. package/plugins/extract-openapi/describe.go +19 -0
  236. package/plugins/extract-openapi/describe_test.go +11 -0
  237. package/plugins/extract-openapi/discover.go +243 -0
  238. package/plugins/extract-openapi/extract.go +526 -0
  239. package/plugins/extract-openapi/extract_test.go +545 -0
  240. package/plugins/extract-openapi/main.go +75 -0
  241. package/plugins/extract-openapi/options.schema.json +62 -0
  242. package/plugins/extract-openapi/spec.go +350 -0
  243. package/plugins/extract-project/README.md +22 -0
  244. package/plugins/extract-project/describe.go +19 -0
  245. package/plugins/extract-project/describe_test.go +11 -0
  246. package/plugins/extract-project/extract.go +221 -0
  247. package/plugins/extract-project/extract_test.go +109 -0
  248. package/plugins/extract-project/main.go +41 -0
  249. package/plugins/extract-project/options.schema.json +48 -0
  250. package/plugins/extract-proto/ast.go +125 -0
  251. package/plugins/extract-proto/consumes.go +77 -0
  252. package/plugins/extract-proto/describe.go +19 -0
  253. package/plugins/extract-proto/extract.go +293 -0
  254. package/plugins/extract-proto/extract_test.go +459 -0
  255. package/plugins/extract-proto/ids.go +89 -0
  256. package/plugins/extract-proto/ids_test.go +57 -0
  257. package/plugins/extract-proto/lex.go +285 -0
  258. package/plugins/extract-proto/main.go +100 -0
  259. package/plugins/extract-proto/module.go +120 -0
  260. package/plugins/extract-proto/options.schema.json +51 -0
  261. package/plugins/extract-proto/parse.go +720 -0
  262. package/plugins/extract-proto/parse_test.go +307 -0
  263. package/plugins/extract-proto/provides.go +236 -0
  264. package/plugins/extract-proto/resolve.go +222 -0
  265. package/plugins/extract-proto/resolve_test.go +119 -0
  266. package/plugins/extract-python-kafka/README.md +36 -0
  267. package/plugins/extract-python-kafka/extract.py +211 -0
  268. package/plugins/extract-python-kafka/extract_test.py +68 -0
  269. package/plugins/extract-python-kafka/main.py +59 -0
  270. package/plugins/extract-python-kafka/options.py +29 -0
  271. package/plugins/extract-python-kafka/options.schema.json +31 -0
  272. package/plugins/extract-redis/README.md +11 -0
  273. package/plugins/extract-redis/describe.go +19 -0
  274. package/plugins/extract-redis/describe_test.go +11 -0
  275. package/plugins/extract-redis/extract.go +183 -0
  276. package/plugins/extract-redis/extract_test.go +168 -0
  277. package/plugins/extract-redis/keyspaces.go +469 -0
  278. package/plugins/extract-redis/main.go +44 -0
  279. package/plugins/extract-redis/options.schema.json +30 -0
  280. package/plugins/extract-river/describe.go +19 -0
  281. package/plugins/extract-river/describe_test.go +11 -0
  282. package/plugins/extract-river/extract.go +507 -0
  283. package/plugins/extract-river/extract_test.go +132 -0
  284. package/plugins/extract-river/main.go +42 -0
  285. package/plugins/extract-river/options.schema.json +21 -0
  286. package/plugins/extract-rust/Cargo.lock +133 -0
  287. package/plugins/extract-rust/Cargo.toml +23 -0
  288. package/plugins/extract-rust/README.md +204 -0
  289. package/plugins/extract-rust/options.schema.json +65 -0
  290. package/plugins/extract-rust/rustfmt.toml +1 -0
  291. package/plugins/extract-sql/ddl.go +893 -0
  292. package/plugins/extract-sql/ddl_test.go +401 -0
  293. package/plugins/extract-sql/describe.go +19 -0
  294. package/plugins/extract-sql/describe_test.go +11 -0
  295. package/plugins/extract-sql/layout.go +221 -0
  296. package/plugins/extract-sql/layout_test.go +55 -0
  297. package/plugins/extract-sql/lineage.go +107 -0
  298. package/plugins/extract-sql/main.go +141 -0
  299. package/plugins/extract-sql/maps.go +564 -0
  300. package/plugins/extract-sql/maps_java.go +117 -0
  301. package/plugins/extract-sql/maps_rust.go +333 -0
  302. package/plugins/extract-sql/maps_rust_test.go +70 -0
  303. package/plugins/extract-sql/maps_test.go +204 -0
  304. package/plugins/extract-sql/maps_ts.go +398 -0
  305. package/plugins/extract-sql/maps_ts_test.go +136 -0
  306. package/plugins/extract-sql/options.schema.json +53 -0
  307. package/plugins/extract-sql/projection.go +70 -0
  308. package/plugins/extract-sql/projection_test.go +50 -0
  309. package/plugins/extract-sql/store.go +420 -0
  310. package/plugins/extract-sql/store_test.go +233 -0
  311. package/plugins/extract-sql/view.go +295 -0
  312. package/plugins/extract-ts/README.md +306 -0
  313. package/plugins/extract-ts/ast.test.ts +178 -0
  314. package/plugins/extract-ts/ast.ts +648 -0
  315. package/plugins/extract-ts/clients.ts +185 -0
  316. package/plugins/extract-ts/domain.ts +164 -0
  317. package/plugins/extract-ts/extract.test.ts +208 -0
  318. package/plugins/extract-ts/extract.ts +200 -0
  319. package/plugins/extract-ts/flows.ts +783 -0
  320. package/plugins/extract-ts/graphql.test.ts +73 -0
  321. package/plugins/extract-ts/graphql.ts +181 -0
  322. package/plugins/extract-ts/ids.test.ts +21 -0
  323. package/plugins/extract-ts/ids.ts +52 -0
  324. package/plugins/extract-ts/lifecycle.ts +138 -0
  325. package/plugins/extract-ts/main.ts +48 -0
  326. package/plugins/extract-ts/openapi.test.ts +64 -0
  327. package/plugins/extract-ts/openapi.ts +99 -0
  328. package/plugins/extract-ts/operations.ts +93 -0
  329. package/plugins/extract-ts/options.schema.json +96 -0
  330. package/plugins/extract-ts/source.test.ts +174 -0
  331. package/plugins/extract-ts/source.ts +465 -0
  332. package/plugins/extract-ts/transport.ts +147 -0
  333. package/plugins/extract-ts/tsconfig.json +13 -0
  334. package/plugins/extract-ts/wiring.ts +190 -0
  335. package/plugins/extract-watermill/describe.go +19 -0
  336. package/plugins/extract-watermill/describe_test.go +11 -0
  337. package/plugins/extract-watermill/extract.go +1228 -0
  338. package/plugins/extract-watermill/extract_test.go +234 -0
  339. package/plugins/extract-watermill/main.go +41 -0
  340. package/plugins/extract-watermill/options.schema.json +21 -0
  341. package/plugins/extract-wsdl/describe.go +19 -0
  342. package/plugins/extract-wsdl/describe_test.go +11 -0
  343. package/plugins/extract-wsdl/extract.go +148 -0
  344. package/plugins/extract-wsdl/extract_test.go +45 -0
  345. package/plugins/extract-wsdl/main.go +53 -0
  346. package/plugins/extract-wsdl/options.schema.json +59 -0
  347. package/plugins/fetch-bsr/auth.go +114 -0
  348. package/plugins/fetch-bsr/bsr.go +240 -0
  349. package/plugins/fetch-bsr/cache.go +66 -0
  350. package/plugins/fetch-bsr/describe.go +19 -0
  351. package/plugins/fetch-bsr/fetch.go +192 -0
  352. package/plugins/fetch-bsr/fetch_test.go +465 -0
  353. package/plugins/fetch-bsr/lock.go +71 -0
  354. package/plugins/fetch-bsr/main.go +117 -0
  355. package/plugins/fetch-bsr/options.schema.json +42 -0
  356. package/plugins/fetch-csr/auth.go +95 -0
  357. package/plugins/fetch-csr/cache.go +69 -0
  358. package/plugins/fetch-csr/describe.go +19 -0
  359. package/plugins/fetch-csr/describe_test.go +11 -0
  360. package/plugins/fetch-csr/fetch.go +254 -0
  361. package/plugins/fetch-csr/fetch_test.go +494 -0
  362. package/plugins/fetch-csr/lock.go +95 -0
  363. package/plugins/fetch-csr/main.go +122 -0
  364. package/plugins/fetch-csr/options.schema.json +42 -0
  365. package/plugins/fetch-csr/registry.go +209 -0
  366. package/plugins/fetch-git/cache.go +65 -0
  367. package/plugins/fetch-git/describe.go +19 -0
  368. package/plugins/fetch-git/describe_test.go +11 -0
  369. package/plugins/fetch-git/fetch.go +181 -0
  370. package/plugins/fetch-git/fetch_test.go +332 -0
  371. package/plugins/fetch-git/git.go +169 -0
  372. package/plugins/fetch-git/lock.go +72 -0
  373. package/plugins/fetch-git/main.go +127 -0
  374. package/plugins/fetch-git/offline.go +39 -0
  375. package/plugins/fetch-git/options.schema.json +42 -0
  376. package/plugins/fetch-git/pin.go +90 -0
  377. package/plugins/fetch-git/pin_test.go +74 -0
  378. package/plugins/gen-backstage/describe.go +17 -0
  379. package/plugins/gen-backstage/main.go +22 -0
  380. package/plugins/gen-backstage/options.schema.json +30 -0
  381. package/plugins/gen-backstage/plugin.go +473 -0
  382. package/plugins/gen-backstage/plugin_test.go +145 -0
  383. package/plugins/gen-backstage.wasm +0 -0
  384. package/plugins/gen-markdown/adr.go +162 -0
  385. package/plugins/gen-markdown/aggregate.go +362 -0
  386. package/plugins/gen-markdown/canonical.go +93 -0
  387. package/plugins/gen-markdown/context.go +90 -0
  388. package/plugins/gen-markdown/coverage_test.go +89 -0
  389. package/plugins/gen-markdown/describe.go +19 -0
  390. package/plugins/gen-markdown/describe_test.go +11 -0
  391. package/plugins/gen-markdown/external.go +71 -0
  392. package/plugins/gen-markdown/flow.go +277 -0
  393. package/plugins/gen-markdown/glossary.go +76 -0
  394. package/plugins/gen-markdown/glossary_test.go +148 -0
  395. package/plugins/gen-markdown/llms.go +302 -0
  396. package/plugins/gen-markdown/main.go +22 -0
  397. package/plugins/gen-markdown/markdown.go +254 -0
  398. package/plugins/gen-markdown/markdown_test.go +100 -0
  399. package/plugins/gen-markdown/module.go +107 -0
  400. package/plugins/gen-markdown/options.schema.json +16 -0
  401. package/plugins/gen-markdown/plugin.go +39 -0
  402. package/plugins/gen-markdown/quality_test.go +176 -0
  403. package/plugins/gen-markdown/redis_test.go +38 -0
  404. package/plugins/gen-markdown/render.go +344 -0
  405. package/plugins/gen-markdown/render_test.go +186 -0
  406. package/plugins/gen-markdown/service.go +456 -0
  407. package/plugins/gen-markdown/source.go +177 -0
  408. package/plugins/gen-markdown/store.go +201 -0
  409. package/plugins/gen-markdown.wasm +0 -0
  410. package/plugins/gen-mermaid/describe.go +17 -0
  411. package/plugins/gen-mermaid/main.go +22 -0
  412. package/plugins/gen-mermaid/options.schema.json +10 -0
  413. package/plugins/gen-mermaid/plugin.go +104 -0
  414. package/plugins/gen-mermaid/plugin_test.go +33 -0
  415. package/plugins/gen-mermaid.wasm +0 -0
  416. package/plugins/openapi/ids.go +261 -0
  417. package/plugins/openapi/ids_test.go +98 -0
  418. package/plugins/pyplugin/catalog.py +209 -0
  419. package/plugins/pyplugin/celery_conf.py +436 -0
  420. package/plugins/pyplugin/celery_tasks.py +76 -0
  421. package/plugins/pyplugin/kafka.py +570 -0
  422. package/plugins/pyplugin/names.py +38 -0
  423. package/plugins/pyplugin/protocol.py +79 -0
  424. package/plugins/pyplugin/source.py +285 -0
  425. package/plugins/verify-codeowners/describe.go +19 -0
  426. package/plugins/verify-codeowners/describe_test.go +11 -0
  427. package/plugins/verify-codeowners/main.go +75 -0
  428. package/plugins/verify-codeowners/match.go +85 -0
  429. package/plugins/verify-codeowners/match_test.go +47 -0
  430. package/plugins/verify-codeowners/options.schema.json +15 -0
  431. package/plugins/verify-codeowners/owners.go +164 -0
  432. package/plugins/verify-codeowners/owners_test.go +225 -0
  433. package/plugins/verify-codeowners/parse.go +90 -0
  434. package/plugins/verify-codeowners/parse_test.go +62 -0
  435. package/plugins/verify-otel/describe.go +19 -0
  436. package/plugins/verify-otel/describe_test.go +11 -0
  437. package/plugins/verify-otel/main.go +53 -0
  438. package/plugins/verify-otel/match.go +336 -0
  439. package/plugins/verify-otel/options.schema.json +30 -0
  440. package/plugins/verify-otel/otlp.go +200 -0
  441. package/plugins/verify-otel/verify.go +734 -0
  442. package/plugins/verify-otel/verify_test.go +460 -0
  443. package/portolan.json +1073 -0
  444. package/public/favicon.svg +12 -0
  445. package/schema/portolan.schema.json +2558 -0
  446. package/scripts/README.md +53 -0
  447. package/scripts/build-report.mjs +60 -0
  448. package/scripts/build-report.test.mjs +44 -0
  449. package/scripts/builtin-plugins.mjs +49 -0
  450. package/scripts/catalog-sources.mjs +67 -0
  451. package/scripts/diff.mjs +315 -0
  452. package/scripts/diff.test.mjs +151 -0
  453. package/scripts/forge-comment.mjs +144 -0
  454. package/scripts/forge-comment.test.mjs +151 -0
  455. package/scripts/forge-release.mjs +138 -0
  456. package/scripts/forge-release.test.mjs +106 -0
  457. package/scripts/forge.mjs +61 -0
  458. package/scripts/gen-likec4.mjs +854 -0
  459. package/scripts/gen-likec4.test.mjs +258 -0
  460. package/scripts/gen.mjs +603 -0
  461. package/scripts/generated-docs.test.mjs +66 -0
  462. package/scripts/local-api.mjs +1192 -0
  463. package/scripts/local-api.test.mjs +387 -0
  464. package/scripts/manifest.mjs +214 -0
  465. package/scripts/manifest.test.mjs +148 -0
  466. package/scripts/output-path.mjs +79 -0
  467. package/scripts/output-path.test.mjs +45 -0
  468. package/scripts/package-smoke.mjs +37 -0
  469. package/scripts/plugin-host.mjs +419 -0
  470. package/scripts/plugin-host.test.mjs +109 -0
  471. package/scripts/plugin-wasm-worker.mjs +34 -0
  472. package/scripts/run-builtin.mjs +81 -0
  473. package/scripts/schema.mjs +357 -0
  474. package/scripts/site-docs.mjs +295 -0
  475. package/scripts/site-docs.test.mjs +181 -0
  476. package/scripts/vendor-lock.mjs +58 -0
  477. package/scripts/vendor-lock.test.mjs +69 -0
  478. package/src/README.md +38 -0
  479. package/src/app/App.tsx +448 -0
  480. package/src/app/BranchPicker.test.ts +46 -0
  481. package/src/app/BranchPicker.tsx +153 -0
  482. package/src/app/Breadcrumbs.test.ts +38 -0
  483. package/src/app/Breadcrumbs.tsx +223 -0
  484. package/src/app/BuildStamp.tsx +146 -0
  485. package/src/app/CommandPalette.tsx +292 -0
  486. package/src/app/HashScroll.tsx +45 -0
  487. package/src/app/Sidebar.tsx +1742 -0
  488. package/src/app/TopBar.tsx +393 -0
  489. package/src/app/density.tsx +64 -0
  490. package/src/app/forge-access.test.ts +27 -0
  491. package/src/app/forge-access.tsx +80 -0
  492. package/src/app/panels.tsx +141 -0
  493. package/src/app/pins.tsx +242 -0
  494. package/src/app/query-client.ts +24 -0
  495. package/src/app/responsive.ts +55 -0
  496. package/src/app/shortcuts.test.ts +39 -0
  497. package/src/app/shortcuts.tsx +282 -0
  498. package/src/app/theme.tsx +51 -0
  499. package/src/app/title.test.ts +14 -0
  500. package/src/app/title.ts +30 -0
  501. package/src/app/toast.tsx +59 -0
  502. package/src/app/tree-keys.test.ts +80 -0
  503. package/src/app/tree-keys.ts +87 -0
  504. package/src/app/ui-store.ts +52 -0
  505. package/src/catalog-error.test.ts +105 -0
  506. package/src/catalog-profile.test.ts +59 -0
  507. package/src/catalog-profile.ts +101 -0
  508. package/src/catalog-stores.test.ts +431 -0
  509. package/src/catalog.test.ts +1215 -0
  510. package/src/catalog.ts +3125 -0
  511. package/src/chat/Cards.tsx +408 -0
  512. package/src/chat/ChatPanel.tsx +79 -0
  513. package/src/chat/ChatSettings.tsx +219 -0
  514. package/src/chat/Composer.tsx +99 -0
  515. package/src/chat/Conversation.tsx +281 -0
  516. package/src/chat/Header.tsx +46 -0
  517. package/src/chat/Message.tsx +49 -0
  518. package/src/chat/Notice.tsx +195 -0
  519. package/src/chat/Starter.tsx +67 -0
  520. package/src/chat/Steps.tsx +45 -0
  521. package/src/chat/Waiting.tsx +87 -0
  522. package/src/chat/flags.test.ts +59 -0
  523. package/src/chat/flags.ts +155 -0
  524. package/src/chat/linkify.test.ts +30 -0
  525. package/src/chat/linkify.ts +45 -0
  526. package/src/chat/prefs.ts +56 -0
  527. package/src/chat/prompt.test.ts +43 -0
  528. package/src/chat/prompt.ts +157 -0
  529. package/src/chat/store.ts +35 -0
  530. package/src/chat/tools.ts +58 -0
  531. package/src/chat/transport.ts +70 -0
  532. package/src/components/AdrRow.tsx +29 -0
  533. package/src/components/AnchorLink.tsx +67 -0
  534. package/src/components/ApiReference.tsx +134 -0
  535. package/src/components/AsyncApiReference.tsx +120 -0
  536. package/src/components/CatalogStamp.tsx +155 -0
  537. package/src/components/ChannelRows.tsx +132 -0
  538. package/src/components/CommandRows.tsx +139 -0
  539. package/src/components/DiagramSkeleton.tsx +71 -0
  540. package/src/components/EditorLink.tsx +85 -0
  541. package/src/components/FieldTree.tsx +305 -0
  542. package/src/components/FlowTrigger.tsx +34 -0
  543. package/src/components/Ident.tsx +89 -0
  544. package/src/components/Integrations.tsx +243 -0
  545. package/src/components/LifecycleDiagram.tsx +110 -0
  546. package/src/components/MachineDocs.tsx +42 -0
  547. package/src/components/Markdown.tsx +173 -0
  548. package/src/components/MarkdownTable.tsx +206 -0
  549. package/src/components/Mermaid.tsx +211 -0
  550. package/src/components/MethodRows.tsx +309 -0
  551. package/src/components/Overlay.tsx +125 -0
  552. package/src/components/PageHeader.tsx +189 -0
  553. package/src/components/RowActions.tsx +97 -0
  554. package/src/components/SchemaDocument.tsx +89 -0
  555. package/src/components/Select.tsx +126 -0
  556. package/src/components/ShapeRows.tsx +118 -0
  557. package/src/components/SourceDoc.tsx +82 -0
  558. package/src/components/SourcePreview.tsx +441 -0
  559. package/src/components/TabRow.tsx +182 -0
  560. package/src/components/TechIcon.tsx +34 -0
  561. package/src/components/Toc.tsx +132 -0
  562. package/src/components/WhatLinksHere.tsx +273 -0
  563. package/src/components/WsdlReference.tsx +84 -0
  564. package/src/components/ddd-icons.tsx +107 -0
  565. package/src/components/kind.tsx +157 -0
  566. package/src/components/logo.tsx +61 -0
  567. package/src/components/primitives.tsx +264 -0
  568. package/src/data.test.ts +18 -0
  569. package/src/data.ts +202 -0
  570. package/src/enrich.test.ts +1150 -0
  571. package/src/enrich.ts +672 -0
  572. package/src/er/ErCanvas.tsx +533 -0
  573. package/src/er/RedisSchema.tsx +74 -0
  574. package/src/er/RelationCard.tsx +267 -0
  575. package/src/er/StoreHeader.tsx +155 -0
  576. package/src/er/TableNode.tsx +84 -0
  577. package/src/er/ViewNode.tsx +83 -0
  578. package/src/er/layout.test.ts +115 -0
  579. package/src/er/layout.ts +77 -0
  580. package/src/er/lineage.test.ts +140 -0
  581. package/src/er/lineage.ts +107 -0
  582. package/src/er/markers.tsx +79 -0
  583. package/src/er/spec.test.ts +344 -0
  584. package/src/er/spec.ts +426 -0
  585. package/src/flow/ChainList.tsx +162 -0
  586. package/src/flow/FlowTable.tsx +123 -0
  587. package/src/flow/FlowToolbar.tsx +372 -0
  588. package/src/flow/StepDetail.tsx +380 -0
  589. package/src/flow/StepRail.tsx +428 -0
  590. package/src/flow/answers.test.ts +50 -0
  591. package/src/flow/answers.ts +58 -0
  592. package/src/flow/chain.test.ts +383 -0
  593. package/src/flow/chain.ts +353 -0
  594. package/src/flow/chapters.test.ts +185 -0
  595. package/src/flow/chapters.ts +225 -0
  596. package/src/flow/continues.test.ts +102 -0
  597. package/src/flow/continues.ts +65 -0
  598. package/src/flow/cross-context.test.ts +97 -0
  599. package/src/flow/cross-context.ts +35 -0
  600. package/src/flow/mermaid.test.ts +22 -0
  601. package/src/flow/mermaid.ts +141 -0
  602. package/src/flow/outline.test.ts +212 -0
  603. package/src/flow/outline.ts +170 -0
  604. package/src/flow/paths.test.ts +129 -0
  605. package/src/flow/paths.ts +147 -0
  606. package/src/flow/prefs.test.ts +80 -0
  607. package/src/flow/prefs.ts +104 -0
  608. package/src/graph/DependencyGraph.tsx +283 -0
  609. package/src/graph/DependencyNodes.tsx +162 -0
  610. package/src/graph/EdgeLabel.tsx +61 -0
  611. package/src/graph/FocusedEventGraph.tsx +211 -0
  612. package/src/graph/GraphToolbar.tsx +187 -0
  613. package/src/graph/RoutedEdge.tsx +80 -0
  614. package/src/graph/dependency-layout.test.ts +360 -0
  615. package/src/graph/dependency-layout.ts +444 -0
  616. package/src/graph/elk.test.ts +70 -0
  617. package/src/graph/elk.ts +287 -0
  618. package/src/graph/nodes.tsx +97 -0
  619. package/src/graph/theme.ts +81 -0
  620. package/src/graph/useElkFlow.ts +125 -0
  621. package/src/index.css +1547 -0
  622. package/src/language/InLanguage.tsx +40 -0
  623. package/src/language/TermCard.tsx +132 -0
  624. package/src/language/cards.test.ts +114 -0
  625. package/src/language/cards.ts +111 -0
  626. package/src/lib/admonition.test.ts +49 -0
  627. package/src/lib/admonition.ts +48 -0
  628. package/src/lib/adr.test.ts +131 -0
  629. package/src/lib/adr.ts +68 -0
  630. package/src/lib/api.test.ts +122 -0
  631. package/src/lib/api.ts +135 -0
  632. package/src/lib/backlinks.test.ts +330 -0
  633. package/src/lib/backlinks.ts +567 -0
  634. package/src/lib/branch-compare.ts +16 -0
  635. package/src/lib/build-info.test.ts +76 -0
  636. package/src/lib/build-info.ts +80 -0
  637. package/src/lib/catalog-diff.test.ts +275 -0
  638. package/src/lib/catalog-diff.ts +655 -0
  639. package/src/lib/clipboard.ts +43 -0
  640. package/src/lib/comparison-memory.test.ts +40 -0
  641. package/src/lib/comparison-memory.ts +39 -0
  642. package/src/lib/context-color.ts +27 -0
  643. package/src/lib/context-map.test.ts +148 -0
  644. package/src/lib/context-map.ts +399 -0
  645. package/src/lib/data-model.test.ts +245 -0
  646. package/src/lib/data-model.ts +399 -0
  647. package/src/lib/data-problems.test.ts +178 -0
  648. package/src/lib/data-problems.ts +314 -0
  649. package/src/lib/derive.test.ts +251 -0
  650. package/src/lib/derive.ts +515 -0
  651. package/src/lib/editor-link.test.ts +125 -0
  652. package/src/lib/editor-link.ts +96 -0
  653. package/src/lib/editor-prefs.ts +27 -0
  654. package/src/lib/event-graph.test.ts +237 -0
  655. package/src/lib/event-graph.ts +327 -0
  656. package/src/lib/export-canvas.ts +37 -0
  657. package/src/lib/export-file.test.ts +30 -0
  658. package/src/lib/export-file.ts +20 -0
  659. package/src/lib/flow-tree.test.ts +177 -0
  660. package/src/lib/flow-tree.ts +160 -0
  661. package/src/lib/forge-refs.test.ts +50 -0
  662. package/src/lib/forge-refs.ts +60 -0
  663. package/src/lib/format.test.ts +35 -0
  664. package/src/lib/format.ts +64 -0
  665. package/src/lib/github-catalog.test.ts +301 -0
  666. package/src/lib/github-catalog.ts +610 -0
  667. package/src/lib/integrations.test.ts +119 -0
  668. package/src/lib/integrations.ts +133 -0
  669. package/src/lib/kinds.test.ts +91 -0
  670. package/src/lib/kinds.ts +204 -0
  671. package/src/lib/lifecycle.test.ts +93 -0
  672. package/src/lib/lifecycle.ts +165 -0
  673. package/src/lib/local-api.ts +194 -0
  674. package/src/lib/motion-features.ts +11 -0
  675. package/src/lib/motion.test.ts +120 -0
  676. package/src/lib/motion.tsx +269 -0
  677. package/src/lib/palette-recent.test.ts +80 -0
  678. package/src/lib/palette-recent.ts +90 -0
  679. package/src/lib/palette.test.ts +365 -0
  680. package/src/lib/palette.ts +593 -0
  681. package/src/lib/pins.test.ts +139 -0
  682. package/src/lib/pins.ts +130 -0
  683. package/src/lib/problems.test.ts +112 -0
  684. package/src/lib/project-preview.ts +5 -0
  685. package/src/lib/proto-problems.test.ts +121 -0
  686. package/src/lib/proto-problems.ts +65 -0
  687. package/src/lib/queries.test.ts +113 -0
  688. package/src/lib/queries.ts +131 -0
  689. package/src/lib/registry.test.ts +219 -0
  690. package/src/lib/registry.ts +208 -0
  691. package/src/lib/scenarios.ts +547 -0
  692. package/src/lib/setup-info.test.ts +199 -0
  693. package/src/lib/setup-info.ts +415 -0
  694. package/src/lib/shape.test.ts +289 -0
  695. package/src/lib/shape.ts +454 -0
  696. package/src/lib/sidebar-prefs.test.ts +32 -0
  697. package/src/lib/sidebar-prefs.ts +75 -0
  698. package/src/lib/source-code.test.ts +159 -0
  699. package/src/lib/source-code.ts +286 -0
  700. package/src/lib/source-doc.test.ts +131 -0
  701. package/src/lib/source-doc.ts +103 -0
  702. package/src/lib/source-highlight.test.ts +29 -0
  703. package/src/lib/source-highlight.ts +24 -0
  704. package/src/lib/source-link.test.ts +153 -0
  705. package/src/lib/source-link.ts +201 -0
  706. package/src/lib/spec-files.test.ts +24 -0
  707. package/src/lib/spec-files.ts +23 -0
  708. package/src/lib/table-directive.test.ts +139 -0
  709. package/src/lib/table-directive.ts +106 -0
  710. package/src/lib/tech.test.ts +120 -0
  711. package/src/lib/tech.ts +421 -0
  712. package/src/lib/terms.test.ts +67 -0
  713. package/src/lib/terms.ts +179 -0
  714. package/src/lib/wire-problems.test.ts +263 -0
  715. package/src/lib/wire-problems.ts +281 -0
  716. package/src/likec4/C4View.tsx +55 -0
  717. package/src/likec4/CanvasBridge.tsx +187 -0
  718. package/src/likec4/FlowView.tsx +235 -0
  719. package/src/likec4/InteractiveView.tsx +144 -0
  720. package/src/likec4/canvas-viewport.test.ts +149 -0
  721. package/src/likec4/canvas-viewport.ts +135 -0
  722. package/src/likec4/flow-edges.test.ts +51 -0
  723. package/src/likec4/flow-edges.ts +89 -0
  724. package/src/likec4/frame-theme.test.ts +76 -0
  725. package/src/likec4/frame-theme.ts +101 -0
  726. package/src/likec4/highlight-css.test.ts +49 -0
  727. package/src/likec4/highlight-css.ts +52 -0
  728. package/src/likec4/ids.test.ts +151 -0
  729. package/src/likec4/ids.ts +115 -0
  730. package/src/likec4/levels.tsx +70 -0
  731. package/src/likec4/mapping.test.ts +66 -0
  732. package/src/likec4/mapping.ts +54 -0
  733. package/src/likec4/reserved.json +141 -0
  734. package/src/likec4/view-index.ts +78 -0
  735. package/src/main.tsx +13 -0
  736. package/src/map/ContextMapGraph.tsx +274 -0
  737. package/src/map/ContextNode.tsx +70 -0
  738. package/src/map/FloatingEdge.tsx +86 -0
  739. package/src/merge.test.ts +1038 -0
  740. package/src/merge.ts +1004 -0
  741. package/src/pages/AdrDetail.tsx +242 -0
  742. package/src/pages/AdrIndex.tsx +114 -0
  743. package/src/pages/AggregatePage.tsx +727 -0
  744. package/src/pages/BlockPage.tsx +478 -0
  745. package/src/pages/CatalogFailure.tsx +111 -0
  746. package/src/pages/Changes.tsx +313 -0
  747. package/src/pages/ContextMap.tsx +534 -0
  748. package/src/pages/ContextPage.tsx +346 -0
  749. package/src/pages/EnumPage.tsx +282 -0
  750. package/src/pages/EventPage.tsx +686 -0
  751. package/src/pages/ExternalPage.tsx +204 -0
  752. package/src/pages/FlowDetail.tsx +663 -0
  753. package/src/pages/FlowIndex.tsx +303 -0
  754. package/src/pages/GraphPage.tsx +217 -0
  755. package/src/pages/Language.tsx +257 -0
  756. package/src/pages/ModulePage.tsx +366 -0
  757. package/src/pages/NotFound.tsx +54 -0
  758. package/src/pages/Overview.tsx +344 -0
  759. package/src/pages/Problems.tsx +360 -0
  760. package/src/pages/RegistryIndex.tsx +118 -0
  761. package/src/pages/ServicePage.tsx +746 -0
  762. package/src/pages/Settings.tsx +975 -0
  763. package/src/pages/StorePage.tsx +114 -0
  764. package/src/peek/PeekCard.test.ts +35 -0
  765. package/src/peek/PeekCard.tsx +118 -0
  766. package/src/peek/PeekLayer.tsx +147 -0
  767. package/src/peek/model.test.ts +156 -0
  768. package/src/peek/model.ts +253 -0
  769. package/src/routes.test.ts +222 -0
  770. package/src/routes.ts +449 -0
  771. package/src/selection/DetailPanel.tsx +1407 -0
  772. package/src/selection/hash.test.ts +73 -0
  773. package/src/selection/hash.ts +58 -0
  774. package/src/selection/model.test.ts +199 -0
  775. package/src/selection/model.ts +473 -0
  776. package/src/selection/pages.test.ts +115 -0
  777. package/src/selection/pages.ts +268 -0
  778. package/src/selection/store.test.ts +62 -0
  779. package/src/selection/store.ts +46 -0
  780. package/src/selection/sync.tsx +99 -0
  781. package/src/table/ColumnsMenu.tsx +72 -0
  782. package/src/table/DataTable.tsx +780 -0
  783. package/src/table/Facets.tsx +54 -0
  784. package/src/table/GroupMenu.tsx +69 -0
  785. package/src/table/Toolbar.tsx +190 -0
  786. package/src/table/cells.tsx +117 -0
  787. package/src/table/compare.test.ts +117 -0
  788. package/src/table/compare.ts +196 -0
  789. package/src/table/export.test.ts +88 -0
  790. package/src/table/export.ts +66 -0
  791. package/src/table/facet-groups.test.ts +74 -0
  792. package/src/table/facet-groups.ts +56 -0
  793. package/src/table/grouping.test.ts +84 -0
  794. package/src/table/grouping.ts +82 -0
  795. package/src/table/infer.test.ts +57 -0
  796. package/src/table/infer.ts +50 -0
  797. package/src/table/persist.test.ts +76 -0
  798. package/src/table/persist.ts +112 -0
  799. package/src/table/sort-url.test.ts +75 -0
  800. package/src/table/sort-url.ts +52 -0
  801. package/src/table/types.ts +80 -0
  802. package/src/table/useTableState.ts +220 -0
  803. package/src/test-catalog.ts +7 -0
  804. package/src/testing/estate/data__catalog.json +2847 -0
  805. package/src/testing/estate/data__flows.json +1724 -0
  806. package/src/testing/estate/examples__auth__portolan__api.json +212 -0
  807. package/src/testing/estate/examples__auth__portolan__domain.json +1204 -0
  808. package/src/testing/estate/examples__auth__portolan__glossary.json +226 -0
  809. package/src/testing/estate/examples__auth__portolan__observed.json +757 -0
  810. package/src/testing/estate/examples__auth__portolan__stores.json +195 -0
  811. package/src/testing/estate/examples__shop__cart__portolan__api.json +234 -0
  812. package/src/testing/estate/examples__shop__cart__portolan__commands.json +86 -0
  813. package/src/testing/estate/examples__shop__cart__portolan__domain.json +966 -0
  814. package/src/testing/estate/examples__shop__cart__portolan__observed.json +498 -0
  815. package/src/testing/estate/examples__shop__cart__portolan__stores.json +208 -0
  816. package/src/testing/estate/examples__shop__oms__portolan__glossary.json +98 -0
  817. package/src/testing/estate/examples__shop__oms__portolan__proto.json +186 -0
  818. package/src/testing/estate.ts +49 -0
  819. package/src/trail/Trail.tsx +81 -0
  820. package/src/trail/model.test.ts +157 -0
  821. package/src/trail/model.ts +189 -0
  822. package/src/trail/record.tsx +32 -0
  823. package/src/trail/store.test.ts +66 -0
  824. package/src/trail/store.ts +102 -0
  825. package/tsconfig.json +35 -0
  826. package/vite.config.ts +171 -0
@@ -0,0 +1,567 @@
1
+ // What links here.
2
+ //
3
+ // Every entity page answers what it depends on: an event lists its schema, a
4
+ // service its calls, a flow its steps. None of that answers the question a
5
+ // reader actually arrives with — "who depends on THIS?" — because the answer
6
+ // is not held by the thing itself. It is spread across everything else.
7
+ //
8
+ // So it is derived, once, here. A consumer, a flow step, a decision record and
9
+ // a field naming a shared type are four different edges in the catalog, and a
10
+ // reader scanning the bottom of a page asks one question of all four, so they
11
+ // are one row type here and one component downstream.
12
+ //
13
+ // Pure, like derive.ts: catalog and index in, plain values out. Nothing builds
14
+ // a URL — a Backlink says WHAT points at the target and routes.ts says where
15
+ // that thing lives, which keeps this file testable without a router.
16
+
17
+ import type { Adr, Catalog, CatalogIndex, Flow, Status, Term } from "../catalog";
18
+ import { allServices, walkSteps } from "../catalog";
19
+ import { sortAdrs, adrNumber } from "./adr";
20
+ import { readersOfStore } from "./data-model";
21
+ import { flowsForService, usagesOfDef } from "./derive";
22
+ import { usagesOfEnum } from "./shape";
23
+ import { bindTerms } from "./terms";
24
+ import type { DefUsage } from "./derive";
25
+ import type { Kind } from "./kinds";
26
+
27
+ /** Flows are named by slug, because that is what a route and a step id use. */
28
+ export interface BacklinkTarget {
29
+ kind: Kind;
30
+ id: string;
31
+ }
32
+
33
+ export interface Backlink {
34
+ /** What is doing the pointing. */
35
+ kind: Kind;
36
+ /** Its catalog id — what a link is built from, and what "copy" copies. */
37
+ id: string;
38
+ /** What to print: an identifier, not a sentence. */
39
+ name: string;
40
+ /** What holds it — a context name, an aggregate id, a decision's title. */
41
+ owner: string | null;
42
+ /** For the colour dot; null for anything outside every context. */
43
+ context: string | null;
44
+ /**
45
+ * Why it points here, said in the catalog's own words: "consumes",
46
+ * "step 12", "relates.events", the names of the fields that carry a type.
47
+ * Without it a backlink is a name with no reason attached, which is exactly
48
+ * the thing a reader would have to open the page to find out.
49
+ */
50
+ via: string;
51
+ /** The exact place inside the linking thing — a flow's step id. */
52
+ at?: string;
53
+ /** Carried through from the edge, so an unresolved one still says so. */
54
+ status?: Status;
55
+ /** Event versions in which a shared type appears, oldest first. */
56
+ versions?: string[];
57
+ }
58
+
59
+ export interface BacklinkGroup {
60
+ kind: Kind;
61
+ links: Backlink[];
62
+ }
63
+
64
+ /**
65
+ * Group order, loudest dependency first. Services are what breaks when this
66
+ * changes; flows are where that break is seen; decisions are what was said
67
+ * about it; the rest is structure.
68
+ */
69
+ const GROUP_ORDER: readonly Kind[] = [
70
+ "service",
71
+ "flow",
72
+ "adr",
73
+ "event",
74
+ "aggregate",
75
+ "entity",
76
+ "vo",
77
+ "enum",
78
+ "def",
79
+ "context",
80
+ // Last. A term does not depend on this page in the sense every row above
81
+ // does - nothing breaks when the aggregate changes - it is what the thing
82
+ // is CALLED, which a reader wants after they know who breaks.
83
+ "term",
84
+ ] as const;
85
+
86
+ function grouped(links: Backlink[]): BacklinkGroup[] {
87
+ const by = new Map<Kind, Backlink[]>();
88
+ for (const link of links) {
89
+ const list = by.get(link.kind) ?? [];
90
+ list.push(link);
91
+ by.set(link.kind, list);
92
+ }
93
+ return GROUP_ORDER.filter((kind) => by.has(kind)).map((kind) => ({
94
+ kind,
95
+ links: by.get(kind) ?? [],
96
+ }));
97
+ }
98
+
99
+ export function backlinkCount(groups: readonly BacklinkGroup[]): number {
100
+ return groups.reduce((n, g) => n + g.links.length, 0);
101
+ }
102
+
103
+ // ---------------------------------------------------------------------------
104
+ // Row builders. One per kind of thing that can do the pointing.
105
+ // ---------------------------------------------------------------------------
106
+
107
+ function serviceLink(
108
+ index: CatalogIndex,
109
+ id: string,
110
+ via: string,
111
+ status?: Status,
112
+ ): Backlink {
113
+ const context = index.serviceContext.get(id);
114
+ return {
115
+ kind: "service",
116
+ id,
117
+ name: id,
118
+ // A consumer named by an event but absent from the catalog is still a real
119
+ // dependency. The graph draws it as a ghost; this row says so in words.
120
+ owner: context ? context.name : "not in the catalog",
121
+ context: context?.id ?? null,
122
+ via,
123
+ ...(status ? { status } : {}),
124
+ };
125
+ }
126
+
127
+ /** The word the context's glossary uses for this thing. */
128
+ function termLink(term: Term): Backlink {
129
+ return {
130
+ kind: "term",
131
+ id: term.id,
132
+ name: term.name,
133
+ owner: `glossary of ${term.context}`,
134
+ context: term.context,
135
+ via: "names it",
136
+ };
137
+ }
138
+
139
+ function flowLink(flow: Flow, via: string, at?: string): Backlink {
140
+ return {
141
+ kind: "flow",
142
+ id: flow.slug,
143
+ name: flow.slug,
144
+ owner: flow.name,
145
+ // A flow belongs to no single context; the wash on its own page takes the
146
+ // first one it crosses, and so does its dot here.
147
+ context: flow.participants.find((p) => p.context)?.context ?? null,
148
+ via,
149
+ ...(at ? { at } : {}),
150
+ };
151
+ }
152
+
153
+ function adrLink(index: CatalogIndex, adr: Adr, via: string): Backlink {
154
+ const context =
155
+ adr.scope.kind === "context"
156
+ ? adr.scope.context
157
+ : adr.scope.kind === "service"
158
+ ? (index.serviceContext.get(adr.scope.service)?.id ?? null)
159
+ : null;
160
+ return {
161
+ kind: "adr",
162
+ id: adr.id,
163
+ name: adrNumber(adr),
164
+ owner: adr.title,
165
+ context,
166
+ via,
167
+ };
168
+ }
169
+
170
+ /**
171
+ * A usage of a shared type, as a row. An RPC message has no page of its own,
172
+ * so it is filed under the service that answers it and says "rpc" in its
173
+ * reason rather than pretending to be one.
174
+ */
175
+ function usageLink(index: CatalogIndex, usage: DefUsage): Backlink {
176
+ const via =
177
+ usage.fields.length > 0 ? usage.fields.join(", ") : "the same shape";
178
+ switch (usage.kind) {
179
+ case "event": {
180
+ const owner = index.eventOwner.get(usage.id);
181
+ return {
182
+ kind: "event",
183
+ id: usage.id,
184
+ name: usage.name,
185
+ owner: usage.owner,
186
+ context: owner
187
+ ? (index.serviceContext.get(owner.service.id)?.id ?? null)
188
+ : null,
189
+ via,
190
+ ...(usage.versions && usage.versions.length > 0
191
+ ? { versions: usage.versions }
192
+ : {}),
193
+ };
194
+ }
195
+ case "entity":
196
+ case "vo":
197
+ return {
198
+ kind: usage.kind,
199
+ id: usage.id,
200
+ name: usage.name,
201
+ owner: usage.owner,
202
+ context: index.blockById.get(usage.id)?.context.id ?? null,
203
+ via,
204
+ };
205
+ case "rpc":
206
+ return {
207
+ kind: "service",
208
+ id: usage.owner,
209
+ name: usage.name,
210
+ owner: usage.owner,
211
+ context: index.serviceContext.get(usage.owner)?.id ?? null,
212
+ via: `rpc · ${via}`,
213
+ };
214
+ case "def":
215
+ return {
216
+ kind: "def",
217
+ id: usage.id,
218
+ name: usage.name,
219
+ owner: "shared types",
220
+ context: null,
221
+ via,
222
+ };
223
+ }
224
+ }
225
+
226
+ // ---------------------------------------------------------------------------
227
+ // Traversals
228
+ // ---------------------------------------------------------------------------
229
+
230
+ export interface StepInto {
231
+ flow: Flow;
232
+ stepId: string;
233
+ /** The step's place in the whole flow, frames included. */
234
+ number: number;
235
+ eventId: string;
236
+ }
237
+
238
+ /**
239
+ * Every step across every flow that carries one of these events. The generic
240
+ * form of derive's `stepsReferencing`: an aggregate is reached through all of
241
+ * its events at once, and the rows must still come out in flow order.
242
+ */
243
+ export function stepsInto(catalog: Catalog, eventIds: Set<string>): StepInto[] {
244
+ const out: StepInto[] = [];
245
+ for (const flow of catalog.flows) {
246
+ walkSteps(flow.steps).forEach((step, i) => {
247
+ if (step.ref && eventIds.has(step.ref)) {
248
+ out.push({ flow, stepId: step.id, number: i + 1, eventId: step.ref });
249
+ }
250
+ });
251
+ }
252
+ return out;
253
+ }
254
+
255
+ // ---------------------------------------------------------------------------
256
+ // Per-target derivations
257
+ // ---------------------------------------------------------------------------
258
+
259
+ function eventBacklinks(
260
+ catalog: Catalog,
261
+ index: CatalogIndex,
262
+ eventId: string,
263
+ ): Backlink[] {
264
+ const event = index.eventById.get(eventId);
265
+ if (!event) return [];
266
+ const out: Backlink[] = [];
267
+ for (const consumer of event.consumers) {
268
+ out.push(serviceLink(index, consumer.service, "consumes", consumer.status));
269
+ }
270
+ for (const step of stepsInto(catalog, new Set([eventId]))) {
271
+ out.push(flowLink(step.flow, `step ${step.number}`, step.stepId));
272
+ }
273
+ for (const adr of index.adrsByEvent.get(eventId) ?? []) {
274
+ out.push(adrLink(index, adr, "relates.events"));
275
+ }
276
+ return out;
277
+ }
278
+
279
+ function aggregateBacklinks(
280
+ catalog: Catalog,
281
+ index: CatalogIndex,
282
+ aggregateId: string,
283
+ ): Backlink[] {
284
+ const aggregate = index.aggregateById.get(aggregateId);
285
+ if (!aggregate) return [];
286
+ const out: Backlink[] = [];
287
+ const names = new Map(aggregate.events.map((e) => [e.id, e.name]));
288
+
289
+ for (const event of aggregate.events) {
290
+ for (const consumer of event.consumers) {
291
+ out.push(
292
+ serviceLink(
293
+ index,
294
+ consumer.service,
295
+ `consumes ${event.name}`,
296
+ consumer.status,
297
+ ),
298
+ );
299
+ }
300
+ }
301
+ for (const step of stepsInto(catalog, new Set(names.keys()))) {
302
+ out.push(
303
+ flowLink(
304
+ step.flow,
305
+ `step ${step.number} · ${names.get(step.eventId) ?? step.eventId}`,
306
+ step.stepId,
307
+ ),
308
+ );
309
+ }
310
+ // A decision naming two of this aggregate's events is one decision, not two
311
+ // rows, so the events it names are joined into its reason instead.
312
+ const byAdr = new Map<string, { adr: Adr; events: string[] }>();
313
+ for (const event of aggregate.events) {
314
+ for (const adr of index.adrsByEvent.get(event.id) ?? []) {
315
+ const seen = byAdr.get(adr.id) ?? { adr, events: [] };
316
+ seen.events.push(event.name);
317
+ byAdr.set(adr.id, seen);
318
+ }
319
+ }
320
+ for (const { adr, events } of byAdr.values()) {
321
+ out.push(adrLink(index, adr, `relates ${events.join(", ")}`));
322
+ }
323
+ return out;
324
+ }
325
+
326
+ function serviceBacklinks(
327
+ catalog: Catalog,
328
+ index: CatalogIndex,
329
+ serviceId: string,
330
+ ): Backlink[] {
331
+ const service = index.serviceById.get(serviceId);
332
+ if (!service) return [];
333
+ const out: Backlink[] = [];
334
+
335
+ // Who calls it. The service page lists `provides` and `consumes`, both of
336
+ // which point outwards; this is the only place the callers are named.
337
+ for (const other of allServices(catalog)) {
338
+ if (other.id === serviceId) continue;
339
+ for (const call of other.consumes) {
340
+ if (call.peer === serviceId) {
341
+ out.push(serviceLink(index, other.id, `calls ${call.id}`, call.status));
342
+ }
343
+ }
344
+ }
345
+ for (const aggregate of service.aggregates) {
346
+ for (const event of aggregate.events) {
347
+ for (const consumer of event.consumers) {
348
+ if (consumer.service === serviceId) continue;
349
+ out.push(
350
+ serviceLink(
351
+ index,
352
+ consumer.service,
353
+ `consumes ${event.name}`,
354
+ consumer.status,
355
+ ),
356
+ );
357
+ }
358
+ }
359
+ }
360
+ for (const flow of flowsForService(catalog, serviceId)) {
361
+ out.push(flowLink(flow, "participant"));
362
+ }
363
+ // Only decisions that NAME this service. An org-wide record governs it too,
364
+ // but "everything" is not a backlink, and the decisions tab already has it.
365
+ for (const adr of sortAdrs(catalog.adrs)) {
366
+ if (adr.scope.kind === "service" && adr.scope.service === serviceId) {
367
+ out.push(adrLink(index, adr, "scope"));
368
+ } else if ((adr.relates.services ?? []).includes(serviceId)) {
369
+ out.push(adrLink(index, adr, "relates.services"));
370
+ }
371
+ }
372
+ return out;
373
+ }
374
+
375
+ function contextBacklinks(
376
+ catalog: Catalog,
377
+ index: CatalogIndex,
378
+ contextId: string,
379
+ ): Backlink[] {
380
+ const context = catalog.contexts.find((c) => c.id === contextId);
381
+ if (!context) return [];
382
+ const out: Backlink[] = [];
383
+ const inside = new Set(context.services.map((s) => s.id));
384
+ const events = context.services.flatMap((s) =>
385
+ s.aggregates.flatMap((a) => a.events),
386
+ );
387
+ const eventIds = new Set(events.map((e) => e.id));
388
+
389
+ // Traffic that starts outside. A call between two of its own services is
390
+ // internal wiring, not something the context is depended on for.
391
+ for (const other of allServices(catalog)) {
392
+ if (inside.has(other.id)) continue;
393
+ for (const call of other.consumes) {
394
+ if (inside.has(call.peer)) {
395
+ out.push(serviceLink(index, other.id, `calls ${call.id}`, call.status));
396
+ }
397
+ }
398
+ }
399
+ for (const event of events) {
400
+ for (const consumer of event.consumers) {
401
+ if (inside.has(consumer.service)) continue;
402
+ out.push(
403
+ serviceLink(
404
+ index,
405
+ consumer.service,
406
+ `consumes ${event.name}`,
407
+ consumer.status,
408
+ ),
409
+ );
410
+ }
411
+ }
412
+ for (const flow of catalog.flows) {
413
+ const here = flow.participants
414
+ .filter((p) => p.context === contextId)
415
+ .map((p) => p.id);
416
+ if (here.length > 0) out.push(flowLink(flow, here.join(", ")));
417
+ }
418
+ for (const adr of sortAdrs(catalog.adrs)) {
419
+ const scope = adr.scope;
420
+ const services = (adr.relates.services ?? []).filter((s) => inside.has(s));
421
+ if (scope.kind === "context" && scope.context === contextId) {
422
+ out.push(adrLink(index, adr, "scope"));
423
+ } else if (scope.kind === "service" && inside.has(scope.service)) {
424
+ out.push(adrLink(index, adr, `scope ${scope.service}`));
425
+ } else if (services.length > 0) {
426
+ out.push(adrLink(index, adr, `relates ${services.join(", ")}`));
427
+ } else {
428
+ const named = (adr.relates.events ?? []).filter((e) => eventIds.has(e));
429
+ if (named.length > 0) {
430
+ out.push(
431
+ adrLink(
432
+ index,
433
+ adr,
434
+ `relates ${named.map((e) => index.eventById.get(e)?.name ?? e).join(", ")}`,
435
+ ),
436
+ );
437
+ }
438
+ }
439
+ }
440
+ return out;
441
+ }
442
+
443
+ function blockBacklinks(
444
+ catalog: Catalog,
445
+ index: CatalogIndex,
446
+ blockId: string,
447
+ ): Backlink[] {
448
+ const owner = index.blockById.get(blockId);
449
+ const ref = owner?.block.ref;
450
+ // An inline shape is used only where it is written. There is nothing to
451
+ // find, which is a fact about the block rather than an empty answer.
452
+ if (!ref) return [];
453
+ return usagesOfDef(catalog, ref, blockId).map((usage) =>
454
+ usageLink(index, usage),
455
+ );
456
+ }
457
+
458
+ /**
459
+ * The fields that switch on an enum. Found by the resolver's name match
460
+ * rather than by a ref, and each row says "by name" so that a reader knows
461
+ * the claim is the catalog's and not the source's.
462
+ */
463
+ function enumBacklinks(
464
+ catalog: Catalog,
465
+ index: CatalogIndex,
466
+ enumId: string,
467
+ ): Backlink[] {
468
+ if (!index.enumById.has(enumId)) return [];
469
+ return usagesOfEnum(catalog, enumId).map((usage) => {
470
+ const link = usageLink(index, usage);
471
+ return { ...link, via: `${link.via} · by name` };
472
+ });
473
+ }
474
+
475
+ /**
476
+ * Who depends on a schema: the services that read it - allowed, and exactly
477
+ * what makes a column rename expensive - and the aggregates its tables hold.
478
+ * The owner is not a row; it is the page's own header.
479
+ */
480
+ function storeBacklinks(
481
+ catalog: Catalog,
482
+ index: CatalogIndex,
483
+ storeId: string,
484
+ ): Backlink[] {
485
+ const store = index.storeById.get(storeId);
486
+ if (!store) return [];
487
+ const out: Backlink[] = [];
488
+ for (const reader of readersOfStore(catalog, storeId, store.owner)) {
489
+ out.push(serviceLink(index, reader.id, "reads"));
490
+ }
491
+ for (const table of store.tables) {
492
+ const aggregateId = table.persists?.aggregate;
493
+ const aggregate = aggregateId
494
+ ? index.aggregateById.get(aggregateId)
495
+ : undefined;
496
+ const owner = aggregateId ? index.aggregateOwner.get(aggregateId) : undefined;
497
+ if (!aggregateId || !aggregate || !owner) continue;
498
+ out.push({
499
+ kind: "aggregate",
500
+ id: aggregateId,
501
+ name: aggregate.name,
502
+ owner: owner.id,
503
+ context: index.serviceContext.get(owner.id)?.id ?? null,
504
+ via: `persisted in ${table.name}`,
505
+ });
506
+ }
507
+ return out;
508
+ }
509
+
510
+ function flowBacklinks(
511
+ catalog: Catalog,
512
+ index: CatalogIndex,
513
+ slug: string,
514
+ ): Backlink[] {
515
+ if (!index.flowBySlug.has(slug)) return [];
516
+ return sortAdrs(catalog.adrs)
517
+ .filter((adr) => (adr.relates.flows ?? []).includes(slug))
518
+ .map((adr) => adrLink(index, adr, "relates.flows"));
519
+ }
520
+
521
+ /**
522
+ * Everything in the catalog that points at one thing, grouped by what does the
523
+ * pointing.
524
+ *
525
+ * Decision records are not a target: the only thing that names an ADR is
526
+ * another ADR, through supersedes / supersededBy, and that is already drawn at
527
+ * the top of its page as a banner — a warning that the record is no longer in
528
+ * force says more than a row in a list can.
529
+ */
530
+ /** The rows, plus the word the glossary uses for the thing they point at. */
531
+ function named(catalog: Catalog, id: string, links: Backlink[]): Backlink[] {
532
+ const term = bindTerms(catalog).byTarget.get(id);
533
+
534
+ return term ? [...links, termLink(term)] : links;
535
+ }
536
+
537
+ export function backlinksFor(
538
+ catalog: Catalog,
539
+ index: CatalogIndex,
540
+ target: BacklinkTarget,
541
+ ): BacklinkGroup[] {
542
+ switch (target.kind) {
543
+ case "context":
544
+ return grouped(contextBacklinks(catalog, index, target.id));
545
+ case "service":
546
+ return grouped(serviceBacklinks(catalog, index, target.id));
547
+ // The three kinds a glossary can name. A context, a service and a flow are
548
+ // not words in a vocabulary - they are places the words are spoken.
549
+ case "aggregate":
550
+ return grouped(named(catalog, target.id, aggregateBacklinks(catalog, index, target.id)));
551
+ case "event":
552
+ return grouped(named(catalog, target.id, eventBacklinks(catalog, index, target.id)));
553
+ case "vo":
554
+ case "entity":
555
+ return grouped(named(catalog, target.id, blockBacklinks(catalog, index, target.id)));
556
+ case "enum":
557
+ return grouped(named(catalog, target.id, enumBacklinks(catalog, index, target.id)));
558
+ case "flow":
559
+ return grouped(flowBacklinks(catalog, index, target.id));
560
+ case "store":
561
+ return grouped(storeBacklinks(catalog, index, target.id));
562
+ // A module and an external API list their callers in a section of their
563
+ // own; a decision's incoming edges are its banner. Nothing to add here.
564
+ default:
565
+ return [];
566
+ }
567
+ }
@@ -0,0 +1,16 @@
1
+ import { buildInfo } from "./build-info";
2
+ import type { BuildInfo } from "./build-info";
3
+
4
+ /** A forge comparison for the selected pair, when this build knows its forge. */
5
+ export function branchCompareHref(
6
+ base: string,
7
+ head: string,
8
+ info: BuildInfo = buildInfo,
9
+ ): string | null {
10
+ if (!info.repoUrl || !base || !head || base === head) return null;
11
+ const pair = `${encodeURIComponent(base)}...${encodeURIComponent(head)}`;
12
+ const root = info.repoUrl.replace(/\/$/, "");
13
+ return info.forge === "gitlab" || /gitlab/i.test(root)
14
+ ? `${root}/-/compare/${pair}`
15
+ : `${root}/compare/${pair}`;
16
+ }
@@ -0,0 +1,76 @@
1
+ import { describe, expect, it } from "vitest";
2
+ import {
3
+ buildHref,
4
+ buildLabel,
5
+ buildTitle,
6
+ type BuildInfo,
7
+ } from "./build-info";
8
+
9
+ const CI: BuildInfo = {
10
+ commit: "4f1c9ae0c1b2d3e4f5a6b7c8d9e0f1a2b3c4d5e6",
11
+ shortCommit: "4f1c9ae",
12
+ branch: "main",
13
+ builtAt: "2026-08-29T09:14:22Z",
14
+ commitUrl:
15
+ "https://gitlab.com/acme/portolan/-/commit/4f1c9ae0c1b2d3e4f5a6b7c8d9e0f1a2b3c4d5e6",
16
+ buildUrl: "https://gitlab.com/acme/portolan/-/pipelines/17390211",
17
+ buildNumber: "128",
18
+ dirty: false,
19
+ repoUrl: "https://gitlab.com/acme/portolan",
20
+ };
21
+
22
+ const UNKNOWN: BuildInfo = {
23
+ commit: "",
24
+ shortCommit: "",
25
+ branch: "",
26
+ builtAt: "",
27
+ commitUrl: "",
28
+ buildUrl: "",
29
+ buildNumber: "",
30
+ dirty: false,
31
+ repoUrl: "",
32
+ };
33
+
34
+ describe("buildHref", () => {
35
+ it("opens the commit the stamp names", () => {
36
+ expect(buildHref(CI)).toBe(CI.commitUrl);
37
+ });
38
+
39
+ it("settles for the pipeline when the forge is unknown", () => {
40
+ expect(buildHref({ ...CI, commitUrl: "" })).toBe(CI.buildUrl);
41
+ });
42
+
43
+ it("is null when there is nowhere to go", () => {
44
+ expect(buildHref(UNKNOWN)).toBeNull();
45
+ });
46
+ });
47
+
48
+ describe("buildLabel", () => {
49
+ it("reads as the commit, not the build number, even on CI", () => {
50
+ expect(buildLabel(CI)).toBe("4f1c9ae");
51
+ });
52
+
53
+ it("marks a tree that was dirty when it was built", () => {
54
+ expect(buildLabel({ ...CI, dirty: true })).toBe("4f1c9ae+");
55
+ });
56
+
57
+ it("falls back to the build number when there is no commit", () => {
58
+ expect(buildLabel({ ...UNKNOWN, buildNumber: "128" })).toBe("#128");
59
+ });
60
+
61
+ it("says dev rather than nothing when git knew nothing", () => {
62
+ expect(buildLabel(UNKNOWN)).toBe("dev");
63
+ });
64
+ });
65
+
66
+ describe("buildTitle", () => {
67
+ it("spells out every field that has an answer", () => {
68
+ expect(buildTitle(CI)).toBe(
69
+ "commit 4f1c9ae · branch main · build #128 · built 2026-08-29 09:14:22Z",
70
+ );
71
+ });
72
+
73
+ it("drops the parts it cannot fill", () => {
74
+ expect(buildTitle(UNKNOWN)).toBe("built at an unrecorded time");
75
+ });
76
+ });