@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,526 +0,0 @@
1
- package main
2
-
3
- import (
4
- "encoding/json"
5
- "fmt"
6
- "os"
7
- "path/filepath"
8
- "strings"
9
-
10
- "github.com/shortlink-org/portolan/catalog"
11
- "github.com/shortlink-org/portolan/plugin"
12
- "github.com/shortlink-org/portolan/plugins/openapi"
13
- "gopkg.in/yaml.v3"
14
- )
15
-
16
- func extract(in plugin.Input, opts Options) (plugin.Response, error) {
17
- b := &plugin.Builder{}
18
- root := in.Root
19
-
20
- // Whose document this is: a service of the estate, or a system outside it
21
- // whose copy is vendored beside the adapter that calls it. The two are told
22
- // apart by the manifest and by nothing in the document, because a document
23
- // does not know which side of the boundary it was read on.
24
- external := opts.External != ""
25
- if external && strings.Contains(opts.External, ".") {
26
- return plugin.Response{}, fmt.Errorf("external %q has a dot in its id; an external sits at the root and is addressed by a bare name", opts.External)
27
- }
28
- if !external {
29
- if opts.Context == "" {
30
- opts.Context = filepath.Base(root)
31
- }
32
- if opts.Service == "" {
33
- opts.Service = filepath.Base(root)
34
- }
35
- }
36
- owner := opts.Context + "." + opts.Service
37
- if external {
38
- owner = opts.External
39
- }
40
-
41
- fragment := catalog.Catalog{
42
- GeneratedAt: in.GeneratedAt,
43
- Commit: in.Commit,
44
- Contexts: []catalog.BoundedContext{},
45
- Defs: map[string]catalog.TypeDef{},
46
- Flows: []catalog.Flow{},
47
- Adrs: []catalog.Adr{},
48
- }
49
-
50
- var provides []catalog.RpcService
51
- var externals []catalog.External
52
- if opts.Spec == "" && !external {
53
- // Nothing named: the tree says which documents it implements and
54
- // which it calls.
55
- provides, externals = discover(root, owner, opts, b)
56
- } else {
57
- specPath, err := findSpec(root, opts.Spec, b)
58
- if err != nil {
59
- return plugin.Response{}, err
60
- }
61
-
62
- doc, err := load(specPath)
63
- if err != nil {
64
- return plugin.Response{}, fmt.Errorf("%s: %w", specPath, err)
65
- }
66
-
67
- source := filepath.ToSlash(specPath)
68
- api := firstNonEmpty(opts.API, apiID(doc))
69
-
70
- provides = rpcServices(doc, api, source, b)
71
- if len(provides) == 0 {
72
- b.Warn(owner, source+" declares no operations")
73
- }
74
- }
75
- if provides == nil {
76
- provides = []catalog.RpcService{}
77
- }
78
-
79
- if external {
80
- // Everything the catalog may claim about a system it does not own: what
81
- // it answers on, and what the manifest says it is called and is for.
82
- fragment.Externals = []catalog.External{{
83
- ID: opts.External,
84
- Slug: opts.External,
85
- Name: opts.ExternalName,
86
- Summary: opts.ExternalSummary,
87
- URL: opts.ExternalURL,
88
- Provides: provides,
89
- }}
90
- } else {
91
- fragment.Contexts = []catalog.BoundedContext{{
92
- ID: opts.Context,
93
- Slug: opts.Context,
94
- // Named by whichever source knows the name. This one describes what
95
- // the service answers, not what it is called, and a fragment that
96
- // filled these in from an API title would be inventing.
97
- Services: []catalog.Service{{
98
- ID: opts.Context + "." + opts.Service,
99
- Slug: opts.Service,
100
- Provides: provides,
101
- Consumes: []catalog.RpcCall{},
102
- Aggregates: []catalog.Aggregate{},
103
- }},
104
- }}
105
- if len(externals) > 0 {
106
- fragment.Externals = externals
107
- }
108
- }
109
-
110
- encoded, err := json.MarshalIndent(fragment, "", " ")
111
- if err != nil {
112
- return plugin.Response{}, err
113
- }
114
-
115
- b.File(firstNonEmpty(opts.Out, "api.json"), string(encoded)+"\n")
116
-
117
- return b.Response(), nil
118
- }
119
-
120
- // rpcServices turns one OpenAPI document into one provided HTTP API.
121
- //
122
- // Tags organise operations inside a document; they do not declare independent
123
- // contracts. Treating every tag as an interface repeats shared schemas in every
124
- // tag card and makes one specification look like many APIs.
125
- func rpcServices(doc *document, api, source string, b *plugin.Builder) []catalog.RpcService {
126
- methods := []catalog.RpcMethod{}
127
- schemas := []schemaRef{}
128
- seen := map[string]bool{}
129
- visited := map[string]bool{}
130
-
131
- for _, p := range entries(child(doc.root, "paths")) {
132
- for _, verb := range verbs {
133
- operation := child(p.value, verb)
134
- if operation == nil {
135
- continue
136
- }
137
-
138
- method := text(child(operation, "operationId"))
139
- if method == "" {
140
- // Falling back rather than skipping: an operation with no id is
141
- // still an endpoint, and a reader looking for it wants to find
142
- // it under something.
143
- method = strings.ToUpper(verb) + " " + p.key
144
- b.Warn(api, p.key+" "+strings.ToUpper(verb)+" has no operationId; listed by verb and path")
145
- }
146
-
147
- // The name, the route, and the shapes on either side. The
148
- // document names the last two whenever the body is a $ref, which
149
- // is what lets a flow draw what comes back from a call and not
150
- // only that one was made.
151
- request, response := doc.shapes(p.value, operation)
152
- methods = append(methods, catalog.RpcMethod{
153
- Name: method,
154
- Request: request,
155
- Response: response,
156
- HTTP: &catalog.HttpRoute{Method: strings.ToUpper(verb), Path: p.key},
157
- })
158
- doc.schemaRefs(operation, &schemas, seen, visited)
159
- }
160
- }
161
-
162
- if len(methods) == 0 {
163
- return []catalog.RpcService{}
164
- }
165
-
166
- return []catalog.RpcService{{
167
- ID: openapi.InterfaceID(api, ""),
168
- Methods: methods,
169
- Source: source,
170
- Messages: messages(doc, schemas, seen, visited, b),
171
- }}
172
- }
173
-
174
- // messages turns every schema the group's operations reach into a named shape.
175
- //
176
- // The list grows while it is walked: a request body names a schema, that schema
177
- // names another, and a reader who has to follow three links to find out what
178
- // comes back has been given a worse document than the yaml.
179
- func messages(doc *document, refs []schemaRef, seen, visited map[string]bool, b *plugin.Builder) []catalog.RpcMessage {
180
- out := []catalog.RpcMessage{}
181
- for i := 0; i < len(refs); i++ {
182
- ref := refs[i]
183
- // Anything this schema refers to joins the queue behind it.
184
- ref.doc.schemaRefs(ref.node, &refs, seen, visited)
185
-
186
- out = append(out, catalog.RpcMessage{
187
- Name: ref.name,
188
- Fields: schemaFields(ref.doc, ref.node),
189
- Discriminator: schemaDiscriminator(ref.doc, ref.node),
190
- })
191
- }
192
-
193
- return out
194
- }
195
-
196
- func schemaDiscriminator(doc *document, node *yaml.Node) *catalog.RpcDiscriminator {
197
- return discriminatorIn(doc, node, map[string]bool{})
198
- }
199
-
200
- func discriminatorIn(doc *document, node *yaml.Node, resolving map[string]bool) *catalog.RpcDiscriminator {
201
- if node == nil {
202
- return nil
203
- }
204
- if ref := text(child(node, "$ref")); ref != "" {
205
- if targetDoc, target, pointer, ok := doc.resolve(ref); ok {
206
- key := targetDoc.path + "#" + pointer
207
- if !resolving[key] {
208
- resolving[key] = true
209
- found := discriminatorIn(targetDoc, target, resolving)
210
- delete(resolving, key)
211
- if found != nil {
212
- return found
213
- }
214
- }
215
- }
216
- }
217
-
218
- discriminator := child(node, "discriminator")
219
- property := text(child(discriminator, "propertyName"))
220
- if property != "" {
221
- byMessage := map[string][]string{}
222
- var mapped []catalog.RpcVariant
223
- for _, entry := range entries(child(discriminator, "mapping")) {
224
- if message := schemaMessageName(doc, text(entry.value)); message != "" {
225
- byMessage[message] = append(byMessage[message], entry.key)
226
- mapped = append(mapped, catalog.RpcVariant{Value: entry.key, Message: message})
227
- }
228
- }
229
-
230
- var variants []catalog.RpcVariant
231
- seen := map[string]bool{}
232
- appendVariant := func(variant catalog.RpcVariant) {
233
- key := variant.Value + "\x00" + variant.Message
234
- if !seen[key] {
235
- variants = append(variants, variant)
236
- seen[key] = true
237
- }
238
- }
239
- for _, keyword := range []string{"oneOf", "anyOf"} {
240
- for _, branch := range itemsOf(child(node, keyword)) {
241
- message := schemaMessageName(doc, text(child(branch, "$ref")))
242
- if message == "" {
243
- continue
244
- }
245
- values := byMessage[message]
246
- if len(values) == 0 {
247
- values = []string{message}
248
- }
249
- for _, value := range values {
250
- appendVariant(catalog.RpcVariant{Value: value, Message: message})
251
- }
252
- }
253
- }
254
- for _, variant := range mapped {
255
- appendVariant(variant)
256
- }
257
- return &catalog.RpcDiscriminator{Property: property, Variants: variants}
258
- }
259
-
260
- for _, branch := range itemsOf(child(node, "allOf")) {
261
- if found := discriminatorIn(doc, branch, resolving); found != nil {
262
- return found
263
- }
264
- }
265
- return nil
266
- }
267
-
268
- func schemaMessageName(doc *document, ref string) string {
269
- if ref == "" {
270
- return ""
271
- }
272
- if _, _, pointer, ok := doc.resolve(ref); ok {
273
- if name, schema := schemaName(pointer); schema {
274
- return name
275
- }
276
- }
277
- if name, ok := refName(ref); ok {
278
- return name
279
- }
280
- return ""
281
- }
282
-
283
- func schemaFields(doc *document, node *yaml.Node) []catalog.Field {
284
- fields, required := schemaShape(doc, node, map[string]bool{})
285
- for i := range fields {
286
- if !required[fields[i].Name] {
287
- fields[i].Doc = strings.TrimSpace("Optional. " + fields[i].Doc)
288
- }
289
- }
290
- return fields
291
- }
292
-
293
- // schemaShape flattens object composition into the field model the catalog
294
- // has. allOf contributes every required field; oneOf/anyOf contributes the
295
- // union, with a field required only when every variant requires it.
296
- func schemaShape(doc *document, node *yaml.Node, resolving map[string]bool) ([]catalog.Field, map[string]bool) {
297
- fields := []catalog.Field{}
298
- required := map[string]bool{}
299
- merge := func(more []catalog.Field, moreRequired map[string]bool, requireMode bool) {
300
- for _, field := range more {
301
- at := -1
302
- for i := range fields {
303
- if fields[i].Name == field.Name {
304
- at = i
305
- break
306
- }
307
- }
308
- if at < 0 {
309
- fields = append(fields, field)
310
- } else {
311
- if fields[at].Type != field.Type && field.Type != "" {
312
- fields[at].Type = unionTypes(fields[at].Type, field.Type)
313
- }
314
- if fields[at].Doc == "" {
315
- fields[at].Doc = field.Doc
316
- }
317
- }
318
- if requireMode && moreRequired[field.Name] {
319
- required[field.Name] = true
320
- }
321
- }
322
- }
323
- if ref := text(child(node, "$ref")); ref != "" {
324
- if targetDoc, target, pointer, ok := doc.resolve(ref); ok {
325
- key := targetDoc.path + "#" + pointer
326
- if !resolving[key] {
327
- resolving[key] = true
328
- more, moreRequired := schemaShape(targetDoc, target, resolving)
329
- delete(resolving, key)
330
- merge(more, moreRequired, true)
331
- }
332
- }
333
- }
334
-
335
- for _, name := range list(child(node, "required")) {
336
- required[name] = true
337
- }
338
- for _, property := range entries(child(node, "properties")) {
339
- fields = append(fields, catalog.Field{Name: property.key, Type: typeOf(doc, property.value), Doc: text(child(property.value, "description"))})
340
- }
341
-
342
- for _, branch := range itemsOf(child(node, "allOf")) {
343
- more, moreRequired := schemaShape(doc, branch, resolving)
344
- merge(more, moreRequired, true)
345
- }
346
-
347
- for _, keyword := range []string{"oneOf", "anyOf"} {
348
- variants := itemsOf(child(node, keyword))
349
- if len(variants) == 0 {
350
- continue
351
- }
352
- counts := map[string]int{}
353
- for _, branch := range variants {
354
- more, moreRequired := schemaShape(doc, branch, resolving)
355
- merge(more, moreRequired, false)
356
- for name := range moreRequired {
357
- counts[name]++
358
- }
359
- }
360
- for name, count := range counts {
361
- if count == len(variants) {
362
- required[name] = true
363
- }
364
- }
365
- }
366
- return fields, required
367
- }
368
-
369
- func itemsOf(node *yaml.Node) []*yaml.Node {
370
- if node == nil || node.Kind != yaml.SequenceNode {
371
- return nil
372
- }
373
- return node.Content
374
- }
375
-
376
- // typeOf renders composed schemas, enums, maps and OpenAPI 3.1 nullable type
377
- // arrays while keeping the compact spelling used elsewhere in the catalog.
378
- func typeOf(doc *document, node *yaml.Node) string {
379
- if ref := text(child(node, "$ref")); ref != "" {
380
- if _, _, pointer, ok := doc.resolve(ref); ok {
381
- if name, schema := schemaName(pointer); schema {
382
- return name
383
- }
384
- }
385
- if name, ok := refName(ref); ok {
386
- return name
387
- }
388
-
389
- return ref
390
- }
391
-
392
- for _, keyword := range []string{"oneOf", "anyOf"} {
393
- var variants []string
394
- for _, branch := range itemsOf(child(node, keyword)) {
395
- variants = append(variants, typeOf(doc, branch))
396
- }
397
- if len(variants) > 0 {
398
- return nullableType(strings.Join(unique(variants), " | "), node)
399
- }
400
- }
401
- if branches := itemsOf(child(node, "allOf")); len(branches) > 0 {
402
- var variants []string
403
- for _, branch := range branches {
404
- variants = append(variants, typeOf(doc, branch))
405
- }
406
- return nullableType(strings.Join(unique(variants), " & "), node)
407
- }
408
-
409
- kind := text(child(node, "type"))
410
- if child(node, "type") != nil && child(node, "type").Kind == yaml.SequenceNode {
411
- kinds := unique(list(child(node, "type")))
412
- if format := text(child(node, "format")); format != "" {
413
- for i := range kinds {
414
- if kinds[i] != "null" {
415
- kinds[i] += " (" + format + ")"
416
- }
417
- }
418
- }
419
- kind = strings.Join(kinds, " | ")
420
- if values := list(child(node, "enum")); len(values) > 0 {
421
- kind += " enum(" + strings.Join(values, " | ") + ")"
422
- }
423
- return nullableType(kind, node)
424
- }
425
- if kind == "array" {
426
- return nullableType("[]"+typeOf(doc, child(node, "items")), node)
427
- }
428
- if kind == "object" {
429
- additional := child(node, "additionalProperties")
430
- if additional != nil && (additional.Kind == yaml.MappingNode || text(additional) == "true") {
431
- value := "any"
432
- if additional.Kind == yaml.MappingNode {
433
- value = typeOf(doc, additional)
434
- }
435
- return nullableType("map[string]"+value, node)
436
- }
437
- }
438
-
439
- if format := text(child(node, "format")); format != "" {
440
- kind += " (" + format + ")"
441
- }
442
-
443
- if kind == "" {
444
- kind = "object"
445
- }
446
- if values := list(child(node, "enum")); len(values) > 0 {
447
- kind += " enum(" + strings.Join(values, " | ") + ")"
448
- }
449
- return nullableType(kind, node)
450
- }
451
-
452
- func nullableType(kind string, node *yaml.Node) string {
453
- if text(child(node, "nullable")) == "true" && !strings.Contains(kind, "null") {
454
- return kind + " | null"
455
- }
456
- return kind
457
- }
458
-
459
- func unique(values []string) []string {
460
- seen := map[string]bool{}
461
- out := make([]string, 0, len(values))
462
- for _, value := range values {
463
- if value != "" && !seen[value] {
464
- seen[value] = true
465
- out = append(out, value)
466
- }
467
- }
468
- return out
469
- }
470
-
471
- func unionTypes(left, right string) string {
472
- return strings.Join(unique(append(strings.Split(left, " | "), strings.Split(right, " | ")...)), " | ")
473
- }
474
-
475
- // apiID is the document's title and major version: `auth` 1.0.0 gives
476
- // `auth.v1`. Spelled by the package the client-side extractor shares, so a
477
- // call and the method it lands on are named alike.
478
- func apiID(doc *document) string {
479
- info := child(doc.root, "info")
480
-
481
- return openapi.DocumentAPIID(text(child(info, "x-portolan-api")), text(child(info, "title")), text(child(info, "version")))
482
- }
483
-
484
- // findSpec locates the document. Told where it is, it looks there; otherwise it
485
- // searches, and says what it found so a service with two specs does not get
486
- // documented from whichever one sorted first without anybody noticing.
487
- func findSpec(root, declared string, b *plugin.Builder) (string, error) {
488
- if declared != "" {
489
- return filepath.Join(root, filepath.FromSlash(declared)), nil
490
- }
491
-
492
- var found []string
493
- err := filepath.WalkDir(root, func(p string, entry os.DirEntry, err error) error {
494
- if err != nil || entry.IsDir() {
495
- return err
496
- }
497
- switch entry.Name() {
498
- case "openapi.yaml", "openapi.yml", "swagger.yaml", "swagger.yml":
499
- found = append(found, p)
500
- }
501
-
502
- return nil
503
- })
504
- if err != nil {
505
- return "", err
506
- }
507
-
508
- if len(found) == 0 {
509
- return "", fmt.Errorf("no openapi document under %s, and none named in the options", root)
510
- }
511
- if len(found) > 1 {
512
- b.Warn("", "found "+strings.Join(found, ", ")+"; reading the first and ignoring the rest")
513
- }
514
-
515
- return found[0], nil
516
- }
517
-
518
- func firstNonEmpty(values ...string) string {
519
- for _, value := range values {
520
- if strings.TrimSpace(value) != "" {
521
- return value
522
- }
523
- }
524
-
525
- return ""
526
- }