@shortlink-org/portolan 0.1.0 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (381) hide show
  1. package/README.md +69 -205
  2. package/cli/init.mjs +285 -0
  3. package/cli/init.test.mjs +158 -0
  4. package/cli/portolan.mjs +37 -73
  5. package/index.html +19 -0
  6. package/package.json +5 -10
  7. package/plugins/README.md +66 -27
  8. package/plugins/portolan-go.wasm +0 -0
  9. package/portolan.json +52 -73
  10. package/public/landing/cats/bug-hunter.webp +0 -0
  11. package/public/landing/cats/compass-nap.webp +0 -0
  12. package/public/landing/cats/diagrammer.webp +0 -0
  13. package/public/landing/cats/docs-reader.webp +0 -0
  14. package/public/landing/cats/laptop.webp +0 -0
  15. package/public/landing/cats/map-inspector.webp +0 -0
  16. package/public/landing/cats/server-break.webp +0 -0
  17. package/public/landing/cats/star-mapper.webp +0 -0
  18. package/public/landing/cats/system-builder.webp +0 -0
  19. package/public/landing/cats/thread-tangle.webp +0 -0
  20. package/public/og.png +0 -0
  21. package/public/readme-header.webp +0 -0
  22. package/schema/portolan.schema.json +180 -0
  23. package/scripts/builtin-plugins.mjs +14 -0
  24. package/scripts/delivery-presets.mjs +353 -0
  25. package/scripts/gen.mjs +37 -4
  26. package/scripts/history.mjs +126 -0
  27. package/scripts/history.test.mjs +110 -0
  28. package/scripts/host-plugins/fetch-bsr.mjs +351 -0
  29. package/{plugins/fetch-bsr/options.schema.json → scripts/host-plugins/fetch-bsr.options.json} +4 -0
  30. package/scripts/host-plugins/fetch-bsr.test.mjs +212 -0
  31. package/scripts/host-plugins/fetch-csr.mjs +386 -0
  32. package/scripts/host-plugins/fetch-csr.test.mjs +178 -0
  33. package/scripts/host-plugins/fetch-git.mjs +379 -0
  34. package/scripts/host-plugins/fetch-git.test.mjs +210 -0
  35. package/scripts/local-api.mjs +22 -4
  36. package/scripts/local-api.test.mjs +84 -2
  37. package/scripts/manifest.mjs +3 -2
  38. package/scripts/package-smoke.mjs +6 -2
  39. package/scripts/plugin-host.mjs +77 -18
  40. package/scripts/plugin-host.test.mjs +50 -1
  41. package/scripts/plugin-wasm-worker.mjs +6 -3
  42. package/scripts/run-builtin.mjs +6 -26
  43. package/scripts/schema.mjs +6 -1
  44. package/src/app/App.tsx +24 -429
  45. package/src/app/Breadcrumbs.test.ts +7 -0
  46. package/src/app/Breadcrumbs.tsx +15 -2
  47. package/src/app/CatalogApp.tsx +436 -0
  48. package/src/app/Sidebar.tsx +0 -1
  49. package/src/chat/ChatPanel.tsx +98 -41
  50. package/src/chat/Composer.tsx +4 -2
  51. package/src/chat/Conversation.tsx +79 -15
  52. package/src/chat/Header.tsx +43 -9
  53. package/src/chat/Starter.tsx +64 -3
  54. package/src/chat/Steps.tsx +1 -1
  55. package/src/chat/flags.test.ts +51 -14
  56. package/src/chat/flags.ts +8 -2
  57. package/src/chat/page-context.test.ts +51 -0
  58. package/src/chat/page-context.ts +128 -0
  59. package/src/chat/prompt.test.ts +49 -1
  60. package/src/chat/prompt.ts +42 -1
  61. package/src/chat/tools.ts +7 -2
  62. package/src/chat/transport.ts +16 -4
  63. package/src/components/ProblemRow.tsx +182 -0
  64. package/src/data.ts +20 -5
  65. package/src/er/ErCanvas.tsx +1 -1
  66. package/src/graph/DependencyGraph.tsx +10 -1
  67. package/src/graph/FocusedEventGraph.tsx +1 -1
  68. package/src/graph/GraphToolbar.tsx +2 -4
  69. package/src/index.css +220 -2
  70. package/src/landing/DraggableReveal.tsx +168 -0
  71. package/src/landing/EstateGraph.tsx +28 -0
  72. package/src/landing/FlowPlayback.tsx +282 -0
  73. package/src/landing/HeroMap.tsx +128 -0
  74. package/src/landing/LandingPage.tsx +757 -0
  75. package/src/landing/ProductFrame.tsx +68 -0
  76. package/src/landing/ProductTour.tsx +371 -0
  77. package/src/landing/catalog.ts +25 -0
  78. package/src/landing/motion.tsx +52 -0
  79. package/src/lib/all-problems.ts +28 -0
  80. package/src/lib/local-api.ts +35 -1
  81. package/src/lib/setup-info.test.ts +8 -0
  82. package/src/lib/setup-info.ts +3 -2
  83. package/src/lib/source-code.ts +2 -1
  84. package/src/likec4/C4View.tsx +5 -0
  85. package/src/likec4/InteractiveView.tsx +5 -1
  86. package/src/map/ContextMapGraph.tsx +47 -20
  87. package/src/pages/ContextMap.tsx +5 -1
  88. package/src/pages/FlowDetail.tsx +24 -7
  89. package/src/pages/Overview.tsx +68 -12
  90. package/src/pages/Problems.tsx +6 -193
  91. package/src/pages/Settings.tsx +133 -108
  92. package/src/pages/settings/DeliverySettings.tsx +229 -0
  93. package/src/pages/settings/PreferencesSettings.tsx +101 -0
  94. package/src/routes.test.ts +20 -0
  95. package/src/routes.ts +13 -1
  96. package/vite.config.ts +12 -8
  97. package/catalog/enum_test.go +0 -46
  98. package/catalog/model.go +0 -932
  99. package/catalog/roundtrip_test.go +0 -185
  100. package/catalog/via_test.go +0 -38
  101. package/go.mod +0 -14
  102. package/go.sum +0 -14
  103. package/internal/commands/cargo.go +0 -154
  104. package/internal/commands/commands.go +0 -143
  105. package/internal/commands/commands_test.go +0 -323
  106. package/internal/commands/gradle.go +0 -77
  107. package/internal/commands/justfile.go +0 -82
  108. package/internal/commands/makefile.go +0 -106
  109. package/internal/commands/maven.go +0 -93
  110. package/internal/commands/packagejson.go +0 -159
  111. package/internal/commands/pyproject.go +0 -169
  112. package/internal/commands/taskfile.go +0 -101
  113. package/internal/commands/testdata/estate/.cargo/config.toml +0 -7
  114. package/internal/commands/testdata/estate/Makefile +0 -33
  115. package/internal/commands/testdata/estate/Taskfile.yml +0 -21
  116. package/internal/commands/testdata/estate/build.gradle.kts +0 -21
  117. package/internal/commands/testdata/estate/justfile +0 -20
  118. package/internal/commands/testdata/estate/package.json +0 -13
  119. package/internal/commands/testdata/estate/pom.xml +0 -29
  120. package/internal/commands/testdata/estate/pyproject.toml +0 -27
  121. package/internal/commands/testdata/estate/xtask/src/main.rs +0 -27
  122. package/internal/commands/testdata/golden/commands.json +0 -305
  123. package/internal/gohttp/analyze.go +0 -2500
  124. package/internal/gohttp/endpoints.go +0 -1067
  125. package/internal/gohttp/roots.go +0 -320
  126. package/internal/gohttp/typed.go +0 -143
  127. package/internal/goscan/constants.go +0 -85
  128. package/internal/goscan/goscan_test.go +0 -227
  129. package/internal/goscan/names.go +0 -52
  130. package/internal/goscan/parse_test.go +0 -11
  131. package/internal/goscan/source.go +0 -36
  132. package/internal/goscan/tree.go +0 -155
  133. package/internal/goscan/types.go +0 -99
  134. package/internal/wsdl/ids.go +0 -127
  135. package/internal/wsdl/ids_test.go +0 -21
  136. package/internal/wsdl/model.go +0 -62
  137. package/internal/wsdl/parse.go +0 -920
  138. package/internal/wsdl/parse_test.go +0 -133
  139. package/plugin/describe.go +0 -107
  140. package/plugin/describe_test.go +0 -114
  141. package/plugin/protocol.go +0 -117
  142. package/plugin/schematest/schematest.go +0 -126
  143. package/plugins/extract-adr/describe.go +0 -19
  144. package/plugins/extract-adr/describe_test.go +0 -11
  145. package/plugins/extract-adr/extract.go +0 -153
  146. package/plugins/extract-adr/extract_test.go +0 -350
  147. package/plugins/extract-adr/history.go +0 -99
  148. package/plugins/extract-adr/main.go +0 -65
  149. package/plugins/extract-adr/parse.go +0 -642
  150. package/plugins/extract-adr/parse_test.go +0 -405
  151. package/plugins/extract-asyncapi/describe.go +0 -19
  152. package/plugins/extract-asyncapi/describe_test.go +0 -11
  153. package/plugins/extract-asyncapi/extract.go +0 -315
  154. package/plugins/extract-asyncapi/extract_test.go +0 -197
  155. package/plugins/extract-asyncapi/main.go +0 -48
  156. package/plugins/extract-asyncapi/spec.go +0 -151
  157. package/plugins/extract-commands/describe.go +0 -19
  158. package/plugins/extract-commands/describe_test.go +0 -11
  159. package/plugins/extract-commands/extract.go +0 -79
  160. package/plugins/extract-commands/extract_test.go +0 -92
  161. package/plugins/extract-commands/main.go +0 -55
  162. package/plugins/extract-csr/avro.go +0 -258
  163. package/plugins/extract-csr/describe.go +0 -19
  164. package/plugins/extract-csr/describe_test.go +0 -11
  165. package/plugins/extract-csr/extract.go +0 -338
  166. package/plugins/extract-csr/extract_test.go +0 -374
  167. package/plugins/extract-csr/jsonschema.go +0 -343
  168. package/plugins/extract-csr/lock.go +0 -25
  169. package/plugins/extract-csr/main.go +0 -84
  170. package/plugins/extract-csr/subject.go +0 -102
  171. package/plugins/extract-flows/describe.go +0 -19
  172. package/plugins/extract-flows/describe_test.go +0 -11
  173. package/plugins/extract-flows/extract.go +0 -91
  174. package/plugins/extract-flows/main.go +0 -45
  175. package/plugins/extract-flows/parse.go +0 -593
  176. package/plugins/extract-flows/parse_test.go +0 -204
  177. package/plugins/extract-glossary/describe.go +0 -19
  178. package/plugins/extract-glossary/describe_test.go +0 -11
  179. package/plugins/extract-glossary/extract.go +0 -115
  180. package/plugins/extract-glossary/extract_test.go +0 -220
  181. package/plugins/extract-glossary/main.go +0 -59
  182. package/plugins/extract-glossary/parse.go +0 -214
  183. package/plugins/extract-glossary/parse_test.go +0 -203
  184. package/plugins/extract-go/aggregate.go +0 -214
  185. package/plugins/extract-go/client.go +0 -409
  186. package/plugins/extract-go/client_test.go +0 -266
  187. package/plugins/extract-go/describe.go +0 -19
  188. package/plugins/extract-go/describe_test.go +0 -11
  189. package/plugins/extract-go/enum.go +0 -195
  190. package/plugins/extract-go/enum_test.go +0 -82
  191. package/plugins/extract-go/event.go +0 -99
  192. package/plugins/extract-go/extract.go +0 -191
  193. package/plugins/extract-go/extract_test.go +0 -261
  194. package/plugins/extract-go/flow.go +0 -1441
  195. package/plugins/extract-go/flow_test.go +0 -609
  196. package/plugins/extract-go/httpclient.go +0 -174
  197. package/plugins/extract-go/httpclient_test.go +0 -296
  198. package/plugins/extract-go/ids.go +0 -92
  199. package/plugins/extract-go/layout.go +0 -225
  200. package/plugins/extract-go/layout_test.go +0 -125
  201. package/plugins/extract-go/lifecycle.go +0 -324
  202. package/plugins/extract-go/lifecycle_test.go +0 -108
  203. package/plugins/extract-go/main.go +0 -79
  204. package/plugins/extract-go/operation.go +0 -157
  205. package/plugins/extract-go/source.go +0 -316
  206. package/plugins/extract-go/transport.go +0 -321
  207. package/plugins/extract-go/transport_test.go +0 -145
  208. package/plugins/extract-go/wiring.go +0 -434
  209. package/plugins/extract-go-nats/describe.go +0 -19
  210. package/plugins/extract-go-nats/describe_test.go +0 -11
  211. package/plugins/extract-go-nats/extract.go +0 -177
  212. package/plugins/extract-go-nats/extract_test.go +0 -389
  213. package/plugins/extract-go-nats/index.go +0 -379
  214. package/plugins/extract-go-nats/main.go +0 -42
  215. package/plugins/extract-go-nats/resolve.go +0 -161
  216. package/plugins/extract-go-nats/sites.go +0 -224
  217. package/plugins/extract-graphql/describe.go +0 -19
  218. package/plugins/extract-graphql/describe_test.go +0 -11
  219. package/plugins/extract-graphql/extract.go +0 -433
  220. package/plugins/extract-graphql/extract_test.go +0 -256
  221. package/plugins/extract-graphql/ids.go +0 -49
  222. package/plugins/extract-graphql/lex.go +0 -237
  223. package/plugins/extract-graphql/main.go +0 -51
  224. package/plugins/extract-graphql/parse.go +0 -621
  225. package/plugins/extract-graphql/parse_test.go +0 -122
  226. package/plugins/extract-http-clients/describe.go +0 -19
  227. package/plugins/extract-http-clients/describe_test.go +0 -11
  228. package/plugins/extract-http-clients/extract.go +0 -705
  229. package/plugins/extract-http-clients/extract_test.go +0 -1263
  230. package/plugins/extract-http-clients/main.go +0 -42
  231. package/plugins/extract-openapi/describe.go +0 -19
  232. package/plugins/extract-openapi/describe_test.go +0 -11
  233. package/plugins/extract-openapi/discover.go +0 -243
  234. package/plugins/extract-openapi/extract.go +0 -526
  235. package/plugins/extract-openapi/extract_test.go +0 -545
  236. package/plugins/extract-openapi/main.go +0 -75
  237. package/plugins/extract-openapi/spec.go +0 -350
  238. package/plugins/extract-project/describe.go +0 -19
  239. package/plugins/extract-project/describe_test.go +0 -11
  240. package/plugins/extract-project/extract.go +0 -221
  241. package/plugins/extract-project/extract_test.go +0 -109
  242. package/plugins/extract-project/main.go +0 -41
  243. package/plugins/extract-proto/ast.go +0 -125
  244. package/plugins/extract-proto/consumes.go +0 -77
  245. package/plugins/extract-proto/describe.go +0 -19
  246. package/plugins/extract-proto/extract.go +0 -293
  247. package/plugins/extract-proto/extract_test.go +0 -459
  248. package/plugins/extract-proto/ids.go +0 -89
  249. package/plugins/extract-proto/ids_test.go +0 -57
  250. package/plugins/extract-proto/lex.go +0 -285
  251. package/plugins/extract-proto/main.go +0 -100
  252. package/plugins/extract-proto/module.go +0 -120
  253. package/plugins/extract-proto/parse.go +0 -720
  254. package/plugins/extract-proto/parse_test.go +0 -307
  255. package/plugins/extract-proto/provides.go +0 -236
  256. package/plugins/extract-proto/resolve.go +0 -222
  257. package/plugins/extract-proto/resolve_test.go +0 -119
  258. package/plugins/extract-redis/describe.go +0 -19
  259. package/plugins/extract-redis/describe_test.go +0 -11
  260. package/plugins/extract-redis/extract.go +0 -183
  261. package/plugins/extract-redis/extract_test.go +0 -168
  262. package/plugins/extract-redis/keyspaces.go +0 -469
  263. package/plugins/extract-redis/main.go +0 -44
  264. package/plugins/extract-river/describe.go +0 -19
  265. package/plugins/extract-river/describe_test.go +0 -11
  266. package/plugins/extract-river/extract.go +0 -507
  267. package/plugins/extract-river/extract_test.go +0 -132
  268. package/plugins/extract-river/main.go +0 -42
  269. package/plugins/extract-sql/ddl.go +0 -893
  270. package/plugins/extract-sql/ddl_test.go +0 -401
  271. package/plugins/extract-sql/describe.go +0 -19
  272. package/plugins/extract-sql/describe_test.go +0 -11
  273. package/plugins/extract-sql/layout.go +0 -221
  274. package/plugins/extract-sql/layout_test.go +0 -55
  275. package/plugins/extract-sql/lineage.go +0 -107
  276. package/plugins/extract-sql/main.go +0 -141
  277. package/plugins/extract-sql/maps.go +0 -564
  278. package/plugins/extract-sql/maps_java.go +0 -117
  279. package/plugins/extract-sql/maps_rust.go +0 -333
  280. package/plugins/extract-sql/maps_rust_test.go +0 -70
  281. package/plugins/extract-sql/maps_test.go +0 -204
  282. package/plugins/extract-sql/maps_ts.go +0 -398
  283. package/plugins/extract-sql/maps_ts_test.go +0 -136
  284. package/plugins/extract-sql/projection.go +0 -70
  285. package/plugins/extract-sql/projection_test.go +0 -50
  286. package/plugins/extract-sql/store.go +0 -420
  287. package/plugins/extract-sql/store_test.go +0 -233
  288. package/plugins/extract-sql/view.go +0 -295
  289. package/plugins/extract-watermill/describe.go +0 -19
  290. package/plugins/extract-watermill/describe_test.go +0 -11
  291. package/plugins/extract-watermill/extract.go +0 -1228
  292. package/plugins/extract-watermill/extract_test.go +0 -234
  293. package/plugins/extract-watermill/main.go +0 -41
  294. package/plugins/extract-wsdl/describe.go +0 -19
  295. package/plugins/extract-wsdl/describe_test.go +0 -11
  296. package/plugins/extract-wsdl/extract.go +0 -148
  297. package/plugins/extract-wsdl/extract_test.go +0 -45
  298. package/plugins/extract-wsdl/main.go +0 -53
  299. package/plugins/fetch-bsr/auth.go +0 -114
  300. package/plugins/fetch-bsr/bsr.go +0 -240
  301. package/plugins/fetch-bsr/cache.go +0 -66
  302. package/plugins/fetch-bsr/describe.go +0 -19
  303. package/plugins/fetch-bsr/fetch.go +0 -192
  304. package/plugins/fetch-bsr/fetch_test.go +0 -465
  305. package/plugins/fetch-bsr/lock.go +0 -71
  306. package/plugins/fetch-bsr/main.go +0 -117
  307. package/plugins/fetch-csr/auth.go +0 -95
  308. package/plugins/fetch-csr/cache.go +0 -69
  309. package/plugins/fetch-csr/describe.go +0 -19
  310. package/plugins/fetch-csr/describe_test.go +0 -11
  311. package/plugins/fetch-csr/fetch.go +0 -254
  312. package/plugins/fetch-csr/fetch_test.go +0 -494
  313. package/plugins/fetch-csr/lock.go +0 -95
  314. package/plugins/fetch-csr/main.go +0 -122
  315. package/plugins/fetch-csr/registry.go +0 -209
  316. package/plugins/fetch-git/cache.go +0 -65
  317. package/plugins/fetch-git/describe.go +0 -19
  318. package/plugins/fetch-git/describe_test.go +0 -11
  319. package/plugins/fetch-git/fetch.go +0 -181
  320. package/plugins/fetch-git/fetch_test.go +0 -332
  321. package/plugins/fetch-git/git.go +0 -169
  322. package/plugins/fetch-git/lock.go +0 -72
  323. package/plugins/fetch-git/main.go +0 -127
  324. package/plugins/fetch-git/offline.go +0 -39
  325. package/plugins/fetch-git/pin.go +0 -90
  326. package/plugins/fetch-git/pin_test.go +0 -74
  327. package/plugins/gen-backstage/describe.go +0 -17
  328. package/plugins/gen-backstage/main.go +0 -22
  329. package/plugins/gen-backstage/plugin.go +0 -473
  330. package/plugins/gen-backstage/plugin_test.go +0 -145
  331. package/plugins/gen-backstage.wasm +0 -0
  332. package/plugins/gen-markdown/adr.go +0 -162
  333. package/plugins/gen-markdown/aggregate.go +0 -362
  334. package/plugins/gen-markdown/canonical.go +0 -93
  335. package/plugins/gen-markdown/context.go +0 -90
  336. package/plugins/gen-markdown/coverage_test.go +0 -89
  337. package/plugins/gen-markdown/describe.go +0 -19
  338. package/plugins/gen-markdown/describe_test.go +0 -11
  339. package/plugins/gen-markdown/external.go +0 -71
  340. package/plugins/gen-markdown/flow.go +0 -277
  341. package/plugins/gen-markdown/glossary.go +0 -76
  342. package/plugins/gen-markdown/glossary_test.go +0 -148
  343. package/plugins/gen-markdown/llms.go +0 -302
  344. package/plugins/gen-markdown/main.go +0 -22
  345. package/plugins/gen-markdown/markdown.go +0 -254
  346. package/plugins/gen-markdown/markdown_test.go +0 -100
  347. package/plugins/gen-markdown/module.go +0 -107
  348. package/plugins/gen-markdown/plugin.go +0 -39
  349. package/plugins/gen-markdown/quality_test.go +0 -176
  350. package/plugins/gen-markdown/redis_test.go +0 -38
  351. package/plugins/gen-markdown/render.go +0 -344
  352. package/plugins/gen-markdown/render_test.go +0 -186
  353. package/plugins/gen-markdown/service.go +0 -456
  354. package/plugins/gen-markdown/source.go +0 -177
  355. package/plugins/gen-markdown/store.go +0 -201
  356. package/plugins/gen-markdown.wasm +0 -0
  357. package/plugins/gen-mermaid/describe.go +0 -17
  358. package/plugins/gen-mermaid/main.go +0 -22
  359. package/plugins/gen-mermaid/plugin.go +0 -104
  360. package/plugins/gen-mermaid/plugin_test.go +0 -33
  361. package/plugins/gen-mermaid.wasm +0 -0
  362. package/plugins/openapi/ids.go +0 -261
  363. package/plugins/openapi/ids_test.go +0 -98
  364. package/plugins/verify-codeowners/describe.go +0 -19
  365. package/plugins/verify-codeowners/describe_test.go +0 -11
  366. package/plugins/verify-codeowners/main.go +0 -75
  367. package/plugins/verify-codeowners/match.go +0 -85
  368. package/plugins/verify-codeowners/match_test.go +0 -47
  369. package/plugins/verify-codeowners/owners.go +0 -164
  370. package/plugins/verify-codeowners/owners_test.go +0 -225
  371. package/plugins/verify-codeowners/parse.go +0 -90
  372. package/plugins/verify-codeowners/parse_test.go +0 -62
  373. package/plugins/verify-otel/describe.go +0 -19
  374. package/plugins/verify-otel/describe_test.go +0 -11
  375. package/plugins/verify-otel/main.go +0 -53
  376. package/plugins/verify-otel/match.go +0 -336
  377. package/plugins/verify-otel/otlp.go +0 -200
  378. package/plugins/verify-otel/verify.go +0 -734
  379. package/plugins/verify-otel/verify_test.go +0 -460
  380. /package/{plugins/fetch-csr/options.schema.json → scripts/host-plugins/fetch-csr.options.json} +0 -0
  381. /package/{plugins/fetch-git/options.schema.json → scripts/host-plugins/fetch-git.options.json} +0 -0
@@ -1,405 +0,0 @@
1
- package main
2
-
3
- import (
4
- "strings"
5
- "testing"
6
-
7
- "github.com/shortlink-org/portolan/catalog"
8
- )
9
-
10
- const sample = `# auth.0003 — Session expiry publishes no event
11
-
12
- - **Status:** accepted
13
- - **Date:** 2026-08-22
14
- - **Scope:** auth.auth
15
- - **Supersedes:** auth.0001, auth.0002
16
- - **Relates:** auth.auth.session.SessionEnded, shop.cart, checkout
17
- - **Note:** how a revocation is kept out of the cache was decided again in
18
- auth.0010; the drop turned out not to be enough.
19
-
20
- ## Context and Problem Statement
21
-
22
- A session stops being usable at ` + "`ExpiresAt`" + `.
23
-
24
- ### An aside
25
-
26
- Kept as written.
27
-
28
- ## Decision Outcome
29
-
30
- No event.
31
- `
32
-
33
- func parsed(t *testing.T, src string) catalog.Adr {
34
- t.Helper()
35
-
36
- adr, errs := parseAdr("examples/auth/docs/adr/0003-expiry-publishes-nothing.md", src, defaults{})
37
- if len(errs) > 0 {
38
- t.Fatalf("errors: %s", strings.Join(errs, "\n"))
39
- }
40
-
41
- return adr
42
- }
43
-
44
- func refused(t *testing.T, src string) string {
45
- t.Helper()
46
-
47
- _, errs := parseAdr("examples/auth/docs/adr/0003-expiry-publishes-nothing.md", src, defaults{})
48
- if len(errs) == 0 {
49
- t.Fatal("parsed without complaint")
50
- }
51
-
52
- return strings.Join(errs, "\n")
53
- }
54
-
55
- // Replaces one meta bullet with another, so a test says what it is about and
56
- // nothing else.
57
- func with(bullet, replacement string) string {
58
- return strings.Replace(sample, bullet, replacement, 1)
59
- }
60
-
61
- func TestTheTitleCarriesTheIDAndTheTitle(t *testing.T) {
62
- adr := parsed(t, sample)
63
- if adr.ID != "auth.0003" || adr.Number != 3 || adr.Title != "Session expiry publishes no event" {
64
- t.Errorf("id %q, number %d, title %q", adr.ID, adr.Number, adr.Title)
65
- }
66
- }
67
-
68
- // The slug is the id with its dots opened out, then the file's own kebab: the
69
- // id says which record this is and the file's name says what it was about,
70
- // which is the slug the catalog carried when these were written by hand.
71
- func TestTheSlugIsTheIDAndTheFileName(t *testing.T) {
72
- adr := parsed(t, sample)
73
- if adr.Slug != "auth-0003-expiry-publishes-nothing" {
74
- t.Errorf("slug = %q", adr.Slug)
75
- }
76
- if adr.Source != "examples/auth/docs/adr/0003-expiry-publishes-nothing.md" {
77
- t.Errorf("source = %q", adr.Source)
78
- }
79
- }
80
-
81
- func TestAFileNumberedAwayFromItsRecordIsRefused(t *testing.T) {
82
- _, errs := parseAdr("docs/adr/0009-expiry-publishes-nothing.md", sample, defaults{})
83
- if len(errs) != 1 || !strings.Contains(errs[0], "numbered 0009") {
84
- t.Errorf("errors: %v", errs)
85
- }
86
- }
87
-
88
- func TestTheMetaBulletsAreRead(t *testing.T) {
89
- adr := parsed(t, sample)
90
- if adr.Status != catalog.AdrAccepted || adr.Date != "2026-08-22" {
91
- t.Errorf("status %q, date %q", adr.Status, adr.Date)
92
- }
93
- if adr.Scope != (catalog.AdrScope{Kind: "service", Service: "auth.auth"}) {
94
- t.Errorf("scope = %+v", adr.Scope)
95
- }
96
- if strings.Join(adr.Supersedes, ",") != "auth.0001,auth.0002" {
97
- t.Errorf("supersedes = %v", adr.Supersedes)
98
- }
99
- }
100
-
101
- // A bullet wrapped onto the next line is one bullet: the break is the author's
102
- // line width and carries no meaning, so it closes up into a space.
103
- func TestAWrappedBulletIsOneValue(t *testing.T) {
104
- adr := parsed(t, sample)
105
- want := "how a revocation is kept out of the cache was decided again in auth.0010; the drop turned out not to be enough."
106
- if adr.Note != want {
107
- t.Errorf("note = %q", adr.Note)
108
- }
109
- }
110
-
111
- // The scope's shape says which of the three kinds it is, and nothing else can:
112
- // an extractor sees one service's tree and has no catalog to look a name up in.
113
- func TestScopeKindComesFromTheNumberOfSegments(t *testing.T) {
114
- for _, c := range []struct {
115
- written string
116
- want catalog.AdrScope
117
- }{
118
- {"org", catalog.AdrScope{Kind: "org"}},
119
- {"", catalog.AdrScope{Kind: "org"}},
120
- {"payments", catalog.AdrScope{Kind: "context", Context: "payments"}},
121
- {"shop.cart", catalog.AdrScope{Kind: "service", Service: "shop.cart"}},
122
- } {
123
- adr := parsed(t, with("- **Scope:** auth.auth", "- **Scope:** "+c.written))
124
- if adr.Scope != c.want {
125
- t.Errorf("%q read as %+v, want %+v", c.written, adr.Scope, c.want)
126
- }
127
- }
128
- }
129
-
130
- // One list in the file, three in the catalog. An author should not have to
131
- // remember which of the three a name belongs in, so the name's shape says.
132
- func TestRelatesIsSortedByShape(t *testing.T) {
133
- adr := parsed(t, sample)
134
- if strings.Join(adr.Relates.Events, ",") != "auth.auth.session.SessionEnded" {
135
- t.Errorf("events = %v", adr.Relates.Events)
136
- }
137
- if strings.Join(adr.Relates.Services, ",") != "shop.cart" {
138
- t.Errorf("services = %v", adr.Relates.Services)
139
- }
140
- if strings.Join(adr.Relates.Flows, ",") != "checkout" {
141
- t.Errorf("flows = %v", adr.Relates.Flows)
142
- }
143
- }
144
-
145
- func TestARelationOfNoRecognisableShapeIsRefused(t *testing.T) {
146
- errs := refused(t, with("- **Relates:** auth.auth.session.SessionEnded, shop.cart, checkout", "- **Relates:** auth.auth.session"))
147
- if !strings.Contains(errs, "not the shape of an event, a service or a flow") {
148
- t.Errorf("errors: %s", errs)
149
- }
150
- }
151
-
152
- // The body is frozen history. It starts at the first `##` and is carried into
153
- // the catalog exactly as written, headings and all, because nothing on an ADR
154
- // page is ever regenerated from the model as it stands now.
155
- func TestTheBodyStartsAtTheFirstSectionAndIsUnedited(t *testing.T) {
156
- adr := parsed(t, sample)
157
- if !strings.HasPrefix(adr.Body, "## Context and Problem Statement\n") {
158
- t.Errorf("body starts %q", adr.Body[:40])
159
- }
160
- if !strings.Contains(adr.Body, "### An aside") || !strings.Contains(adr.Body, "`ExpiresAt`") {
161
- t.Errorf("body = %q", adr.Body)
162
- }
163
- if !strings.HasSuffix(adr.Body, "No event.\n") {
164
- t.Errorf("body ends %q", adr.Body[len(adr.Body)-20:])
165
- }
166
- }
167
-
168
- func TestWhatARecordCannotBeReadWithout(t *testing.T) {
169
- for key, bullet := range map[string]string{
170
- "Status": "- **Status:** accepted",
171
- "Date": "- **Date:** 2026-08-22",
172
- "Scope": "- **Scope:** auth.auth",
173
- } {
174
- errs := refused(t, strings.Replace(sample, bullet+"\n", "", 1))
175
- if !strings.Contains(errs, "says no \""+key+"\"") {
176
- t.Errorf("dropping %s gave: %s", key, errs)
177
- }
178
- }
179
- }
180
-
181
- func TestAStatusOrDateThatIsNeitherIsRefused(t *testing.T) {
182
- if errs := refused(t, with("- **Status:** accepted", "- **Status:** agreed")); !strings.Contains(errs, "is not a status") {
183
- t.Errorf("errors: %s", errs)
184
- }
185
- if errs := refused(t, with("- **Date:** 2026-08-22", "- **Date:** last August")); !strings.Contains(errs, "is not a date") {
186
- t.Errorf("errors: %s", errs)
187
- }
188
- }
189
-
190
- // Supersession is a two-way fact, and half of it recorded is a bug. Both
191
- // halves that live in one file are held against each other here; the halves in
192
- // two files are held against each other by extract.
193
- func TestHalfARecordedSupersessionIsRefused(t *testing.T) {
194
- errs := refused(t, with("- **Status:** accepted", "- **Status:** superseded"))
195
- if !strings.Contains(errs, "superseded and says by what") {
196
- t.Errorf("errors: %s", errs)
197
- }
198
-
199
- errs = refused(t, with("- **Supersedes:** auth.0001, auth.0002", "- **Superseded by:** auth.0007"))
200
- if !strings.Contains(errs, `its status is "accepted"`) {
201
- t.Errorf("errors: %s", errs)
202
- }
203
- }
204
-
205
- func TestABulletTheFormatDoesNotHaveIsRefused(t *testing.T) {
206
- errs := refused(t, with("- **Note:**", "- **Owner:**"))
207
- if !strings.Contains(errs, "is not one of the bullets a record carries") {
208
- t.Errorf("errors: %s", errs)
209
- }
210
- }
211
-
212
- // The head is bullets and nothing else. A paragraph that drifted above the
213
- // first `##` would be read by a person and dropped by the extractor, and the
214
- // record on the page would quietly be missing its opening.
215
- func TestProseBetweenTheTitleAndTheRecordIsRefused(t *testing.T) {
216
- errs := refused(t, with("## Context and Problem Statement", "A stray paragraph.\n\n## Context and Problem Statement"))
217
- if !strings.Contains(errs, "only meta bullets belong") {
218
- t.Errorf("errors: %s", errs)
219
- }
220
- }
221
-
222
- func TestARecordWithNoBodyIsRefused(t *testing.T) {
223
- errs := refused(t, "# auth.0003 — A title\n\n- **Status:** accepted\n- **Date:** 2026-08-22\n- **Scope:** auth.auth\n")
224
- if !strings.Contains(errs, "no body") {
225
- t.Errorf("errors: %s", errs)
226
- }
227
- }
228
-
229
- func TestATitleThatIsNotOneIsRefused(t *testing.T) {
230
- if errs := refused(t, strings.Replace(sample, "auth.0003 — Session", "auth.0003 - Session", 1)); !strings.Contains(errs, "em dash") {
231
- t.Errorf("errors: %s", errs)
232
- }
233
- if errs := refused(t, strings.Replace(sample, "auth.0003 —", "auth.3 —", 1)); !strings.Contains(errs, "four padded digits") {
234
- t.Errorf("errors: %s", errs)
235
- }
236
- }
237
-
238
- // Every mistake in a header, not the first one: a file with two typos in it
239
- // should be fixed once.
240
- func TestEveryMistakeIsReported(t *testing.T) {
241
- src := with("- **Status:** accepted", "- **Status:** agreed")
242
- src = strings.Replace(src, "- **Date:** 2026-08-22", "- **Date:** last August", 1)
243
- if _, errs := parseAdr("docs/adr/0003-expiry-publishes-nothing.md", src, defaults{}); len(errs) != 2 {
244
- t.Errorf("errors: %v", errs)
245
- }
246
- }
247
-
248
- // The other format: what adr-tools writes. The number and the title are on the
249
- // title line, the date is a line of its own, and the status is the record's
250
- // first section. The id's prefix and the scope come from the step, because
251
- // the format has no place for either.
252
- const toolsSample = `# 3. Use DTO
253
-
254
- Date: 2024-09-10
255
-
256
- ## Status
257
-
258
- Accepted
259
-
260
- ## Context
261
-
262
- Every supplier answers in its own shape.
263
-
264
- ## Decision
265
-
266
- Translate at the boundary.
267
- `
268
-
269
- func parsedTools(t *testing.T, src string, scope string) catalog.Adr {
270
- t.Helper()
271
-
272
- adr, errs := parseAdr("etg/aviasupp/docs/ADR/0003-use-dto.md", src, defaults{Scope: scope})
273
- if len(errs) > 0 {
274
- t.Fatalf("errors: %s", strings.Join(errs, "\n"))
275
- }
276
-
277
- return adr
278
- }
279
-
280
- func refusedTools(t *testing.T, src string) string {
281
- t.Helper()
282
-
283
- _, errs := parseAdr("etg/aviasupp/docs/ADR/0003-use-dto.md", src, defaults{Scope: "avia.aviasupp"})
284
- if len(errs) == 0 {
285
- t.Fatal("parsed without complaint")
286
- }
287
-
288
- return strings.Join(errs, "\n")
289
- }
290
-
291
- func TestAnAdrToolsRecordIsRead(t *testing.T) {
292
- adr := parsedTools(t, toolsSample, "avia.aviasupp")
293
- if adr.ID != "aviasupp.0003" || adr.Number != 3 || adr.Title != "Use DTO" {
294
- t.Errorf("id %q, number %d, title %q", adr.ID, adr.Number, adr.Title)
295
- }
296
- if adr.Slug != "aviasupp-0003-use-dto" {
297
- t.Errorf("slug = %q", adr.Slug)
298
- }
299
- if adr.Status != catalog.AdrAccepted || adr.Date != "2024-09-10" {
300
- t.Errorf("status %q, date %q", adr.Status, adr.Date)
301
- }
302
- if adr.Scope.Kind != "service" || adr.Scope.Service != "avia.aviasupp" {
303
- t.Errorf("scope = %+v", adr.Scope)
304
- }
305
- // The status section is part of the record, and the record is kept whole.
306
- if !strings.HasPrefix(adr.Body, "## Status\n\nAccepted\n\n## Context") {
307
- t.Errorf("body = %q", adr.Body)
308
- }
309
- }
310
-
311
- // The prefix follows the scope the way the estate's own records do: the
312
- // service, the context, or "org".
313
- func TestTheIDPrefixComesFromTheScope(t *testing.T) {
314
- for scope, id := range map[string]string{
315
- "avia.aviasupp": "aviasupp.0003",
316
- "payments": "payments.0003",
317
- "org": "org.0003",
318
- "": "org.0003",
319
- } {
320
- if adr := parsedTools(t, toolsSample, scope); adr.ID != id {
321
- t.Errorf("scope %q: id = %q", scope, adr.ID)
322
- }
323
- }
324
- if adr := parsedTools(t, toolsSample, ""); adr.Scope.Kind != "org" {
325
- t.Errorf("no scope: %+v", adr.Scope)
326
- }
327
- }
328
-
329
- // Supersession is written the way adr-tools writes it: in the Status section,
330
- // as a link to the other record, and the later statement wins.
331
- func TestAnAdrToolsSupersessionIsRead(t *testing.T) {
332
- src := strings.Replace(toolsSample, "Accepted\n", "Accepted\n\nSuperseded by [5. Use schemas](0005-use-schemas.md)\n", 1)
333
- adr := parsedTools(t, src, "avia.aviasupp")
334
- if adr.Status != catalog.AdrSuperseded || adr.SupersededBy != "aviasupp.0005" {
335
- t.Errorf("status %q, superseded by %q", adr.Status, adr.SupersededBy)
336
- }
337
-
338
- src = strings.Replace(toolsSample, "Accepted\n", "Accepted\n\nSupersedes [1. Record decisions](0001-record.md), ADR-0002\n", 1)
339
- adr = parsedTools(t, src, "avia.aviasupp")
340
- if strings.Join(adr.Supersedes, " ") != "aviasupp.0001" {
341
- t.Errorf("supersedes = %v", adr.Supersedes)
342
- }
343
-
344
- src = strings.Replace(toolsSample, "Accepted\n", "Accepted\n\nSupersedes ADR-0001 and 2\n", 1)
345
- if adr = parsedTools(t, src, "avia.aviasupp"); strings.Join(adr.Supersedes, " ") != "aviasupp.0001 aviasupp.0002" {
346
- t.Errorf("bare numbers: supersedes = %v", adr.Supersedes)
347
- }
348
- }
349
-
350
- func TestAStatusWordIsReadWhateverItsCase(t *testing.T) {
351
- for _, word := range []string{"accepted", "ACCEPTED", "Accepted."} {
352
- if adr := parsedTools(t, strings.Replace(toolsSample, "Accepted\n", word+"\n", 1), ""); adr.Status != catalog.AdrAccepted {
353
- t.Errorf("%q read as %q", word, adr.Status)
354
- }
355
- }
356
- }
357
-
358
- func TestWhatAnAdrToolsRecordCannotBeReadWithout(t *testing.T) {
359
- if errs := refusedTools(t, strings.Replace(toolsSample, "Date: 2024-09-10\n", "", 1)); !strings.Contains(errs, `no "Date:"`) {
360
- t.Errorf("errors: %s", errs)
361
- }
362
- if errs := refusedTools(t, strings.Replace(toolsSample, "## Status\n\nAccepted\n", "", 1)); !strings.Contains(errs, "no `## Status` section") {
363
- t.Errorf("errors: %s", errs)
364
- }
365
- if errs := refusedTools(t, strings.Replace(toolsSample, "Accepted", "Agreed", 1)); !strings.Contains(errs, `"Agreed" is not a status`) {
366
- t.Errorf("errors: %s", errs)
367
- }
368
- if errs := refusedTools(t, strings.Replace(toolsSample, "Accepted", "Superseded by a later one", 1)); !strings.Contains(errs, "says by which one record") {
369
- t.Errorf("errors: %s", errs)
370
- }
371
- if errs := refusedTools(t, strings.Replace(toolsSample, "Date: 2024-09-10", "Date: 2024-09-10\n\nWritten in a hurry.", 1)); !strings.Contains(errs, `only "Date:" belongs`) {
372
- t.Errorf("errors: %s", errs)
373
- }
374
- }
375
-
376
- // A MADR record that says no Scope is about whatever the step says its tree
377
- // is about, and only a record with neither is refused.
378
- func TestAMissingScopeFallsBackToTheStep(t *testing.T) {
379
- src := with("- **Scope:** auth.auth\n", "")
380
- adr, errs := parseAdr("examples/auth/docs/adr/0003-expiry-publishes-nothing.md", src, defaults{Scope: "auth"})
381
- if len(errs) > 0 {
382
- t.Fatalf("errors: %s", strings.Join(errs, "\n"))
383
- }
384
- if adr.Scope.Kind != "context" || adr.Scope.Context != "auth" {
385
- t.Errorf("scope = %+v", adr.Scope)
386
- }
387
- if errs := refused(t, src); !strings.Contains(errs, `no "Scope"`) {
388
- t.Errorf("errors: %s", errs)
389
- }
390
- }
391
-
392
- // A record with no number in its title at all - written by hand into an
393
- // adr-tools tree - is numbered by its file. A MADR title with the wrong dash
394
- // is not one of these: it opens with an id, and stays refused as a typo.
395
- func TestATitleWithNoNumberIsNumberedByItsFile(t *testing.T) {
396
- adr := parsedTools(t, strings.Replace(toolsSample, "# 3. Use DTO", "# Use DTO", 1), "avia.aviasupp")
397
- if adr.ID != "aviasupp.0003" || adr.Number != 3 || adr.Title != "Use DTO" {
398
- t.Errorf("id %q, number %d, title %q", adr.ID, adr.Number, adr.Title)
399
- }
400
-
401
- _, errs := parseAdr("docs/ADR/river.md", strings.Replace(toolsSample, "# 3. Use DTO", "# River", 1), defaults{})
402
- if len(errs) == 0 || !strings.Contains(errs[0], "nothing numbers the record") {
403
- t.Errorf("errors: %v", errs)
404
- }
405
- }
@@ -1,19 +0,0 @@
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-asyncapi",
15
- Summary: "Reads an AsyncAPI document and answers with the channels a service publishes on and listens to.",
16
- Phases: []string{plugin.PhaseExtract},
17
- Options: optionsSchema,
18
- }
19
- }
@@ -1,11 +0,0 @@
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
- }