@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,133 +0,0 @@
1
- package wsdl
2
-
3
- import (
4
- "os"
5
- "path/filepath"
6
- "strings"
7
- "testing"
8
- )
9
-
10
- func writeFixture(t *testing.T, root, name, contents string) {
11
- t.Helper()
12
- path := filepath.Join(root, name)
13
- if err := os.MkdirAll(filepath.Dir(path), 0o755); err != nil {
14
- t.Fatal(err)
15
- }
16
- if err := os.WriteFile(path, []byte(contents), 0o644); err != nil {
17
- t.Fatal(err)
18
- }
19
- }
20
-
21
- func TestReadJoinsImportedPortTypeBindingAndSchema(t *testing.T) {
22
- root := t.TempDir()
23
- writeFixture(t, root, "types.xsd", `
24
- <xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema" targetNamespace="urn:booking" xmlns:tns="urn:booking">
25
- <xsd:complexType name="CancelRequestType">
26
- <xsd:sequence>
27
- <xsd:element name="bookingId" type="xsd:string"/>
28
- <xsd:element name="passengerIds" type="xsd:string" maxOccurs="unbounded"/>
29
- </xsd:sequence>
30
- </xsd:complexType>
31
- <xsd:element name="CancelRequest" type="tns:CancelRequestType"/>
32
- <xsd:element name="CancelResponse">
33
- <xsd:complexType><xsd:sequence><xsd:element name="accepted" type="xsd:boolean"/></xsd:sequence></xsd:complexType>
34
- </xsd:element>
35
- </xsd:schema>`)
36
- writeFixture(t, root, "contract.wsdl", `
37
- <wsdl:definitions xmlns:wsdl="http://schemas.xmlsoap.org/wsdl/" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:tns="urn:booking" targetNamespace="urn:booking">
38
- <wsdl:types><xsd:schema targetNamespace="urn:booking"><xsd:import namespace="urn:booking" schemaLocation="types.xsd"/></xsd:schema></wsdl:types>
39
- <wsdl:message name="CancelRequestMessage"><wsdl:part name="body" element="tns:CancelRequest"/></wsdl:message>
40
- <wsdl:message name="CancelResponseMessage"><wsdl:part name="body" element="tns:CancelResponse"/></wsdl:message>
41
- <wsdl:message name="BookingFault"><wsdl:part name="reason" type="xsd:string"/></wsdl:message>
42
- <wsdl:portType name="BookingPortType">
43
- <wsdl:operation name="CancelBooking">
44
- <wsdl:documentation>Cancels a booking.</wsdl:documentation>
45
- <wsdl:input message="tns:CancelRequestMessage"/>
46
- <wsdl:output message="tns:CancelResponseMessage"/>
47
- <wsdl:fault name="BookingFault" message="tns:BookingFault"/>
48
- </wsdl:operation>
49
- </wsdl:portType>
50
- </wsdl:definitions>`)
51
- writeFixture(t, root, "service.wsdl", `
52
- <wsdl:definitions xmlns:wsdl="http://schemas.xmlsoap.org/wsdl/" xmlns:soap="http://schemas.xmlsoap.org/wsdl/soap/" xmlns:soap12="http://schemas.xmlsoap.org/wsdl/soap12/" xmlns:tns="urn:booking" targetNamespace="urn:booking" name="BookingDefinitions">
53
- <wsdl:import namespace="urn:booking" location="contract.wsdl"/>
54
- <wsdl:binding name="BookingBinding" type="tns:BookingPortType">
55
- <soap12:binding style="document" transport="http://schemas.xmlsoap.org/soap/http"/>
56
- <wsdl:operation name="CancelBooking">
57
- <soap12:operation soapAction="urn:booking:cancel"/>
58
- <wsdl:input><soap12:body use="literal"/><soap12:header message="tns:BookingFault" part="reason" use="literal"/></wsdl:input>
59
- </wsdl:operation>
60
- </wsdl:binding>
61
- <wsdl:binding name="BookingBinding11" type="tns:BookingPortType">
62
- <soap:binding style="rpc" transport="http://schemas.xmlsoap.org/soap/http"/>
63
- <wsdl:operation name="CancelBooking"><soap:operation soapAction="urn:booking:cancel:v1"/></wsdl:operation>
64
- </wsdl:binding>
65
- <wsdl:service name="BookingService">
66
- <wsdl:documentation>Booking supplier API.</wsdl:documentation>
67
- <wsdl:port name="BookingPort" binding="tns:BookingBinding"><soap12:address location="https://booking.example/soap"/></wsdl:port>
68
- <wsdl:port name="BookingPort11" binding="tns:BookingBinding11"><soap:address location="https://booking.example/soap/v1"/></wsdl:port>
69
- </wsdl:service>
70
- </wsdl:definitions>`)
71
-
72
- result, err := Read(root, "service.wsdl")
73
- if err != nil {
74
- t.Fatal(err)
75
- }
76
- if len(result.Contracts) != 1 {
77
- t.Fatalf("contracts = %+v", result.Contracts)
78
- }
79
- contract := result.Contracts[0]
80
- if contract.Name != "BookingService" || contract.Summary != "Booking supplier API." || contract.Source != "service.wsdl" {
81
- t.Fatalf("contract = %+v", contract)
82
- }
83
- if len(contract.Interfaces) != 2 {
84
- t.Fatalf("interfaces = %+v", contract.Interfaces)
85
- }
86
- var iface Interface
87
- for _, candidate := range contract.Interfaces {
88
- if candidate.Name == "BookingPort" {
89
- iface = candidate
90
- }
91
- }
92
- if iface.Name != "BookingPort" || iface.PortType != "BookingPortType" || iface.Binding != "BookingBinding" || iface.Version != "1.2" || iface.Style != "document" || iface.Endpoint != "https://booking.example/soap" {
93
- t.Fatalf("interface = %+v", iface)
94
- }
95
- if len(iface.Operations) != 1 {
96
- t.Fatalf("operations = %+v", iface.Operations)
97
- }
98
- op := iface.Operations[0]
99
- if op.Action != "urn:booking:cancel" || op.Request != "CancelRequestMessage" || op.Response != "CancelResponseMessage" || strings.Join(op.Faults, ",") != "BookingFault" || strings.Join(op.Headers, ",") != "BookingFault.reason" {
100
- t.Fatalf("operation = %+v", op)
101
- }
102
- messages := map[string]Message{}
103
- for _, message := range iface.Messages {
104
- messages[message.Name] = message
105
- }
106
- request := messages["CancelRequestMessage"]
107
- if len(request.Fields) != 2 || request.Fields[0].Name != "bookingId" || request.Fields[0].Type != "string" || request.Fields[1].Type != "string[]" {
108
- t.Fatalf("request = %+v", request)
109
- }
110
- response := messages["CancelResponseMessage"]
111
- if len(response.Fields) != 1 || response.Fields[0].Name != "accepted" || response.Fields[0].Type != "boolean" {
112
- t.Fatalf("response = %+v", response)
113
- }
114
- }
115
-
116
- func TestRemoteImportsAreWarningsAndNeverFetched(t *testing.T) {
117
- root := t.TempDir()
118
- writeFixture(t, root, "service.wsdl", `
119
- <definitions xmlns="http://schemas.xmlsoap.org/wsdl/" targetNamespace="urn:local">
120
- <import namespace="urn:remote" location="https://example.invalid/remote.wsdl"/>
121
- <portType name="Local"><operation name="Ping"/></portType>
122
- </definitions>`)
123
- result, err := Read(root, "service.wsdl")
124
- if err != nil {
125
- t.Fatal(err)
126
- }
127
- if len(result.Warnings) != 1 || !strings.Contains(result.Warnings[0], "remote import") {
128
- t.Fatalf("warnings = %v", result.Warnings)
129
- }
130
- if len(result.Contracts) != 1 || len(result.Contracts[0].Interfaces) != 1 {
131
- t.Fatalf("contracts = %+v", result.Contracts)
132
- }
133
- }
@@ -1,107 +0,0 @@
1
- // Describing a plugin: what it is called, where it belongs in a run, and what
2
- // it can be told.
3
- //
4
- // The options a plugin takes are facts the source does not carry - which
5
- // bounded context a Go module belongs to, whose proto package a client calls.
6
- // Only the plugin knows them, and until it could say so the manifest was a
7
- // file you wrote by reading somebody's struct. A misspelled key was silently
8
- // dropped, because that is what encoding/json does with a field it does not
9
- // recognise.
10
- //
11
- // So a plugin answers a second question now. The schema it hands back is
12
- // composed into schema/portolan.schema.json, which an editor reads while the
13
- // manifest is being written and the host checks before it runs anything.
14
-
15
- package plugin
16
-
17
- import (
18
- "encoding/json"
19
- "fmt"
20
- "io"
21
- )
22
-
23
- // KindDescribe asks a plugin what it is rather than asking it to work.
24
- const KindDescribe = "describe"
25
-
26
- // Phases a step can be declared under in the manifest. A plugin names the ones
27
- // it belongs in, which is what stops the schema from offering an extractor
28
- // where a renderer goes.
29
- const (
30
- PhaseExtract = "extract"
31
- PhaseVerify = "verify"
32
- PhaseGenerate = "generate"
33
- )
34
-
35
- // Descriptor is a plugin's answer to KindDescribe.
36
- type Descriptor struct {
37
- // Name is what the plugin calls itself. The manifest is free to declare it
38
- // under another name - the schema is keyed by the declared one - so this is
39
- // for the reader, not for lookup.
40
- Name string `json:"name"`
41
-
42
- // Summary is one line, and it is the line an editor shows beside the step.
43
- Summary string `json:"summary,omitempty"`
44
-
45
- // Phases are the manifest sections this plugin belongs in.
46
- Phases []string `json:"phases"`
47
-
48
- // Options is a JSON Schema for what the manifest may pass in `options`.
49
- // It must close itself with `additionalProperties: false`: the misspelled
50
- // key is the error worth catching, and a schema that accepts anything
51
- // catches nothing.
52
- Options json.RawMessage `json:"options,omitempty"`
53
- }
54
-
55
- // Serve reads one request and writes one response - the whole of a plugin's
56
- // main, apart from the work itself.
57
- //
58
- // Every plugin had this loop written out, and each copy had to remember the
59
- // describe branch once there was one. The options type is the only thing that
60
- // differs, so it is the only thing the caller supplies.
61
- func Serve[O any](stdin io.Reader, stdout io.Writer, d Descriptor, run func(Request, O) (Response, error)) error {
62
- in, err := io.ReadAll(stdin)
63
- if err != nil {
64
- return fmt.Errorf("reading the request: %w", err)
65
- }
66
-
67
- var req Request
68
- if err := json.Unmarshal(in, &req); err != nil {
69
- return fmt.Errorf("the request is not a portolan plugin request: %w", err)
70
- }
71
- if req.PortolanVersion != "" && req.PortolanVersion != Version {
72
- return fmt.Errorf("unsupported portolan protocol %q (plugin supports %s)", req.PortolanVersion, Version)
73
- }
74
-
75
- resp := Response{}
76
- switch req.Kind {
77
- case KindDescribe:
78
- // Files is named rather than left nil because the host reads it on
79
- // every response, and a describe that answers `"files": null` is a
80
- // plugin that looks broken the first time anyone asks it anything.
81
- resp = Response{Files: []File{}, Describe: &d}
82
- case "":
83
- var opts O
84
- if len(req.Options) > 0 {
85
- if err := json.Unmarshal(req.Options, &opts); err != nil {
86
- return fmt.Errorf("options: %w", err)
87
- }
88
- }
89
-
90
- if resp, err = run(req, opts); err != nil {
91
- return err
92
- }
93
- default:
94
- return fmt.Errorf("unknown request kind %q", req.Kind)
95
- }
96
-
97
- out, err := json.Marshal(resp)
98
- if err != nil {
99
- return fmt.Errorf("encoding the response: %w", err)
100
- }
101
-
102
- if _, err := stdout.Write(out); err != nil {
103
- return fmt.Errorf("writing the response: %w", err)
104
- }
105
-
106
- return nil
107
- }
@@ -1,114 +0,0 @@
1
- package plugin_test
2
-
3
- import (
4
- "bytes"
5
- "encoding/json"
6
- "strings"
7
- "testing"
8
-
9
- "github.com/shortlink-org/portolan/plugin"
10
- )
11
-
12
- type options struct {
13
- Context string `json:"context"`
14
- }
15
-
16
- func descriptor() plugin.Descriptor {
17
- return plugin.Descriptor{
18
- Name: "example",
19
- Phases: []string{plugin.PhaseExtract},
20
- Options: json.RawMessage(`{"type":"object","additionalProperties":false}`),
21
- }
22
- }
23
-
24
- func TestServeDescribesWithoutWorking(t *testing.T) {
25
- worked := false
26
- out := &bytes.Buffer{}
27
-
28
- err := plugin.Serve(strings.NewReader(`{"kind":"describe"}`), out, descriptor(),
29
- func(plugin.Request, options) (plugin.Response, error) {
30
- worked = true
31
-
32
- return plugin.Response{}, nil
33
- })
34
- if err != nil {
35
- t.Fatalf("describe: %v", err)
36
- }
37
-
38
- if worked {
39
- t.Error("a describe ran the plugin, which is the one thing it must not do")
40
- }
41
-
42
- var resp plugin.Response
43
- if err := json.Unmarshal(out.Bytes(), &resp); err != nil {
44
- t.Fatalf("the answer is not a response: %v", err)
45
- }
46
-
47
- if resp.Describe == nil {
48
- t.Fatal("a describe answered without a descriptor")
49
- }
50
- if resp.Describe.Name != "example" {
51
- t.Errorf("descriptor names %q", resp.Describe.Name)
52
- }
53
-
54
- // The host reads files on every response, so a describe that answers null
55
- // is a plugin that looks broken the first time anyone asks it anything.
56
- if !strings.Contains(out.String(), `"files":[]`) {
57
- t.Errorf("a describe answered %s, which has no files list", out)
58
- }
59
- }
60
-
61
- func TestServeReadsOptions(t *testing.T) {
62
- var seen options
63
- out := &bytes.Buffer{}
64
-
65
- err := plugin.Serve(
66
- strings.NewReader(`{"input":{"root":"examples/auth"},"options":{"context":"auth"}}`),
67
- out, descriptor(),
68
- func(req plugin.Request, opts options) (plugin.Response, error) {
69
- seen = opts
70
-
71
- return plugin.Response{Files: []plugin.File{{Name: req.Input.Root + ".json"}}}, nil
72
- })
73
- if err != nil {
74
- t.Fatalf("run: %v", err)
75
- }
76
-
77
- if seen.Context != "auth" {
78
- t.Errorf("options came through as %+v", seen)
79
- }
80
- if !strings.Contains(out.String(), "examples/auth.json") {
81
- t.Errorf("the response is %s", out)
82
- }
83
- }
84
-
85
- func TestServeRefusesAnUnknownKind(t *testing.T) {
86
- err := plugin.Serve(strings.NewReader(`{"kind":"explain"}`), &bytes.Buffer{}, descriptor(),
87
- func(plugin.Request, options) (plugin.Response, error) {
88
- return plugin.Response{}, nil
89
- })
90
-
91
- if err == nil || !strings.Contains(err.Error(), "explain") {
92
- t.Errorf("an unknown kind gave %v", err)
93
- }
94
- }
95
-
96
- func TestServeRefusesAnIncompatibleProtocol(t *testing.T) {
97
- err := plugin.Serve(strings.NewReader(`{"portolanVersion":"9.0.0","kind":"describe"}`), &bytes.Buffer{}, descriptor(),
98
- func(plugin.Request, options) (plugin.Response, error) { return plugin.Response{}, nil })
99
- if err == nil || !strings.Contains(err.Error(), "unsupported portolan protocol") {
100
- t.Errorf("incompatible version gave %v", err)
101
- }
102
- }
103
-
104
- func TestWarningsAreNotAResponseProperty(t *testing.T) {
105
- b := &plugin.Builder{}
106
- b.Warn("fixture", "not represented")
107
- out, err := json.Marshal(b.Response())
108
- if err != nil {
109
- t.Fatal(err)
110
- }
111
- if strings.Contains(string(out), "diagnostics") || strings.Contains(string(out), "warning") {
112
- t.Errorf("warning leaked into wire response: %s", out)
113
- }
114
- }
@@ -1,117 +0,0 @@
1
- // Package plugin is the wire contract between the host and a generator.
2
- //
3
- // One JSON message in, one JSON message out, and the same shape whichever
4
- // direction the generator runs in: an extractor reads source and answers with
5
- // a catalog fragment, a renderer reads the catalog and answers with pages.
6
- // Both name files; neither writes them. The host does that, which is what lets
7
- // a generator run as a wasm module with nothing preopened at all.
8
- package plugin
9
-
10
- import (
11
- "encoding/json"
12
- "fmt"
13
- "os"
14
-
15
- "github.com/shortlink-org/portolan/catalog"
16
- )
17
-
18
- const Version = "0.1.0"
19
-
20
- // Request is everything a plugin is allowed to know. There is no ambient state
21
- // to read, no environment, and - for a wasm plugin - no filesystem either.
22
- type Request struct {
23
- PortolanVersion string `json:"portolanVersion"`
24
-
25
- // Kind is what the host is asking for. Empty - the only thing anyone sent
26
- // before this field existed - means do the work. KindDescribe means answer
27
- // with a Descriptor and read nothing: it is how the manifest schema is
28
- // built, and it has to be cheap enough to run over every declared plugin.
29
- Kind string `json:"kind,omitempty"`
30
-
31
- // Catalog is the merged catalog, for a plugin that renders from it. An
32
- // extractor is handed the zero value: it runs before there is one.
33
- Catalog catalog.Catalog `json:"catalog"`
34
-
35
- // Input is where a plugin that reads source should look. Empty for a
36
- // renderer, which has no business touching the tree.
37
- Input Input `json:"input"`
38
-
39
- // Options belong to the plugin and are passed through from the manifest
40
- // unread, so the host never has to know what any plugin can be told.
41
- Options json.RawMessage `json:"options"`
42
- }
43
-
44
- type Input struct {
45
- // Root is the directory to read, relative to the repository.
46
- Root string `json:"root"`
47
-
48
- // Output is the directory where the host will place the files returned by
49
- // this plugin, relative to the repository. Extractors use it only when a
50
- // catalog entry needs to point at a generated companion document.
51
- Output string `json:"output,omitempty"`
52
-
53
- // Commit and GeneratedAt stamp the fragment (portolan.0002). The host works them out - the
54
- // last commit that touched Root, and its date - rather than the plugin
55
- // reading a clock, because a fragment that changes on every run cannot be
56
- // committed and cannot be checked. Stamped this way it changes exactly when
57
- // the source it describes changes.
58
- Commit string `json:"commit"`
59
- GeneratedAt string `json:"generatedAt"`
60
- }
61
-
62
- // Response is what comes back.
63
- //
64
- // Files are named, not written (portolan.0001). The host rejects a name that climbs out of the
65
- // output directory, and that rejection is the whole of a plugin's authority
66
- // over the tree.
67
- type Response struct {
68
- Files []File `json:"files"`
69
- warnings []Warning
70
-
71
- // Describe answers KindDescribe and is absent otherwise. A plugin written
72
- // against an older version of this protocol answers without it, which the
73
- // host reports rather than treating as a plugin with no options.
74
- Describe *Descriptor `json:"describe,omitempty"`
75
- }
76
-
77
- type File struct {
78
- Name string `json:"name"`
79
- Contents string `json:"contents"`
80
- }
81
-
82
- // Warning is an in-process extraction note. It is intentionally not serialized
83
- // into Response: the wire contract has one decisive outcome, files or error.
84
- type Warning struct {
85
- Severity string `json:"severity"` // "warning" | "error"
86
- Message string `json:"message"`
87
- Ref string `json:"ref,omitempty"`
88
- }
89
-
90
- // Builder accumulates a response. Every plugin needs exactly this and nothing
91
- // more, so it lives here rather than being written twice.
92
- type Builder struct {
93
- Files []File
94
- Warnings []Warning
95
- }
96
-
97
- func (b *Builder) File(name, contents string) {
98
- b.Files = append(b.Files, File{Name: name, Contents: contents})
99
- }
100
-
101
- func (b *Builder) Warn(ref, message string) {
102
- b.Warnings = append(b.Warnings, Warning{Severity: "warning", Message: message, Ref: ref})
103
- where := ""
104
- if ref != "" {
105
- where = ref + ": "
106
- }
107
- fmt.Fprintln(os.Stderr, "warning:", where+message)
108
- }
109
-
110
- func (b *Builder) Response() Response {
111
- return Response{Files: b.Files, warnings: b.Warnings}
112
- }
113
-
114
- // Warnings exposes extraction notes to in-process tests and embedders. They
115
- // are deliberately absent from the JSON protocol: a caller's result is files
116
- // or an error, not files plus an advisory property nobody is required to act on.
117
- func (r Response) Warnings() []Warning { return r.warnings }
@@ -1,126 +0,0 @@
1
- // Package schematest checks that a plugin's options schema still describes its
2
- // options struct.
3
- //
4
- // The schema is written by hand rather than reflected out of the struct,
5
- // because what makes it worth having is the prose: which slug a store expects,
6
- // whose proto package a peer answers for. None of that survives reflection.
7
- // What reflection is good for is the half nobody would notice going stale - a
8
- // field renamed, a field added, a schema left behind - so that is all this
9
- // does.
10
- package schematest
11
-
12
- import (
13
- "encoding/json"
14
- "reflect"
15
- "sort"
16
- "strings"
17
- "testing"
18
- )
19
-
20
- type document struct {
21
- Type string `json:"type"`
22
- AdditionalProperties *bool `json:"additionalProperties"`
23
- Properties map[string]json.RawMessage `json:"properties"`
24
- Required []string `json:"required"`
25
- }
26
-
27
- // Check reports every way the schema and the struct have come apart.
28
- //
29
- // options is a zero value of the plugin's options type; schema is the embedded
30
- // document it hands back when asked to describe itself.
31
- func Check(t *testing.T, schema []byte, options any) {
32
- t.Helper()
33
-
34
- var doc document
35
- if err := json.Unmarshal(schema, &doc); err != nil {
36
- t.Fatalf("options schema is not JSON: %v", err)
37
- }
38
-
39
- if doc.Type != "object" {
40
- t.Errorf("options schema has type %q, want object", doc.Type)
41
- }
42
-
43
- // Without this an unknown key validates, and the misspelled option - the
44
- // one error this whole mechanism exists to catch - goes through silently.
45
- if doc.AdditionalProperties == nil || *doc.AdditionalProperties {
46
- t.Error("options schema must set additionalProperties: false")
47
- }
48
-
49
- described := keys(doc.Properties)
50
- declared := fields(reflect.TypeOf(options))
51
-
52
- for _, name := range missing(declared, described) {
53
- t.Errorf("option %q is in the struct but not in the schema", name)
54
- }
55
- for _, name := range missing(described, declared) {
56
- t.Errorf("option %q is in the schema but not in the struct", name)
57
- }
58
-
59
- for _, name := range doc.Required {
60
- if _, ok := doc.Properties[name]; !ok {
61
- t.Errorf("option %q is required but not described", name)
62
- }
63
- }
64
-
65
- // A property with nothing to say is a property the manifest schema will
66
- // offer with no hint of what it means, which is where this started.
67
- for name, raw := range doc.Properties {
68
- var property struct {
69
- Description string `json:"description"`
70
- }
71
- if err := json.Unmarshal(raw, &property); err != nil {
72
- t.Errorf("option %q: %v", name, err)
73
-
74
- continue
75
- }
76
- if strings.TrimSpace(property.Description) == "" {
77
- t.Errorf("option %q has no description", name)
78
- }
79
- }
80
- }
81
-
82
- // fields are the json names of an options struct, which is the only shape this
83
- // takes: a plugin's options are a flat object read out of the manifest.
84
- func fields(t reflect.Type) []string {
85
- var names []string
86
-
87
- for i := 0; i < t.NumField(); i++ {
88
- tag := t.Field(i).Tag.Get("json")
89
- name, _, _ := strings.Cut(tag, ",")
90
- if name == "" || name == "-" {
91
- continue
92
- }
93
- names = append(names, name)
94
- }
95
-
96
- sort.Strings(names)
97
-
98
- return names
99
- }
100
-
101
- func keys(m map[string]json.RawMessage) []string {
102
- names := make([]string, 0, len(m))
103
- for name := range m {
104
- names = append(names, name)
105
- }
106
- sort.Strings(names)
107
-
108
- return names
109
- }
110
-
111
- // missing is everything in a that is not in b.
112
- func missing(a, b []string) []string {
113
- have := make(map[string]bool, len(b))
114
- for _, name := range b {
115
- have[name] = true
116
- }
117
-
118
- var gone []string
119
- for _, name := range a {
120
- if !have[name] {
121
- gone = append(gone, name)
122
- }
123
- }
124
-
125
- return gone
126
- }
@@ -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-adr",
15
- Summary: "Reads decision records written by hand - MADR markdown with a title, meta bullets and the record itself, or the numbered records adr-tools writes - into a catalog fragment.",
16
- Phases: []string{plugin.PhaseExtract},
17
- Options: optionsSchema,
18
- }
19
- }
@@ -1,11 +0,0 @@
1
- package main
2
-
3
- import (
4
- "testing"
5
-
6
- "github.com/shortlink-org/portolan/plugin/schematest"
7
- )
8
-
9
- func TestOptionsSchemaMatchesStruct(t *testing.T) {
10
- schematest.Check(t, optionsSchema, Options{})
11
- }