@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,494 +0,0 @@
1
- package main
2
-
3
- // The fetcher, against a recorded registry. NEVER against a live one: a test
4
- // that reaches a Confluent endpoint would fail on a train, and the whole point
5
- // of this plugin's design is that the build does not depend on a registry
6
- // being up.
7
-
8
- import (
9
- "encoding/base64"
10
- "encoding/json"
11
- "net/http"
12
- "net/http/httptest"
13
- "os"
14
- "path/filepath"
15
- "sort"
16
- "strings"
17
- "testing"
18
-
19
- "github.com/shortlink-org/portolan/plugin"
20
- )
21
-
22
- const (
23
- valueSubject = "shop.oms.order-value"
24
- partySubject = "shop.oms.Party"
25
- )
26
-
27
- // The value schema references the party one, which is how a registry says a
28
- // record is shared between subjects.
29
- var (
30
- orderSchema = `{"type":"record","namespace":"shop.oms","name":"OrderPlaced","fields":[{"name":"order_id","type":"string"},{"name":"buyer","type":"shop.oms.Party"}]}`
31
- partySchema = `{"type":"record","namespace":"shop.oms","name":"Party","fields":[{"name":"id","type":"string"}]}`
32
- )
33
-
34
- // registry serves the one call this plugin makes, from recorded shapes.
35
- func registry(t *testing.T, seen *[]string) *httptest.Server {
36
- t.Helper()
37
-
38
- answers := map[string]registration{
39
- "/subjects/shop.oms.order-value/versions/3": {
40
- Subject: valueSubject, Version: 3, ID: 100021, GUID: "8f0d",
41
- SchemaType: "AVRO", Schema: orderSchema,
42
- References: []Reference{{Name: "shop.oms.Party", Subject: partySubject, Version: 1}},
43
- },
44
- "/subjects/shop.oms.order-value/versions/latest": {
45
- Subject: valueSubject, Version: 3, ID: 100021, GUID: "8f0d",
46
- SchemaType: "AVRO", Schema: orderSchema,
47
- References: []Reference{{Name: "shop.oms.Party", Subject: partySubject, Version: 1}},
48
- },
49
- "/subjects/shop.oms.Party/versions/1": {
50
- Subject: partySubject, Version: 1, ID: 100014,
51
- SchemaType: "AVRO", Schema: partySchema,
52
- },
53
- }
54
-
55
- return httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
56
- if seen != nil {
57
- *seen = append(*seen, r.Header.Get("Authorization"))
58
- }
59
-
60
- w.Header().Set("Content-Type", "application/json")
61
-
62
- answer, known := answers[r.URL.Path]
63
- if !known {
64
- w.WriteHeader(http.StatusNotFound)
65
- _, _ = w.Write([]byte(`{"error_code":40401,"message":"Subject '` + r.URL.Path + `' not found."}`))
66
-
67
- return
68
- }
69
-
70
- _ = json.NewEncoder(w).Encode(answer)
71
- }))
72
- }
73
-
74
- func options(server *httptest.Server, cache string, subjects ...Subject) Options {
75
- return Options{Registry: server.URL, Cache: cache, Subjects: subjects}
76
- }
77
-
78
- // names is the file list, sorted, so a test can say what a run produced
79
- // without depending on the order the queue happened to drain in.
80
- func names(resp plugin.Response) []string {
81
- var out []string
82
- for _, f := range resp.Files {
83
- out = append(out, f.Name)
84
- }
85
- sort.Strings(out)
86
-
87
- return out
88
- }
89
-
90
- func contents(t *testing.T, resp plugin.Response, name string) string {
91
- t.Helper()
92
-
93
- for _, f := range resp.Files {
94
- if f.Name == name {
95
- return f.Contents
96
- }
97
- }
98
- t.Fatalf("no file named %s in %v", name, names(resp))
99
-
100
- return ""
101
- }
102
-
103
- // write is the host's half: it puts a response into a tree, so the next run
104
- // can replay from it the way a real one replays from what was committed.
105
- func write(t *testing.T, root string, resp plugin.Response) {
106
- t.Helper()
107
-
108
- for _, f := range resp.Files {
109
- at := filepath.Join(root, filepath.FromSlash(f.Name))
110
- if err := os.MkdirAll(filepath.Dir(at), 0o755); err != nil {
111
- t.Fatal(err)
112
- }
113
- if err := os.WriteFile(at, []byte(f.Contents), 0o644); err != nil {
114
- t.Fatal(err)
115
- }
116
- }
117
- }
118
-
119
- func offlineOff(t *testing.T) {
120
- t.Helper()
121
- t.Setenv(OfflineEnv, "")
122
- t.Setenv("CI", "")
123
- }
124
-
125
- func TestFetchWritesSchemaAndLock(t *testing.T) {
126
- offlineOff(t)
127
- server := registry(t, nil)
128
- defer server.Close()
129
-
130
- cache := t.TempDir()
131
- resp, err := fetch(options(server, cache, Subject{Subject: valueSubject, Version: 3}))
132
- if err != nil {
133
- t.Fatal(err)
134
- }
135
-
136
- want := []string{
137
- "shop.oms.Party/csr.lock.json",
138
- "shop.oms.Party/v1.avsc",
139
- "shop.oms.order-value/csr.lock.json",
140
- "shop.oms.order-value/v3.avsc",
141
- }
142
- if got := names(resp); !equal(got, want) {
143
- t.Errorf("files:\n got %v\nwant %v", got, want)
144
- }
145
-
146
- // The reference was followed without the manifest naming it: the referring
147
- // schema pinned it, which is the whole argument for following one at all.
148
- if got := contents(t, resp, "shop.oms.Party/v1.avsc"); !strings.Contains(got, `"name": "Party"`) {
149
- t.Errorf("the referenced schema is not the one the registry served:\n%s", got)
150
- }
151
-
152
- // Indented rather than the one line the registry answers with, so a
153
- // version bump is a diff a person can read.
154
- schema := contents(t, resp, "shop.oms.order-value/v3.avsc")
155
- if !strings.Contains(schema, "\n \"type\": \"record\"") {
156
- t.Errorf("the schema was not indented:\n%s", schema)
157
- }
158
- if !strings.HasSuffix(schema, "\n") {
159
- t.Error("the schema does not end in a newline")
160
- }
161
- // json.Indent reformats without reordering, so the file still says what
162
- // the registry said in the order it said it.
163
- if strings.Index(schema, `"namespace"`) > strings.Index(schema, `"name"`) {
164
- t.Errorf("the schema's keys were reordered:\n%s", schema)
165
- }
166
-
167
- var lock Lock
168
- if err := json.Unmarshal([]byte(contents(t, resp, "shop.oms.order-value/csr.lock.json")), &lock); err != nil {
169
- t.Fatal(err)
170
- }
171
- if lock.Registry != server.URL {
172
- t.Errorf("lock names registry %q, want %q", lock.Registry, server.URL)
173
- }
174
- if len(lock.Subjects) != 1 {
175
- t.Fatalf("lock names %d subjects, want 1", len(lock.Subjects))
176
- }
177
-
178
- entry := lock.Subjects[0]
179
- if entry.Subject != valueSubject || entry.Version != 3 || entry.ID != 100021 {
180
- t.Errorf("lock entry: %+v", entry)
181
- }
182
- if entry.SchemaType != "AVRO" {
183
- t.Errorf("schema type %q, want AVRO", entry.SchemaType)
184
- }
185
- if len(entry.References) != 1 || entry.References[0].Subject != partySubject {
186
- t.Errorf("references: %+v", entry.References)
187
- }
188
- if len(entry.Files) != 1 || entry.Files[0].Path != "v3.avsc" {
189
- t.Fatalf("files: %+v", entry.Files)
190
- }
191
- // The digest is over the bytes as written, so verifying needs no
192
- // reformatting of anything.
193
- if entry.Files[0].SHA256 != digestOf([]byte(schema)) {
194
- t.Error("the digest is not over the bytes that were written")
195
- }
196
- if entry.Files[0].Size != len(schema) {
197
- t.Errorf("size %d, want %d", entry.Files[0].Size, len(schema))
198
- }
199
- }
200
-
201
- func TestAbsentSchemaTypeIsAvro(t *testing.T) {
202
- offlineOff(t)
203
-
204
- server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
205
- // A registry that predates the field says nothing, and that silence
206
- // means Avro rather than "unknown".
207
- _, _ = w.Write([]byte(`{"subject":"legacy-value","version":1,"id":7,"schema":` +
208
- mustQuote(partySchema) + `}`))
209
- }))
210
- defer server.Close()
211
-
212
- resp, err := fetch(options(server, t.TempDir(), Subject{Subject: "legacy-value", Version: 1}))
213
- if err != nil {
214
- t.Fatal(err)
215
- }
216
- if got := names(resp); !equal(got, []string{"legacy-value/csr.lock.json", "legacy-value/v1.avsc"}) {
217
- t.Errorf("files: %v", got)
218
- }
219
- }
220
-
221
- func TestUnpinnedResolvesLatestAndSaysSo(t *testing.T) {
222
- offlineOff(t)
223
- server := registry(t, nil)
224
- defer server.Close()
225
-
226
- resp, err := fetch(options(server, t.TempDir(), Subject{Subject: valueSubject}))
227
- if err != nil {
228
- t.Fatal(err)
229
- }
230
- if got := contents(t, resp, "shop.oms.order-value/v3.avsc"); got == "" {
231
- t.Error("latest resolved to nothing")
232
- }
233
-
234
- warnings := resp.Warnings()
235
- if len(warnings) != 1 || !strings.Contains(warnings[0].Message, "not pinned") {
236
- t.Errorf("an unpinned subject was not reported: %+v", warnings)
237
- }
238
- }
239
-
240
- func TestOfflineReplaysWhatWasCommitted(t *testing.T) {
241
- offlineOff(t)
242
- server := registry(t, nil)
243
-
244
- cache := t.TempDir()
245
- opts := options(server, cache, Subject{Subject: valueSubject, Version: 3})
246
-
247
- first, err := fetch(opts)
248
- if err != nil {
249
- t.Fatal(err)
250
- }
251
- write(t, cache, first)
252
-
253
- // The registry is gone AND the plugin is told not to look, which is CI.
254
- server.Close()
255
- t.Setenv(OfflineEnv, "1")
256
-
257
- second, err := fetch(opts)
258
- if err != nil {
259
- t.Fatal(err)
260
- }
261
-
262
- // Byte-identical, or `gen:check` would go red on a build that changed
263
- // nothing.
264
- if !sameFiles(first, second) {
265
- t.Errorf("the offline run differs:\n online %v\noffline %v", names(first), names(second))
266
- }
267
- // The reference was followed from the lock rather than from the registry.
268
- if got := contents(t, second, "shop.oms.Party/v1.avsc"); got == "" {
269
- t.Error("the referenced subject was not replayed")
270
- }
271
- }
272
-
273
- func TestOfflineRefusesAnUnpinnedSubject(t *testing.T) {
274
- t.Setenv(OfflineEnv, "1")
275
- server := registry(t, nil)
276
- defer server.Close()
277
-
278
- _, err := fetch(options(server, t.TempDir(), Subject{Subject: valueSubject}))
279
- if err == nil || !strings.Contains(err.Error(), "not pinned") {
280
- t.Errorf("an unpinned subject was replayed offline: %v", err)
281
- }
282
- }
283
-
284
- func TestAFailedFetchFallsBackToTheTree(t *testing.T) {
285
- offlineOff(t)
286
- server := registry(t, nil)
287
-
288
- cache := t.TempDir()
289
- opts := options(server, cache, Subject{Subject: valueSubject, Version: 3})
290
-
291
- first, err := fetch(opts)
292
- if err != nil {
293
- t.Fatal(err)
294
- }
295
- write(t, cache, first)
296
-
297
- // Rule 2: the registry is down, the tree still holds a good copy, so the
298
- // output is unchanged.
299
- server.Close()
300
-
301
- second, err := fetch(opts)
302
- if err != nil {
303
- t.Fatal(err)
304
- }
305
- if !sameFiles(first, second) {
306
- t.Error("a fetch that failed over to the tree produced different files")
307
- }
308
- if len(second.Warnings()) == 0 {
309
- t.Error("falling back to the tree was not reported")
310
- }
311
- }
312
-
313
- func TestAFailedFetchWithNoTreeIsAnError(t *testing.T) {
314
- offlineOff(t)
315
- server := registry(t, nil)
316
- server.Close()
317
-
318
- // Rule 3: never a short file list. The host deletes what a step stops
319
- // naming, and a laptop going offline must not empty the vendored schemas.
320
- _, err := fetch(options(server, t.TempDir(), Subject{Subject: valueSubject, Version: 3}))
321
- if err == nil {
322
- t.Fatal("a fetch with nothing to fall back to succeeded")
323
- }
324
- if !strings.Contains(err.Error(), "no usable copy") {
325
- t.Errorf("the error does not say the tree is empty: %v", err)
326
- }
327
- }
328
-
329
- func TestAnEditedVendoredCopyIsReported(t *testing.T) {
330
- offlineOff(t)
331
- server := registry(t, nil)
332
-
333
- cache := t.TempDir()
334
- opts := options(server, cache, Subject{Subject: valueSubject, Version: 3})
335
-
336
- first, err := fetch(opts)
337
- if err != nil {
338
- t.Fatal(err)
339
- }
340
- write(t, cache, first)
341
-
342
- at := filepath.Join(cache, "shop.oms.order-value", "v3.avsc")
343
- if err := os.WriteFile(at, []byte("{}\n"), 0o644); err != nil {
344
- t.Fatal(err)
345
- }
346
-
347
- server.Close()
348
- t.Setenv(OfflineEnv, "1")
349
-
350
- _, err = fetch(opts)
351
- if err == nil || !strings.Contains(err.Error(), "edited by hand") {
352
- t.Errorf("an edited vendored copy was accepted: %v", err)
353
- }
354
- }
355
-
356
- func TestAMovedPinIsReportedRatherThanReplayed(t *testing.T) {
357
- offlineOff(t)
358
- server := registry(t, nil)
359
-
360
- cache := t.TempDir()
361
- first, err := fetch(options(server, cache, Subject{Subject: valueSubject, Version: 3}))
362
- if err != nil {
363
- t.Fatal(err)
364
- }
365
- write(t, cache, first)
366
-
367
- server.Close()
368
- t.Setenv(OfflineEnv, "1")
369
-
370
- _, err = fetch(options(server, cache, Subject{Subject: valueSubject, Version: 4}))
371
- if err == nil || !strings.Contains(err.Error(), "holds version 3") {
372
- t.Errorf("a moved pin was replayed from the old version: %v", err)
373
- }
374
- }
375
-
376
- func TestOneSubjectAtTwoVersionsIsRefused(t *testing.T) {
377
- offlineOff(t)
378
- server := registry(t, nil)
379
- defer server.Close()
380
-
381
- _, err := fetch(options(server, t.TempDir(),
382
- Subject{Subject: valueSubject, Version: 3},
383
- Subject{Subject: valueSubject, Version: 2},
384
- ))
385
- if err == nil || !strings.Contains(err.Error(), "one version per subject") {
386
- t.Errorf("two versions of one subject were fetched into one directory: %v", err)
387
- }
388
- }
389
-
390
- func TestAMissingSubjectSaysWhatTheRegistrySaid(t *testing.T) {
391
- offlineOff(t)
392
- server := registry(t, nil)
393
- defer server.Close()
394
-
395
- _, err := fetch(options(server, t.TempDir(), Subject{Subject: "nope-value", Version: 1}))
396
- if err == nil || !strings.Contains(err.Error(), "not found") {
397
- t.Errorf("the registry's own message was lost: %v", err)
398
- }
399
- }
400
-
401
- func TestTheCredentialComesFromTheEnvironment(t *testing.T) {
402
- offlineOff(t)
403
-
404
- var seen []string
405
- server := registry(t, &seen)
406
- defer server.Close()
407
-
408
- t.Setenv(TokenEnv, "")
409
- t.Setenv(ShortKeyEnv, "KEY")
410
- t.Setenv(ShortSecretEnv, "SECRET")
411
- t.Setenv(KeyEnv, "")
412
- t.Setenv(SecretEnv, "")
413
-
414
- if _, err := fetch(options(server, t.TempDir(), Subject{Subject: valueSubject, Version: 3})); err != nil {
415
- t.Fatal(err)
416
- }
417
-
418
- want := "Basic " + base64.StdEncoding.EncodeToString([]byte("KEY:SECRET"))
419
- for _, got := range seen {
420
- if got != want {
421
- t.Fatalf("Authorization %q, want %q", got, want)
422
- }
423
- }
424
- if len(seen) == 0 {
425
- t.Fatal("the registry was never called")
426
- }
427
- }
428
-
429
- func TestABareTokenIsGivenAScheme(t *testing.T) {
430
- t.Setenv(TokenEnv, "abc123")
431
- if got := authorization(); got != "Bearer abc123" {
432
- t.Errorf("authorization %q", got)
433
- }
434
-
435
- // One that already names its scheme is passed through: guessing on the
436
- // caller's behalf is how a working token starts failing.
437
- t.Setenv(TokenEnv, "Negotiate abc123")
438
- if got := authorization(); got != "Negotiate abc123" {
439
- t.Errorf("authorization %q", got)
440
- }
441
- }
442
-
443
- func TestSlugOfKeepsTheSubjectReadable(t *testing.T) {
444
- for subject, want := range map[string]string{
445
- "shop.oms.order-value": "shop.oms.order-value",
446
- "shop.oms.OrderPlaced": "shop.oms.OrderPlaced",
447
- "a/b:c value": "a_b_c_value",
448
- "...": "_",
449
- } {
450
- if got := slugOf(subject); got != want {
451
- t.Errorf("slugOf(%q) = %q, want %q", subject, got, want)
452
- }
453
- }
454
- }
455
-
456
- func equal(a, b []string) bool {
457
- if len(a) != len(b) {
458
- return false
459
- }
460
- for i := range a {
461
- if a[i] != b[i] {
462
- return false
463
- }
464
- }
465
-
466
- return true
467
- }
468
-
469
- func sameFiles(a, b plugin.Response) bool {
470
- if len(a.Files) != len(b.Files) {
471
- return false
472
- }
473
-
474
- held := map[string]string{}
475
- for _, f := range a.Files {
476
- held[f.Name] = f.Contents
477
- }
478
- for _, f := range b.Files {
479
- if held[f.Name] != f.Contents {
480
- return false
481
- }
482
- }
483
-
484
- return true
485
- }
486
-
487
- func mustQuote(s string) string {
488
- out, err := json.Marshal(s)
489
- if err != nil {
490
- panic(err)
491
- }
492
-
493
- return string(out)
494
- }
@@ -1,95 +0,0 @@
1
- package main
2
-
3
- // What was fetched, so it can be checked without fetching again.
4
- //
5
- // One lock per subject directory rather than one for the whole step, so a
6
- // vendored subject is self-describing: the schema, the registry it came from,
7
- // the version it was pinned to and the subjects it references sit together,
8
- // and the reader finds the lock by looking beside the file it is already
9
- // reading.
10
-
11
- import (
12
- "crypto/sha256"
13
- "encoding/hex"
14
- "encoding/json"
15
- "sort"
16
- )
17
-
18
- // LockName is the file written beside each subject's schema.
19
- const LockName = "csr.lock.json"
20
-
21
- type Lock struct {
22
- // Registry is where these bytes came from. A module's registry-global name
23
- // carries its registry; a subject's name does not, so it is written down
24
- // here or the vendored copy cannot say where it belongs.
25
- Registry string `json:"registry"`
26
-
27
- Subjects []LockSubject `json:"subjects"`
28
- }
29
-
30
- type LockSubject struct {
31
- Subject string `json:"subject"`
32
-
33
- // Version is what was actually fetched. A registered version is immutable,
34
- // so a pinned download is byte-reproducible and this is what makes
35
- // replaying from disk equivalent to fetching again.
36
- Version int `json:"version"`
37
-
38
- // ID is the registry-global schema id, and GUID the wider identifier for
39
- // the same schema. Neither is needed to replay; both are what a message on
40
- // the wire actually carries, so an incident that has a schema id and
41
- // nothing else can be traced back to this file.
42
- ID int `json:"id"`
43
- GUID string `json:"guid,omitempty"`
44
-
45
- SchemaType string `json:"schemaType"`
46
-
47
- // References are the subjects this schema depends on, pinned by the schema
48
- // itself. Each is fetched into its own directory, so this list is also how
49
- // an offline replay knows what else to read.
50
- References []Reference `json:"references,omitempty"`
51
-
52
- Files []LockEntry `json:"files"`
53
- }
54
-
55
- type LockEntry struct {
56
- Path string `json:"path"`
57
-
58
- // SHA256 is over the bytes as written. It is what turns a cache into
59
- // something checkable: a vendored copy someone edited by hand stops
60
- // matching, which is exactly the drift docs/adr/org.0001.md wants visible.
61
- SHA256 string `json:"sha256"`
62
- Size int `json:"size"`
63
- }
64
-
65
- func digestOf(content []byte) string {
66
- sum := sha256.Sum256(content)
67
-
68
- return hex.EncodeToString(sum[:])
69
- }
70
-
71
- // encode writes the lock the way every generated file in this repository is
72
- // written: sorted, indented, newline-terminated. It is committed and compared
73
- // by `gen:check`, so an unstable order would rewrite it on every run.
74
- //
75
- // References are sorted too. The registry answers them in registration order,
76
- // which is stable for a pinned version, but they are a lookup from name to
77
- // subject rather than a sequence, and sorting says so.
78
- func (l Lock) encode() (string, error) {
79
- for i := range l.Subjects {
80
- sort.Slice(l.Subjects[i].Files, func(a, b int) bool {
81
- return l.Subjects[i].Files[a].Path < l.Subjects[i].Files[b].Path
82
- })
83
- sort.Slice(l.Subjects[i].References, func(a, b int) bool {
84
- return l.Subjects[i].References[a].Name < l.Subjects[i].References[b].Name
85
- })
86
- }
87
- sort.Slice(l.Subjects, func(a, b int) bool { return l.Subjects[a].Subject < l.Subjects[b].Subject })
88
-
89
- out, err := json.MarshalIndent(l, "", " ")
90
- if err != nil {
91
- return "", err
92
- }
93
-
94
- return string(out) + "\n", nil
95
- }