@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,603 @@
1
+ // Runs the generators named in portolan.json.
2
+ //
3
+ // node scripts/gen.mjs write the output
4
+ // node scripts/gen.mjs --check fail if the output on disk is not what the
5
+ // generators produce now
6
+ //
7
+ // The check mode is what makes generated documentation reviewable: the files
8
+ // are committed, so a change to them shows up in a diff like any other, and CI
9
+ // refuses a commit where they no longer follow from the catalog.
10
+
11
+ import {
12
+ lstatSync,
13
+ readdirSync,
14
+ readFileSync,
15
+ rmSync,
16
+ statSync,
17
+ } from "node:fs";
18
+ import { execFileSync } from "node:child_process";
19
+ import { createHash } from "node:crypto";
20
+ import { join, relative, resolve } from "node:path";
21
+
22
+ import {
23
+ addBuildStep,
24
+ createBuildReport,
25
+ finishBuildReport,
26
+ writeBuildReport,
27
+ } from "./build-report.mjs";
28
+ import { loadCatalog } from "./catalog-sources.mjs";
29
+ import { loadManifest, stepKeys } from "./manifest.mjs";
30
+ import { runPlugin } from "./plugin-host.mjs";
31
+ import { vendoredCommit } from "./vendor-lock.mjs";
32
+ import {
33
+ removeOutputFile,
34
+ safeOutputPath,
35
+ writeOutputFile,
36
+ } from "./output-path.mjs";
37
+ import { builtinPlugin } from "./builtin-plugins.mjs";
38
+
39
+ const PORTOLAN_VERSION = "0.1.0";
40
+ const EVENTS = process.env.PORTOLAN_EVENTS === "1";
41
+ const EVENT_PREFIX = "::portolan-event::";
42
+
43
+ function event(value) {
44
+ if (EVENTS) process.stdout.write(`${EVENT_PREFIX}${JSON.stringify(value)}\n`);
45
+ }
46
+
47
+ // Every file a generator wrote last time is listed here, so a page that stops
48
+ // being generated is deleted instead of lingering as documentation of a service
49
+ // that no longer exists.
50
+ //
51
+ // The listing is keyed by step, not by directory. Two extractors writing
52
+ // fragments side by side into the same directory is the normal case - one knows
53
+ // the domain, the other the API - and a directory-wide list would have each of
54
+ // them delete the other's work on every run. What names a step is decided in
55
+ // manifest.mjs: its plugin, and the file it names when two steps of one plugin
56
+ // share a directory.
57
+ //
58
+ // A key that no step of this run answers to belonged to a step that was taken
59
+ // out of the manifest or renamed. Its files are as stale as any other, and they
60
+ // are removed once every step has run - not before, because a renamed step
61
+ // writes the same files under its new key, and a file removed and written back
62
+ // in one run is drift that never happened.
63
+ const MANIFEST = ".portolan-manifest";
64
+
65
+ const check = process.argv.includes("--check");
66
+ const manifestSha256 = createHash("sha256")
67
+ .update(readFileSync("portolan.json"))
68
+ .digest("hex");
69
+ const report = createBuildReport({
70
+ mode: check ? "check" : "write",
71
+ manifestSha256,
72
+ });
73
+ let manifest = {};
74
+ let keys = stepKeys({});
75
+ let drifted = false;
76
+
77
+ // What each step wrote into each directory on this run, by key. The listing
78
+ // on disk says what was written last time; in check mode it is never updated,
79
+ // so the sweep at the end reads this rather than the file.
80
+ const wroteThisRun = new Map();
81
+
82
+ // Directories already swept this run; a second pass would report the same
83
+ // removals again in check mode.
84
+ const swept = new Set();
85
+
86
+ try {
87
+ persistReport();
88
+ await generate();
89
+ finishBuildReport(report, drifted ? "drifted" : "ok");
90
+ persistReport();
91
+ event({ type: "run-finished", status: report.status, durationMs: report.durationMs });
92
+ if (drifted) {
93
+ console.error("\nGenerated documentation is out of date. Run `npm run gen`.");
94
+ process.exitCode = 1;
95
+ }
96
+ } catch (cause) {
97
+ finishBuildReport(report, "failed");
98
+ persistReport();
99
+ event({ type: "run-finished", status: "failed", durationMs: report.durationMs, message: cause instanceof Error ? cause.message : String(cause) });
100
+ console.error(`portolan gen: ${cause instanceof Error ? cause.message : String(cause)}`);
101
+ process.exitCode = 1;
102
+ }
103
+
104
+ async function generate() {
105
+ // Checked before anything runs, against the schema the plugins describe. A
106
+ // step with a misspelled option would otherwise run to completion and write a
107
+ // fragment missing whatever that option was for.
108
+ const loaded = loadManifest("portolan.json");
109
+ manifest = loaded.manifest;
110
+ if (loaded.problems.length > 0) {
111
+ throw new Error(
112
+ `portolan.json does not match schema/portolan.schema.json:\n${loaded.problems
113
+ .map((problem) => ` ${problem}`)
114
+ .join("\n")}`,
115
+ );
116
+ }
117
+ keys = stepKeys(manifest);
118
+ event({ type: "pipeline-ready", stepCount: (manifest.extract?.length ?? 0) + (manifest.verify?.length ?? 0) + (manifest.generate?.length ?? 0) });
119
+
120
+ // Extractors run first and write catalog fragments; only then is there a
121
+ // catalog for anything else to read.
122
+ for (const step of manifest.extract ?? []) {
123
+ const plugin = pluginNamed(step.plugin);
124
+ const stamp = stampFor(step.in, step.out);
125
+ await executeStep("extract", step, `${step.plugin} ← ${step.in}`, async () =>
126
+ runPlugin(plugin, {
127
+ portolanVersion: PORTOLAN_VERSION,
128
+ input: { root: step.in, output: step.out, commit: stamp.commit, generatedAt: stamp.generatedAt },
129
+ options: step.options ?? {},
130
+ }),
131
+ );
132
+ }
133
+
134
+ // Verifiers read observed evidence against the merged catalog while leaving
135
+ // their own previous output out of that evidence - and what a dropped
136
+ // extract step wrote, which is still on disk because the sweep runs once
137
+ // every step has written. Read into this merge, a service nobody extracts
138
+ // any more would be written back into an overlay, and the overlay would
139
+ // carry it into the generators and into the next run.
140
+ for (const step of manifest.verify ?? []) {
141
+ const plugin = pluginNamed(step.plugin);
142
+ const stamp = stampFor(step.in, step.out);
143
+ const own = (previous(step.out)[keys.keyOf(step)] ?? []).map((name) => join(step.out, name));
144
+ const { catalog } = await loadSources({ exclude: [...own, ...staleAll()] });
145
+ await executeStep("verify", step, `${step.plugin} ⇐ ${step.in}`, async () =>
146
+ runPlugin(plugin, {
147
+ portolanVersion: PORTOLAN_VERSION,
148
+ input: { root: step.in, output: step.out, commit: stamp.commit, generatedAt: stamp.generatedAt },
149
+ catalog,
150
+ options: step.options ?? {},
151
+ }),
152
+ );
153
+ }
154
+
155
+ // What a dropped extract step wrote is not part of the catalog, so it is
156
+ // swept before the catalog is read - not after, when the generators would
157
+ // already have documented it once more.
158
+ sweepAll();
159
+
160
+ const { catalog, sources, conflicts } = await loadSources();
161
+ console.log(
162
+ `catalog: ${sources.length} source${sources.length === 1 ? "" : "s"} — ${sources
163
+ .map((source) => `${source.path} @ ${source.commit || "?"}`)
164
+ .join(", ")}`,
165
+ );
166
+ for (const conflict of conflicts) {
167
+ console.warn(` conflict ${conflict.where}: ${conflict.message}`);
168
+ }
169
+
170
+ for (const step of manifest.generate ?? []) {
171
+ const plugin = pluginNamed(step.plugin);
172
+ const generatedCatalog = step.catalog
173
+ ? (await loadSources({ profile: step.catalog })).catalog
174
+ : catalog;
175
+ await executeStep("generate", step, `${step.plugin} → ${step.out}`, async () =>
176
+ runPlugin(plugin, {
177
+ portolanVersion: PORTOLAN_VERSION,
178
+ catalog: generatedCatalog,
179
+ options: step.options ?? {},
180
+ }),
181
+ );
182
+ }
183
+
184
+ sweepAll();
185
+ }
186
+
187
+ function sweepAll() {
188
+ for (const out of wroteThisRun.keys()) {
189
+ if (swept.has(out)) continue;
190
+ swept.add(out);
191
+ const changes = sweep(out, check);
192
+ if (changes.length > 0) {
193
+ drifted = summarise(`${out}: steps no longer in portolan.json`, [], changes) || drifted;
194
+ }
195
+ }
196
+ }
197
+
198
+ /** Every file the sweep would remove, in every directory a step writes into. */
199
+ function staleAll() {
200
+ const outs = new Set(
201
+ [...(manifest.extract ?? []), ...(manifest.verify ?? []), ...(manifest.generate ?? [])].map(
202
+ (step) => step.out,
203
+ ),
204
+ );
205
+ const stale = [];
206
+ for (const out of outs) {
207
+ for (const name of staleIn(out)) stale.push(join(out, name));
208
+ }
209
+ return stale;
210
+ }
211
+
212
+ /**
213
+ * What the steps no longer in the manifest wrote into a directory and no live
214
+ * step claims. A file a dead key listed and a live step wrote again this run
215
+ * is not stale - it was renamed, not dropped - and a live step that has not
216
+ * run yet is taken at its previous listing.
217
+ */
218
+ function staleIn(out) {
219
+ const listing = previous(out);
220
+ const live = keys.liveIn(out);
221
+ const dead = Object.keys(listing).filter((key) => !live.has(key));
222
+ if (dead.length === 0) return [];
223
+
224
+ const kept = new Set();
225
+ for (const key of live) {
226
+ for (const name of wroteThisRun.get(out)?.get(key) ?? listing[key] ?? []) kept.add(name);
227
+ }
228
+ const stale = [];
229
+ for (const key of dead) {
230
+ for (const name of listing[key]) {
231
+ if (kept.has(name)) continue;
232
+ kept.add(name);
233
+ stale.push(name);
234
+ }
235
+ }
236
+ return stale;
237
+ }
238
+
239
+ async function executeStep(phase, step, label, work) {
240
+ const startedAt = Date.now();
241
+ event({ type: "step-started", ordinal: report.steps.length, phase, plugin: step.plugin, input: step.in, output: step.out });
242
+ try {
243
+ const { files, warnings = [] } = await work();
244
+ const changes = apply(files, step.out, keys.keyOf(step), check);
245
+ const changed = summarise(label, files, changes, warnings);
246
+ drifted = changed || drifted;
247
+ const result = {
248
+ phase,
249
+ plugin: step.plugin,
250
+ ...(step.in ? { input: step.in } : {}),
251
+ output: step.out,
252
+ status: changed ? "drifted" : changes.length > 0 ? "written" : "up-to-date",
253
+ durationMs: Date.now() - startedAt,
254
+ fileCount: files.length,
255
+ changedCount: changes.length,
256
+ changes,
257
+ files: files.map((file) => join(step.out, file.name)),
258
+ // What the plugin could not read, in its own words. Already printed as
259
+ // it ran; kept here so the Settings page can list it beside the step.
260
+ warnings,
261
+ };
262
+ addBuildStep(report, result);
263
+ persistReport();
264
+ event({ type: "step-finished", ordinal: report.steps.length - 1, ...result });
265
+ } catch (cause) {
266
+ const result = {
267
+ phase,
268
+ plugin: step.plugin,
269
+ ...(step.in ? { input: step.in } : {}),
270
+ output: step.out,
271
+ status: "failed",
272
+ durationMs: Date.now() - startedAt,
273
+ fileCount: 0,
274
+ changedCount: 0,
275
+ changes: [],
276
+ files: [],
277
+ warnings: [],
278
+ };
279
+ addBuildStep(report, result);
280
+ persistReport();
281
+ event({ type: "step-finished", ordinal: report.steps.length - 1, ...result, message: cause instanceof Error ? cause.message : String(cause) });
282
+ throw cause;
283
+ }
284
+ }
285
+
286
+ function persistReport() {
287
+ try {
288
+ writeBuildReport(report);
289
+ } catch (cause) {
290
+ console.warn(`portolan gen: could not write build report: ${cause instanceof Error ? cause.message : String(cause)}`);
291
+ }
292
+ }
293
+
294
+ function pluginNamed(name) {
295
+ const plugin = (manifest.plugins ?? []).find((p) => p.name === name);
296
+ const shipped = builtinPlugin(name);
297
+ if (!plugin && !shipped) {
298
+ fail(`portolan.json: a step names plugin "${name}", which is not declared`);
299
+ }
300
+
301
+ return plugin ?? shipped;
302
+ }
303
+
304
+ /** Prints what a step did, and says whether it left the tree out of date. */
305
+ function summarise(label, files, changes, warnings = []) {
306
+ const said = warnings.length > 0 ? `, ${warnings.length} warning${warnings.length === 1 ? "" : "s"}` : "";
307
+ const summary = `${label}: ${files.length} file${files.length === 1 ? "" : "s"}${said}`;
308
+
309
+ if (changes.length === 0) {
310
+ console.log(`${summary}, up to date`);
311
+
312
+ return false;
313
+ }
314
+
315
+ if (!check) {
316
+ console.log(`${summary}, ${changes.length} written`);
317
+
318
+ return false;
319
+ }
320
+
321
+ console.error(`${summary}, ${changes.length} out of date:`);
322
+ for (const change of changes.slice(0, 20)) console.error(` ${change.kind.padEnd(8)} ${change.path}`);
323
+ if (changes.length > 20) console.error(` ... and ${changes.length - 20} more`);
324
+
325
+ return true;
326
+ }
327
+
328
+ /**
329
+ * When the source a fragment describes last changed, and at which commit
330
+ * (portolan.0002).
331
+ *
332
+ * The host works this out rather than the extractor, for two reasons. A plugin
333
+ * that reads a clock produces a different fragment on every run, which cannot
334
+ * be committed and cannot be checked; and a plugin that shells out to git is a
335
+ * plugin that can never be sandboxed. Stamped from the last commit to touch the
336
+ * directory, a fragment changes exactly when its subject does.
337
+ *
338
+ * The output is excluded from that history, and it has to be: a fragment
339
+ * written beside the code it describes is inside the directory it is stamped
340
+ * from, so committing one would move the stamp, which would make the fragment
341
+ * out of date, which would rewrite it - and `--check` would never come back
342
+ * clean two runs in a row.
343
+ */
344
+ function stampFor(root, out) {
345
+ // A copy of another repository is dated by the commit it is a copy OF, which
346
+ // the fetch step wrote down beside it. Read before git, and without touching
347
+ // git at all: the local history of a vendored directory says when somebody
348
+ // ran the fetch, which is a fact about this repository and not about the
349
+ // service the fragment describes.
350
+ //
351
+ // Short, like every other stamp, because a stamp is read rather than
352
+ // resolved. The full sha travels separately, in the pin the fetch also
353
+ // wrote, where a link is built from it.
354
+ const vendored = vendoredCommit(root);
355
+ if (vendored) {
356
+ // No date. Nothing here knows when that commit was made - the lock records
357
+ // what was fetched, not when it was authored - and a date invented from
358
+ // the local clock would make the merge call this the stalest source in the
359
+ // estate every time it ran.
360
+ return { commit: vendored.slice(0, 7), generatedAt: "" };
361
+ }
362
+
363
+ // The history read is the one the root lives in. A manifest pointed at a
364
+ // checkout elsewhere on the disk - the service being documented, not a copy
365
+ // of it vendored here - is stamped from that checkout's history, because the
366
+ // fragment describes that service and not the repository the manifest sits
367
+ // in. A root with no repository around it is stamped as uncommitted.
368
+ const repo = repositoryOf(root);
369
+ if (!repo) {
370
+ return { commit: "uncommitted", generatedAt: process.env.PORTOLAN_GENERATED_AT || new Date().toISOString() };
371
+ }
372
+
373
+ // A shallow clone has no history to read: the one commit that was fetched has
374
+ // no parent, so every path looks as though it changed there and every fragment
375
+ // is stamped with the checkout rather than with its subject. That is wrong
376
+ // quietly - the fragments regenerate, `--check` reports drift, and nothing
377
+ // says why - so it is refused here instead.
378
+ if (shallow(repo)) {
379
+ fail(
380
+ `${repo} is a shallow clone, where every path looks as though it changed in ` +
381
+ "the single commit that was fetched, so a fragment cannot be stamped " +
382
+ "with the commit it describes. Fetch the full history first " +
383
+ "(git fetch --unshallow, or actions/checkout with fetch-depth: 0).",
384
+ );
385
+ }
386
+
387
+ // Only an output INSIDE the root is excluded. An output beside it, or above
388
+ // it, is not in the root's history to begin with - and excluding a parent
389
+ // would exclude the root itself, leaving nothing to read and a stamp that
390
+ // moved on every commit.
391
+ const rootInRepo = relative(repo, resolve(root)) || ".";
392
+ const outInRepo = out ? relative(repo, resolve(out)) : "";
393
+ const inside = out && (outInRepo === rootInRepo || outInRepo.startsWith(`${rootInRepo.replace(/\/$/, "")}/`) || rootInRepo === ".");
394
+ const exclude = inside && outInRepo !== rootInRepo && !outInRepo.startsWith("..") ? [`:(exclude)${outInRepo}`] : [];
395
+
396
+ for (const args of [
397
+ ["log", "-1", "--format=%h %cI", "--", rootInRepo, ...exclude],
398
+ ["log", "-1", "--format=%h %cI"],
399
+ ]) {
400
+ const answer = git(repo, args);
401
+ if (!answer) continue;
402
+ const [commit, generatedAt] = answer.split(" ");
403
+ if (commit && generatedAt) return { commit, generatedAt };
404
+ }
405
+
406
+ return { commit: "uncommitted", generatedAt: process.env.PORTOLAN_GENERATED_AT || new Date().toISOString() };
407
+ }
408
+
409
+ /** Runs git in a repository and answers with its trimmed output, or "" when it refused. */
410
+ function git(repo, args) {
411
+ try {
412
+ return execFileSync("git", ["-C", repo, ...args], {
413
+ encoding: "utf8",
414
+ stdio: ["ignore", "pipe", "ignore"],
415
+ }).trim();
416
+ } catch {
417
+ // Not a repository, or no commit touches this path yet. git has already
418
+ // said so on its own stderr, which is not this run's log.
419
+ return "";
420
+ }
421
+ }
422
+
423
+ /** The working tree a directory belongs to, or "" when no repository holds it. */
424
+ function repositoryOf(dir) {
425
+ return git(dir, ["rev-parse", "--show-toplevel"]);
426
+ }
427
+
428
+ /** Whether the history this runs against is truncated. */
429
+ function shallow(repo) {
430
+ return git(repo, ["rev-parse", "--is-shallow-repository"]) === "true";
431
+ }
432
+
433
+ /** Reads, merges and validates every source the manifest names. */
434
+ async function loadSources(options = {}) {
435
+ try {
436
+ return await loadCatalog("portolan.json", options);
437
+ } catch (cause) {
438
+ fail(cause.message);
439
+ }
440
+ }
441
+
442
+ /**
443
+ * Writes what the plugin asked for, and removes what it no longer asks for.
444
+ * Returns a line per file that differs; in check mode nothing is touched.
445
+ */
446
+ function apply(files, out, key, checkOnly) {
447
+ const changes = [];
448
+ const written = new Set();
449
+ if (!wroteThisRun.has(out)) wroteThisRun.set(out, new Map());
450
+ wroteThisRun.get(out).set(key, written);
451
+
452
+ for (const file of files) {
453
+ const target = safeJoin(out, file.name);
454
+ written.add(file.name);
455
+
456
+ let current = null;
457
+ try {
458
+ current = readFileSync(target, "utf8");
459
+ } catch {
460
+ // Absent, which the comparison below reports as added.
461
+ }
462
+
463
+ if (current === file.contents) continue;
464
+
465
+ changes.push({ kind: current === null ? "added" : "changed", path: join(out, file.name) });
466
+ if (checkOnly) continue;
467
+
468
+ try {
469
+ writeOutputFile(out, file.name, file.contents);
470
+ } catch (cause) {
471
+ fail(cause.message);
472
+ }
473
+ }
474
+
475
+ const listing = previous(out);
476
+ const claimedByOthers = new Set(
477
+ Object.entries(listing)
478
+ .filter(([other]) => other !== key)
479
+ .flatMap(([, names]) => names),
480
+ );
481
+
482
+ for (const stale of (listing[key] ?? []).filter(
483
+ (name) => !written.has(name) && !claimedByOthers.has(name),
484
+ )) {
485
+ changes.push({ kind: "removed", path: join(out, stale) });
486
+ if (checkOnly) continue;
487
+
488
+ try {
489
+ removeOutputFile(out, stale);
490
+ } catch (cause) {
491
+ fail(cause.message);
492
+ }
493
+ }
494
+
495
+ if (!checkOnly) {
496
+ listing[key] = [...written].sort();
497
+ try {
498
+ writeOutputFile(out, MANIFEST, `${JSON.stringify(listing, null, 2)}\n`);
499
+ } catch (cause) {
500
+ fail(cause.message);
501
+ }
502
+ pruneEmptyDirs(out);
503
+ }
504
+
505
+ return changes;
506
+ }
507
+
508
+ /**
509
+ * Removes what the steps that are no longer in the manifest wrote into a
510
+ * directory, and forgets them. Runs after every step has written, so a file a
511
+ * dead key listed and a live step wrote again this run is kept - it was
512
+ * renamed, not dropped.
513
+ */
514
+ function sweep(out, checkOnly) {
515
+ const changes = [];
516
+ const listing = previous(out);
517
+ const live = keys.liveIn(out);
518
+ const dead = Object.keys(listing).filter((key) => !live.has(key));
519
+ if (dead.length === 0) return changes;
520
+
521
+ for (const stale of staleIn(out)) {
522
+ changes.push({ kind: "removed", path: join(out, stale) });
523
+ if (checkOnly) continue;
524
+
525
+ try {
526
+ removeOutputFile(out, stale);
527
+ } catch (cause) {
528
+ fail(cause.message);
529
+ }
530
+ }
531
+ for (const key of dead) delete listing[key];
532
+
533
+ if (!checkOnly) {
534
+ try {
535
+ writeOutputFile(out, MANIFEST, `${JSON.stringify(listing, null, 2)}\n`);
536
+ } catch (cause) {
537
+ fail(cause.message);
538
+ }
539
+ pruneEmptyDirs(out);
540
+ }
541
+
542
+ return changes;
543
+ }
544
+
545
+ /** What each step wrote into this directory last time. */
546
+ function previous(out) {
547
+ try {
548
+ const parsed = JSON.parse(readFileSync(join(out, MANIFEST), "utf8"));
549
+
550
+ return typeof parsed === "object" && parsed !== null ? parsed : {};
551
+ } catch {
552
+ return {};
553
+ }
554
+ }
555
+
556
+ /**
557
+ * Joins a plugin-supplied name onto the output directory, refusing anything
558
+ * that would land outside it.
559
+ *
560
+ * This is the whole of a plugin's authority over the tree. A wasm plugin cannot
561
+ * open a file at all; a process plugin very much can, and the names it hands
562
+ * back are still not allowed to point anywhere they like.
563
+ */
564
+ function safeJoin(out, name) {
565
+ try {
566
+ return safeOutputPath(out, name);
567
+ } catch (cause) {
568
+ fail(cause.message);
569
+ }
570
+ }
571
+
572
+ /** Removes directories left behind by files that are no longer generated. */
573
+ function pruneEmptyDirs(root) {
574
+ const walk = (dir) => {
575
+ let entries;
576
+ try {
577
+ entries = readdirSync(dir);
578
+ } catch {
579
+ return true;
580
+ }
581
+
582
+ let empty = true;
583
+ for (const entry of entries) {
584
+ const path = join(dir, entry);
585
+ if (lstatSync(path).isSymbolicLink()) {
586
+ empty = false;
587
+ } else if (statSync(path).isDirectory()) {
588
+ if (walk(path)) rmSync(path, { recursive: true, force: true });
589
+ else empty = false;
590
+ } else {
591
+ empty = false;
592
+ }
593
+ }
594
+
595
+ return empty;
596
+ };
597
+
598
+ walk(root);
599
+ }
600
+
601
+ function fail(message) {
602
+ throw new Error(message);
603
+ }
@@ -0,0 +1,66 @@
1
+ import { existsSync, readFileSync } from "node:fs";
2
+ import { glob } from "node:fs/promises";
3
+ import { dirname, join, normalize } from "node:path";
4
+ import { describe, expect, it } from "vitest";
5
+ import DOMPurify from "dompurify";
6
+
7
+ // Mermaid's parser loads its browser sanitizer even when it only parses. The
8
+ // Node test needs the two no-DOM hooks, while the grammar remains Mermaid's.
9
+ DOMPurify.addHook ??= () => {};
10
+ DOMPurify.sanitize ??= (value) => value;
11
+ const { default: mermaid } = await import("mermaid");
12
+
13
+ async function markdownFiles() {
14
+ const files = [];
15
+ for await (const file of glob("docs/**/*.md")) files.push(file);
16
+ return files.sort();
17
+ }
18
+
19
+ function explicitAnchors(text) {
20
+ return new Set([...text.matchAll(/<a id="([^"]+)"><\/a>/g)].map((match) => match[1]));
21
+ }
22
+
23
+ describe("generated documentation quality", () => {
24
+ it("has no broken relative files or explicit anchors", async () => {
25
+ const problems = [];
26
+ for (const file of await markdownFiles()) {
27
+ const text = readFileSync(file, "utf8");
28
+ for (const match of text.matchAll(/\]\(([^\s)]+)(?:\s+[^)]*)?\)/g)) {
29
+ const target = match[1];
30
+ if (/^[a-z][a-z0-9+.-]*:/i.test(target) || target.startsWith("/")) continue;
31
+ const [relative, anchor] = target.split("#", 2);
32
+ const destination = relative ? normalize(join(dirname(file), relative)) : file;
33
+ if (!existsSync(destination)) {
34
+ problems.push(`${file}: missing ${target}`);
35
+ } else if (anchor?.startsWith("type-") || anchor?.startsWith("event-") || anchor?.startsWith("relation-") || anchor?.startsWith("step-")) {
36
+ if (!explicitAnchors(readFileSync(destination, "utf8")).has(anchor)) problems.push(`${file}: missing #${anchor} in ${destination}`);
37
+ }
38
+ }
39
+ }
40
+ expect(problems).toEqual([]);
41
+ });
42
+
43
+ it("parses every generated Mermaid block and standalone diagram", async () => {
44
+ const diagrams = [];
45
+ for (const file of await markdownFiles()) {
46
+ const text = readFileSync(file, "utf8");
47
+ for (const match of text.matchAll(/```mermaid\n([\s\S]*?)```/g)) diagrams.push([file, match[1]]);
48
+ }
49
+ for await (const file of glob("exports/mermaid/*.mmd")) diagrams.push([file, readFileSync(file, "utf8")]);
50
+ expect(diagrams.length).toBeGreaterThan(0);
51
+ for (const [file, diagram] of diagrams) {
52
+ try { await mermaid.parse(diagram); } catch (cause) { throw new Error(`${file}: ${cause instanceof Error ? cause.message : String(cause)}`); }
53
+ }
54
+ });
55
+
56
+ it("keeps the standalone Mermaid bundle identical to flow pages", async () => {
57
+ const mismatches = [];
58
+ for await (const file of glob("exports/mermaid/*.mmd")) {
59
+ const slug = file.split("/").at(-1).replace(/\.mmd$/, "");
60
+ const page = readFileSync(`docs/flows/${slug}.md`, "utf8");
61
+ const embedded = /```mermaid\n([\s\S]*?)```/.exec(page)?.[1];
62
+ if (embedded !== readFileSync(file, "utf8")) mismatches.push(slug);
63
+ }
64
+ expect(mismatches).toEqual([]);
65
+ });
66
+ });