@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,350 +0,0 @@
1
- package main
2
-
3
- import (
4
- "os"
5
- "path/filepath"
6
- "strconv"
7
- "strings"
8
-
9
- "gopkg.in/yaml.v3"
10
- )
11
-
12
- // The document is read as a node tree rather than into structs.
13
- //
14
- // Two reasons, and both are about the reader of the output. A YAML mapping
15
- // unmarshalled into a Go map loses its order, and the order paths and schemas
16
- // are written in is the author's - alphabetising it would be a worse document
17
- // than the one they wrote. And a path item holds keys that are not verbs, and a
18
- // schema holds keywords this does not model; walking the nodes means ignoring
19
- // them rather than failing on them.
20
-
21
- // verbs, in the order a reader expects to meet them rather than the order they
22
- // happen to appear.
23
- var verbs = []string{"get", "post", "put", "patch", "delete", "head", "options", "trace"}
24
-
25
- type document struct {
26
- root *yaml.Node
27
- path string
28
- cache map[string]*document
29
- }
30
-
31
- func load(path string) (*document, error) {
32
- cache := map[string]*document{}
33
- return loadCached(path, cache)
34
- }
35
-
36
- func loadCached(path string, cache map[string]*document) (*document, error) {
37
- path = filepath.Clean(path)
38
- if held := cache[path]; held != nil {
39
- return held, nil
40
- }
41
- contents, err := os.ReadFile(path)
42
- if err != nil {
43
- return nil, err
44
- }
45
-
46
- var root yaml.Node
47
- if err := yaml.Unmarshal(contents, &root); err != nil {
48
- return nil, err
49
- }
50
-
51
- // A document node wraps the mapping.
52
- node := &root
53
- if node.Kind == yaml.DocumentNode && len(node.Content) > 0 {
54
- node = node.Content[0]
55
- }
56
-
57
- doc := &document{root: node, path: path, cache: cache}
58
- cache[path] = doc
59
- return doc, nil
60
- }
61
-
62
- // entry is one key/value pair of a mapping, in document order.
63
- type entry struct {
64
- key string
65
- value *yaml.Node
66
- }
67
-
68
- func entries(node *yaml.Node) []entry {
69
- if node == nil || node.Kind != yaml.MappingNode {
70
- return nil
71
- }
72
-
73
- out := make([]entry, 0, len(node.Content)/2)
74
- for i := 0; i+1 < len(node.Content); i += 2 {
75
- out = append(out, entry{key: node.Content[i].Value, value: node.Content[i+1]})
76
- }
77
-
78
- return out
79
- }
80
-
81
- // child follows a path of mapping keys, and answers nil for anything missing.
82
- func child(node *yaml.Node, keys ...string) *yaml.Node {
83
- for _, key := range keys {
84
- found := false
85
- for _, e := range entries(node) {
86
- if e.key == key {
87
- node = e.value
88
- found = true
89
-
90
- break
91
- }
92
- }
93
- if !found {
94
- return nil
95
- }
96
- }
97
-
98
- return node
99
- }
100
-
101
- func text(node *yaml.Node) string {
102
- if node == nil {
103
- return ""
104
- }
105
-
106
- return strings.TrimSpace(node.Value)
107
- }
108
-
109
- func list(node *yaml.Node) []string {
110
- if node == nil || node.Kind != yaml.SequenceNode {
111
- return nil
112
- }
113
-
114
- out := make([]string, 0, len(node.Content))
115
- for _, item := range node.Content {
116
- out = append(out, item.Value)
117
- }
118
-
119
- return out
120
- }
121
-
122
- // Where a document keeps its named schemas: under components in OpenAPI 3, and
123
- // under definitions in Swagger 2.0. A reference is a pointer to one of these
124
- // two places, and the name is what follows.
125
- var schemaHomes = []string{"components/schemas/", "definitions/"}
126
-
127
- type schemaRef struct {
128
- doc *document
129
- node *yaml.Node
130
- name string
131
- key string
132
- }
133
-
134
- // schemaRefs collects every component schema a subtree names, in the order they
135
- // are met.
136
- //
137
- // A $ref that points at another component - a shared response, a parameter - is
138
- // followed rather than ignored, because that is how a spec says "the same error
139
- // body as everywhere else", and a reader who is not shown it is being told this
140
- // endpoint returns nothing on failure.
141
- func (d *document) schemaRefs(node *yaml.Node, into *[]schemaRef, seen, visited map[string]bool) {
142
- if node == nil {
143
- return
144
- }
145
-
146
- switch node.Kind {
147
- case yaml.MappingNode:
148
- for _, e := range entries(node) {
149
- if e.key != "$ref" {
150
- d.schemaRefs(e.value, into, seen, visited)
151
-
152
- continue
153
- }
154
-
155
- ref := text(e.value)
156
- targetDoc, target, pointer, ok := d.resolve(ref)
157
- if !ok {
158
- continue
159
- }
160
- key := targetDoc.path + "#" + pointer
161
- if name, schema := schemaName(pointer); schema {
162
- if !seen[key] {
163
- seen[key] = true
164
- *into = append(*into, schemaRef{doc: targetDoc, node: target, name: name, key: key})
165
- }
166
- continue
167
- }
168
- if visited[key] {
169
- continue
170
- }
171
- visited[key] = true
172
- targetDoc.schemaRefs(target, into, seen, visited)
173
- }
174
- case yaml.SequenceNode:
175
- for _, item := range node.Content {
176
- d.schemaRefs(item, into, seen, visited)
177
- }
178
- }
179
- }
180
-
181
- // shapes are what an operation sends and what it answers with, by the names the
182
- // document gives them.
183
- //
184
- // A body behind a $ref has a name a reader can look up in Messages; an inline
185
- // schema has none, and inventing one would put a shape in the catalog the
186
- // document does not have. A success with no body at all is answered by its
187
- // status: 204 is a real answer, and saying nothing for it reads as a call that
188
- // never came back.
189
- func (d *document) shapes(item, operation *yaml.Node) (string, string) {
190
- request := d.bodyName(child(operation, "requestBody"))
191
- if request == "" {
192
- request = d.bodyParameter(operation, item)
193
- }
194
-
195
- code, body := "", (*yaml.Node)(nil)
196
- for _, e := range entries(child(operation, "responses")) {
197
- status, err := strconv.Atoi(e.key)
198
- if err != nil || status < 200 || status > 299 {
199
- continue
200
- }
201
- if code == "" || e.key < code {
202
- code, body = e.key, e.value
203
- }
204
- }
205
- if body == nil {
206
- return request, ""
207
- }
208
- if name := d.bodyName(body); name != "" {
209
- return request, name
210
- }
211
-
212
- return request, code
213
- }
214
-
215
- // bodyName is the component schema a request or response body names, following
216
- // one $ref to a shared component on the way. A list of a schema is that schema
217
- // with brackets: the message is the item, and the reader wants to see there are
218
- // several.
219
- //
220
- // OpenAPI 3 puts the schema under a media type; Swagger 2.0 puts it straight on
221
- // the response. Both are read, so a document generated by a tool that still
222
- // writes 2.0 - swag, for one - names its shapes like any other.
223
- func (d *document) bodyName(node *yaml.Node) string {
224
- bodyDoc, body := d.follow(node)
225
- for _, e := range entries(child(body, "content")) {
226
- mediaDoc, media := bodyDoc.follow(e.value)
227
- schema := child(media, "schema")
228
- if name := mediaDoc.schemaTypeName(schema); name != "" {
229
- return name
230
- }
231
- }
232
- if schema := child(body, "schema"); schema != nil {
233
- return bodyDoc.schemaTypeName(schema)
234
- }
235
-
236
- return ""
237
- }
238
-
239
- // bodyParameter is the Swagger 2.0 request body: a parameter with `in: body`,
240
- // on the operation or inherited from its path item, whose schema is the shape.
241
- // The operation's own list is read first, because it is the one that wins when
242
- // both declare one.
243
- func (d *document) bodyParameter(operation, item *yaml.Node) string {
244
- for _, owner := range []*yaml.Node{operation, item} {
245
- for _, param := range itemsOf(child(owner, "parameters")) {
246
- paramDoc, param := d.follow(param)
247
- if text(child(param, "in")) != "body" {
248
- continue
249
- }
250
- return paramDoc.schemaTypeName(child(param, "schema"))
251
- }
252
- }
253
-
254
- return ""
255
- }
256
-
257
- func (d *document) schemaTypeName(node *yaml.Node) string {
258
- if _, _, pointer, ok := d.resolve(text(child(node, "$ref"))); ok {
259
- if name, schema := schemaName(pointer); schema {
260
- return name
261
- }
262
- }
263
- if text(child(node, "type")) == "array" {
264
- if name := d.schemaTypeName(child(node, "items")); name != "" {
265
- return name + "[]"
266
- }
267
- }
268
- for _, keyword := range []string{"oneOf", "anyOf"} {
269
- var names []string
270
- for _, branch := range itemsOf(child(node, keyword)) {
271
- if name := d.schemaTypeName(branch); name != "" {
272
- names = append(names, name)
273
- }
274
- }
275
- if len(names) > 0 {
276
- return strings.Join(unique(names), " | ")
277
- }
278
- }
279
- return ""
280
- }
281
-
282
- // follow resolves chains of local or relative-file component references.
283
- func (d *document) follow(node *yaml.Node) (*document, *yaml.Node) {
284
- seen := map[string]bool{}
285
- for {
286
- ref := text(child(node, "$ref"))
287
- nextDoc, next, pointer, ok := d.resolve(ref)
288
- if !ok {
289
- return d, node
290
- }
291
- key := nextDoc.path + "#" + pointer
292
- if seen[key] {
293
- return d, node
294
- }
295
- seen[key] = true
296
- d, node = nextDoc, next
297
- }
298
- }
299
-
300
- // resolve follows JSON Pointer refs in this document or in a relative YAML/
301
- // JSON file. Remote refs are intentionally not fetched by an extractor.
302
- func (d *document) resolve(ref string) (*document, *yaml.Node, string, bool) {
303
- if ref == "" || strings.HasPrefix(ref, "http://") || strings.HasPrefix(ref, "https://") {
304
- return nil, nil, "", false
305
- }
306
- file, fragment, hasFragment := strings.Cut(ref, "#")
307
- if !hasFragment {
308
- return nil, nil, "", false
309
- }
310
- target := d
311
- if file != "" {
312
- loaded, err := loadCached(filepath.Join(filepath.Dir(d.path), filepath.FromSlash(file)), d.cache)
313
- if err != nil {
314
- return nil, nil, "", false
315
- }
316
- target = loaded
317
- }
318
- pointer := strings.TrimPrefix(fragment, "/")
319
- if pointer == "" {
320
- return target, target.root, pointer, true
321
- }
322
- parts := strings.Split(pointer, "/")
323
- for i := range parts {
324
- parts[i] = strings.ReplaceAll(strings.ReplaceAll(parts[i], "~1", "/"), "~0", "~")
325
- }
326
- node := child(target.root, parts...)
327
- return target, node, pointer, node != nil
328
- }
329
-
330
- func schemaName(pointer string) (string, bool) {
331
- for _, home := range schemaHomes {
332
- name, ok := strings.CutPrefix(pointer, home)
333
- if !ok || name == "" || strings.Contains(name, "/") {
334
- continue
335
- }
336
- return strings.ReplaceAll(strings.ReplaceAll(name, "~1", "/"), "~0", "~"), true
337
- }
338
- return "", false
339
- }
340
-
341
- // refName is the name a reference spells even when it points at nothing: a
342
- // document that names a schema it never defines still tells the reader what the
343
- // author called it.
344
- func refName(ref string) (string, bool) {
345
- pointer, ok := strings.CutPrefix(ref, "#/")
346
- if !ok {
347
- return "", false
348
- }
349
- return schemaName(pointer)
350
- }
@@ -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-project",
15
- Summary: "Reads repository metadata and describes a group and component without assuming DDD or a particular language.",
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
- }
@@ -1,221 +0,0 @@
1
- package main
2
-
3
- import (
4
- "encoding/json"
5
- "os"
6
- "path/filepath"
7
- "regexp"
8
- "strings"
9
-
10
- "github.com/shortlink-org/portolan/catalog"
11
- "github.com/shortlink-org/portolan/internal/commands"
12
- "github.com/shortlink-org/portolan/plugin"
13
- )
14
-
15
- var packageRepository = regexp.MustCompile(`(?m)"repository"\s*:\s*(?:"([^"]+)"|\{[^}]*"url"\s*:\s*"([^"]+)")`)
16
- var cargoName = regexp.MustCompile(`(?m)^name\s*=\s*"([^"]+)"`)
17
-
18
- func extract(in plugin.Input, opts Options) (plugin.Response, error) {
19
- root := in.Root
20
- base := slug(filepath.Base(root))
21
- group := firstNonEmpty(slug(opts.Group), base, "project")
22
- component := firstNonEmpty(slug(opts.Component), base, "component")
23
- readme := read(filepath.Join(root, "README.md"))
24
- b := &plugin.Builder{}
25
- cmds, warnings := commands.Read(root)
26
- for _, warning := range warnings {
27
- b.Warn(root, warning)
28
- }
29
-
30
- fragment := catalog.Catalog{
31
- GeneratedAt: in.GeneratedAt,
32
- Commit: in.Commit,
33
- Contexts: []catalog.BoundedContext{{
34
- ID: group,
35
- Slug: group,
36
- Name: firstNonEmpty(opts.GroupName, title(group)),
37
- Summary: opts.GroupSummary,
38
- Kind: catalog.GroupKind(firstNonEmpty(opts.GroupKind, string(catalog.GroupKindSystem))),
39
- Services: []catalog.Service{{
40
- ID: group + "." + component,
41
- Slug: component,
42
- Name: firstNonEmpty(opts.ComponentName, markdownTitle(readme), title(component)),
43
- Repo: firstNonEmpty(opts.Repo, repository(root)),
44
- Path: filepath.ToSlash(root),
45
- Readme: readme,
46
- Kind: catalog.ComponentKind(firstNonEmpty(opts.ComponentKind, inferredKind(root))),
47
- Technologies: stated(opts.Technologies, technologies(root)),
48
- Provides: []catalog.RpcService{},
49
- Consumes: []catalog.RpcCall{},
50
- Aggregates: []catalog.Aggregate{},
51
- Commands: cmds,
52
- }},
53
- }},
54
- Defs: map[string]catalog.TypeDef{},
55
- Flows: []catalog.Flow{},
56
- Adrs: []catalog.Adr{},
57
- }
58
-
59
- encoded, err := json.MarshalIndent(fragment, "", " ")
60
- if err != nil {
61
- return plugin.Response{}, err
62
- }
63
- b.File(firstNonEmpty(opts.Out, "project.json"), string(encoded)+"\n")
64
- return b.Response(), nil
65
- }
66
-
67
- func repository(root string) string {
68
- for _, line := range strings.Split(read(filepath.Join(root, "go.mod")), "\n") {
69
- if value, ok := strings.CutPrefix(strings.TrimSpace(line), "module "); ok {
70
- return strings.TrimSpace(value)
71
- }
72
- }
73
- if match := packageRepository.FindStringSubmatch(read(filepath.Join(root, "package.json"))); len(match) > 0 {
74
- return strings.TrimSuffix(firstNonEmpty(match[1], match[2]), ".git")
75
- }
76
- if match := cargoName.FindStringSubmatch(read(filepath.Join(root, "Cargo.toml"))); len(match) > 0 {
77
- return match[1]
78
- }
79
- return ""
80
- }
81
-
82
- func inferredKind(root string) string {
83
- if exists(filepath.Join(root, "cmd")) || exists(filepath.Join(root, "Dockerfile")) || exists(filepath.Join(root, "docker-compose.yml")) || exists(filepath.Join(root, "docker-compose.yaml")) {
84
- return string(catalog.ComponentKindApplication)
85
- }
86
- return string(catalog.ComponentKindLibrary)
87
- }
88
-
89
- func technologies(root string) []string {
90
- goMod := read(filepath.Join(root, "go.mod"))
91
- packageJSON := read(filepath.Join(root, "package.json"))
92
- cargo := read(filepath.Join(root, "Cargo.toml"))
93
- markers := []struct {
94
- name string
95
- seen bool
96
- }{
97
- {"Go", goMod != ""},
98
- {"Node.js", packageJSON != ""},
99
- {"Rust", cargo != ""},
100
- {"Java", exists(filepath.Join(root, "pom.xml")) || exists(filepath.Join(root, "build.gradle")) || exists(filepath.Join(root, "build.gradle.kts"))},
101
- {"Python", exists(filepath.Join(root, "pyproject.toml")) || exists(filepath.Join(root, "requirements.txt")) || exists(filepath.Join(root, "manage.py"))},
102
- {"Gin", strings.Contains(goMod, "github.com/gin-gonic/gin")},
103
- {"Kafka", strings.Contains(goMod, "segmentio/kafka-go") || strings.Contains(goMod, "Shopify/sarama") || strings.Contains(goMod, "IBM/sarama") || strings.Contains(goMod, "ThreeDotsLabs/watermill-kafka")},
104
- {"PostgreSQL", strings.Contains(goMod, "jackc/pgx") || strings.Contains(goMod, "lib/pq")},
105
- {"Redis", strings.Contains(goMod, "go-redis/redis") || strings.Contains(goMod, "redis/go-redis")},
106
- {"OpenTelemetry", strings.Contains(goMod, "go.opentelemetry.io/otel")},
107
- {"River", strings.Contains(goMod, "riverqueue/river")},
108
- {"Watermill", strings.Contains(goMod, "github.com/ThreeDotsLabs/watermill")},
109
- {"Docker", exists(filepath.Join(root, "Dockerfile")) || exists(filepath.Join(root, "docker-compose.yml")) || exists(filepath.Join(root, "docker-compose.yaml"))},
110
- {"Helm", hasHelm(root)},
111
- }
112
- out := make([]string, 0, len(markers))
113
- for _, marker := range markers {
114
- if marker.seen {
115
- out = append(out, marker.name)
116
- }
117
- }
118
- return out
119
- }
120
-
121
- func hasHelm(root string) bool {
122
- if exists(filepath.Join(root, "Chart.yaml")) || exists(filepath.Join(root, "charts")) || exists(filepath.Join(root, "helm")) {
123
- return true
124
- }
125
- entries, err := os.ReadDir(root)
126
- if err != nil {
127
- return false
128
- }
129
- for _, entry := range entries {
130
- if entry.IsDir() && strings.HasPrefix(strings.ToLower(entry.Name()), ".helm") {
131
- return true
132
- }
133
- }
134
- return false
135
- }
136
-
137
- func exists(path string) bool {
138
- _, err := os.Stat(path)
139
- return err == nil
140
- }
141
-
142
- func read(path string) string {
143
- contents, err := os.ReadFile(path)
144
- if err != nil {
145
- return ""
146
- }
147
- return strings.TrimSpace(string(contents))
148
- }
149
-
150
- func markdownTitle(md string) string {
151
- fence := ""
152
- for _, line := range strings.Split(md, "\n") {
153
- trimmed := strings.TrimSpace(line)
154
- marker := ""
155
- if strings.HasPrefix(trimmed, "```") {
156
- marker = "```"
157
- } else if strings.HasPrefix(trimmed, "~~~") {
158
- marker = "~~~"
159
- }
160
- if marker != "" {
161
- if fence == "" {
162
- fence = marker
163
- } else if fence == marker {
164
- fence = ""
165
- }
166
- continue
167
- }
168
- if fence != "" {
169
- continue
170
- }
171
- if value, ok := strings.CutPrefix(trimmed, "# "); ok {
172
- return strings.TrimSpace(value)
173
- }
174
- }
175
- return ""
176
- }
177
-
178
- func slug(value string) string {
179
- value = strings.ToLower(strings.TrimSpace(value))
180
- var out strings.Builder
181
- dash := false
182
- for _, r := range value {
183
- if r >= 'a' && r <= 'z' || r >= '0' && r <= '9' {
184
- out.WriteRune(r)
185
- dash = false
186
- } else if out.Len() > 0 && !dash {
187
- out.WriteByte('-')
188
- dash = true
189
- }
190
- }
191
- return strings.TrimSuffix(out.String(), "-")
192
- }
193
-
194
- func title(value string) string {
195
- parts := strings.FieldsFunc(value, func(r rune) bool { return r == '-' || r == '_' })
196
- for i := range parts {
197
- if parts[i] != "" {
198
- parts[i] = strings.ToUpper(parts[i][:1]) + parts[i][1:]
199
- }
200
- }
201
- return strings.Join(parts, " ")
202
- }
203
-
204
- func firstNonEmpty(values ...string) string {
205
- for _, value := range values {
206
- if strings.TrimSpace(value) != "" {
207
- return value
208
- }
209
- }
210
- return ""
211
- }
212
-
213
- // stated prefers what the manifest says to what the tree implies: a component
214
- // whose manifests sit above its root, one of several in a repository, has
215
- // nothing at its root for technologies to read.
216
- func stated(told, inferred []string) []string {
217
- if len(told) > 0 {
218
- return told
219
- }
220
- return inferred
221
- }
@@ -1,109 +0,0 @@
1
- package main
2
-
3
- import (
4
- "encoding/json"
5
- "os"
6
- "path/filepath"
7
- "testing"
8
-
9
- "github.com/shortlink-org/portolan/catalog"
10
- "github.com/shortlink-org/portolan/plugin"
11
- )
12
-
13
- func TestExtractsANeutralApplicationWithoutDDD(t *testing.T) {
14
- root := t.TempDir()
15
- mustWrite(t, filepath.Join(root, "README.md"), "# Flight support\n\nCoordinates providers.\n")
16
- mustWrite(t, filepath.Join(root, "go.mod"), "module example.com/flight/support\n")
17
- mustWrite(t, filepath.Join(root, "cmd", "support", "main.go"), "package main\n")
18
- mustWrite(t, filepath.Join(root, "Dockerfile"), "FROM scratch\n")
19
- mustWrite(t, filepath.Join(root, "Makefile"), "build: ## Compile the binary\n\tgo build ./cmd/support\n")
20
-
21
- response, err := extract(plugin.Input{Root: root, Commit: "abc", GeneratedAt: "2026-09-06T00:00:00Z"}, Options{Group: "avia", Component: "support"})
22
- if err != nil {
23
- t.Fatal(err)
24
- }
25
- var got catalog.Catalog
26
- if err := json.Unmarshal([]byte(response.Files[0].Contents), &got); err != nil {
27
- t.Fatal(err)
28
- }
29
- group := got.Contexts[0]
30
- component := group.Services[0]
31
- if group.Kind != catalog.GroupKindSystem {
32
- t.Errorf("group kind = %q", group.Kind)
33
- }
34
- if component.Kind != catalog.ComponentKindApplication {
35
- t.Errorf("component kind = %q", component.Kind)
36
- }
37
- if component.Name != "Flight support" || component.Repo != "example.com/flight/support" {
38
- t.Errorf("component = %+v", component)
39
- }
40
- if len(component.Technologies) != 2 || component.Technologies[0] != "Go" || component.Technologies[1] != "Docker" {
41
- t.Errorf("technologies = %v", component.Technologies)
42
- }
43
- if len(component.Aggregates) != 0 {
44
- t.Fatalf("invented aggregates: %+v", component.Aggregates)
45
- }
46
- if len(component.Commands) != 1 || component.Commands[0].Run != "make build" || component.Commands[0].Doc != "Compile the binary" {
47
- t.Errorf("commands = %+v", component.Commands)
48
- }
49
- }
50
-
51
- func TestExplicitKindsWin(t *testing.T) {
52
- root := t.TempDir()
53
- response, err := extract(plugin.Input{Root: root}, Options{Group: "platform", GroupKind: "team", Component: "tools", ComponentKind: "cli"})
54
- if err != nil {
55
- t.Fatal(err)
56
- }
57
- var got catalog.Catalog
58
- if err := json.Unmarshal([]byte(response.Files[0].Contents), &got); err != nil {
59
- t.Fatal(err)
60
- }
61
- if got.Contexts[0].Kind != catalog.GroupKindTeam || got.Contexts[0].Services[0].Kind != catalog.ComponentKindCLI {
62
- t.Fatalf("explicit kinds were lost: %+v", got.Contexts[0])
63
- }
64
- }
65
-
66
- func TestMarkdownTitleIgnoresShellCommentsInsideFences(t *testing.T) {
67
- md := "## Getting started\n\n```sh\nmake migrate\n# add superuser\n```\n\n# Actual service\n"
68
- if got := markdownTitle(md); got != "Actual service" {
69
- t.Fatalf("markdownTitle = %q", got)
70
- }
71
- md = "~~~sh\n# also not a title\n~~~\n"
72
- if got := markdownTitle(md); got != "" {
73
- t.Fatalf("markdownTitle without a real H1 = %q", got)
74
- }
75
- }
76
-
77
- func TestRecognizesMessagingLibrariesAsTechnologies(t *testing.T) {
78
- root := t.TempDir()
79
- mustWrite(t, filepath.Join(root, "go.mod"), `module example.com/worker
80
- require (
81
- github.com/riverqueue/river v0.26.0
82
- github.com/ThreeDotsLabs/watermill v1.5.1
83
- github.com/ThreeDotsLabs/watermill-kafka/v2 v2.5.0
84
- )
85
- `)
86
-
87
- got := technologies(root)
88
- for _, want := range []string{"Kafka", "River", "Watermill"} {
89
- found := false
90
- for _, value := range got {
91
- if value == want {
92
- found = true
93
- }
94
- }
95
- if !found {
96
- t.Errorf("technologies %v do not contain %q", got, want)
97
- }
98
- }
99
- }
100
-
101
- func mustWrite(t *testing.T, path, contents string) {
102
- t.Helper()
103
- if err := os.MkdirAll(filepath.Dir(path), 0o755); err != nil {
104
- t.Fatal(err)
105
- }
106
- if err := os.WriteFile(path, []byte(contents), 0o644); err != nil {
107
- t.Fatal(err)
108
- }
109
- }