@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,473 +0,0 @@
1
- package main
2
-
3
- import (
4
- "encoding/json"
5
- "fmt"
6
- "path"
7
- "sort"
8
- "strings"
9
-
10
- "github.com/shortlink-org/portolan/catalog"
11
- "github.com/shortlink-org/portolan/plugin"
12
- "gopkg.in/yaml.v3"
13
- )
14
-
15
- type Options struct {
16
- Domain string `json:"domain,omitempty"`
17
- DomainTitle string `json:"domainTitle,omitempty"`
18
- Owner string `json:"owner,omitempty"`
19
- Lifecycle string `json:"lifecycle,omitempty"`
20
- SourceBaseURL string `json:"sourceBaseUrl,omitempty"`
21
- }
22
-
23
- type entity struct {
24
- APIVersion string `yaml:"apiVersion"`
25
- Kind string `yaml:"kind"`
26
- Metadata metadata `yaml:"metadata"`
27
- Spec map[string]any `yaml:"spec"`
28
- }
29
-
30
- type metadata struct {
31
- Name string `yaml:"name"`
32
- Title string `yaml:"title,omitempty"`
33
- Description string `yaml:"description,omitempty"`
34
- Annotations map[string]string `yaml:"annotations,omitempty"`
35
- Tags []string `yaml:"tags,omitempty"`
36
- Links []link `yaml:"links,omitempty"`
37
- }
38
-
39
- // link is one of Backstage's entity links, the card on the entity page
40
- // that takes a reader somewhere. `type` is Backstage's own free-text
41
- // category; every link here is a command.
42
- type link struct {
43
- URL string `yaml:"url"`
44
- Title string `yaml:"title,omitempty"`
45
- Type string `yaml:"type,omitempty"`
46
- }
47
-
48
- func render(req plugin.Request, opts Options) (plugin.Response, error) {
49
- if opts.Domain == "" {
50
- opts.Domain = "architecture"
51
- }
52
- if opts.DomainTitle == "" {
53
- opts.DomainTitle = "Architecture"
54
- }
55
- if opts.Owner == "" {
56
- opts.Owner = "architecture"
57
- }
58
- if opts.Lifecycle == "" {
59
- opts.Lifecycle = "production"
60
- }
61
-
62
- domain := entityFor("Domain", opts.Domain, opts.DomainTitle, "Architecture catalog exported by Portolan", "portolan.domain")
63
- domain.Spec = map[string]any{"owner": opts.Owner}
64
- entities := []entity{domain}
65
-
66
- contexts := append([]catalog.BoundedContext(nil), req.Catalog.Contexts...)
67
- sort.Slice(contexts, func(i, j int) bool { return contexts[i].ID < contexts[j].ID })
68
- apiName := map[string]string{}
69
- serviceName := map[string]string{}
70
- for i := range contexts {
71
- for j := range contexts[i].Services {
72
- svc := &contexts[i].Services[j]
73
- serviceName[svc.ID] = nameOf(svc.ID)
74
- for k := range svc.Provides {
75
- apiName[svc.Provides[k].ID] = nameOf(svc.Provides[k].ID)
76
- }
77
- }
78
- }
79
- for i := range req.Catalog.Externals {
80
- serviceName[req.Catalog.Externals[i].ID] = nameOf(req.Catalog.Externals[i].ID)
81
- for j := range req.Catalog.Externals[i].Provides {
82
- apiName[req.Catalog.Externals[i].Provides[j].ID] = nameOf(req.Catalog.Externals[i].Provides[j].ID)
83
- }
84
- }
85
-
86
- externals := append([]catalog.External(nil), req.Catalog.Externals...)
87
- sort.Slice(externals, func(i, j int) bool { return externals[i].ID < externals[j].ID })
88
- for i := range externals {
89
- ext := &externals[i]
90
- component := entityFor("Component", ext.ID, ext.Name, ext.Summary, ext.ID)
91
- provides := make([]string, 0, len(ext.Provides))
92
- for j := range ext.Provides {
93
- provides = append(provides, "api:default/"+nameOf(ext.Provides[j].ID))
94
- }
95
- component.Spec = compact(map[string]any{
96
- "type": "external-service", "lifecycle": opts.Lifecycle,
97
- "owner": opts.Owner, "providesApis": sortedUnique(provides),
98
- })
99
- entities = append(entities, component)
100
- for j := range ext.Provides {
101
- api := entityFor("API", ext.Provides[j].ID, ext.Provides[j].ID, "Interface provided by "+ext.Name, ext.Provides[j].ID)
102
- definition, err := json.MarshalIndent(map[string]any{"id": ext.Provides[j].ID, "methods": ext.Provides[j].Methods, "messages": ext.Provides[j].Messages}, "", " ")
103
- if err != nil {
104
- return plugin.Response{}, err
105
- }
106
- api.Spec = map[string]any{"type": "portolan", "lifecycle": opts.Lifecycle, "owner": opts.Owner, "definition": string(definition)}
107
- entities = append(entities, api)
108
- }
109
- }
110
-
111
- for i := range contexts {
112
- ctx := &contexts[i]
113
- system := entityFor("System", ctx.ID, ctx.Name, ctx.Summary, ctx.ID)
114
- system.Spec = map[string]any{"owner": opts.Owner, "domain": nameOf(opts.Domain)}
115
- entities = append(entities, system)
116
-
117
- services := append([]catalog.Service(nil), ctx.Services...)
118
- sort.Slice(services, func(i, j int) bool { return services[i].ID < services[j].ID })
119
- for j := range services {
120
- svc := &services[j]
121
- owner := ownerOf(svc, opts.Owner)
122
- component := entityFor("Component", svc.ID, svc.Name, firstLine(svc.Readme), svc.ID)
123
- component.Metadata.Annotations = annotations(opts.SourceBaseURL, svc.ID, svc.Path, svc.Repo, true)
124
- component.Metadata.Tags = backstageTags(svc.Technologies)
125
- commands(&component.Metadata, svc, opts.SourceBaseURL)
126
- depends := []string{}
127
- consumes := []string{}
128
- for _, call := range svc.Consumes {
129
- if peer := serviceName[call.Peer]; peer != "" {
130
- depends = append(depends, "component:default/"+peer)
131
- }
132
- if api := apiName[interfaceOf(call.ID)]; api != "" {
133
- consumes = append(consumes, "api:default/"+api)
134
- }
135
- }
136
- for _, store := range svc.Stores {
137
- depends = append(depends, "resource:default/"+nameOf(store))
138
- }
139
- for _, module := range svc.Modules {
140
- depends = append(depends, "resource:default/"+nameOf(module))
141
- }
142
- provides := make([]string, 0, len(svc.Provides))
143
- for _, provided := range svc.Provides {
144
- provides = append(provides, "api:default/"+nameOf(provided.ID))
145
- }
146
- component.Spec = compact(map[string]any{
147
- "type": componentType(svc.Kind), "lifecycle": opts.Lifecycle, "owner": owner,
148
- "system": nameOf(ctx.ID), "dependsOn": sortedUnique(depends),
149
- "providesApis": sortedUnique(provides), "consumesApis": sortedUnique(consumes),
150
- })
151
- entities = append(entities, component)
152
- for k := range svc.Provides {
153
- api, err := apiEntity(req, &svc.Provides[k], svc, ctx, opts)
154
- if err != nil {
155
- return plugin.Response{}, err
156
- }
157
- entities = append(entities, api)
158
- }
159
- }
160
- }
161
-
162
- stores := append([]catalog.Store(nil), req.Catalog.Stores...)
163
- sort.Slice(stores, func(i, j int) bool { return stores[i].ID < stores[j].ID })
164
- for i := range stores {
165
- store := &stores[i]
166
- resource := entityFor("Resource", store.ID, store.Name, "State owned by "+store.Owner, store.ID)
167
- resource.Metadata.Annotations = annotations(opts.SourceBaseURL, store.ID, store.Source, repoOf(req.Catalog, store.Owner), sourceIsDirectory(store.Source))
168
- resource.Spec = compact(map[string]any{"type": string(store.Kind), "owner": ownerForID(req.Catalog, store.Owner, opts.Owner), "system": systemFor(req.Catalog, store.Owner)})
169
- entities = append(entities, resource)
170
- }
171
-
172
- modules := append([]catalog.ProtoModule(nil), req.Catalog.Modules...)
173
- sort.Slice(modules, func(i, j int) bool { return modules[i].ID < modules[j].ID })
174
- for i := range modules {
175
- module := &modules[i]
176
- resource := entityFor("Resource", module.ID, module.Name, "Schema module "+module.ID, module.ID)
177
- resource.Metadata.Annotations = annotations(opts.SourceBaseURL, module.ID, module.Source, repoOf(req.Catalog, module.Owner), sourceIsDirectory(module.Source))
178
- resource.Spec = compact(map[string]any{"type": "schema-module", "owner": ownerForID(req.Catalog, module.Owner, opts.Owner), "system": systemFor(req.Catalog, module.Owner)})
179
- entities = append(entities, resource)
180
- }
181
-
182
- documents := make([]string, 0, len(entities))
183
- for i := range entities {
184
- if err := validateEntity(entities[i], entities); err != nil {
185
- return plugin.Response{}, err
186
- }
187
- raw, err := yaml.Marshal(entities[i])
188
- if err != nil {
189
- return plugin.Response{}, fmt.Errorf("backstage entity %s: %w", entities[i].Metadata.Name, err)
190
- }
191
- documents = append(documents, strings.TrimSpace(string(raw)))
192
- }
193
- return plugin.Response{Files: []plugin.File{{Name: "catalog-info.yaml", Contents: strings.Join(documents, "\n---\n") + "\n"}}}, nil
194
- }
195
-
196
- func componentType(kind catalog.ComponentKind) string {
197
- switch kind {
198
- case catalog.ComponentKindWebapp:
199
- return "website"
200
- case catalog.ComponentKindLibrary:
201
- return "library"
202
- case catalog.ComponentKindCLI:
203
- return "tool"
204
- case catalog.ComponentKindDataPipeline:
205
- return "data-pipeline"
206
- case catalog.ComponentKindApplication:
207
- return "application"
208
- case catalog.ComponentKindWorker:
209
- return "worker"
210
- case catalog.ComponentKindJob:
211
- return "job"
212
- case catalog.ComponentKindFunction:
213
- return "function"
214
- default:
215
- return "service"
216
- }
217
- }
218
-
219
- func backstageTags(technologies []string) []string {
220
- tags := make([]string, 0, len(technologies))
221
- for _, technology := range technologies {
222
- if tag := nameOf(technology); tag != "" {
223
- tags = append(tags, tag)
224
- }
225
- }
226
- return sortedUnique(tags)
227
- }
228
-
229
- func apiEntity(req plugin.Request, rpc *catalog.RpcService, svc *catalog.Service, ctx *catalog.BoundedContext, opts Options) (entity, error) {
230
- definition, err := json.MarshalIndent(map[string]any{"id": rpc.ID, "module": rpc.Module, "methods": rpc.Methods, "messages": rpc.Messages}, "", " ")
231
- if err != nil {
232
- return entity{}, err
233
- }
234
- api := entityFor("API", rpc.ID, rpc.ID, "Interface provided by "+svc.Name, rpc.ID)
235
- api.Metadata.Annotations = annotations(opts.SourceBaseURL, rpc.ID, sourceWithin(svc.Path, rpc.Source), svc.Repo, false)
236
- // The definition is Portolan's normalized contract, not the original
237
- // OpenAPI/proto document. Calling it openapi or grpc would make Backstage
238
- // feed valid JSON to the wrong renderer.
239
- api.Spec = compact(map[string]any{"type": "portolan", "lifecycle": opts.Lifecycle, "owner": ownerOf(svc, opts.Owner), "system": nameOf(ctx.ID), "definition": string(definition)})
240
- return api, nil
241
- }
242
-
243
- func entityFor(kind, id, title, description, portolanID string) entity {
244
- return entity{APIVersion: "backstage.io/v1alpha1", Kind: kind, Metadata: metadata{Name: nameOf(id), Title: title, Description: strings.TrimSpace(description), Annotations: map[string]string{"portolan.io/id": portolanID}}, Spec: map[string]any{}}
245
- }
246
-
247
- func annotations(sourceBase, id, source, repo string, directory bool) map[string]string {
248
- out := map[string]string{"portolan.io/id": id}
249
- if url := sourceURL(sourceBase, source, repo, directory, false); url != "" {
250
- out["backstage.io/source-location"] = "url:" + url
251
- }
252
- return out
253
- }
254
-
255
- // sourceURL is where `source` is on the forge, when the source base names
256
- // the same repository - or "" when the catalog cannot say. A `:line` suffix
257
- // is dropped, or kept as the `#L` anchor both forges read, when asked.
258
- func sourceURL(sourceBase, source, repo string, directory, keepLine bool) string {
259
- if sourceBase == "" || !sameRepo(repo, sourceBaseRepo(sourceBase)) {
260
- return ""
261
- }
262
- path := strings.TrimPrefix(strings.TrimSpace(source), "/")
263
- line := ""
264
- if at := strings.LastIndex(path, ":"); at > 0 {
265
- if keepLine {
266
- line = "#L" + path[at+1:]
267
- }
268
- path = path[:at]
269
- }
270
- if path == "" {
271
- return ""
272
- }
273
- base := strings.TrimSuffix(sourceBase, "/")
274
- if directory {
275
- base = strings.Replace(base, "/-/blob/", "/-/tree/", 1)
276
- base = strings.Replace(base, "/blob/", "/tree/", 1)
277
- }
278
- return base + "/" + path + line
279
- }
280
-
281
- // commands puts what a developer types onto the component, twice, because
282
- // Backstage has no field for it. The annotation is the list itself, one
283
- // command a line with its description, for anything that reads the entity;
284
- // the links are the same commands as the Links card draws them, each
285
- // leading to the line of the runner file it was read from, for the reader
286
- // on the entity page. Links need a URL, so they exist only when the source
287
- // base says where the repository is.
288
- func commands(meta *metadata, svc *catalog.Service, sourceBase string) {
289
- if len(svc.Commands) == 0 {
290
- return
291
- }
292
- lines := make([]string, 0, len(svc.Commands))
293
- for _, cmd := range svc.Commands {
294
- line := cmd.Run
295
- if cmd.Doc != "" {
296
- line += " — " + cmd.Doc
297
- }
298
- lines = append(lines, line)
299
- if url := sourceURL(sourceBase, cmd.Source, svc.Repo, false, true); url != "" {
300
- meta.Links = append(meta.Links, link{URL: url, Title: line, Type: "command"})
301
- }
302
- }
303
- meta.Annotations["portolan.io/commands"] = strings.Join(lines, "\n")
304
- }
305
-
306
- func sourceBaseRepo(base string) string {
307
- for _, marker := range []string{"/-/blob/", "/-/tree/", "/blob/", "/tree/"} {
308
- if at := strings.Index(base, marker); at >= 0 {
309
- return base[:at]
310
- }
311
- }
312
- return base
313
- }
314
-
315
- func nameOf(value string) string {
316
- var b strings.Builder
317
- dash := false
318
- for _, r := range strings.ToLower(value) {
319
- if r >= 'a' && r <= 'z' || r >= '0' && r <= '9' {
320
- b.WriteRune(r)
321
- dash = false
322
- } else if b.Len() > 0 && !dash {
323
- b.WriteByte('-')
324
- dash = true
325
- }
326
- }
327
- name := strings.Trim(b.String(), "-")
328
- if name == "" {
329
- return "portolan-entity"
330
- }
331
- if len(name) > 63 {
332
- name = strings.TrimRight(name[:63], "-")
333
- }
334
- return name
335
- }
336
-
337
- func sourceWithin(root, source string) string {
338
- root = strings.Trim(strings.TrimSpace(root), "/")
339
- source = strings.TrimPrefix(strings.TrimSpace(source), "/")
340
- if root == "" || source == "" || source == root || strings.HasPrefix(source, root+"/") {
341
- return source
342
- }
343
- return path.Join(root, source)
344
- }
345
-
346
- func sourceIsDirectory(source string) bool {
347
- source = strings.TrimSuffix(strings.TrimSpace(source), "/")
348
- if at := strings.LastIndex(source, ":"); at > 0 {
349
- source = source[:at]
350
- }
351
- return path.Ext(source) == ""
352
- }
353
-
354
- func validateEntity(current entity, all []entity) error {
355
- seen := map[string]bool{}
356
- for _, candidate := range all {
357
- key := strings.ToLower(candidate.Kind) + ":default/" + candidate.Metadata.Name
358
- if seen[key] {
359
- return fmt.Errorf("duplicate Backstage entity %s", key)
360
- }
361
- seen[key] = true
362
- }
363
-
364
- check := func(field string, refs []string) error {
365
- for _, ref := range refs {
366
- if !seen[strings.ToLower(ref)] {
367
- return fmt.Errorf("backstage entity %s/%s has unresolved %s reference %q", current.Kind, current.Metadata.Name, field, ref)
368
- }
369
- }
370
- return nil
371
- }
372
- for _, field := range []string{"dependsOn", "providesApis", "consumesApis"} {
373
- if refs, ok := current.Spec[field].([]string); ok {
374
- if err := check(field, refs); err != nil {
375
- return err
376
- }
377
- }
378
- }
379
- if value, ok := current.Spec["system"].(string); ok && value != "" {
380
- if err := check("system", []string{"system:default/" + value}); err != nil {
381
- return err
382
- }
383
- }
384
- if value, ok := current.Spec["domain"].(string); ok && value != "" {
385
- if err := check("domain", []string{"domain:default/" + value}); err != nil {
386
- return err
387
- }
388
- }
389
- return nil
390
- }
391
-
392
- func ownerOf(service *catalog.Service, fallback string) string {
393
- if len(service.Owners) == 0 {
394
- return fallback
395
- }
396
- return strings.TrimPrefix(service.Owners[0], "@")
397
- }
398
-
399
- func ownerForID(cat catalog.Catalog, id, fallback string) string {
400
- for i := range cat.Contexts {
401
- for j := range cat.Contexts[i].Services {
402
- if cat.Contexts[i].Services[j].ID == id {
403
- return ownerOf(&cat.Contexts[i].Services[j], fallback)
404
- }
405
- }
406
- }
407
- return fallback
408
- }
409
- func systemFor(cat catalog.Catalog, service string) string {
410
- for i := range cat.Contexts {
411
- for j := range cat.Contexts[i].Services {
412
- if cat.Contexts[i].Services[j].ID == service {
413
- return nameOf(cat.Contexts[i].ID)
414
- }
415
- }
416
- }
417
- return ""
418
- }
419
- func repoOf(cat catalog.Catalog, service string) string {
420
- for i := range cat.Contexts {
421
- for j := range cat.Contexts[i].Services {
422
- if cat.Contexts[i].Services[j].ID == service {
423
- return cat.Contexts[i].Services[j].Repo
424
- }
425
- }
426
- }
427
- return ""
428
- }
429
- func interfaceOf(call string) string {
430
- if at := strings.LastIndex(call, "/"); at > 0 {
431
- return call[:at]
432
- }
433
- return call
434
- }
435
- func sameRepo(a, b string) bool {
436
- clean := func(v string) string {
437
- return strings.ToLower(strings.TrimSuffix(strings.TrimSuffix(strings.TrimPrefix(strings.TrimPrefix(v, "https://"), "http://"), ".git"), "/"))
438
- }
439
- return a == "" || clean(a) == clean(b)
440
- }
441
- func firstLine(value string) string {
442
- if at := strings.Index(value, "\n"); at >= 0 {
443
- value = value[:at]
444
- }
445
- return strings.TrimSpace(strings.TrimPrefix(value, "# "))
446
- }
447
- func sortedUnique(values []string) []string {
448
- seen := map[string]bool{}
449
- out := []string{}
450
- for _, value := range values {
451
- if value != "" && !seen[value] {
452
- seen[value] = true
453
- out = append(out, value)
454
- }
455
- }
456
- sort.Strings(out)
457
- return out
458
- }
459
- func compact(values map[string]any) map[string]any {
460
- for key, value := range values {
461
- switch v := value.(type) {
462
- case string:
463
- if v == "" {
464
- delete(values, key)
465
- }
466
- case []string:
467
- if len(v) == 0 {
468
- delete(values, key)
469
- }
470
- }
471
- }
472
- return values
473
- }
@@ -1,145 +0,0 @@
1
- package main
2
-
3
- import (
4
- "strings"
5
- "testing"
6
-
7
- "github.com/shortlink-org/portolan/catalog"
8
- "github.com/shortlink-org/portolan/plugin"
9
- "github.com/shortlink-org/portolan/plugin/schematest"
10
- "gopkg.in/yaml.v3"
11
- )
12
-
13
- func TestOptionsSchema(t *testing.T) { schematest.Check(t, optionsSchema, Options{}) }
14
-
15
- func TestRenderBackstageRelationships(t *testing.T) {
16
- cat := catalog.Catalog{
17
- Contexts: []catalog.BoundedContext{{
18
- ID: "shop", Name: "Shop",
19
- Services: []catalog.Service{{
20
- ID: "shop.cart", Name: "Cart", Repo: "github.com/acme/shop", Path: "services/cart",
21
- Owners: []string{"@team-cart"}, Stores: []string{"shop.cart.pg"},
22
- Provides: []catalog.RpcService{{
23
- ID: "shop.v1.Cart", Source: "api.yaml",
24
- Methods: []catalog.RpcMethod{{Name: "Get", HTTP: &catalog.HttpRoute{Method: "GET", Path: "/cart"}}},
25
- }},
26
- }},
27
- }},
28
- Stores: []catalog.Store{{ID: "shop.cart.pg", Name: "Cart DB", Kind: catalog.StoreKindPostgres, Owner: "shop.cart"}},
29
- }
30
- resp, err := render(plugin.Request{Catalog: cat}, Options{SourceBaseURL: "https://github.com/acme/shop/blob/main"})
31
- if err != nil {
32
- t.Fatal(err)
33
- }
34
- parts := strings.Split(resp.Files[0].Contents, "\n---\n")
35
- if len(parts) != 5 {
36
- t.Fatalf("got %d entities\n%s", len(parts), resp.Files[0].Contents)
37
- }
38
- for _, part := range parts {
39
- var value map[string]any
40
- if err := yaml.Unmarshal([]byte(part), &value); err != nil {
41
- t.Fatal(err)
42
- }
43
- }
44
- if !strings.Contains(resp.Files[0].Contents, "providesApis:") || !strings.Contains(resp.Files[0].Contents, "team-cart") {
45
- t.Fatal(resp.Files[0].Contents)
46
- }
47
- if !strings.Contains(resp.Files[0].Contents, "url:https://github.com/acme/shop/blob/main/services/cart/api.yaml") {
48
- t.Fatal(resp.Files[0].Contents)
49
- }
50
- }
51
-
52
- func TestComponentCarriesItsCommands(t *testing.T) {
53
- cat := catalog.Catalog{Contexts: []catalog.BoundedContext{{
54
- ID: "shop", Name: "Shop", Services: []catalog.Service{{
55
- ID: "shop.pricing", Name: "Pricing", Repo: "github.com/acme/shop", Path: "services/pricing",
56
- Commands: []catalog.Command{
57
- {Runner: "make", Name: "gen", Run: "make gen", Doc: "Regenerate the stubs", Body: "buf generate", Source: "services/pricing/Makefile:6"},
58
- {Runner: "make", Name: "test", Run: "make test", Source: "services/pricing/Makefile:15"},
59
- },
60
- }},
61
- }}}
62
- resp, err := render(plugin.Request{Catalog: cat}, Options{SourceBaseURL: "https://github.com/acme/shop/blob/main"})
63
- if err != nil {
64
- t.Fatal(err)
65
- }
66
- var component struct {
67
- Metadata struct {
68
- Annotations map[string]string
69
- Links []struct{ URL, Title, Type string }
70
- }
71
- }
72
- var part string
73
- for _, candidate := range strings.Split(resp.Files[0].Contents, "\n---\n") {
74
- if strings.Contains(candidate, "kind: Component") {
75
- part = candidate
76
- }
77
- }
78
- if err := yaml.Unmarshal([]byte(part), &component); err != nil {
79
- t.Fatal(err)
80
- }
81
- if got := component.Metadata.Annotations["portolan.io/commands"]; got != "make gen — Regenerate the stubs\nmake test" {
82
- t.Errorf("annotation = %q", got)
83
- }
84
- if len(component.Metadata.Links) != 2 || component.Metadata.Links[0].URL != "https://github.com/acme/shop/blob/main/services/pricing/Makefile#L6" || component.Metadata.Links[0].Title != "make gen — Regenerate the stubs" || component.Metadata.Links[0].Type != "command" {
85
- t.Errorf("links = %+v", component.Metadata.Links)
86
- }
87
-
88
- // Without a source base there is nowhere to link to; the list still
89
- // travels in the annotation.
90
- resp, err = render(plugin.Request{Catalog: cat}, Options{})
91
- if err != nil {
92
- t.Fatal(err)
93
- }
94
- if strings.Contains(resp.Files[0].Contents, "links:") || !strings.Contains(resp.Files[0].Contents, "portolan.io/commands") {
95
- t.Fatal(resp.Files[0].Contents)
96
- }
97
- }
98
-
99
- func TestNameIsBackstageSafe(t *testing.T) {
100
- if got := nameOf("buf.build/Acme/Very Long API"); got != "buf-build-acme-very-long-api" {
101
- t.Fatalf("%q", got)
102
- }
103
- }
104
-
105
- func TestNeutralComponentKindBecomesBackstageType(t *testing.T) {
106
- cat := catalog.Catalog{Contexts: []catalog.BoundedContext{{
107
- ID: "tools", Name: "Tools", Kind: catalog.GroupKindSystem, Services: []catalog.Service{{
108
- ID: "tools.cli", Name: "CLI", Kind: catalog.ComponentKindCLI, Technologies: []string{"OpenTelemetry", "Go"},
109
- }},
110
- }}}
111
- resp, err := render(plugin.Request{Catalog: cat}, Options{})
112
- if err != nil {
113
- t.Fatal(err)
114
- }
115
- if !strings.Contains(resp.Files[0].Contents, "type: tool") {
116
- t.Fatal(resp.Files[0].Contents)
117
- }
118
- if !strings.Contains(resp.Files[0].Contents, "- go") || !strings.Contains(resp.Files[0].Contents, "- opentelemetry") {
119
- t.Fatal(resp.Files[0].Contents)
120
- }
121
- }
122
-
123
- func TestNameIsASCII(t *testing.T) {
124
- if got := nameOf("Оплата.API"); got != "api" {
125
- t.Fatalf("%q", got)
126
- }
127
- }
128
-
129
- func TestSourceDirectoryDetection(t *testing.T) {
130
- if !sourceIsDirectory("services/cart/repository") || sourceIsDirectory("services/cart/api.yaml:12") {
131
- t.Fatal("source directory detection drifted")
132
- }
133
- }
134
-
135
- func TestRenderRejectsDanglingBackstageReferences(t *testing.T) {
136
- cat := catalog.Catalog{Contexts: []catalog.BoundedContext{{
137
- ID: "shop", Name: "Shop", Services: []catalog.Service{{
138
- ID: "shop.cart", Name: "Cart", Stores: []string{"missing.store"},
139
- }},
140
- }}}
141
- _, err := render(plugin.Request{Catalog: cat}, Options{})
142
- if err == nil || !strings.Contains(err.Error(), "unresolved dependsOn") {
143
- t.Fatalf("expected unresolved reference, got %v", err)
144
- }
145
- }
Binary file