@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,256 +0,0 @@
1
- package main
2
-
3
- import (
4
- "encoding/json"
5
- "strings"
6
- "testing"
7
-
8
- "github.com/shortlink-org/portolan/catalog"
9
- "github.com/shortlink-org/portolan/plugin"
10
- )
11
-
12
- func fragment(t *testing.T) catalog.Catalog {
13
- t.Helper()
14
-
15
- resp, err := extract(
16
- plugin.Input{Root: "testdata", Commit: "abc1234", GeneratedAt: "2026-01-01T00:00:00Z"},
17
- Options{Context: "storefront", Service: "bff", Schema: "schema", API: "storefront.v1"},
18
- )
19
- if err != nil {
20
- t.Fatal(err)
21
- }
22
- if len(resp.Files) != 1 {
23
- t.Fatalf("expected one fragment, got %d files", len(resp.Files))
24
- }
25
-
26
- var out catalog.Catalog
27
- if err := json.Unmarshal([]byte(resp.Files[0].Contents), &out); err != nil {
28
- t.Fatal(err)
29
- }
30
-
31
- return out
32
- }
33
-
34
- func provided(t *testing.T) []catalog.RpcService {
35
- t.Helper()
36
-
37
- return fragment(t).Contexts[0].Services[0].Provides
38
- }
39
-
40
- func interfaceNamed(t *testing.T, id string) catalog.RpcService {
41
- t.Helper()
42
-
43
- for _, p := range provided(t) {
44
- if p.ID == id {
45
- return p
46
- }
47
- }
48
- t.Fatalf("no interface %q in the fragment", id)
49
-
50
- return catalog.RpcService{}
51
- }
52
-
53
- func method(t *testing.T, id, name string) catalog.RpcMethod {
54
- t.Helper()
55
-
56
- for _, m := range interfaceNamed(t, id).Methods {
57
- if m.Name == name {
58
- return m
59
- }
60
- }
61
- t.Fatalf("no method %q on %q", name, id)
62
-
63
- return catalog.RpcMethod{}
64
- }
65
-
66
- func message(t *testing.T, id, name string) catalog.RpcMessage {
67
- t.Helper()
68
-
69
- for _, m := range interfaceNamed(t, id).Messages {
70
- if m.Name == name {
71
- return m
72
- }
73
- }
74
- t.Fatalf("no message %q on %q", name, id)
75
-
76
- return catalog.RpcMessage{}
77
- }
78
-
79
- // A module is how a schema's author says "these fields belong together", the
80
- // way a tag is in an OpenAPI document - and unlike a tag it is a file, so the
81
- // interface can point at something a reader can open.
82
- func TestModulesBecomeInterfaces(t *testing.T) {
83
- got := map[string]string{}
84
- for _, p := range provided(t) {
85
- got[p.ID] = p.Source
86
- }
87
-
88
- want := map[string]string{
89
- "storefront.v1.Basket": "testdata/schema/basket/schema.graphql",
90
- "storefront.v1.Order": "testdata/schema/order/schema.graphql",
91
- }
92
- for id, source := range want {
93
- if got[id] != source {
94
- t.Errorf("%s reads %q, want %q", id, got[id], source)
95
- }
96
- }
97
- if len(got) != len(want) {
98
- t.Errorf("declared %d interfaces, want %d: %v", len(got), len(want), got)
99
- }
100
- }
101
-
102
- // The generator writes the whole schema out again beside the modules it read.
103
- // Reading that copy would give the estate a module nobody wrote, holding every
104
- // field a second time.
105
- func TestTheGeneratedCopyIsNotAModule(t *testing.T) {
106
- for _, p := range provided(t) {
107
- if strings.Contains(p.Source, ".generated.") {
108
- t.Errorf("%s was read out of the generated copy %s", p.ID, p.Source)
109
- }
110
- }
111
- }
112
-
113
- // A field is named by the operation it belongs to. Two operations may each
114
- // have a `basket` field and they are not the same endpoint.
115
- func TestMethodsAreNamedByOperation(t *testing.T) {
116
- var names []string
117
- for _, m := range interfaceNamed(t, "storefront.v1.Basket").Methods {
118
- names = append(names, m.Name)
119
- }
120
-
121
- want := []string{"Query.basket", "Query.legacyBasket", "Mutation.addItem"}
122
- if len(names) != len(want) {
123
- t.Fatalf("methods are %v, want %v", names, want)
124
- }
125
- for i := range want {
126
- if names[i] != want[i] {
127
- t.Errorf("method %d is %q, want %q", i, names[i], want[i])
128
- }
129
- }
130
- }
131
-
132
- // A subscription keeps answering. Drawn as a call it would be a lie about how
133
- // the two ends are coupled, which is what Streaming exists to prevent.
134
- func TestSubscriptionsAreServerStreams(t *testing.T) {
135
- if got := method(t, "storefront.v1.Order", "Subscription.orderStatus").Streaming; got != catalog.StreamingServer {
136
- t.Errorf("streaming is %q, want %q", got, catalog.StreamingServer)
137
- }
138
- if got := method(t, "storefront.v1.Basket", "Query.basket").Streaming; got != "" {
139
- t.Errorf("a query is streaming %q, want unary", got)
140
- }
141
- }
142
-
143
- // A field whose whole input is one input object is already named by the
144
- // schema; a field with loose arguments is not, so a name is made and the
145
- // arguments become its fields.
146
- func TestArgumentsAreNamedOnceAndInventedOtherwise(t *testing.T) {
147
- if got := method(t, "storefront.v1.Basket", "Mutation.addItem").Request; got != "AddItemInput" {
148
- t.Errorf("request is %q, want the input object AddItemInput", got)
149
- }
150
-
151
- if got := method(t, "storefront.v1.Order", "Query.order").Request; got != "QueryOrderArgs" {
152
- t.Errorf("request is %q, want QueryOrderArgs", got)
153
- }
154
-
155
- args := message(t, "storefront.v1.Order", "QueryOrderArgs")
156
- if len(args.Fields) != 2 || args.Fields[0].Name != "id" || args.Fields[1].Name != "at" {
157
- t.Fatalf("QueryOrderArgs holds %+v, want id and at", args.Fields)
158
- }
159
- // `at` is nullable, and the rest of the catalog says that in prose.
160
- if args.Fields[1].Doc != "Optional." {
161
- t.Errorf("the nullable argument reads %q, want it marked optional", args.Fields[1].Doc)
162
- }
163
- }
164
-
165
- // A response names a shape, and the shapes it names are walked until nothing
166
- // new is reached: a reader who has to open the schema to find out what a Line
167
- // holds has been given a worse document than the schema.
168
- func TestMessagesFollowWhatTheyReach(t *testing.T) {
169
- var names []string
170
- for _, m := range interfaceNamed(t, "storefront.v1.Basket").Messages {
171
- names = append(names, m.Name)
172
- }
173
-
174
- for _, want := range []string{"Basket", "Line", "Money", "AddItemInput"} {
175
- found := false
176
- for _, name := range names {
177
- found = found || name == want
178
- }
179
- if !found {
180
- t.Errorf("%s is reachable from the basket module and is not in %v", want, names)
181
- }
182
- }
183
- }
184
-
185
- // A list is spelled the way every other extractor here spells one, and an enum
186
- // carries its values: a status whose values are three clicks away is a status
187
- // nobody reads.
188
- func TestTypesAreSpelledLikeTheRestOfTheCatalog(t *testing.T) {
189
- basket := message(t, "storefront.v1.Basket", "Basket")
190
- byName := map[string]catalog.Field{}
191
- for _, f := range basket.Fields {
192
- byName[f.Name] = f
193
- }
194
-
195
- if got := byName["lines"].Type; got != "[]Line" {
196
- t.Errorf("lines is %q, want []Line", got)
197
- }
198
- if got := byName["quote"].Doc; got != "Optional. Nothing has been priced yet when the basket is empty." {
199
- t.Errorf("the nullable field reads %q", got)
200
- }
201
-
202
- order := message(t, "storefront.v1.Order", "Order")
203
- for _, f := range order.Fields {
204
- if f.Name == "status" && f.Type != "OrderStatus enum(PLACED | CONFIRMED | CANCELLED)" {
205
- t.Errorf("status is %q, want the enum and its values", f.Type)
206
- }
207
- }
208
- }
209
-
210
- // GraphQL is polymorphic in two ways and answers both of them with the same
211
- // field, which is the one a client would select.
212
- func TestUnionsDiscriminateOnTypename(t *testing.T) {
213
- result := message(t, "storefront.v1.Order", "OrderResult")
214
- if result.Discriminator == nil {
215
- t.Fatal("a union came back with no discriminator")
216
- }
217
- if result.Discriminator.Property != "__typename" {
218
- t.Errorf("discriminates on %q, want __typename", result.Discriminator.Property)
219
- }
220
-
221
- var variants []string
222
- for _, v := range result.Discriminator.Variants {
223
- variants = append(variants, v.Message)
224
- }
225
- if len(variants) != 2 || variants[0] != "Order" || variants[1] != "OrderNotFound" {
226
- t.Errorf("variants are %v, want Order and OrderNotFound", variants)
227
- }
228
- }
229
-
230
- // A deprecated field is reported deprecated, and its reason is prose the
231
- // reader gets to see.
232
- func TestDeprecationSurvives(t *testing.T) {
233
- m := method(t, "storefront.v1.Basket", "Query.legacyBasket")
234
- if !m.Deprecated {
235
- t.Error("the deprecated field is not marked deprecated")
236
- }
237
- if m.Doc != "Deprecated: Ask for `basket` instead." {
238
- t.Errorf("doc is %q, want the deprecation reason", m.Doc)
239
- }
240
- }
241
-
242
- // The same schema read twice is the same fragment. Everything that came out of
243
- // a map is sorted, and nothing reads a clock.
244
- func TestExtractionIsDeterministic(t *testing.T) {
245
- first, err := json.Marshal(fragment(t))
246
- if err != nil {
247
- t.Fatal(err)
248
- }
249
- second, err := json.Marshal(fragment(t))
250
- if err != nil {
251
- t.Fatal(err)
252
- }
253
- if string(first) != string(second) {
254
- t.Error("two runs over the same schema produced different fragments")
255
- }
256
- }
@@ -1,49 +0,0 @@
1
- package main
2
-
3
- // What a schema's parts are called in the catalog.
4
- //
5
- // Two readers have to agree on these. This one reads the SDL and says what a
6
- // service provides; `extract-ts` reads the resolvers beside it and says what
7
- // each field goes on to call, and a flow step that named a field differently
8
- // from the method it lands on would never meet it. The spelling is written
9
- // out twice because the two are in different languages; `extract-ts/graphql.ts`
10
- // is the other copy.
11
-
12
- import "strings"
13
-
14
- // interfaceID is the interface a module's root fields land in: the api and the
15
- // module, `storefront.v1.Basket`. A schema in one file has no module to name,
16
- // and its fields land on the api itself.
17
- func interfaceID(api, module string) string {
18
- if module == "" {
19
- return api
20
- }
21
-
22
- return api + "." + title(module)
23
- }
24
-
25
- // methodName is a root field by the operation it belongs to: `Query.basket`.
26
- // GraphQL has no other name for it - two operations may each have a `basket`
27
- // field, and they are not the same endpoint.
28
- func methodName(root, field string) string { return root + "." + field }
29
-
30
- // argsMessage names the shape a field's arguments make when they are not
31
- // already one input object: `Query.basket(id: ID!)` gives `QueryBasketArgs`.
32
- // The name is the one graphql-codegen generates for the same thing, so a
33
- // reader who has the generated types open recognises it.
34
- func argsMessage(root, field string) string { return root + title(field) + "Args" }
35
-
36
- // title is the human form of a module name: `basket` becomes Basket,
37
- // `order_status` becomes OrderStatus.
38
- func title(name string) string {
39
- var b strings.Builder
40
- for _, word := range strings.FieldsFunc(name, func(r rune) bool { return r == '_' || r == '-' || r == ' ' || r == '.' }) {
41
- runes := []rune(word)
42
- if runes[0] >= 'a' && runes[0] <= 'z' {
43
- runes[0] = runes[0] - 'a' + 'A'
44
- }
45
- b.WriteString(string(runes))
46
- }
47
-
48
- return b.String()
49
- }
@@ -1,237 +0,0 @@
1
- package main
2
-
3
- // The tokens of a GraphQL SDL document, and the descriptions attached to them.
4
- //
5
- // A schema documents itself in strings rather than in comments: `# ...` is
6
- // thrown away by every reader, and the prose a client is meant to see sits in
7
- // a string literal directly above the thing it describes. So the lexer keeps
8
- // string tokens as tokens - the parser decides which of them are descriptions -
9
- // and drops comments on the floor, which is the opposite of what the proto
10
- // lexer beside it has to do.
11
-
12
- import (
13
- "fmt"
14
- "strings"
15
- "unicode"
16
- "unicode/utf8"
17
- )
18
-
19
- type kind int
20
-
21
- const (
22
- tokEOF kind = iota
23
- tokName
24
- tokString
25
- tokNumber
26
- tokPunct
27
- )
28
-
29
- type token struct {
30
- kind kind
31
- text string
32
- line int
33
- }
34
-
35
- func (t token) is(text string) bool {
36
- return (t.kind == tokPunct || t.kind == tokName) && t.text == text
37
- }
38
-
39
- type lexer struct {
40
- src string
41
- pos int
42
- line int
43
- }
44
-
45
- func lex(src string) ([]token, error) {
46
- l := &lexer{src: strings.TrimPrefix(src, "\ufeff"), line: 1}
47
-
48
- var out []token
49
- for {
50
- tok, err := l.next()
51
- if err != nil {
52
- return nil, err
53
- }
54
- out = append(out, tok)
55
- if tok.kind == tokEOF {
56
- return out, nil
57
- }
58
- }
59
- }
60
-
61
- func (l *lexer) next() (token, error) {
62
- for {
63
- l.skipIgnored()
64
- if l.pos >= len(l.src) {
65
- return token{kind: tokEOF, line: l.line}, nil
66
- }
67
-
68
- c := l.src[l.pos]
69
- switch {
70
- case c == '#':
71
- l.lineComment()
72
- continue
73
- case c == '"':
74
- return l.stringLiteral()
75
- case c == '_' || isLetter(c):
76
- return l.name(), nil
77
- case c == '-' || isDigit(c):
78
- return l.number(), nil
79
- }
80
-
81
- // `...` is one token; every other punctuator is one byte. Nothing else
82
- // is legal, and a document that reaches here is one no server would
83
- // serve either.
84
- if strings.HasPrefix(l.src[l.pos:], "...") {
85
- tok := token{kind: tokPunct, text: "...", line: l.line}
86
- l.pos += 3
87
- return tok, nil
88
- }
89
- if strings.ContainsRune("!$&()::=@[]{|}", rune(c)) {
90
- tok := token{kind: tokPunct, text: string(c), line: l.line}
91
- l.pos++
92
- return tok, nil
93
- }
94
-
95
- return token{}, fmt.Errorf("line %d: %q is not a graphql token", l.line, string(c))
96
- }
97
- }
98
-
99
- // skipIgnored eats whitespace and the commas GraphQL treats as whitespace.
100
- func (l *lexer) skipIgnored() {
101
- for l.pos < len(l.src) {
102
- switch l.src[l.pos] {
103
- case '\n':
104
- l.line++
105
- l.pos++
106
- case ' ', '\t', '\r', ',':
107
- l.pos++
108
- default:
109
- return
110
- }
111
- }
112
- }
113
-
114
- func (l *lexer) lineComment() {
115
- for l.pos < len(l.src) && l.src[l.pos] != '\n' {
116
- l.pos++
117
- }
118
- }
119
-
120
- func (l *lexer) name() token {
121
- start := l.pos
122
- for l.pos < len(l.src) && (isLetter(l.src[l.pos]) || isDigit(l.src[l.pos]) || l.src[l.pos] == '_') {
123
- l.pos++
124
- }
125
-
126
- return token{kind: tokName, text: l.src[start:l.pos], line: l.line}
127
- }
128
-
129
- // number is lexed only so a default value can be stepped over. Nothing in the
130
- // catalog is interested in what it says.
131
- func (l *lexer) number() token {
132
- start := l.pos
133
- l.pos++
134
- for l.pos < len(l.src) && (isDigit(l.src[l.pos]) || strings.ContainsRune(".eE+-", rune(l.src[l.pos]))) {
135
- l.pos++
136
- }
137
-
138
- return token{kind: tokNumber, text: l.src[start:l.pos], line: l.line}
139
- }
140
-
141
- func (l *lexer) stringLiteral() (token, error) {
142
- line := l.line
143
- if strings.HasPrefix(l.src[l.pos:], `"""`) {
144
- l.pos += 3
145
- start := l.pos
146
- for {
147
- if l.pos >= len(l.src) {
148
- return token{}, fmt.Errorf("line %d: a block string is never closed", line)
149
- }
150
- if strings.HasPrefix(l.src[l.pos:], `"""`) && (l.pos == start || l.src[l.pos-1] != '\\') {
151
- text := l.src[start:l.pos]
152
- l.line += strings.Count(text, "\n")
153
- l.pos += 3
154
- return token{kind: tokString, text: blockString(text), line: line}, nil
155
- }
156
- l.pos++
157
- }
158
- }
159
-
160
- l.pos++
161
- var b strings.Builder
162
- for {
163
- if l.pos >= len(l.src) || l.src[l.pos] == '\n' {
164
- return token{}, fmt.Errorf("line %d: a string is never closed", line)
165
- }
166
- c := l.src[l.pos]
167
- if c == '"' {
168
- l.pos++
169
- return token{kind: tokString, text: b.String(), line: line}, nil
170
- }
171
- if c == '\\' && l.pos+1 < len(l.src) {
172
- l.pos++
173
- switch l.src[l.pos] {
174
- case 'n':
175
- b.WriteByte('\n')
176
- case 't':
177
- b.WriteByte('\t')
178
- case 'u':
179
- if l.pos+4 < len(l.src) {
180
- var r rune
181
- if _, err := fmt.Sscanf(l.src[l.pos+1:l.pos+5], "%04x", &r); err == nil {
182
- b.WriteRune(r)
183
- l.pos += 4
184
- }
185
- }
186
- default:
187
- b.WriteByte(l.src[l.pos])
188
- }
189
- l.pos++
190
- continue
191
- }
192
- r, size := utf8.DecodeRuneInString(l.src[l.pos:])
193
- b.WriteRune(r)
194
- l.pos += size
195
- }
196
- }
197
-
198
- // blockString applies the indentation rules the spec gives for `"""` strings:
199
- // the common indent of every line after the first is removed, and blank lines
200
- // at either end go with it. Without this every description in the catalog
201
- // would carry the indentation of the schema file it was written in.
202
- func blockString(raw string) string {
203
- lines := strings.Split(strings.ReplaceAll(raw, "\r\n", "\n"), "\n")
204
-
205
- indent := -1
206
- for _, line := range lines[1:] {
207
- trimmed := strings.TrimLeft(line, " \t")
208
- if trimmed == "" {
209
- continue
210
- }
211
- if width := len(line) - len(trimmed); indent < 0 || width < indent {
212
- indent = width
213
- }
214
- }
215
- if indent > 0 {
216
- for i, line := range lines[1:] {
217
- if len(line) >= indent {
218
- lines[i+1] = line[indent:]
219
- } else {
220
- lines[i+1] = strings.TrimLeft(line, " \t")
221
- }
222
- }
223
- }
224
-
225
- for len(lines) > 0 && strings.TrimSpace(lines[0]) == "" {
226
- lines = lines[1:]
227
- }
228
- for len(lines) > 0 && strings.TrimSpace(lines[len(lines)-1]) == "" {
229
- lines = lines[:len(lines)-1]
230
- }
231
-
232
- return strings.Join(lines, "\n")
233
- }
234
-
235
- func isLetter(c byte) bool { return unicode.IsLetter(rune(c)) && c < utf8.RuneSelf }
236
-
237
- func isDigit(c byte) bool { return c >= '0' && c <= '9' }
@@ -1,51 +0,0 @@
1
- // Package main is portolan-extract-graphql: a GraphQL schema in, a catalog
2
- // fragment out.
3
- //
4
- // It describes one aspect of a service - what a client may ask it for - and
5
- // nothing else. Who answers underneath is a different fact, read by whichever
6
- // extractor reads the resolvers, and the two meet in the merge. Neither knows
7
- // the other exists, which is the whole reason each of them stays small.
8
- package main
9
-
10
- import (
11
- "fmt"
12
- "io"
13
- "os"
14
-
15
- "github.com/shortlink-org/portolan/plugin"
16
- )
17
-
18
- // Options are what the manifest tells the extractor: the things a schema does
19
- // not say about the estate it belongs to.
20
- type Options struct {
21
- Context string `json:"context"`
22
- Service string `json:"service"`
23
-
24
- // Schema is the document or the directory of documents, relative to the
25
- // input root. Left out, the extractor looks for them.
26
- Schema string `json:"schema,omitempty"`
27
-
28
- // API prefixes the ids of the interfaces this produces. Left out, it is
29
- // the service and a major version - `bff` gives `bff.v1`, and a module
30
- // called `basket` gives `bff.v1.Basket`.
31
- API string `json:"api,omitempty"`
32
-
33
- Out string `json:"out,omitempty"`
34
- }
35
-
36
- func main() {
37
- if err := run(os.Stdin, os.Stdout); err != nil {
38
- fmt.Fprintln(os.Stderr, "portolan-extract-graphql:", err)
39
- os.Exit(1)
40
- }
41
- }
42
-
43
- func run(stdin io.Reader, stdout io.Writer) error {
44
- return plugin.Serve(stdin, stdout, descriptor(), func(req plugin.Request, opts Options) (plugin.Response, error) {
45
- if req.Input.Root == "" {
46
- return plugin.Response{}, fmt.Errorf("no input root: an extractor has nothing to read")
47
- }
48
-
49
- return extract(req.Input, opts)
50
- })
51
- }