@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,19 @@
1
+ package main
2
+
3
+ import (
4
+ _ "embed"
5
+
6
+ "github.com/shortlink-org/portolan/plugin"
7
+ )
8
+
9
+ //go:embed options.schema.json
10
+ var optionsSchema []byte
11
+
12
+ func descriptor() plugin.Descriptor {
13
+ return plugin.Descriptor{
14
+ Name: "extract-adr",
15
+ Summary: "Reads decision records written by hand - MADR markdown with a title, meta bullets and the record itself, or the numbered records adr-tools writes - into a catalog fragment.",
16
+ Phases: []string{plugin.PhaseExtract},
17
+ Options: optionsSchema,
18
+ }
19
+ }
@@ -0,0 +1,11 @@
1
+ package main
2
+
3
+ import (
4
+ "testing"
5
+
6
+ "github.com/shortlink-org/portolan/plugin/schematest"
7
+ )
8
+
9
+ func TestOptionsSchemaMatchesStruct(t *testing.T) {
10
+ schematest.Check(t, optionsSchema, Options{})
11
+ }
@@ -0,0 +1,153 @@
1
+ package main
2
+
3
+ import (
4
+ "encoding/json"
5
+ "errors"
6
+ "os"
7
+ "path/filepath"
8
+ "sort"
9
+ "strings"
10
+
11
+ "github.com/shortlink-org/portolan/catalog"
12
+ "github.com/shortlink-org/portolan/plugin"
13
+ )
14
+
15
+ // extract reads every record the options name and answers with one fragment
16
+ // holding them.
17
+ //
18
+ // A file that does not parse, or that declares a record another file already
19
+ // declared, is left out of the fragment with a warning that names it and the
20
+ // line, and the rest of the tree is read: one record written differently is
21
+ // not a reason to document nothing. What is refused whole is a supersession
22
+ // with only one half recorded, because `src/catalog.ts` refuses to load a
23
+ // catalog that says it, and either record alone would say something untrue.
24
+ func extract(in plugin.Input, opts Options) (plugin.Response, error) {
25
+ b := &plugin.Builder{}
26
+
27
+ patterns := opts.Files
28
+ if len(patterns) == 0 {
29
+ patterns = []string{"docs/adr/*.md"}
30
+ }
31
+ var files []string
32
+ seen := map[string]bool{}
33
+ for _, pattern := range patterns {
34
+ matches, err := filepath.Glob(filepath.Join(in.Root, pattern))
35
+ if err != nil {
36
+ return plugin.Response{}, err
37
+ }
38
+ for _, match := range matches {
39
+ // A README in a directory of records is its index - written by
40
+ // hand or generated elsewhere - and is not itself a decision.
41
+ if strings.EqualFold(filepath.Base(match), "README.md") {
42
+ continue
43
+ }
44
+ if !seen[match] {
45
+ seen[match] = true
46
+ files = append(files, match)
47
+ }
48
+ }
49
+ }
50
+ sort.Strings(files)
51
+ if len(files) == 0 {
52
+ b.Warn(in.Root, "no decision records matched "+strings.Join(patterns, ", ")+"; the fragment holds none")
53
+ }
54
+
55
+ history := newHistory(in.Root, opts.History, b)
56
+
57
+ adrs := []catalog.Adr{}
58
+ ids := map[string]string{}
59
+ slugs := map[string]string{}
60
+ for _, file := range files {
61
+ src, err := os.ReadFile(file)
62
+ if err != nil {
63
+ return plugin.Response{}, err
64
+ }
65
+ rel := filepath.ToSlash(file)
66
+ adr, errs := parseAdr(rel, string(src), defaults{Scope: opts.Scope})
67
+ if len(errs) > 0 {
68
+ b.Warn(rel, "left out of the fragment: "+strings.Join(errs, "; "))
69
+
70
+ continue
71
+ }
72
+ if other, taken := ids[adr.ID]; taken {
73
+ b.Warn(rel, "left out of the fragment: "+adr.ID+" is already declared in "+other)
74
+
75
+ continue
76
+ }
77
+ if other, taken := slugs[adr.Slug]; taken {
78
+ b.Warn(rel, "left out of the fragment: the slug "+adr.Slug+" is already taken by "+other)
79
+
80
+ continue
81
+ }
82
+ ids[adr.ID] = rel
83
+ slugs[adr.Slug] = rel
84
+ adr.Created, adr.Revised = history.of(file)
85
+ adrs = append(adrs, adr)
86
+ }
87
+ if problems := supersessions(adrs, ids); len(problems) > 0 {
88
+ return plugin.Response{}, errors.New(strings.Join(problems, "\n"))
89
+ }
90
+
91
+ fragment := catalog.Catalog{
92
+ GeneratedAt: in.GeneratedAt,
93
+ Commit: in.Commit,
94
+ Contexts: []catalog.BoundedContext{},
95
+ Defs: map[string]catalog.TypeDef{},
96
+ Flows: []catalog.Flow{},
97
+ Adrs: adrs,
98
+ }
99
+ encoded, err := json.MarshalIndent(fragment, "", " ")
100
+ if err != nil {
101
+ return plugin.Response{}, err
102
+ }
103
+ out := opts.Out
104
+ if out == "" {
105
+ out = "adr.json"
106
+ }
107
+ b.File(out, string(encoded)+"\n")
108
+
109
+ return b.Response(), nil
110
+ }
111
+
112
+ // supersessions holds the two halves of a supersession against each other.
113
+ //
114
+ // Only the halves that are both here can be checked: a record superseded by
115
+ // one in another service's tree is a claim this step has no way to look up,
116
+ // and the validator, which sees the merged catalog, reports that one. What
117
+ // this catches is the common case - a record superseded by its neighbour,
118
+ // with only one of the two files edited.
119
+ func supersessions(adrs []catalog.Adr, ids map[string]string) []string {
120
+ var problems []string
121
+
122
+ by := map[string]*catalog.Adr{}
123
+ for i := range adrs {
124
+ by[adrs[i].ID] = &adrs[i]
125
+ }
126
+ names := func(list []string, id string) bool {
127
+ for _, entry := range list {
128
+ if entry == id {
129
+ return true
130
+ }
131
+ }
132
+
133
+ return false
134
+ }
135
+
136
+ for i := range adrs {
137
+ adr := &adrs[i]
138
+ where := ids[adr.ID] + ": "
139
+ if successor, here := by[adr.SupersededBy]; here && !names(successor.Supersedes, adr.ID) {
140
+ problems = append(problems, where+adr.ID+" is superseded by "+successor.ID+", which does not say it supersedes it")
141
+ }
142
+ for _, id := range adr.Supersedes {
143
+ if predecessor, here := by[id]; here && predecessor.SupersededBy != adr.ID {
144
+ problems = append(problems, where+adr.ID+" supersedes "+id+", which is not marked superseded by it")
145
+ }
146
+ }
147
+ if adr.SupersededBy == adr.ID {
148
+ problems = append(problems, where+adr.ID+" supersedes itself")
149
+ }
150
+ }
151
+
152
+ return problems
153
+ }
@@ -0,0 +1,350 @@
1
+ package main
2
+
3
+ // The mapping: records in a tree, a catalog fragment out.
4
+ //
5
+ // Shaped like plugins/extract-proto/extract_test.go, which is the house style
6
+ // for an extractor test - one behaviour per test, and a whole-fragment golden
7
+ // underneath so an unintended field change is a diff rather than an assertion
8
+ // nobody thought to write.
9
+
10
+ import (
11
+ "encoding/json"
12
+ "flag"
13
+ "os"
14
+ "os/exec"
15
+ "path/filepath"
16
+ "strconv"
17
+ "strings"
18
+ "testing"
19
+
20
+ "github.com/shortlink-org/portolan/catalog"
21
+ "github.com/shortlink-org/portolan/plugin"
22
+ )
23
+
24
+ var update = flag.Bool("update", false, "rewrite the golden fragment instead of comparing against it")
25
+
26
+ const goldenPath = "testdata/golden/adr.json"
27
+
28
+ func input(root string) plugin.Input {
29
+ // The stamp comes from the host, which derives it from git. A plugin that
30
+ // read a clock would write a different file every run.
31
+ return plugin.Input{
32
+ Root: root,
33
+ Commit: "abc1234",
34
+ GeneratedAt: "2024-01-01T00:00:00Z",
35
+ }
36
+ }
37
+
38
+ func response(t *testing.T) plugin.Response {
39
+ t.Helper()
40
+
41
+ resp, err := extract(input("testdata/estate"), Options{History: "none"})
42
+ if err != nil {
43
+ t.Fatal(err)
44
+ }
45
+
46
+ return resp
47
+ }
48
+
49
+ func fragment(t *testing.T) catalog.Catalog {
50
+ t.Helper()
51
+
52
+ var cat catalog.Catalog
53
+ if err := json.Unmarshal([]byte(response(t).Files[0].Contents), &cat); err != nil {
54
+ t.Fatal(err)
55
+ }
56
+
57
+ return cat
58
+ }
59
+
60
+ func TestEveryRecordInTheTreeIsRead(t *testing.T) {
61
+ var ids []string
62
+ for _, adr := range fragment(t).Adrs {
63
+ ids = append(ids, adr.ID)
64
+ }
65
+ if strings.Join(ids, " ") != "acme.0001 payments.0002 acme.0003 org.0004" {
66
+ t.Errorf("ids = %v", ids)
67
+ }
68
+ }
69
+
70
+ // A README in a directory of records is its index, not a decision. Reading it
71
+ // as one would put a table of contents on the site as a record with no status.
72
+ func TestAnIndexIsNotARecord(t *testing.T) {
73
+ for _, adr := range fragment(t).Adrs {
74
+ if strings.HasSuffix(adr.Source, "README.md") {
75
+ t.Errorf("%q was read as a record", adr.Source)
76
+ }
77
+ }
78
+ }
79
+
80
+ // The fragment is one source among several, so it carries the empty lists of
81
+ // everything it says nothing about rather than leaving the merge to guess.
82
+ func TestTheFragmentSaysNothingItDidNotRead(t *testing.T) {
83
+ cat := fragment(t)
84
+ if len(cat.Contexts) != 0 || len(cat.Flows) != 0 || len(cat.Defs) != 0 {
85
+ t.Errorf("the fragment claims more than its records: %+v", cat)
86
+ }
87
+ if cat.Commit != "abc1234" || cat.GeneratedAt != "2024-01-01T00:00:00Z" {
88
+ t.Errorf("stamp = %q %q", cat.Commit, cat.GeneratedAt)
89
+ }
90
+ }
91
+
92
+ func TestTheFileIsNamedByTheOptions(t *testing.T) {
93
+ resp, err := extract(input("testdata/estate"), Options{History: "none", Out: "decisions.json"})
94
+ if err != nil {
95
+ t.Fatal(err)
96
+ }
97
+ if resp.Files[0].Name != "decisions.json" {
98
+ t.Errorf("name = %q", resp.Files[0].Name)
99
+ }
100
+ if response(t).Files[0].Name != "adr.json" {
101
+ t.Errorf("the default is not adr.json")
102
+ }
103
+ }
104
+
105
+ // A root with no records is worth saying out loud. A step pointed at the wrong
106
+ // directory answers with an empty fragment either way, and the warning is the
107
+ // only thing that tells the two apart.
108
+ func TestARootWithNoRecordsWarns(t *testing.T) {
109
+ resp, err := extract(input("testdata"), Options{History: "none"})
110
+ if err != nil {
111
+ t.Fatal(err)
112
+ }
113
+ if !warned(resp, "no decision records matched") {
114
+ t.Errorf("diagnostics = %+v", resp.Warnings())
115
+ }
116
+ }
117
+
118
+ // A supersession with one half recorded is refused whole: src/catalog.ts
119
+ // fails the app on it, and either record alone would say something untrue.
120
+ func TestAHalfRecordedSupersessionIsRefusedWhole(t *testing.T) {
121
+ resp, err := extract(input("testdata/broken"), Options{History: "none"})
122
+ if err == nil {
123
+ t.Fatal("the broken tree produced a fragment")
124
+ }
125
+ if len(resp.Files) != 0 {
126
+ t.Errorf("files were named anyway: %+v", resp.Files)
127
+ }
128
+ if want := "acme.0001 is superseded by acme.0002, which does not say it supersedes it"; !strings.Contains(err.Error(), want) {
129
+ t.Errorf("no %q in:\n%s", want, err)
130
+ }
131
+ }
132
+
133
+ // A tree kept with adr-tools, with the mistakes such a tree collects: two
134
+ // records numbered the same by two branches, and a file of notes among them.
135
+ // Each is left out with a warning that names it, and the rest is read.
136
+ func TestATreeOfAdrToolsRecordsIsReadAndItsMistakesAreLeftOut(t *testing.T) {
137
+ resp, err := extract(input("testdata/lenient"), Options{Scope: "avia.aviasupp", Files: []string{"docs/adr/*.md"}, History: "none"})
138
+ if err != nil {
139
+ t.Fatal(err)
140
+ }
141
+ var cat catalog.Catalog
142
+ if err := json.Unmarshal([]byte(resp.Files[0].Contents), &cat); err != nil {
143
+ t.Fatal(err)
144
+ }
145
+
146
+ var ids []string
147
+ for _, adr := range cat.Adrs {
148
+ ids = append(ids, adr.ID)
149
+ }
150
+ if strings.Join(ids, " ") != "aviasupp.0001 aviasupp.0003 aviasupp.0004 aviasupp.0005 acme.0007" {
151
+ t.Errorf("ids = %v", ids)
152
+ }
153
+
154
+ for _, want := range []string{
155
+ "0004-use-river.md: left out of the fragment: aviasupp.0004 is already declared in",
156
+ "0006-a-note-that-is-not-a-record.md: left out of the fragment: ",
157
+ "0007-declared-once.md: left out of the fragment: acme.0007 is already declared in",
158
+ } {
159
+ if !warnedAt(resp, want) {
160
+ t.Errorf("no warning %q among %+v", want, resp.Warnings())
161
+ }
162
+ }
163
+
164
+ by := map[string]catalog.Adr{}
165
+ for _, adr := range cat.Adrs {
166
+ by[adr.ID] = adr
167
+ }
168
+ if dto := by["aviasupp.0003"]; dto.Status != catalog.AdrSuperseded || dto.SupersededBy != "aviasupp.0005" {
169
+ t.Errorf("aviasupp.0003 = %+v", dto)
170
+ }
171
+ if schemas := by["aviasupp.0005"]; strings.Join(schemas.Supersedes, " ") != "aviasupp.0003" {
172
+ t.Errorf("aviasupp.0005 supersedes %v", schemas.Supersedes)
173
+ }
174
+ if action := by["aviasupp.0004"]; action.Status != catalog.AdrProposed || action.Title != "Call action" {
175
+ t.Errorf("aviasupp.0004 = %+v", action)
176
+ }
177
+ }
178
+
179
+ func warnedAt(resp plugin.Response, substring string) bool {
180
+ for _, d := range resp.Warnings() {
181
+ if strings.Contains(d.Ref+": "+d.Message, substring) {
182
+ return true
183
+ }
184
+ }
185
+
186
+ return false
187
+ }
188
+
189
+ // The fragment is committed and compared by gen:check. A map iterated in Go's
190
+ // order would rewrite the file every run and turn every build into a diff.
191
+ func TestOutputIsByteIdentical(t *testing.T) {
192
+ first, err := extract(input("testdata/estate"), Options{History: "none"})
193
+ if err != nil {
194
+ t.Fatal(err)
195
+ }
196
+ for i := 0; i < 5; i++ {
197
+ again, err := extract(input("testdata/estate"), Options{History: "none"})
198
+ if err != nil {
199
+ t.Fatal(err)
200
+ }
201
+ if again.Files[0].Contents != first.Files[0].Contents {
202
+ t.Fatal("two runs over the same tree produced different fragments")
203
+ }
204
+ }
205
+ }
206
+
207
+ // The whole fragment, so a field that changes shape shows up as a diff rather
208
+ // than slipping past every assertion above.
209
+ func TestGoldenFragment(t *testing.T) {
210
+ got := response(t).Files[0].Contents
211
+
212
+ if *update {
213
+ if err := os.WriteFile(goldenPath, []byte(got), 0o644); err != nil {
214
+ t.Fatal(err)
215
+ }
216
+ t.Log("golden fragment rewritten")
217
+
218
+ return
219
+ }
220
+
221
+ want, err := os.ReadFile(goldenPath)
222
+ if err != nil {
223
+ t.Fatalf("reading the golden fragment: %v (run `go test ./plugins/extract-adr -update`)", err)
224
+ }
225
+ if string(want) != got {
226
+ t.Errorf("the fragment differs from the golden file\n%s", firstDifference(string(want), got))
227
+ }
228
+ }
229
+
230
+ func warned(resp plugin.Response, substring string) bool {
231
+ for _, d := range resp.Warnings() {
232
+ if strings.Contains(d.Message, substring) {
233
+ return true
234
+ }
235
+ }
236
+
237
+ return false
238
+ }
239
+
240
+ func firstDifference(want, got string) string {
241
+ a := strings.Split(want, "\n")
242
+ b := strings.Split(got, "\n")
243
+ for i := 0; i < len(a) && i < len(b); i++ {
244
+ if a[i] != b[i] {
245
+ return "line " + strconv.Itoa(i+1) + ":\n want: " + a[i] + "\n got: " + b[i]
246
+ }
247
+ }
248
+
249
+ return "want " + strconv.Itoa(len(a)) + " lines, got " + strconv.Itoa(len(b))
250
+ }
251
+
252
+ // Who wrote a record down and when comes from the file's own commits: the
253
+ // first commit is when it was created, the last when it was revised, and a
254
+ // file committed once has no revision. A tree that is not a checkout says
255
+ // nothing, once.
256
+ func TestWhoCommittedARecordComesFromGit(t *testing.T) {
257
+ root := t.TempDir()
258
+ run := func(args ...string) {
259
+ t.Helper()
260
+ cmd := exec.Command("git", append([]string{"-C", root}, args...)...)
261
+ cmd.Env = append(os.Environ(),
262
+ "GIT_AUTHOR_NAME=Ada Lovelace", "GIT_AUTHOR_EMAIL=ada@example.com",
263
+ "GIT_COMMITTER_NAME=Ada Lovelace", "GIT_COMMITTER_EMAIL=ada@example.com",
264
+ "GIT_COMMITTER_DATE=2026-01-01T09:00:00Z", "GIT_AUTHOR_DATE=2026-01-01T09:00:00Z",
265
+ )
266
+ if out, err := cmd.CombinedOutput(); err != nil {
267
+ t.Fatalf("git %v: %v\n%s", args, err, out)
268
+ }
269
+ }
270
+ write := func(name, contents string) {
271
+ t.Helper()
272
+ if err := os.MkdirAll(filepath.Join(root, "docs", "adr"), 0o755); err != nil {
273
+ t.Fatal(err)
274
+ }
275
+ if err := os.WriteFile(filepath.Join(root, "docs", "adr", name), []byte(contents), 0o644); err != nil {
276
+ t.Fatal(err)
277
+ }
278
+ }
279
+ record := func(title, body string) string {
280
+ return "# " + title + "\n\nDate: 2026-01-01\n\n## Status\n\nAccepted\n\n## Context\n\n" + body + "\n"
281
+ }
282
+
283
+ run("init", "-q")
284
+ write("0001-first.md", record("1. First", "As written."))
285
+ write("0002-second.md", record("2. Second", "As written."))
286
+ run("add", ".")
287
+ run("commit", "-q", "-m", "two records")
288
+
289
+ write("0002-second.md", record("2. Second", "Reworded."))
290
+ cmd := exec.Command("git", "-C", root, "commit", "-q", "-am", "reword")
291
+ cmd.Env = append(os.Environ(),
292
+ "GIT_AUTHOR_NAME=Grace Hopper", "GIT_AUTHOR_EMAIL=grace@example.com",
293
+ "GIT_COMMITTER_NAME=Grace Hopper", "GIT_COMMITTER_EMAIL=grace@example.com",
294
+ "GIT_COMMITTER_DATE=2026-01-03T17:30:00Z", "GIT_AUTHOR_DATE=2026-01-03T17:30:00Z",
295
+ )
296
+ if out, err := cmd.CombinedOutput(); err != nil {
297
+ t.Fatalf("git commit: %v\n%s", err, out)
298
+ }
299
+
300
+ resp, err := extract(input(root), Options{Scope: "org"})
301
+ if err != nil {
302
+ t.Fatal(err)
303
+ }
304
+ var cat catalog.Catalog
305
+ if err := json.Unmarshal([]byte(resp.Files[0].Contents), &cat); err != nil {
306
+ t.Fatal(err)
307
+ }
308
+ if len(cat.Adrs) != 2 {
309
+ t.Fatalf("adrs = %+v", cat.Adrs)
310
+ }
311
+
312
+ first, second := cat.Adrs[0], cat.Adrs[1]
313
+ if first.Created == nil || first.Created.Author != "Ada Lovelace" || first.Created.Date != "2026-01-01T09:00:00Z" || len(first.Created.Commit) != 40 {
314
+ t.Errorf("first created = %+v", first.Created)
315
+ }
316
+ if first.Revised != nil {
317
+ t.Errorf("a file committed once was revised: %+v", first.Revised)
318
+ }
319
+ if second.Created == nil || second.Created.Author != "Ada Lovelace" {
320
+ t.Errorf("second created = %+v", second.Created)
321
+ }
322
+ if second.Revised == nil || second.Revised.Author != "Grace Hopper" || second.Revised.Date != "2026-01-03T17:30:00Z" || second.Revised.Commit == second.Created.Commit {
323
+ t.Errorf("second revised = %+v", second.Revised)
324
+ }
325
+
326
+ // Told not to look, the extractor does not.
327
+ resp, err = extract(input(root), Options{Scope: "org", History: "none"})
328
+ if err != nil {
329
+ t.Fatal(err)
330
+ }
331
+ if strings.Contains(resp.Files[0].Contents, "\"created\"") {
332
+ t.Error("history \"none\" still read git")
333
+ }
334
+
335
+ // A tree with no checkout around it says so once and carries on.
336
+ bare := t.TempDir()
337
+ if err := os.MkdirAll(filepath.Join(bare, "docs", "adr"), 0o755); err != nil {
338
+ t.Fatal(err)
339
+ }
340
+ if err := os.WriteFile(filepath.Join(bare, "docs", "adr", "0001-first.md"), []byte(record("1. First", "As written.")), 0o644); err != nil {
341
+ t.Fatal(err)
342
+ }
343
+ resp, err = extract(input(bare), Options{Scope: "org"})
344
+ if err != nil {
345
+ t.Fatal(err)
346
+ }
347
+ if !warned(resp, "not inside a git checkout") {
348
+ t.Errorf("warnings = %+v", resp.Warnings())
349
+ }
350
+ }
@@ -0,0 +1,99 @@
1
+ package main
2
+
3
+ import (
4
+ "os/exec"
5
+ "path/filepath"
6
+ "strings"
7
+
8
+ "github.com/shortlink-org/portolan/catalog"
9
+ "github.com/shortlink-org/portolan/plugin"
10
+ )
11
+
12
+ // The markdown says when a decision was taken. Git says when it was written
13
+ // down and by whom, and when it was last changed - the other half of "who
14
+ // decided this", and one the author never has to type. Read from the file's
15
+ // own commits, following renames, so a record moved into docs/adr keeps the
16
+ // day it was first committed.
17
+ //
18
+ // This is the one place an extractor reads history rather than the tree. It
19
+ // is still a function of the checkout: the same commit gives the same answer
20
+ // on every machine, which is what lets the fragment be committed and checked.
21
+
22
+ type history struct {
23
+ root string
24
+ // repo is the working tree the root belongs to, or "" when there is none
25
+ // and every record is answered with nothing.
26
+ repo string
27
+ }
28
+
29
+ func newHistory(root, mode string, b *plugin.Builder) *history {
30
+ h := &history{root: root}
31
+ if mode == "none" {
32
+ return h
33
+ }
34
+
35
+ top, err := git(root, "rev-parse", "--show-toplevel")
36
+ if err != nil || top == "" {
37
+ b.Warn(root, "not inside a git checkout, so the records say nothing about who committed them; set `history` to \"none\" to stop hearing this")
38
+
39
+ return h
40
+ }
41
+ h.repo = top
42
+
43
+ return h
44
+ }
45
+
46
+ // of answers with the commit that first added the file and the one that last
47
+ // touched it, or nothing when the file has no history - it is new, or the
48
+ // root is not a checkout.
49
+ func (h *history) of(file string) (created, revised *catalog.AdrCommit) {
50
+ if h.repo == "" {
51
+ return nil, nil
52
+ }
53
+
54
+ // The file is named from where the extractor runs; git is asked from the
55
+ // top of the checkout, so the path is made absolute rather than left to
56
+ // be read against a directory it was not written for.
57
+ abs, err := filepath.Abs(file)
58
+ if err != nil {
59
+ return nil, nil
60
+ }
61
+ out, err := git(h.repo, "log", "--follow", "--format=%H%x1f%an%x1f%cI", "--", abs)
62
+ if err != nil || out == "" {
63
+ return nil, nil
64
+ }
65
+
66
+ lines := strings.Split(out, "\n")
67
+ first := commitOf(lines[len(lines)-1])
68
+ last := commitOf(lines[0])
69
+ if first == nil {
70
+ return nil, nil
71
+ }
72
+ if last != nil && last.Commit != first.Commit {
73
+ return first, last
74
+ }
75
+
76
+ return first, nil
77
+ }
78
+
79
+ func commitOf(line string) *catalog.AdrCommit {
80
+ parts := strings.Split(line, "\x1f")
81
+ if len(parts) != 3 || parts[0] == "" {
82
+ return nil
83
+ }
84
+
85
+ return &catalog.AdrCommit{Commit: parts[0], Author: parts[1], Date: parts[2]}
86
+ }
87
+
88
+ // git runs one command in the directory and answers with its trimmed output.
89
+ // Its stderr is not this run's diagnostics: a directory that is not a
90
+ // checkout is a fact newHistory reports once, in its own words.
91
+ func git(dir string, args ...string) (string, error) {
92
+ cmd := exec.Command("git", append([]string{"-C", filepath.Clean(dir)}, args...)...)
93
+ out, err := cmd.Output()
94
+ if err != nil {
95
+ return "", err
96
+ }
97
+
98
+ return strings.TrimSpace(string(out)), nil
99
+ }
@@ -0,0 +1,65 @@
1
+ // Command extract-adr reads decision records written by hand, as the MADR
2
+ // markdown they already are, and answers with a catalog fragment.
3
+ //
4
+ // A decision is written down once, in a file beside the code it constrains,
5
+ // and it is read there by whoever changed that code. Typing it a second time
6
+ // into the catalog's JSON - as an id, a slug, a number, a scope and a body
7
+ // with every newline escaped - makes the JSON the source and the markdown a
8
+ // copy, which is the arrow pointing the wrong way: the copy is the one that
9
+ // goes stale, and the three records that lived in `data/catalog.json` before
10
+ // this plugin existed all pointed at files that were never there.
11
+ //
12
+ // So the markdown is the source and the fragment is the output. What the
13
+ // format cannot say - which service a record belongs to, whether the record
14
+ // is still in force - it says in bullets under the title, and everything from
15
+ // the first `##` onward is the record itself, carried through untouched. The
16
+ // format is described in plugins/README.md and held to by parse_test.go.
17
+ package main
18
+
19
+ import (
20
+ "fmt"
21
+ "io"
22
+ "os"
23
+
24
+ "github.com/shortlink-org/portolan/plugin"
25
+ )
26
+
27
+ // Options are what the manifest tells the extractor.
28
+ type Options struct {
29
+ // Files are the record files to read, as globs relative to the input
30
+ // root. Left out, every `docs/adr/*.md`, which is where a service keeps
31
+ // them; an estate-wide step points its own glob somewhere else.
32
+ Files []string `json:"files,omitempty"`
33
+
34
+ // Scope is what a record is about when it does not say so itself: a
35
+ // service, `avia.aviasupp`, a context, or the organisation. A record
36
+ // written by adr-tools has no such field, and its id takes its prefix
37
+ // from here. A MADR record with a Scope bullet keeps its own.
38
+ Scope string `json:"scope,omitempty"`
39
+
40
+ // History says where a record's author and dates come from: "git", the
41
+ // default, reads the file's commits - who first added it and who last
42
+ // touched it; "none" reads nothing, for a tree that is not a checkout
43
+ // or a fragment that must not change when the history does.
44
+ History string `json:"history,omitempty"`
45
+
46
+ // Out names the fragment file.
47
+ Out string `json:"out,omitempty"`
48
+ }
49
+
50
+ func main() {
51
+ if err := run(os.Stdin, os.Stdout); err != nil {
52
+ fmt.Fprintln(os.Stderr, "portolan-extract-adr:", err)
53
+ os.Exit(1)
54
+ }
55
+ }
56
+
57
+ func run(stdin io.Reader, stdout io.Writer) error {
58
+ return plugin.Serve(stdin, stdout, descriptor(), func(req plugin.Request, opts Options) (plugin.Response, error) {
59
+ if req.Input.Root == "" {
60
+ return plugin.Response{}, fmt.Errorf("no input root: an extractor has nothing to read")
61
+ }
62
+
63
+ return extract(req.Input, opts)
64
+ })
65
+ }