@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,186 +0,0 @@
1
- package main
2
-
3
- import (
4
- "encoding/json"
5
- "flag"
6
- "os"
7
- "path/filepath"
8
- "sort"
9
- "strconv"
10
- "strings"
11
- "testing"
12
-
13
- "github.com/shortlink-org/portolan/catalog"
14
- "github.com/shortlink-org/portolan/plugin"
15
- )
16
-
17
- var update = flag.Bool("update", false, "rewrite the golden files instead of comparing against them")
18
-
19
- // TestGolden renders a small catalog that exercises every shape the schema has
20
- // - a shared type used twice, an event with two versions, a foreign key, a
21
- // view with lineage, and a flow with a loop around an alt around a parallel -
22
- // and compares the whole output tree against what is committed.
23
- //
24
- // The fixture is small on purpose. A golden test over the real catalog would
25
- // be 200 KB of diff on any change and nobody would read it.
26
- func TestGolden(t *testing.T) {
27
- raw, err := os.ReadFile("testdata/catalog.json")
28
- if err != nil {
29
- t.Fatal(err)
30
- }
31
-
32
- var cat catalog.Catalog
33
- if err := json.Unmarshal(raw, &cat); err != nil {
34
- t.Fatalf("the fixture does not fit the mirror: %v", err)
35
- }
36
-
37
- resp := render(plugin.Request{
38
- PortolanVersion: plugin.Version,
39
- Catalog: cat,
40
- }, Options{Title: "Golden estate"})
41
-
42
- if *update {
43
- if err := os.RemoveAll("testdata/golden"); err != nil {
44
- t.Fatal(err)
45
- }
46
- for _, file := range resp.Files {
47
- path := filepath.Join("testdata/golden", filepath.FromSlash(file.Name))
48
- if err := os.MkdirAll(filepath.Dir(path), 0o755); err != nil {
49
- t.Fatal(err)
50
- }
51
- if err := os.WriteFile(path, []byte(file.Contents), 0o644); err != nil {
52
- t.Fatal(err)
53
- }
54
- }
55
- t.Log("golden files rewritten")
56
-
57
- return
58
- }
59
-
60
- rendered := map[string]string{}
61
- for _, file := range resp.Files {
62
- rendered[file.Name] = file.Contents
63
- }
64
-
65
- for name, want := range goldenFiles(t) {
66
- got, ok := rendered[name]
67
- if !ok {
68
- t.Errorf("%s: in the golden files, no longer rendered", name)
69
-
70
- continue
71
- }
72
- if got != want {
73
- t.Errorf("%s: differs from the golden file\n%s", name, firstDifference(want, got))
74
- }
75
- delete(rendered, name)
76
- }
77
-
78
- for _, name := range sortedNames(rendered) {
79
- t.Errorf("%s: rendered, but not in the golden files", name)
80
- }
81
- }
82
-
83
- // TestGoldenDiagnostics pins what the plugin says it could not do. A render
84
- // that quietly stops noticing a dangling reference is the failure this catches.
85
- func TestGoldenDiagnostics(t *testing.T) {
86
- raw, err := os.ReadFile("testdata/catalog.json")
87
- if err != nil {
88
- t.Fatal(err)
89
- }
90
-
91
- var cat catalog.Catalog
92
- if err := json.Unmarshal(raw, &cat); err != nil {
93
- t.Fatal(err)
94
- }
95
-
96
- resp := render(plugin.Request{Catalog: cat}, Options{})
97
-
98
- // The fixture's one call outward lands on an external with a contract, so
99
- // nothing is unresolved and the render has nothing to report. The two
100
- // warnings that used to stand here were about that same call before the
101
- // gateway had a page.
102
- want := []string{}
103
-
104
- got := make([]string, 0, len(resp.Warnings()))
105
- for _, d := range resp.Warnings() {
106
- got = append(got, d.Ref+": "+d.Message)
107
- }
108
- sort.Strings(got)
109
- sort.Strings(want)
110
-
111
- if strings.Join(got, "\n") != strings.Join(want, "\n") {
112
- t.Errorf("diagnostics changed:\n got: %s\nwant: %s", strings.Join(got, "\n "), strings.Join(want, "\n "))
113
- }
114
- }
115
-
116
- func goldenFiles(t *testing.T) map[string]string {
117
- t.Helper()
118
-
119
- files := map[string]string{}
120
- err := filepath.WalkDir("testdata/golden", func(path string, entry os.DirEntry, err error) error {
121
- if err != nil || entry.IsDir() {
122
- return err
123
- }
124
-
125
- contents, err := os.ReadFile(path)
126
- if err != nil {
127
- return err
128
- }
129
-
130
- name, err := filepath.Rel("testdata/golden", path)
131
- if err != nil {
132
- return err
133
- }
134
- files[filepath.ToSlash(name)] = string(contents)
135
-
136
- return nil
137
- })
138
- if err != nil {
139
- t.Fatalf("reading golden files: %v (run `go test ./plugins/gen-markdown -update`)", err)
140
- }
141
- if len(files) == 0 {
142
- t.Fatal("no golden files; run `go test ./plugins/gen-markdown -update`")
143
- }
144
-
145
- return files
146
- }
147
-
148
- // firstDifference reports the first line that differs, with a little context.
149
- // A whole-file diff of a generated page is unreadable in test output.
150
- func firstDifference(want, got string) string {
151
- wantLines := strings.Split(want, "\n")
152
- gotLines := strings.Split(got, "\n")
153
-
154
- for i := 0; i < len(wantLines) || i < len(gotLines); i++ {
155
- w, g := at(wantLines, i), at(gotLines, i)
156
- if w == g {
157
- continue
158
- }
159
-
160
- return "line " + itoa(i+1) + ":\n want: " + w + "\n got: " + g
161
- }
162
-
163
- return "(files differ only in trailing content)"
164
- }
165
-
166
- func at(lines []string, i int) string {
167
- if i < len(lines) {
168
- return lines[i]
169
- }
170
-
171
- return "(end of file)"
172
- }
173
-
174
- func itoa(n int) string {
175
- return strconv.Itoa(n)
176
- }
177
-
178
- func sortedNames(files map[string]string) []string {
179
- names := make([]string, 0, len(files))
180
- for name := range files {
181
- names = append(names, name)
182
- }
183
- sort.Strings(names)
184
-
185
- return names
186
- }
@@ -1,456 +0,0 @@
1
- package main
2
-
3
- import (
4
- "path"
5
- "regexp"
6
- "strconv"
7
- "strings"
8
-
9
- "github.com/shortlink-org/portolan/catalog"
10
- )
11
-
12
- func (s *site) renderService(ctx *catalog.BoundedContext, svc *catalog.Service) {
13
- self := s.pathOf[svc.ID]
14
-
15
- var b strings.Builder
16
- b.WriteString("# " + svc.Name + "\n\n")
17
- b.WriteString(s.stamp() + "\n")
18
-
19
- groupLabel := "Context"
20
- if ctx.Kind != "" && ctx.Kind != catalog.GroupKindBoundedContext {
21
- groupLabel = "Group"
22
- }
23
- rows := [][]string{
24
- {"Id", code(svc.ID)},
25
- {groupLabel, s.ref(self, ctx.ID, ctx.Name)},
26
- {"Repo", repoLink(svc.Repo)},
27
- {"Path", s.source(self, strings.TrimSuffix(svc.Path, "/")+"/", svc)},
28
- }
29
- if svc.Kind != "" {
30
- rows = append(rows, []string{"Kind", string(svc.Kind)})
31
- }
32
- if len(svc.Technologies) > 0 {
33
- rows = append(rows, []string{"Technologies", strings.Join(svc.Technologies, ", ")})
34
- }
35
- // Who to ask, when the estate keeps a CODEOWNERS. Left out entirely when
36
- // it does not: a line reading "Owners: none" is a claim, and nobody made
37
- // it.
38
- if len(svc.Owners) > 0 {
39
- handles := make([]string, 0, len(svc.Owners))
40
- for _, handle := range svc.Owners {
41
- handles = append(handles, code(handle))
42
- }
43
- rows = append(rows, []string{"Owners", strings.Join(handles, ", ")})
44
- }
45
- b.WriteString(defList(rows))
46
-
47
- // The readme is a whole document of its own, so it goes in one level down
48
- // rather than being paraphrased.
49
- if readme := body(s.rewriteServiceReadmeLinks(self, ctx, svc), svc.Name); readme != "" {
50
- b.WriteString("\n" + readme + "\n")
51
- }
52
-
53
- if svc.Kind == "" || svc.Kind == catalog.ComponentKindService || len(svc.Aggregates) > 0 {
54
- section(&b, "Aggregates", s.aggregateTable(self, svc))
55
- }
56
- section(&b, "Provides", s.providesBlock(self, svc.Provides, svc))
57
- section(&b, "Consumes", s.consumesTable(self, svc))
58
- section(&b, "Publishes", s.publishesTable(self, svc))
59
- section(&b, "Channels", s.channelsBlock(self, svc))
60
- section(&b, "Schema modules", s.modulesTable(self, svc))
61
- section(&b, "Stores", s.storesTable(self, svc))
62
- section(&b, "Commands", s.commandsTable(self, svc))
63
- section(&b, "Decisions", s.adrTable(self, s.adrsFor[svc.ID]))
64
-
65
- s.b.file(self, b.String())
66
-
67
- for i := range svc.Aggregates {
68
- s.renderAggregate(svc, &svc.Aggregates[i])
69
- }
70
- }
71
-
72
- var markdownLink = regexp.MustCompile(`\]\(([^\s)]+)([^)]*)\)`)
73
-
74
- // rewriteServiceReadmeLinks keeps links copied from a service README useful
75
- // after that README moves into the generated tree. ADRs and glossaries have
76
- // canonical generated pages; linking back into the source tree would either
77
- // be broken or make a checked-in documentation bundle depend on its old
78
- // directory layout.
79
- func (s *site) rewriteServiceReadmeLinks(from string, ctx *catalog.BoundedContext, svc *catalog.Service) string {
80
- if svc.Readme == "" || svc.Path == "" {
81
- return svc.Readme
82
- }
83
-
84
- targets := map[string]string{}
85
- for i := range s.cat.Adrs {
86
- adr := &s.cat.Adrs[i]
87
- if generated, ok := s.pathOf[adr.ID]; ok {
88
- targets[sourceFile(adr.Source)] = generated
89
- // A source README may already point at a repository-level docs/
90
- // output. It still has to be made relative to its new page.
91
- targets[path.Join("docs", generated)] = generated
92
- }
93
- }
94
- if terms := s.termsOf[ctx.ID]; len(terms) > 0 {
95
- generated := s.glossaryPath(ctx)
96
- for _, term := range terms {
97
- targets[sourceFile(term.Source)] = generated
98
- }
99
- }
100
-
101
- readmeDir := path.Dir(path.Join(svc.Path, "README.md"))
102
- return markdownLink.ReplaceAllStringFunc(svc.Readme, func(match string) string {
103
- parts := markdownLink.FindStringSubmatch(match)
104
- if len(parts) != 3 {
105
- return match
106
- }
107
- destination, suffix := splitDestination(parts[1])
108
- if destination == "" || strings.HasPrefix(destination, "#") || strings.Contains(destination, "://") || strings.HasPrefix(destination, "mailto:") {
109
- return match
110
- }
111
-
112
- sourceTarget := path.Clean(path.Join(readmeDir, destination))
113
- generated, ok := targets[sourceTarget]
114
- if !ok {
115
- return match
116
- }
117
- return "](" + rel(from, generated) + suffix + parts[2] + ")"
118
- })
119
- }
120
-
121
- func sourceFile(source string) string {
122
- if at := strings.LastIndex(source, ":"); at > 0 {
123
- if isDigits(source[at+1:]) {
124
- return source[:at]
125
- }
126
- }
127
- return source
128
- }
129
-
130
- func isDigits(value string) bool {
131
- if value == "" {
132
- return false
133
- }
134
- for _, char := range value {
135
- if char < '0' || char > '9' {
136
- return false
137
- }
138
- }
139
- return true
140
- }
141
-
142
- func splitDestination(destination string) (string, string) {
143
- if at := strings.IndexAny(destination, "#?"); at >= 0 {
144
- return destination[:at], destination[at:]
145
- }
146
- return destination, ""
147
- }
148
-
149
- func (s *site) aggregateTable(from string, svc *catalog.Service) string {
150
- rows := make([][]string, 0, len(svc.Aggregates))
151
- for i := range svc.Aggregates {
152
- agg := &svc.Aggregates[i]
153
-
154
- commands, queries := 0, 0
155
- for _, op := range agg.Operations {
156
- if op.Kind == catalog.OperationQuery {
157
- queries++
158
- } else {
159
- commands++
160
- }
161
- }
162
-
163
- rows = append(rows, []string{
164
- s.ref(from, agg.ID, agg.Name),
165
- code(agg.Root),
166
- plural(commands, "command"),
167
- plural(queries, "query", "queries"),
168
- plural(len(agg.Events), "event"),
169
- })
170
- }
171
-
172
- return table([]string{"Aggregate", "Root", "Commands", "Queries", "Events"}, rows)
173
- }
174
-
175
- // providesBlock lists interfaces and their methods, for a service of the
176
- // estate and for a system outside it alike: what something answers on is the
177
- // same kind of fact whoever owns the far end.
178
- func (s *site) providesBlock(from string, provides []catalog.RpcService, owner *catalog.Service) string {
179
- var b strings.Builder
180
-
181
- for i := range provides {
182
- rpc := &provides[i]
183
- b.WriteString("### " + rpc.ID + "\n\n")
184
- meta := [][]string{{"Source", s.source(from, rpc.Source, owner)}}
185
- if rpc.Module != "" {
186
- meta = append(meta, []string{"Module", s.ref(from, rpc.Module, rpc.Module)})
187
- }
188
- b.WriteString(defList(meta))
189
-
190
- methods := make([][]string, 0, len(rpc.Methods))
191
- for j := range rpc.Methods {
192
- method := &rpc.Methods[j]
193
- route := ""
194
- if method.HTTP != nil {
195
- route = code(method.HTTP.Method + " " + method.HTTP.Path)
196
- }
197
- if method.SOAP != nil {
198
- soap := "SOAP"
199
- if method.SOAP.Version != "" {
200
- soap += " " + method.SOAP.Version
201
- }
202
- if method.SOAP.Action != "" {
203
- soap += " " + method.SOAP.Action
204
- }
205
- route = code(soap)
206
- }
207
- streaming := string(method.Streaming)
208
- if method.Deprecated {
209
- streaming = strings.TrimSpace(streaming + " deprecated")
210
- }
211
- doc := method.Doc
212
- if method.SOAP != nil {
213
- var details []string
214
- if method.SOAP.Endpoint != "" {
215
- details = append(details, "endpoint "+code(method.SOAP.Endpoint))
216
- }
217
- if method.SOAP.Binding != "" {
218
- details = append(details, "binding "+code(method.SOAP.Binding))
219
- }
220
- if method.SOAP.Style != "" {
221
- details = append(details, method.SOAP.Style)
222
- }
223
- if len(method.SOAP.Headers) > 0 {
224
- details = append(details, "headers "+code(strings.Join(method.SOAP.Headers, ", ")))
225
- }
226
- if len(method.SOAP.Faults) > 0 {
227
- details = append(details, "faults "+code(strings.Join(method.SOAP.Faults, ", ")))
228
- }
229
- if len(details) > 0 {
230
- doc = strings.TrimSpace(strings.TrimSpace(doc) + " " + strings.Join(details, "; "))
231
- }
232
- }
233
- methods = append(methods, []string{
234
- code(method.Name), route, s.methodShape(from, method.Request, method.RequestRef),
235
- s.methodShape(from, method.Response, method.ResponseRef), streaming, doc,
236
- })
237
- }
238
- if rendered := table([]string{"Method", "Route", "Request", "Response", "Mode", "Doc"}, methods); rendered != "" {
239
- b.WriteString("\n" + rendered)
240
- }
241
-
242
- for j := range rpc.Messages {
243
- msg := &rpc.Messages[j]
244
- b.WriteString("\n<a id=\"message-" + anchorID(msg.Name) + "\"></a>\n")
245
- b.WriteString("<details><summary>" + msg.Name + "</summary>\n\n")
246
- if msg.Discriminator != nil {
247
- b.WriteString("Discriminator " + code(msg.Discriminator.Property) + ": ")
248
- variants := make([]string, 0, len(msg.Discriminator.Variants))
249
- for _, variant := range msg.Discriminator.Variants {
250
- variants = append(variants, code(variant.Value)+" → "+code(variant.Message))
251
- }
252
- b.WriteString(strings.Join(variants, ", ") + "\n\n")
253
- }
254
- b.WriteString(s.fieldTable(from, msg.Fields))
255
- b.WriteString("\n</details>\n")
256
- }
257
- for j := range rpc.Enums {
258
- set := &rpc.Enums[j]
259
- b.WriteString("\n<a id=\"enum-" + anchorID(set.Name) + "\"></a>\n")
260
- b.WriteString("<details><summary>" + set.Name + " (enum)</summary>\n\n")
261
- if set.Doc != "" {
262
- b.WriteString(set.Doc + "\n\n")
263
- }
264
- rows := make([][]string, 0, len(set.Values))
265
- for _, value := range set.Values {
266
- rows = append(rows, []string{code(value.Name), strconv.Itoa(value.Number), value.Doc})
267
- }
268
- b.WriteString(table([]string{"Value", "Number", "Doc"}, rows))
269
- b.WriteString("\n</details>\n")
270
- }
271
- b.WriteString("\n")
272
- }
273
-
274
- return b.String()
275
- }
276
-
277
- func (s *site) methodShape(from, local, shared string) string {
278
- if shared != "" {
279
- return s.defRef(from, shared)
280
- }
281
- if local != "" {
282
- return code(local)
283
- }
284
- return ""
285
- }
286
-
287
- func (s *site) consumesTable(from string, svc *catalog.Service) string {
288
- rows := make([][]string, 0, len(svc.Consumes))
289
- for i := range svc.Consumes {
290
- call := &svc.Consumes[i]
291
- rows = append(rows, []string{
292
- code(call.ID),
293
- s.ref(from, call.Peer, call.Peer),
294
- s.ref(from, call.Module, call.Module),
295
- string(call.Status),
296
- s.source(from, call.Source, svc),
297
- s.viaRef(from, call.Via),
298
- call.Note,
299
- })
300
-
301
- if call.Status == catalog.StatusUnresolved {
302
- s.b.warn(svc.ID, "%s calls %q, which nothing in this catalog resolves", svc.ID, call.ID)
303
- }
304
- }
305
-
306
- return table([]string{"Call", "Peer", "Module", "Status", "Source", "Via", "Note"}, rows)
307
- }
308
-
309
- func (s *site) channelsBlock(from string, svc *catalog.Service) string {
310
- var b strings.Builder
311
- for i := range svc.Channels {
312
- channel := &svc.Channels[i]
313
- b.WriteString("### " + channel.Address + "\n\n")
314
- if channel.Kind == catalog.ChannelKindJob {
315
- b.WriteString("`work queue`\n\n")
316
- } else if channel.Kind == catalog.ChannelKindMessage {
317
- b.WriteString("`message stream`\n\n")
318
- }
319
- if channel.Title != "" {
320
- b.WriteString("**" + channel.Title + "**\n\n")
321
- }
322
- if channel.Doc != "" {
323
- b.WriteString(channel.Doc + "\n\n")
324
- }
325
- if channel.Source != "" {
326
- b.WriteString("Source: " + s.source(from, channel.Source, svc) + "\n\n")
327
- }
328
- rows := make([][]string, 0, len(channel.Messages))
329
- for j := range channel.Messages {
330
- message := &channel.Messages[j]
331
- name := code(message.Name)
332
- if eventID := s.wireEvent[message.Name]; eventID != "" {
333
- name = s.eventRef(from, eventID, message.Name)
334
- }
335
- rows = append(rows, []string{string(message.Direction), name, message.Title, message.Doc})
336
- }
337
- if rendered := table([]string{"Direction", "Message", "Title", "Doc"}, rows); rendered != "" {
338
- b.WriteString(rendered)
339
- }
340
- b.WriteString("\n")
341
- }
342
- return b.String()
343
- }
344
-
345
- func (s *site) modulesTable(from string, svc *catalog.Service) string {
346
- rows := make([][]string, 0, len(svc.Modules))
347
- for _, id := range svc.Modules {
348
- module, ok := s.modules[id]
349
- if !ok {
350
- rows = append(rows, []string{code(id), "unknown", "", ""})
351
- continue
352
- }
353
- access := "reads"
354
- if module.Owner == svc.ID {
355
- access = "publishes"
356
- }
357
- rows = append(rows, []string{s.ref(from, id, module.Name), access, module.Commit, strings.Join(module.Packages, ", ")})
358
- }
359
- return table([]string{"Module", "Access", "Commit", "Packages"}, rows)
360
- }
361
-
362
- // publishesTable is the service's events gathered from every aggregate, which
363
- // is the shape a reader integrating with the service wants: they do not care
364
- // which aggregate inside it produced the event.
365
- func (s *site) publishesTable(from string, svc *catalog.Service) string {
366
- var rows [][]string
367
- for i := range svc.Aggregates {
368
- agg := &svc.Aggregates[i]
369
- for j := range agg.Events {
370
- event := &agg.Events[j]
371
-
372
- consumers := make([]string, 0, len(event.Consumers))
373
- for _, consumer := range event.Consumers {
374
- text := consumer.Service
375
- if consumer.Status != catalog.StatusVerified {
376
- text += " (" + string(consumer.Status) + ")"
377
- }
378
- consumers = append(consumers, s.ref(from, consumer.Service, text))
379
- }
380
-
381
- latest := ""
382
- if len(event.Versions) > 0 {
383
- latest = event.Versions[len(event.Versions)-1].Version
384
- }
385
-
386
- rows = append(rows, []string{
387
- s.eventRef(from, event.ID, event.Name),
388
- latest,
389
- strings.Join(consumers, ", "),
390
- })
391
- }
392
- }
393
-
394
- return table([]string{"Event", "Latest", "Consumers"}, rows)
395
- }
396
-
397
- // storesTable separates what the service owns from what it only reads. The
398
- // catalog does not state the difference on the service - a store names its own
399
- // owner - so it is worked out here rather than asked of the reader.
400
- func (s *site) storesTable(from string, svc *catalog.Service) string {
401
- rows := make([][]string, 0, len(svc.Stores))
402
- for _, id := range svc.Stores {
403
- store, known := s.stores[id]
404
- if !known {
405
- rows = append(rows, []string{code(id), "—", "unknown", "—"})
406
- s.b.warn(svc.ID, "%s touches store %q, which is not in this catalog", svc.ID, id)
407
-
408
- continue
409
- }
410
-
411
- access := "reads"
412
- if store.Owner == svc.ID {
413
- access = "owns"
414
- }
415
- schema := plural(len(store.Tables), "table")
416
- if len(store.Keyspaces) > 0 {
417
- schema = plural(len(store.Keyspaces), "key pattern")
418
- }
419
- rows = append(rows, []string{
420
- s.ref(from, store.ID, store.Name),
421
- string(store.Kind),
422
- access,
423
- schema,
424
- })
425
- }
426
-
427
- return table([]string{"Store", "Kind", "Access", "Schema"}, rows)
428
- }
429
-
430
- // commandsTable is what a developer types against the checkout, one row per
431
- // entry of the runner files. The line to type comes first, because it is the
432
- // one cell a reader copies; the body is what it does when the file says
433
- // nothing about it, and is folded to its first line so a build script does
434
- // not become the table.
435
- func (s *site) commandsTable(from string, svc *catalog.Service) string {
436
- rows := make([][]string, 0, len(svc.Commands))
437
- for _, cmd := range svc.Commands {
438
- rows = append(rows, []string{
439
- code(cmd.Run),
440
- cmd.Doc,
441
- code(bodyLine(cmd.Body)),
442
- s.source(from, cmd.Source, svc),
443
- })
444
- }
445
-
446
- return table([]string{"Run", "Does", "Body", "Source"}, rows)
447
- }
448
-
449
- func bodyLine(s string) string {
450
- line, rest, more := strings.Cut(s, "\n")
451
- if more && strings.TrimSpace(rest) != "" {
452
- return line + " …"
453
- }
454
-
455
- return line
456
- }