@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,224 +0,0 @@
1
- package main
2
-
3
- import (
4
- "go/ast"
5
-
6
- "github.com/shortlink-org/portolan/catalog"
7
- "github.com/shortlink-org/portolan/internal/goscan"
8
- )
9
-
10
- // natsCall is one method of nats.go the reader knows: which way messages go,
11
- // and where in the arguments the subject is - directly, inside a *nats.Msg,
12
- // or inside a consumer config.
13
- type natsCall struct {
14
- direction catalog.ChannelDirection
15
- api string
16
- subject int
17
- message int
18
- config int
19
- queue int
20
- durable int
21
- }
22
-
23
- func core(direction catalog.ChannelDirection, subject, queue int) natsCall {
24
- return natsCall{direction: direction, api: "NATS", subject: subject, message: -1, config: -1, queue: queue, durable: -1}
25
- }
26
-
27
- func coreMsg(direction catalog.ChannelDirection, message int) natsCall {
28
- return natsCall{direction: direction, api: "NATS", subject: -1, message: message, config: -1, queue: -1, durable: -1}
29
- }
30
-
31
- func stream(direction catalog.ChannelDirection, subject, queue, durable int) natsCall {
32
- return natsCall{direction: direction, api: "JetStream", subject: subject, message: -1, config: -1, queue: queue, durable: durable}
33
- }
34
-
35
- func streamMsg(message int) natsCall {
36
- return natsCall{direction: catalog.ChannelSend, api: "JetStream", subject: -1, message: message, config: -1, queue: -1, durable: -1}
37
- }
38
-
39
- func consumer(config int) natsCall {
40
- return natsCall{direction: catalog.ChannelReceive, api: "JetStream", subject: -1, message: -1, config: config, queue: -1, durable: -1}
41
- }
42
-
43
- // natsCalls is the surface read, by the type a method is called on. The
44
- // type is what decides: a service's own bus port has a Subscribe too, and
45
- // it is not this one.
46
- var natsCalls = map[string]map[string]natsCall{
47
- natsPkg + ".Conn": {
48
- "Subscribe": core(catalog.ChannelReceive, 0, -1),
49
- "SubscribeSync": core(catalog.ChannelReceive, 0, -1),
50
- "ChanSubscribe": core(catalog.ChannelReceive, 0, -1),
51
- "QueueSubscribe": core(catalog.ChannelReceive, 0, 1),
52
- "QueueSubscribeSync": core(catalog.ChannelReceive, 0, 1),
53
- "ChanQueueSubscribe": core(catalog.ChannelReceive, 0, 1),
54
- "Publish": core(catalog.ChannelSend, 0, -1),
55
- "Request": core(catalog.ChannelSend, 0, -1),
56
- "PublishMsg": coreMsg(catalog.ChannelSend, 0),
57
- "RequestMsg": coreMsg(catalog.ChannelSend, 0),
58
- },
59
- natsPkg + ".JetStreamContext": {
60
- "Subscribe": stream(catalog.ChannelReceive, 0, -1, -1),
61
- "SubscribeSync": stream(catalog.ChannelReceive, 0, -1, -1),
62
- "ChanSubscribe": stream(catalog.ChannelReceive, 0, -1, -1),
63
- "QueueSubscribe": stream(catalog.ChannelReceive, 0, 1, -1),
64
- "QueueSubscribeSync": stream(catalog.ChannelReceive, 0, 1, -1),
65
- "ChanQueueSubscribe": stream(catalog.ChannelReceive, 0, 1, -1),
66
- "PullSubscribe": stream(catalog.ChannelReceive, 0, -1, 1),
67
- "Publish": stream(catalog.ChannelSend, 0, -1, -1),
68
- "PublishAsync": stream(catalog.ChannelSend, 0, -1, -1),
69
- "PublishMsg": streamMsg(0),
70
- "PublishMsgAsync": streamMsg(0),
71
- },
72
- jsPkg + ".JetStream": {
73
- "Publish": stream(catalog.ChannelSend, 1, -1, -1),
74
- "PublishAsync": stream(catalog.ChannelSend, 1, -1, -1),
75
- "PublishMsg": streamMsg(1),
76
- "PublishMsgAsync": streamMsg(1),
77
- "CreateOrUpdateConsumer": consumer(2),
78
- "CreateConsumer": consumer(2),
79
- "UpdateConsumer": consumer(2),
80
- "OrderedConsumer": consumer(2),
81
- },
82
- jsPkg + ".Stream": {
83
- "CreateOrUpdateConsumer": consumer(1),
84
- "CreateConsumer": consumer(1),
85
- "UpdateConsumer": consumer(1),
86
- "OrderedConsumer": consumer(1),
87
- },
88
- }
89
-
90
- // site is one nats.go call as found: where, which way, and the expressions
91
- // that name the subject, the queue group and the durable consumer - still
92
- // expressions, since what they are worth may be decided by a caller.
93
- type site struct {
94
- fn *function
95
- method string
96
- direction catalog.ChannelDirection
97
- api string
98
- configured bool
99
- subjects []ast.Expr
100
- queue ast.Expr
101
- durable ast.Expr
102
- at goscan.Source
103
- }
104
-
105
- // sites is every nats.go call in the tree, in source order.
106
- func (s *scanner) sites() []site {
107
- var out []site
108
- for _, fn := range s.sortedFunctions() {
109
- ast.Inspect(fn.decl.Body, func(node ast.Node) bool {
110
- call, ok := node.(*ast.CallExpr)
111
- if !ok {
112
- return true
113
- }
114
- sel, ok := call.Fun.(*ast.SelectorExpr)
115
- if !ok {
116
- return true
117
- }
118
- spec, known := natsCalls[s.typeOf(sel.X, fn)][sel.Sel.Name]
119
- if !known {
120
- return true
121
- }
122
- out = append(out, s.site(fn, call, sel.Sel.Name, spec))
123
- return true
124
- })
125
- }
126
- return out
127
- }
128
-
129
- func (s *scanner) site(fn *function, call *ast.CallExpr, method string, spec natsCall) site {
130
- found := site{fn: fn, method: method, direction: spec.direction, api: spec.api, at: s.At(call.Pos())}
131
- arg := func(index int) ast.Expr {
132
- if index >= 0 && index < len(call.Args) {
133
- return call.Args[index]
134
- }
135
- return nil
136
- }
137
- if subject := arg(spec.subject); subject != nil {
138
- found.subjects = []ast.Expr{subject}
139
- }
140
- if message := arg(spec.message); message != nil {
141
- if subject := s.messageSubject(message, fn, map[string]bool{}); subject != nil {
142
- found.subjects = []ast.Expr{subject}
143
- }
144
- }
145
- if config := arg(spec.config); config != nil {
146
- found.configured = true
147
- found.subjects, found.durable = s.consumerConfig(config, fn)
148
- }
149
- found.queue = arg(spec.queue)
150
- if found.durable == nil {
151
- found.durable = arg(spec.durable)
152
- }
153
- // The legacy API takes the durable name as an option.
154
- for _, option := range call.Args {
155
- if optionCall, ok := option.(*ast.CallExpr); ok && s.externalKey(optionCall, fn) == natsPkg+".Durable" && len(optionCall.Args) == 1 {
156
- found.durable = optionCall.Args[0]
157
- }
158
- }
159
- return found
160
- }
161
-
162
- // messageSubject is the subject a *nats.Msg was built with: nats.NewMsg(x),
163
- // &nats.Msg{Subject: x}, or a local that was given one of those.
164
- func (s *scanner) messageSubject(expr ast.Expr, fn *function, seen map[string]bool) ast.Expr {
165
- switch value := goscan.Unwrap(expr).(type) {
166
- case *ast.CallExpr:
167
- if s.externalKey(value, fn) == natsPkg+".NewMsg" && len(value.Args) == 1 {
168
- return value.Args[0]
169
- }
170
- case *ast.CompositeLit:
171
- if s.TypeKey(value.Type, fn.file) == natsPkg+".Msg" {
172
- return field(value, "Subject")
173
- }
174
- case *ast.Ident:
175
- if seen[value.Name] {
176
- return nil
177
- }
178
- seen[value.Name] = true
179
- if given, ok := s.assignedTo(fn, value.Name); ok && given.index == 0 {
180
- return s.messageSubject(given.expr, fn, seen)
181
- }
182
- }
183
- return nil
184
- }
185
-
186
- // consumerConfig is what a JetStream consumer config filters on and is
187
- // called: FilterSubject, or each of FilterSubjects, and Durable.
188
- func (s *scanner) consumerConfig(expr ast.Expr, fn *function) (subjects []ast.Expr, durable ast.Expr) {
189
- lit, ok := goscan.Unwrap(expr).(*ast.CompositeLit)
190
- if !ok {
191
- if ident, isIdent := goscan.Unwrap(expr).(*ast.Ident); isIdent {
192
- if given, found := s.assignedTo(fn, ident.Name); found && given.index == 0 {
193
- lit, ok = goscan.Unwrap(given.expr).(*ast.CompositeLit)
194
- }
195
- }
196
- }
197
- if !ok {
198
- return nil, nil
199
- }
200
- if single := field(lit, "FilterSubject"); single != nil {
201
- subjects = append(subjects, single)
202
- }
203
- if many, isList := goscan.Unwrap(field(lit, "FilterSubjects")).(*ast.CompositeLit); isList {
204
- subjects = append(subjects, many.Elts...)
205
- }
206
- return subjects, field(lit, "Durable")
207
- }
208
-
209
- // field is the value a composite literal gives a named field, or nil.
210
- func field(lit *ast.CompositeLit, name string) ast.Expr {
211
- if lit == nil {
212
- return nil
213
- }
214
- for _, raw := range lit.Elts {
215
- pair, ok := raw.(*ast.KeyValueExpr)
216
- if !ok {
217
- continue
218
- }
219
- if key, ok := pair.Key.(*ast.Ident); ok && key.Name == name {
220
- return pair.Value
221
- }
222
- }
223
- return nil
224
- }
@@ -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-graphql",
15
- Summary: "Reads a GraphQL schema and answers with the interfaces a service provides, one per schema module.",
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,433 +0,0 @@
1
- package main
2
-
3
- // The schema in, the interfaces a service provides out.
4
- //
5
- // A GraphQL schema says three things a catalog wants: what a client may ask
6
- // for, what comes back, and which of those answers keep arriving. The first
7
- // two are what every other interface in this estate already carries - methods
8
- // with a request and a response - and the third is what `Streaming` is for: a
9
- // subscription is a server stream, and drawing it as a call would be a lie
10
- // about how the two ends are coupled.
11
- //
12
- // What it deliberately does not say is who answers underneath. A BFF's whole
13
- // job is to fan out to other services, and the schema is silent about that on
14
- // purpose - the resolvers know, and the extractor that reads the resolvers is
15
- // the one that reports the calls.
16
-
17
- import (
18
- "encoding/json"
19
- "fmt"
20
- "io/fs"
21
- "os"
22
- "path/filepath"
23
- "sort"
24
- "strings"
25
-
26
- "github.com/shortlink-org/portolan/catalog"
27
- "github.com/shortlink-org/portolan/plugin"
28
- )
29
-
30
- // operations are the three root operation types, in the order a reader meets
31
- // them: ask, change, keep listening.
32
- var operations = []string{"query", "mutation", "subscription"}
33
-
34
- func extract(in plugin.Input, opts Options) (plugin.Response, error) {
35
- b := &plugin.Builder{}
36
- root := in.Root
37
-
38
- if opts.Context == "" {
39
- opts.Context = filepath.Base(root)
40
- }
41
- if opts.Service == "" {
42
- opts.Service = filepath.Base(root)
43
- }
44
- api := opts.API
45
- if api == "" {
46
- api = opts.Service + ".v1"
47
- }
48
-
49
- files, err := findSchema(root, opts.Schema)
50
- if err != nil {
51
- return plugin.Response{}, err
52
- }
53
-
54
- doc := newDocument()
55
- for _, file := range files {
56
- source, err := os.ReadFile(file)
57
- if err != nil {
58
- return plugin.Response{}, err
59
- }
60
- if err := parseInto(doc, string(source), filepath.ToSlash(file), moduleOf(root, file)); err != nil {
61
- return plugin.Response{}, err
62
- }
63
- }
64
-
65
- provides := rpcServices(doc, api, b)
66
- if len(provides) == 0 {
67
- b.Warn(opts.Context+"."+opts.Service, strings.Join(relAll(files), ", ")+" declares no root fields; nothing answers over this schema")
68
- }
69
-
70
- fragment := catalog.Catalog{
71
- GeneratedAt: in.GeneratedAt,
72
- Commit: in.Commit,
73
- Contexts: []catalog.BoundedContext{{
74
- ID: opts.Context,
75
- Slug: opts.Context,
76
- // Named by whichever source knows the name. A schema says what the
77
- // service answers, not what the service is called.
78
- Services: []catalog.Service{{
79
- ID: opts.Context + "." + opts.Service,
80
- Slug: opts.Service,
81
- Provides: provides,
82
- Consumes: []catalog.RpcCall{},
83
- Aggregates: []catalog.Aggregate{},
84
- }},
85
- }},
86
- Defs: map[string]catalog.TypeDef{},
87
- Flows: []catalog.Flow{},
88
- Adrs: []catalog.Adr{},
89
- }
90
-
91
- encoded, err := json.MarshalIndent(fragment, "", " ")
92
- if err != nil {
93
- return plugin.Response{}, err
94
- }
95
-
96
- b.File(firstNonEmpty(opts.Out, "graphql.json"), string(encoded)+"\n")
97
-
98
- return b.Response(), nil
99
- }
100
-
101
- // rpcServices groups the root fields by the module that declared them.
102
- //
103
- // A tag is how an OpenAPI author says "these endpoints belong together"; a
104
- // schema module - one directory, one `schema.graphql` - is how a GraphQL
105
- // author says it, and it has the advantage of being a file, so the interface
106
- // can point at something a reader can open. A schema written in one file has
107
- // one group, named by the api itself, exactly as an untagged document is.
108
- func rpcServices(doc *document, api string, b *plugin.Builder) []catalog.RpcService {
109
- type group struct {
110
- methods []catalog.RpcMethod
111
- // extra are the argument shapes invented for this interface. Nobody
112
- // declared them, so they are carried here rather than looked up.
113
- extra []catalog.RpcMessage
114
- queue []string
115
- seen map[string]bool
116
- source string
117
- }
118
-
119
- groups := map[string]*group{}
120
- var order []string
121
-
122
- for _, operation := range operations {
123
- rootName := doc.root(operation)
124
- rootType := doc.types[rootName]
125
- if rootType == nil {
126
- continue
127
- }
128
-
129
- for _, field := range rootType.fields {
130
- id := interfaceID(api, field.module)
131
- g, ok := groups[id]
132
- if !ok {
133
- g = &group{seen: map[string]bool{}, source: field.file}
134
- groups[id] = g
135
- order = append(order, id)
136
- }
137
- if g.source != field.file {
138
- // One module, two files. The interface can only point at one
139
- // of them, and a reader following it to the wrong half would
140
- // not know it had.
141
- b.Warn(id, "root fields come from both "+g.source+" and "+field.file+"; the interface points at the first")
142
- }
143
-
144
- method := catalog.RpcMethod{
145
- Name: methodName(rootName, field.name),
146
- Doc: field.doc,
147
- Response: field.typ.name,
148
- Deprecated: field.deprecated,
149
- }
150
- if field.deprecated && field.reason != "" {
151
- method.Doc = strings.TrimSpace(method.Doc + "\n\nDeprecated: " + field.reason)
152
- }
153
- if operation == "subscription" {
154
- method.Streaming = catalog.StreamingServer
155
- }
156
-
157
- request, invented := requestOf(doc, rootName, field)
158
- method.Request = request
159
- if invented != nil {
160
- g.extra = append(g.extra, *invented)
161
- } else {
162
- enqueue(doc, g.seen, &g.queue, request)
163
- }
164
- for _, arg := range field.args {
165
- enqueue(doc, g.seen, &g.queue, arg.typ.name)
166
- }
167
- enqueue(doc, g.seen, &g.queue, field.typ.name)
168
-
169
- g.methods = append(g.methods, method)
170
- }
171
- }
172
-
173
- sort.Strings(order)
174
-
175
- out := make([]catalog.RpcService, 0, len(order))
176
- for _, id := range order {
177
- g := groups[id]
178
- out = append(out, catalog.RpcService{
179
- ID: id,
180
- Methods: g.methods,
181
- Source: g.source,
182
- Messages: append(g.extra, messages(doc, g.seen, g.queue)...),
183
- })
184
- }
185
-
186
- return out
187
- }
188
-
189
- // requestOf names what a field is given.
190
- //
191
- // A field whose whole input is one input object is already named by the
192
- // schema, and inventing a second name for it would be noise. A field with
193
- // loose arguments has no name for them, so one is made and the arguments
194
- // become its fields.
195
- func requestOf(doc *document, rootName string, field fieldDef) (string, *catalog.RpcMessage) {
196
- if len(field.args) == 0 {
197
- return "", nil
198
- }
199
- if len(field.args) == 1 {
200
- if t := doc.types[field.args[0].typ.name]; t != nil && t.kind == kindInput && !field.args[0].typ.list {
201
- return t.name, nil
202
- }
203
- }
204
-
205
- message := catalog.RpcMessage{Name: argsMessage(rootName, field.name)}
206
- for _, arg := range field.args {
207
- message.Fields = append(message.Fields, catalog.Field{
208
- Name: arg.name,
209
- Type: spell(doc, arg.typ),
210
- Doc: optional(arg.doc, arg.typ),
211
- })
212
- }
213
-
214
- return message.Name, &message
215
- }
216
-
217
- func enqueue(doc *document, seen map[string]bool, queue *[]string, name string) {
218
- t := doc.types[name]
219
- if t == nil || seen[name] {
220
- return
221
- }
222
- switch t.kind {
223
- case kindObject, kindInput, kindInterface, kindUnion:
224
- seen[name] = true
225
- *queue = append(*queue, name)
226
- }
227
- }
228
-
229
- // messages turns every type the group's fields reach into a named shape.
230
- //
231
- // The list grows while it is walked - a response names a type, that type names
232
- // another - for the same reason the OpenAPI extractor's does: a reader who has
233
- // to follow three links to find out what came back has been given a worse
234
- // document than the schema.
235
- func messages(doc *document, seen map[string]bool, queue []string) []catalog.RpcMessage {
236
- out := []catalog.RpcMessage{}
237
-
238
- for i := 0; i < len(queue); i++ {
239
- t := doc.types[queue[i]]
240
- if t == nil {
241
- continue
242
- }
243
-
244
- message := catalog.RpcMessage{Name: t.name, Fields: []catalog.Field{}}
245
- for _, field := range t.fields {
246
- prose := optional(field.doc, field.typ)
247
- if field.deprecated {
248
- prose = strings.TrimSpace(prose + "\n\nDeprecated: " + field.reason)
249
- }
250
- message.Fields = append(message.Fields, catalog.Field{
251
- Name: field.name,
252
- Type: spell(doc, field.typ),
253
- Doc: prose,
254
- })
255
- enqueue(doc, seen, &queue, field.typ.name)
256
- for _, arg := range field.args {
257
- enqueue(doc, seen, &queue, arg.typ.name)
258
- }
259
- }
260
- if d := discriminator(doc, t); d != nil {
261
- message.Discriminator = d
262
- for _, variant := range d.Variants {
263
- enqueue(doc, seen, &queue, variant.Message)
264
- }
265
- }
266
- out = append(out, message)
267
- }
268
-
269
- return out
270
- }
271
-
272
- // discriminator is how a polymorphic answer says which shape it is. GraphQL
273
- // has one answer for both of its ways of being polymorphic, and it is the same
274
- // field a client would select: `__typename`.
275
- func discriminator(doc *document, t *typeDef) *catalog.RpcDiscriminator {
276
- var members []string
277
- switch t.kind {
278
- case kindUnion:
279
- members = t.members
280
- case kindInterface:
281
- for _, name := range doc.order {
282
- other := doc.types[name]
283
- if other.kind == kindObject && contains(other.implements, t.name) {
284
- members = append(members, other.name)
285
- }
286
- }
287
- }
288
- if len(members) == 0 {
289
- return nil
290
- }
291
-
292
- variants := make([]catalog.RpcVariant, 0, len(members))
293
- for _, member := range members {
294
- variants = append(variants, catalog.RpcVariant{Value: member, Message: member})
295
- }
296
-
297
- return &catalog.RpcDiscriminator{Property: "__typename", Variants: variants}
298
- }
299
-
300
- // spell renders a field's type. An enum carries its values along with its
301
- // name, the way an OpenAPI enum does, because a status whose values are three
302
- // clicks away is a status nobody reads.
303
- func spell(doc *document, ref typeRef) string {
304
- spelled := ref.spell()
305
- if t := doc.types[ref.name]; t != nil && t.kind == kindEnum && len(t.values) > 0 {
306
- spelled += " enum(" + strings.Join(t.values, " | ") + ")"
307
- }
308
-
309
- return spelled
310
- }
311
-
312
- // optional says in prose what `!` says in the schema, because that is where
313
- // the rest of the catalog says it.
314
- func optional(doc string, ref typeRef) string {
315
- if ref.nonNull {
316
- return doc
317
- }
318
-
319
- return strings.TrimSpace("Optional. " + doc)
320
- }
321
-
322
- // findSchema locates the documents. Told where they are - a file or a
323
- // directory - it reads there; otherwise it searches, because a service with
324
- // its schema in the conventional place should not have to say so.
325
- func findSchema(root, declared string) ([]string, error) {
326
- if declared != "" {
327
- at := filepath.Join(root, filepath.FromSlash(declared))
328
- info, err := os.Stat(at)
329
- if err != nil {
330
- return nil, err
331
- }
332
- if !info.IsDir() {
333
- return []string{at}, nil
334
- }
335
-
336
- return walkSchema(at)
337
- }
338
-
339
- found, err := walkSchema(root)
340
- if err != nil {
341
- return nil, err
342
- }
343
- if len(found) == 0 {
344
- return nil, fmt.Errorf("no graphql schema under %s, and none named in the options", root)
345
- }
346
-
347
- return found, nil
348
- }
349
-
350
- func walkSchema(dir string) ([]string, error) {
351
- var found []string
352
- err := filepath.WalkDir(dir, func(p string, entry fs.DirEntry, err error) error {
353
- if err != nil {
354
- return err
355
- }
356
- if entry.IsDir() {
357
- switch entry.Name() {
358
- case "node_modules", "dist", "vendor", ".git":
359
- return filepath.SkipDir
360
- }
361
-
362
- return nil
363
- }
364
- switch filepath.Ext(entry.Name()) {
365
- case ".graphql", ".graphqls", ".gql":
366
- // The generator writes the whole schema out again beside the
367
- // modules it read - `schema.generated.graphqls` - and reading that
368
- // copy would give the estate a module nobody wrote, holding every
369
- // field twice.
370
- if !strings.Contains(entry.Name(), ".generated.") {
371
- found = append(found, p)
372
- }
373
- }
374
-
375
- return nil
376
- })
377
- if err != nil {
378
- return nil, err
379
- }
380
- sort.Strings(found)
381
-
382
- return found, nil
383
- }
384
-
385
- // moduleOf is what a file is called for the purpose of grouping. A schema
386
- // written as `basket/schema.graphql` is the basket module; one written as
387
- // `basket.graphql` is the same module said another way.
388
- func moduleOf(root, file string) string {
389
- rel, err := filepath.Rel(root, file)
390
- if err != nil {
391
- rel = file
392
- }
393
- name := strings.TrimSuffix(filepath.Base(rel), filepath.Ext(rel))
394
- if name != "schema" && name != "index" {
395
- return name
396
- }
397
-
398
- parent := filepath.Base(filepath.Dir(rel))
399
- if parent == "." || parent == string(filepath.Separator) {
400
- return ""
401
- }
402
-
403
- return parent
404
- }
405
-
406
- func relAll(files []string) []string {
407
- out := make([]string, 0, len(files))
408
- for _, file := range files {
409
- out = append(out, filepath.ToSlash(file))
410
- }
411
-
412
- return out
413
- }
414
-
415
- func contains(values []string, want string) bool {
416
- for _, value := range values {
417
- if value == want {
418
- return true
419
- }
420
- }
421
-
422
- return false
423
- }
424
-
425
- func firstNonEmpty(values ...string) string {
426
- for _, value := range values {
427
- if strings.TrimSpace(value) != "" {
428
- return value
429
- }
430
- }
431
-
432
- return ""
433
- }