@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,192 @@
1
+ """The reader, held to two fixtures.
2
+
3
+ `testdata/billing` is a Django project with Celery in it, each shape this
4
+ plugin claims to read present once; `testdata/drift` is the tree where every
5
+ diagnostic the reader is meant to raise is raised once. `expected.json` beside
6
+ each is what it comes out as. Set UPDATE_GOLDEN=1 to write them again after a
7
+ deliberate change, and read the diff.
8
+ """
9
+
10
+ import json
11
+ import os
12
+ import sys
13
+ import unittest
14
+
15
+ sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
16
+ sys.path.insert(1, os.path.join(os.path.dirname(os.path.abspath(__file__)), "..", "pyplugin"))
17
+
18
+ import main # noqa: E402
19
+ from extract import extract # noqa: E402
20
+ from options import Options # noqa: E402
21
+ from protocol import Builder, Input # noqa: E402
22
+
23
+ HERE = os.path.dirname(os.path.abspath(__file__))
24
+ ROOT = os.path.abspath(os.path.join(HERE, "..", ".."))
25
+
26
+
27
+ def run(fixture, options):
28
+ b = Builder()
29
+ extract(
30
+ Input(root=os.path.relpath(os.path.join(HERE, "testdata", fixture), ROOT), commit="abc1234", generated_at="2026-09-05T00:00:00Z"),
31
+ Options.of(options),
32
+ b,
33
+ cwd=ROOT,
34
+ )
35
+ return {f.name: f.contents for f in b.files}, b.warnings
36
+
37
+
38
+ def golden(test, fixture, contents):
39
+ path = os.path.join(HERE, "testdata", fixture, "expected.json")
40
+ if os.environ.get("UPDATE_GOLDEN"):
41
+ with open(path, "w", encoding="utf-8") as handle:
42
+ handle.write(contents)
43
+ with open(path, "r", encoding="utf-8") as handle:
44
+ test.assertEqual(json.loads(handle.read()), json.loads(contents))
45
+
46
+
47
+ class Billing(unittest.TestCase):
48
+ def setUp(self):
49
+ self.files, self.warnings = run("billing", {"context": "shop", "service": "billing"})
50
+ self.fragment = json.loads(self.files["celery.json"])
51
+ self.service = self.fragment["contexts"][0]["services"][0]
52
+ self.channels = {c["address"]: c for c in self.service["channels"]}
53
+ self.flows = {f["slug"]: f for f in self.fragment["flows"]}
54
+
55
+ def test_the_fragment_it_reads(self):
56
+ golden(self, "billing", self.files["celery.json"])
57
+
58
+ def test_a_tree_where_everything_joins_reports_nothing(self):
59
+ self.assertEqual(self.warnings, [])
60
+
61
+ def test_a_queue_is_a_job_channel_with_a_send_and_a_receive_per_task(self):
62
+ self.assertEqual(sorted(self.channels), ["billing", "billing.mail", "billing.slow"])
63
+ mail = self.channels["billing.mail"]
64
+ self.assertEqual(mail["kind"], "job")
65
+ self.assertEqual([(m["name"], m["direction"]) for m in mail["messages"]], [("invoices.tasks.send_invoice_email", "send"), ("invoices.tasks.send_invoice_email", "receive")])
66
+ self.assertIn("over redis", mail["doc"])
67
+
68
+ def test_a_queue_is_decided_the_way_celery_decides_it(self):
69
+ receive = {c: [m for m in ch["messages"] if m["direction"] == "receive"][0]["doc"] for c, ch in self.channels.items()}
70
+ self.assertIn("routed by task_routes", receive["billing.mail"])
71
+ self.assertIn("named on the task", receive["billing.slow"])
72
+ self.assertIn("the default queue", receive["billing"])
73
+
74
+ def test_a_name_given_on_the_decorator_is_the_wire_name(self):
75
+ self.assertEqual(self.channels["billing"]["messages"][0]["name"], "billing.archive_invoice")
76
+
77
+ def test_an_enqueue_inside_on_commit_is_noted_and_a_countdown_too(self):
78
+ self.assertTrue(self.flows["billing-celery-send-invoice-email"]["steps"][0]["note"].startswith("Enqueued after the transaction commits."))
79
+ self.assertTrue(self.flows["billing-celery-remind-unpaid-invoice"]["steps"][0]["note"].startswith("Enqueued for later, with a countdown or an eta."))
80
+
81
+ def test_the_partial_form_and_the_module_import_resolve_to_the_task(self):
82
+ step = self.flows["billing-celery-archive-invoice"]["steps"][0]
83
+ self.assertTrue(step["line"].endswith("invoices/handlers.py:15"))
84
+ self.assertIn("Also enqueued at plugins/extract-celery/testdata/billing/invoices/services.py:23.", step["note"])
85
+
86
+ def test_the_clock_is_a_producer_and_opens_a_flow_of_its_own(self):
87
+ # Nothing in the code enqueues close_stale_drafts: add_periodic_task does.
88
+ flow = self.flows["billing-celery-close-stale-drafts"]
89
+ self.assertEqual([p["id"] for p in flow["participants"]], ["celery-beat", "shop.billing", "celery-billing"])
90
+ self.assertEqual([(s["from"], s["to"], s["label"]) for s in flow["steps"]], [("celery-beat", "celery-billing", "enqueue close_stale_drafts"), ("celery-billing", "shop.billing", "close_stale_drafts")])
91
+ self.assertEqual(flow["trigger"], {"kind": "scheduled", "label": "cron 0 3 * * *", "confidence": "high"})
92
+ self.assertTrue(flow["steps"][0]["line"].endswith("config/celery.py:22"))
93
+ self.assertTrue(flow["steps"][0]["note"].startswith("Scheduled cron 0 3 * * * (`close-stale-drafts`)."))
94
+ sent = [m for m in self.channels["billing"]["messages"] if m["name"] == "invoices.tasks.close_stale_drafts"]
95
+ self.assertEqual([m["direction"] for m in sent], ["send", "receive"])
96
+ self.assertTrue(sent[0]["doc"].startswith("Sent by Celery beat, cron 0 3 * * * (`close-stale-drafts`)."))
97
+ self.assertNotIn("Nothing in this tree enqueues it.", sent[1]["doc"])
98
+
99
+ def test_a_task_the_code_and_the_clock_both_enqueue_has_two_flows(self):
100
+ self.assertIn("billing-celery-remind-unpaid-invoice", self.flows)
101
+ beat = self.flows["billing-celery-remind-unpaid-invoice-beat"]
102
+ self.assertEqual(beat["trigger"]["label"], "every 6h")
103
+ self.assertEqual(beat["steps"][1]["handoff"], self.flows["billing-celery-remind-unpaid-invoice"]["steps"][1]["handoff"])
104
+
105
+ def test_the_settings_entries_are_read_too(self):
106
+ archive = self.flows["billing-celery-archive-invoice-beat"]
107
+ self.assertEqual(archive["participants"][2]["id"], "celery-billing")
108
+ self.assertEqual(archive["trigger"]["label"], "cron 30 2 * * *")
109
+ self.assertTrue(archive["source"].endswith("config/settings.py"))
110
+
111
+ def test_a_flow_goes_out_to_the_broker_and_comes_back(self):
112
+ flow = self.flows["billing-celery-send-invoice-email"]
113
+ self.assertEqual([p["id"] for p in flow["participants"]], ["shop.billing", "celery-billing-mail"])
114
+ self.assertEqual([(s["from"], s["to"], s["label"]) for s in flow["steps"]], [("shop.billing", "celery-billing-mail", "enqueue send_invoice_email"), ("celery-billing-mail", "shop.billing", "send_invoice_email")])
115
+ self.assertTrue(flow["steps"][1]["line"].endswith("invoices/tasks.py:13"))
116
+ self.assertEqual(flow["trigger"], {"kind": "job", "label": "Celery · billing.mail", "confidence": "high"})
117
+ self.assertEqual(
118
+ [(step["handoff"]["direction"], step["handoff"]["message"]) for step in flow["steps"]],
119
+ [("send", "invoices.tasks.send_invoice_email"), ("receive", "invoices.tasks.send_invoice_email")],
120
+ )
121
+ self.assertEqual(flow["steps"][1]["continuesAt"], "python:invoices.tasks:send_invoice_email")
122
+
123
+
124
+ class Drift(unittest.TestCase):
125
+ def setUp(self):
126
+ self.files, self.warnings = run("drift", {"context": "ops", "service": "reports"})
127
+ self.fragment = json.loads(self.files["celery.json"])
128
+ self.channels = {c["address"]: c for c in self.fragment["contexts"][0]["services"][0]["channels"]}
129
+
130
+ def test_the_fragment_it_reads(self):
131
+ golden(self, "drift", self.files["celery.json"])
132
+
133
+ def test_every_diagnostic_once(self):
134
+ self.assertEqual(
135
+ [(w.ref.split("/")[-1], w.message.split(",")[0].split(":")[0]) for w in self.warnings],
136
+ [
137
+ ("ops.reports", "settings module `config.settings` not found"),
138
+ ("ops.reports", "task_routes pattern `reports.tasks.*` matches no task in this tree"),
139
+ ("services.py:19", "`mail.tasks.send_receipt` is sent as a task no function in this tree declares; recorded as a send with no handler here"),
140
+ ("services.py:23", "`helper` is enqueued"),
141
+ ("celery.py:15", "beat entry `nightly` schedules `reports.tasks.nightly`"),
142
+ ("celery.py:16", "beat entry `rebuild` has a schedule this reader cannot read as syntax"),
143
+ ("tasks.py:19", "task `jobs.tasks.orphan` is declared"),
144
+ ],
145
+ )
146
+
147
+ def test_the_app_module_configures_the_queue_when_the_settings_are_missing(self):
148
+ self.assertEqual(sorted(self.channels), ["mail", "rebuild", "reports.default"])
149
+ self.assertIn("over amqp", self.channels["mail"]["doc"])
150
+
151
+ def test_a_chain_is_one_enqueue_per_signature_noted_as_such(self):
152
+ notes = [f["steps"][0]["note"] for f in self.fragment["flows"] if f["trigger"]["kind"] == "job"]
153
+ self.assertTrue(all(n.startswith("Enqueued in a chain.") for n in notes))
154
+ self.assertEqual(len(notes), 2)
155
+
156
+ def test_a_task_sent_by_name_to_another_tree_is_a_send_with_no_handler(self):
157
+ self.assertEqual([m["direction"] for m in self.channels["mail"]["messages"]], ["send"])
158
+ self.assertEqual([f["slug"] for f in self.fragment["flows"]], ["reports-celery-build-report", "reports-celery-build-report-beat", "reports-celery-deliver-report"])
159
+
160
+ def test_an_opaque_schedule_is_kept_as_written_and_the_entry_options_place_the_task(self):
161
+ beat = [f for f in self.fragment["flows"] if f["slug"] == "reports-celery-build-report-beat"][0]
162
+ self.assertEqual(beat["trigger"], {"kind": "scheduled", "label": "every_other_day()", "confidence": "high"})
163
+ # The code enqueues build_report on the default queue; the entry's
164
+ # options put the scheduled run on one of its own.
165
+ self.assertEqual(beat["participants"][2]["id"], "celery-rebuild")
166
+ self.assertEqual([m["direction"] for m in self.channels["rebuild"]["messages"]], ["send", "receive"])
167
+
168
+ def test_a_task_nothing_enqueues_is_still_worked(self):
169
+ orphan = [m for m in self.channels["reports.default"]["messages"] if m["name"] == "jobs.tasks.orphan"]
170
+ self.assertEqual([m["direction"] for m in orphan], ["receive"])
171
+ self.assertIn("Nothing in this tree enqueues it.", orphan[0]["doc"])
172
+
173
+
174
+ class Protocol(unittest.TestCase):
175
+ def test_describe_answers_with_the_options_schema(self):
176
+ out = json.loads(main.serve(json.dumps({"portolanVersion": "0.1.0", "kind": "describe"})))
177
+ self.assertEqual(out["files"], [])
178
+ self.assertEqual(out["describe"]["name"], "extract-celery")
179
+ self.assertEqual(out["describe"]["options"]["additionalProperties"], False)
180
+ self.assertIn("settings", out["describe"]["options"]["properties"])
181
+
182
+ def test_an_option_nobody_reads_is_refused_rather_than_dropped(self):
183
+ with self.assertRaises(ValueError):
184
+ Options.of({"context": "shop", "setings": "config.settings"})
185
+
186
+ def test_no_tasks_is_said_rather_than_an_empty_fragment_nobody_questions(self):
187
+ _, warnings = run("billing", {"context": "shop", "service": "billing", "source": "config"})
188
+ self.assertIn("no Celery tasks under", warnings[0].message)
189
+
190
+
191
+ if __name__ == "__main__":
192
+ unittest.main()
@@ -0,0 +1,66 @@
1
+ """portolan-extract-celery: a tree with Celery in it in, work queues and job
2
+ flows out.
3
+
4
+ The same protocol as every other extractor: one JSON request on stdin, one
5
+ JSON response on stdout, and a `describe` that answers with what the plugin is
6
+ and what it can be told. Python 3.9 or newer, and nothing but the standard
7
+ library: the project is never imported, and neither is Celery.
8
+ """
9
+
10
+ from __future__ import annotations
11
+
12
+ import json
13
+ import os
14
+ import sys
15
+
16
+ sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
17
+ sys.path.insert(1, os.path.join(os.path.dirname(os.path.abspath(__file__)), "..", "pyplugin"))
18
+
19
+ from extract import extract # noqa: E402
20
+ from options import Options # noqa: E402
21
+ from protocol import Builder, Input # noqa: E402
22
+
23
+ HERE = os.path.dirname(os.path.abspath(__file__))
24
+
25
+ DESCRIPTOR = {
26
+ "name": "extract-celery",
27
+ "summary": "Reads Celery task declarations, the calls that enqueue them and the routes that place them into work queues and source-backed job flows.",
28
+ "phases": ["extract"],
29
+ }
30
+
31
+
32
+ def descriptor():
33
+ with open(os.path.join(HERE, "options.schema.json"), "r", encoding="utf-8") as handle:
34
+ out = dict(DESCRIPTOR)
35
+ out["options"] = json.load(handle)
36
+ return out
37
+
38
+
39
+ def serve(raw: str) -> str:
40
+ request = json.loads(raw)
41
+ version = request.get("portolanVersion", "")
42
+ if version and version != "0.1.0":
43
+ raise ValueError("unsupported portolan protocol %r (plugin supports 0.1.0)" % version)
44
+ if request.get("kind") == "describe":
45
+ return json.dumps({"files": [], "describe": descriptor()})
46
+ input_ = Input.of(request.get("input"))
47
+ if not input_.root:
48
+ raise ValueError("no input root: an extractor has nothing to read")
49
+ builder = Builder()
50
+ extract(input_, Options.of(request.get("options")), builder)
51
+ for warning in builder.warnings:
52
+ sys.stderr.write("warning: %s%s\n" % ((warning.ref + ": ") if warning.ref else "", warning.message))
53
+ return json.dumps(builder.response(), indent=2)
54
+
55
+
56
+ def main() -> int:
57
+ try:
58
+ sys.stdout.write(serve(sys.stdin.read()))
59
+ except Exception as err: # the host reads a non-zero exit and the message
60
+ sys.stderr.write("portolan-extract-celery: %s\n" % err)
61
+ return 1
62
+ return 0
63
+
64
+
65
+ if __name__ == "__main__":
66
+ sys.exit(main())
@@ -0,0 +1,31 @@
1
+ """What the manifest tells the extractor: the things a tree with Celery in it
2
+ does not say about the estate it belongs to."""
3
+
4
+ from __future__ import annotations
5
+
6
+ from dataclasses import dataclass
7
+ from typing import Any
8
+
9
+ from protocol import read_options
10
+
11
+
12
+ @dataclass
13
+ class Options:
14
+ context: str = ""
15
+ service: str = ""
16
+ source: str = "."
17
+ settings: str = ""
18
+ out: str = "celery.json"
19
+
20
+ # The option as the manifest spells it, against the field that holds it.
21
+ KEYS = {
22
+ "context": "context",
23
+ "service": "service",
24
+ "source": "source",
25
+ "settings": "settings",
26
+ "out": "out",
27
+ }
28
+
29
+ @staticmethod
30
+ def of(raw: Any) -> "Options":
31
+ return read_options(Options(), Options.KEYS, raw)
@@ -0,0 +1,31 @@
1
+ {
2
+ "type": "object",
3
+ "additionalProperties": false,
4
+ "properties": {
5
+ "context": {
6
+ "type": "string",
7
+ "description": "Slug of the bounded context the service belongs to. Defaults to the name of the input directory.",
8
+ "examples": ["shop"]
9
+ },
10
+ "service": {
11
+ "type": "string",
12
+ "description": "Slug of the service that enqueues and works the tasks. Defaults to the name of the input directory.",
13
+ "examples": ["billing"]
14
+ },
15
+ "source": {
16
+ "type": "string",
17
+ "description": "The directory the tasks and their callers are looked for in, relative to the input root.",
18
+ "default": "."
19
+ },
20
+ "settings": {
21
+ "type": "string",
22
+ "description": "The Django settings module the Celery app is configured from, as an import spells it. Defaults to what manage.py sets DJANGO_SETTINGS_MODULE to.",
23
+ "examples": ["config.settings"]
24
+ },
25
+ "out": {
26
+ "type": "string",
27
+ "description": "Name of the fragment file, inside the step's output directory.",
28
+ "default": "celery.json"
29
+ }
30
+ }
31
+ }
@@ -0,0 +1,156 @@
1
+ """The calls that put a task on a queue.
2
+
3
+ `t.delay(...)` and `t.apply_async(...)` are the two ways a task is enqueued,
4
+ and `t.s(...).apply_async()` is the same call through a signature. A
5
+ `chain`, `group` or `chord` holds several signatures, each of which is an
6
+ enqueue and is recorded as one, noted with the canvas it sits in; the order
7
+ the canvas imposes is not drawn. `app.send_task("name")` enqueues by wire
8
+ name, for a task that may live in another tree. `transaction.on_commit(lambda:
9
+ t.delay(...))` - and the `partial` form - is the same enqueue with a note that
10
+ it waits for the commit, which is the one fact about *when* a message leaves
11
+ that the code states plainly.
12
+
13
+ Names are resolved by import and by file, so `tasks.send_invoice_email.delay`
14
+ and `send_invoice_email.delay` after `from .tasks import send_invoice_email`
15
+ land on the same function.
16
+ """
17
+
18
+ from __future__ import annotations
19
+
20
+ import ast
21
+ from dataclasses import dataclass
22
+ from typing import List, Optional, Tuple
23
+
24
+ from source import Module, Project, const_str, dotted, keyword, keyword_str
25
+ from celery_tasks import package_of
26
+
27
+ ENQUEUE = ("delay", "apply_async")
28
+ SIGNATURE = ("s", "si", "signature", "subtask")
29
+ CANVAS = {"chain": "in a chain", "group": "in a group", "chord": "in a chord"}
30
+ AFTER_COMMIT = "after the transaction commits"
31
+ LATER = "for later, with a countdown or an eta"
32
+
33
+
34
+ @dataclass
35
+ class Producer:
36
+ module: Module
37
+ node: ast.Call
38
+ key: Optional[Tuple[str, str]] # the task's, when the call resolves to one
39
+ wire: str # the name `send_task` was given, "" otherwise
40
+ label: str # the callee as written, last segment
41
+ queue: str # `queue=` at the call, "" when none
42
+ notes: List[str]
43
+
44
+ @property
45
+ def line(self) -> str:
46
+ return self.module.where(self.node)
47
+
48
+
49
+ def read_producers(project: Project) -> List[Producer]:
50
+ out: List[Producer] = []
51
+ for module in sorted(project.modules.values(), key=lambda m: m.rel):
52
+ Visitor(project, module, out).visit(module.tree)
53
+ return out
54
+
55
+
56
+ class Visitor(ast.NodeVisitor):
57
+ def __init__(self, project: Project, module: Module, out: List[Producer]):
58
+ self.project = project
59
+ self.module = module
60
+ self.out = out
61
+ self.pending: List[str] = [] # notes the enclosing calls add
62
+
63
+ def visit_Call(self, node: ast.Call) -> None:
64
+ name = dotted(node.func)
65
+ last = name.split(".")[-1]
66
+ if last == "on_commit":
67
+ self.pending.append(AFTER_COMMIT)
68
+ self.generic_visit(node)
69
+ self.pending.pop()
70
+ return
71
+ if last == "partial" and node.args and isinstance(node.args[0], ast.Attribute) and node.args[0].attr in ENQUEUE:
72
+ self.enqueue(node.args[0].value, node)
73
+ for arg in node.args[1:]:
74
+ self.visit(arg)
75
+ for kw in node.keywords:
76
+ self.visit(kw.value)
77
+ return
78
+ if last in ENQUEUE and isinstance(node.func, ast.Attribute):
79
+ self.enqueue(node.func.value, node)
80
+ self.generic_visit(node)
81
+ return
82
+ if last == "send_task" and node.args and const_str(node.args[0]):
83
+ wire = const_str(node.args[0])
84
+ self.out.append(Producer(self.module, node, None, wire, wire, keyword_str(node, "queue"), self.notes(node)))
85
+ self.generic_visit(node)
86
+ return
87
+ # A canvas called outright: `chain(a.s(), b.s())()`.
88
+ if isinstance(node.func, ast.Call) and dotted(node.func.func).split(".")[-1] in CANVAS:
89
+ self.enqueue(node.func, node)
90
+ return
91
+ self.generic_visit(node)
92
+
93
+ def enqueue(self, receiver: ast.AST, call: ast.Call, extra: str = "") -> None:
94
+ """`receiver` is what `.delay` or `.apply_async` was called on: the task,
95
+ a signature of it, or a canvas of signatures."""
96
+ if isinstance(receiver, ast.Call) and isinstance(receiver.func, ast.Attribute) and receiver.func.attr in SIGNATURE:
97
+ self.record(receiver.func.value, call, extra)
98
+ return
99
+ if isinstance(receiver, ast.Call) and dotted(receiver.func).split(".")[-1] in CANVAS:
100
+ how = CANVAS[dotted(receiver.func).split(".")[-1]]
101
+ for arg in flatten(receiver.args):
102
+ if isinstance(arg, ast.Call):
103
+ self.enqueue(arg, call, how)
104
+ return
105
+ self.record(receiver, call, extra)
106
+
107
+ def record(self, target: ast.AST, call: ast.Call, extra: str) -> None:
108
+ name = dotted(target)
109
+ if not name:
110
+ return
111
+ key = resolve(self.project, self.module, name)
112
+ self.out.append(Producer(self.module, call, key, "", name.split(".")[-1], keyword_str(call, "queue"), self.notes(call, extra)))
113
+
114
+ def notes(self, call: ast.Call, extra: str = "") -> List[str]:
115
+ out = list(self.pending)
116
+ if extra:
117
+ out.append(extra)
118
+ if keyword(call, "countdown") is not None or keyword(call, "eta") is not None:
119
+ out.append(LATER)
120
+ return out
121
+
122
+
123
+ def flatten(args: List[ast.AST]) -> List[ast.AST]:
124
+ out: List[ast.AST] = []
125
+ for arg in args:
126
+ if isinstance(arg, ast.Starred):
127
+ arg = arg.value
128
+ if isinstance(arg, (ast.List, ast.Tuple)):
129
+ out += arg.elts
130
+ else:
131
+ out.append(arg)
132
+ return out
133
+
134
+
135
+ def resolve(project: Project, module: Module, name: str) -> Optional[Tuple[str, str]]:
136
+ """`send_invoice_email` through the import that brought it here,
137
+ `tasks.send_invoice_email` through the module import, to (module, function).
138
+ None when the head of the name is nothing this module imported or defined."""
139
+ parts = name.split(".")
140
+ if len(parts) == 1:
141
+ hit = project.resolve(module, name)
142
+ if hit is None:
143
+ return None
144
+ target, local = hit
145
+ return package_of(target), local
146
+ head, rest = parts[0], parts[1:]
147
+ imported = module.imports.get(head)
148
+ if imported is None:
149
+ return None
150
+ if imported.name == "*":
151
+ # `import a.b` binds `a` and is used as `a.b.fn`; `import a.b as z` is `z.fn`.
152
+ base = [head] if imported.module == head or imported.module.startswith(head + ".") else imported.module.split(".")
153
+ else:
154
+ base = imported.module.split(".") + [imported.name]
155
+ full = base + rest
156
+ return ".".join(full[:-1]), full[-1]
@@ -0,0 +1,106 @@
1
+ """The order a queue is decided in, and the configuration it is read from."""
2
+
3
+ import os
4
+ import sys
5
+ import unittest
6
+
7
+ sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
8
+ sys.path.insert(1, os.path.join(os.path.dirname(os.path.abspath(__file__)), "..", "pyplugin"))
9
+
10
+ import celery_conf as conf # noqa: E402
11
+ import celery_conf as routes # noqa: E402
12
+ from source import Project # noqa: E402
13
+
14
+ HERE = os.path.dirname(os.path.abspath(__file__))
15
+
16
+
17
+ def project(fixture):
18
+ root = os.path.join(HERE, "testdata", fixture)
19
+ return Project(root, root, lambda p: os.path.relpath(p, root))
20
+
21
+
22
+ class Order(unittest.TestCase):
23
+ def setUp(self):
24
+ self.cfg = conf.Config(routes=[("app.tasks.send", "exact"), ("app.tasks.*", "glob")], default_queue="dflt")
25
+
26
+ def test_the_call_first_then_the_task_then_the_routes_then_the_default(self):
27
+ self.assertEqual(routes.queue_for("app.tasks.send", "here", "there", self.cfg), ("here", routes.AT_CALL))
28
+ self.assertEqual(routes.queue_for("app.tasks.send", "", "there", self.cfg), ("there", routes.ON_TASK))
29
+ self.assertEqual(routes.queue_for("app.tasks.send", "", "", self.cfg), ("exact", routes.BY_ROUTES))
30
+ self.assertEqual(routes.queue_for("app.tasks.other", "", "", self.cfg), ("glob", routes.BY_ROUTES))
31
+ self.assertEqual(routes.queue_for("elsewhere.task", "", "", self.cfg), ("dflt", routes.BY_DEFAULT))
32
+
33
+ def test_past_everything_celery_has_a_queue_of_its_own(self):
34
+ self.assertEqual(routes.queue_for("x", "", "", conf.Config()), ("celery", routes.BY_CELERY))
35
+
36
+ def test_a_route_that_names_no_task_is_found(self):
37
+ self.assertEqual(routes.unmatched(self.cfg, ["app.tasks.send"]), [])
38
+ self.assertEqual(routes.unmatched(self.cfg, ["other.task"]), ["app.tasks.send", "app.tasks.*"])
39
+
40
+
41
+ class Schedules(unittest.TestCase):
42
+ """A schedule object, as the sentence the page says."""
43
+
44
+ def text(self, source):
45
+ import ast
46
+
47
+ from source import Module
48
+
49
+ module = Module.__new__(Module)
50
+ module.tree = ast.parse("x = " + source)
51
+ return conf.schedule_text(module.tree.body[0].value, module)
52
+
53
+ def test_crontab_in_its_own_field_order_with_stars_for_what_is_not_said(self):
54
+ self.assertEqual(self.text("crontab(minute=0, hour='*/3')"), ("cron 0 */3 * * *", True))
55
+ self.assertEqual(self.text("crontab(hour=9, minute=30, day_of_week='mon-fri')"), ("cron 30 9 * * mon-fri", True))
56
+ self.assertEqual(self.text("crontab()"), ("cron * * * * *", True))
57
+
58
+ def test_an_interval_in_seconds_or_a_timedelta(self):
59
+ self.assertEqual(self.text("30.0"), ("every 30s", True))
60
+ self.assertEqual(self.text("90"), ("every 1m30s", True))
61
+ self.assertEqual(self.text("timedelta(hours=6)"), ("every 6h", True))
62
+ self.assertEqual(self.text("timedelta(days=1, hours=12)"), ("every 1d12h", True))
63
+ self.assertEqual(self.text("schedule(run_every=timedelta(minutes=5))"), ("every 5m", True))
64
+
65
+ def test_a_solar_event_and_anything_else_as_written(self):
66
+ self.assertEqual(self.text("solar('sunset', 59.9, 10.7)"), ("solar sunset", True))
67
+ self.assertEqual(self.text("crontab(minute=MINUTE)"), ("crontab(minute=MINUTE)", False))
68
+ self.assertEqual(self.text("every_other_day()"), ("every_other_day()", False))
69
+
70
+ def test_the_entries_are_read_off_the_settings_and_the_app_module(self):
71
+ cfg = conf.read_config(project("billing"), "")
72
+ self.assertEqual(
73
+ [(s.name, s.task or s.ref, s.when, s.queue, s.opaque) for s in cfg.schedules],
74
+ [
75
+ ("remind-unpaid-invoices", "invoices.tasks.remind_unpaid_invoice", "every 6h", "", False),
76
+ ("archive-closed-invoices", "billing.archive_invoice", "cron 30 2 * * *", "", False),
77
+ ("close-stale-drafts", "close_stale_drafts", "cron 0 3 * * *", "", False),
78
+ ],
79
+ )
80
+ self.assertFalse(cfg.beat_in_database)
81
+
82
+
83
+ class Reading(unittest.TestCase):
84
+ def test_the_settings_under_the_namespace_the_app_names(self):
85
+ cfg = conf.read_config(project("billing"), "")
86
+ self.assertEqual(cfg.settings, "config.settings")
87
+ self.assertTrue(cfg.settings_found)
88
+ self.assertEqual(cfg.routes, [("invoices.tasks.send_invoice_email", "billing.mail")])
89
+ self.assertEqual(cfg.default_queue, "billing")
90
+ self.assertEqual(cfg.broker_scheme, "redis")
91
+ self.assertEqual([a.name for a in cfg.apps], ["app"])
92
+
93
+ def test_the_app_module_over_settings_that_are_not_there(self):
94
+ cfg = conf.read_config(project("drift"), "")
95
+ self.assertFalse(cfg.settings_found)
96
+ self.assertEqual(cfg.routes, [("reports.tasks.*", "reports")])
97
+ self.assertEqual(cfg.default_queue, "reports.default")
98
+ self.assertEqual(cfg.broker_scheme, "amqp")
99
+
100
+ def test_an_environment_lookup_is_its_default_and_no_further(self):
101
+ cfg = conf.read_config(project("billing"), "")
102
+ self.assertEqual(cfg.broker, "redis://localhost:6379/0")
103
+
104
+
105
+ if __name__ == "__main__":
106
+ unittest.main()
@@ -0,0 +1,19 @@
1
+ package main
2
+
3
+ import (
4
+ _ "embed"
5
+
6
+ "github.com/shortlink-org/portolan/plugin"
7
+ )
8
+
9
+ //go:embed options.schema.json
10
+ var optionsSchema []byte
11
+
12
+ func descriptor() plugin.Descriptor {
13
+ return plugin.Descriptor{
14
+ Name: "extract-commands",
15
+ Summary: "Reads the make targets, npm scripts, just recipes and task-runner tasks a repository declares onto its service.",
16
+ Phases: []string{plugin.PhaseExtract},
17
+ Options: optionsSchema,
18
+ }
19
+ }
@@ -0,0 +1,11 @@
1
+ package main
2
+
3
+ import (
4
+ "testing"
5
+
6
+ "github.com/shortlink-org/portolan/plugin/schematest"
7
+ )
8
+
9
+ func TestOptionsSchemaMatchesStruct(t *testing.T) {
10
+ schematest.Check(t, optionsSchema, Options{})
11
+ }