@shortlink-org/portolan 0.2.4 → 0.4.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 (291) hide show
  1. package/README.md +174 -6
  2. package/catalog/enum_test.go +46 -0
  3. package/catalog/evidence_test.go +35 -0
  4. package/catalog/model.go +1213 -0
  5. package/catalog/roundtrip_test.go +210 -0
  6. package/catalog/via_test.go +38 -0
  7. package/cli/init.test.mjs +6 -1
  8. package/cli/portolan.mjs +14 -1
  9. package/cli/portolan.test.mjs +49 -0
  10. package/go.mod +21 -0
  11. package/go.sum +34 -0
  12. package/internal/gocall/README.md +19 -0
  13. package/internal/gocall/analyze.go +189 -0
  14. package/internal/gocall/analyze_test.go +107 -0
  15. package/internal/gohttp/analyze.go +2562 -0
  16. package/internal/gohttp/destination.go +373 -0
  17. package/internal/gohttp/endpoints.go +1067 -0
  18. package/internal/gohttp/roots.go +320 -0
  19. package/internal/gohttp/typed.go +96 -0
  20. package/internal/goscan/constants.go +85 -0
  21. package/internal/goscan/goscan_test.go +227 -0
  22. package/internal/goscan/index.go +629 -0
  23. package/internal/goscan/index_test.go +66 -0
  24. package/internal/goscan/names.go +52 -0
  25. package/internal/goscan/parse_test.go +11 -0
  26. package/internal/goscan/source.go +37 -0
  27. package/internal/goscan/tree.go +284 -0
  28. package/internal/goscan/types.go +99 -0
  29. package/internal/wsdl/ids.go +127 -0
  30. package/internal/wsdl/ids_test.go +21 -0
  31. package/internal/wsdl/model.go +70 -0
  32. package/internal/wsdl/parse.go +949 -0
  33. package/internal/wsdl/parse_test.go +170 -0
  34. package/package.json +28 -15
  35. package/plugin/describe.go +156 -0
  36. package/plugin/describe_test.go +114 -0
  37. package/plugin/protocol.go +141 -0
  38. package/plugin/schematest/schematest.go +126 -0
  39. package/plugins/README.md +404 -50
  40. package/plugins/cmd/portolan-http-clients/main.go +19 -0
  41. package/plugins/extract-argocd/options.schema.json +44 -0
  42. package/plugins/extract-celery/extract.py +0 -2
  43. package/plugins/extract-celery/extract_test.py +1 -1
  44. package/plugins/extract-celery/main.py +1 -0
  45. package/plugins/extract-csharp-ddd/README.md +213 -0
  46. package/plugins/extract-csharp-ddd/options.schema.json +55 -0
  47. package/plugins/extract-django/README.md +52 -19
  48. package/plugins/extract-django/choices.py +47 -0
  49. package/plugins/extract-django/domain.py +30 -18
  50. package/plugins/extract-django/extract.py +21 -7
  51. package/plugins/extract-django/extract_test.py +68 -2
  52. package/plugins/extract-django/lifecycle.py +4 -28
  53. package/plugins/extract-django/main.py +1 -0
  54. package/plugins/extract-django/operations.py +1 -1
  55. package/plugins/extract-django/routing_test.py +109 -1
  56. package/plugins/extract-django/rules.py +154 -0
  57. package/plugins/extract-django/rules_test.py +158 -0
  58. package/plugins/extract-django/store.py +1 -1
  59. package/plugins/extract-django/transport.py +101 -55
  60. package/plugins/extract-django/verbs.py +241 -0
  61. package/plugins/extract-go/README.md +47 -0
  62. package/plugins/extract-go-sqs/options.schema.json +21 -0
  63. package/plugins/extract-http-clients/describe.go +20 -0
  64. package/plugins/extract-http-clients/describe_test.go +11 -0
  65. package/plugins/extract-http-clients/extract.go +740 -0
  66. package/plugins/extract-http-clients/extract_test.go +1561 -0
  67. package/plugins/extract-http-clients/main.go +41 -0
  68. package/plugins/extract-java/build/org/portolan/extract/Extract.class +0 -0
  69. package/plugins/extract-java/build/org/portolan/extract/Main.class +0 -0
  70. package/plugins/extract-java/build/org/portolan/extract/Protocol$Builder.class +0 -0
  71. package/plugins/extract-java/build/org/portolan/extract/Protocol$Input.class +0 -0
  72. package/plugins/extract-java/build/org/portolan/extract/Protocol$Options.class +0 -0
  73. package/plugins/extract-k8s/options.schema.json +32 -0
  74. package/plugins/extract-laravel/Cargo.lock +962 -0
  75. package/plugins/extract-laravel/Cargo.toml +20 -0
  76. package/plugins/extract-laravel/README.md +200 -0
  77. package/plugins/extract-laravel/options.schema.json +68 -0
  78. package/plugins/extract-laravel/rustfmt.toml +1 -0
  79. package/plugins/extract-php-ddd/Cargo.lock +972 -0
  80. package/plugins/extract-php-ddd/Cargo.toml +22 -0
  81. package/plugins/extract-php-ddd/README.md +141 -0
  82. package/plugins/extract-php-ddd/options.schema.json +50 -0
  83. package/plugins/extract-php-ddd/rustfmt.toml +1 -0
  84. package/plugins/extract-python-kafka/extract.py +0 -2
  85. package/plugins/extract-python-kafka/extract_test.py +1 -1
  86. package/plugins/extract-python-kafka/main.py +1 -0
  87. package/plugins/extract-terraform/options.schema.json +26 -0
  88. package/plugins/extract-ts/extract.test.ts +2 -2
  89. package/plugins/extract-ts/extract.ts +4 -5
  90. package/plugins/extract-ts/graphql.test.ts +1 -1
  91. package/plugins/extract-ts/main.ts +1 -0
  92. package/plugins/openapi/ids.go +261 -0
  93. package/plugins/openapi/ids_test.go +98 -0
  94. package/plugins/phpscan/Cargo.lock +953 -0
  95. package/plugins/phpscan/Cargo.toml +19 -0
  96. package/plugins/phpscan/rustfmt.toml +1 -0
  97. package/plugins/portolan-go.wasm +0 -0
  98. package/plugins/pyplugin/catalog.py +24 -1
  99. package/plugins/pyplugin/protocol.py +1 -5
  100. package/plugins/verify-otel/options.schema.json +12 -0
  101. package/portolan.json +132 -4
  102. package/rules/builtin.json +218 -0
  103. package/schema/portolan.schema.json +905 -4
  104. package/scripts/README.md +21 -13
  105. package/scripts/catalog-sources.mjs +6 -0
  106. package/scripts/delivery-presets.mjs +57 -14
  107. package/scripts/diff.mjs +5 -1
  108. package/scripts/django-aggregates.test.mjs +58 -0
  109. package/scripts/gen-likec4.mjs +150 -17
  110. package/scripts/gen-likec4.test.mjs +96 -0
  111. package/scripts/gen.mjs +148 -118
  112. package/scripts/gitops-example.test.mjs +108 -0
  113. package/scripts/go-discovery.test.mjs +30 -0
  114. package/scripts/history.mjs +186 -3
  115. package/scripts/history.test.mjs +1 -1
  116. package/scripts/host-plugins/fetch-argocd.mjs +338 -0
  117. package/scripts/host-plugins/fetch-argocd.options.json +49 -0
  118. package/scripts/host-plugins/fetch-argocd.test.mjs +274 -0
  119. package/scripts/host-plugins/fetch-bsr.mjs +1 -0
  120. package/scripts/host-plugins/fetch-csr.mjs +1 -0
  121. package/scripts/host-plugins/fetch-git.mjs +78 -21
  122. package/scripts/host-plugins/fetch-git.test.mjs +62 -8
  123. package/scripts/host-plugins/fetch-k8s.mjs +263 -0
  124. package/scripts/host-plugins/fetch-k8s.options.json +50 -0
  125. package/scripts/host-plugins/fetch-k8s.test.mjs +259 -0
  126. package/scripts/host-plugins/k8s-topology.mjs +183 -0
  127. package/scripts/json-format.mjs +192 -0
  128. package/scripts/json-format.test.mjs +97 -0
  129. package/scripts/local-api.mjs +287 -12
  130. package/scripts/local-api.test.mjs +150 -5
  131. package/scripts/local-discovery.mjs +94 -9
  132. package/scripts/manifest.mjs +14 -3
  133. package/scripts/manifest.test.mjs +24 -0
  134. package/scripts/output-diff.mjs +94 -0
  135. package/scripts/output-diff.test.mjs +36 -0
  136. package/scripts/package-smoke.mjs +62 -4
  137. package/scripts/plugin-host.mjs +45 -3
  138. package/scripts/plugin-host.test.mjs +9 -0
  139. package/scripts/plugin-wasm-worker.mjs +4 -1
  140. package/scripts/plugins-fresh.mjs +97 -0
  141. package/scripts/plugins-fresh.test.mjs +64 -0
  142. package/scripts/provenance.mjs +72 -0
  143. package/scripts/provenance.test.mjs +149 -0
  144. package/scripts/run-builtin.mjs +62 -7
  145. package/scripts/schema.mjs +167 -19
  146. package/scripts/trace-trials.mjs +176 -0
  147. package/scripts/trace-trials.test.mjs +142 -0
  148. package/scripts/warning-policy.mjs +167 -0
  149. package/scripts/warning-policy.test.mjs +93 -0
  150. package/src/app/Breadcrumbs.test.ts +3 -0
  151. package/src/app/Breadcrumbs.tsx +3 -0
  152. package/src/app/CatalogApp.tsx +2 -0
  153. package/src/app/Sidebar.tsx +3 -3
  154. package/src/app/SidebarFooter.tsx +20 -4
  155. package/src/catalog-docs.test.ts +64 -0
  156. package/src/catalog-docs.ts +35 -0
  157. package/src/catalog-error.test.ts +15 -0
  158. package/src/catalog-index.ts +25 -0
  159. package/src/catalog-model.ts +293 -5
  160. package/src/catalog-validation.ts +113 -2
  161. package/src/catalog.test.ts +40 -1
  162. package/src/chat/Starter.tsx +5 -11
  163. package/src/chat/tools.test.ts +27 -0
  164. package/src/chat/tools.ts +5 -9
  165. package/src/components/CatalogStamp.tsx +10 -8
  166. package/src/components/ChannelRows.test.tsx +76 -1
  167. package/src/components/ChannelRows.tsx +223 -60
  168. package/src/components/DeploymentRows.tsx +131 -0
  169. package/src/components/DocsLinks.test.tsx +27 -0
  170. package/src/components/DocsLinks.tsx +56 -0
  171. package/src/components/FieldTree.tsx +5 -2
  172. package/src/components/HTTPDestinationEvidence.test.tsx +23 -0
  173. package/src/components/HTTPDestinationEvidence.tsx +31 -0
  174. package/src/components/Integrations.tsx +1 -1
  175. package/src/components/MachineDocs.tsx +6 -5
  176. package/src/components/MethodRows.tsx +9 -2
  177. package/src/components/PluginIcon.tsx +77 -0
  178. package/src/components/ProblemRow.tsx +173 -153
  179. package/src/components/RelationEvidence.test.tsx +14 -0
  180. package/src/components/RelationEvidence.tsx +53 -0
  181. package/src/components/RuleMarks.tsx +22 -0
  182. package/src/components/ShapeRows.tsx +24 -20
  183. package/src/data.ts +46 -7
  184. package/src/enrich.test.ts +459 -4
  185. package/src/enrich.ts +308 -7
  186. package/src/er/ErCanvas.tsx +218 -12
  187. package/src/er/GroupNode.tsx +57 -0
  188. package/src/er/StoreHeader.tsx +1 -0
  189. package/src/er/layout.test.ts +85 -2
  190. package/src/er/layout.ts +140 -5
  191. package/src/er/spec.test.ts +17 -0
  192. package/src/er/spec.ts +23 -10
  193. package/src/flow/Recordings.test.tsx +52 -0
  194. package/src/flow/Recordings.tsx +236 -0
  195. package/src/flow/StepDetail.tsx +59 -0
  196. package/src/flow/TraceTrial.tsx +419 -0
  197. package/src/flow/evidence.test.ts +16 -0
  198. package/src/flow/evidence.ts +34 -0
  199. package/src/flow/examples.test.ts +33 -0
  200. package/src/flow/examples.ts +37 -0
  201. package/src/flow/outline.test.ts +28 -0
  202. package/src/flow/outline.ts +7 -2
  203. package/src/flow/trace-trial-resume.test.ts +42 -0
  204. package/src/flow/trace-trial-resume.ts +74 -0
  205. package/src/graph/elk.ts +78 -0
  206. package/src/index.css +68 -0
  207. package/src/landing/DraggableReveal.tsx +3 -2
  208. package/src/landing/EvidencePipeline.tsx +105 -0
  209. package/src/landing/LandingPage.tsx +17 -70
  210. package/src/landing/ProductTour.tsx +6 -6
  211. package/src/lib/all-problems.ts +27 -17
  212. package/src/lib/catalog-diff.ts +1 -1
  213. package/src/lib/centrality.test.ts +251 -0
  214. package/src/lib/centrality.ts +232 -0
  215. package/src/lib/confluence.test.ts +37 -0
  216. package/src/lib/confluence.ts +41 -0
  217. package/src/lib/context-color.ts +1 -1
  218. package/src/lib/deployment-drift.ts +22 -0
  219. package/src/lib/derive.ts +43 -69
  220. package/src/lib/django-aggregates.d.mts +9 -0
  221. package/src/lib/django-aggregates.mjs +36 -0
  222. package/src/lib/django-aggregates.test.ts +29 -0
  223. package/src/lib/django-aggregates.ts +5 -0
  224. package/src/lib/environments.test.ts +83 -0
  225. package/src/lib/environments.ts +57 -0
  226. package/src/lib/integration-url.test.ts +30 -0
  227. package/src/lib/integration-url.ts +63 -0
  228. package/src/lib/kafka-ui.ts +3 -45
  229. package/src/lib/local-api.ts +116 -4
  230. package/src/lib/notion.ts +13 -0
  231. package/src/lib/plugin-index.json +2765 -0
  232. package/src/lib/plugins.test.ts +68 -0
  233. package/src/lib/plugins.ts +264 -0
  234. package/src/lib/problem-flows.test.ts +61 -0
  235. package/src/lib/problem-flows.ts +78 -0
  236. package/src/lib/problem-rules-cel.d.mts +43 -0
  237. package/src/lib/problem-rules-cel.mjs +407 -0
  238. package/src/lib/problem-rules.test.ts +287 -0
  239. package/src/lib/problem-rules.ts +271 -0
  240. package/src/lib/problem-subjects.ts +737 -0
  241. package/src/lib/rule-entries.ts +39 -0
  242. package/src/lib/{data-problems.test.ts → rules-data.test.ts} +18 -16
  243. package/src/lib/rules-deploy.test.ts +133 -0
  244. package/src/lib/{problems.test.ts → rules-edges.test.ts} +8 -3
  245. package/src/lib/{proto-problems.test.ts → rules-proto.test.ts} +9 -25
  246. package/src/lib/{wire-problems.test.ts → rules-wire.test.ts} +13 -11
  247. package/src/lib/rules.test.ts +51 -0
  248. package/src/lib/rules.ts +86 -0
  249. package/src/lib/setup-info.test.ts +17 -0
  250. package/src/lib/setup-info.ts +58 -0
  251. package/src/lib/shape.test.ts +32 -0
  252. package/src/lib/shape.ts +30 -6
  253. package/src/lib/tech.ts +16 -0
  254. package/src/lib/trace-project.test.ts +34 -0
  255. package/src/lib/trace-project.ts +50 -0
  256. package/src/lib/use-problems.ts +23 -0
  257. package/src/lib/warnings.test.ts +63 -0
  258. package/src/lib/warnings.ts +260 -0
  259. package/src/likec4/ids.test.ts +6 -2
  260. package/src/likec4/ids.ts +43 -0
  261. package/src/main.tsx +23 -0
  262. package/src/map/ContextMapGraph.tsx +76 -32
  263. package/src/merge-deployments.test.ts +127 -0
  264. package/src/merge.test.ts +82 -0
  265. package/src/merge.ts +189 -18
  266. package/src/pages/AggregatePage.tsx +65 -14
  267. package/src/pages/ContextMap.tsx +45 -3
  268. package/src/pages/ContextPage.tsx +8 -5
  269. package/src/pages/EventPage.tsx +15 -5
  270. package/src/pages/FlowDetail.tsx +23 -2
  271. package/src/pages/GraphPage.tsx +40 -3
  272. package/src/pages/Overview.tsx +152 -12
  273. package/src/pages/PluginIndex.tsx +190 -0
  274. package/src/pages/Problems.tsx +396 -128
  275. package/src/pages/ServicePage.tsx +62 -5
  276. package/src/pages/Settings.tsx +217 -43
  277. package/src/pages/settings/AboutSettings.tsx +8 -1
  278. package/src/pages/settings/DjangoAggregateChoices.tsx +79 -0
  279. package/src/pages/settings/IntegrationsSettings.tsx +63 -17
  280. package/src/pages/settings/RecordingSettings.tsx +138 -0
  281. package/src/pages/settings/RulesSettings.tsx +825 -0
  282. package/src/routes.test.ts +9 -0
  283. package/src/routes.ts +22 -1
  284. package/src/selection/DetailPanel.tsx +15 -0
  285. package/src/virtual-provenance.d.ts +11 -0
  286. package/vite.config.ts +5 -0
  287. package/scripts/vendor-lock.mjs +0 -58
  288. package/scripts/vendor-lock.test.mjs +0 -69
  289. package/src/lib/data-problems.ts +0 -314
  290. package/src/lib/proto-problems.ts +0 -237
  291. package/src/lib/wire-problems.ts +0 -342
package/README.md CHANGED
@@ -87,8 +87,9 @@ reference).
87
87
  - **Diagrams** — LikeC4 C4 views (estate landscape, every container in the
88
88
  estate with its technology and the protocol on each edge, one per context,
89
89
  two per service, one dynamic view per flow), an ELK-routed dependency graph,
90
- and a context map. The app never draws these itself; `npm run likec4:gen`
91
- writes the model from the catalog.
90
+ and a context map. The app never draws these itself; `npm run gen` writes
91
+ the model from the catalog as its last step, and holds it to the catalog in
92
+ `gen:check`, and `npm run likec4:gen` writes it on its own before `dev`.
92
93
  - **ER canvases** — per store: tables, views, keys and crow's feet, plus column
93
94
  lineage (`from`) drawn dashed; hovering a column lights the whole chain back
94
95
  to where the value came from.
@@ -116,7 +117,43 @@ first:
116
117
  with a second writer, a table that no longer holds the aggregate it claims,
117
118
  a column whose type has drifted from its field's, an outbox with no payload;
118
119
  - a channel with a second publisher, an event on a channel its service does not
119
- declare, a declared channel no event names, a subscription nothing publishes.
120
+ declare, a declared channel no event names, a subscription nothing publishes;
121
+ - what the deployer runs from a place no service lives at, and where the GitOps
122
+ tree and the deployer disagree.
123
+
124
+ Each check is a rule in `rules/builtin.json`: a passport - id, severity, what
125
+ it looks for and what to do about a row - and the rule itself, in CEL over one
126
+ subject. A subject is one row with every fact a rule would otherwise have to
127
+ look up already on it - a table knows who writes it, a channel knows who else
128
+ publishes there, a call knows whether its peer is in the estate - so a rule is
129
+ one line a reader can read, copy and tighten. **Settings → Rules** lists them
130
+ with the rows each produces now. A shipped rule can be switched off or
131
+ re-graded in `portolan.json`, with a reason, and a rule of your own is written
132
+ there the same way - over a service, a call, a copy, an event, a consumer, a
133
+ channel, a subscription, a table, a column, a deployment, a flow or an
134
+ aggregate - and runs in the page the moment it is saved:
135
+
136
+ ```json
137
+ {
138
+ "problemRules": [
139
+ { "id": "shared-store", "enabled": false, "reason": "the estate shares one database by design" },
140
+ {
141
+ "id": "team.quiet-event",
142
+ "over": "event",
143
+ "severity": "warning",
144
+ "title": "Event nobody consumes",
145
+ "when": "size(event.consumers) == 0 && !event.name.endsWith('Audit')",
146
+ "message": "'nothing consumes ' + event.id",
147
+ "peer": "event.service"
148
+ }
149
+ ]
150
+ }
151
+ ```
152
+
153
+ Expressions are type-checked against the subject's fields when the manifest is
154
+ read - `event.nme` is refused, not shown as an empty page - and again in the
155
+ page, by the same module. The Rules page writes the entry after a preview of
156
+ the rows it would add (portolan.0016, portolan.0017).
120
157
 
121
158
  ## Where the facts come from
122
159
 
@@ -125,17 +162,47 @@ Plugins, one JSON message in and one out (`plugins/README.md`), declared in
125
162
 
126
163
  | phase | plugins |
127
164
  | --- | --- |
128
- | extract | `extract-project`, `extract-go`, `extract-ts`, `extract-rust`, `extract-java`, `extract-django`, `extract-celery`, `extract-python-kafka`, `extract-openapi`, `extract-wsdl`, `extract-http-clients`, `extract-redis`, `extract-asyncapi`, `extract-graphql`, `extract-proto`, `extract-river`, `extract-watermill`, `extract-go-nats`, `extract-csr`, `extract-sql`, `extract-flows`, `extract-adr`, `extract-glossary`, `extract-commands` |
165
+ | extract | `extract-project`, `extract-go`, `extract-ts`, `extract-rust`, `extract-java`, `extract-django`, `extract-laravel`, `extract-php-ddd`, `extract-csharp-ddd`, `extract-celery`, `extract-python-kafka`, `extract-openapi`, `extract-wsdl`, `extract-http-clients`, `extract-redis`, `extract-asyncapi`, `extract-graphql`, `extract-proto`, `extract-river`, `extract-watermill`, `extract-go-nats`, `extract-go-sqs`, `extract-csr`, `extract-sql`, `extract-flows`, `extract-adr`, `extract-glossary`, `extract-commands`, `extract-k8s`, `extract-argocd` |
129
166
  | verify | `verify-otel` — reads traces, marks the hops they show as `verified`; `verify-codeowners` — reads CODEOWNERS, says who to ask about each service |
130
167
  | generate | `gen-markdown` — `docs/`, `gen-mermaid` — standalone flow diagrams, `gen-backstage` — Backstage entities |
131
168
 
132
169
  `fetch-git`, `fetch-bsr` and `fetch-csr` bring in sources from other
133
170
  repositories, the Buf Schema Registry and a Confluent Schema Registry, against a
134
- lock, so a later build can reproduce them without a socket.
171
+ lock, so a later build can reproduce them without a socket. `fetch-k8s` reads a
172
+ live cluster through `kubectl` into the names each service answers on and
173
+ dials, and replays the committed fragment when there is no cluster to ask.
174
+ `fetch-argocd` reads the applications an Argo CD server manages into a snapshot
175
+ of where each service runs - environment, cluster, namespace, revision, images -
176
+ against the same kind of lock; the service page shows it under "Where it runs".
135
177
 
136
178
  Each plugin describes its own options; `npm run schema` asks all of them and
137
179
  composes `schema/portolan.schema.json`, which editors complete against and `gen`
138
- checks before running anything.
180
+ checks before running anything. The same answers are rendered as the site's
181
+ plugin reference - `/plugins` on any generated site, grouped by what each reads
182
+ or makes, with the options it takes - so the list above is the short form.
183
+
184
+ Non-fatal extractor diagnostics remain attached to their pipeline step. The
185
+ Settings page groups repetitions by plugin, stable rule and severity, and shows
186
+ the recommended next action. A reviewed limitation can be hidden from the
187
+ active view without discarding it:
188
+
189
+ ```json
190
+ {
191
+ "warningPolicies": [
192
+ {
193
+ "when": "plugin == 'openapi' && rule == 'openapi.missing-operation-id' && project == 'aviacore'",
194
+ "action": "suppress",
195
+ "reason": "The partner-owned contract cannot be changed in this repository."
196
+ }
197
+ ]
198
+ }
199
+ ```
200
+
201
+ The rule id is shown beside the warning. CEL expressions can read `plugin`,
202
+ `rule`, `severity`, `project`, `phase`, `ref`, `message`, and the integer
203
+ `count` for that rule in the step. Expressions are type-checked when the
204
+ manifest is read, must return `bool`, and run during generation. A suppression
205
+ requires a reason and remains available through the `suppressed` filter.
139
206
 
140
207
  ## Use it in your project
141
208
 
@@ -234,6 +301,107 @@ live in a dedicated architecture repository: `fetch-git` pins the service
234
301
  repositories at immutable commits and the normal merge, check, diff, and build
235
302
  commands operate on the combined estate.
236
303
 
304
+ ### A real one: Bagisto
305
+
306
+ The services under `examples/` are small on purpose. For what the catalog
307
+ looks like on a real application, the site ships a third profile: pick
308
+ **Bagisto** in the catalog selector. [Bagisto](https://github.com/bagisto/bagisto)
309
+ is an open-source e-commerce platform on Laravel - 41 packages, one per
310
+ module, talking to each other through named events - read by
311
+ `extract-laravel` alone, with nothing annotated for the catalog.
312
+
313
+ What comes out of Bagisto 2.4, pinned in `vendor/repos/bagisto/bagisto`:
314
+
315
+ | | |
316
+ | --- | --- |
317
+ | model groups | 28, one per package with models, 125 Eloquent models and 22 enums between them |
318
+ | events | 309, most of them named - `Event::dispatch('sales.order.cancel.after', …)` - owned by the package their name says, 50 with a listener |
319
+ | HTTP | 520 operations in 10 interfaces, one per route file, with an inferred OpenAPI document |
320
+ | database | 138 tables and 1300 columns replayed from 189 migrations, 185 foreign keys, 120 places the code reads or writes them |
321
+ | queues | 17 jobs on one queue, each a hop in the flow that dispatches it and a worker flow of its own |
322
+ | flows | 610 |
323
+
324
+ Go to the service and read it the way the tabs are ordered: **bus** for the
325
+ queue, **data** for the schema laid out by model group - the picker beside
326
+ the arrangement toggle switches groups off, the search brings a table up
327
+ close - and **flows** for `shop-checkout-onepage-orders-store`, the checkout
328
+ followed from the controller through the repository it holds to the events
329
+ and the job it ends in. Every "view source" link opens the file on GitHub at
330
+ the pinned commit.
331
+
332
+ The warnings the extractor left are findings about Bagisto, not about the
333
+ tool: 218 events under the admin package are dispatched by name and declare
334
+ no payload; `Settings\CurrencyController` answers admin routes and does not
335
+ exist; the sitemap job reads a `pages` table no migration creates; a
336
+ foreign key points at a `theme_customizations` table no migration creates;
337
+ one listener waits for `bagisto.shop.products.price.after`, which nothing
338
+ dispatches, and one for the repository events of a vendored package. How the
339
+ fragments were made, and how to refresh them, is in
340
+ `vendor/repos/bagisto/bagisto/README.md`; the reading rules are in
341
+ `plugins/extract-laravel/README.md`.
342
+
343
+ ### The small one, by the book: CodelyTV php-ddd-example
344
+
345
+ The fourth profile, **CodelyTV php-ddd-example**, is the opposite kind of
346
+ real: [php-ddd-example](https://github.com/CodelyTV/php-ddd-example) is
347
+ CodelyTV's reference for DDD, hexagonal architecture and CQRS in PHP, a
348
+ Symfony monorepository laid out as `src/<Context>/<Module>/{Domain,Application,Infrastructure}`
349
+ with the deployables under `apps/`, read by `extract-php-ddd` alone - the
350
+ layout is the claim, and nothing is annotated. What comes out, pinned in
351
+ `vendor/repos/CodelyTV/php-ddd-example`: four contexts (Mooc, Backoffice,
352
+ Analytics, Retention), six services, five aggregates with their value
353
+ objects and three domain events on one RabbitMQ exchange, one queue per
354
+ subscriber, the MySQL tables from the Doctrine mappings including a JOINED
355
+ `steps` family, and sixteen flows. The one to read is
356
+ `backoffice-frontend-courses-post`: the back office's form dispatches
357
+ Mooc's `CreateCourseCommand` over an in-memory bus, so the flow crosses to
358
+ `mooc.backend` with a `call` step, records `CourseCreated`, saves the
359
+ course, and the subscribers in Mooc, Backoffice and Analytics pick the event
360
+ up from their own queues.
361
+
362
+ The warnings are findings about the project: Analytics and Retention have no
363
+ application under `apps/`, so their code deploys nowhere and Analytics'
364
+ subscriber to every event runs nowhere; `TrimVideoCommandHandler` is named
365
+ like a handler and implements neither bus interface; Retention's four modules
366
+ and Mooc's Notifications are empty scaffolding; the front end routes
367
+ `/api/courses` to a controller that does not exist. How the fragments were
368
+ made is in `vendor/repos/CodelyTV/php-ddd-example/README.md`; the reading
369
+ rules are in `plugins/extract-php-ddd/README.md`.
370
+
371
+ ### The same lesson in C#: Modular Monolith with DDD
372
+
373
+ The fifth profile, **Modular Monolith with DDD**, is Kamil Grzybek's
374
+ [modular-monolith-with-ddd](https://github.com/kgrzybek/modular-monolith-with-ddd),
375
+ the reference for the pattern in .NET: five modules laid out as
376
+ `src/Modules/<Module>/{Domain,Application,Infrastructure,IntegrationEvents}`,
377
+ one API host, one SQL Server database with a schema per module, read by
378
+ `extract-csharp-ddd` alone - through Roslyn, the compiler's own parser, over
379
+ the tree as one compilation and without restoring a single package. What
380
+ comes out, pinned in `vendor/repos/kgrzybek/modular-monolith-with-ddd`: five
381
+ contexts with one service each, nineteen aggregates and five model groups,
382
+ sixty domain events dispatched in process and seven integration events on
383
+ the in-memory bus, sixty-one commands and twenty-nine queries, an internal command queue
384
+ per module, the forty-three tables and seventeen views of the database
385
+ project with the root tables mapped to their aggregates through the EF
386
+ configurations, fifty-one HTTP operations across four documents, the
387
+ project's seventeen decision records, and a hundred and two flows. The one
388
+ to read is `register-new-user`: the User Access controller hands
389
+ `RegisterNewUserCommand` to the Registrations module's facade in process,
390
+ the flow crosses with a `call` step, `NewUserRegisteredDomainEvent` is
391
+ raised and saved, and from there the outbox republishes it to the bus as an
392
+ integration event that Meetings, Administration and Payments each hear and
393
+ turn into a job on their own internal queue.
394
+
395
+ The warnings are findings about the project: `MemberCreatedIntegrationEvent`
396
+ is declared and published nowhere while User Access subscribes to it;
397
+ Payments subscribes to `MeetingGroupProposalAcceptedIntegrationEvent` and
398
+ has no handler; the `app` schema belongs to no module; the view is created
399
+ as `v_Countriess` and the query reads `v_Countries`; and two modules keep a
400
+ `Domain/Users` directory with a typed id and nothing to hold it. How the
401
+ fragments were made is in
402
+ `vendor/repos/kgrzybek/modular-monolith-with-ddd/README.md`; the reading
403
+ rules are in `plugins/extract-csharp-ddd/README.md`.
404
+
237
405
  ## Develop Portolan itself
238
406
 
239
407
  ```bash
@@ -0,0 +1,46 @@
1
+ package catalog
2
+
3
+ import (
4
+ "encoding/json"
5
+ "testing"
6
+ )
7
+
8
+ // An enum round-trips whole: the aggregate's list, the values with their
9
+ // docs, the deprecation marks - and the proto side's numbers. The fixture is
10
+ // literal rather than read from a catalog so that the test says exactly what
11
+ // the mirror is expected to keep.
12
+ func TestEnumRoundTrip(t *testing.T) {
13
+ raw := `{"id":"a","slug":"a","name":"A","readme":"","root":"A","entities":[],"valueObjects":[],"operations":[],"events":[],
14
+ "enums":[{"id":"shop.oms.order.status","slug":"status","name":"Status","doc":"Where an order is.","deprecated":true,
15
+ "values":[{"name":"placed","doc":"just in"},{"name":"legacy","doc":"","deprecated":true}]}]}`
16
+
17
+ var agg Aggregate
18
+ if err := json.Unmarshal([]byte(raw), &agg); err != nil {
19
+ t.Fatal(err)
20
+ }
21
+ if len(agg.Enums) != 1 || agg.Enums[0].ID != "shop.oms.order.status" || !agg.Enums[0].Deprecated {
22
+ t.Fatalf("enum not read: %+v", agg.Enums)
23
+ }
24
+ if got := agg.Enums[0].Values; len(got) != 2 || got[0].Name != "placed" || !got[1].Deprecated {
25
+ t.Fatalf("values not read: %+v", got)
26
+ }
27
+
28
+ out, err := json.Marshal(agg)
29
+ if err != nil {
30
+ t.Fatal(err)
31
+ }
32
+ var before, after any
33
+ _ = json.Unmarshal([]byte(raw), &before)
34
+ _ = json.Unmarshal(out, &after)
35
+ if diffs := compare("aggregate", normalize(before), normalize(after)); len(diffs) != 0 {
36
+ t.Fatalf("round trip lost something: %v", diffs)
37
+ }
38
+
39
+ var svc RpcService
40
+ if err := json.Unmarshal([]byte(`{"id":"shop.v1.OrderService","methods":[],"source":"","enums":[{"name":"Status","values":[{"name":"STATUS_UNSPECIFIED","number":0},{"name":"PLACED","number":1,"doc":"in"}]}]}`), &svc); err != nil {
41
+ t.Fatal(err)
42
+ }
43
+ if len(svc.Enums) != 1 || svc.Enums[0].Values[1].Number != 1 || svc.Enums[0].Values[0].Number != 0 {
44
+ t.Fatalf("proto enum not read: %+v", svc.Enums)
45
+ }
46
+ }
@@ -0,0 +1,35 @@
1
+ package catalog
2
+
3
+ import (
4
+ "encoding/json"
5
+ "reflect"
6
+ "testing"
7
+ )
8
+
9
+ func TestRelationEvidenceSurvivesEverySupportedCarrier(t *testing.T) {
10
+ evidence := []RelationEvidence{{Kind: "binding", Rule: "provider-signature", Source: "di.go:14", Symbol: "Provide", Candidates: []string{"one", "two"}}}
11
+ before := struct {
12
+ Step Step
13
+ Call RpcCall
14
+ Table Table
15
+ Persists Persists
16
+ }{
17
+ Step: Step{Evidence: evidence}, Call: RpcCall{Evidence: evidence},
18
+ Table: Table{Evidence: evidence}, Persists: Persists{Evidence: evidence},
19
+ }
20
+ raw, err := json.Marshal(before)
21
+ if err != nil {
22
+ t.Fatal(err)
23
+ }
24
+ after := before
25
+ after.Step.Evidence = nil
26
+ after.Call.Evidence = nil
27
+ after.Table.Evidence = nil
28
+ after.Persists.Evidence = nil
29
+ if err := json.Unmarshal(raw, &after); err != nil {
30
+ t.Fatal(err)
31
+ }
32
+ if !reflect.DeepEqual(before, after) {
33
+ t.Fatalf("evidence changed: %s", raw)
34
+ }
35
+ }