@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,95 +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 - it changes whether the fetch succeeds, never what
12
- // the fetch says. Isolating it in one file that nothing else calls is what
13
- // 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
- "encoding/base64"
20
- "os"
21
- "strings"
22
- )
23
-
24
- // The names Confluent's own tools use, longest first so the specific one wins
25
- // over a shell that exports both.
26
- const (
27
- KeyEnv = "CONFLUENT_SCHEMA_REGISTRY_API_KEY"
28
- SecretEnv = "CONFLUENT_SCHEMA_REGISTRY_API_SECRET"
29
-
30
- // Short aliases, for a self-hosted registry nobody would call Confluent.
31
- ShortKeyEnv = "CSR_API_KEY"
32
- ShortSecretEnv = "CSR_API_SECRET"
33
-
34
- // TokenEnv is a whole Authorization value, for a registry fronted by
35
- // something that wants a bearer token rather than an API key pair.
36
- TokenEnv = "CSR_TOKEN"
37
- )
38
-
39
- // authorization is the header value to send, or "" for none.
40
- //
41
- // None is a normal outcome, not a failure: a registry inside a private network
42
- // is commonly open to it, and the local one the tests run against certainly is.
43
- func authorization() string {
44
- if raw := env(TokenEnv); raw != "" {
45
- // Passed through when it already names its scheme, so the same
46
- // variable serves "Bearer ..." and an opaque token a proxy expects
47
- // bare. Guessing on the caller's behalf is how a working token starts
48
- // failing after somebody adds a prefix.
49
- if strings.Contains(raw, " ") {
50
- return raw
51
- }
52
-
53
- return "Bearer " + raw
54
- }
55
-
56
- key := first(env(KeyEnv), env(ShortKeyEnv))
57
- secret := first(env(SecretEnv), env(ShortSecretEnv))
58
- if key == "" || secret == "" {
59
- return ""
60
- }
61
-
62
- return "Basic " + base64.StdEncoding.EncodeToString([]byte(key+":"+secret))
63
- }
64
-
65
- func env(name string) string { return strings.TrimSpace(os.Getenv(name)) }
66
-
67
- func first(values ...string) string {
68
- for _, value := range values {
69
- if value != "" {
70
- return value
71
- }
72
- }
73
-
74
- return ""
75
- }
76
-
77
- // OfflineEnv turns the fetch off. Explicit, rather than inferred from a socket
78
- // timeout: CI sets it in one line, and a build that would otherwise depend on
79
- // the registry being up cannot silently start doing so again.
80
- const OfflineEnv = "PORTOLAN_OFFLINE"
81
-
82
- func offline() bool {
83
- if env(OfflineEnv) != "" {
84
- return true
85
- }
86
-
87
- // CI verifies what was committed. It never needs to reach the registry, and
88
- // a fork's pull request has no secret to reach it with.
89
- switch strings.ToLower(env("CI")) {
90
- case "", "0", "false":
91
- return false
92
- default:
93
- return true
94
- }
95
- }
@@ -1,69 +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 schemas 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
- "encoding/json"
13
- "fmt"
14
- "os"
15
- "path/filepath"
16
- )
17
-
18
- // cached is one subject's files, read back from the tree.
19
- type cached struct {
20
- registry string
21
- lock LockSubject
22
- files map[string][]byte
23
- }
24
-
25
- // replay reads a subject's committed copy and checks it against its lock.
26
- //
27
- // An incomplete or edited cache is reported rather than patched over. The two
28
- // failures it distinguishes matter: nothing on disk means there is nothing to
29
- // fall back to, and bytes that no longer match their digest mean somebody
30
- // edited a vendored copy - the drift docs/adr/org.0001.md wants visible.
31
- func replay(dir string) (*cached, error) {
32
- at := filepath.Join(dir, LockName)
33
-
34
- raw, err := os.ReadFile(at)
35
- if os.IsNotExist(err) {
36
- return nil, fmt.Errorf("no %s in %s", LockName, filepath.ToSlash(dir))
37
- }
38
- if err != nil {
39
- return nil, err
40
- }
41
-
42
- var lock Lock
43
- if err := json.Unmarshal(raw, &lock); err != nil {
44
- return nil, fmt.Errorf("%s: %w", filepath.ToSlash(at), err)
45
- }
46
- if len(lock.Subjects) != 1 {
47
- return nil, fmt.Errorf("%s names %d subjects; expected exactly one",
48
- filepath.ToSlash(at), len(lock.Subjects))
49
- }
50
-
51
- entry := lock.Subjects[0]
52
- files := make(map[string][]byte, len(entry.Files))
53
-
54
- for _, want := range entry.Files {
55
- content, err := os.ReadFile(filepath.Join(dir, filepath.FromSlash(want.Path)))
56
- if os.IsNotExist(err) {
57
- return nil, fmt.Errorf("%s is in the lock but not on disk", want.Path)
58
- }
59
- if err != nil {
60
- return nil, err
61
- }
62
- if got := digestOf(content); got != want.SHA256 {
63
- return nil, fmt.Errorf("%s does not match its digest; the vendored copy was edited by hand", want.Path)
64
- }
65
- files[want.Path] = content
66
- }
67
-
68
- return &cached{registry: lock.Registry, lock: entry, files: files}, nil
69
- }
@@ -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-csr",
15
- Summary: "Fetches pinned subject versions from a Confluent Schema Registry into the tree, with a lock beside each, so a schema published to a registry can be read without talking to it.",
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,254 +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 schemas because a laptop went offline is worse than a
9
- // red build.
10
-
11
- import (
12
- "fmt"
13
- "path"
14
- "path/filepath"
15
- "sort"
16
- "strconv"
17
- "strings"
18
-
19
- "github.com/shortlink-org/portolan/plugin"
20
- )
21
-
22
- // fetch takes no plugin.Input: it reads nothing out of the tree being
23
- // described. Its `cache` is repo-relative, like every other path in the
24
- // manifest, and everything else it needs is in the options.
25
- func fetch(opts Options) (plugin.Response, error) {
26
- b := &plugin.Builder{}
27
-
28
- if strings.TrimSpace(opts.Registry) == "" {
29
- return plugin.Response{}, fmt.Errorf("no registry: name the schema registry's base URL in the manifest")
30
- }
31
- if len(opts.Subjects) == 0 {
32
- return plugin.Response{}, fmt.Errorf("no subjects to fetch: name at least one in the manifest")
33
- }
34
- if opts.Cache == "" {
35
- return plugin.Response{}, fmt.Errorf(
36
- "no cache directory: set `cache` to the same path as the step's `out`, " +
37
- "so an offline run can replay what the last online one wrote")
38
- }
39
-
40
- // Sorted, so the file list and the diagnostics come out the same way every
41
- // run whatever order the manifest happens to list them in.
42
- queue := append([]Subject(nil), opts.Subjects...)
43
- sort.Slice(queue, func(i, j int) bool { return queue[i].Subject < queue[j].Subject })
44
-
45
- c := newClient(opts.Registry, authorization())
46
- skip := offline()
47
-
48
- // Two subjects that slug to the same directory would overwrite each other
49
- // silently, and one subject wanted at two versions cannot be held in a
50
- // tree that keeps one version per subject. Both are refused by name rather
51
- // than resolved: the manifest is what has to change.
52
- claimed := map[string]Subject{}
53
- done := map[string]bool{}
54
-
55
- for len(queue) > 0 {
56
- want := queue[0]
57
- queue = queue[1:]
58
-
59
- if want.Subject == "" {
60
- return plugin.Response{}, fmt.Errorf("a subject entry names no subject")
61
- }
62
- if held, taken := claimed[want.Subject]; taken {
63
- if held.Version != want.Version {
64
- return plugin.Response{}, fmt.Errorf(
65
- "%s is wanted at version %s and at version %s; the tree keeps one version per subject, so pin one",
66
- want.Subject, pin(held.Version), pin(want.Version))
67
- }
68
-
69
- continue
70
- }
71
-
72
- dir := slugOf(want.Subject)
73
- if done[dir] {
74
- // Not caught by `claimed`, which is keyed by subject: two
75
- // different subjects can slug into one directory.
76
- return plugin.Response{}, fmt.Errorf(
77
- "%s and another subject both vendor into %s; the tree cannot hold both",
78
- want.Subject, dir)
79
- }
80
- claimed[want.Subject] = want
81
- done[dir] = true
82
-
83
- at := filepath.Join(filepath.FromSlash(opts.Cache), filepath.FromSlash(dir))
84
-
85
- var (
86
- refs []Reference
87
- err error
88
- )
89
-
90
- if skip {
91
- refs, err = emitCached(b, opts.Registry, dir, at, want, "offline")
92
- if err != nil {
93
- return plugin.Response{}, err
94
- }
95
- } else if refs, err = live(c, b, opts.Registry, dir, want); err != nil {
96
- // Rule 2: the tree still holds a good copy, so the output is
97
- // unchanged and `--check` stays clean.
98
- cachedRefs, cacheErr := emitCached(b, opts.Registry, dir, at, want, err.Error())
99
- if cacheErr != nil {
100
- // Rule 3: nothing to fall back to.
101
- return plugin.Response{}, fmt.Errorf(
102
- "%s could not be fetched (%w) and there is no usable copy in the tree (%v)",
103
- want.Subject, err, cacheErr)
104
- }
105
- refs = cachedRefs
106
- }
107
-
108
- // A referenced subject is pinned by the schema that references it, so
109
- // following one needs no permission from the manifest and adds no
110
- // lottery: the version is part of the bytes we already have.
111
- following := make([]Subject, 0, len(refs))
112
- for _, ref := range refs {
113
- if ref.Subject == "" || claimed[ref.Subject].Subject != "" {
114
- continue
115
- }
116
- following = append(following, Subject{Subject: ref.Subject, Version: ref.Version})
117
- }
118
- sort.Slice(following, func(i, j int) bool { return following[i].Subject < following[j].Subject })
119
- queue = append(queue, following...)
120
- }
121
-
122
- return b.Response(), nil
123
- }
124
-
125
- // live fetches one subject, pinning it first if the manifest did not.
126
- func live(c *client, b *plugin.Builder, registry, dir string, want Subject) ([]Reference, error) {
127
- got, err := c.version(want.Subject, want.Version)
128
- if err != nil {
129
- return nil, err
130
- }
131
-
132
- if want.Version == 0 {
133
- // Rule: pin, or every run is a lottery. Resolving `latest` still
134
- // works, but it is said out loud, because two runs a day apart would
135
- // then produce two different trees from one manifest.
136
- b.Warn(want.Subject, fmt.Sprintf(
137
- "is not pinned; \"latest\" resolved to version %d. Pin it in portolan.json or every run is a lottery.",
138
- got.Version))
139
- }
140
-
141
- extension, err := extensionFor(got.SchemaType)
142
- if err != nil {
143
- return nil, fmt.Errorf("%s at version %d: %w", want.Subject, got.Version, err)
144
- }
145
-
146
- name := "v" + strconv.Itoa(got.Version) + extension
147
- content := body(got.SchemaType, got.Schema)
148
-
149
- entry := LockSubject{
150
- Subject: want.Subject,
151
- Version: got.Version,
152
- ID: got.ID,
153
- GUID: got.GUID,
154
- SchemaType: schemaType(got.SchemaType),
155
- References: got.References,
156
- Files: []LockEntry{{
157
- Path: name,
158
- SHA256: digestOf([]byte(content)),
159
- Size: len(content),
160
- }},
161
- }
162
-
163
- b.File(path.Join(dir, name), content)
164
- if err := emitLock(b, registry, dir, entry); err != nil {
165
- return nil, err
166
- }
167
-
168
- return entry.References, nil
169
- }
170
-
171
- // emitCached re-emits the committed copy unchanged.
172
- //
173
- // Byte-identical to what the last online run wrote, which is the whole point:
174
- // `gen:check` sees no drift, so CI can verify an estate whose schemas live in a
175
- // registry it never talks to.
176
- func emitCached(b *plugin.Builder, registry, dir, at string, want Subject, why string) ([]Reference, error) {
177
- if want.Version == 0 {
178
- return nil, fmt.Errorf("%s is not pinned to a version, so there is nothing to replay", want.Subject)
179
- }
180
-
181
- held, err := replay(at)
182
- if err != nil {
183
- return nil, err
184
- }
185
- if held.lock.Version != want.Version {
186
- return nil, fmt.Errorf("%s holds version %d but the manifest pins %d; fetch it",
187
- want.Subject, held.lock.Version, want.Version)
188
- }
189
- if held.lock.Subject != want.Subject {
190
- return nil, fmt.Errorf("%s vendors into %s, which holds %s",
191
- want.Subject, filepath.ToSlash(at), held.lock.Subject)
192
- }
193
- if held.registry != "" && held.registry != registry {
194
- return nil, fmt.Errorf("%s was fetched from %s but the manifest names %s; fetch it again",
195
- want.Subject, held.registry, registry)
196
- }
197
-
198
- for _, entry := range held.lock.Files {
199
- b.File(path.Join(dir, entry.Path), string(held.files[entry.Path]))
200
- }
201
- if err := emitLock(b, registry, dir, held.lock); err != nil {
202
- return nil, err
203
- }
204
-
205
- b.Warn(want.Subject, "not fetched ("+why+"); the copy committed in this repository is used unchanged")
206
-
207
- return held.lock.References, nil
208
- }
209
-
210
- func emitLock(b *plugin.Builder, registry, dir string, entry LockSubject) error {
211
- lock, err := Lock{Registry: registry, Subjects: []LockSubject{entry}}.encode()
212
- if err != nil {
213
- return err
214
- }
215
- b.File(path.Join(dir, LockName), lock)
216
-
217
- return nil
218
- }
219
-
220
- // slugOf is the directory a subject vendors into.
221
- //
222
- // A subject name is whatever the producer registered, and under
223
- // RecordNameStrategy that is a language's full name; a directory is not. So
224
- // everything outside a safe set becomes an underscore, and the true subject is
225
- // written in the lock beside the file rather than inferred back from the path.
226
- func slugOf(subject string) string {
227
- var out strings.Builder
228
-
229
- for _, r := range subject {
230
- switch {
231
- case r >= 'a' && r <= 'z', r >= 'A' && r <= 'Z', r >= '0' && r <= '9':
232
- out.WriteRune(r)
233
- case r == '.' || r == '-' || r == '_':
234
- out.WriteRune(r)
235
- default:
236
- out.WriteByte('_')
237
- }
238
- }
239
-
240
- slug := strings.Trim(out.String(), ".")
241
- if slug == "" {
242
- return "_"
243
- }
244
-
245
- return slug
246
- }
247
-
248
- func pin(version int) string {
249
- if version == 0 {
250
- return "latest"
251
- }
252
-
253
- return strconv.Itoa(version)
254
- }