@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,601 @@
1
+ """One Django service in, one fragment out - two, when it keeps a database.
2
+
3
+ A fragment, not a catalog: it carries one context and one service, names peers
4
+ it does not own, and is merged with everything else before anything validates
5
+ it.
6
+ """
7
+
8
+ from __future__ import annotations
9
+
10
+ import ast
11
+ import os
12
+ import re
13
+ from typing import Any, Dict, List
14
+
15
+ import apps as apps_module
16
+ import auth as auth_module
17
+ import catalog
18
+ import clients as clients_module
19
+ import contracts
20
+ import database
21
+ import domain
22
+ import events as events_module
23
+ import flows
24
+ import lifecycle
25
+ import operations
26
+ import routing
27
+ import serializers as serializers_module
28
+ import store as store_module
29
+ import transport
30
+ from ids import service_id, slug, title
31
+ from options import Options
32
+ from protocol import Builder, Input
33
+ from source import Project, const_str, dotted, keyword, read
34
+
35
+
36
+ def extract(input_: Input, opts: Options, b: Builder, cwd: str = "") -> None:
37
+ cwd = cwd or os.getcwd()
38
+ root = os.path.abspath(os.path.join(cwd, input_.root))
39
+
40
+ def rel(path: str) -> str:
41
+ return os.path.relpath(path, cwd).replace(os.sep, "/")
42
+
43
+ source = os.path.normpath(os.path.join(root, opts.source or "."))
44
+ context = opts.context or os.path.basename(root)
45
+ service = opts.service or os.path.basename(root)
46
+ svc_id = service_id(context, service)
47
+
48
+ project = Project(root, source, rel)
49
+ for path, message in project.broken:
50
+ b.warn(path, "cannot be parsed, so nothing in it is read: %s" % message)
51
+
52
+ applications = apps_module.discover(project, list(opts.apps))
53
+ if not applications:
54
+ b.warn(svc_id, "no Django application under %s: a directory with a models module is what this reads" % rel(source))
55
+
56
+ models_by_app = {app.dotted: domain.read_models(app) for app in applications}
57
+ concrete_models = [model for models in models_by_app.values() for model in models if model.concrete]
58
+ configured_databases = database.databases_of(project, opts.settings)
59
+ primary_database = next((item for item in configured_databases if item.alias == "default"), None)
60
+ effective_store = opts.store
61
+ if not effective_store and concrete_models and primary_database is not None and primary_database.kind:
62
+ effective_store = database.store_slug(primary_database.kind)
63
+ store_aliases = {item.alias: (effective_store if item.alias == "default" else slug(item.alias)) for item in configured_databases}
64
+
65
+ aggregates = domain.read_aggregates(
66
+ project,
67
+ applications,
68
+ svc_id,
69
+ dict(opts.aggregates),
70
+ b,
71
+ models_by_app,
72
+ )
73
+ route_table = routing.read(project, opts.settings)
74
+ endpoint_apps = routed_applications(project, applications, route_table)
75
+ endpoints = []
76
+ for app in endpoint_apps:
77
+ endpoints += [(app, endpoint) for endpoint in transport.read_endpoints(app, b, route_table)]
78
+ serializer_registry = serializers_module.read(project, endpoint_apps)
79
+ auth_registry = auth_module.Registry(project, opts.settings, b)
80
+
81
+ known_events: Dict[str, Any] = {}
82
+ use_cases: List[operations.UseCase] = []
83
+ clients: List[clients_module.Client] = []
84
+ for agg in aggregates:
85
+ found, registry = events_module.read_events(agg, service, b)
86
+ agg.aggregate["events"] = found
87
+ known_events.update(registry)
88
+ use_cases += operations.read_use_cases(agg, b)
89
+ clients += clients_module.read_clients(agg.app, dict(opts.peers), rel, b)
90
+
91
+ reader = flows.FlowReader(
92
+ flows.Options(
93
+ context=context,
94
+ svc_id=svc_id,
95
+ service=service,
96
+ store=effective_store,
97
+ stores=store_aliases,
98
+ peers=dict(opts.peers),
99
+ events=dict(opts.events),
100
+ flow_wrappers=dict(opts.flow_wrappers),
101
+ settings=opts.settings,
102
+ ),
103
+ project,
104
+ aggregates,
105
+ concrete_models + serializer_registry.models,
106
+ use_cases,
107
+ clients,
108
+ known_events,
109
+ rel,
110
+ b,
111
+ )
112
+
113
+ found_flows = []
114
+ exposed: Dict[str, List[str]] = {}
115
+ for _app, endpoint in endpoints:
116
+ serializer = serializer_registry.for_endpoint(endpoint)
117
+ model = serializer_registry.model_for_endpoint(endpoint) if isinstance(endpoint.node, ast.ClassDef) else None
118
+ flow = reader.endpoint_flow(endpoint, serializer, model)
119
+ if flow is not None:
120
+ found_flows.append(flow)
121
+ for key in endpoint.use_cases:
122
+ exposed.setdefault(key, []).append(endpoint.id)
123
+ for agg in aggregates:
124
+ for module, node, decorator in transport.receivers(agg.app):
125
+ flow = reader.policy_flow(agg, module, node, decorator)
126
+ if flow is not None:
127
+ found_flows.append(flow)
128
+ found_flows.extend(reader.task_flows())
129
+
130
+ for agg in aggregates:
131
+ life = lifecycle.read(agg, known_events, b)
132
+ if life is not None:
133
+ agg.aggregate["lifecycle"] = life
134
+ for use_case in use_cases:
135
+ if use_case.app is not agg.app:
136
+ continue
137
+ agg.aggregate["operations"].append(operations.operation(use_case, exposed.get(use_case.key)))
138
+ agg.aggregate["operations"].sort(key=lambda o: o["id"])
139
+ for event in agg.aggregate["events"]:
140
+ if event["id"] not in reader.referenced:
141
+ b.warn(event["id"], "no flow reaches this event: nothing this extractor could follow publishes it")
142
+ # Where the code puts the event on the wire is its channel, when
143
+ # the dataclass did not say; when both say and disagree, one of
144
+ # the two is stale, and the dataclass's claim stays on the page.
145
+ for address, line in reader.produced.get(event["id"], []):
146
+ declared = event.get("wire", {}).get("channel", "")
147
+ if not declared:
148
+ event.setdefault("wire", {})["channel"] = address
149
+ elif declared != address:
150
+ b.warn(event["id"], "declares channel %s but is put on %s at %s" % (declared, address, line))
151
+
152
+ readme_path = os.path.join(root, "README.md")
153
+ readme = read(readme_path).strip() if os.path.isfile(readme_path) else ""
154
+ schema_name = schema_title(route_table)
155
+ display_name = opts.service_name or service_name_from_schema(schema_name) or readme_title(readme) or title(service)
156
+
157
+ openapi_name = opts.openapi_out or "openapi.inferred.yaml"
158
+ openapi_source = generated_source(input_, root, rel, openapi_name)
159
+ service_obj = {
160
+ "id": svc_id,
161
+ "slug": service,
162
+ "name": display_name,
163
+ "repo": opts.repo or project_repo(root),
164
+ "path": rel(root),
165
+ "readme": readme,
166
+ "provides": http_contracts(endpoints, svc_id, openapi_source),
167
+ "consumes": reader.consumes(),
168
+ "aggregates": [agg.aggregate for agg in aggregates],
169
+ }
170
+
171
+ fragment = {
172
+ "generatedAt": input_.generated_at,
173
+ "commit": input_.commit,
174
+ "contexts": [
175
+ {
176
+ "id": context,
177
+ "slug": context,
178
+ "name": opts.context_name or title(context),
179
+ "summary": opts.context_summary or "",
180
+ "services": [service_obj],
181
+ }
182
+ ],
183
+ "defs": {},
184
+ "flows": found_flows,
185
+ "adrs": [],
186
+ }
187
+ if opts.classification:
188
+ fragment["contexts"][0]["classification"] = opts.classification
189
+ b.files.append(dump(opts.out or "domain.json", fragment))
190
+ if service_obj["provides"]:
191
+ b.files.append(dump(openapi_name, openapi_document(
192
+ endpoints,
193
+ display_name,
194
+ b,
195
+ serializer_registry,
196
+ "" if opts.service_name else schema_name,
197
+ auth_registry,
198
+ )))
199
+
200
+ if route_table.runtime_schema and service_obj["provides"]:
201
+ b.warn(
202
+ route_table.runtime_schema,
203
+ "OpenAPI/Swagger is generated at runtime; the emitted OpenAPI document is inferred from URLConf, DRF declarations, schema decorators and handler expressions, so details assembled only at runtime remain unavailable without a checked-in document",
204
+ )
205
+
206
+ if not effective_store:
207
+ b.warn(
208
+ svc_id,
209
+ "the project has ORM models, but DATABASES['default'] cannot be read and no `store` option names their database",
210
+ )
211
+ return
212
+ kind, engine, implied = database.store_kind(project, opts.settings, opts.store_kind)
213
+ if implied:
214
+ b.warn(svc_id, "the manifest says storeKind %s but the settings' DATABASES engine is %s, which is %s; the manifest's kind is used" % (kind, engine, implied))
215
+ tables = store_module.read_all(
216
+ models_by_app,
217
+ aggregates,
218
+ svc_id,
219
+ effective_store,
220
+ kind,
221
+ database.default_auto_field(project, opts.settings),
222
+ b,
223
+ )
224
+ store_id = "%s.%s" % (svc_id, effective_store)
225
+ stores_fragment = {
226
+ "generatedAt": input_.generated_at,
227
+ "commit": input_.commit,
228
+ "contexts": [
229
+ {
230
+ "id": context,
231
+ "slug": context,
232
+ "name": "",
233
+ "summary": "",
234
+ "services": [
235
+ {
236
+ "id": svc_id,
237
+ "slug": service,
238
+ "name": "",
239
+ "repo": "",
240
+ "path": "",
241
+ "readme": "",
242
+ "provides": [],
243
+ "consumes": [],
244
+ "aggregates": [],
245
+ "stores": [store_id],
246
+ }
247
+ ],
248
+ }
249
+ ],
250
+ "defs": {},
251
+ "flows": [],
252
+ "adrs": [],
253
+ "stores": [
254
+ catalog.store(
255
+ store_id,
256
+ effective_store,
257
+ opts.store_name or title(service) + " database",
258
+ kind,
259
+ svc_id,
260
+ tables,
261
+ rel(source),
262
+ )
263
+ ],
264
+ }
265
+ b.files.append(dump(opts.stores_out or "stores.json", stores_fragment))
266
+
267
+
268
+ def dump(name: str, fragment: Dict[str, Any]):
269
+ import json
270
+
271
+ from protocol import File
272
+
273
+ return File(name=name, contents=json.dumps(fragment, indent=2, ensure_ascii=False) + "\n")
274
+
275
+
276
+ def generated_source(input_: Input, root: str, rel, name: str) -> str:
277
+ """Repository-relative location at which the host writes ``name``.
278
+
279
+ New hosts tell plugins the exact output directory. The conventional
280
+ ``<service>/portolan`` fallback keeps direct and older hosts useful.
281
+ """
282
+ output = input_.output.strip("/")
283
+ if output:
284
+ return "%s/%s" % (output, name)
285
+ return rel(os.path.join(root, "portolan", name))
286
+
287
+
288
+ def http_contracts(endpoints, svc_id: str, source: str) -> List[Dict[str, Any]]:
289
+ """Interfaces the service answers on, grouped by Django application.
290
+
291
+ A runtime Swagger generator proves that a document can be served, not that
292
+ a static document exists for this process to read. URLConf plus the view
293
+ declarations still prove the operation and its route, which is enough for
294
+ a useful partial contract and is labelled as inferred by its Python source.
295
+ """
296
+ grouped: Dict[str, List[Any]] = {}
297
+ apps: Dict[str, Any] = {}
298
+ for app, endpoint in endpoints:
299
+ if not endpoint.verb or not endpoint.path:
300
+ continue
301
+ grouped.setdefault(app.dotted, []).append(endpoint)
302
+ apps[app.dotted] = app
303
+ out = []
304
+ for dotted_name in sorted(grouped):
305
+ app = apps[dotted_name]
306
+ methods = []
307
+ seen = set()
308
+ for endpoint in sorted(grouped[dotted_name], key=lambda item: (item.id, item.verb, item.path)):
309
+ name = endpoint.id
310
+ if name in seen:
311
+ name = "%s_%s" % (name, slug(endpoint.path))
312
+ seen.add(name)
313
+ method = {"name": name}
314
+ if endpoint.doc:
315
+ method["doc"] = endpoint.doc
316
+ method["http"] = {"method": endpoint.verb, "path": endpoint.path}
317
+ methods.append(method)
318
+ if not methods:
319
+ continue
320
+ out.append({"id": "%s.%s" % (svc_id, slug(app.label)), "methods": methods, "source": source})
321
+ return out
322
+
323
+
324
+ def openapi_document(endpoints, service_name: str, b: Builder, serializer_registry=None, api_title: str = "", auth_registry=None) -> Dict[str, Any]:
325
+ """A conservative OpenAPI view over facts Django declares statically.
326
+
327
+ Routes and verbs are evidence. A bound DRF serializer and generic view add
328
+ the payload and framework response semantics; other operations retain an
329
+ explicitly unknown default response.
330
+ """
331
+ paths: Dict[str, Dict[str, Any]] = {}
332
+ tags = set()
333
+ operation_ids = set()
334
+ for app, endpoint in sorted(endpoints, key=lambda item: (item[1].path, item[1].verb, item[0].label, item[1].id)):
335
+ if not endpoint.path or not endpoint.verb:
336
+ continue
337
+ method = endpoint.verb.lower()
338
+ path_item = paths.setdefault(endpoint.path, {})
339
+ if method in path_item:
340
+ b.warn(endpoint.route_source, "%s %s is declared more than once; the first route is kept in inferred OpenAPI" % (endpoint.verb, endpoint.path))
341
+ continue
342
+ tag = app.label
343
+ tags.add(tag)
344
+ operation_id = slug("%s-%s" % (tag, endpoint.id)).replace("-", "_")
345
+ base = operation_id
346
+ suffix = 2
347
+ while operation_id in operation_ids:
348
+ operation_id = "%s_%d" % (base, suffix)
349
+ suffix += 1
350
+ operation_ids.add(operation_id)
351
+ summary = endpoint.doc.strip().splitlines()[0] if endpoint.doc.strip() else title(endpoint.action)
352
+ operation: Dict[str, Any] = {
353
+ "operationId": operation_id,
354
+ "summary": summary,
355
+ "tags": [tag],
356
+ "responses": {
357
+ "default": {
358
+ "description": "Response schema and status are not available from static Django route analysis."
359
+ }
360
+ },
361
+ "x-portolan-inferred": True,
362
+ "x-portolan-source": endpoint.route_source,
363
+ }
364
+ if endpoint.doc.strip() and "\n" in endpoint.doc.strip():
365
+ operation["description"] = endpoint.doc.strip()
366
+ parameters = []
367
+ for name in dict.fromkeys(re.findall(r"\{([A-Za-z_][A-Za-z0-9_]*)\}", endpoint.path)):
368
+ converter = endpoint.path_parameters.get(name, "str")
369
+ schema: Dict[str, Any] = {"type": "integer"} if converter == "int" else {"type": "string"}
370
+ if converter == "uuid":
371
+ schema["format"] = "uuid"
372
+ elif converter == "slug":
373
+ schema["pattern"] = "^[-a-zA-Z0-9_]+$"
374
+ parameters.append(
375
+ {
376
+ "name": name,
377
+ "in": "path",
378
+ "required": True,
379
+ "description": "Inferred from Django's %s path converter." % converter if name in endpoint.path_parameters else "Type was not recoverable from the resolved Django route.",
380
+ "schema": schema,
381
+ "x-portolan-inferred": True,
382
+ }
383
+ )
384
+ if parameters:
385
+ operation["parameters"] = parameters
386
+ statuses = response_statuses(endpoint.node)
387
+ serializer = serializer_registry.for_endpoint(endpoint) if serializer_registry is not None else None
388
+ if serializer is not None and serializer_registry.describes_contract(endpoint):
389
+ attach_serializer_contract(operation, endpoint, serializer, serializer_registry, statuses)
390
+ elif statuses:
391
+ operation["responses"] = {
392
+ status: {
393
+ "description": "No content response read from the Django handler."
394
+ if status == "204"
395
+ else "Error response status read from the Django handler; its schema is unknown."
396
+ if not status.startswith("2")
397
+ else "Response status read from the Django handler; its schema is unknown."
398
+ }
399
+ for status in statuses
400
+ }
401
+ if serializer_registry is not None:
402
+ detail = contracts.read(endpoint, serializer_registry)
403
+ if detail.summary:
404
+ operation["summary"] = detail.summary
405
+ if detail.description:
406
+ operation["description"] = detail.description
407
+ if detail.operation_id:
408
+ operation_ids.discard(operation["operationId"])
409
+ operation["operationId"] = unique_operation_id(detail.operation_id, operation_ids)
410
+ if detail.tags:
411
+ operation["tags"] = detail.tags
412
+ tags.update(detail.tags)
413
+ if detail.parameters:
414
+ operation.setdefault("parameters", [])
415
+ for parameter in detail.parameters:
416
+ contracts.upsert_parameter(operation["parameters"], parameter)
417
+ if detail.request_schema is not None:
418
+ operation["requestBody"] = {
419
+ "required": True,
420
+ "content": {"application/json": {"schema": detail.request_schema}},
421
+ }
422
+ if detail.responses:
423
+ operation["responses"] = detail.responses
424
+ if auth_registry is not None:
425
+ security = auth_registry.security_for(endpoint)
426
+ if security.requirement is not None:
427
+ operation["security"] = security.requirement
428
+ if security.permissions:
429
+ operation["x-portolan-permissions"] = security.permissions
430
+ path_item[method] = operation
431
+
432
+ document = {
433
+ "openapi": "3.1.0",
434
+ "info": {
435
+ "title": api_title or "%s HTTP API" % service_name,
436
+ "version": "inferred",
437
+ "description": "Generated statically from Django URLConf, DRF declarations, schema decorators and handler expressions. Unknown details are left unspecified.",
438
+ },
439
+ "tags": [{"name": name} for name in sorted(tags)],
440
+ "paths": paths,
441
+ "x-portolan-inferred": True,
442
+ "x-portolan-generator": "extract-django",
443
+ }
444
+ components = serializer_registry.components() if serializer_registry is not None else {}
445
+ if components:
446
+ document["components"] = {"schemas": components}
447
+ if auth_registry is not None:
448
+ # The document-level requirement is what the settings alone decide,
449
+ # and the schemes are every one an operation or that default named.
450
+ default = auth_registry.default_security()
451
+ if default is not None:
452
+ document["security"] = default
453
+ if auth_registry.schemes:
454
+ document.setdefault("components", {})["securitySchemes"] = {name: auth_registry.schemes[name] for name in sorted(auth_registry.schemes)}
455
+ return document
456
+
457
+
458
+ def unique_operation_id(preferred: str, used: set) -> str:
459
+ candidate = slug(preferred).replace("-", "_")
460
+ if candidate in used:
461
+ suffix = 2
462
+ while "%s_%d" % (candidate, suffix) in used:
463
+ suffix += 1
464
+ candidate = "%s_%d" % (candidate, suffix)
465
+ used.add(candidate)
466
+ return candidate
467
+
468
+
469
+ def attach_serializer_contract(operation: Dict[str, Any], endpoint, serializer, registry, statuses: List[str]) -> None:
470
+ reference: Dict[str, Any] = {"$ref": "#/components/schemas/%s" % serializer.component}
471
+ verb = endpoint.verb.upper()
472
+ if verb in ("POST", "PUT", "PATCH"):
473
+ request_schema: Dict[str, Any] = dict(reference)
474
+ if verb == "PATCH":
475
+ request_schema = {"allOf": [reference], "x-portolan-partial": True}
476
+ operation["requestBody"] = {
477
+ "required": True,
478
+ "content": {"application/json": {"schema": request_schema}},
479
+ }
480
+
481
+ if not statuses and (verb == "DELETE" or endpoint.action == "destroy"):
482
+ operation["responses"] = {"204": {"description": "Deleted successfully."}}
483
+ return
484
+
485
+ response_schema: Dict[str, Any] = reference
486
+ if registry.is_list(endpoint):
487
+ response_schema = {"type": "array", "items": reference}
488
+ statuses = statuses or (["201"] if endpoint.action == "create" else ["200"])
489
+ operation["responses"] = {}
490
+ for status in statuses:
491
+ success = status.startswith("2")
492
+ response: Dict[str, Any] = {
493
+ "description": (
494
+ "No content response read from the Django handler."
495
+ if status == "204"
496
+ else "Serialized response inferred from DRF view configuration."
497
+ if success
498
+ else "Error response status read from the Django handler; its schema is unknown."
499
+ ),
500
+ }
501
+ if success and status != "204":
502
+ response["content"] = {"application/json": {"schema": response_schema}}
503
+ operation["responses"][status] = response
504
+
505
+
506
+ def response_statuses(node: ast.AST) -> List[str]:
507
+ if not isinstance(node, (ast.FunctionDef, ast.AsyncFunctionDef)):
508
+ return []
509
+ out = set()
510
+ for call in ast.walk(node):
511
+ if not isinstance(call, ast.Call) or dotted(call.func).split(".")[-1] not in ("Response", "JsonResponse"):
512
+ continue
513
+ value = next((kw.value for kw in call.keywords if kw.arg == "status"), None)
514
+ if value is None and dotted(call.func).split(".")[-1] == "Response" and len(call.args) > 1:
515
+ value = call.args[1]
516
+ if isinstance(value, ast.Constant) and isinstance(value.value, int):
517
+ out.add(str(value.value))
518
+ continue
519
+ symbolic = dotted(value)
520
+ match = re.search(r"HTTP_(\d{3})_", symbolic)
521
+ out.add(match.group(1) if match else "200")
522
+ return sorted(out)
523
+
524
+
525
+ def routed_applications(project: Project, model_apps, routes: routing.Routes):
526
+ """Django applications that expose routes, including stateless ones.
527
+
528
+ ``apps.discover`` intentionally uses a models module as the application
529
+ boundary for domain extraction. HTTP is a different concern: health,
530
+ proxy and mailer applications often own URLConf and views but no model.
531
+ Their routes remain part of the service contract.
532
+ """
533
+ out = {app.dotted: app for app in model_apps}
534
+ for route in routes.entries:
535
+ marker = ".views"
536
+ package = route.module.split(marker, 1)[0] if marker in route.module else route.module.rsplit(".", 1)[0]
537
+ if not package or package in out:
538
+ continue
539
+ app = apps_module.build(project, package)
540
+ if app is not None:
541
+ out[package] = app
542
+ return [out[name] for name in sorted(out, key=lambda name: out[name].rel)]
543
+
544
+
545
+ def readme_title(markdown: str) -> str:
546
+ fence = ""
547
+ for line in markdown.split("\n"):
548
+ text = line.strip()
549
+ marker = "```" if text.startswith("```") else "~~~" if text.startswith("~~~") else ""
550
+ if marker:
551
+ if not fence:
552
+ fence = marker
553
+ elif fence == marker:
554
+ fence = ""
555
+ continue
556
+ if fence:
557
+ continue
558
+ if text.startswith("# "):
559
+ return text[2:].strip()
560
+ return ""
561
+
562
+
563
+ def schema_title(routes: routing.Routes) -> str:
564
+ """The human API title declared by drf-yasg in the root URLConf.
565
+
566
+ This is read from the AST only: importing a Django URLConf would execute
567
+ application code merely to name its documentation.
568
+ """
569
+ if routes.root is None:
570
+ return ""
571
+ for node in ast.walk(routes.root.tree):
572
+ if not isinstance(node, ast.Call) or dotted(node.func).split(".")[-1] != "Info":
573
+ continue
574
+ value = const_str(keyword(node, "title"))
575
+ if not value and node.args:
576
+ value = const_str(node.args[0])
577
+ if value:
578
+ return value.strip()
579
+ return ""
580
+
581
+
582
+ def service_name_from_schema(value: str) -> str:
583
+ """Turn an API document title into the component name shown in catalog."""
584
+ without_suffix = re.sub(r"\s+(?:HTTP\s+)?API\s*$", "", value, flags=re.IGNORECASE).strip()
585
+ return without_suffix or value.strip()
586
+
587
+
588
+ def project_repo(root: str) -> str:
589
+ """`pyproject.toml`'s repository url, spelled the way go.mod spells a
590
+ module: host/owner/name."""
591
+ path = os.path.join(root, "pyproject.toml")
592
+ if not os.path.isfile(path):
593
+ return ""
594
+ match = re.search(r"(?im)^\s*(?:repository|Repository|Source)\s*=\s*[\"']([^\"']+)[\"']", read(path))
595
+ if not match:
596
+ return ""
597
+ url = match.group(1)
598
+ url = re.sub(r"^git\+", "", url)
599
+ url = re.sub(r"^(https?|ssh)://", "", url)
600
+ url = re.sub(r"^git@", "", url).replace(":", "/", 1)
601
+ return re.sub(r"\.git$", "", url).rstrip("/")