@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,168 +0,0 @@
1
- package main
2
-
3
- import (
4
- "encoding/json"
5
- "os"
6
- "path/filepath"
7
- "testing"
8
-
9
- "github.com/shortlink-org/portolan/catalog"
10
- "github.com/shortlink-org/portolan/plugin"
11
- )
12
-
13
- func writeGo(t *testing.T, root, name, contents string) {
14
- t.Helper()
15
- file := filepath.Join(root, name)
16
- if err := os.MkdirAll(filepath.Dir(file), 0o755); err != nil {
17
- t.Fatal(err)
18
- }
19
- if err := os.WriteFile(file, []byte(contents), 0o644); err != nil {
20
- t.Fatal(err)
21
- }
22
- }
23
-
24
- func extracted(t *testing.T, root string, opts Options) (catalog.Catalog, plugin.Response) {
25
- t.Helper()
26
- response, err := extract(plugin.Input{Root: root, Commit: "abc", GeneratedAt: "2026-01-01T00:00:00Z"}, opts)
27
- if err != nil {
28
- t.Fatal(err)
29
- }
30
- var out catalog.Catalog
31
- if err := json.Unmarshal([]byte(response.Files[0].Contents), &out); err != nil {
32
- t.Fatal(err)
33
- }
34
- return out, response
35
- }
36
-
37
- func TestExtractsGoRedisConstructionAsOwnedStore(t *testing.T) {
38
- root := t.TempDir()
39
- writeGo(t, root, "go.mod", "module example.com/cache\n")
40
- writeGo(t, root, "internal/cache/redis.go", `package cache
41
- import redisv9 "github.com/redis/go-redis/v9"
42
- func New() *redisv9.Client {
43
- return redisv9.NewClient(&redisv9.Options{Addr: "localhost:6379"})
44
- }
45
- `)
46
-
47
- out, response := extracted(t, root, Options{Context: "sales", Service: "catalog"})
48
- if len(response.Warnings()) != 0 {
49
- t.Fatalf("warnings = %+v", response.Warnings())
50
- }
51
- if out.Commit != "abc" || out.GeneratedAt != "2026-01-01T00:00:00Z" {
52
- t.Fatalf("stamp = %q %q", out.Commit, out.GeneratedAt)
53
- }
54
- if len(out.Stores) != 1 {
55
- t.Fatalf("stores = %+v", out.Stores)
56
- }
57
- store := out.Stores[0]
58
- if store.ID != "sales.catalog.redis" || store.Owner != "sales.catalog" || store.Kind != catalog.StoreKindRedis || store.Name != "catalog Redis" {
59
- t.Fatalf("store = %+v", store)
60
- }
61
- if store.Source != "internal/cache/redis.go:4" || store.Tables == nil || len(store.Tables) != 0 {
62
- t.Fatalf("source/tables = %q %+v", store.Source, store.Tables)
63
- }
64
- service := out.Contexts[0].Services[0]
65
- if len(service.Stores) != 1 || service.Stores[0] != store.ID {
66
- t.Fatalf("service stores = %+v", service.Stores)
67
- }
68
- }
69
-
70
- func TestResolvesThePackageNameOfAVersionedGoRedisImport(t *testing.T) {
71
- root := t.TempDir()
72
- writeGo(t, root, "go.mod", "module example.com/cache\n")
73
- writeGo(t, root, "redis.go", `package cache
74
- import "github.com/redis/go-redis/v9"
75
- func connect() { _ = redis.NewClient(&redis.Options{}) }
76
- `)
77
-
78
- out, _ := extracted(t, root, Options{Context: "sales", Service: "catalog"})
79
- if len(out.Stores) != 1 {
80
- t.Fatalf("stores = %+v", out.Stores)
81
- }
82
- }
83
-
84
- func TestExtractsKeyPatternsOperationsTTLAndValueTypes(t *testing.T) {
85
- root := t.TempDir()
86
- writeGo(t, root, "go.mod", "module example.com/cache\n")
87
- writeGo(t, root, "cache.go", `package cache
88
- import (
89
- "context"
90
- "encoding/json"
91
- "time"
92
- "github.com/redis/go-redis/v9"
93
- )
94
- const sessionTTL = 24 * time.Hour
95
- type Session struct { ID string }
96
- type Cache struct { client *redis.Client }
97
- func New() *Cache { return &Cache{client: redis.NewClient(&redis.Options{})} }
98
- func sessionKey(id string) string { return "session:" + id }
99
- func (c *Cache) Save(ctx context.Context, id string, session Session) error {
100
- data, _ := json.Marshal(session)
101
- return c.client.Set(ctx, sessionKey(id), data, sessionTTL).Err()
102
- }
103
- func (c *Cache) Load(ctx context.Context, id string) error {
104
- return c.client.Get(ctx, sessionKey(id)).Err()
105
- }
106
- func (c *Cache) Delete(ctx context.Context, id string) error {
107
- return c.client.Del(ctx, sessionKey(id)).Err()
108
- }
109
- func (c *Cache) Query(ctx context.Context, provider, pnr string, value []byte, ttl time.Duration) error {
110
- key := "query_" + provider
111
- if pnr != "" { key = key + "_" + pnr }
112
- return c.client.Set(ctx, key, value, ttl).Err()
113
- }
114
- `)
115
-
116
- out, _ := extracted(t, root, Options{Context: "sales", Service: "catalog"})
117
- keyspaces := out.Stores[0].Keyspaces
118
- if len(keyspaces) != 2 {
119
- t.Fatalf("keyspaces = %+v", keyspaces)
120
- }
121
- if got := keyspaces[1]; got.Pattern != "session:{id}" || got.TTL != "24h" || got.Value != "Session" ||
122
- len(got.Operations) != 3 || got.Operations[0] != catalog.RedisOperationRead || got.Operations[1] != catalog.RedisOperationWrite || got.Operations[2] != catalog.RedisOperationDelete {
123
- t.Fatalf("session keyspace = %+v", got)
124
- }
125
- if got := keyspaces[0]; got.Pattern != "query_{provider}[_{pnr}]" || got.TTL != "caller-provided (ttl)" || got.Value != "[]byte" {
126
- t.Fatalf("query keyspace = %+v", got)
127
- }
128
- }
129
-
130
- func TestRecognizesSupportedRedisClients(t *testing.T) {
131
- cases := []struct {
132
- name string
133
- importPath string
134
- call string
135
- }{
136
- {name: "go redis v8", importPath: "github.com/go-redis/redis/v8", call: "NewClusterClient(&client.ClusterOptions{})"},
137
- {name: "rueidis", importPath: "github.com/redis/rueidis", call: "NewClient(client.ClientOption{})"},
138
- {name: "redigo", importPath: "github.com/gomodule/redigo/redis", call: `DialURL("redis://localhost")`},
139
- }
140
- for _, tc := range cases {
141
- t.Run(tc.name, func(t *testing.T) {
142
- root := t.TempDir()
143
- writeGo(t, root, "go.mod", "module example.com/cache\n")
144
- writeGo(t, root, "redis.go", "package cache\nimport client \""+tc.importPath+"\"\nfunc connect() { _, _ = client."+tc.call+" }\n")
145
- out, _ := extracted(t, root, Options{Context: "sales", Service: "catalog"})
146
- if len(out.Stores) != 1 {
147
- t.Fatalf("stores = %+v", out.Stores)
148
- }
149
- })
150
- }
151
- }
152
-
153
- func TestDependencyOrUnrelatedRedisPackageIsNotRuntimeEvidence(t *testing.T) {
154
- root := t.TempDir()
155
- writeGo(t, root, "go.mod", "module example.com/cache\nrequire github.com/redis/go-redis/v9 v9.0.0\n")
156
- writeGo(t, root, "redis.go", `package cache
157
- import redis "example.com/cache/redis"
158
- func connect() { redis.NewClient() }
159
- `)
160
-
161
- out, response := extracted(t, root, Options{Context: "sales", Service: "catalog"})
162
- if len(out.Stores) != 0 || len(out.Contexts[0].Services[0].Stores) != 0 {
163
- t.Fatalf("unexpected store = %+v", out.Stores)
164
- }
165
- if len(response.Warnings()) != 1 {
166
- t.Fatalf("warnings = %+v", response.Warnings())
167
- }
168
- }
@@ -1,469 +0,0 @@
1
- package main
2
-
3
- import (
4
- "go/ast"
5
- "go/token"
6
- "sort"
7
- "strconv"
8
- "strings"
9
-
10
- "github.com/shortlink-org/portolan/catalog"
11
- "github.com/shortlink-org/portolan/internal/goscan"
12
- )
13
-
14
- type keyBuilder struct {
15
- file *goscan.File
16
- params []string
17
- result ast.Expr
18
- }
19
-
20
- type keyspaceScanner struct {
21
- *goscan.Tree
22
- clientFields map[string]map[string]bool
23
- builders map[string]keyBuilder
24
- keyspaces map[string]*catalog.RedisKeyspace
25
- }
26
-
27
- func scanRedisKeyspaces(tree *goscan.Tree) []catalog.RedisKeyspace {
28
- s := &keyspaceScanner{
29
- Tree: tree,
30
- clientFields: map[string]map[string]bool{},
31
- builders: map[string]keyBuilder{},
32
- keyspaces: map[string]*catalog.RedisKeyspace{},
33
- }
34
- s.index()
35
- for _, file := range tree.Files {
36
- for _, decl := range file.Node.Decls {
37
- fn, ok := decl.(*ast.FuncDecl)
38
- if !ok || fn.Recv == nil || fn.Body == nil {
39
- continue
40
- }
41
- receiverType := tree.TypeKey(fn.Recv.List[0].Type, file)
42
- fields := s.clientFields[receiverType]
43
- if len(fields) == 0 || len(fn.Recv.List[0].Names) == 0 {
44
- continue
45
- }
46
- s.scanMethod(file, fn, fn.Recv.List[0].Names[0].Name, fields)
47
- }
48
- }
49
-
50
- out := make([]catalog.RedisKeyspace, 0, len(s.keyspaces))
51
- for _, keyspace := range s.keyspaces {
52
- sort.SliceStable(keyspace.Operations, func(i, j int) bool {
53
- return redisOperationOrder(keyspace.Operations[i]) < redisOperationOrder(keyspace.Operations[j])
54
- })
55
- out = append(out, *keyspace)
56
- }
57
- sort.Slice(out, func(i, j int) bool { return out[i].Pattern < out[j].Pattern })
58
- return out
59
- }
60
-
61
- func (s *keyspaceScanner) index() {
62
- for _, file := range s.Files {
63
- for _, decl := range file.Node.Decls {
64
- switch value := decl.(type) {
65
- case *ast.GenDecl:
66
- if value.Tok != token.TYPE {
67
- continue
68
- }
69
- for _, raw := range value.Specs {
70
- spec := raw.(*ast.TypeSpec)
71
- body, ok := spec.Type.(*ast.StructType)
72
- if !ok {
73
- continue
74
- }
75
- key := file.Pkg + "." + spec.Name.Name
76
- for _, field := range body.Fields.List {
77
- if !isRedisClientType(file, field.Type) {
78
- continue
79
- }
80
- if s.clientFields[key] == nil {
81
- s.clientFields[key] = map[string]bool{}
82
- }
83
- for _, name := range field.Names {
84
- s.clientFields[key][name.Name] = true
85
- }
86
- }
87
- }
88
- case *ast.FuncDecl:
89
- if value.Recv != nil || value.Body == nil || value.Type.Results == nil || len(value.Type.Results.List) == 0 {
90
- continue
91
- }
92
- result := firstReturn(value.Body)
93
- if result == nil {
94
- continue
95
- }
96
- s.builders[file.Pkg+"."+value.Name.Name] = keyBuilder{file: file, params: parameterNames(value.Type.Params), result: result}
97
- }
98
- }
99
- }
100
- }
101
-
102
- func isRedisClientType(file *goscan.File, expr ast.Expr) bool {
103
- expr = goscan.Unwrap(expr)
104
- sel, ok := expr.(*ast.SelectorExpr)
105
- if !ok {
106
- return false
107
- }
108
- pkg, ok := sel.X.(*ast.Ident)
109
- if !ok {
110
- return false
111
- }
112
- importPath := redisImportPath(file, pkg.Name)
113
- if !isRedisClientImport(importPath) {
114
- return false
115
- }
116
- switch sel.Sel.Name {
117
- case "Client", "ClusterClient":
118
- return true
119
- }
120
- return false
121
- }
122
-
123
- func firstReturn(body *ast.BlockStmt) ast.Expr {
124
- for _, stmt := range body.List {
125
- switch value := stmt.(type) {
126
- case *ast.ReturnStmt:
127
- if len(value.Results) == 1 {
128
- return value.Results[0]
129
- }
130
- case *ast.BlockStmt:
131
- if result := firstReturn(value); result != nil {
132
- return result
133
- }
134
- }
135
- }
136
- return nil
137
- }
138
-
139
- func parameterNames(fields *ast.FieldList) []string {
140
- if fields == nil {
141
- return nil
142
- }
143
- var out []string
144
- for _, field := range fields.List {
145
- for _, name := range field.Names {
146
- out = append(out, name.Name)
147
- }
148
- }
149
- return out
150
- }
151
-
152
- func (s *keyspaceScanner) scanMethod(file *goscan.File, fn *ast.FuncDecl, receiver string, fields map[string]bool) {
153
- patterns := map[string]string{}
154
- values := map[string]string{}
155
- for _, field := range fn.Type.Params.List {
156
- kind := s.PrintNode(field.Type)
157
- for _, name := range field.Names {
158
- patterns[name.Name] = "{" + name.Name + "}"
159
- values[name.Name] = kind
160
- }
161
- }
162
-
163
- for _, stmt := range fn.Body.List {
164
- s.applyAssignments(file, stmt, patterns, values)
165
- ast.Inspect(stmt, func(node ast.Node) bool {
166
- call, ok := node.(*ast.CallExpr)
167
- if !ok {
168
- return true
169
- }
170
- sel, ok := call.Fun.(*ast.SelectorExpr)
171
- if !ok {
172
- return true
173
- }
174
- operation, keyArg, ttlArg, valueArg, ok := redisOperation(sel.Sel.Name)
175
- if !ok || len(call.Args) <= keyArg || !callsRedisField(sel.X, receiver, fields) {
176
- return true
177
- }
178
- pattern := s.patternOf(file, call.Args[keyArg], patterns, 0)
179
- if pattern == "" {
180
- return true
181
- }
182
- ttl := ""
183
- if ttlArg >= 0 && len(call.Args) > ttlArg {
184
- ttl = s.durationOf(file, call.Args[ttlArg], map[string]bool{})
185
- }
186
- value := ""
187
- if valueArg >= 0 && len(call.Args) > valueArg {
188
- value = valueType(call.Args[valueArg], values)
189
- }
190
- s.record(pattern, operation, ttl, value, s.At(call.Pos()).String())
191
- return true
192
- })
193
- }
194
- }
195
-
196
- func callsRedisField(expr ast.Expr, receiver string, fields map[string]bool) bool {
197
- field, ok := goscan.Unwrap(expr).(*ast.SelectorExpr)
198
- if !ok || !fields[field.Sel.Name] {
199
- return false
200
- }
201
- base, ok := goscan.Unwrap(field.X).(*ast.Ident)
202
- return ok && base.Name == receiver
203
- }
204
-
205
- func redisOperation(name string) (catalog.RedisOperation, int, int, int, bool) {
206
- switch name {
207
- case "Get", "MGet", "HGet", "HGetAll", "SMembers", "SIsMember", "LRange", "ZRange", "ZScore":
208
- return catalog.RedisOperationRead, 1, -1, -1, true
209
- case "Set", "SetNX", "SetXX":
210
- return catalog.RedisOperationWrite, 1, 3, 2, true
211
- case "HSet", "MSet", "SAdd", "LPush", "RPush", "ZAdd":
212
- return catalog.RedisOperationWrite, 1, -1, 2, true
213
- case "Del", "HDel", "SRem", "LPop", "RPop", "ZRem":
214
- return catalog.RedisOperationDelete, 1, -1, -1, true
215
- case "Exists":
216
- return catalog.RedisOperationExists, 1, -1, -1, true
217
- case "Expire", "ExpireAt":
218
- return catalog.RedisOperationExpire, 1, 2, -1, true
219
- case "Incr", "IncrBy", "Decr", "DecrBy":
220
- return catalog.RedisOperationCount, 1, -1, -1, true
221
- default:
222
- return "", 0, 0, 0, false
223
- }
224
- }
225
-
226
- func (s *keyspaceScanner) applyAssignments(file *goscan.File, stmt ast.Stmt, patterns, values map[string]string) {
227
- switch value := stmt.(type) {
228
- case *ast.AssignStmt:
229
- for i, lhs := range value.Lhs {
230
- name, ok := lhs.(*ast.Ident)
231
- if !ok || len(value.Rhs) == 0 {
232
- continue
233
- }
234
- rhs := value.Rhs[min(i, len(value.Rhs)-1)]
235
- if pattern := s.patternOf(file, rhs, patterns, 0); pattern != "" {
236
- patterns[name.Name] = pattern
237
- }
238
- if kind := s.valueOf(file, rhs, values); kind != "" {
239
- values[name.Name] = kind
240
- }
241
- }
242
- case *ast.IfStmt:
243
- branchPatterns := cloneStrings(patterns)
244
- branchValues := cloneStrings(values)
245
- for _, nested := range value.Body.List {
246
- s.applyAssignments(file, nested, branchPatterns, branchValues)
247
- }
248
- for name, branch := range branchPatterns {
249
- base := patterns[name]
250
- if base == "" || branch == base {
251
- continue
252
- }
253
- if suffix, ok := strings.CutPrefix(branch, base); ok {
254
- patterns[name] = base + "[" + suffix + "]"
255
- }
256
- }
257
- }
258
- }
259
-
260
- func cloneStrings(values map[string]string) map[string]string {
261
- out := make(map[string]string, len(values))
262
- for key, value := range values {
263
- out[key] = value
264
- }
265
- return out
266
- }
267
-
268
- func (s *keyspaceScanner) patternOf(file *goscan.File, expr ast.Expr, env map[string]string, depth int) string {
269
- if expr == nil || depth > 8 {
270
- return ""
271
- }
272
- switch value := goscan.Unwrap(expr).(type) {
273
- case *ast.BasicLit:
274
- if value.Kind == token.STRING {
275
- text, _ := strconv.Unquote(value.Value)
276
- return text
277
- }
278
- case *ast.Ident:
279
- if known := env[value.Name]; known != "" {
280
- return known
281
- }
282
- return s.StringOf(value, file, nil)
283
- case *ast.SelectorExpr:
284
- return s.StringOf(value, file, nil)
285
- case *ast.BinaryExpr:
286
- if value.Op == token.ADD {
287
- left := s.patternOf(file, value.X, env, depth+1)
288
- right := s.patternOf(file, value.Y, env, depth+1)
289
- if left != "" && right != "" {
290
- return left + right
291
- }
292
- }
293
- case *ast.CallExpr:
294
- if sel, ok := value.Fun.(*ast.SelectorExpr); ok && sel.Sel.Name == "Sprintf" {
295
- if pkg, ok := sel.X.(*ast.Ident); ok && file.Imports[pkg.Name] == "fmt" && len(value.Args) > 0 {
296
- format := s.patternOf(file, value.Args[0], env, depth+1)
297
- args := make([]string, 0, len(value.Args)-1)
298
- for _, arg := range value.Args[1:] {
299
- args = append(args, s.patternOf(file, arg, env, depth+1))
300
- }
301
- return sprintfPattern(format, args)
302
- }
303
- }
304
- if name, ok := value.Fun.(*ast.Ident); ok {
305
- if builder, found := s.builders[file.Pkg+"."+name.Name]; found {
306
- bound := map[string]string{}
307
- for i, param := range builder.params {
308
- if i < len(value.Args) {
309
- bound[param] = s.patternOf(file, value.Args[i], env, depth+1)
310
- }
311
- }
312
- return s.patternOf(builder.file, builder.result, bound, depth+1)
313
- }
314
- }
315
- }
316
- return ""
317
- }
318
-
319
- func sprintfPattern(format string, args []string) string {
320
- if format == "" {
321
- return ""
322
- }
323
- var out strings.Builder
324
- arg := 0
325
- for i := 0; i < len(format); i++ {
326
- if format[i] != '%' || i+1 >= len(format) {
327
- out.WriteByte(format[i])
328
- continue
329
- }
330
- if format[i+1] == '%' {
331
- out.WriteByte('%')
332
- i++
333
- continue
334
- }
335
- if strings.ContainsRune("sdvq", rune(format[i+1])) {
336
- if arg < len(args) {
337
- out.WriteString(args[arg])
338
- }
339
- arg++
340
- i++
341
- continue
342
- }
343
- out.WriteByte(format[i])
344
- }
345
- return out.String()
346
- }
347
-
348
- func (s *keyspaceScanner) durationOf(file *goscan.File, expr ast.Expr, visiting map[string]bool) string {
349
- switch value := goscan.Unwrap(expr).(type) {
350
- case *ast.BasicLit:
351
- if value.Value == "0" {
352
- return "none"
353
- }
354
- return value.Value
355
- case *ast.Ident:
356
- key := file.Pkg + "." + value.Name
357
- if known, ok := s.Constants[key]; ok && !visiting[key] {
358
- visiting[key] = true
359
- result := s.durationOf(known.File, known.Expr, visiting)
360
- delete(visiting, key)
361
- return result
362
- }
363
- return "caller-provided (" + value.Name + ")"
364
- case *ast.SelectorExpr:
365
- if pkg, ok := value.X.(*ast.Ident); ok && file.Imports[pkg.Name] == "time" {
366
- switch value.Sel.Name {
367
- case "Nanosecond":
368
- return "1ns"
369
- case "Microsecond":
370
- return "1µs"
371
- case "Millisecond":
372
- return "1ms"
373
- case "Second":
374
- return "1s"
375
- case "Minute":
376
- return "1m"
377
- case "Hour":
378
- return "1h"
379
- }
380
- }
381
- return "configured"
382
- case *ast.BinaryExpr:
383
- if value.Op == token.MUL {
384
- left := s.durationOf(file, value.X, visiting)
385
- right := s.durationOf(file, value.Y, visiting)
386
- if strings.HasPrefix(right, "1") {
387
- return left + strings.TrimPrefix(right, "1")
388
- }
389
- }
390
- }
391
- return s.PrintNode(expr)
392
- }
393
-
394
- func (s *keyspaceScanner) valueOf(file *goscan.File, expr ast.Expr, values map[string]string) string {
395
- switch value := goscan.Unwrap(expr).(type) {
396
- case *ast.Ident:
397
- return values[value.Name]
398
- case *ast.CompositeLit:
399
- return s.PrintNode(value.Type)
400
- case *ast.CallExpr:
401
- sel, ok := value.Fun.(*ast.SelectorExpr)
402
- if !ok || sel.Sel.Name != "Marshal" || len(value.Args) == 0 {
403
- return ""
404
- }
405
- pkg, ok := sel.X.(*ast.Ident)
406
- if !ok || file.Imports[pkg.Name] != "encoding/json" {
407
- return ""
408
- }
409
- return valueType(value.Args[0], values)
410
- }
411
- return ""
412
- }
413
-
414
- func valueType(expr ast.Expr, values map[string]string) string {
415
- switch value := goscan.Unwrap(expr).(type) {
416
- case *ast.Ident:
417
- return values[value.Name]
418
- case *ast.BasicLit:
419
- if value.Kind == token.STRING {
420
- return "string"
421
- }
422
- }
423
- return ""
424
- }
425
-
426
- func (s *keyspaceScanner) record(pattern string, operation catalog.RedisOperation, ttl, value, source string) {
427
- found := s.keyspaces[pattern]
428
- if found == nil {
429
- found = &catalog.RedisKeyspace{Pattern: pattern, Operations: []catalog.RedisOperation{}, Source: source}
430
- s.keyspaces[pattern] = found
431
- }
432
- if !containsOperation(found.Operations, operation) {
433
- found.Operations = append(found.Operations, operation)
434
- }
435
- if found.TTL == "" && ttl != "" {
436
- found.TTL = ttl
437
- }
438
- if found.Value == "" && value != "" {
439
- found.Value = value
440
- }
441
- }
442
-
443
- func containsOperation(operations []catalog.RedisOperation, wanted catalog.RedisOperation) bool {
444
- for _, operation := range operations {
445
- if operation == wanted {
446
- return true
447
- }
448
- }
449
- return false
450
- }
451
-
452
- func redisOperationOrder(operation catalog.RedisOperation) int {
453
- switch operation {
454
- case catalog.RedisOperationRead:
455
- return 0
456
- case catalog.RedisOperationWrite:
457
- return 1
458
- case catalog.RedisOperationDelete:
459
- return 2
460
- case catalog.RedisOperationExists:
461
- return 3
462
- case catalog.RedisOperationExpire:
463
- return 4
464
- case catalog.RedisOperationCount:
465
- return 5
466
- default:
467
- return 6
468
- }
469
- }
@@ -1,44 +0,0 @@
1
- // Package main is portolan-extract-redis: source-backed Redis client
2
- // construction in a Go repository in, a Redis store owned by the service out.
3
- package main
4
-
5
- import (
6
- "fmt"
7
- "io"
8
- "os"
9
- "path/filepath"
10
-
11
- "github.com/shortlink-org/portolan/internal/goscan"
12
- "github.com/shortlink-org/portolan/plugin"
13
- )
14
-
15
- type Options struct {
16
- Context string `json:"context"`
17
- Service string `json:"service"`
18
- Store string `json:"store,omitempty"`
19
- Name string `json:"name,omitempty"`
20
- Out string `json:"out,omitempty"`
21
- }
22
-
23
- func main() {
24
- if err := run(os.Stdin, os.Stdout); err != nil {
25
- fmt.Fprintln(os.Stderr, "portolan-extract-redis:", err)
26
- os.Exit(1)
27
- }
28
- }
29
-
30
- func run(stdin io.Reader, stdout io.Writer) error {
31
- return plugin.Serve(stdin, stdout, descriptor(), func(req plugin.Request, opts Options) (plugin.Response, error) {
32
- if req.Input.Root == "" {
33
- return plugin.Response{}, fmt.Errorf("no input root: an extractor has nothing to read")
34
- }
35
- if opts.Context == "" {
36
- opts.Context = goscan.Slug(filepath.Base(req.Input.Root))
37
- }
38
- if opts.Service == "" {
39
- opts.Service = goscan.Slug(filepath.Base(req.Input.Root))
40
- }
41
-
42
- return extract(req.Input, opts)
43
- })
44
- }
@@ -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-river",
15
- Summary: "Reads River JobArgs, Insert calls and registered workers into work queues and source-backed job flows.",
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
- }