@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,240 +0,0 @@
1
- package main
2
-
3
- // The only file that knows the registry's wire.
4
- //
5
- // The BSR's public API is Connect, and a Connect unary call is an ordinary
6
- // HTTP POST carrying the request message as JSON - so this needs net/http and
7
- // encoding/json and nothing else. No grpc dependency, no protobuf runtime, in
8
- // a go.mod that has two narrow dependencies.
9
- //
10
- // The message shapes below are buf/registry/module/v1, and the JSON names are
11
- // protojson's: lowerCamelCase, `bytes` as base64. Everything the wire knows is
12
- // in this file on purpose - if buf changes the shape, or the older
13
- // buf.alpha.registry.v1alpha1 endpoints have to be fallen back to, this is the
14
- // only file that moves.
15
-
16
- import (
17
- "bytes"
18
- "encoding/hex"
19
- "encoding/json"
20
- "fmt"
21
- "io"
22
- "net/http"
23
- "strings"
24
- "time"
25
- )
26
-
27
- const (
28
- downloadMethod = "/buf.registry.module.v1.DownloadService/Download"
29
- commitsMethod = "/buf.registry.module.v1.CommitService/GetCommits"
30
-
31
- // The Connect protocol asks a client to say which version it speaks.
32
- connectVersionHeader = "Connect-Protocol-Version"
33
- connectVersion = "1"
34
- )
35
-
36
- // resourceRef names a module and which version of it is wanted.
37
- type resourceRef struct {
38
- Name *refName `json:"name,omitempty"`
39
- ID string `json:"id,omitempty"`
40
- }
41
-
42
- type refName struct {
43
- Owner string `json:"owner"`
44
- Module string `json:"module"`
45
- // Ref is untyped: a commit id, a label, a tag. A pinned module always sends
46
- // a commit id, which is the only form that makes a download reproducible.
47
- Ref string `json:"ref,omitempty"`
48
- }
49
-
50
- type downloadRequest struct {
51
- Values []downloadValue `json:"values"`
52
- }
53
-
54
- type downloadValue struct {
55
- ResourceRef resourceRef `json:"resourceRef"`
56
- // Only .proto: the docs and licence a module also carries are not something
57
- // portolan reads, and fetching them would put files in the tree that
58
- // nothing explains.
59
- FileTypes []string `json:"fileTypes,omitempty"`
60
- Paths []string `json:"paths,omitempty"`
61
- // Without this, narrowing to a path the module does not have is an error
62
- // rather than an empty answer.
63
- PathsAllowNotExist bool `json:"pathsAllowNotExist,omitempty"`
64
- }
65
-
66
- type downloadResponse struct {
67
- Contents []struct {
68
- Commit wireCommit `json:"commit"`
69
- Files []struct {
70
- Path string `json:"path"`
71
- // bytes on the wire, so base64 in JSON.
72
- Content []byte `json:"content"`
73
- } `json:"files"`
74
- } `json:"contents"`
75
- }
76
-
77
- type wireCommit struct {
78
- ID string `json:"id"`
79
- Digest struct {
80
- Type string `json:"type"`
81
- Value []byte `json:"value"`
82
- } `json:"digest"`
83
- }
84
-
85
- // display renders a digest the way buf writes one: `b5:<hex>`.
86
- func (c wireCommit) display() string {
87
- if len(c.Digest.Value) == 0 {
88
- return ""
89
- }
90
- kind := strings.ToLower(strings.TrimPrefix(c.Digest.Type, "DIGEST_TYPE_"))
91
- if kind == "" || kind == "unspecified" {
92
- kind = "b5"
93
- }
94
-
95
- return kind + ":" + hex.EncodeToString(c.Digest.Value)
96
- }
97
-
98
- type getCommitsRequest struct {
99
- ResourceRefs []resourceRef `json:"resourceRefs"`
100
- }
101
-
102
- type getCommitsResponse struct {
103
- Commits []wireCommit `json:"commits"`
104
- }
105
-
106
- // client talks to one registry.
107
- type client struct {
108
- http *http.Client
109
- base string
110
- token string
111
- }
112
-
113
- func newClient(registry, token string) *client {
114
- return &client{
115
- // A timeout rather than none: a plugin that hangs holds up the whole
116
- // generate run, and the host has no way to interrupt it.
117
- http: &http.Client{Timeout: 60 * time.Second},
118
- base: baseURL(registry),
119
- token: token,
120
- }
121
- }
122
-
123
- // baseURL lets a registry be given with a scheme.
124
- //
125
- // buf.build is https and needs no help. A self-hosted BSR behind a plain-http
126
- // internal address does, and so does a test server - which is the same need,
127
- // and worth meeting once rather than threading a seam through the plugin.
128
- func baseURL(registry string) string {
129
- if strings.HasPrefix(registry, "http://") || strings.HasPrefix(registry, "https://") {
130
- return strings.TrimSuffix(registry, "/")
131
- }
132
-
133
- return "https://" + registry
134
- }
135
-
136
- // download fetches a module's .proto files at a given ref.
137
- func (c *client) download(owner, module, ref string, paths []string) (wireCommit, map[string][]byte, error) {
138
- req := downloadRequest{Values: []downloadValue{{
139
- ResourceRef: resourceRef{Name: &refName{Owner: owner, Module: module, Ref: ref}},
140
- FileTypes: []string{"FILE_TYPE_PROTO"},
141
- Paths: paths,
142
- PathsAllowNotExist: len(paths) > 0,
143
- }}}
144
-
145
- var resp downloadResponse
146
- if err := c.call(downloadMethod, req, &resp); err != nil {
147
- return wireCommit{}, nil, err
148
- }
149
- if len(resp.Contents) == 0 {
150
- return wireCommit{}, nil, fmt.Errorf("%s/%s: the registry returned no content", owner, module)
151
- }
152
-
153
- content := resp.Contents[0]
154
- files := make(map[string][]byte, len(content.Files))
155
- for _, f := range content.Files {
156
- files[f.Path] = f.Content
157
- }
158
-
159
- return content.Commit, files, nil
160
- }
161
-
162
- // resolve turns a label into the commit it currently points at.
163
- func (c *client) resolve(owner, module, ref string) (wireCommit, error) {
164
- req := getCommitsRequest{ResourceRefs: []resourceRef{
165
- {Name: &refName{Owner: owner, Module: module, Ref: ref}},
166
- }}
167
-
168
- var resp getCommitsResponse
169
- if err := c.call(commitsMethod, req, &resp); err != nil {
170
- return wireCommit{}, err
171
- }
172
- if len(resp.Commits) == 0 {
173
- return wireCommit{}, fmt.Errorf("%s/%s: no commit for %q", owner, module, ref)
174
- }
175
-
176
- return resp.Commits[0], nil
177
- }
178
-
179
- func (c *client) call(method string, in, out any) error {
180
- body, err := json.Marshal(in)
181
- if err != nil {
182
- return err
183
- }
184
-
185
- url := c.base + method
186
- req, err := http.NewRequest(http.MethodPost, url, bytes.NewReader(body))
187
- if err != nil {
188
- return err
189
- }
190
- req.Header.Set("Content-Type", "application/json")
191
- req.Header.Set(connectVersionHeader, connectVersion)
192
- if c.token != "" {
193
- req.Header.Set("Authorization", "Bearer "+c.token)
194
- }
195
-
196
- resp, err := c.http.Do(req)
197
- if err != nil {
198
- return err
199
- }
200
- defer resp.Body.Close()
201
-
202
- raw, err := io.ReadAll(io.LimitReader(resp.Body, 256<<20))
203
- if err != nil {
204
- return err
205
- }
206
-
207
- if resp.StatusCode != http.StatusOK {
208
- return fmt.Errorf("%s: %s", method, connectError(resp.StatusCode, raw))
209
- }
210
-
211
- return json.Unmarshal(raw, out)
212
- }
213
-
214
- // connectError reads the error body Connect sends, falling back to the status
215
- // when the body is not one.
216
- func connectError(status int, body []byte) string {
217
- var wire struct {
218
- Code string `json:"code"`
219
- Message string `json:"message"`
220
- }
221
- if err := json.Unmarshal(body, &wire); err == nil && wire.Message != "" {
222
- if wire.Code != "" {
223
- return wire.Code + ": " + wire.Message
224
- }
225
-
226
- return wire.Message
227
- }
228
-
229
- return fmt.Sprintf("http %d", status)
230
- }
231
-
232
- // splitModule breaks `buf.build/acme/shop` into its three parts.
233
- func splitModule(name string) (registry, owner, module string, err error) {
234
- parts := strings.Split(strings.TrimSpace(name), "/")
235
- if len(parts) != 3 || parts[0] == "" || parts[1] == "" || parts[2] == "" {
236
- return "", "", "", fmt.Errorf("%q is not a module name; expected <registry>/<owner>/<module>", name)
237
- }
238
-
239
- return parts[0], parts[1], parts[2], nil
240
- }
@@ -1,66 +0,0 @@
1
- package main
2
-
3
- // Replaying a fetch from what is already on disk.
4
- //
5
- // The cache is not a second copy of anything: it IS the tree. The protos this
6
- // plugin fetched were written by the host through the ordinary Response.Files
7
- // path, so they are committed, reviewable, and GC'd when the step stops naming
8
- // them. Reading them back and checking them against the lock is what lets a
9
- // build that never opens a socket produce byte-identical output.
10
-
11
- import (
12
- "fmt"
13
- "os"
14
- "path/filepath"
15
- )
16
-
17
- // cached is one module's files, read back from the tree.
18
- type cached struct {
19
- lock LockModule
20
- files map[string][]byte
21
- }
22
-
23
- // replay reads a module's committed copy and checks it against its lock.
24
- //
25
- // An incomplete or edited cache is reported rather than patched over. The two
26
- // failures it distinguishes matter: nothing on disk means there is nothing to
27
- // fall back to, and bytes that no longer match their digest mean somebody
28
- // edited a vendored copy - the drift docs/adr/org.0001.md wants visible.
29
- func replay(dir string) (*cached, error) {
30
- raw, err := os.ReadFile(filepath.Join(dir, LockName))
31
- if os.IsNotExist(err) {
32
- return nil, fmt.Errorf("no %s in %s", LockName, filepath.ToSlash(dir))
33
- }
34
- if err != nil {
35
- return nil, err
36
- }
37
-
38
- var lock Lock
39
- if err := jsonUnmarshal(raw, &lock); err != nil {
40
- return nil, fmt.Errorf("%s: %w", filepath.ToSlash(filepath.Join(dir, LockName)), err)
41
- }
42
- if len(lock.Modules) != 1 {
43
- return nil, fmt.Errorf("%s names %d modules; expected exactly one",
44
- filepath.ToSlash(filepath.Join(dir, LockName)), len(lock.Modules))
45
- }
46
-
47
- entry := lock.Modules[0]
48
- files := make(map[string][]byte, len(entry.Files))
49
-
50
- for _, want := range entry.Files {
51
- at := filepath.Join(dir, filepath.FromSlash(want.Path))
52
- content, err := os.ReadFile(at)
53
- if os.IsNotExist(err) {
54
- return nil, fmt.Errorf("%s is in the lock but not on disk", want.Path)
55
- }
56
- if err != nil {
57
- return nil, err
58
- }
59
- if got := digestOf(content); got != want.SHA256 {
60
- return nil, fmt.Errorf("%s does not match its digest; the vendored copy was edited by hand", want.Path)
61
- }
62
- files[want.Path] = content
63
- }
64
-
65
- return &cached{lock: entry, files: files}, nil
66
- }
@@ -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: "fetch-bsr",
15
- Summary: "Fetches pinned modules from a Buf Schema Registry into the tree, with a lock beside each, so the proto extractor can read a published contract.",
16
- Phases: []string{plugin.PhaseExtract},
17
- Options: optionsSchema,
18
- }
19
- }
@@ -1,192 +0,0 @@
1
- package main
2
-
3
- // Deciding whether to fetch, and what to do when the fetch cannot happen.
4
- //
5
- // The rules below are the design, not error handling. The one that matters
6
- // most is the third: a failed fetch with no cache is a NON-ZERO EXIT, never a
7
- // short file list. The host deletes files a step stops naming, and dropping a
8
- // repository's vendored protos because a laptop went offline is worse than a
9
- // red build.
10
-
11
- import (
12
- "encoding/json"
13
- "fmt"
14
- "path"
15
- "path/filepath"
16
- "sort"
17
-
18
- "github.com/shortlink-org/portolan/plugin"
19
- )
20
-
21
- func jsonUnmarshal(raw []byte, into any) error { return json.Unmarshal(raw, into) }
22
-
23
- // fetch takes no plugin.Input: it reads nothing out of the tree being
24
- // described. Its `cache` is repo-relative, like every other path in the
25
- // manifest, and everything else it needs is in the options.
26
- func fetch(opts Options) (plugin.Response, error) {
27
- b := &plugin.Builder{}
28
-
29
- if len(opts.Modules) == 0 {
30
- return plugin.Response{}, fmt.Errorf("no modules to fetch: name at least one in the manifest")
31
- }
32
- if opts.Cache == "" {
33
- return plugin.Response{}, fmt.Errorf(
34
- "no cache directory: set `cache` to the same path as the step's `out`, " +
35
- "so an offline run can replay what the last online one wrote")
36
- }
37
-
38
- // Sorted, so the file list and the diagnostics come out the same way every
39
- // run whatever order the manifest happens to list them in.
40
- wanted := append([]Module(nil), opts.Modules...)
41
- sort.Slice(wanted, func(i, j int) bool { return wanted[i].Module < wanted[j].Module })
42
-
43
- skip := offline()
44
-
45
- for _, want := range wanted {
46
- registry, owner, module, err := splitModule(want.Module)
47
- if err != nil {
48
- return plugin.Response{}, err
49
- }
50
- if opts.Registry != "" {
51
- registry = opts.Registry
52
- }
53
-
54
- // Every module lives in its own directory with its own lock, so a
55
- // vendored module is self-describing and extract-proto finds the lock
56
- // by looking beside the files it is already reading.
57
- dir := path.Join(owner, module)
58
- at := filepath.Join(filepath.FromSlash(opts.Cache), filepath.FromSlash(dir))
59
-
60
- if skip {
61
- if err := emitCached(b, dir, at, want, "offline"); err != nil {
62
- return plugin.Response{}, err
63
- }
64
-
65
- continue
66
- }
67
-
68
- commit, files, err := live(registry, owner, module, want, b)
69
- if err != nil {
70
- // Rule 2: the tree still holds a good copy, so the output is
71
- // unchanged and `--check` stays clean.
72
- if cacheErr := emitCached(b, dir, at, want, err.Error()); cacheErr != nil {
73
- // Rule 3: nothing to fall back to.
74
- return plugin.Response{}, fmt.Errorf(
75
- "%s could not be fetched (%w) and there is no usable copy in the tree (%v)",
76
- want.Module, err, cacheErr)
77
- }
78
-
79
- continue
80
- }
81
-
82
- if err := emitFetched(b, dir, want.Module, commit, files); err != nil {
83
- return plugin.Response{}, err
84
- }
85
- }
86
-
87
- return b.Response(), nil
88
- }
89
-
90
- // live fetches one module, pinning it first if the manifest did not.
91
- func live(registry, owner, module string, want Module, b *plugin.Builder) (wireCommit, map[string][]byte, error) {
92
- c := newClient(registry, token(registry))
93
-
94
- ref := want.Commit
95
- if ref == "" {
96
- // Rule: pin, or every run is a lottery. Resolving a label still works,
97
- // but it is said out loud, because two runs a day apart would then
98
- // produce two different trees from one manifest.
99
- ref = firstNonEmpty(want.Ref, "main")
100
- resolved, err := c.resolve(owner, module, ref)
101
- if err != nil {
102
- return wireCommit{}, nil, err
103
- }
104
- b.Warn(want.Module, fmt.Sprintf(
105
- "is not pinned; %q resolved to %s. Pin it in portolan.json or every run is a lottery.",
106
- ref, resolved.ID))
107
- ref = resolved.ID
108
- }
109
-
110
- commit, files, err := c.download(owner, module, ref, want.Paths)
111
- if err != nil {
112
- return wireCommit{}, nil, err
113
- }
114
- if len(files) == 0 {
115
- return wireCommit{}, nil, fmt.Errorf("%s at %s holds no .proto files", want.Module, ref)
116
- }
117
-
118
- return commit, files, nil
119
- }
120
-
121
- // emitFetched writes what came back, plus the lock that makes it checkable.
122
- func emitFetched(b *plugin.Builder, dir, name string, commit wireCommit, files map[string][]byte) error {
123
- entry := LockModule{Module: name, Commit: commit.ID, Digest: commit.display()}
124
-
125
- paths := make([]string, 0, len(files))
126
- for at := range files {
127
- paths = append(paths, at)
128
- }
129
- sort.Strings(paths)
130
-
131
- for _, at := range paths {
132
- content := files[at]
133
- b.File(path.Join(dir, at), string(content))
134
- entry.Files = append(entry.Files, LockEntry{
135
- Path: at,
136
- SHA256: digestOf(content),
137
- Size: len(content),
138
- })
139
- }
140
-
141
- lock, err := Lock{Modules: []LockModule{entry}}.encode()
142
- if err != nil {
143
- return err
144
- }
145
- b.File(path.Join(dir, LockName), lock)
146
-
147
- return nil
148
- }
149
-
150
- // emitCached re-emits the committed copy unchanged.
151
- //
152
- // Byte-identical to what the last online run wrote, which is the whole point:
153
- // `gen:check` sees no drift, so CI can verify an estate whose schemas live in a
154
- // registry it never talks to.
155
- func emitCached(b *plugin.Builder, dir, at string, want Module, why string) error {
156
- if want.Commit == "" {
157
- return fmt.Errorf("%s is not pinned to a commit, so there is nothing to replay", want.Module)
158
- }
159
-
160
- held, err := replay(at)
161
- if err != nil {
162
- return err
163
- }
164
- if held.lock.Commit != want.Commit {
165
- return fmt.Errorf("%s holds commit %s but the manifest pins %s; fetch it",
166
- want.Module, held.lock.Commit, want.Commit)
167
- }
168
-
169
- for _, entry := range held.lock.Files {
170
- b.File(path.Join(dir, entry.Path), string(held.files[entry.Path]))
171
- }
172
-
173
- lock, err := Lock{Modules: []LockModule{held.lock}}.encode()
174
- if err != nil {
175
- return err
176
- }
177
- b.File(path.Join(dir, LockName), lock)
178
-
179
- b.Warn(want.Module, "not fetched ("+why+"); the copy committed in this repository is used unchanged")
180
-
181
- return nil
182
- }
183
-
184
- func firstNonEmpty(values ...string) string {
185
- for _, v := range values {
186
- if v != "" {
187
- return v
188
- }
189
- }
190
-
191
- return ""
192
- }