@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,343 +0,0 @@
1
- package main
2
-
3
- // JSON Schema, read for the same two things Avro is read for.
4
- //
5
- // The one thing that has to be careful here is ORDER. An Avro record lists its
6
- // fields in an array, so reading them into Go keeps the order the author wrote
7
- // them in. A JSON Schema lists them as the keys of an object, and decoding an
8
- // object into a map loses that order - which would put the fields on the page
9
- // in a different arrangement every run and rewrite the fragment each time. So
10
- // the properties are walked with a token decoder instead.
11
-
12
- import (
13
- "bytes"
14
- "encoding/json"
15
- "fmt"
16
- "sort"
17
- "strings"
18
-
19
- "github.com/shortlink-org/portolan/catalog"
20
- )
21
-
22
- // jsonSchema reads one .json into the shapes it declares, the same way avro
23
- // does: the name of the shape at the top, and every named shape inside it.
24
- //
25
- // fallback names the top-level shape when the document does not name itself,
26
- // which most of them do not: a JSON Schema's `title` is optional and a
27
- // registered one usually leans on the subject to say what it is.
28
- func jsonSchema(raw []byte, fallback string) (string, map[string]catalog.TypeDef, error) {
29
- var node jsonNode
30
- if err := json.Unmarshal(raw, &node); err != nil {
31
- return "", nil, fmt.Errorf("not a JSON Schema: %w", err)
32
- }
33
-
34
- name := firstNonEmpty(node.Title, fallback)
35
- r := &jsonReader{defs: map[string]catalog.TypeDef{}, prefix: name}
36
-
37
- // The named shapes a document keeps to one side are read first, so a `$ref`
38
- // pointing at one resolves to a def rather than to a bare label.
39
- //
40
- // They are filed under the document's own name. `definitions` is local to
41
- // the document by definition, and `defs` in the catalog is not: two
42
- // services that both keep a "Carrier" to one side, meaning different
43
- // things, would otherwise collide in the merge and be reported as a
44
- // disagreement neither of them is having.
45
- for _, held := range []map[string]json.RawMessage{node.Definitions, node.Defs} {
46
- for _, key := range keysInOrder(held) {
47
- var nested jsonNode
48
- if err := json.Unmarshal(held[key], &nested); err != nil {
49
- continue
50
- }
51
- r.shape(nested, r.qualify(key))
52
- }
53
- }
54
-
55
- r.shape(node, name)
56
-
57
- return name, r.defs, nil
58
- }
59
-
60
- // jsonNode is the part of a JSON Schema this reads. Everything it leaves out -
61
- // every keyword that constrains a value rather than naming or shaping it - is
62
- // the registry's business, not the catalog's.
63
- type jsonNode struct {
64
- Title string `json:"title"`
65
- Description string `json:"description"`
66
- Type json.RawMessage `json:"type"`
67
- Format string `json:"format"`
68
- Ref string `json:"$ref"`
69
- Items *jsonNode `json:"items"`
70
- Required []string `json:"required"`
71
- OneOf []jsonNode `json:"oneOf"`
72
- AnyOf []jsonNode `json:"anyOf"`
73
-
74
- Properties json.RawMessage `json:"properties"`
75
- Definitions map[string]json.RawMessage `json:"definitions"`
76
- Defs map[string]json.RawMessage `json:"$defs"`
77
- }
78
-
79
- type jsonReader struct {
80
- defs map[string]catalog.TypeDef
81
- open map[string]bool
82
-
83
- // prefix is the document's own shape name, which is what makes a
84
- // document-local definition globally addressable.
85
- prefix string
86
- }
87
-
88
- // qualify files a document-local name under the document.
89
- func (r *jsonReader) qualify(name string) string {
90
- if r.prefix == "" || name == "" {
91
- return name
92
- }
93
-
94
- return r.prefix + "." + name
95
- }
96
-
97
- // declared says whether a shape is one this reader wrote down, including one
98
- // still being read further up the stack.
99
- func (r *jsonReader) declared(name string) bool {
100
- if r.open[name] {
101
- return true
102
- }
103
- _, written := r.defs[name]
104
-
105
- return written
106
- }
107
-
108
- // shape writes down one object's fields, under the name given, and returns
109
- // whether it wrote anything. A schema with no properties is not a shape.
110
- func (r *jsonReader) shape(node jsonNode, name string) bool {
111
- if name == "" || len(node.Properties) == 0 {
112
- return false
113
- }
114
- if r.open[name] {
115
- return true
116
- }
117
- if _, written := r.defs[name]; written {
118
- return true
119
- }
120
-
121
- if r.open == nil {
122
- r.open = map[string]bool{}
123
- }
124
- r.open[name] = true
125
- defer delete(r.open, name)
126
-
127
- required := map[string]bool{}
128
- for _, key := range node.Required {
129
- required[key] = true
130
- }
131
-
132
- // Written before the fields are read, so a schema that names itself inside
133
- // itself finds an entry rather than recursing forever.
134
- r.defs[name] = catalog.TypeDef{Fields: []catalog.Field{}}
135
-
136
- fields := []catalog.Field{}
137
- for _, key := range propertiesInOrder(node.Properties) {
138
- var property jsonNode
139
- if err := json.Unmarshal(key.value, &property); err != nil {
140
- continue
141
- }
142
-
143
- label, ref := r.typeOf(property, name+"."+key.name)
144
- if !required[key.name] && !strings.HasSuffix(label, "?") {
145
- label += "?"
146
- }
147
-
148
- fields = append(fields, catalog.Field{
149
- Name: key.name,
150
- Type: label,
151
- Doc: strings.TrimSpace(property.Description),
152
- Ref: ref,
153
- })
154
- }
155
-
156
- r.defs[name] = catalog.TypeDef{Fields: fields}
157
-
158
- return true
159
- }
160
-
161
- // typeOf renders one property, and returns the defs key when it is an object
162
- // this reader wrote down.
163
- //
164
- // path is what an anonymous nested object is called: a schema that inlines an
165
- // address under `shipping` has named it, just not with a `title`, and
166
- // "OrderPlaced.shipping" is the name it gave it.
167
- func (r *jsonReader) typeOf(node jsonNode, path string) (label, ref string) {
168
- if node.Ref != "" {
169
- // The label stays the name the document uses - "Carrier" is what a
170
- // reader of the schema sees - while the ref carries the qualified key
171
- // the catalog files it under. The two are allowed to differ, and here
172
- // they have to.
173
- name := refName(node.Ref)
174
- if key := r.qualify(name); r.declared(key) {
175
- return name, key
176
- }
177
-
178
- return name, ""
179
- }
180
-
181
- if members := append(append([]jsonNode{}, node.OneOf...), node.AnyOf...); len(members) > 0 {
182
- return r.choice(members, path), ""
183
- }
184
-
185
- kinds, nullable := typeNames(node.Type)
186
-
187
- if node.Items != nil {
188
- items, _ := r.typeOf(*node.Items, path)
189
-
190
- return suffix(items+"[]", nullable), ""
191
- }
192
-
193
- if len(node.Properties) > 0 {
194
- name := firstNonEmpty(node.Title, path)
195
- if r.shape(node, name) {
196
- return suffix(name, nullable), name
197
- }
198
- }
199
-
200
- // A format says what the string MEANS, and it is what belongs on a page:
201
- // "date-time" says more than the "string" underneath it.
202
- if node.Format != "" {
203
- return suffix(node.Format, nullable), ""
204
- }
205
- if len(kinds) == 0 {
206
- return suffix(firstNonEmpty(node.Title, "any"), nullable), ""
207
- }
208
-
209
- return suffix(strings.Join(kinds, "|"), nullable), ""
210
- }
211
-
212
- // choice renders oneOf/anyOf. A member that is only `{"type":"null"}` is the
213
- // document saying the value is optional, which is a "?" rather than a member.
214
- func (r *jsonReader) choice(members []jsonNode, path string) string {
215
- var (
216
- labels []string
217
- nullable bool
218
- )
219
-
220
- for _, member := range members {
221
- kinds, memberNull := typeNames(member.Type)
222
- if memberNull || (len(kinds) == 1 && kinds[0] == "null") {
223
- nullable = true
224
- if len(kinds) <= 1 && member.Ref == "" && len(member.Properties) == 0 {
225
- continue
226
- }
227
- }
228
-
229
- label, _ := r.typeOf(member, path)
230
- labels = append(labels, strings.TrimSuffix(label, "?"))
231
- }
232
-
233
- if len(labels) == 0 {
234
- return "null"
235
- }
236
-
237
- return suffix(strings.Join(labels, "|"), nullable)
238
- }
239
-
240
- // typeNames reads `type`, which is either a string or an array of them, and
241
- // pulls "null" out of the array into the nullability it actually means.
242
- func typeNames(raw json.RawMessage) (kinds []string, nullable bool) {
243
- if len(raw) == 0 {
244
- return nil, false
245
- }
246
-
247
- var one string
248
- if json.Unmarshal(raw, &one) == nil {
249
- if one == "null" {
250
- return nil, true
251
- }
252
-
253
- return []string{one}, false
254
- }
255
-
256
- var many []string
257
- if json.Unmarshal(raw, &many) != nil {
258
- return nil, false
259
- }
260
-
261
- for _, kind := range many {
262
- if kind == "null" {
263
- nullable = true
264
-
265
- continue
266
- }
267
- kinds = append(kinds, kind)
268
- }
269
-
270
- return kinds, nullable
271
- }
272
-
273
- func suffix(label string, nullable bool) string {
274
- if nullable && !strings.HasSuffix(label, "?") {
275
- return label + "?"
276
- }
277
-
278
- return label
279
- }
280
-
281
- // refName is the last segment of a `$ref`, which is the name the definition is
282
- // filed under. A `$ref` reaching outside the document names something this
283
- // extractor has never seen, and the last segment is still the best label for
284
- // it.
285
- func refName(ref string) string {
286
- at := strings.LastIndex(ref, "/")
287
- if at < 0 {
288
- return ref
289
- }
290
-
291
- return ref[at+1:]
292
- }
293
-
294
- type property struct {
295
- name string
296
- value json.RawMessage
297
- }
298
-
299
- // propertiesInOrder walks an object with a token decoder, which is the only
300
- // way to keep the order its keys were written in.
301
- func propertiesInOrder(raw json.RawMessage) []property {
302
- if len(raw) == 0 {
303
- return nil
304
- }
305
-
306
- decoder := json.NewDecoder(bytes.NewReader(raw))
307
- if _, err := decoder.Token(); err != nil { // the opening brace
308
- return nil
309
- }
310
-
311
- var out []property
312
- for decoder.More() {
313
- key, err := decoder.Token()
314
- if err != nil {
315
- return out
316
- }
317
- name, isString := key.(string)
318
- if !isString {
319
- return out
320
- }
321
-
322
- var value json.RawMessage
323
- if err := decoder.Decode(&value); err != nil {
324
- return out
325
- }
326
- out = append(out, property{name: name, value: value})
327
- }
328
-
329
- return out
330
- }
331
-
332
- // keysInOrder is the same problem for `definitions`, which is decoded into a
333
- // map because nothing about it is ordered on the page - only sorted, so the
334
- // fragment comes out the same way every run.
335
- func keysInOrder(held map[string]json.RawMessage) []string {
336
- names := make([]string, 0, len(held))
337
- for name := range held {
338
- names = append(names, name)
339
- }
340
- sort.Strings(names)
341
-
342
- return names
343
- }
@@ -1,25 +0,0 @@
1
- package main
2
-
3
- // The lock, as this side reads it.
4
- //
5
- // A narrower copy of what portolan-fetch-csr writes, on purpose: this plugin
6
- // needs the subject, the version and which file holds the schema, and reading
7
- // a shape it does not use would be a second place to keep the writer's
8
- // decisions in step. The two are held together by the fields' names, which is
9
- // the same contract every fragment in this repository is held to.
10
- type Lock struct {
11
- Registry string `json:"registry"`
12
- Subjects []LockSubject `json:"subjects"`
13
- }
14
-
15
- type LockSubject struct {
16
- Subject string `json:"subject"`
17
- Version int `json:"version"`
18
- ID int `json:"id"`
19
- SchemaType string `json:"schemaType"`
20
- Files []LockEntry `json:"files"`
21
- }
22
-
23
- type LockEntry struct {
24
- Path string `json:"path"`
25
- }
@@ -1,84 +0,0 @@
1
- // Package main is portolan-extract-csr: schemas vendored out of a Confluent
2
- // Schema Registry in, a catalog fragment out.
3
- //
4
- // It reads what portolan-fetch-csr committed - a directory per subject, each
5
- // holding one schema and the `csr.lock.json` that says which registration it
6
- // is - and answers with the two facts a registry knows that nothing else in
7
- // the estate does: which topics carry which messages, and what those messages
8
- // are shaped like, field by field.
9
- //
10
- // It opens nothing. That is the point of the split: portolan-fetch-csr owns
11
- // the socket and the credential, this owns the reading, and CI runs this over
12
- // a tree it can verify without a registry existing at all.
13
- //
14
- // What it does NOT do is invent events. An Event in the catalog belongs to an
15
- // aggregate, and a registry has no idea which aggregate raises what - it holds
16
- // schemas, not domains. So the shapes land in `defs`, where a shared shape
17
- // belongs, and the topics land in the service's channels, the same place an
18
- // AsyncAPI document's do. The domain extractor says an aggregate raises
19
- // OrderPlaced and calls it `shop.oms.OrderPlaced` on the wire; this says a
20
- // schema by that name is registered against topic `shop.oms.order` and has
21
- // these fields. Neither knows the other exists, and the pages hold the two
22
- // against each other.
23
- //
24
- // PROTOBUF subjects are named but not shaped. Reading protobuf properly is
25
- // portolan-extract-proto's whole job, and a second, worse parser here would
26
- // be a second answer to one question. Point extract-proto at the vendored
27
- // directory for those.
28
- package main
29
-
30
- import (
31
- "fmt"
32
- "io"
33
- "os"
34
-
35
- "github.com/shortlink-org/portolan/plugin"
36
- )
37
-
38
- // Options are what the manifest tells the extractor: the things a registry
39
- // does not say about the estate its schemas belong to.
40
- type Options struct {
41
- Context string `json:"context,omitempty"`
42
- Service string `json:"service,omitempty"`
43
-
44
- // Paths narrows the search to the directories fetch-csr wrote into,
45
- // relative to the input root. Left out, the whole root is walked for
46
- // locks.
47
- Paths []string `json:"paths,omitempty"`
48
-
49
- // Strategy is how a subject name relates to a topic and a record. It is
50
- // the producer's choice of SubjectNameStrategy, and nothing in the
51
- // registry's answer says which one was used - the same string is a topic
52
- // plus a suffix under one and a record's full name under another.
53
- Strategy string `json:"strategy,omitempty"`
54
-
55
- // Direction is which way the channels this service registers schemas for
56
- // travel, from this service's side. A registry records no producer and no
57
- // consumer, so this cannot be read and has to be told; "send" is the
58
- // default because the service that registers a schema is usually the one
59
- // putting it on the bus.
60
- Direction string `json:"direction,omitempty"`
61
-
62
- // Subjects overrides Direction for named subjects, for the service that
63
- // both publishes and listens.
64
- Subjects map[string]string `json:"subjects,omitempty"`
65
-
66
- Out string `json:"out,omitempty"`
67
- }
68
-
69
- func main() {
70
- if err := run(os.Stdin, os.Stdout); err != nil {
71
- fmt.Fprintln(os.Stderr, "portolan-extract-csr:", err)
72
- os.Exit(1)
73
- }
74
- }
75
-
76
- func run(stdin io.Reader, stdout io.Writer) error {
77
- return plugin.Serve(stdin, stdout, descriptor(), func(req plugin.Request, opts Options) (plugin.Response, error) {
78
- if req.Input.Root == "" {
79
- return plugin.Response{}, fmt.Errorf("no input root: an extractor has nothing to read")
80
- }
81
-
82
- return extract(req.Input, opts)
83
- })
84
- }
@@ -1,102 +0,0 @@
1
- package main
2
-
3
- // Reading a subject name.
4
- //
5
- // A registry stores subjects. A subject is whatever the producer's serializer
6
- // decided to call the registration, and the rule it used - the
7
- // SubjectNameStrategy - is not recorded anywhere in the answer. The same
8
- // string "shop.oms.order-value" is a topic plus a suffix under one strategy
9
- // and a record's full name under another, and nothing but the manifest can
10
- // say which.
11
- //
12
- // So the strategy is told, and everything here is the consequence of being
13
- // told it.
14
-
15
- import "strings"
16
-
17
- // Strategies, by the names Confluent's own serializers go by, shortened.
18
- const (
19
- StrategyTopic = "topic" // TopicNameStrategy
20
- StrategyRecord = "record" // RecordNameStrategy
21
- StrategyTopicRecord = "topic-record" // TopicRecordNameStrategy
22
- )
23
-
24
- // registration is what a subject name resolves to.
25
- type registration struct {
26
- // Topic is the channel address the schema is registered against, empty
27
- // under RecordNameStrategy - which deliberately says nothing about topics,
28
- // so a record can be reused across many.
29
- Topic string
30
-
31
- // Record is the schema's full name, and it is the name a message on the
32
- // bus goes by - the same string an event's Wire.Name carries.
33
- Record string
34
-
35
- // Key is set for the "-key" half of a topic. A key is not a message on the
36
- // channel; it is part of every message on it. So its shape is kept and its
37
- // name is not put on the channel.
38
- Key bool
39
- }
40
-
41
- // resolve reads a subject name into what it registers.
42
- //
43
- // declared is the record's full name as the schema itself gives it, which is
44
- // the only reliable way to split a TopicRecordNameStrategy subject: the
45
- // separator is a hyphen and both halves may contain one, so the suffix is
46
- // matched rather than searched for. Under the other two strategies it is a
47
- // fallback for a schema that names itself nothing.
48
- func resolve(subject, declared, strategy string) registration {
49
- switch strategy {
50
- case StrategyRecord:
51
- return registration{Record: firstNonEmpty(declared, subject)}
52
-
53
- case StrategyTopicRecord:
54
- if declared != "" && strings.HasSuffix(subject, "-"+declared) {
55
- return registration{
56
- Topic: strings.TrimSuffix(subject, "-"+declared),
57
- Record: declared,
58
- }
59
- }
60
- // The schema did not name itself, or named itself something the
61
- // subject does not end with. Splitting at the first hyphen is the only
62
- // thing left, and it is right for the ordinary "orders-com.acme.Order".
63
- topic, record, found := strings.Cut(subject, "-")
64
- if !found {
65
- return registration{Record: firstNonEmpty(declared, subject)}
66
- }
67
-
68
- return registration{Topic: topic, Record: firstNonEmpty(declared, record)}
69
-
70
- default: // StrategyTopic
71
- if topic, found := strings.CutSuffix(subject, "-value"); found {
72
- return registration{Topic: topic, Record: firstNonEmpty(declared, subject)}
73
- }
74
- if topic, found := strings.CutSuffix(subject, "-key"); found {
75
- return registration{Topic: topic, Record: firstNonEmpty(declared, subject), Key: true}
76
- }
77
-
78
- // A subject under this strategy always ends in one of the two, so one
79
- // that does not is a subject registered some other way. It still names
80
- // a shape; it just cannot name a topic.
81
- return registration{Record: firstNonEmpty(declared, subject)}
82
- }
83
- }
84
-
85
- func strategyOf(declared string) string {
86
- switch declared {
87
- case StrategyRecord, StrategyTopicRecord, StrategyTopic:
88
- return declared
89
- }
90
-
91
- return StrategyTopic
92
- }
93
-
94
- func firstNonEmpty(values ...string) string {
95
- for _, value := range values {
96
- if strings.TrimSpace(value) != "" {
97
- return value
98
- }
99
- }
100
-
101
- return ""
102
- }
@@ -1,19 +0,0 @@
1
- package main
2
-
3
- import (
4
- _ "embed"
5
-
6
- "github.com/shortlink-org/portolan/plugin"
7
- )
8
-
9
- //go:embed options.schema.json
10
- var optionsSchema []byte
11
-
12
- func descriptor() plugin.Descriptor {
13
- return plugin.Descriptor{
14
- Name: "extract-flows",
15
- Summary: "Reads flows written by hand as *.flow.md - one line per hop, frames closed by end - into a catalog fragment.",
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,91 +0,0 @@
1
- package main
2
-
3
- import (
4
- "encoding/json"
5
- "errors"
6
- "os"
7
- "path/filepath"
8
- "sort"
9
- "strings"
10
-
11
- "github.com/shortlink-org/portolan/catalog"
12
- "github.com/shortlink-org/portolan/plugin"
13
- )
14
-
15
- // extract reads every flow file the options name and answers with one
16
- // fragment holding the flows. A file that does not parse fails the whole run,
17
- // the way a compiler would: a flow written by hand is written to be read, and
18
- // one silently left out of the catalog is the kind of missing nobody notices.
19
- func extract(in plugin.Input, opts Options) (plugin.Response, error) {
20
- b := &plugin.Builder{}
21
-
22
- patterns := opts.Files
23
- if len(patterns) == 0 {
24
- patterns = []string{"*.flow.md"}
25
- }
26
- var files []string
27
- seen := map[string]bool{}
28
- for _, pattern := range patterns {
29
- matches, err := filepath.Glob(filepath.Join(in.Root, pattern))
30
- if err != nil {
31
- return plugin.Response{}, err
32
- }
33
- for _, match := range matches {
34
- if !seen[match] {
35
- seen[match] = true
36
- files = append(files, match)
37
- }
38
- }
39
- }
40
- sort.Strings(files)
41
- if len(files) == 0 {
42
- b.Warn(in.Root, "no flow files matched "+strings.Join(patterns, ", ")+"; the fragment holds no flows")
43
- }
44
-
45
- flows := []catalog.Flow{}
46
- var problems []string
47
- slugs := map[string]string{}
48
- for _, file := range files {
49
- src, err := os.ReadFile(file)
50
- if err != nil {
51
- return plugin.Response{}, err
52
- }
53
- rel := filepath.ToSlash(file)
54
- flow, errs := parseFlow(rel, string(src))
55
- if len(errs) > 0 {
56
- problems = append(problems, errs...)
57
-
58
- continue
59
- }
60
- if other, taken := slugs[flow.Slug]; taken {
61
- problems = append(problems, rel+": flow "+flow.Slug+" is already declared in "+other)
62
-
63
- continue
64
- }
65
- slugs[flow.Slug] = rel
66
- flows = append(flows, flow)
67
- }
68
- if len(problems) > 0 {
69
- return plugin.Response{}, errors.New(strings.Join(problems, "\n"))
70
- }
71
-
72
- fragment := catalog.Catalog{
73
- GeneratedAt: in.GeneratedAt,
74
- Commit: in.Commit,
75
- Contexts: []catalog.BoundedContext{},
76
- Defs: map[string]catalog.TypeDef{},
77
- Flows: flows,
78
- Adrs: []catalog.Adr{},
79
- }
80
- encoded, err := json.MarshalIndent(fragment, "", " ")
81
- if err != nil {
82
- return plugin.Response{}, err
83
- }
84
- out := opts.Out
85
- if out == "" {
86
- out = "flows.json"
87
- }
88
- b.File(out, string(encoded)+"\n")
89
-
90
- return b.Response(), nil
91
- }