@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,108 +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
- )
10
-
11
- const lifecycleSrc = `package session
12
-
13
- import "github.com/shortlink-org/go-sdk/fsm"
14
-
15
- const (
16
- StateLive fsm.State = "live"
17
- StateRevoked fsm.State = "revoked"
18
- EventRevoke fsm.Event = "revoke"
19
- EventExpire fsm.Event = "expire"
20
- )
21
-
22
- var Rules = fsm.TransitionRuleSet{
23
- StateLive: {EventRevoke: StateRevoked, EventExpire: StateExpired},
24
- StateRevoked: {EventRevive: StateLive},
25
- }
26
-
27
- const StateExpired fsm.State = "expired"
28
-
29
- type Session struct{ RevokedAt int }
30
-
31
- func (s *Session) trigger(ev fsm.Event) bool {
32
- m := &fsm.FSM{CurrentState: StateLive, TransitionRules: Rules}
33
- return m.TriggerEvent(nil, ev) == nil
34
- }
35
-
36
- func (s *Session) Revoke() (event.SessionEnded, bool) {
37
- if !s.trigger(EventRevoke) {
38
- return event.SessionEnded{}, false
39
- }
40
- return event.SessionEnded{}, true
41
- }
42
-
43
- func (s *Session) Poke(ev fsm.Event) { s.trigger(ev) }
44
-
45
- // Two moves, one event: the event is the last move's.
46
- func (s *Session) Bounce() (event.SessionEnded, bool) {
47
- s.trigger(EventRevive)
48
- s.trigger(EventRevoke)
49
- return event.SessionEnded{}, true
50
- }
51
-
52
- const EventRevive fsm.Event = "revive"
53
- `
54
-
55
- func TestLifecycleIsReadOffTheRuleSetAndTheMover(t *testing.T) {
56
- p, err := parseSource("session.go", lifecycleSrc)
57
- if err != nil {
58
- t.Fatal(err)
59
- }
60
- b := &plugin.Builder{}
61
- events := []catalog.Event{{ID: "auth.auth.session.SessionEnded", Name: "SessionEnded"}}
62
- lc := readLifecycle(p, "Session", events, "auth.auth.session", b)
63
- if lc == nil {
64
- t.Fatal("no lifecycle read")
65
- }
66
- // The literal's order, then the state only a target names.
67
- if got := strings.Join(lc.States, ","); got != "live,revoked,expired" {
68
- t.Fatalf("states = %s", got)
69
- }
70
- if len(lc.Transitions) != 3 {
71
- t.Fatalf("transitions = %+v", lc.Transitions)
72
- }
73
- // In table order: live → revoked twice (Bounce, Revoke), then revoked → live.
74
- tr := lc.Transitions[1]
75
- if tr.From != "live" || tr.To != "revoked" || tr.On != "Revoke" || tr.Emits != "auth.auth.session.SessionEnded" || !strings.HasSuffix(tr.Source, "session.go:27") {
76
- t.Fatalf("transition = %+v", tr)
77
- }
78
- // Bounce revives and then revokes: the event it returns is the revoke's.
79
- if b1 := lc.Transitions[0]; b1.On != "Bounce" || b1.To != "revoked" || b1.Emits == "" {
80
- t.Fatalf("Bounce's last move = %+v", b1)
81
- }
82
- if b0 := lc.Transitions[2]; b0.On != "Bounce" || b0.To != "live" || b0.Emits != "" {
83
- t.Fatalf("Bounce's first move = %+v", b0)
84
- }
85
-
86
- var messages []string
87
- for _, d := range b.Warnings {
88
- messages = append(messages, d.Message)
89
- }
90
- joined := strings.Join(messages, "\n")
91
- // The rule nobody takes, and the method that hands the mover a variable.
92
- if !strings.Contains(joined, `live → expired on "expire", and no method of Session makes that move`) {
93
- t.Fatalf("missing the untaken rule: %s", joined)
94
- }
95
- if !strings.Contains(joined, "Poke hands trigger something that is not a constant") {
96
- t.Fatalf("missing the non-constant warning: %s", joined)
97
- }
98
- }
99
-
100
- func TestNoRuleSetMeansNoLifecycle(t *testing.T) {
101
- p, err := parseSource("user.go", "package user\n\ntype User struct{}\n")
102
- if err != nil {
103
- t.Fatal(err)
104
- }
105
- if lc := readLifecycle(p, "User", nil, "auth.auth.user", &plugin.Builder{}); lc != nil {
106
- t.Fatalf("read a lifecycle off nothing: %+v", lc)
107
- }
108
- }
@@ -1,79 +0,0 @@
1
- // Package main is portolan-extract-go: a Go service in, a catalog fragment out.
2
- //
3
- // It reads the source with go/parser alone - no go/packages, no `go list`, no
4
- // module download. The domain layer of a service laid out this way is regular
5
- // enough that the syntax carries the answer, and staying out of the toolchain
6
- // means the extractor runs on a checkout that has never been built.
7
- //
8
- // What it does not know, it says. An aggregate whose root it cannot identify,
9
- // an event with no name, a use case it cannot classify - each is a diagnostic
10
- // beside the fragment rather than a guess inside it.
11
- package main
12
-
13
- import (
14
- "fmt"
15
- "io"
16
- "os"
17
-
18
- "github.com/shortlink-org/portolan/plugin"
19
- )
20
-
21
- // Options are what the manifest tells the extractor. Everything here is a fact
22
- // about the estate that the source does not carry: a Go module knows its own
23
- // import path, not which bounded context it belongs to.
24
- type Options struct {
25
- Context string `json:"context"`
26
- ContextName string `json:"contextName,omitempty"`
27
- ContextSummary string `json:"contextSummary,omitempty"`
28
- Classification string `json:"classification,omitempty"`
29
-
30
- Service string `json:"service"`
31
- ServiceName string `json:"serviceName,omitempty"`
32
- Repo string `json:"repo,omitempty"`
33
-
34
- // Store is the slug of the database this service keeps its state in - the
35
- // same one the SQL extractor is given. It is here for the flows: a call on
36
- // a repository lands somewhere, and only the manifest knows where.
37
- Store string `json:"store,omitempty"`
38
-
39
- // Peers says which service answers to a proto package this service calls:
40
- // {"risk.v1": "shop.risk"}. The generated client in the tree names the
41
- // package and the rpc; only the manifest knows whose it is. A package
42
- // with no line here is called as `unknown`, and the steps are unresolved.
43
- Peers map[string]string `json:"peers,omitempty"`
44
-
45
- // Externals says which system outside the estate answers to an api this
46
- // service calls, as the api id to the external's bare id: {"stripe.v1":
47
- // "stripe"}. Written when the name the document would give the system is
48
- // not the one wanted; left out, a generated HTTP client whose package no
49
- // peers line claims is read as calling the system its vendored document
50
- // is titled after.
51
- Externals map[string]string `json:"externals,omitempty"`
52
-
53
- // Events says which aggregate another service's events belong to, as the
54
- // import path a policy reads them from to the aggregate id. A service that
55
- // vendors the shape of somebody else's event has the type and nothing else;
56
- // only the manifest knows whose aggregate raised it.
57
- Events map[string]string `json:"events,omitempty"`
58
-
59
- // Out names the fragment file. One extractor, one file, so that a fragment
60
- // carries the provenance of the run that produced it.
61
- Out string `json:"out,omitempty"`
62
- }
63
-
64
- func main() {
65
- if err := run(os.Stdin, os.Stdout); err != nil {
66
- fmt.Fprintln(os.Stderr, "portolan-extract-go:", err)
67
- os.Exit(1)
68
- }
69
- }
70
-
71
- func run(stdin io.Reader, stdout io.Writer) error {
72
- return plugin.Serve(stdin, stdout, descriptor(), func(req plugin.Request, opts Options) (plugin.Response, error) {
73
- if req.Input.Root == "" {
74
- return plugin.Response{}, fmt.Errorf("no input root: an extractor has nothing to read")
75
- }
76
-
77
- return extract(req.Input, opts)
78
- })
79
- }
@@ -1,157 +0,0 @@
1
- package main
2
-
3
- import (
4
- "go/ast"
5
- "path/filepath"
6
- "strings"
7
-
8
- "github.com/shortlink-org/portolan/catalog"
9
- "github.com/shortlink-org/portolan/plugin"
10
- )
11
-
12
- // extractOperations reads every discovered application use case, keyed by the
13
- // aggregate or feature the use case sits under.
14
- //
15
- // The application layer is where the commands and queries actually are. The
16
- // aggregate's own methods are the mechanics of one - Register, ChangePassword -
17
- // but a use case is the whole operation, it already has a name a reader would
18
- // recognise, and in this codebase it has a README of its own.
19
- func extractOperations(root string, layout sourceLayout, exposures map[string][]string, b *plugin.Builder) map[string][]catalog.Operation {
20
- out := map[string][]catalog.Operation{}
21
-
22
- for _, key := range sortedKeys(layout.useCases) {
23
- aggregate, name, _ := strings.Cut(key, "/")
24
- dir := layout.useCases[key]
25
- pkg, err := parsePkg(root, dir)
26
- if err != nil {
27
- b.Warn(aggregate, dir+" could not be parsed; skipped")
28
-
29
- continue
30
- }
31
-
32
- out[aggregate] = append(out[aggregate], catalog.Operation{
33
- ID: camel(name),
34
- Kind: operationKind(pkg),
35
- Doc: operationDoc(root, dir, pkg),
36
- // Which endpoints run it, read from the transport layer. Absent
37
- // is the honest answer for a use case nothing outside can
38
- // reach - and in this service that is a deliberate design, not
39
- // an oversight.
40
- ExposedBy: exposures[key],
41
- })
42
- }
43
-
44
- return out
45
- }
46
-
47
- // operationKind asks whether the use case writes.
48
- //
49
- // A use case that reaches a repository's Save or Delete changes state, and one
50
- // that does not is answering a question. Every method on UseCase is looked at,
51
- // not just Handle: a use case that revokes a list of sessions does the writing
52
- // in a helper, and reading only the entry point would file it as a query.
53
- //
54
- // This is a reading of the code rather than a declaration in it, so it is wrong
55
- // exactly when a use case mutates through a name this does not know - which is
56
- // a rule that can be read here, over an annotation nobody would keep current.
57
- func operationKind(pkg *pkg) catalog.OperationKind {
58
- ports := map[string]bool{}
59
- for _, held := range pkg.structs() {
60
- if held.name != "UseCase" || held.fields == nil || held.fields.Fields == nil {
61
- continue
62
- }
63
- for _, field := range held.fields.Fields.List {
64
- for _, name := range field.Names {
65
- ports[name.Name] = true
66
- }
67
- }
68
- }
69
- for _, fn := range pkg.methods("UseCase") {
70
- if callsWritePort(fn, ports) {
71
- return catalog.OperationCommand
72
- }
73
- }
74
-
75
- return catalog.OperationQuery
76
- }
77
-
78
- // All language extractors share this core vocabulary. Framework extractors
79
- // may add native write verbs (Django's bulk_update, for example), but a helper
80
- // method or an unrelated value called Save is not enough: the call has to be
81
- // made through a dependency held by the use case.
82
- var writeMethods = map[string]bool{
83
- "save": true, "delete": true, "create": true, "update": true,
84
- "publish": true, "remove": true, "insert": true, "upsert": true,
85
- }
86
-
87
- func callsWritePort(fn *ast.FuncDecl, ports map[string]bool) bool {
88
- if fn == nil || fn.Body == nil {
89
- return false
90
- }
91
- receiver := receiverIdent(fn)
92
- found := false
93
- ast.Inspect(fn.Body, func(node ast.Node) bool {
94
- call, ok := node.(*ast.CallExpr)
95
- if !ok {
96
- return true
97
- }
98
- method, ok := call.Fun.(*ast.SelectorExpr)
99
- if !ok || !writeMethods[strings.ToLower(method.Sel.Name)] {
100
- return true
101
- }
102
- field, ok := method.X.(*ast.SelectorExpr)
103
- if !ok || !ports[field.Sel.Name] {
104
- return true
105
- }
106
- base, ok := field.X.(*ast.Ident)
107
- if ok && base.Name == receiver {
108
- found = true
109
- return false
110
- }
111
- return true
112
- })
113
- return found
114
- }
115
-
116
- // operationDoc prefers the use case's README, which is written for a reader,
117
- // over its package comment, which is written for whoever opens the file next.
118
- func operationDoc(root, dir string, pkg *pkg) string {
119
- readme := readFile(filepath.Join(root, filepath.FromSlash(dir), "README.md"))
120
- if readme != "" {
121
- if summary := firstParagraphAfterTitle(readme); summary != "" {
122
- return summary
123
- }
124
- }
125
-
126
- return firstSentenceOrAll(pkg.doc())
127
- }
128
-
129
- // firstParagraphAfterTitle is the prose under a readme's heading and before its
130
- // first section: the one paragraph that says what the thing does.
131
- func firstParagraphAfterTitle(md string) string {
132
- var lines []string
133
-
134
- started := false
135
- for _, line := range strings.Split(md, "\n") {
136
- trimmed := strings.TrimSpace(line)
137
-
138
- if strings.HasPrefix(trimmed, "#") {
139
- if started {
140
- break
141
- }
142
- started = true
143
-
144
- continue
145
- }
146
- if trimmed == "" {
147
- if len(lines) > 0 {
148
- break
149
- }
150
-
151
- continue
152
- }
153
- lines = append(lines, trimmed)
154
- }
155
-
156
- return strings.Join(lines, " ")
157
- }
@@ -1,316 +0,0 @@
1
- package main
2
-
3
- import (
4
- "go/ast"
5
- "go/parser"
6
- "go/token"
7
- "go/types"
8
- "os"
9
- "path"
10
- "path/filepath"
11
- "strings"
12
-
13
- "github.com/shortlink-org/portolan/catalog"
14
- )
15
-
16
- // A parsed package: the files that make it up, and where they came from.
17
- //
18
- // Tests are left out. A _test.go file is about how the code is exercised, and
19
- // a helper struct in one is not part of the domain however much it looks like
20
- // it.
21
- type pkg struct {
22
- dir string // as a reader would open it, relative to the repository
23
- name string
24
- files []*ast.File
25
- fset *token.FileSet
26
- paths map[*ast.File]string
27
- }
28
-
29
- func parsePkg(root, rel string) (*pkg, error) {
30
- dir := filepath.Join(root, rel)
31
-
32
- entries, err := os.ReadDir(dir)
33
- if err != nil {
34
- return nil, err
35
- }
36
-
37
- p := &pkg{
38
- dir: path.Join(filepath.ToSlash(root), rel),
39
- fset: token.NewFileSet(),
40
- paths: map[*ast.File]string{},
41
- }
42
-
43
- for _, entry := range entries {
44
- name := entry.Name()
45
- if entry.IsDir() || !strings.HasSuffix(name, ".go") || strings.HasSuffix(name, "_test.go") {
46
- continue
47
- }
48
-
49
- file, err := parser.ParseFile(p.fset, filepath.Join(dir, name), nil, parser.ParseComments)
50
- if err != nil {
51
- return nil, err
52
- }
53
-
54
- p.name = file.Name.Name
55
- p.files = append(p.files, file)
56
- p.paths[file] = path.Join(p.dir, name)
57
- }
58
-
59
- if len(p.files) == 0 {
60
- return nil, os.ErrNotExist
61
- }
62
-
63
- return p, nil
64
- }
65
-
66
- // doc is the package comment: the block immediately above `package x`, in
67
- // whichever file carries it.
68
- //
69
- // It is the aggregate's readme, and it is worth saying why that is not a
70
- // fallback. A package comment is where a Go author already writes down what the
71
- // package is for and what it refuses to do; asking them to write it a second
72
- // time somewhere a generator can find it is how documentation goes stale.
73
- func (p *pkg) doc() string {
74
- for _, file := range p.files {
75
- if file.Doc == nil {
76
- continue
77
- }
78
- if text := strings.TrimSpace(file.Doc.Text()); text != "" {
79
- return text
80
- }
81
- }
82
-
83
- return ""
84
- }
85
-
86
- // position is where a node sits, as a reader would write it down: the file
87
- // path relative to the repository, and the line. It is what a flow step points
88
- // at, and it is read from the file set rather than remembered per declaration.
89
- func (p *pkg) position(pos token.Pos) (string, int) {
90
- at := p.fset.Position(pos)
91
-
92
- return filepath.ToSlash(at.Filename), at.Line
93
- }
94
-
95
- // structDecl is a named struct type with the comment above it.
96
- type structDecl struct {
97
- name string
98
- doc string
99
- fields *ast.StructType
100
- source string
101
- }
102
-
103
- func (p *pkg) structs() []structDecl {
104
- var out []structDecl
105
-
106
- for _, file := range p.files {
107
- for _, decl := range file.Decls {
108
- gen, ok := decl.(*ast.GenDecl)
109
- if !ok || gen.Tok != token.TYPE {
110
- continue
111
- }
112
-
113
- for _, spec := range gen.Specs {
114
- typeSpec, ok := spec.(*ast.TypeSpec)
115
- if !ok {
116
- continue
117
- }
118
- structType, ok := typeSpec.Type.(*ast.StructType)
119
- if !ok {
120
- continue
121
- }
122
-
123
- // A single-spec declaration carries its comment on the
124
- // GenDecl; one inside a `type (...)` block carries its own.
125
- comment := typeSpec.Doc
126
- if comment == nil && len(gen.Specs) == 1 {
127
- comment = gen.Doc
128
- }
129
-
130
- out = append(out, structDecl{
131
- name: typeSpec.Name.Name,
132
- doc: firstSentenceOrAll(comment.Text()),
133
- fields: structType,
134
- source: p.paths[file],
135
- })
136
- }
137
- }
138
- }
139
-
140
- return out
141
- }
142
-
143
- // methods returns the methods declared on a receiver type, by name.
144
- func (p *pkg) methods(recv string) map[string]*ast.FuncDecl {
145
- out := map[string]*ast.FuncDecl{}
146
-
147
- for _, file := range p.files {
148
- for _, decl := range file.Decls {
149
- fn, ok := decl.(*ast.FuncDecl)
150
- if !ok || fn.Recv == nil || len(fn.Recv.List) == 0 {
151
- continue
152
- }
153
- if receiverName(fn.Recv.List[0].Type) == recv {
154
- out[fn.Name.Name] = fn
155
- }
156
- }
157
- }
158
-
159
- return out
160
- }
161
-
162
- func receiverName(expr ast.Expr) string {
163
- if star, ok := expr.(*ast.StarExpr); ok {
164
- expr = star.X
165
- }
166
- if ident, ok := expr.(*ast.Ident); ok {
167
- return ident.Name
168
- }
169
-
170
- return ""
171
- }
172
-
173
- // fields turns a struct's members into catalog fields.
174
- //
175
- // The type is rendered from the syntax rather than resolved: `email.Address`
176
- // is written down as `email.Address`, which is what a reader of the catalog
177
- // wants to see and what the shape of the schema asks for. Resolving it would
178
- // need the whole module built, and would answer a question nobody asked.
179
- func fields(st *ast.StructType) []catalog.Field {
180
- if st == nil || st.Fields == nil {
181
- return nil
182
- }
183
-
184
- var out []catalog.Field
185
- for _, field := range st.Fields.List {
186
- typeName := types.ExprString(field.Type)
187
-
188
- doc := strings.TrimSpace(field.Doc.Text())
189
- if doc == "" {
190
- doc = strings.TrimSpace(field.Comment.Text())
191
- }
192
- doc = firstSentenceOrAll(doc)
193
-
194
- if len(field.Names) == 0 {
195
- // An embedded field: named by its type.
196
- out = append(out, catalog.Field{Name: typeName, Type: typeName, Doc: doc})
197
-
198
- continue
199
- }
200
-
201
- for _, name := range field.Names {
202
- out = append(out, catalog.Field{Name: name.Name, Type: typeName, Doc: doc})
203
- }
204
- }
205
-
206
- return out
207
- }
208
-
209
- // returnedString is the string a one-line method returns, which is how an
210
- // event's name on the bus is read out of `func (E) Name() string`: a literal,
211
- // or a constant the package declares - `return TopicAccountLocked` - since a
212
- // topic is routinely named once and returned by name.
213
- func returnedString(p *pkg, fn *ast.FuncDecl) (string, bool) {
214
- if fn == nil || fn.Body == nil {
215
- return "", false
216
- }
217
-
218
- for _, stmt := range fn.Body.List {
219
- ret, ok := stmt.(*ast.ReturnStmt)
220
- if !ok || len(ret.Results) != 1 {
221
- continue
222
- }
223
- if value, ok := constString(ret.Results[0], stringConsts(p)); ok {
224
- return value, true
225
- }
226
- }
227
-
228
- return "", false
229
- }
230
-
231
- // calls reports whether a function's body calls a method with one of these
232
- // names on anything at all.
233
- func calls(fn *ast.FuncDecl, names ...string) bool {
234
- if fn == nil || fn.Body == nil {
235
- return false
236
- }
237
-
238
- wanted := map[string]bool{}
239
- for _, name := range names {
240
- wanted[name] = true
241
- }
242
-
243
- found := false
244
- ast.Inspect(fn.Body, func(node ast.Node) bool {
245
- call, ok := node.(*ast.CallExpr)
246
- if !ok {
247
- return true
248
- }
249
- if selector, ok := call.Fun.(*ast.SelectorExpr); ok && wanted[selector.Sel.Name] {
250
- found = true
251
-
252
- return false
253
- }
254
-
255
- return true
256
- })
257
-
258
- return found
259
- }
260
-
261
- // subdirs lists the immediate subdirectories of a path, sorted by os.ReadDir,
262
- // which reads them in filename order - so the fragment comes out the same way
263
- // every time.
264
- func subdirs(root, rel string) []string {
265
- entries, err := os.ReadDir(filepath.Join(root, rel))
266
- if err != nil {
267
- return nil
268
- }
269
-
270
- var out []string
271
- for _, entry := range entries {
272
- if entry.IsDir() {
273
- out = append(out, entry.Name())
274
- }
275
- }
276
-
277
- return out
278
- }
279
-
280
- // firstSentenceOrAll trims a doc comment down to something that fits in a
281
- // table cell, and leaves it alone when it is already short.
282
- //
283
- // Go doc comments open with a sentence naming the thing, then explain. The
284
- // first sentence is the label; the rest belongs on the page, not in a column.
285
- func firstSentenceOrAll(doc string) string {
286
- doc = strings.TrimSpace(doc)
287
- if doc == "" {
288
- return ""
289
- }
290
-
291
- // Everything up to the first blank line is the opening paragraph.
292
- if i := strings.Index(doc, "\n\n"); i >= 0 {
293
- doc = doc[:i]
294
- }
295
-
296
- return strings.Join(strings.Fields(doc), " ")
297
- }
298
-
299
- // parseSource builds a package out of one file of source. It exists for tests:
300
- // every other caller has a directory.
301
- func parseSource(name, src string) (*pkg, error) {
302
- fset := token.NewFileSet()
303
-
304
- file, err := parser.ParseFile(fset, name, src, parser.ParseComments)
305
- if err != nil {
306
- return nil, err
307
- }
308
-
309
- return &pkg{
310
- dir: ".",
311
- name: file.Name.Name,
312
- files: []*ast.File{file},
313
- fset: fset,
314
- paths: map[*ast.File]string{file: name},
315
- }, nil
316
- }