@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,45 +0,0 @@
1
- // Command extract-flows reads flows written by hand, in a text form that reads
2
- // like the sequence diagram it becomes, and answers with a catalog fragment.
3
- //
4
- // Some flows will always be written by people: the design doc for something
5
- // not built yet, the reconstruction after an incident, the path a test does
6
- // not pin. The catalog's JSON is the wrong place to write them - a tree of
7
- // nodes, a unique id per step, every lane declared twice - so this is the
8
- // right one: one file per flow, one line per hop, frames closed by `end`. The
9
- // format is described in plugins/README.md and held to by parse_test.go.
10
- package main
11
-
12
- import (
13
- "fmt"
14
- "io"
15
- "os"
16
-
17
- "github.com/shortlink-org/portolan/plugin"
18
- )
19
-
20
- // Options are what the manifest tells the extractor.
21
- type Options struct {
22
- // Files are the flow files to read, as globs relative to the input root.
23
- // Left out, every `*.flow.md` directly under the root.
24
- Files []string `json:"files,omitempty"`
25
-
26
- // Out names the fragment file.
27
- Out string `json:"out,omitempty"`
28
- }
29
-
30
- func main() {
31
- if err := run(os.Stdin, os.Stdout); err != nil {
32
- fmt.Fprintln(os.Stderr, "portolan-extract-flows:", err)
33
- os.Exit(1)
34
- }
35
- }
36
-
37
- func run(stdin io.Reader, stdout io.Writer) error {
38
- return plugin.Serve(stdin, stdout, descriptor(), func(req plugin.Request, opts Options) (plugin.Response, error) {
39
- if req.Input.Root == "" {
40
- return plugin.Response{}, fmt.Errorf("no input root: an extractor has nothing to read")
41
- }
42
-
43
- return extract(req.Input, opts)
44
- })
45
- }
@@ -1,593 +0,0 @@
1
- package main
2
-
3
- import (
4
- "path"
5
- "regexp"
6
- "strconv"
7
- "strings"
8
-
9
- "github.com/shortlink-org/portolan/catalog"
10
- )
11
-
12
- // The format, in one place.
13
- //
14
- // # Order accepted the name; the slug is the file's
15
- // owner: shop the context that owns the flow
16
- // source: services/oms/test/x_test.go where it was read from; the file itself when left out
17
- // slug: order-accepted only when the file's name is not it
18
- //
19
- // One or more paragraphs of summary.
20
- //
21
- // ## Participants
22
- // - oms-db: store in shop "oms-db (postgres)"
23
- // - psp-gateway: external "psp-gateway (external)"
24
- //
25
- // ## Steps
26
- // shop.oms -> oms-db: insertOrderAndOutboxRow [verified] @order_repo.go:141 #a1
27
- // > A note, as many lines as it takes.
28
- // shop.oms -> bus: event shop.oms.order.OrderPlaced [verified]
29
- // shop.oms -> shop.pricing: rpc shop.v1.Pricing/GetQuote as "GetQuote (250 ms)"
30
- // alt score below 40 #alt-risk
31
- // ...
32
- // else score at or above 40
33
- // ...
34
- // stop
35
- // else
36
- // end
37
- // par OrderPlaced fan-out
38
- // ...
39
- // and
40
- // ...
41
- // end
42
- // loop until the batch is empty
43
- // ...
44
- // end
45
- //
46
- // A hop is `from -> to: [call|rpc|event] label-or-ref`, `call` when no kind is
47
- // written, and after it in any order: `as "label"`, `[status]`, `@file:line`,
48
- // `#id`. An event's or rpc's label is the last segment of its ref unless `as`
49
- // says otherwise. `stop` ends the alt branch it is in - the flow does not
50
- // continue past the frame on that path. `else` with no condition is
51
- // "otherwise". Services are known by their `context.service` id, `bus` and
52
- // `client` by their names; anything else is declared under Participants, in
53
- // the order the lanes should be drawn. Lines starting with `//` are comments.
54
-
55
- type parser struct {
56
- file string
57
- lines []string
58
- errs []string
59
-
60
- flow catalog.Flow
61
- declared []catalog.Participant
62
- lanes map[string]catalog.Participant
63
- used []catalog.Participant
64
- ids map[string]bool
65
- n int
66
- stack []*frame
67
- last *catalog.Step // for notes
68
- stopped bool // the current alt branch has ended
69
- hasSteps bool
70
- sawTitle bool
71
- sawOwner bool
72
- sawSource bool
73
- }
74
-
75
- // frame is an open alt, par or loop and the branch of it being read.
76
- type frame struct {
77
- kind string
78
- id string
79
- // title is the frame's own, for a par or a loop; current is the branch
80
- // being read, for an alt, where every branch has a condition.
81
- title string
82
- current string
83
- alt []catalog.AltBranch
84
- par []catalog.FlowNodes
85
- steps catalog.FlowNodes
86
- }
87
-
88
- var (
89
- titleLine = regexp.MustCompile(`^#\s+(.+?)\s*$`)
90
- metaLine = regexp.MustCompile(`^(owner|source|slug):\s*(.*?)\s*$`)
91
- partLine = regexp.MustCompile(`^-\s+([^\s:]+):\s*(actor|service|broker|store|external|unknown)(?:\s+in\s+([^\s"]+))?(?:\s+"([^"]*)")?\s*$`)
92
- stepLine = regexp.MustCompile(`^([^\s>]+)\s*->\s*([^\s:]+):\s*(.*)$`)
93
- idSuffix = regexp.MustCompile(`\s+#(\S+)\s*$`)
94
- kindPrefix = regexp.MustCompile(`^(call|rpc|event)\s+(.*)$`)
95
- )
96
-
97
- func parseFlow(file, src string) (catalog.Flow, []string) {
98
- p := &parser{
99
- file: file,
100
- lines: strings.Split(strings.ReplaceAll(src, "\r\n", "\n"), "\n"),
101
- lanes: map[string]catalog.Participant{},
102
- ids: map[string]bool{},
103
- }
104
- p.read()
105
- if len(p.errs) > 0 {
106
- return catalog.Flow{}, p.errs
107
- }
108
-
109
- return p.flow, nil
110
- }
111
-
112
- func (p *parser) fail(line int, msg string) {
113
- p.errs = append(p.errs, p.file+":"+strconv.Itoa(line+1)+": "+msg)
114
- }
115
-
116
- func (p *parser) read() {
117
- section := "head"
118
- var summary []string
119
- var paragraph []string
120
- flush := func() {
121
- if len(paragraph) > 0 {
122
- summary = append(summary, strings.Join(paragraph, " "))
123
- paragraph = nil
124
- }
125
- }
126
-
127
- for i, raw := range p.lines {
128
- line := strings.TrimSpace(raw)
129
- if strings.HasPrefix(line, "//") {
130
- continue
131
- }
132
-
133
- switch {
134
- case strings.HasPrefix(line, "## "):
135
- flush()
136
- switch strings.ToLower(strings.TrimSpace(line[3:])) {
137
- case "participants":
138
- section = "participants"
139
- case "steps":
140
- section = "steps"
141
- default:
142
- p.fail(i, "unknown section "+strconv.Quote(line[3:])+"; the sections are Participants and Steps")
143
- }
144
-
145
- continue
146
- case section == "head":
147
- p.head(i, line, &paragraph, flush)
148
- case section == "participants":
149
- p.participant(i, line)
150
- case section == "steps":
151
- p.step(i, line)
152
- }
153
- }
154
- flush()
155
-
156
- if !p.sawTitle {
157
- p.fail(0, "no title: the first line names the flow, as `# Name`")
158
- }
159
- if !p.sawOwner {
160
- p.fail(0, "no owner: say which context the flow belongs to, as `owner: <context>`")
161
- }
162
- if !p.hasSteps {
163
- p.fail(0, "no steps: a flow is at least one hop under `## Steps`")
164
- }
165
- for _, f := range p.stack {
166
- p.fail(len(p.lines)-1, f.kind+" is never closed; every frame ends with `end`")
167
- }
168
- if len(p.errs) > 0 {
169
- return
170
- }
171
-
172
- p.flow.Summary = strings.Join(summary, "\n\n")
173
- if p.flow.Slug == "" {
174
- base := path.Base(p.file)
175
- p.flow.Slug = strings.TrimSuffix(strings.TrimSuffix(base, ".md"), ".flow")
176
- }
177
- p.flow.ID = "flow." + p.flow.Slug
178
- if !p.sawSource {
179
- p.flow.Source = p.file
180
- }
181
- p.flow.Participants = append(append([]catalog.Participant{}, p.declared...), p.used...)
182
- if p.flow.Participants == nil {
183
- p.flow.Participants = []catalog.Participant{}
184
- }
185
- }
186
-
187
- func (p *parser) head(i int, line string, paragraph *[]string, flush func()) {
188
- if line == "" {
189
- flush()
190
-
191
- return
192
- }
193
- if m := titleLine.FindStringSubmatch(line); m != nil && !p.sawTitle {
194
- p.flow.Name = m[1]
195
- p.sawTitle = true
196
-
197
- return
198
- }
199
- if m := metaLine.FindStringSubmatch(line); m != nil && len(*paragraph) == 0 {
200
- switch m[1] {
201
- case "owner":
202
- p.flow.Owner = m[2]
203
- p.sawOwner = true
204
- case "source":
205
- p.flow.Source = m[2]
206
- p.sawSource = true
207
- case "slug":
208
- p.flow.Slug = m[2]
209
- }
210
-
211
- return
212
- }
213
- *paragraph = append(*paragraph, line)
214
- }
215
-
216
- func (p *parser) participant(i int, line string) {
217
- if line == "" {
218
- return
219
- }
220
- m := partLine.FindStringSubmatch(line)
221
- if m == nil {
222
- p.fail(i, "not a participant: write `- <id>: <kind> [in <context>] [\"label\"]`, with kind one of actor, service, broker, store, external, unknown")
223
-
224
- return
225
- }
226
- id, kind, context, label := m[1], m[2], m[3], m[4]
227
- if _, dup := p.lanes[id]; dup {
228
- p.fail(i, "participant "+id+" is declared twice")
229
-
230
- return
231
- }
232
- lane := catalog.Participant{ID: id, Kind: catalog.ParticipantKind(kind), Label: label}
233
- if kind == "service" && context == "" {
234
- context, _, _ = strings.Cut(id, ".")
235
- }
236
- if context != "" {
237
- lane.Context = &context
238
- }
239
- p.lanes[id] = lane
240
- p.declared = append(p.declared, lane)
241
- }
242
-
243
- // lane resolves a name in a step to a participant: declared, or inferable -
244
- // `bus` is the broker, `context.service` is a service.
245
- func (p *parser) lane(i int, id string) (string, bool) {
246
- if _, ok := p.lanes[id]; ok {
247
- return id, true
248
- }
249
- var lane catalog.Participant
250
- switch {
251
- case id == "bus":
252
- lane = catalog.Participant{ID: id, Kind: catalog.ParticipantBroker}
253
- case id == "client":
254
- lane = catalog.Participant{ID: id, Kind: catalog.ParticipantActor}
255
- case strings.Count(id, ".") == 1 && !strings.HasPrefix(id, ".") && !strings.HasSuffix(id, "."):
256
- context, _, _ := strings.Cut(id, ".")
257
- lane = catalog.Participant{ID: id, Kind: catalog.ParticipantService, Context: &context}
258
- default:
259
- p.fail(i, "participant "+strconv.Quote(id)+" is not declared, and is neither a service (context.service) nor the bus")
260
-
261
- return "", false
262
- }
263
- p.lanes[id] = lane
264
- p.used = append(p.used, lane)
265
-
266
- return id, true
267
- }
268
-
269
- // sink is where the next node goes: the open frame's branch, or the flow.
270
- func (p *parser) sink() *catalog.FlowNodes {
271
- if len(p.stack) == 0 {
272
- return &p.flow.Steps
273
- }
274
-
275
- return &p.stack[len(p.stack)-1].steps
276
- }
277
-
278
- func (p *parser) nextID(prefix string, explicit string, i int) string {
279
- id := explicit
280
- if id == "" {
281
- p.n++
282
- id = prefix + strconv.Itoa(p.n)
283
- }
284
- if p.ids[id] {
285
- p.fail(i, "id "+id+" is used twice")
286
- }
287
- p.ids[id] = true
288
-
289
- return id
290
- }
291
-
292
- func (p *parser) step(i int, line string) {
293
- if line == "" {
294
- return
295
- }
296
-
297
- // A note continues the step above it.
298
- if strings.HasPrefix(line, ">") {
299
- text := strings.TrimSpace(strings.TrimPrefix(line, ">"))
300
- if p.last == nil {
301
- p.fail(i, "a note (`> ...`) has to follow the step it is about")
302
-
303
- return
304
- }
305
- if text == "" {
306
- return
307
- }
308
- if p.last.Note == "" {
309
- p.last.Note = text
310
- } else {
311
- p.last.Note += " " + text
312
- }
313
-
314
- return
315
- }
316
-
317
- word, rest, _ := strings.Cut(line, " ")
318
- rest = strings.TrimSpace(rest)
319
- switch word {
320
- case "alt", "par", "loop":
321
- title, id := splitID(rest)
322
- if word == "loop" && title == "" {
323
- p.fail(i, "a loop needs a title saying what it repeats until")
324
- }
325
- p.last = nil
326
- p.stopped = false
327
- p.stack = append(p.stack, &frame{kind: word, id: p.nextID(word, id, i), title: title, current: title})
328
-
329
- return
330
- case "else", "and":
331
- f := p.open(i, word)
332
- if f == nil {
333
- return
334
- }
335
- p.branch(f)
336
- f.current = rest
337
- p.last = nil
338
- p.stopped = false
339
-
340
- return
341
- case "stop":
342
- f := p.open(i, "stop")
343
- if f == nil {
344
- return
345
- }
346
- p.stopped = true
347
- p.last = nil
348
-
349
- return
350
- case "end":
351
- if len(p.stack) == 0 {
352
- p.fail(i, "`end` with no frame open")
353
-
354
- return
355
- }
356
- f := p.stack[len(p.stack)-1]
357
- p.branch(f)
358
- p.stack = p.stack[:len(p.stack)-1]
359
- p.close(i, f)
360
- p.last = nil
361
- p.stopped = false
362
-
363
- return
364
- }
365
-
366
- if p.stopped {
367
- p.fail(i, "nothing runs after `stop`; it ends the branch")
368
-
369
- return
370
- }
371
- m := stepLine.FindStringSubmatch(line)
372
- if m == nil {
373
- p.fail(i, "not a step: write `from -> to: [call|rpc|event] label`, or alt/else/par/and/loop/stop/end")
374
-
375
- return
376
- }
377
- from, okFrom := p.lane(i, m[1])
378
- to, okTo := p.lane(i, m[2])
379
- if !okFrom || !okTo {
380
- return
381
- }
382
- step := p.hop(i, m[3])
383
- if step == nil {
384
- return
385
- }
386
- step.From, step.To = from, to
387
- *p.sink() = append(*p.sink(), step)
388
- p.last = step
389
- p.hasSteps = true
390
- }
391
-
392
- // open is the innermost frame `else`, `and` or `stop` belongs to, checked to
393
- // be the right kind.
394
- func (p *parser) open(i int, word string) *frame {
395
- if len(p.stack) == 0 {
396
- p.fail(i, "`"+word+"` outside any frame")
397
-
398
- return nil
399
- }
400
- f := p.stack[len(p.stack)-1]
401
- want := map[string]string{"else": "alt", "and": "par", "stop": "alt"}[word]
402
- if f.kind != want {
403
- p.fail(i, "`"+word+"` inside a "+f.kind+"; it belongs to an "+want)
404
-
405
- return nil
406
- }
407
-
408
- return f
409
- }
410
-
411
- // branch closes the branch being read and starts the next.
412
- func (p *parser) branch(f *frame) {
413
- steps := f.steps
414
- if steps == nil {
415
- steps = catalog.FlowNodes{}
416
- }
417
- switch f.kind {
418
- case "alt":
419
- title := f.current
420
- if len(f.alt) > 0 && title == "" {
421
- title = "otherwise"
422
- }
423
- f.alt = append(f.alt, catalog.AltBranch{Title: title, Steps: steps, Terminal: p.stopped})
424
- case "par":
425
- f.par = append(f.par, steps)
426
- case "loop":
427
- f.steps = steps
428
-
429
- return
430
- }
431
- f.steps = nil
432
- f.current = ""
433
- }
434
-
435
- // close turns a finished frame into its node and appends it where it belongs.
436
- func (p *parser) close(i int, f *frame) {
437
- var node catalog.FlowNode
438
- switch f.kind {
439
- case "alt":
440
- if len(f.alt) < 2 {
441
- p.fail(i, "an alt states a choice and needs an `else`; a branch with nothing in it is fine")
442
- }
443
- if f.alt[0].Title == "" {
444
- p.fail(i, "the first branch of an alt needs a condition: `alt <when>`")
445
- }
446
- all := len(f.alt) > 0
447
- for _, b := range f.alt {
448
- if !b.Terminal {
449
- all = false
450
- }
451
- }
452
- if all {
453
- p.fail(i, "every branch of this alt stops; the flow cannot end on every path of a choice about what comes next")
454
- }
455
- node = &catalog.Alt{Type: "alt", ID: f.id, Branches: f.alt}
456
- case "par":
457
- if len(f.par) < 2 {
458
- p.fail(i, "a par runs branches side by side and needs an `and`")
459
- }
460
- node = &catalog.Parallel{Type: "parallel", ID: f.id, Title: f.title, Branches: f.par}
461
- case "loop":
462
- node = &catalog.Loop{Type: "loop", ID: f.id, Title: f.title, Steps: f.steps}
463
- }
464
- *p.sink() = append(*p.sink(), node)
465
- p.hasSteps = p.hasSteps || f.kind == "loop"
466
- }
467
-
468
- // hop reads what follows `from -> to:`.
469
- func (p *parser) hop(i int, rest string) *catalog.Step {
470
- // The label runs up to the first trailer: ` as "`, ` [`, ` @`, ` #`.
471
- head, tail := rest, ""
472
- cut := len(rest)
473
- for _, marker := range []string{` as "`, ` [`, ` @`, ` #`} {
474
- if at := strings.Index(rest, marker); at >= 0 && at < cut {
475
- cut = at
476
- }
477
- }
478
- if cut < len(rest) {
479
- head, tail = rest[:cut], rest[cut+1:]
480
- }
481
- head = strings.TrimSpace(head)
482
-
483
- kind := catalog.StepCall
484
- if m := kindPrefix.FindStringSubmatch(head); m != nil {
485
- kind = catalog.StepKind(m[1])
486
- head = strings.TrimSpace(m[2])
487
- }
488
- if head == "" {
489
- p.fail(i, "a step needs a label, or a ref for an event or rpc")
490
-
491
- return nil
492
- }
493
-
494
- step := &catalog.Step{Type: "step", Kind: kind, Status: catalog.StatusDeclared}
495
- switch kind {
496
- case catalog.StepEvent:
497
- step.Ref = head
498
- step.Label = head[strings.LastIndex(head, ".")+1:]
499
- case catalog.StepRPC:
500
- // A ref is `<package.Service>/<Method>`, one word with a dot before
501
- // the slash. `POST /webhooks/psp/v2` is a label: a route a webhook
502
- // arrives on, which no interface in the catalog declares.
503
- if isCallID(head) {
504
- step.Ref = head
505
- step.Label = head[strings.LastIndex(head, "/")+1:]
506
- } else {
507
- step.Label = head
508
- }
509
- default:
510
- step.Label = head
511
- }
512
-
513
- // Each trailer runs to the next marker, so a source location may have
514
- // spaces in it: `@trace 9f2c1a../span 04` is where a step read from a
515
- // trace was seen, and it is not a file:line.
516
- var explicit string
517
- for _, part := range trailers(tail) {
518
- switch {
519
- case strings.HasPrefix(part, "as \""):
520
- label, after, closed := strings.Cut(part[4:], "\"")
521
- if !closed || strings.TrimSpace(after) != "" {
522
- p.fail(i, "cannot read "+strconv.Quote(part)+"; a label after `as` is closed by its quote and followed by nothing")
523
-
524
- return nil
525
- }
526
- step.Label = label
527
- case strings.HasPrefix(part, "["):
528
- status, after, closed := strings.Cut(part[1:], "]")
529
- if !closed || strings.TrimSpace(after) != "" {
530
- p.fail(i, "cannot read "+strconv.Quote(part)+"; a status is one word in brackets and followed by nothing")
531
-
532
- return nil
533
- }
534
- switch catalog.Status(status) {
535
- case catalog.StatusVerified, catalog.StatusDeclared, catalog.StatusUnresolved:
536
- step.Status = catalog.Status(status)
537
- default:
538
- p.fail(i, "status ["+status+"] is not one of verified, declared, unresolved")
539
- }
540
- case strings.HasPrefix(part, "@"):
541
- step.Line = strings.TrimSpace(part[1:])
542
- case strings.HasPrefix(part, "#"):
543
- explicit = strings.TrimSpace(part[1:])
544
- default:
545
- p.fail(i, "cannot read "+strconv.Quote(part)+" after the label; what may follow is as \"label\", [status], @file:line and #id")
546
-
547
- return nil
548
- }
549
- }
550
- step.ID = p.nextID("s", explicit, i)
551
-
552
- return step
553
- }
554
-
555
- func isCallID(s string) bool {
556
- slash := strings.Index(s, "/")
557
-
558
- return slash > 0 && !strings.ContainsAny(s, " \t") && strings.Contains(s[:slash], ".") && slash < len(s)-1
559
- }
560
-
561
- // trailers splits what follows a label at its markers, keeping each marker
562
- // on the piece it starts.
563
- func trailers(tail string) []string {
564
- tail = strings.TrimSpace(tail)
565
- if tail == "" {
566
- return nil
567
- }
568
- var out []string
569
- for tail != "" {
570
- cut := len(tail)
571
- for _, marker := range []string{` as "`, ` [`, ` @`, ` #`} {
572
- if at := strings.Index(tail, marker); at >= 0 && at < cut {
573
- cut = at
574
- }
575
- }
576
- out = append(out, strings.TrimSpace(tail[:cut]))
577
- if cut == len(tail) {
578
- break
579
- }
580
- tail = strings.TrimSpace(tail[cut:])
581
- }
582
-
583
- return out
584
- }
585
-
586
- // splitID takes a trailing `#id` off a frame's header.
587
- func splitID(rest string) (string, string) {
588
- if m := idSuffix.FindStringSubmatch(" " + rest); m != nil {
589
- return strings.TrimSpace(strings.TrimSuffix(rest, m[0][1:])), m[1]
590
- }
591
-
592
- return strings.TrimSpace(rest), ""
593
- }