@shortlink-org/portolan 0.1.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 (826) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +387 -0
  3. package/catalog/enum_test.go +46 -0
  4. package/catalog/model.go +932 -0
  5. package/catalog/roundtrip_test.go +185 -0
  6. package/catalog/via_test.go +38 -0
  7. package/cli/portolan.mjs +368 -0
  8. package/go.mod +14 -0
  9. package/go.sum +14 -0
  10. package/index.html +13 -0
  11. package/internal/commands/cargo.go +154 -0
  12. package/internal/commands/commands.go +143 -0
  13. package/internal/commands/commands_test.go +323 -0
  14. package/internal/commands/gradle.go +77 -0
  15. package/internal/commands/justfile.go +82 -0
  16. package/internal/commands/makefile.go +106 -0
  17. package/internal/commands/maven.go +93 -0
  18. package/internal/commands/packagejson.go +159 -0
  19. package/internal/commands/pyproject.go +169 -0
  20. package/internal/commands/taskfile.go +101 -0
  21. package/internal/commands/testdata/estate/.cargo/config.toml +7 -0
  22. package/internal/commands/testdata/estate/Makefile +33 -0
  23. package/internal/commands/testdata/estate/Taskfile.yml +21 -0
  24. package/internal/commands/testdata/estate/build.gradle.kts +21 -0
  25. package/internal/commands/testdata/estate/justfile +20 -0
  26. package/internal/commands/testdata/estate/package.json +13 -0
  27. package/internal/commands/testdata/estate/pom.xml +29 -0
  28. package/internal/commands/testdata/estate/pyproject.toml +27 -0
  29. package/internal/commands/testdata/estate/xtask/src/main.rs +27 -0
  30. package/internal/commands/testdata/golden/commands.json +305 -0
  31. package/internal/gohttp/analyze.go +2500 -0
  32. package/internal/gohttp/endpoints.go +1067 -0
  33. package/internal/gohttp/roots.go +320 -0
  34. package/internal/gohttp/typed.go +143 -0
  35. package/internal/goscan/constants.go +85 -0
  36. package/internal/goscan/goscan_test.go +227 -0
  37. package/internal/goscan/names.go +52 -0
  38. package/internal/goscan/parse_test.go +11 -0
  39. package/internal/goscan/source.go +36 -0
  40. package/internal/goscan/tree.go +155 -0
  41. package/internal/goscan/types.go +99 -0
  42. package/internal/wsdl/ids.go +127 -0
  43. package/internal/wsdl/ids_test.go +21 -0
  44. package/internal/wsdl/model.go +62 -0
  45. package/internal/wsdl/parse.go +920 -0
  46. package/internal/wsdl/parse_test.go +133 -0
  47. package/package.json +117 -0
  48. package/plugin/describe.go +107 -0
  49. package/plugin/describe_test.go +114 -0
  50. package/plugin/protocol.go +117 -0
  51. package/plugin/schematest/schematest.go +126 -0
  52. package/plugins/README.md +1160 -0
  53. package/plugins/extract-adr/describe.go +19 -0
  54. package/plugins/extract-adr/describe_test.go +11 -0
  55. package/plugins/extract-adr/extract.go +153 -0
  56. package/plugins/extract-adr/extract_test.go +350 -0
  57. package/plugins/extract-adr/history.go +99 -0
  58. package/plugins/extract-adr/main.go +65 -0
  59. package/plugins/extract-adr/options.schema.json +26 -0
  60. package/plugins/extract-adr/parse.go +642 -0
  61. package/plugins/extract-adr/parse_test.go +405 -0
  62. package/plugins/extract-asyncapi/describe.go +19 -0
  63. package/plugins/extract-asyncapi/describe_test.go +11 -0
  64. package/plugins/extract-asyncapi/extract.go +315 -0
  65. package/plugins/extract-asyncapi/extract_test.go +197 -0
  66. package/plugins/extract-asyncapi/main.go +48 -0
  67. package/plugins/extract-asyncapi/options.schema.json +26 -0
  68. package/plugins/extract-asyncapi/spec.go +151 -0
  69. package/plugins/extract-celery/README.md +169 -0
  70. package/plugins/extract-celery/extract.py +346 -0
  71. package/plugins/extract-celery/extract_test.py +192 -0
  72. package/plugins/extract-celery/main.py +66 -0
  73. package/plugins/extract-celery/options.py +31 -0
  74. package/plugins/extract-celery/options.schema.json +31 -0
  75. package/plugins/extract-celery/producers.py +156 -0
  76. package/plugins/extract-celery/routes_test.py +106 -0
  77. package/plugins/extract-commands/describe.go +19 -0
  78. package/plugins/extract-commands/describe_test.go +11 -0
  79. package/plugins/extract-commands/extract.go +79 -0
  80. package/plugins/extract-commands/extract_test.go +92 -0
  81. package/plugins/extract-commands/main.go +55 -0
  82. package/plugins/extract-commands/options.schema.json +20 -0
  83. package/plugins/extract-csr/avro.go +258 -0
  84. package/plugins/extract-csr/describe.go +19 -0
  85. package/plugins/extract-csr/describe_test.go +11 -0
  86. package/plugins/extract-csr/extract.go +338 -0
  87. package/plugins/extract-csr/extract_test.go +374 -0
  88. package/plugins/extract-csr/jsonschema.go +343 -0
  89. package/plugins/extract-csr/lock.go +25 -0
  90. package/plugins/extract-csr/main.go +84 -0
  91. package/plugins/extract-csr/options.schema.json +43 -0
  92. package/plugins/extract-csr/subject.go +102 -0
  93. package/plugins/extract-django/README.md +386 -0
  94. package/plugins/extract-django/apps.py +100 -0
  95. package/plugins/extract-django/auth.py +302 -0
  96. package/plugins/extract-django/auth_test.py +188 -0
  97. package/plugins/extract-django/clients.py +121 -0
  98. package/plugins/extract-django/contracts.py +458 -0
  99. package/plugins/extract-django/database.py +119 -0
  100. package/plugins/extract-django/domain.py +318 -0
  101. package/plugins/extract-django/events.py +86 -0
  102. package/plugins/extract-django/extract.py +601 -0
  103. package/plugins/extract-django/extract_test.py +397 -0
  104. package/plugins/extract-django/flows.py +1278 -0
  105. package/plugins/extract-django/flows_test.py +338 -0
  106. package/plugins/extract-django/ids.py +42 -0
  107. package/plugins/extract-django/ids_test.py +30 -0
  108. package/plugins/extract-django/lifecycle.py +222 -0
  109. package/plugins/extract-django/lifecycle_test.py +76 -0
  110. package/plugins/extract-django/main.py +66 -0
  111. package/plugins/extract-django/openapi.py +229 -0
  112. package/plugins/extract-django/openapi_test.py +60 -0
  113. package/plugins/extract-django/operations.py +96 -0
  114. package/plugins/extract-django/options.py +61 -0
  115. package/plugins/extract-django/options.schema.json +126 -0
  116. package/plugins/extract-django/routing.py +214 -0
  117. package/plugins/extract-django/routing_test.py +198 -0
  118. package/plugins/extract-django/serializers.py +558 -0
  119. package/plugins/extract-django/store.py +434 -0
  120. package/plugins/extract-django/transport.py +242 -0
  121. package/plugins/extract-flows/describe.go +19 -0
  122. package/plugins/extract-flows/describe_test.go +11 -0
  123. package/plugins/extract-flows/extract.go +91 -0
  124. package/plugins/extract-flows/main.go +45 -0
  125. package/plugins/extract-flows/options.schema.json +16 -0
  126. package/plugins/extract-flows/parse.go +593 -0
  127. package/plugins/extract-flows/parse_test.go +204 -0
  128. package/plugins/extract-glossary/describe.go +19 -0
  129. package/plugins/extract-glossary/describe_test.go +11 -0
  130. package/plugins/extract-glossary/extract.go +115 -0
  131. package/plugins/extract-glossary/extract_test.go +220 -0
  132. package/plugins/extract-glossary/main.go +59 -0
  133. package/plugins/extract-glossary/options.schema.json +21 -0
  134. package/plugins/extract-glossary/parse.go +214 -0
  135. package/plugins/extract-glossary/parse_test.go +203 -0
  136. package/plugins/extract-go/aggregate.go +214 -0
  137. package/plugins/extract-go/client.go +409 -0
  138. package/plugins/extract-go/client_test.go +266 -0
  139. package/plugins/extract-go/describe.go +19 -0
  140. package/plugins/extract-go/describe_test.go +11 -0
  141. package/plugins/extract-go/enum.go +195 -0
  142. package/plugins/extract-go/enum_test.go +82 -0
  143. package/plugins/extract-go/event.go +99 -0
  144. package/plugins/extract-go/extract.go +191 -0
  145. package/plugins/extract-go/extract_test.go +261 -0
  146. package/plugins/extract-go/flow.go +1441 -0
  147. package/plugins/extract-go/flow_test.go +609 -0
  148. package/plugins/extract-go/httpclient.go +174 -0
  149. package/plugins/extract-go/httpclient_test.go +296 -0
  150. package/plugins/extract-go/ids.go +92 -0
  151. package/plugins/extract-go/layout.go +225 -0
  152. package/plugins/extract-go/layout_test.go +125 -0
  153. package/plugins/extract-go/lifecycle.go +324 -0
  154. package/plugins/extract-go/lifecycle_test.go +108 -0
  155. package/plugins/extract-go/main.go +79 -0
  156. package/plugins/extract-go/operation.go +157 -0
  157. package/plugins/extract-go/options.schema.json +66 -0
  158. package/plugins/extract-go/source.go +316 -0
  159. package/plugins/extract-go/transport.go +321 -0
  160. package/plugins/extract-go/transport_test.go +145 -0
  161. package/plugins/extract-go/wiring.go +434 -0
  162. package/plugins/extract-go-nats/describe.go +19 -0
  163. package/plugins/extract-go-nats/describe_test.go +11 -0
  164. package/plugins/extract-go-nats/extract.go +177 -0
  165. package/plugins/extract-go-nats/extract_test.go +389 -0
  166. package/plugins/extract-go-nats/index.go +379 -0
  167. package/plugins/extract-go-nats/main.go +42 -0
  168. package/plugins/extract-go-nats/options.schema.json +21 -0
  169. package/plugins/extract-go-nats/resolve.go +161 -0
  170. package/plugins/extract-go-nats/sites.go +224 -0
  171. package/plugins/extract-graphql/describe.go +19 -0
  172. package/plugins/extract-graphql/describe_test.go +11 -0
  173. package/plugins/extract-graphql/extract.go +433 -0
  174. package/plugins/extract-graphql/extract_test.go +256 -0
  175. package/plugins/extract-graphql/ids.go +49 -0
  176. package/plugins/extract-graphql/lex.go +237 -0
  177. package/plugins/extract-graphql/main.go +51 -0
  178. package/plugins/extract-graphql/options.schema.json +31 -0
  179. package/plugins/extract-graphql/parse.go +621 -0
  180. package/plugins/extract-graphql/parse_test.go +122 -0
  181. package/plugins/extract-http-clients/describe.go +19 -0
  182. package/plugins/extract-http-clients/describe_test.go +11 -0
  183. package/plugins/extract-http-clients/extract.go +705 -0
  184. package/plugins/extract-http-clients/extract_test.go +1263 -0
  185. package/plugins/extract-http-clients/main.go +42 -0
  186. package/plugins/extract-http-clients/options.schema.json +33 -0
  187. package/plugins/extract-java/README.md +190 -0
  188. package/plugins/extract-java/build/org/portolan/extract/Catalog.class +0 -0
  189. package/plugins/extract-java/build/org/portolan/extract/Clients$1.class +0 -0
  190. package/plugins/extract-java/build/org/portolan/extract/Clients$2.class +0 -0
  191. package/plugins/extract-java/build/org/portolan/extract/Clients$Call.class +0 -0
  192. package/plugins/extract-java/build/org/portolan/extract/Clients$Client.class +0 -0
  193. package/plugins/extract-java/build/org/portolan/extract/Clients$Route.class +0 -0
  194. package/plugins/extract-java/build/org/portolan/extract/Clients.class +0 -0
  195. package/plugins/extract-java/build/org/portolan/extract/Domain$Aggregate.class +0 -0
  196. package/plugins/extract-java/build/org/portolan/extract/Domain.class +0 -0
  197. package/plugins/extract-java/build/org/portolan/extract/Events$Found.class +0 -0
  198. package/plugins/extract-java/build/org/portolan/extract/Events.class +0 -0
  199. package/plugins/extract-java/build/org/portolan/extract/Extract.class +0 -0
  200. package/plugins/extract-java/build/org/portolan/extract/Flows$1.class +0 -0
  201. package/plugins/extract-java/build/org/portolan/extract/Flows$Binding.class +0 -0
  202. package/plugins/extract-java/build/org/portolan/extract/Flows$Draft.class +0 -0
  203. package/plugins/extract-java/build/org/portolan/extract/Flows$Frame.class +0 -0
  204. package/plugins/extract-java/build/org/portolan/extract/Flows$Options.class +0 -0
  205. package/plugins/extract-java/build/org/portolan/extract/Flows.class +0 -0
  206. package/plugins/extract-java/build/org/portolan/extract/Ids.class +0 -0
  207. package/plugins/extract-java/build/org/portolan/extract/Json$Reader.class +0 -0
  208. package/plugins/extract-java/build/org/portolan/extract/Json.class +0 -0
  209. package/plugins/extract-java/build/org/portolan/extract/Lifecycle$1.class +0 -0
  210. package/plugins/extract-java/build/org/portolan/extract/Lifecycle$Move.class +0 -0
  211. package/plugins/extract-java/build/org/portolan/extract/Lifecycle.class +0 -0
  212. package/plugins/extract-java/build/org/portolan/extract/Main.class +0 -0
  213. package/plugins/extract-java/build/org/portolan/extract/OpenApi$Operation.class +0 -0
  214. package/plugins/extract-java/build/org/portolan/extract/OpenApi$Spec.class +0 -0
  215. package/plugins/extract-java/build/org/portolan/extract/OpenApi.class +0 -0
  216. package/plugins/extract-java/build/org/portolan/extract/Operations$1.class +0 -0
  217. package/plugins/extract-java/build/org/portolan/extract/Operations$UseCase.class +0 -0
  218. package/plugins/extract-java/build/org/portolan/extract/Operations.class +0 -0
  219. package/plugins/extract-java/build/org/portolan/extract/Proto$Rpc.class +0 -0
  220. package/plugins/extract-java/build/org/portolan/extract/Proto$Service.class +0 -0
  221. package/plugins/extract-java/build/org/portolan/extract/Proto.class +0 -0
  222. package/plugins/extract-java/build/org/portolan/extract/Protocol$Builder.class +0 -0
  223. package/plugins/extract-java/build/org/portolan/extract/Protocol$Input.class +0 -0
  224. package/plugins/extract-java/build/org/portolan/extract/Protocol$Options.class +0 -0
  225. package/plugins/extract-java/build/org/portolan/extract/Protocol.class +0 -0
  226. package/plugins/extract-java/build/org/portolan/extract/Source$Project.class +0 -0
  227. package/plugins/extract-java/build/org/portolan/extract/Source$Unit.class +0 -0
  228. package/plugins/extract-java/build/org/portolan/extract/Source.class +0 -0
  229. package/plugins/extract-java/build/org/portolan/extract/Transport$Endpoint.class +0 -0
  230. package/plugins/extract-java/build/org/portolan/extract/Transport.class +0 -0
  231. package/plugins/extract-java/build/org/portolan/extract/Yaml$Line.class +0 -0
  232. package/plugins/extract-java/build/org/portolan/extract/Yaml$Reader.class +0 -0
  233. package/plugins/extract-java/build/org/portolan/extract/Yaml.class +0 -0
  234. package/plugins/extract-java/options.schema.json +70 -0
  235. package/plugins/extract-openapi/describe.go +19 -0
  236. package/plugins/extract-openapi/describe_test.go +11 -0
  237. package/plugins/extract-openapi/discover.go +243 -0
  238. package/plugins/extract-openapi/extract.go +526 -0
  239. package/plugins/extract-openapi/extract_test.go +545 -0
  240. package/plugins/extract-openapi/main.go +75 -0
  241. package/plugins/extract-openapi/options.schema.json +62 -0
  242. package/plugins/extract-openapi/spec.go +350 -0
  243. package/plugins/extract-project/README.md +22 -0
  244. package/plugins/extract-project/describe.go +19 -0
  245. package/plugins/extract-project/describe_test.go +11 -0
  246. package/plugins/extract-project/extract.go +221 -0
  247. package/plugins/extract-project/extract_test.go +109 -0
  248. package/plugins/extract-project/main.go +41 -0
  249. package/plugins/extract-project/options.schema.json +48 -0
  250. package/plugins/extract-proto/ast.go +125 -0
  251. package/plugins/extract-proto/consumes.go +77 -0
  252. package/plugins/extract-proto/describe.go +19 -0
  253. package/plugins/extract-proto/extract.go +293 -0
  254. package/plugins/extract-proto/extract_test.go +459 -0
  255. package/plugins/extract-proto/ids.go +89 -0
  256. package/plugins/extract-proto/ids_test.go +57 -0
  257. package/plugins/extract-proto/lex.go +285 -0
  258. package/plugins/extract-proto/main.go +100 -0
  259. package/plugins/extract-proto/module.go +120 -0
  260. package/plugins/extract-proto/options.schema.json +51 -0
  261. package/plugins/extract-proto/parse.go +720 -0
  262. package/plugins/extract-proto/parse_test.go +307 -0
  263. package/plugins/extract-proto/provides.go +236 -0
  264. package/plugins/extract-proto/resolve.go +222 -0
  265. package/plugins/extract-proto/resolve_test.go +119 -0
  266. package/plugins/extract-python-kafka/README.md +36 -0
  267. package/plugins/extract-python-kafka/extract.py +211 -0
  268. package/plugins/extract-python-kafka/extract_test.py +68 -0
  269. package/plugins/extract-python-kafka/main.py +59 -0
  270. package/plugins/extract-python-kafka/options.py +29 -0
  271. package/plugins/extract-python-kafka/options.schema.json +31 -0
  272. package/plugins/extract-redis/README.md +11 -0
  273. package/plugins/extract-redis/describe.go +19 -0
  274. package/plugins/extract-redis/describe_test.go +11 -0
  275. package/plugins/extract-redis/extract.go +183 -0
  276. package/plugins/extract-redis/extract_test.go +168 -0
  277. package/plugins/extract-redis/keyspaces.go +469 -0
  278. package/plugins/extract-redis/main.go +44 -0
  279. package/plugins/extract-redis/options.schema.json +30 -0
  280. package/plugins/extract-river/describe.go +19 -0
  281. package/plugins/extract-river/describe_test.go +11 -0
  282. package/plugins/extract-river/extract.go +507 -0
  283. package/plugins/extract-river/extract_test.go +132 -0
  284. package/plugins/extract-river/main.go +42 -0
  285. package/plugins/extract-river/options.schema.json +21 -0
  286. package/plugins/extract-rust/Cargo.lock +133 -0
  287. package/plugins/extract-rust/Cargo.toml +23 -0
  288. package/plugins/extract-rust/README.md +204 -0
  289. package/plugins/extract-rust/options.schema.json +65 -0
  290. package/plugins/extract-rust/rustfmt.toml +1 -0
  291. package/plugins/extract-sql/ddl.go +893 -0
  292. package/plugins/extract-sql/ddl_test.go +401 -0
  293. package/plugins/extract-sql/describe.go +19 -0
  294. package/plugins/extract-sql/describe_test.go +11 -0
  295. package/plugins/extract-sql/layout.go +221 -0
  296. package/plugins/extract-sql/layout_test.go +55 -0
  297. package/plugins/extract-sql/lineage.go +107 -0
  298. package/plugins/extract-sql/main.go +141 -0
  299. package/plugins/extract-sql/maps.go +564 -0
  300. package/plugins/extract-sql/maps_java.go +117 -0
  301. package/plugins/extract-sql/maps_rust.go +333 -0
  302. package/plugins/extract-sql/maps_rust_test.go +70 -0
  303. package/plugins/extract-sql/maps_test.go +204 -0
  304. package/plugins/extract-sql/maps_ts.go +398 -0
  305. package/plugins/extract-sql/maps_ts_test.go +136 -0
  306. package/plugins/extract-sql/options.schema.json +53 -0
  307. package/plugins/extract-sql/projection.go +70 -0
  308. package/plugins/extract-sql/projection_test.go +50 -0
  309. package/plugins/extract-sql/store.go +420 -0
  310. package/plugins/extract-sql/store_test.go +233 -0
  311. package/plugins/extract-sql/view.go +295 -0
  312. package/plugins/extract-ts/README.md +306 -0
  313. package/plugins/extract-ts/ast.test.ts +178 -0
  314. package/plugins/extract-ts/ast.ts +648 -0
  315. package/plugins/extract-ts/clients.ts +185 -0
  316. package/plugins/extract-ts/domain.ts +164 -0
  317. package/plugins/extract-ts/extract.test.ts +208 -0
  318. package/plugins/extract-ts/extract.ts +200 -0
  319. package/plugins/extract-ts/flows.ts +783 -0
  320. package/plugins/extract-ts/graphql.test.ts +73 -0
  321. package/plugins/extract-ts/graphql.ts +181 -0
  322. package/plugins/extract-ts/ids.test.ts +21 -0
  323. package/plugins/extract-ts/ids.ts +52 -0
  324. package/plugins/extract-ts/lifecycle.ts +138 -0
  325. package/plugins/extract-ts/main.ts +48 -0
  326. package/plugins/extract-ts/openapi.test.ts +64 -0
  327. package/plugins/extract-ts/openapi.ts +99 -0
  328. package/plugins/extract-ts/operations.ts +93 -0
  329. package/plugins/extract-ts/options.schema.json +96 -0
  330. package/plugins/extract-ts/source.test.ts +174 -0
  331. package/plugins/extract-ts/source.ts +465 -0
  332. package/plugins/extract-ts/transport.ts +147 -0
  333. package/plugins/extract-ts/tsconfig.json +13 -0
  334. package/plugins/extract-ts/wiring.ts +190 -0
  335. package/plugins/extract-watermill/describe.go +19 -0
  336. package/plugins/extract-watermill/describe_test.go +11 -0
  337. package/plugins/extract-watermill/extract.go +1228 -0
  338. package/plugins/extract-watermill/extract_test.go +234 -0
  339. package/plugins/extract-watermill/main.go +41 -0
  340. package/plugins/extract-watermill/options.schema.json +21 -0
  341. package/plugins/extract-wsdl/describe.go +19 -0
  342. package/plugins/extract-wsdl/describe_test.go +11 -0
  343. package/plugins/extract-wsdl/extract.go +148 -0
  344. package/plugins/extract-wsdl/extract_test.go +45 -0
  345. package/plugins/extract-wsdl/main.go +53 -0
  346. package/plugins/extract-wsdl/options.schema.json +59 -0
  347. package/plugins/fetch-bsr/auth.go +114 -0
  348. package/plugins/fetch-bsr/bsr.go +240 -0
  349. package/plugins/fetch-bsr/cache.go +66 -0
  350. package/plugins/fetch-bsr/describe.go +19 -0
  351. package/plugins/fetch-bsr/fetch.go +192 -0
  352. package/plugins/fetch-bsr/fetch_test.go +465 -0
  353. package/plugins/fetch-bsr/lock.go +71 -0
  354. package/plugins/fetch-bsr/main.go +117 -0
  355. package/plugins/fetch-bsr/options.schema.json +42 -0
  356. package/plugins/fetch-csr/auth.go +95 -0
  357. package/plugins/fetch-csr/cache.go +69 -0
  358. package/plugins/fetch-csr/describe.go +19 -0
  359. package/plugins/fetch-csr/describe_test.go +11 -0
  360. package/plugins/fetch-csr/fetch.go +254 -0
  361. package/plugins/fetch-csr/fetch_test.go +494 -0
  362. package/plugins/fetch-csr/lock.go +95 -0
  363. package/plugins/fetch-csr/main.go +122 -0
  364. package/plugins/fetch-csr/options.schema.json +42 -0
  365. package/plugins/fetch-csr/registry.go +209 -0
  366. package/plugins/fetch-git/cache.go +65 -0
  367. package/plugins/fetch-git/describe.go +19 -0
  368. package/plugins/fetch-git/describe_test.go +11 -0
  369. package/plugins/fetch-git/fetch.go +181 -0
  370. package/plugins/fetch-git/fetch_test.go +332 -0
  371. package/plugins/fetch-git/git.go +169 -0
  372. package/plugins/fetch-git/lock.go +72 -0
  373. package/plugins/fetch-git/main.go +127 -0
  374. package/plugins/fetch-git/offline.go +39 -0
  375. package/plugins/fetch-git/options.schema.json +42 -0
  376. package/plugins/fetch-git/pin.go +90 -0
  377. package/plugins/fetch-git/pin_test.go +74 -0
  378. package/plugins/gen-backstage/describe.go +17 -0
  379. package/plugins/gen-backstage/main.go +22 -0
  380. package/plugins/gen-backstage/options.schema.json +30 -0
  381. package/plugins/gen-backstage/plugin.go +473 -0
  382. package/plugins/gen-backstage/plugin_test.go +145 -0
  383. package/plugins/gen-backstage.wasm +0 -0
  384. package/plugins/gen-markdown/adr.go +162 -0
  385. package/plugins/gen-markdown/aggregate.go +362 -0
  386. package/plugins/gen-markdown/canonical.go +93 -0
  387. package/plugins/gen-markdown/context.go +90 -0
  388. package/plugins/gen-markdown/coverage_test.go +89 -0
  389. package/plugins/gen-markdown/describe.go +19 -0
  390. package/plugins/gen-markdown/describe_test.go +11 -0
  391. package/plugins/gen-markdown/external.go +71 -0
  392. package/plugins/gen-markdown/flow.go +277 -0
  393. package/plugins/gen-markdown/glossary.go +76 -0
  394. package/plugins/gen-markdown/glossary_test.go +148 -0
  395. package/plugins/gen-markdown/llms.go +302 -0
  396. package/plugins/gen-markdown/main.go +22 -0
  397. package/plugins/gen-markdown/markdown.go +254 -0
  398. package/plugins/gen-markdown/markdown_test.go +100 -0
  399. package/plugins/gen-markdown/module.go +107 -0
  400. package/plugins/gen-markdown/options.schema.json +16 -0
  401. package/plugins/gen-markdown/plugin.go +39 -0
  402. package/plugins/gen-markdown/quality_test.go +176 -0
  403. package/plugins/gen-markdown/redis_test.go +38 -0
  404. package/plugins/gen-markdown/render.go +344 -0
  405. package/plugins/gen-markdown/render_test.go +186 -0
  406. package/plugins/gen-markdown/service.go +456 -0
  407. package/plugins/gen-markdown/source.go +177 -0
  408. package/plugins/gen-markdown/store.go +201 -0
  409. package/plugins/gen-markdown.wasm +0 -0
  410. package/plugins/gen-mermaid/describe.go +17 -0
  411. package/plugins/gen-mermaid/main.go +22 -0
  412. package/plugins/gen-mermaid/options.schema.json +10 -0
  413. package/plugins/gen-mermaid/plugin.go +104 -0
  414. package/plugins/gen-mermaid/plugin_test.go +33 -0
  415. package/plugins/gen-mermaid.wasm +0 -0
  416. package/plugins/openapi/ids.go +261 -0
  417. package/plugins/openapi/ids_test.go +98 -0
  418. package/plugins/pyplugin/catalog.py +209 -0
  419. package/plugins/pyplugin/celery_conf.py +436 -0
  420. package/plugins/pyplugin/celery_tasks.py +76 -0
  421. package/plugins/pyplugin/kafka.py +570 -0
  422. package/plugins/pyplugin/names.py +38 -0
  423. package/plugins/pyplugin/protocol.py +79 -0
  424. package/plugins/pyplugin/source.py +285 -0
  425. package/plugins/verify-codeowners/describe.go +19 -0
  426. package/plugins/verify-codeowners/describe_test.go +11 -0
  427. package/plugins/verify-codeowners/main.go +75 -0
  428. package/plugins/verify-codeowners/match.go +85 -0
  429. package/plugins/verify-codeowners/match_test.go +47 -0
  430. package/plugins/verify-codeowners/options.schema.json +15 -0
  431. package/plugins/verify-codeowners/owners.go +164 -0
  432. package/plugins/verify-codeowners/owners_test.go +225 -0
  433. package/plugins/verify-codeowners/parse.go +90 -0
  434. package/plugins/verify-codeowners/parse_test.go +62 -0
  435. package/plugins/verify-otel/describe.go +19 -0
  436. package/plugins/verify-otel/describe_test.go +11 -0
  437. package/plugins/verify-otel/main.go +53 -0
  438. package/plugins/verify-otel/match.go +336 -0
  439. package/plugins/verify-otel/options.schema.json +30 -0
  440. package/plugins/verify-otel/otlp.go +200 -0
  441. package/plugins/verify-otel/verify.go +734 -0
  442. package/plugins/verify-otel/verify_test.go +460 -0
  443. package/portolan.json +1073 -0
  444. package/public/favicon.svg +12 -0
  445. package/schema/portolan.schema.json +2558 -0
  446. package/scripts/README.md +53 -0
  447. package/scripts/build-report.mjs +60 -0
  448. package/scripts/build-report.test.mjs +44 -0
  449. package/scripts/builtin-plugins.mjs +49 -0
  450. package/scripts/catalog-sources.mjs +67 -0
  451. package/scripts/diff.mjs +315 -0
  452. package/scripts/diff.test.mjs +151 -0
  453. package/scripts/forge-comment.mjs +144 -0
  454. package/scripts/forge-comment.test.mjs +151 -0
  455. package/scripts/forge-release.mjs +138 -0
  456. package/scripts/forge-release.test.mjs +106 -0
  457. package/scripts/forge.mjs +61 -0
  458. package/scripts/gen-likec4.mjs +854 -0
  459. package/scripts/gen-likec4.test.mjs +258 -0
  460. package/scripts/gen.mjs +603 -0
  461. package/scripts/generated-docs.test.mjs +66 -0
  462. package/scripts/local-api.mjs +1192 -0
  463. package/scripts/local-api.test.mjs +387 -0
  464. package/scripts/manifest.mjs +214 -0
  465. package/scripts/manifest.test.mjs +148 -0
  466. package/scripts/output-path.mjs +79 -0
  467. package/scripts/output-path.test.mjs +45 -0
  468. package/scripts/package-smoke.mjs +37 -0
  469. package/scripts/plugin-host.mjs +419 -0
  470. package/scripts/plugin-host.test.mjs +109 -0
  471. package/scripts/plugin-wasm-worker.mjs +34 -0
  472. package/scripts/run-builtin.mjs +81 -0
  473. package/scripts/schema.mjs +357 -0
  474. package/scripts/site-docs.mjs +295 -0
  475. package/scripts/site-docs.test.mjs +181 -0
  476. package/scripts/vendor-lock.mjs +58 -0
  477. package/scripts/vendor-lock.test.mjs +69 -0
  478. package/src/README.md +38 -0
  479. package/src/app/App.tsx +448 -0
  480. package/src/app/BranchPicker.test.ts +46 -0
  481. package/src/app/BranchPicker.tsx +153 -0
  482. package/src/app/Breadcrumbs.test.ts +38 -0
  483. package/src/app/Breadcrumbs.tsx +223 -0
  484. package/src/app/BuildStamp.tsx +146 -0
  485. package/src/app/CommandPalette.tsx +292 -0
  486. package/src/app/HashScroll.tsx +45 -0
  487. package/src/app/Sidebar.tsx +1742 -0
  488. package/src/app/TopBar.tsx +393 -0
  489. package/src/app/density.tsx +64 -0
  490. package/src/app/forge-access.test.ts +27 -0
  491. package/src/app/forge-access.tsx +80 -0
  492. package/src/app/panels.tsx +141 -0
  493. package/src/app/pins.tsx +242 -0
  494. package/src/app/query-client.ts +24 -0
  495. package/src/app/responsive.ts +55 -0
  496. package/src/app/shortcuts.test.ts +39 -0
  497. package/src/app/shortcuts.tsx +282 -0
  498. package/src/app/theme.tsx +51 -0
  499. package/src/app/title.test.ts +14 -0
  500. package/src/app/title.ts +30 -0
  501. package/src/app/toast.tsx +59 -0
  502. package/src/app/tree-keys.test.ts +80 -0
  503. package/src/app/tree-keys.ts +87 -0
  504. package/src/app/ui-store.ts +52 -0
  505. package/src/catalog-error.test.ts +105 -0
  506. package/src/catalog-profile.test.ts +59 -0
  507. package/src/catalog-profile.ts +101 -0
  508. package/src/catalog-stores.test.ts +431 -0
  509. package/src/catalog.test.ts +1215 -0
  510. package/src/catalog.ts +3125 -0
  511. package/src/chat/Cards.tsx +408 -0
  512. package/src/chat/ChatPanel.tsx +79 -0
  513. package/src/chat/ChatSettings.tsx +219 -0
  514. package/src/chat/Composer.tsx +99 -0
  515. package/src/chat/Conversation.tsx +281 -0
  516. package/src/chat/Header.tsx +46 -0
  517. package/src/chat/Message.tsx +49 -0
  518. package/src/chat/Notice.tsx +195 -0
  519. package/src/chat/Starter.tsx +67 -0
  520. package/src/chat/Steps.tsx +45 -0
  521. package/src/chat/Waiting.tsx +87 -0
  522. package/src/chat/flags.test.ts +59 -0
  523. package/src/chat/flags.ts +155 -0
  524. package/src/chat/linkify.test.ts +30 -0
  525. package/src/chat/linkify.ts +45 -0
  526. package/src/chat/prefs.ts +56 -0
  527. package/src/chat/prompt.test.ts +43 -0
  528. package/src/chat/prompt.ts +157 -0
  529. package/src/chat/store.ts +35 -0
  530. package/src/chat/tools.ts +58 -0
  531. package/src/chat/transport.ts +70 -0
  532. package/src/components/AdrRow.tsx +29 -0
  533. package/src/components/AnchorLink.tsx +67 -0
  534. package/src/components/ApiReference.tsx +134 -0
  535. package/src/components/AsyncApiReference.tsx +120 -0
  536. package/src/components/CatalogStamp.tsx +155 -0
  537. package/src/components/ChannelRows.tsx +132 -0
  538. package/src/components/CommandRows.tsx +139 -0
  539. package/src/components/DiagramSkeleton.tsx +71 -0
  540. package/src/components/EditorLink.tsx +85 -0
  541. package/src/components/FieldTree.tsx +305 -0
  542. package/src/components/FlowTrigger.tsx +34 -0
  543. package/src/components/Ident.tsx +89 -0
  544. package/src/components/Integrations.tsx +243 -0
  545. package/src/components/LifecycleDiagram.tsx +110 -0
  546. package/src/components/MachineDocs.tsx +42 -0
  547. package/src/components/Markdown.tsx +173 -0
  548. package/src/components/MarkdownTable.tsx +206 -0
  549. package/src/components/Mermaid.tsx +211 -0
  550. package/src/components/MethodRows.tsx +309 -0
  551. package/src/components/Overlay.tsx +125 -0
  552. package/src/components/PageHeader.tsx +189 -0
  553. package/src/components/RowActions.tsx +97 -0
  554. package/src/components/SchemaDocument.tsx +89 -0
  555. package/src/components/Select.tsx +126 -0
  556. package/src/components/ShapeRows.tsx +118 -0
  557. package/src/components/SourceDoc.tsx +82 -0
  558. package/src/components/SourcePreview.tsx +441 -0
  559. package/src/components/TabRow.tsx +182 -0
  560. package/src/components/TechIcon.tsx +34 -0
  561. package/src/components/Toc.tsx +132 -0
  562. package/src/components/WhatLinksHere.tsx +273 -0
  563. package/src/components/WsdlReference.tsx +84 -0
  564. package/src/components/ddd-icons.tsx +107 -0
  565. package/src/components/kind.tsx +157 -0
  566. package/src/components/logo.tsx +61 -0
  567. package/src/components/primitives.tsx +264 -0
  568. package/src/data.test.ts +18 -0
  569. package/src/data.ts +202 -0
  570. package/src/enrich.test.ts +1150 -0
  571. package/src/enrich.ts +672 -0
  572. package/src/er/ErCanvas.tsx +533 -0
  573. package/src/er/RedisSchema.tsx +74 -0
  574. package/src/er/RelationCard.tsx +267 -0
  575. package/src/er/StoreHeader.tsx +155 -0
  576. package/src/er/TableNode.tsx +84 -0
  577. package/src/er/ViewNode.tsx +83 -0
  578. package/src/er/layout.test.ts +115 -0
  579. package/src/er/layout.ts +77 -0
  580. package/src/er/lineage.test.ts +140 -0
  581. package/src/er/lineage.ts +107 -0
  582. package/src/er/markers.tsx +79 -0
  583. package/src/er/spec.test.ts +344 -0
  584. package/src/er/spec.ts +426 -0
  585. package/src/flow/ChainList.tsx +162 -0
  586. package/src/flow/FlowTable.tsx +123 -0
  587. package/src/flow/FlowToolbar.tsx +372 -0
  588. package/src/flow/StepDetail.tsx +380 -0
  589. package/src/flow/StepRail.tsx +428 -0
  590. package/src/flow/answers.test.ts +50 -0
  591. package/src/flow/answers.ts +58 -0
  592. package/src/flow/chain.test.ts +383 -0
  593. package/src/flow/chain.ts +353 -0
  594. package/src/flow/chapters.test.ts +185 -0
  595. package/src/flow/chapters.ts +225 -0
  596. package/src/flow/continues.test.ts +102 -0
  597. package/src/flow/continues.ts +65 -0
  598. package/src/flow/cross-context.test.ts +97 -0
  599. package/src/flow/cross-context.ts +35 -0
  600. package/src/flow/mermaid.test.ts +22 -0
  601. package/src/flow/mermaid.ts +141 -0
  602. package/src/flow/outline.test.ts +212 -0
  603. package/src/flow/outline.ts +170 -0
  604. package/src/flow/paths.test.ts +129 -0
  605. package/src/flow/paths.ts +147 -0
  606. package/src/flow/prefs.test.ts +80 -0
  607. package/src/flow/prefs.ts +104 -0
  608. package/src/graph/DependencyGraph.tsx +283 -0
  609. package/src/graph/DependencyNodes.tsx +162 -0
  610. package/src/graph/EdgeLabel.tsx +61 -0
  611. package/src/graph/FocusedEventGraph.tsx +211 -0
  612. package/src/graph/GraphToolbar.tsx +187 -0
  613. package/src/graph/RoutedEdge.tsx +80 -0
  614. package/src/graph/dependency-layout.test.ts +360 -0
  615. package/src/graph/dependency-layout.ts +444 -0
  616. package/src/graph/elk.test.ts +70 -0
  617. package/src/graph/elk.ts +287 -0
  618. package/src/graph/nodes.tsx +97 -0
  619. package/src/graph/theme.ts +81 -0
  620. package/src/graph/useElkFlow.ts +125 -0
  621. package/src/index.css +1547 -0
  622. package/src/language/InLanguage.tsx +40 -0
  623. package/src/language/TermCard.tsx +132 -0
  624. package/src/language/cards.test.ts +114 -0
  625. package/src/language/cards.ts +111 -0
  626. package/src/lib/admonition.test.ts +49 -0
  627. package/src/lib/admonition.ts +48 -0
  628. package/src/lib/adr.test.ts +131 -0
  629. package/src/lib/adr.ts +68 -0
  630. package/src/lib/api.test.ts +122 -0
  631. package/src/lib/api.ts +135 -0
  632. package/src/lib/backlinks.test.ts +330 -0
  633. package/src/lib/backlinks.ts +567 -0
  634. package/src/lib/branch-compare.ts +16 -0
  635. package/src/lib/build-info.test.ts +76 -0
  636. package/src/lib/build-info.ts +80 -0
  637. package/src/lib/catalog-diff.test.ts +275 -0
  638. package/src/lib/catalog-diff.ts +655 -0
  639. package/src/lib/clipboard.ts +43 -0
  640. package/src/lib/comparison-memory.test.ts +40 -0
  641. package/src/lib/comparison-memory.ts +39 -0
  642. package/src/lib/context-color.ts +27 -0
  643. package/src/lib/context-map.test.ts +148 -0
  644. package/src/lib/context-map.ts +399 -0
  645. package/src/lib/data-model.test.ts +245 -0
  646. package/src/lib/data-model.ts +399 -0
  647. package/src/lib/data-problems.test.ts +178 -0
  648. package/src/lib/data-problems.ts +314 -0
  649. package/src/lib/derive.test.ts +251 -0
  650. package/src/lib/derive.ts +515 -0
  651. package/src/lib/editor-link.test.ts +125 -0
  652. package/src/lib/editor-link.ts +96 -0
  653. package/src/lib/editor-prefs.ts +27 -0
  654. package/src/lib/event-graph.test.ts +237 -0
  655. package/src/lib/event-graph.ts +327 -0
  656. package/src/lib/export-canvas.ts +37 -0
  657. package/src/lib/export-file.test.ts +30 -0
  658. package/src/lib/export-file.ts +20 -0
  659. package/src/lib/flow-tree.test.ts +177 -0
  660. package/src/lib/flow-tree.ts +160 -0
  661. package/src/lib/forge-refs.test.ts +50 -0
  662. package/src/lib/forge-refs.ts +60 -0
  663. package/src/lib/format.test.ts +35 -0
  664. package/src/lib/format.ts +64 -0
  665. package/src/lib/github-catalog.test.ts +301 -0
  666. package/src/lib/github-catalog.ts +610 -0
  667. package/src/lib/integrations.test.ts +119 -0
  668. package/src/lib/integrations.ts +133 -0
  669. package/src/lib/kinds.test.ts +91 -0
  670. package/src/lib/kinds.ts +204 -0
  671. package/src/lib/lifecycle.test.ts +93 -0
  672. package/src/lib/lifecycle.ts +165 -0
  673. package/src/lib/local-api.ts +194 -0
  674. package/src/lib/motion-features.ts +11 -0
  675. package/src/lib/motion.test.ts +120 -0
  676. package/src/lib/motion.tsx +269 -0
  677. package/src/lib/palette-recent.test.ts +80 -0
  678. package/src/lib/palette-recent.ts +90 -0
  679. package/src/lib/palette.test.ts +365 -0
  680. package/src/lib/palette.ts +593 -0
  681. package/src/lib/pins.test.ts +139 -0
  682. package/src/lib/pins.ts +130 -0
  683. package/src/lib/problems.test.ts +112 -0
  684. package/src/lib/project-preview.ts +5 -0
  685. package/src/lib/proto-problems.test.ts +121 -0
  686. package/src/lib/proto-problems.ts +65 -0
  687. package/src/lib/queries.test.ts +113 -0
  688. package/src/lib/queries.ts +131 -0
  689. package/src/lib/registry.test.ts +219 -0
  690. package/src/lib/registry.ts +208 -0
  691. package/src/lib/scenarios.ts +547 -0
  692. package/src/lib/setup-info.test.ts +199 -0
  693. package/src/lib/setup-info.ts +415 -0
  694. package/src/lib/shape.test.ts +289 -0
  695. package/src/lib/shape.ts +454 -0
  696. package/src/lib/sidebar-prefs.test.ts +32 -0
  697. package/src/lib/sidebar-prefs.ts +75 -0
  698. package/src/lib/source-code.test.ts +159 -0
  699. package/src/lib/source-code.ts +286 -0
  700. package/src/lib/source-doc.test.ts +131 -0
  701. package/src/lib/source-doc.ts +103 -0
  702. package/src/lib/source-highlight.test.ts +29 -0
  703. package/src/lib/source-highlight.ts +24 -0
  704. package/src/lib/source-link.test.ts +153 -0
  705. package/src/lib/source-link.ts +201 -0
  706. package/src/lib/spec-files.test.ts +24 -0
  707. package/src/lib/spec-files.ts +23 -0
  708. package/src/lib/table-directive.test.ts +139 -0
  709. package/src/lib/table-directive.ts +106 -0
  710. package/src/lib/tech.test.ts +120 -0
  711. package/src/lib/tech.ts +421 -0
  712. package/src/lib/terms.test.ts +67 -0
  713. package/src/lib/terms.ts +179 -0
  714. package/src/lib/wire-problems.test.ts +263 -0
  715. package/src/lib/wire-problems.ts +281 -0
  716. package/src/likec4/C4View.tsx +55 -0
  717. package/src/likec4/CanvasBridge.tsx +187 -0
  718. package/src/likec4/FlowView.tsx +235 -0
  719. package/src/likec4/InteractiveView.tsx +144 -0
  720. package/src/likec4/canvas-viewport.test.ts +149 -0
  721. package/src/likec4/canvas-viewport.ts +135 -0
  722. package/src/likec4/flow-edges.test.ts +51 -0
  723. package/src/likec4/flow-edges.ts +89 -0
  724. package/src/likec4/frame-theme.test.ts +76 -0
  725. package/src/likec4/frame-theme.ts +101 -0
  726. package/src/likec4/highlight-css.test.ts +49 -0
  727. package/src/likec4/highlight-css.ts +52 -0
  728. package/src/likec4/ids.test.ts +151 -0
  729. package/src/likec4/ids.ts +115 -0
  730. package/src/likec4/levels.tsx +70 -0
  731. package/src/likec4/mapping.test.ts +66 -0
  732. package/src/likec4/mapping.ts +54 -0
  733. package/src/likec4/reserved.json +141 -0
  734. package/src/likec4/view-index.ts +78 -0
  735. package/src/main.tsx +13 -0
  736. package/src/map/ContextMapGraph.tsx +274 -0
  737. package/src/map/ContextNode.tsx +70 -0
  738. package/src/map/FloatingEdge.tsx +86 -0
  739. package/src/merge.test.ts +1038 -0
  740. package/src/merge.ts +1004 -0
  741. package/src/pages/AdrDetail.tsx +242 -0
  742. package/src/pages/AdrIndex.tsx +114 -0
  743. package/src/pages/AggregatePage.tsx +727 -0
  744. package/src/pages/BlockPage.tsx +478 -0
  745. package/src/pages/CatalogFailure.tsx +111 -0
  746. package/src/pages/Changes.tsx +313 -0
  747. package/src/pages/ContextMap.tsx +534 -0
  748. package/src/pages/ContextPage.tsx +346 -0
  749. package/src/pages/EnumPage.tsx +282 -0
  750. package/src/pages/EventPage.tsx +686 -0
  751. package/src/pages/ExternalPage.tsx +204 -0
  752. package/src/pages/FlowDetail.tsx +663 -0
  753. package/src/pages/FlowIndex.tsx +303 -0
  754. package/src/pages/GraphPage.tsx +217 -0
  755. package/src/pages/Language.tsx +257 -0
  756. package/src/pages/ModulePage.tsx +366 -0
  757. package/src/pages/NotFound.tsx +54 -0
  758. package/src/pages/Overview.tsx +344 -0
  759. package/src/pages/Problems.tsx +360 -0
  760. package/src/pages/RegistryIndex.tsx +118 -0
  761. package/src/pages/ServicePage.tsx +746 -0
  762. package/src/pages/Settings.tsx +975 -0
  763. package/src/pages/StorePage.tsx +114 -0
  764. package/src/peek/PeekCard.test.ts +35 -0
  765. package/src/peek/PeekCard.tsx +118 -0
  766. package/src/peek/PeekLayer.tsx +147 -0
  767. package/src/peek/model.test.ts +156 -0
  768. package/src/peek/model.ts +253 -0
  769. package/src/routes.test.ts +222 -0
  770. package/src/routes.ts +449 -0
  771. package/src/selection/DetailPanel.tsx +1407 -0
  772. package/src/selection/hash.test.ts +73 -0
  773. package/src/selection/hash.ts +58 -0
  774. package/src/selection/model.test.ts +199 -0
  775. package/src/selection/model.ts +473 -0
  776. package/src/selection/pages.test.ts +115 -0
  777. package/src/selection/pages.ts +268 -0
  778. package/src/selection/store.test.ts +62 -0
  779. package/src/selection/store.ts +46 -0
  780. package/src/selection/sync.tsx +99 -0
  781. package/src/table/ColumnsMenu.tsx +72 -0
  782. package/src/table/DataTable.tsx +780 -0
  783. package/src/table/Facets.tsx +54 -0
  784. package/src/table/GroupMenu.tsx +69 -0
  785. package/src/table/Toolbar.tsx +190 -0
  786. package/src/table/cells.tsx +117 -0
  787. package/src/table/compare.test.ts +117 -0
  788. package/src/table/compare.ts +196 -0
  789. package/src/table/export.test.ts +88 -0
  790. package/src/table/export.ts +66 -0
  791. package/src/table/facet-groups.test.ts +74 -0
  792. package/src/table/facet-groups.ts +56 -0
  793. package/src/table/grouping.test.ts +84 -0
  794. package/src/table/grouping.ts +82 -0
  795. package/src/table/infer.test.ts +57 -0
  796. package/src/table/infer.ts +50 -0
  797. package/src/table/persist.test.ts +76 -0
  798. package/src/table/persist.ts +112 -0
  799. package/src/table/sort-url.test.ts +75 -0
  800. package/src/table/sort-url.ts +52 -0
  801. package/src/table/types.ts +80 -0
  802. package/src/table/useTableState.ts +220 -0
  803. package/src/test-catalog.ts +7 -0
  804. package/src/testing/estate/data__catalog.json +2847 -0
  805. package/src/testing/estate/data__flows.json +1724 -0
  806. package/src/testing/estate/examples__auth__portolan__api.json +212 -0
  807. package/src/testing/estate/examples__auth__portolan__domain.json +1204 -0
  808. package/src/testing/estate/examples__auth__portolan__glossary.json +226 -0
  809. package/src/testing/estate/examples__auth__portolan__observed.json +757 -0
  810. package/src/testing/estate/examples__auth__portolan__stores.json +195 -0
  811. package/src/testing/estate/examples__shop__cart__portolan__api.json +234 -0
  812. package/src/testing/estate/examples__shop__cart__portolan__commands.json +86 -0
  813. package/src/testing/estate/examples__shop__cart__portolan__domain.json +966 -0
  814. package/src/testing/estate/examples__shop__cart__portolan__observed.json +498 -0
  815. package/src/testing/estate/examples__shop__cart__portolan__stores.json +208 -0
  816. package/src/testing/estate/examples__shop__oms__portolan__glossary.json +98 -0
  817. package/src/testing/estate/examples__shop__oms__portolan__proto.json +186 -0
  818. package/src/testing/estate.ts +49 -0
  819. package/src/trail/Trail.tsx +81 -0
  820. package/src/trail/model.test.ts +157 -0
  821. package/src/trail/model.ts +189 -0
  822. package/src/trail/record.tsx +32 -0
  823. package/src/trail/store.test.ts +66 -0
  824. package/src/trail/store.ts +102 -0
  825. package/tsconfig.json +35 -0
  826. package/vite.config.ts +171 -0
@@ -0,0 +1,401 @@
1
+ package main
2
+
3
+ import (
4
+ "fmt"
5
+ "strings"
6
+ "testing"
7
+
8
+ "github.com/shortlink-org/portolan/catalog"
9
+ )
10
+
11
+ func read(t *testing.T, sql string) ([]relation, []string) {
12
+ t.Helper()
13
+
14
+ relations, _, unread, err := readDDL(sql, "test.sql")
15
+ if err != nil {
16
+ t.Fatal(err)
17
+ }
18
+
19
+ return relations, unread
20
+ }
21
+
22
+ func TestColumnsKeysAndNullability(t *testing.T) {
23
+ relations, _ := read(t, `
24
+ CREATE TABLE users (
25
+ id text PRIMARY KEY,
26
+ email text NOT NULL,
27
+ nickname text,
28
+ version bigint NOT NULL
29
+ );`)
30
+
31
+ if len(relations) != 1 {
32
+ t.Fatalf("expected one table, got %d", len(relations))
33
+ }
34
+
35
+ got := map[string]catalog.Column{}
36
+ for _, column := range relations[0].table.Columns {
37
+ got[column.Name] = column
38
+ }
39
+
40
+ if !got["id"].PK {
41
+ t.Error("id is the primary key")
42
+ }
43
+ // A primary key is not null whether or not anybody wrote it down.
44
+ if got["id"].Nullable {
45
+ t.Error("a primary key is never nullable")
46
+ }
47
+ if got["email"].Nullable {
48
+ t.Error("email is NOT NULL")
49
+ }
50
+ if !got["nickname"].Nullable {
51
+ t.Error("nickname has no constraint, so it is nullable")
52
+ }
53
+ }
54
+
55
+ // The grammar normalises the standard names to PostgreSQL's internal ones. The
56
+ // catalog asks for the type as declared, so they are put back.
57
+ func TestTypesKeepTheirWrittenSpelling(t *testing.T) {
58
+ relations, _ := read(t, `
59
+ CREATE TABLE t (
60
+ a bigint,
61
+ b timestamptz,
62
+ c boolean,
63
+ d char(3),
64
+ e jsonb
65
+ );`)
66
+
67
+ want := map[string]string{
68
+ "a": "bigint",
69
+ "b": "timestamptz",
70
+ "c": "boolean",
71
+ "d": "char(3)",
72
+ "e": "jsonb",
73
+ }
74
+
75
+ for _, column := range relations[0].table.Columns {
76
+ if got := column.Type; got != want[column.Name] {
77
+ t.Errorf("%s: type = %q, want %q", column.Name, got, want[column.Name])
78
+ }
79
+ }
80
+ }
81
+
82
+ func TestTableLevelPrimaryKey(t *testing.T) {
83
+ relations, _ := read(t, `
84
+ CREATE TABLE order_items (
85
+ order_id uuid NOT NULL,
86
+ line_no integer NOT NULL,
87
+ PRIMARY KEY (order_id, line_no)
88
+ );`)
89
+
90
+ for _, column := range relations[0].table.Columns {
91
+ if !column.PK {
92
+ t.Errorf("%s is part of the composite key", column.Name)
93
+ }
94
+ }
95
+ }
96
+
97
+ func TestForeignKey(t *testing.T) {
98
+ relations, _ := read(t, `
99
+ CREATE TABLE lines (
100
+ id uuid PRIMARY KEY,
101
+ order_id uuid NOT NULL REFERENCES orders (id) ON DELETE CASCADE
102
+ );`)
103
+
104
+ var fk *catalog.FK
105
+ for _, column := range relations[0].table.Columns {
106
+ if column.Name == "order_id" {
107
+ fk = column.FK
108
+ }
109
+ }
110
+
111
+ if fk == nil {
112
+ t.Fatal("order_id has a foreign key")
113
+ }
114
+ if fk.Table != "orders" || fk.Column != "id" || fk.OnDelete != "cascade" {
115
+ t.Errorf("fk = %+v", fk)
116
+ }
117
+ }
118
+
119
+ func TestIndexesCarryTheirColumnsAndUniqueness(t *testing.T) {
120
+ relations, _ := read(t, `
121
+ CREATE TABLE sessions (id text PRIMARY KEY, user_id text, issued_at timestamptz);
122
+ CREATE UNIQUE INDEX sessions_token_key ON sessions (id);
123
+ CREATE INDEX sessions_user_id_idx ON sessions (user_id, issued_at);`)
124
+
125
+ indexes := relations[0].indexes
126
+ if len(indexes) != 2 {
127
+ t.Fatalf("expected two indexes, got %d", len(indexes))
128
+ }
129
+ if !indexes[0].Unique || indexes[0].Name != "sessions_token_key" {
130
+ t.Errorf("first index = %+v", indexes[0])
131
+ }
132
+ if indexes[1].Unique || strings.Join(indexes[1].Columns, ",") != "user_id,issued_at" {
133
+ t.Errorf("second index = %+v", indexes[1])
134
+ }
135
+ }
136
+
137
+ // A migration this reader does not model is reported. Quietly skipping one
138
+ // would leave the catalog confidently wrong about the schema.
139
+ func TestUnreadStatementsAreReported(t *testing.T) {
140
+ relations, unread := read(t, `
141
+ CREATE TABLE t (id text PRIMARY KEY);
142
+ ALTER TABLE t ADD COLUMN extra text;
143
+ CREATE INDEX elsewhere_idx ON other_table (id);`)
144
+
145
+ if len(unread) != 1 {
146
+ t.Fatalf("expected one note, got %v", unread)
147
+ }
148
+ if got := relations[0].table.Columns[1].Name; got != "extra" {
149
+ t.Errorf("ALTER ADD COLUMN was not applied: %q", got)
150
+ }
151
+ if !strings.Contains(strings.Join(unread, " "), "other_table") {
152
+ t.Errorf("an index on a table from another file should be named: %v", unread)
153
+ }
154
+ }
155
+
156
+ func TestMigrationsBuildFinalSchemaState(t *testing.T) {
157
+ state := newDDLState()
158
+ steps := []string{
159
+ `CREATE TABLE accounts (id uuid PRIMARY KEY, email text, obsolete text);`,
160
+ `ALTER TABLE accounts ADD COLUMN version integer NOT NULL;
161
+ ALTER TABLE accounts ALTER COLUMN email SET NOT NULL;
162
+ CREATE UNIQUE INDEX accounts_email_key ON accounts (email);`,
163
+ `ALTER TABLE accounts RENAME COLUMN email TO login;
164
+ ALTER TABLE accounts ALTER COLUMN version TYPE bigint;
165
+ ALTER TABLE accounts DROP COLUMN obsolete;
166
+ ALTER TABLE accounts RENAME TO users;`,
167
+ }
168
+ for i, sql := range steps {
169
+ unread, err := state.apply(sql, fmt.Sprintf("%03d.sql", i+1))
170
+ if err != nil {
171
+ t.Fatal(err)
172
+ }
173
+ if len(unread) != 0 {
174
+ t.Fatalf("migration %d left unread DDL: %v", i+1, unread)
175
+ }
176
+ }
177
+
178
+ if len(state.relations) != 1 || state.relations[0].table.Name != "users" {
179
+ t.Fatalf("relations = %+v", state.relations)
180
+ }
181
+ r := state.relations[0]
182
+ if len(r.table.Columns) != 3 {
183
+ t.Fatalf("columns = %+v", r.table.Columns)
184
+ }
185
+ want := map[string]string{"id": "uuid", "login": "text", "version": "bigint"}
186
+ for _, column := range r.table.Columns {
187
+ if column.Type != want[column.Name] {
188
+ t.Errorf("column %s type = %q, want %q", column.Name, column.Type, want[column.Name])
189
+ }
190
+ if column.Name == "login" && column.Nullable {
191
+ t.Error("renamed login column lost NOT NULL")
192
+ }
193
+ }
194
+ if len(r.indexes) != 1 || strings.Join(r.indexes[0].Columns, ",") != "login" {
195
+ t.Errorf("index did not follow renamed column: %+v", r.indexes)
196
+ }
197
+ }
198
+
199
+ func TestPostgresSchemasKeepRelationsDistinct(t *testing.T) {
200
+ state := newDDLState()
201
+ unread, err := state.apply(`
202
+ CREATE SCHEMA sales;
203
+ CREATE SCHEMA audit;
204
+ CREATE TABLE sales.orders (id uuid PRIMARY KEY, tenant_id uuid NOT NULL);
205
+ CREATE TABLE audit.orders (id uuid PRIMARY KEY, payload jsonb NOT NULL);
206
+ CREATE TABLE sales.order_lines (
207
+ id uuid PRIMARY KEY,
208
+ order_id uuid NOT NULL REFERENCES sales.orders (id)
209
+ );
210
+ CREATE INDEX orders_tenant_idx ON sales.orders (tenant_id);
211
+ CREATE VIEW sales.order_ids AS SELECT id FROM sales.orders;
212
+ `, "001.sql")
213
+ if err != nil {
214
+ t.Fatal(err)
215
+ }
216
+ if len(unread) != 0 {
217
+ t.Fatalf("nothing should be left unread: %v", unread)
218
+ }
219
+ if len(state.relations) != 3 {
220
+ t.Fatalf("relations = %+v", state.relations)
221
+ }
222
+ if state.relations[state.relationAt["sales.orders"]].table.Name != "sales.orders" ||
223
+ state.relations[state.relationAt["audit.orders"]].table.Name != "audit.orders" {
224
+ t.Fatalf("schema-qualified relations were merged: %+v", state.relationAt)
225
+ }
226
+ if got := state.relations[state.relationAt["sales.orders"]].indexes; len(got) != 1 || got[0].Name != "orders_tenant_idx" {
227
+ t.Errorf("schema-qualified index was not attached: %+v", got)
228
+ }
229
+
230
+ unread, err = state.apply(`ALTER TABLE sales.orders SET SCHEMA archive;`, "002.sql")
231
+ if err != nil {
232
+ t.Fatal(err)
233
+ }
234
+ if len(unread) != 0 {
235
+ t.Fatalf("SET SCHEMA should be applied: %v", unread)
236
+ }
237
+ if _, exists := state.relationAt["sales.orders"]; exists {
238
+ t.Error("old qualified table name still exists")
239
+ }
240
+ if _, exists := state.relationAt["archive.orders"]; !exists {
241
+ t.Fatalf("moved table is missing: %+v", state.relationAt)
242
+ }
243
+ lines := state.relations[state.relationAt["sales.order_lines"]]
244
+ if fk := lines.table.Columns[1].FK; fk == nil || fk.Table != "archive.orders" {
245
+ t.Errorf("foreign key did not follow SET SCHEMA: %+v", fk)
246
+ }
247
+ if got := strings.Join(state.views[0].reads, ","); got != "archive.orders" {
248
+ t.Errorf("view lineage did not follow SET SCHEMA: %q", got)
249
+ }
250
+ }
251
+
252
+ func TestPostgresEnumsAreRenderedInColumnTypes(t *testing.T) {
253
+ state := newDDLState()
254
+ steps := []string{
255
+ `CREATE SCHEMA billing;
256
+ CREATE TYPE billing.payment_status AS ENUM ('pending', 'paid');
257
+ CREATE TABLE billing.payments (
258
+ id uuid PRIMARY KEY,
259
+ status billing.payment_status NOT NULL,
260
+ history billing.payment_status[] NOT NULL
261
+ );`,
262
+ `ALTER TYPE billing.payment_status ADD VALUE 'authorized' BEFORE 'paid';
263
+ ALTER TYPE billing.payment_status RENAME VALUE 'pending' TO 'created';`,
264
+ `ALTER TYPE billing.payment_status SET SCHEMA public;`,
265
+ }
266
+ for i, sql := range steps {
267
+ unread, err := state.apply(sql, fmt.Sprintf("%03d.sql", i+1))
268
+ if err != nil {
269
+ t.Fatal(err)
270
+ }
271
+ if len(unread) != 0 {
272
+ t.Fatalf("migration %d left unread DDL: %v", i+1, unread)
273
+ }
274
+ }
275
+
276
+ table := state.relations[state.relationAt["billing.payments"]].table
277
+ got := map[string]string{}
278
+ for _, column := range table.Columns {
279
+ got[column.Name] = state.renderType(column.Type)
280
+ }
281
+ if got["status"] != "payment_status enum(created | authorized | paid)" {
282
+ t.Errorf("status type = %q", got["status"])
283
+ }
284
+ if got["history"] != "payment_status enum(created | authorized | paid)[]" {
285
+ t.Errorf("history type = %q", got["history"])
286
+ }
287
+ }
288
+
289
+ // A view is the other half of what a migration builds, and the half a table
290
+ // cannot express: no key, no constraints, and what it reads instead.
291
+ func TestReadsAViewAndWhereItsColumnsComeFrom(t *testing.T) {
292
+ sql := `
293
+ CREATE TABLE payments (id text PRIMARY KEY, order_id text NOT NULL, amount_minor bigint NOT NULL);
294
+ CREATE TABLE refunds (id text PRIMARY KEY, payment_id text NOT NULL, amount_minor bigint NOT NULL);
295
+ CREATE VIEW v_payment_state AS
296
+ SELECT p.id AS payment_id, p.order_id, coalesce(sum(r.amount_minor), 0) AS refunded_minor
297
+ FROM payments p
298
+ LEFT JOIN refunds r ON r.payment_id = p.id
299
+ GROUP BY p.id;
300
+ `
301
+
302
+ _, views, unread, err := readDDL(sql, "test.sql")
303
+ if err != nil {
304
+ t.Fatal(err)
305
+ }
306
+ if len(unread) != 0 {
307
+ t.Fatalf("nothing should be left unread: %v", unread)
308
+ }
309
+ if len(views) != 1 {
310
+ t.Fatalf("want one view, got %d", len(views))
311
+ }
312
+
313
+ view := views[0]
314
+ if view.name != "v_payment_state" {
315
+ t.Errorf("name %q", view.name)
316
+ }
317
+ if strings.Join(view.reads, ",") != "payments,refunds" {
318
+ t.Errorf("reads %v", view.reads)
319
+ }
320
+
321
+ want := map[string]string{
322
+ "payment_id": "payments.id",
323
+ "order_id": "payments.order_id",
324
+ "refunded_minor": "refunds.amount_minor",
325
+ }
326
+ if len(view.columns) != len(want) {
327
+ t.Fatalf("columns %v", view.columns)
328
+ }
329
+ for _, column := range view.columns {
330
+ if strings.Join(column.from, ",") != want[column.name] {
331
+ t.Errorf("%s comes from %v, want %q", column.name, column.from, want[column.name])
332
+ }
333
+ }
334
+
335
+ // The definition is the SQL somebody wrote, not the tree printed back.
336
+ if !strings.HasPrefix(view.definition, "CREATE VIEW v_payment_state AS") || !strings.HasSuffix(view.definition, ";") {
337
+ t.Errorf("definition %q", view.definition)
338
+ }
339
+ }
340
+
341
+ // A column copied from another service's row is a fact the migration declares,
342
+ // because the statement that creates the column cannot show it.
343
+ func TestReadsWhereACopiedColumnCameFrom(t *testing.T) {
344
+ sql := `
345
+ CREATE TABLE packages (
346
+ id text NOT NULL PRIMARY KEY,
347
+ -- from: shop.oms.pg.orders.ship_to
348
+ ship_to text NOT NULL,
349
+ -- from: packages.id
350
+ label_of text NOT NULL,
351
+ weight_g integer NOT NULL
352
+ );
353
+ `
354
+
355
+ copies := readCopies(sql, "delivery.core.pg")
356
+
357
+ if got := strings.Join(copies["packages"]["ship_to"], ","); got != "shop.oms.pg.orders.ship_to" {
358
+ t.Errorf("ship_to came from %q", got)
359
+ }
360
+ // A column of this store may be named without it.
361
+ if got := strings.Join(copies["packages"]["label_of"], ","); got != "delivery.core.pg.packages.id" {
362
+ t.Errorf("label_of came from %q", got)
363
+ }
364
+ if _, said := copies["packages"]["weight_g"]; said {
365
+ t.Error("a column with no comment above it says nothing")
366
+ }
367
+ if _, said := copies["packages"]["id"]; said {
368
+ t.Error("the comment belongs to the column under it, not the one before")
369
+ }
370
+ }
371
+
372
+ // The other spelling of a view: the grammar calls it a CREATE TABLE AS whose
373
+ // object is a matview, and the difference a reader needs is that the rows are
374
+ // kept and can be stale.
375
+ func TestReadsAMaterializedView(t *testing.T) {
376
+ sql := `
377
+ CREATE TABLE routes (id text PRIMARY KEY, planned_for date NOT NULL);
378
+ CREATE TABLE route_stops (route_id text NOT NULL, seq integer NOT NULL);
379
+ CREATE MATERIALIZED VIEW mv_route_load AS
380
+ SELECT r.id AS route_id, count(s.seq) AS stops
381
+ FROM routes r LEFT JOIN route_stops s ON s.route_id = r.id
382
+ GROUP BY r.id;
383
+ `
384
+
385
+ _, views, unread, err := readDDL(sql, "test.sql")
386
+ if err != nil {
387
+ t.Fatal(err)
388
+ }
389
+ if len(unread) != 0 {
390
+ t.Fatalf("nothing should be left unread: %v", unread)
391
+ }
392
+ if len(views) != 1 || !views[0].materialized {
393
+ t.Fatalf("views = %+v", views)
394
+ }
395
+ if strings.Join(views[0].reads, ",") != "routes,route_stops" {
396
+ t.Errorf("reads %v", views[0].reads)
397
+ }
398
+ if !strings.HasPrefix(views[0].definition, "CREATE MATERIALIZED VIEW mv_route_load AS") {
399
+ t.Errorf("definition %q", views[0].definition)
400
+ }
401
+ }
@@ -0,0 +1,19 @@
1
+ package main
2
+
3
+ import (
4
+ _ "embed"
5
+
6
+ "github.com/shortlink-org/portolan/plugin"
7
+ )
8
+
9
+ //go:embed options.schema.json
10
+ var optionsSchema []byte
11
+
12
+ func descriptor() plugin.Descriptor {
13
+ return plugin.Descriptor{
14
+ Name: "extract-sql",
15
+ Summary: "Reads a service's migrations and answers with the store it builds: tables, columns, keys, and which aggregate each table persists.",
16
+ Phases: []string{plugin.PhaseExtract},
17
+ Options: optionsSchema,
18
+ }
19
+ }
@@ -0,0 +1,11 @@
1
+ package main
2
+
3
+ import (
4
+ "testing"
5
+
6
+ "github.com/shortlink-org/portolan/plugin/schematest"
7
+ )
8
+
9
+ func TestOptionsSchemaMatchesStruct(t *testing.T) {
10
+ schematest.Check(t, optionsSchema, Options{})
11
+ }
@@ -0,0 +1,221 @@
1
+ package main
2
+
3
+ import (
4
+ "io/fs"
5
+ "os"
6
+ "path"
7
+ "path/filepath"
8
+ "sort"
9
+ "strings"
10
+ )
11
+
12
+ type storagePackage struct {
13
+ name string
14
+ dir string
15
+ migrations string
16
+ }
17
+
18
+ type storageLayout struct {
19
+ repositories []storagePackage
20
+ projectors []storagePackage
21
+ source string
22
+ }
23
+
24
+ // discoverStorageLayout keeps filesystem conventions in one place. Explicit
25
+ // roots retain the original "one child package per aggregate" contract, while
26
+ // omitted roots discover both horizontal and feature-sliced Go layouts.
27
+ func discoverStorageLayout(root, repositories, projectors string) storageLayout {
28
+ var layout storageLayout
29
+
30
+ if repositories != "" {
31
+ layout.repositories = packagesUnder(root, repositories, "repository")
32
+ } else {
33
+ layout.repositories = discoverPackages(root, "repository")
34
+ }
35
+ if projectors != "" {
36
+ layout.projectors = packagesUnder(root, projectors, "projector")
37
+ } else {
38
+ layout.projectors = discoverPackages(root, "projector")
39
+ }
40
+
41
+ if repositories != "" {
42
+ layout.source = filepath.ToSlash(filepath.Join(root, filepath.FromSlash(repositories)))
43
+ return layout
44
+ }
45
+ if projectors != "" {
46
+ layout.source = filepath.ToSlash(filepath.Join(root, filepath.FromSlash(projectors)))
47
+ return layout
48
+ }
49
+
50
+ dirs := make([]string, 0, len(layout.repositories)+len(layout.projectors))
51
+ for _, pkg := range layout.repositories {
52
+ dirs = append(dirs, pkg.dir)
53
+ }
54
+ for _, pkg := range layout.projectors {
55
+ dirs = append(dirs, pkg.dir)
56
+ }
57
+ layout.source = storageSource(root, dirs)
58
+
59
+ return layout
60
+ }
61
+
62
+ func packagesUnder(root, base, kind string) []storagePackage {
63
+ base = filepath.ToSlash(filepath.Clean(base))
64
+ var out []storagePackage
65
+
66
+ // A feature slice points directly at its repository adapter, with
67
+ // migrations immediately below it.
68
+ if hasUpMigrations(root, path.Join(base, "migrations")) {
69
+ name := packageName(strings.Split(base, "/"), kind)
70
+ if name == "" {
71
+ name = path.Base(base)
72
+ }
73
+ return []storagePackage{{name: name, dir: base, migrations: path.Join(base, "migrations")}}
74
+ }
75
+
76
+ for _, child := range subdirs(root, base) {
77
+ dir := path.Join(base, child)
78
+ migrations := path.Join(dir, "migrations")
79
+ if hasUpMigrations(root, migrations) {
80
+ out = append(out, storagePackage{name: child, dir: dir, migrations: migrations})
81
+ }
82
+ }
83
+
84
+ sortStoragePackages(out)
85
+ return out
86
+ }
87
+
88
+ func discoverPackages(root, kind string) []storagePackage {
89
+ base := filepath.Join(root, "internal")
90
+ seen := map[string]bool{}
91
+ var out []storagePackage
92
+
93
+ _ = filepath.WalkDir(base, func(filename string, entry fs.DirEntry, err error) error {
94
+ if err != nil {
95
+ return nil
96
+ }
97
+ if !entry.IsDir() {
98
+ return nil
99
+ }
100
+ if filename != base && strings.HasPrefix(entry.Name(), ".") {
101
+ return filepath.SkipDir
102
+ }
103
+ if entry.Name() != "migrations" {
104
+ return nil
105
+ }
106
+
107
+ rel, err := filepath.Rel(root, filename)
108
+ if err != nil {
109
+ return filepath.SkipDir
110
+ }
111
+ migrations := filepath.ToSlash(rel)
112
+ if !hasUpMigrations(root, migrations) {
113
+ return filepath.SkipDir
114
+ }
115
+ dir := path.Dir(migrations)
116
+ parts := strings.Split(dir, "/")
117
+ name := packageName(parts, kind)
118
+ if name == "" || seen[dir] {
119
+ return filepath.SkipDir
120
+ }
121
+ seen[dir] = true
122
+ out = append(out, storagePackage{name: name, dir: dir, migrations: migrations})
123
+
124
+ return filepath.SkipDir
125
+ })
126
+
127
+ sortStoragePackages(out)
128
+ return out
129
+ }
130
+
131
+ // packageName accepts these shapes and ignores migrations belonging to the
132
+ // other adapter kind:
133
+ //
134
+ // internal/infrastructure/repository/user
135
+ // internal/user/infrastructure/repository
136
+ // internal/user/infrastructure/projector/directory
137
+ func packageName(parts []string, kind string) string {
138
+ for i, part := range parts {
139
+ if part != kind {
140
+ continue
141
+ }
142
+ if i+1 < len(parts) {
143
+ return parts[i+1]
144
+ }
145
+ if i >= 2 && parts[i-1] == "infrastructure" {
146
+ return parts[i-2]
147
+ }
148
+ }
149
+ return ""
150
+ }
151
+
152
+ func hasUpMigrations(root, dir string) bool {
153
+ entries, err := os.ReadDir(filepath.Join(root, filepath.FromSlash(dir)))
154
+ if err != nil {
155
+ return false
156
+ }
157
+ for _, entry := range entries {
158
+ if !entry.IsDir() && strings.HasSuffix(entry.Name(), ".sql") && !strings.HasSuffix(entry.Name(), ".down.sql") {
159
+ return true
160
+ }
161
+ }
162
+ return false
163
+ }
164
+
165
+ func sortStoragePackages(packages []storagePackage) {
166
+ sort.Slice(packages, func(i, j int) bool {
167
+ if packages[i].name != packages[j].name {
168
+ return packages[i].name < packages[j].name
169
+ }
170
+ return packages[i].dir < packages[j].dir
171
+ })
172
+ }
173
+
174
+ func storageSource(root string, dirs []string) string {
175
+ if len(dirs) == 0 {
176
+ return ""
177
+ }
178
+ sort.Strings(dirs)
179
+ if len(dirs) == 1 {
180
+ return filepath.ToSlash(filepath.Join(root, filepath.FromSlash(dirs[0])))
181
+ }
182
+
183
+ parent := path.Dir(dirs[0])
184
+ sameParent := true
185
+ for _, dir := range dirs[1:] {
186
+ if path.Dir(dir) != parent {
187
+ sameParent = false
188
+ break
189
+ }
190
+ }
191
+ if sameParent {
192
+ return filepath.ToSlash(filepath.Join(root, filepath.FromSlash(parent)))
193
+ }
194
+
195
+ parts := strings.Split(dirs[0], "/")
196
+ for _, dir := range dirs[1:] {
197
+ other := strings.Split(dir, "/")
198
+ if len(other) != len(parts) {
199
+ return filepath.ToSlash(filepath.Join(root, "internal"))
200
+ }
201
+ for i := range parts {
202
+ if parts[i] != other[i] {
203
+ parts[i] = "*"
204
+ }
205
+ }
206
+ }
207
+
208
+ return filepath.ToSlash(filepath.Join(root, filepath.FromSlash(strings.Join(parts, "/"))))
209
+ }
210
+
211
+ // mapSourceDir accepts both supported repository shapes: a collection root
212
+ // with one child package per aggregate, and a feature repository package that
213
+ // is already the aggregate's source directory.
214
+ func mapSourceDir(root, repositoryDir, aggregate string) string {
215
+ child := path.Join(repositoryDir, aggregate)
216
+ if info, err := os.Stat(filepath.Join(root, filepath.FromSlash(child))); err == nil && info.IsDir() {
217
+ return child
218
+ }
219
+
220
+ return repositoryDir
221
+ }
@@ -0,0 +1,55 @@
1
+ package main
2
+
3
+ import "testing"
4
+
5
+ func TestDiscoversHorizontalAndFeatureStoragePackages(t *testing.T) {
6
+ root := writeTree(t, map[string]string{
7
+ "internal/infrastructure/repository/quote/migrations/0001_quote.sql": `CREATE TABLE quotes (id text PRIMARY KEY);`,
8
+ "internal/user/infrastructure/repository/migrations/0001_user.sql": `CREATE TABLE users (id text PRIMARY KEY);`,
9
+ "internal/search/infrastructure/projector/results/migrations/0001_results.sql": `CREATE TABLE results (id text PRIMARY KEY);`,
10
+ })
11
+
12
+ layout := discoverStorageLayout(root, "", "")
13
+ if len(layout.repositories) != 2 {
14
+ t.Fatalf("repositories = %+v", layout.repositories)
15
+ }
16
+ if layout.repositories[0].name != "quote" || layout.repositories[0].dir != "internal/infrastructure/repository/quote" {
17
+ t.Errorf("horizontal repository = %+v", layout.repositories[0])
18
+ }
19
+ if layout.repositories[1].name != "user" || layout.repositories[1].dir != "internal/user/infrastructure/repository" {
20
+ t.Errorf("feature repository = %+v", layout.repositories[1])
21
+ }
22
+ if len(layout.projectors) != 1 || layout.projectors[0].name != "results" {
23
+ t.Errorf("projectors = %+v", layout.projectors)
24
+ }
25
+ }
26
+
27
+ func TestFeatureRepositoriesProduceAStableSourcePattern(t *testing.T) {
28
+ root := writeTree(t, map[string]string{
29
+ "internal/user/infrastructure/repository/migrations/0001_user.sql": `CREATE TABLE users (id text PRIMARY KEY);`,
30
+ "internal/session/infrastructure/repository/migrations/0001_session.sql": `CREATE TABLE sessions (id text PRIMARY KEY);`,
31
+ })
32
+
33
+ layout := discoverStorageLayout(root, "", "")
34
+ want := root + "/internal/*/infrastructure/repository"
35
+ if layout.source != want {
36
+ t.Errorf("source = %q, want %q", layout.source, want)
37
+ }
38
+ }
39
+
40
+ func TestExplicitRootAcceptsCollectionAndFeaturePackage(t *testing.T) {
41
+ root := writeTree(t, map[string]string{
42
+ "src/infrastructure/repository/order/migrations/0001_order.sql": `CREATE TABLE orders (id text PRIMARY KEY);`,
43
+ "internal/user/infrastructure/repository/migrations/0001_user.sql": `CREATE TABLE users (id text PRIMARY KEY);`,
44
+ })
45
+
46
+ collection := discoverStorageLayout(root, "src/infrastructure/repository", "missing")
47
+ if len(collection.repositories) != 1 || collection.repositories[0].name != "order" {
48
+ t.Errorf("collection = %+v", collection.repositories)
49
+ }
50
+
51
+ feature := discoverStorageLayout(root, "internal/user/infrastructure/repository", "missing")
52
+ if len(feature.repositories) != 1 || feature.repositories[0].name != "user" {
53
+ t.Errorf("feature = %+v", feature.repositories)
54
+ }
55
+ }