@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,386 @@
1
+ # extract-django
2
+
3
+ A Django service in, a catalog fragment out — two, when the service keeps a
4
+ database. The Django twin of `extract-go` and `extract-ts`, with one difference
5
+ that runs through all of it: those read a layout somebody keeps by hand, and
6
+ this reads the framework. A Django project already says where the model is,
7
+ what the rows are and what answers a request; nothing is annotated for the
8
+ catalog, and the applications are the claim.
9
+
10
+ Written in Python and run as a process plugin, `python3 plugins/extract-django/main.py`.
11
+ Python 3.9 or newer and nothing but the standard library: a plugin that needed
12
+ the project's own dependencies installed could not be run over somebody else's
13
+ checkout. **The project is never imported.** `django.setup()` runs the code it
14
+ finds, reads the environment and opens sockets, and an extractor doing any of
15
+ those has stopped being a pure function of the tree. Everything is read through
16
+ `ast`, and names are resolved by import and by file, the way `extract-ts`
17
+ resolves them without a type checker.
18
+
19
+ ## The layout it reads
20
+
21
+ ```
22
+ <app>/ an application: a package with a models module
23
+ models.py the root, named after the application, and its entities
24
+ values.py value objects: the frozen dataclasses
25
+ events.py events: a dataclass with a `name`, or a bare Signal
26
+ services.py use cases: one function per scenario
27
+ views.py DRF views: a ViewSet's actions, an @api_view function
28
+ urls.py the router registration, which names the endpoints
29
+ handlers.py policies: `@receiver(signal)`, also read from signals.py
30
+ tasks.py Celery tasks: what runs later; an enqueue of one is a hop
31
+ clients/<peer>/
32
+ openapi.yaml the peer's document, vendored
33
+ client.py the class that calls it
34
+ apps.py the AppConfig, read for the label half of a table name
35
+ README.md the aggregate's page, or the docstring of the root
36
+ ```
37
+
38
+ `models.py` may be a `models/` package, and so may every other module here.
39
+
40
+ ## What becomes what
41
+
42
+ **Aggregate.** One per application. The root is the model named after the
43
+ application — `invoices` holds `Invoice` — or the only model there is, or the
44
+ one `aggregates` names in the manifest; an application with several models and
45
+ no such name is reported and skipped, because guessing which of them the others
46
+ hang off is how an aggregate boundary gets drawn wrong and stays wrong. The
47
+ aggregate is named and slugged after the root, so a Django `invoices` and a Go
48
+ `invoice` package land on the same id. Every concrete model of the application
49
+ is an entity, the root included and first; abstract models are not. The
50
+ readme is `README.md` in the application, or the root's docstring.
51
+
52
+ **Field.** Each model attribute assigned a field, with the type as written:
53
+ `CharField`, `DateTimeField`, and a relation as `ForeignKey[Invoice]`. The doc
54
+ is the field's own `help_text`, which is the one place a Django model already
55
+ writes down what a column means. Value objects are the frozen dataclasses in
56
+ `values.py`, their fields the annotations as written.
57
+
58
+ **Event.** `events.py`, in either of the two ways a Django project says it. A
59
+ dataclass with `name = "billing.InvoiceIssued"` is an event, its payload the
60
+ annotated fields and its wire name that string; `channel` beside it is where it
61
+ goes out — or, when the dataclass does not say, the address the code puts it
62
+ on (below), and when both say and disagree that is reported, with the
63
+ dataclass's claim kept on the page. A module-level `Signal()` is one too — leaving it out would hide a
64
+ publish — and it declares no payload, which is a diagnostic rather than an
65
+ empty shape nobody questions. A signal named after a dataclass event (
66
+ `invoice_issued` beside `InvoiceIssued`) is how that event travels, not a
67
+ second event.
68
+
69
+ **Operation.** Each public function of `services.py`, its id the function name
70
+ in PascalCase: `issue_invoice` → `IssueInvoice`. It is a command when it saves,
71
+ creates, deletes, updates or publishes — or opens a transaction — and a query
72
+ when it does not. The doc is the function's docstring, first paragraph.
73
+ `exposedBy` names the endpoints that run it.
74
+
75
+ **Endpoint and inferred HTTP contract.** The root URLConf is found through
76
+ `DJANGO_SETTINGS_MODULE` and `ROOT_URLCONF`; nested `include()` prefixes,
77
+ `path`/`re_path`, DRF router registrations, concrete generic views and local
78
+ HTTP handlers are then followed without importing the project. A `ViewSet`'s
79
+ inherited actions and every `@action` it adds are included. The id is the
80
+ router basename and action: `router.register("invoices", InvoiceViewSet,
81
+ basename="invoice")` makes `invoice_issue` at the collection or detail route.
82
+
83
+ Those operations become a partial `provides` interface and an
84
+ `openapi.inferred.yaml` OpenAPI 3.1 document even when Swagger is generated
85
+ only at runtime. The normal API reference can therefore render the result. It
86
+ is partial deliberately and carries `x-portolan-inferred`: URLConf proves the
87
+ verb and path, while request and response schemas and status codes stay absent
88
+ unless source code proves them. A checked-in document remains
89
+ `extract-openapi`'s richer source of truth. Stateless Django applications with
90
+ routes but no models contribute to this HTTP contract without being invented
91
+ as domain aggregates.
92
+
93
+ **DRF payload schemas.** A `Serializer` becomes an OpenAPI object from its
94
+ declared fields. A `ModelSerializer` joins `Meta.fields` to the named model's
95
+ Django fields and honours explicit overrides and `read_only_fields`.
96
+ Inheritance and nested serializers (including `many=True`) become component
97
+ references. The field mapping carries scalar formats, nullability, length,
98
+ help text, defaults and read/write direction where syntax proves them.
99
+
100
+ An endpoint is joined to `serializer_class`, an action's own
101
+ `serializer_class`, or a statically decidable `get_serializer_class()` branch.
102
+ Standard generic actions supply list/object response shapes and the normal
103
+ `200`, `201` and `204` statuses. A concrete `Response` or `JsonResponse`
104
+ supplies the statuses written in its handler. Operations without that evidence
105
+ keep an unknown default response rather than borrowing a nearby serializer.
106
+
107
+ For custom handlers, literal response objects and lists are read recursively;
108
+ `Serializer(...).data` and `self.get_serializer(..., many=True).data` retain
109
+ their component references even when nested in an envelope such as
110
+ `{"data": serializer.data}`. `swagger_auto_schema` and `extend_schema` take
111
+ precedence where they declare request/response schemas, operation text, tags,
112
+ ids or manual parameters. Query parameters come from explicit
113
+ `request.GET.get(...)` and `request.query_params.get(...)` reads, including
114
+ literal defaults and simple numeric casts. `filterset_fields`, `search_fields`,
115
+ `ordering_fields` and an explicit pagination class add the conventional DRF
116
+ filter, search, ordering and page parameters. A parameter is marked required only when a missing value is
117
+ statically followed by a raise.
118
+
119
+ **Auth, in the contract.** Who may call an operation is read off DRF's
120
+ `permission_classes` and `authentication_classes`, in the order DRF applies
121
+ them: the action's own (`@action(permission_classes=…)`, or the decorators on
122
+ an `@api_view` function), then a `get_permissions()` a `self.action` branch
123
+ can decide, then the class attribute — the view's own or one a local base
124
+ class declares — then `REST_FRAMEWORK` in the settings, and past all of those
125
+ DRF's defaults. An authentication class is written as an OpenAPI security
126
+ scheme under the id drf-spectacular would give it (`SessionAuthentication` →
127
+ `cookieAuth`, `TokenAuthentication` → `tokenAuth`, a JWT class → `jwtAuth`),
128
+ and the permissions decide the operation's `security`: required for
129
+ `IsAuthenticated` and its kin, optional (`{}` among the alternatives) for
130
+ `AllowAny`, and for `IsAuthenticatedOrReadOnly` required on everything but
131
+ `GET`, `HEAD` and `OPTIONS`. The classes as the code names them are kept under
132
+ `x-portolan-permissions`. `extend_schema(auth=…)` and
133
+ `swagger_auto_schema(security=…)` win outright when written as literals. A
134
+ permission the reader does not know — a project's own `IsOwner` — is not
135
+ guessed at: the operation carries no `security` of its own, the class is still
136
+ named, and the diagnostic says so; a custom authentication class becomes a
137
+ scheme of its own whose transport is said to be unknown.
138
+
139
+ **Lifecycle.** Read off the table the model keeps, never off the branches of
140
+ its methods. Either the table is a `TRANSITIONS` mapping beside the
141
+ `TextChoices` that names the states —
142
+
143
+ ```python
144
+ class Status(models.TextChoices):
145
+ DRAFT = "draft", "Draft"
146
+ ISSUED = "issued", "Issued"
147
+
148
+ TRANSITIONS = {Status.DRAFT: [Status.ISSUED], Status.ISSUED: []}
149
+ ```
150
+
151
+ — and the mover is the method assigning `self.status`; or the table is
152
+ django-fsm's `@transition(field=status, source=…, target=…)` decorators, which
153
+ are the same table written one edge at a time. Either way `emits` is the event
154
+ the method's return annotation names. The first state is where a new row
155
+ starts; a state nothing leads out of is terminal, which is derived on the page
156
+ and never written down.
157
+
158
+ **Flow, from an endpoint.** Each view action opens one: `client → service : rpc
159
+ <endpoint>`, then follows project-local calls through imported functions,
160
+ `self.method()`, `super().method()` and class or static methods until it reaches
161
+ observable effects. Traversal is bounded and cycles are cut by symbol, so a
162
+ recursive helper cannot make extraction recursive. ORM models from routed
163
+ applications remain visible here even when the application has no unambiguous
164
+ aggregate root. A URLConf may directly mount an arbitrarily named method of a
165
+ plain class, such as `Planet.fetch`; that is still an HTTP flow root. When no
166
+ decorator or conventional handler name proves one verb, the flow is retained
167
+ but the route is omitted from inferred OpenAPI rather than guessed. An
168
+ inherited DRF generic action has no local handler body, so
169
+ its framework behaviour is reconstructed instead: list/retrieve read the
170
+ model, create/update validate through the selected serializer and persist it,
171
+ and destroy reads then deletes it. The model must be proven by `queryset`,
172
+ `get_queryset()` or a serializer's `Meta.model`; a URL or class name is never
173
+ used to invent a persistence hop. These steps point at the view declaration
174
+ and say that DRF supplied them, keeping inferred framework behaviour distinct
175
+ from a custom handler read directly from code.
176
+
177
+ **Flow, from a policy.** Each `@receiver` opens one on the bus: `bus → service
178
+ : event <ref>`, where the event is the signal it is given — one of this
179
+ service's own, or another service's placed by the manifest's `events`. A
180
+ receiver on one of Django's model signals — `post_save`, `pre_delete`,
181
+ `m2m_changed` and the rest — opens none: that is a hook on the row, not a
182
+ policy on an event. It says nothing about what happened, and it fires for
183
+ every save, migrations and fixtures included, so it is reported, with the
184
+ `sender` it hangs on, as the one thing left to move onto an event.
185
+
186
+ **Inside a body.** Statements are read in source order, and a chain left to
187
+ right. `Invoice.objects.get(…)` is a hop into the store, and so are
188
+ `Invoice.objects.filter(…).first()` - where the queryset is built, which is
189
+ where the line is - and a `save()` or `delete()` on something the ORM handed
190
+ back; an event handed to anything — a signal's `send`, a project's own
191
+ `publish` — is the event leaving for the bus, and where the call names an
192
+ address — `producer.send("topic", …)`, `produce`, NATS' or Redis' `publish`,
193
+ Channels' `group_send`, as a literal, a module constant or a `settings.X` —
194
+ or is a function of the project whose body does, the step says `on <address>`;
195
+ a standard `requests`, `httpx` or `aiohttp` call is an unresolved rpc to the
196
+ URL's host, including URLs carried through local variables, settings and
197
+ `urljoin`; a call on a vendored client is an rpc to the peer; a `.delay()` or `.apply_async()` on a function decorated
198
+ `@shared_task` or `@app.task`, directly or through `.s()`, is a hop to the
199
+ queue it lands on, `celery-<queue>` — decided the way Celery decides it, by the
200
+ reader `extract-celery` shares through `pyplugin`: the `queue=` at the call,
201
+ then the decorator's, then `task_routes`, then `task_default_queue` — so the
202
+ endpoint and task flows carry the same exact queue and task wire name and are
203
+ composed at the worker receive step; and `transaction.on_commit(…)` around it,
204
+ as a lambda or a `partial`, is the note that it waits for the commit. A call into `services.py` and ordinary project helpers is followed within the same
205
+ bounded traversal. `if` becomes an alt when some arm holds
206
+ a hop, and a branch ending in a `return` or a `raise` is terminal; a `for`, a
207
+ `while`, a `with transaction.atomic()` and an `except` are a note on the steps
208
+ inside them. `await` is transparent. Every step is `declared`.
209
+
210
+ The worker receive is also a source seam. When `extract-celery` is enabled,
211
+ its transport flow continues into the task function body read here, so one
212
+ request flow can show `HTTP → enqueue → worker → database/API` without
213
+ repeating the enqueue. The task-body fragment is source-backed and disappears
214
+ as a standalone fragment once composition consumes it.
215
+
216
+ **Project wrappers.** Some repositories deliberately put a semantic boundary
217
+ around infrastructure or a business integration. `flowWrappers` maps that
218
+ callable's fully qualified Python name to the step the reader should show,
219
+ instead of expanding its implementation on every call:
220
+
221
+ ```json
222
+ {
223
+ "flowWrappers": {
224
+ "util.logger.send_log": {
225
+ "label": "Record application log",
226
+ "technical": true
227
+ },
228
+ "mailer.client.send": {
229
+ "label": "Send email",
230
+ "target": "messaging.mailer"
231
+ }
232
+ }
233
+ }
234
+ ```
235
+
236
+ `technical: true` keeps one counted step per flow (`×N`) rather than flooding
237
+ every branch with the same logger, metric or tracing implementation. It does
238
+ not silently discard the calls: their count, wrapper name and first source
239
+ line remain visible. A wrapper without `technical` is a normal semantic step
240
+ and is kept at every call site. `target` is an optional catalog service id;
241
+ without it the wrapper is shown as an operation inside the current service.
242
+
243
+ **Peer.** `clients/<peer>/` holds the class that calls and the document it was
244
+ vendored from. The code names the verb and the route —
245
+ `self._http.post("/v1/quotes")`, httpx or requests alike — and the document
246
+ says which operation answers there, so the call is recorded under the id the
247
+ callee's own extractor gives it: `pricing.v1.Quotes/createQuote`, spelled by
248
+ the same rules as `plugins/openapi`. The peer is the manifest's `peers` entry
249
+ for that api id; without one the lane is `unknown` and the step unresolved.
250
+ Without a document there is no id to share, and the call is recorded against
251
+ the route it names.
252
+
253
+ **Store.** The models are the schema, so the tables come out of this plugin
254
+ rather than out of `extract-sql`. A Django model is both the domain object and
255
+ the row, which means the two facts a SQL reader has to pair up — a column, and
256
+ the field it carries — are one declaration here: `maps` is exact, and so is
257
+ `persists`, down to the block. The table is `Meta.db_table` or the name Django
258
+ composes from the application's label; a column is `db_column` or the field's
259
+ own name, `_id` for a relation; the type is what Django's PostgreSQL backend
260
+ emits, so `CharField(max_length=32)` is `varchar(32)` and a key into a
261
+ `bigserial` is a `bigint`. Indexes come from `db_index`, `unique`,
262
+ `Meta.indexes`, `Meta.unique_together` and `Meta.constraints`; an index Django
263
+ would name with a hash of its own is called after its table and columns, since
264
+ inventing the hash would be a name no database has.
265
+
266
+ The primary store is inferred from `DATABASES["default"]` when that setting is
267
+ statically readable; `store` remains the override for settings assembled at
268
+ runtime or for a catalog that needs a different stable slug. Every concrete,
269
+ non-proxy model becomes a table even when its application has several possible
270
+ aggregate roots: ambiguity limits the domain model, not the persistence
271
+ schema. Fields from abstract model bases are copied into those tables
272
+ regardless of which model module sorts first. `DEFAULT_AUTO_FIELD` supplies
273
+ implicit primary-key types. PostgreSQL `ArrayField` nesting and
274
+ `MultiSelectField` storage are rendered as their database types; `to_field`
275
+ selects a relation's referenced column, and `db_constraint=False` keeps a
276
+ logical relation from being drawn as a physical foreign-key constraint. An
277
+ implicit `ManyToManyField` contributes the join table Django creates, while an
278
+ explicit `through` model is already a normal model and therefore a normal
279
+ table. Calls routed through `.using("alias")` or `.db_manager("alias")` keep
280
+ that alias on the flow instead of being attributed to the primary store.
281
+
282
+ ## What it does not read
283
+
284
+ Named here rather than left to be discovered: **migrations** (the models are
285
+ the schema; a migration is how it got there), **the bodies of Celery tasks**
286
+ (an enqueue is a hop to its queue, and what the worker does there is
287
+ `extract-celery`'s), **admin**, **templates**, **middleware**, **management
288
+ commands**, **signals connected outside a `@receiver`**, and a **many-to-many**
289
+ field with an explicit `through` model beyond the table represented by that
290
+ model itself.
291
+
292
+ ## Options
293
+
294
+ ```json
295
+ {
296
+ "context": "shop",
297
+ "service": "billing",
298
+ "store": "pg",
299
+ "storeKind": "postgres",
300
+ "aggregates": { "invoices": "Invoice" },
301
+ "peers": { "pricing.v1": "shop.pricing" },
302
+ "events": { "payments.events": "payments.ledger.payment" },
303
+ "source": ".",
304
+ "settings": "config.settings",
305
+ "out": "domain.json",
306
+ "storesOut": "stores.json"
307
+ }
308
+ ```
309
+
310
+ `source` is the directory the applications are looked for in, the input root
311
+ unless said otherwise; `apps` names them outright for a project that keeps them
312
+ somewhere a models module would not be found. `store` overrides the stable slug
313
+ inferred from `DATABASES["default"]`; it is required only when the settings are
314
+ not statically readable. `storeKind` is read off
315
+ `DATABASES["default"]["ENGINE"]` in the settings module when left out —
316
+ `postgresql`, `postgis` and `psqlextra.backend` are `postgres`, and `sqlite3`
317
+ is `sqlite`; given, it wins, and a disagreement with the settings is reported.
318
+ `settings` names the Django settings module — for the database, and
319
+ for the queues Celery is configured with — only where `manage.py` does not.
320
+ Everything else means what it means for `extract-ts`.
321
+
322
+ ## Extraction limits
323
+
324
+ These cases do not become facts in the fragment:
325
+
326
+ - a domain aggregate for an application with no models, or with several and no
327
+ root among them named after it (its statically resolvable HTTP routes are
328
+ still included);
329
+ - a model that declares no fields;
330
+ - an events module holding a class with no wire name, and a signal with no
331
+ payload;
332
+ - an application with no services module;
333
+ - a dynamically assembled URLConf target or an unrestricted function view
334
+ whose HTTP method cannot be proven from syntax;
335
+ - serializer fields created dynamically, a `get_serializer_class()` decision
336
+ that is not reducible to `self.action`, and custom serializer classes that
337
+ do not derive from DRF `Serializer`/`ModelSerializer`;
338
+ - a table with an edge no method makes, a method moving into a state the table
339
+ lacks, a status assigned to something the states do not name, and a model
340
+ that moves its status while declaring no table at all;
341
+ - a permission class DRF does not ship, and a permission or authentication
342
+ list built with `|`, `&` or anything but a literal list of classes;
343
+ - a client with no document beside it, and a route its document does not
344
+ declare;
345
+ - an api id the manifest names no peer for;
346
+ - a receiver on a model signal, `post_save` and its kin, named with the model
347
+ it hangs on;
348
+ - a many-to-many field, whose join table is not named here;
349
+ - an event no flow reaches;
350
+ - a file that does not parse, by path.
351
+
352
+ ## Manifest
353
+
354
+ ```json
355
+ {
356
+ "plugins": [{ "name": "django-domain", "process": { "command": "python3", "args": ["plugins/extract-django/main.py"] } }],
357
+ "extract": [
358
+ {
359
+ "plugin": "django-domain",
360
+ "in": "examples/shop/billing",
361
+ "out": "examples/shop/billing/portolan",
362
+ "options": {
363
+ "context": "shop",
364
+ "service": "billing",
365
+ "store": "pg",
366
+ "peers": { "pricing.v1": "shop.pricing" },
367
+ "events": { "payments.events": "payments.ledger.payment" }
368
+ }
369
+ }
370
+ ]
371
+ }
372
+ ```
373
+
374
+ ## Tests
375
+
376
+ ```bash
377
+ python3 -m unittest discover -s plugins/extract-django -t plugins/extract-django -p "*_test.py"
378
+ ```
379
+
380
+ The reader is held to fixtures under `testdata/`: `billing`, a service in the
381
+ layout above with each shape it claims to read present once, against the golden
382
+ `expected.json` and `expected-stores.json`; `fsm`, the same lifecycle written
383
+ with django-fsm; and `drift`, a table and the methods that no longer agree with
384
+ it, where every diagnostic is one the reader is meant to raise.
385
+ `UPDATE_GOLDEN=1` writes the goldens again after a deliberate change, and the
386
+ diff is the review.
@@ -0,0 +1,100 @@
1
+ """Which directories are Django applications.
2
+
3
+ An application is a package holding `models.py` (or a `models/` package): that
4
+ is what Django itself looks for, and it is the smallest claim a project makes
5
+ about where one part of the model ends and the next begins. `apps` in the
6
+ manifest names them outright for a project that keeps its applications
7
+ somewhere this would not look.
8
+ """
9
+
10
+ from __future__ import annotations
11
+
12
+ import ast
13
+ import os
14
+ from dataclasses import dataclass, field
15
+ from typing import List, Optional
16
+
17
+ from source import Module, Project, bases, dotted, read
18
+
19
+
20
+ @dataclass
21
+ class App:
22
+ label: str # the directory's name: "invoices"
23
+ dotted: str # the package, as an import spells it: "billing.invoices"
24
+ dir: str # absolute
25
+ rel: str
26
+ models: List[Module] = field(default_factory=list)
27
+ modules: List[Module] = field(default_factory=list)
28
+ readme: str = ""
29
+
30
+ def module(self, name: str) -> Optional[Module]:
31
+ """`<app>/<name>.py`, or the `__init__` of `<app>/<name>/`."""
32
+ for m in self.modules:
33
+ if m.dotted in (self.dotted + "." + name, self.dotted + "." + name + ".__init__"):
34
+ return m
35
+ return None
36
+
37
+ def package(self, name: str) -> List[Module]:
38
+ """Every module of `<app>/<name>.py` or `<app>/<name>/`."""
39
+ prefix = self.dotted + "." + name
40
+ return [m for m in self.modules if m.dotted == prefix or m.dotted.startswith(prefix + ".")]
41
+
42
+
43
+ def discover(project: Project, named: List[str]) -> List[App]:
44
+ """The applications, in path order. A name in `apps` is a package, so
45
+ `billing.invoices` and `invoices` are both accepted."""
46
+ packages = []
47
+ if named:
48
+ for name in named:
49
+ packages.append(name)
50
+ else:
51
+ for dotted_name in sorted(project.modules):
52
+ if dotted_name.endswith(".models"):
53
+ packages.append(dotted_name[: -len(".models")])
54
+ elif dotted_name.endswith(".models.__init__"):
55
+ packages.append(dotted_name[: -len(".models.__init__")])
56
+ out = []
57
+ seen = set()
58
+ for package in packages:
59
+ if package in seen or not package:
60
+ continue
61
+ seen.add(package)
62
+ app = build(project, package)
63
+ if app is not None:
64
+ out.append(app)
65
+ return sorted(out, key=lambda a: a.rel)
66
+
67
+
68
+ def build(project: Project, package: str) -> Optional[App]:
69
+ directory = os.path.join(project.source, *package.split("."))
70
+ if not os.path.isdir(directory):
71
+ return None
72
+ app = App(label=package.split(".")[-1], dotted=package, dir=directory, rel=project.rel(directory))
73
+ app.modules = project.under(package)
74
+ app.models = [m for m in app.modules if m.dotted in (package + ".models", package + ".models.__init__") or m.dotted.startswith(package + ".models.")]
75
+ readme = os.path.join(directory, "README.md")
76
+ if os.path.isfile(readme):
77
+ app.readme = read(readme).strip()
78
+ return app
79
+
80
+
81
+ def is_config(node) -> bool:
82
+ """A class in `apps.py` deriving from AppConfig."""
83
+ return any(base.split(".")[-1] == "AppConfig" for base in bases(node))
84
+
85
+
86
+ def label_of(app: App) -> str:
87
+ """The label Django would use for the application: `AppConfig.label` when
88
+ one is set, the package's last segment otherwise. It is half of every
89
+ default table name, which is why it is read rather than assumed."""
90
+ module = app.module("apps")
91
+ if module is not None:
92
+ for node in module.classes():
93
+ if not is_config(node):
94
+ continue
95
+ for stmt in node.body:
96
+ if isinstance(stmt, ast.Assign) and len(stmt.targets) == 1 and dotted(stmt.targets[0]) == "label":
97
+ value = stmt.value
98
+ if isinstance(value, ast.Constant) and isinstance(value.value, str):
99
+ return value.value
100
+ return app.label