@shortlink-org/portolan 0.1.0 → 0.2.1

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 +27 -10
  36. package/scripts/local-api.test.mjs +85 -2
  37. package/scripts/manifest.mjs +3 -2
  38. package/scripts/package-smoke.mjs +24 -3
  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 +17 -9
  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,234 +0,0 @@
1
- package main
2
-
3
- import (
4
- "encoding/json"
5
- "os"
6
- "path/filepath"
7
- "strings"
8
- "testing"
9
-
10
- "github.com/shortlink-org/portolan/catalog"
11
- "github.com/shortlink-org/portolan/plugin"
12
- )
13
-
14
- func write(t *testing.T, root, name, contents string) {
15
- t.Helper()
16
- path := filepath.Join(root, name)
17
- if err := os.MkdirAll(filepath.Dir(path), 0o755); err != nil {
18
- t.Fatal(err)
19
- }
20
- if err := os.WriteFile(path, []byte(contents), 0o644); err != nil {
21
- t.Fatal(err)
22
- }
23
- }
24
-
25
- func extracted(t *testing.T, root string) (catalog.Catalog, plugin.Response) {
26
- t.Helper()
27
- resp, err := extract(plugin.Input{Root: root, Commit: "abc", GeneratedAt: "2026-01-01T00:00:00Z"}, Options{Context: "sales", Service: "mailer"})
28
- if err != nil {
29
- t.Fatal(err)
30
- }
31
- var out catalog.Catalog
32
- if err := json.Unmarshal([]byte(resp.Files[0].Contents), &out); err != nil {
33
- t.Fatal(err)
34
- }
35
- return out, resp
36
- }
37
-
38
- func TestExtractsConfiguredHandlerAndHelperPublications(t *testing.T) {
39
- root := t.TempDir()
40
- write(t, root, "go.mod", "module example.com/mailer\n")
41
- write(t, root, "config/config.go", `package config
42
- type Bus struct {
43
- Input string `+"`envconfig:\"MAIL_INPUT_TOPIC\" default:\"mail.input\"`"+`
44
- Output string `+"`envconfig:\"MAIL_OUTPUT_TOPIC\" default:\"mail.output\"`"+`
45
- Errors string `+"`envconfig:\"MAIL_ERROR_TOPIC\" default:\"mail.error\"`"+`
46
- }
47
- `)
48
- write(t, root, "api/messages.go", `package api
49
- type Input struct {
50
- ID string `+"`json:\"id\"`"+`
51
- Valid bool `+"`json:\"valid\"`"+`
52
- }
53
- type Output struct { Accepted bool `+"`json:\"accepted\"`"+` }
54
- type Failure struct { Reason string `+"`json:\"reason\"`"+` }
55
- `)
56
- write(t, root, "app/router.go", `package app
57
- import (
58
- "encoding/json"
59
- "github.com/ThreeDotsLabs/watermill"
60
- "github.com/ThreeDotsLabs/watermill/message"
61
- "example.com/mailer/api"
62
- "example.com/mailer/config"
63
- )
64
- const ConsumerGroup = "mail-workers"
65
- func NewSubscriber(config.Bus, string) (message.Subscriber, error) { return nil, nil }
66
- func publishFailure(pub message.Publisher, topic string) error {
67
- value := api.Failure{Reason: "bad"}
68
- payload, _ := json.Marshal(value)
69
- msg := message.NewMessage(watermill.NewUUID(), payload)
70
- return pub.Publish(topic, msg)
71
- }
72
- func Register(r *message.Router, cfg config.Bus, pub message.Publisher) {
73
- sub, _ := NewSubscriber(cfg, ConsumerGroup)
74
- handlerName := "mail_router"
75
- r.AddHandler(handlerName, cfg.Input, sub, "", nil, func(msg *message.Message) ([]*message.Message, error) {
76
- var input api.Input
77
- _ = json.Unmarshal(msg.Payload, &input)
78
- if !input.Valid {
79
- _ = publishFailure(pub, cfg.Errors)
80
- return nil, nil
81
- }
82
- value := api.Output{Accepted: true}
83
- payload, _ := json.Marshal(value)
84
- out := message.NewMessage(watermill.NewUUID(), payload)
85
- _ = pub.Publish(cfg.Output, out)
86
- return nil, nil
87
- })
88
- }
89
- `)
90
-
91
- out, _ := extracted(t, root)
92
- service := out.Contexts[0].Services[0]
93
- if len(service.Channels) != 3 {
94
- t.Fatalf("channels = %+v", service.Channels)
95
- }
96
- byAddress := map[string]catalog.Channel{}
97
- for _, channel := range service.Channels {
98
- byAddress[channel.Address] = channel
99
- }
100
- if got := byAddress["mail.input"].Messages[0]; got.Direction != catalog.ChannelReceive || got.Name != "Input" || !strings.Contains(got.Doc, "id string") {
101
- t.Fatalf("input = %+v", got)
102
- }
103
- if got := byAddress["mail.output"].Messages[0]; got.Direction != catalog.ChannelSend || got.Name != "Output" {
104
- t.Fatalf("output = %+v", got)
105
- }
106
- if got := byAddress["mail.error"].Messages[0]; got.Name != "Failure" || !strings.Contains(got.Doc, "reason string") {
107
- t.Fatalf("error = %+v", got)
108
- }
109
- if !strings.Contains(byAddress["mail.input"].Doc, "MAIL_INPUT_TOPIC") {
110
- t.Fatalf("channel doc = %q", byAddress["mail.input"].Doc)
111
- }
112
- if len(out.Flows) != 1 {
113
- t.Fatalf("flows = %+v", out.Flows)
114
- }
115
- flow := out.Flows[0]
116
- if len(flow.Steps) != 2 || !strings.Contains(flow.Steps[0].(*catalog.Step).Note, "mail-workers") {
117
- t.Fatalf("flow = %+v", flow)
118
- }
119
- branches := flow.Steps[1].(*catalog.Alt).Branches
120
- if len(branches) != 2 || branches[0].Title != "!input.Valid" || branches[1].Title != "not (!input.Valid)" || !branches[0].Terminal {
121
- t.Fatalf("branches = %+v", branches)
122
- }
123
- receive := flow.Steps[0].(*catalog.Step).Handoff
124
- errorSend := branches[0].Steps[0].(*catalog.Step).Handoff
125
- successSend := branches[1].Steps[0].(*catalog.Step).Handoff
126
- if receive == nil || receive.Direction != "receive" || receive.Transport != "watermill" || receive.Channel != "mail.input" {
127
- t.Fatalf("receive handoff = %+v", receive)
128
- }
129
- if errorSend == nil || errorSend.Direction != "send" || errorSend.Channel != "mail.error" || successSend == nil || successSend.Channel != "mail.output" {
130
- t.Fatalf("publish handoffs = error %+v, success %+v", errorSend, successSend)
131
- }
132
- }
133
-
134
- func TestExtractsNamedNoPublisherHandler(t *testing.T) {
135
- root := t.TempDir()
136
- write(t, root, "go.mod", "module example.com/consumer\n")
137
- write(t, root, "consumer.go", `package consumer
138
- import (
139
- "encoding/json"
140
- "github.com/ThreeDotsLabs/watermill/message"
141
- )
142
- type Notice struct { Code string `+"`json:\"code\"`"+` }
143
- func consume(msg *message.Message) error {
144
- var notice Notice
145
- return json.Unmarshal(msg.Payload, &notice)
146
- }
147
- func Register(r *message.Router, sub message.Subscriber) {
148
- r.AddNoPublisherHandler("notice_handler", "notice.input", sub, consume)
149
- }
150
- `)
151
-
152
- out, _ := extracted(t, root)
153
- service := out.Contexts[0].Services[0]
154
- if len(service.Channels) != 1 || service.Channels[0].Messages[0].Name != "Notice" {
155
- t.Fatalf("channels = %+v", service.Channels)
156
- }
157
- if len(out.Flows) != 1 || len(out.Flows[0].Steps) != 1 {
158
- t.Fatalf("flows = %+v", out.Flows)
159
- }
160
- if got := out.Flows[0].Steps[0].(*catalog.Step).ContinuesAt; got != "consume" {
161
- t.Fatalf("handler continuation = %q", got)
162
- }
163
- if got := out.Flows[0].Steps[0].(*catalog.Step).Handoff; got == nil || got.Kind != "message" || got.Channel != "notice.input" || got.Direction != "receive" {
164
- t.Fatalf("handler handoff = %+v", got)
165
- }
166
- }
167
-
168
- func TestExtractsGenericCQRSProcessors(t *testing.T) {
169
- root := t.TempDir()
170
- write(t, root, "go.mod", "module example.com/cqrsapp\n")
171
- write(t, root, "cqrs.go", `package cqrsapp
172
- import (
173
- "context"
174
- "github.com/ThreeDotsLabs/watermill/components/cqrs"
175
- "github.com/ThreeDotsLabs/watermill/message"
176
- )
177
- type OrderPlaced struct { OrderID string `+"`json:\"order_id\"`"+` }
178
- type BookOrder struct { CustomerID string `+"`json:\"customer_id\"`"+` }
179
- func Register(router *message.Router) {
180
- events, _ := cqrs.NewEventProcessorWithConfig(router, cqrs.EventProcessorConfig{
181
- GenerateSubscribeTopic: func(cqrs.EventProcessorGenerateSubscribeTopicParams) (string, error) { return "domain.events", nil },
182
- })
183
- project := cqrs.NewEventHandler("project_order", func(ctx context.Context, event *OrderPlaced) error { return nil })
184
- _ = events.AddHandlers(project)
185
-
186
- commands, _ := cqrs.NewCommandProcessorWithConfig(router, cqrs.CommandProcessorConfig{
187
- GenerateSubscribeTopic: func(params cqrs.CommandProcessorGenerateSubscribeTopicParams) (string, error) { return params.CommandName, nil },
188
- })
189
- book := cqrs.NewCommandHandler("book_order", func(ctx context.Context, command *BookOrder) error { return nil })
190
- _, _ = commands.AddHandler(book)
191
- }
192
- `)
193
-
194
- out, _ := extracted(t, root)
195
- service := out.Contexts[0].Services[0]
196
- if len(service.Channels) != 2 {
197
- t.Fatalf("channels = %+v", service.Channels)
198
- }
199
- byAddress := map[string]catalog.Channel{}
200
- for _, channel := range service.Channels {
201
- byAddress[channel.Address] = channel
202
- }
203
- if got := byAddress["domain.events"].Messages[0]; got.Name != "OrderPlaced" || got.Direction != catalog.ChannelReceive {
204
- t.Fatalf("event = %+v", got)
205
- }
206
- if got := byAddress["BookOrder"].Messages[0]; got.Name != "BookOrder" || got.Direction != catalog.ChannelReceive {
207
- t.Fatalf("command = %+v", got)
208
- }
209
- if len(out.Flows) != 2 {
210
- t.Fatalf("flows = %+v", out.Flows)
211
- }
212
- }
213
-
214
- func TestBranchesRequireSourceBackedConditions(t *testing.T) {
215
- unstructured := []publication{
216
- {topic: topic{address: "one"}, payload: "One"},
217
- {topic: topic{address: "two"}, payload: "Two"},
218
- }
219
- if structuredBranches(unstructured) {
220
- t.Fatal("two publications without control-flow evidence became an alt")
221
- }
222
-
223
- structured := []publication{
224
- {topic: topic{address: "errors"}, conditions: []string{"ready", "err != nil"}},
225
- {topic: topic{address: "success"}, conditions: []string{"ready", "not (err != nil)"}},
226
- }
227
- if !structuredBranches(structured) {
228
- t.Fatal("complementary source paths did not become an alt")
229
- }
230
- got := branchConditions(structured)
231
- if got[0] != "err != nil" || got[1] != "not (err != nil)" {
232
- t.Fatalf("branch conditions = %v", got)
233
- }
234
- }
@@ -1,41 +0,0 @@
1
- // Package main is portolan-extract-watermill: Watermill router declarations
2
- // and publications in a Go repository in, channels and source-backed flows out.
3
- package main
4
-
5
- import (
6
- "fmt"
7
- "io"
8
- "os"
9
- "path/filepath"
10
-
11
- "github.com/shortlink-org/portolan/internal/goscan"
12
- "github.com/shortlink-org/portolan/plugin"
13
- )
14
-
15
- type Options struct {
16
- Context string `json:"context"`
17
- Service string `json:"service"`
18
- Out string `json:"out,omitempty"`
19
- }
20
-
21
- func main() {
22
- if err := run(os.Stdin, os.Stdout); err != nil {
23
- fmt.Fprintln(os.Stderr, "portolan-extract-watermill:", err)
24
- os.Exit(1)
25
- }
26
- }
27
-
28
- func run(stdin io.Reader, stdout io.Writer) error {
29
- return plugin.Serve(stdin, stdout, descriptor(), func(req plugin.Request, opts Options) (plugin.Response, error) {
30
- if req.Input.Root == "" {
31
- return plugin.Response{}, fmt.Errorf("no input root: an extractor has nothing to read")
32
- }
33
- if opts.Context == "" {
34
- opts.Context = goscan.Slug(filepath.Base(req.Input.Root))
35
- }
36
- if opts.Service == "" {
37
- opts.Service = goscan.Slug(filepath.Base(req.Input.Root))
38
- }
39
- return extract(req.Input, opts)
40
- })
41
- }
@@ -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-wsdl",
15
- Summary: "Reads WSDL services, SOAP bindings, operations, messages, faults, and local XSD shapes.",
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,148 +0,0 @@
1
- package main
2
-
3
- import (
4
- "encoding/json"
5
- "fmt"
6
- "sort"
7
- "strings"
8
-
9
- "github.com/shortlink-org/portolan/catalog"
10
- "github.com/shortlink-org/portolan/internal/wsdl"
11
- "github.com/shortlink-org/portolan/plugin"
12
- )
13
-
14
- func extract(in plugin.Input, opts Options) (plugin.Response, error) {
15
- if opts.Mode != "" && opts.Mode != "service" && opts.Mode != "external" {
16
- return plugin.Response{}, fmt.Errorf("mode %q is not service or external", opts.Mode)
17
- }
18
- if opts.External != "" && strings.Contains(opts.External, ".") {
19
- return plugin.Response{}, fmt.Errorf("external %q has a dot in its id", opts.External)
20
- }
21
- var result wsdl.Result
22
- var err error
23
- if opts.Spec != "" {
24
- result, err = wsdl.Read(in.Root, opts.Spec)
25
- } else {
26
- result, err = wsdl.Discover(in.Root)
27
- }
28
- if err != nil {
29
- return plugin.Response{}, err
30
- }
31
- b := &plugin.Builder{}
32
- for _, warning := range result.Warnings {
33
- b.Warn(in.Root, warning)
34
- }
35
- fragment := catalog.Catalog{
36
- GeneratedAt: in.GeneratedAt, Commit: in.Commit,
37
- Contexts: []catalog.BoundedContext{}, Defs: map[string]catalog.TypeDef{},
38
- Flows: []catalog.Flow{}, Adrs: []catalog.Adr{}, Externals: []catalog.External{},
39
- }
40
- apiIDs := wsdl.APIIDs(result.Contracts)
41
- if opts.Mode == "external" || opts.External != "" {
42
- fragment.Externals = externalContracts(result.Contracts, apiIDs, opts)
43
- } else {
44
- var provides []catalog.RpcService
45
- for _, contract := range result.Contracts {
46
- api := apiOf(contract, apiIDs, opts, len(result.Contracts))
47
- provides = append(provides, servicesOf(contract, api)...)
48
- }
49
- sort.Slice(provides, func(i, j int) bool { return provides[i].ID < provides[j].ID })
50
- fragment.Contexts = []catalog.BoundedContext{{
51
- ID: opts.Context, Slug: opts.Context,
52
- Services: []catalog.Service{{
53
- ID: opts.Context + "." + opts.Service, Slug: opts.Service,
54
- Provides: provides, Consumes: []catalog.RpcCall{}, Aggregates: []catalog.Aggregate{},
55
- }},
56
- }}
57
- }
58
- if len(result.Contracts) == 0 {
59
- b.Warn(in.Root, "no WSDL service or port type was found")
60
- }
61
- encoded, err := json.MarshalIndent(fragment, "", " ")
62
- if err != nil {
63
- return plugin.Response{}, err
64
- }
65
- b.File(firstNonEmpty(opts.Out, "wsdl.json"), string(encoded)+"\n")
66
- return b.Response(), nil
67
- }
68
-
69
- func externalContracts(contracts []wsdl.Contract, apiIDs map[string]string, opts Options) []catalog.External {
70
- byID := map[string]*catalog.External{}
71
- for _, contract := range contracts {
72
- api := apiOf(contract, apiIDs, opts, len(contracts))
73
- id := firstNonEmpty(opts.External, opts.Externals[api], wsdl.ExternalID(contract))
74
- external := byID[id]
75
- if external == nil {
76
- external = &catalog.External{
77
- ID: id, Slug: id,
78
- Name: firstNonEmpty(opts.ExternalName, contract.Name),
79
- Summary: firstNonEmpty(opts.ExternalSummary, contract.Summary),
80
- URL: opts.ExternalURL, Provides: []catalog.RpcService{},
81
- }
82
- byID[id] = external
83
- }
84
- if external.URL == "" {
85
- for _, iface := range contract.Interfaces {
86
- if iface.Endpoint != "" {
87
- external.URL = iface.Endpoint
88
- break
89
- }
90
- }
91
- }
92
- external.Provides = append(external.Provides, servicesOf(contract, api)...)
93
- }
94
- var out []catalog.External
95
- for _, external := range byID {
96
- sort.Slice(external.Provides, func(i, j int) bool { return external.Provides[i].ID < external.Provides[j].ID })
97
- out = append(out, *external)
98
- }
99
- sort.Slice(out, func(i, j int) bool { return out[i].ID < out[j].ID })
100
- return out
101
- }
102
-
103
- func apiOf(contract wsdl.Contract, apiIDs map[string]string, opts Options, count int) string {
104
- if opts.API != "" && count == 1 {
105
- return opts.API
106
- }
107
- return apiIDs[wsdl.ContractKey(contract)]
108
- }
109
-
110
- func servicesOf(contract wsdl.Contract, api string) []catalog.RpcService {
111
- var out []catalog.RpcService
112
- for _, iface := range contract.Interfaces {
113
- methods := make([]catalog.RpcMethod, 0, len(iface.Operations))
114
- for _, operation := range iface.Operations {
115
- methods = append(methods, catalog.RpcMethod{
116
- Name: operation.Name, Doc: operation.Doc,
117
- Request: operation.Request, Response: operation.Response,
118
- SOAP: &catalog.SoapRoute{
119
- Action: operation.Action, Version: iface.Version, Style: iface.Style,
120
- Endpoint: iface.Endpoint, Binding: iface.Binding,
121
- Faults: append([]string(nil), operation.Faults...), Headers: append([]string(nil), operation.Headers...),
122
- },
123
- })
124
- }
125
- messages := make([]catalog.RpcMessage, 0, len(iface.Messages))
126
- for _, message := range iface.Messages {
127
- fields := make([]catalog.Field, 0, len(message.Fields))
128
- for _, field := range message.Fields {
129
- fields = append(fields, catalog.Field{Name: field.Name, Type: field.Type, Doc: field.Doc})
130
- }
131
- messages = append(messages, catalog.RpcMessage{Name: message.Name, Fields: fields})
132
- }
133
- out = append(out, catalog.RpcService{
134
- ID: wsdl.InterfaceID(api, iface), Source: contract.Source,
135
- Methods: methods, Messages: messages,
136
- })
137
- }
138
- return out
139
- }
140
-
141
- func firstNonEmpty(values ...string) string {
142
- for _, value := range values {
143
- if strings.TrimSpace(value) != "" {
144
- return strings.TrimSpace(value)
145
- }
146
- }
147
- return ""
148
- }
@@ -1,45 +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 TestWSDLBecomesStructuredExternalAPI(t *testing.T) {
14
- root := t.TempDir()
15
- spec := `<definitions xmlns="http://schemas.xmlsoap.org/wsdl/" xmlns:soap="http://schemas.xmlsoap.org/wsdl/soap/" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:tns="urn:payments" name="Payments" targetNamespace="urn:payments">
16
- <types><xsd:schema targetNamespace="urn:payments"><xsd:element name="Pay"><xsd:complexType><xsd:sequence><xsd:element name="amount" type="xsd:decimal"/></xsd:sequence></xsd:complexType></xsd:element></xsd:schema></types>
17
- <message name="PayRequest"><part name="body" element="tns:Pay"/></message>
18
- <message name="PayResponse"><part name="id" type="xsd:string"/></message>
19
- <portType name="PaymentsPortType"><operation name="Pay"><input message="tns:PayRequest"/><output message="tns:PayResponse"/></operation></portType>
20
- <binding name="PaymentsBinding" type="tns:PaymentsPortType"><soap:binding style="document"/><operation name="Pay"><soap:operation soapAction="urn:pay"/></operation></binding>
21
- <service name="PaymentsService"><port name="PaymentsPort" binding="tns:PaymentsBinding"><soap:address location="https://pay.example/soap"/></port></service>
22
- </definitions>`
23
- if err := os.WriteFile(filepath.Join(root, "payments.wsdl"), []byte(spec), 0o644); err != nil {
24
- t.Fatal(err)
25
- }
26
- resp, err := extract(plugin.Input{Root: root, Commit: "abc", GeneratedAt: "2026-01-01T00:00:00Z"}, Options{Mode: "external", Spec: "payments.wsdl"})
27
- if err != nil {
28
- t.Fatal(err)
29
- }
30
- var got catalog.Catalog
31
- if err := json.Unmarshal([]byte(resp.Files[0].Contents), &got); err != nil {
32
- t.Fatal(err)
33
- }
34
- if len(got.Externals) != 1 || got.Externals[0].ID != "payments" || got.Externals[0].URL != "https://pay.example/soap" {
35
- t.Fatalf("externals = %+v", got.Externals)
36
- }
37
- provided := got.Externals[0].Provides[0]
38
- if provided.ID != "paymentsservice.soap.PaymentsPort" || provided.Source != "payments.wsdl" || len(provided.Messages) != 2 {
39
- t.Fatalf("provided = %+v", provided)
40
- }
41
- method := provided.Methods[0]
42
- if method.Request != "PayRequest" || method.Response != "PayResponse" || method.SOAP == nil || method.SOAP.Action != "urn:pay" || method.SOAP.Version != "1.1" {
43
- t.Fatalf("method = %+v", method)
44
- }
45
- }
@@ -1,53 +0,0 @@
1
- // Package main is portolan-extract-wsdl: WSDL contracts and their local XSD
2
- // graph in, structured SOAP interfaces in the catalog out.
3
- package main
4
-
5
- import (
6
- "fmt"
7
- "io"
8
- "os"
9
- "path/filepath"
10
-
11
- "github.com/shortlink-org/portolan/internal/wsdl"
12
- "github.com/shortlink-org/portolan/plugin"
13
- )
14
-
15
- type Options struct {
16
- Context string `json:"context"`
17
- Service string `json:"service"`
18
- Spec string `json:"spec,omitempty"`
19
- API string `json:"api,omitempty"`
20
-
21
- // Mode is service for a contract implemented by this component, external
22
- // for copies used to call systems outside the estate.
23
- Mode string `json:"mode,omitempty"`
24
- External string `json:"external,omitempty"`
25
- Externals map[string]string `json:"externals,omitempty"`
26
-
27
- ExternalName string `json:"externalName,omitempty"`
28
- ExternalSummary string `json:"externalSummary,omitempty"`
29
- ExternalURL string `json:"externalUrl,omitempty"`
30
- Out string `json:"out,omitempty"`
31
- }
32
-
33
- func main() {
34
- if err := run(os.Stdin, os.Stdout); err != nil {
35
- fmt.Fprintln(os.Stderr, "portolan-extract-wsdl:", err)
36
- os.Exit(1)
37
- }
38
- }
39
-
40
- func run(stdin io.Reader, stdout io.Writer) error {
41
- return plugin.Serve(stdin, stdout, descriptor(), func(req plugin.Request, opts Options) (plugin.Response, error) {
42
- if req.Input.Root == "" {
43
- return plugin.Response{}, fmt.Errorf("no input root: an extractor has nothing to read")
44
- }
45
- if opts.Context == "" {
46
- opts.Context = wsdl.Slug(filepath.Base(req.Input.Root))
47
- }
48
- if opts.Service == "" {
49
- opts.Service = wsdl.Slug(filepath.Base(req.Input.Root))
50
- }
51
- return extract(req.Input, opts)
52
- })
53
- }
@@ -1,114 +0,0 @@
1
- package main
2
-
3
- // The only file that reads the environment.
4
- //
5
- // The plugin protocol says a plugin has "no ambient state to read, no
6
- // environment". That is about FACTS: nothing about the estate may come from
7
- // anywhere but the request, or a fragment stops being reproducible from the
8
- // tree it describes.
9
- //
10
- // A credential is not a fact about the estate. It is how the transport is
11
- // allowed to open a socket, the same way a git credential is - it changes
12
- // whether the fetch succeeds, never what the fetch says. Isolating it in one
13
- // file that nothing else calls is what keeps that claim checkable.
14
- //
15
- // It is also why this plugin can never be wasm. It needs a socket and a
16
- // secret, and `process` exists for exactly that trade.
17
-
18
- import (
19
- "bufio"
20
- "os"
21
- "path/filepath"
22
- "strings"
23
- )
24
-
25
- // TokenEnv is where `buf registry login` is usually mirrored in CI.
26
- const TokenEnv = "BUF_TOKEN"
27
-
28
- // token finds a credential for a registry, or returns none.
29
- //
30
- // None is a normal outcome, not a failure: public modules download
31
- // anonymously, and an estate whose schemas are public never needs a token.
32
- func token(registry string) string {
33
- if t := strings.TrimSpace(os.Getenv(TokenEnv)); t != "" {
34
- return t
35
- }
36
-
37
- return netrcToken(registry)
38
- }
39
-
40
- // netrcToken reads the password `buf registry login` wrote.
41
- func netrcToken(registry string) string {
42
- home, err := os.UserHomeDir()
43
- if err != nil {
44
- return ""
45
- }
46
-
47
- for _, name := range []string{".netrc", "_netrc"} {
48
- if t := readNetrc(filepath.Join(home, name), registry); t != "" {
49
- return t
50
- }
51
- }
52
-
53
- return ""
54
- }
55
-
56
- // readNetrc walks a netrc for the machine asked about.
57
- //
58
- // Deliberately small: netrc is a stream of words, and the only entry that
59
- // matters here is the password belonging to one machine.
60
- func readNetrc(path, machine string) string {
61
- file, err := os.Open(path)
62
- if err != nil {
63
- return ""
64
- }
65
- defer file.Close()
66
-
67
- words := bufio.NewScanner(file)
68
- words.Split(bufio.ScanWords)
69
-
70
- current := ""
71
- for words.Scan() {
72
- switch words.Text() {
73
- case "machine":
74
- if !words.Scan() {
75
- return ""
76
- }
77
- current = words.Text()
78
- case "default":
79
- current = machine
80
- case "password":
81
- if !words.Scan() {
82
- return ""
83
- }
84
- if current == machine {
85
- return words.Text()
86
- }
87
- case "login", "account":
88
- // Read past the value so it cannot be mistaken for a keyword.
89
- words.Scan()
90
- }
91
- }
92
-
93
- return ""
94
- }
95
-
96
- // OfflineEnv turns the fetch off. Explicit, rather than inferred from a socket
97
- // timeout: CI sets it in one line, and a build that would otherwise depend on
98
- // the registry being up cannot silently start doing so again.
99
- const OfflineEnv = "PORTOLAN_OFFLINE"
100
-
101
- func offline() bool {
102
- if strings.TrimSpace(os.Getenv(OfflineEnv)) != "" {
103
- return true
104
- }
105
-
106
- // CI verifies what was committed. It never needs to reach the registry, and
107
- // a fork's pull request has no secret to reach it with.
108
- switch strings.ToLower(strings.TrimSpace(os.Getenv("CI"))) {
109
- case "", "0", "false":
110
- return false
111
- default:
112
- return true
113
- }
114
- }