@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
@@ -0,0 +1,170 @@
1
+ package wsdl
2
+
3
+ import (
4
+ "os"
5
+ "path/filepath"
6
+ "strings"
7
+ "testing"
8
+ )
9
+
10
+ func writeFixture(t *testing.T, root, name, contents string) {
11
+ t.Helper()
12
+ path := filepath.Join(root, name)
13
+ if err := os.MkdirAll(filepath.Dir(path), 0o755); err != nil {
14
+ t.Fatal(err)
15
+ }
16
+ if err := os.WriteFile(path, []byte(contents), 0o644); err != nil {
17
+ t.Fatal(err)
18
+ }
19
+ }
20
+
21
+ func TestReadJoinsImportedPortTypeBindingAndSchema(t *testing.T) {
22
+ root := t.TempDir()
23
+ writeFixture(t, root, "types.xsd", `
24
+ <xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema" targetNamespace="urn:booking" xmlns:tns="urn:booking">
25
+ <xsd:complexType name="CancelRequestType">
26
+ <xsd:sequence>
27
+ <xsd:element name="bookingId" type="xsd:string"/>
28
+ <xsd:element name="passengerIds" type="xsd:string" maxOccurs="unbounded"/>
29
+ </xsd:sequence>
30
+ </xsd:complexType>
31
+ <xsd:element name="CancelRequest" type="tns:CancelRequestType"/>
32
+ <xsd:element name="CancelResponse">
33
+ <xsd:complexType><xsd:sequence><xsd:element name="accepted" type="xsd:boolean"/></xsd:sequence></xsd:complexType>
34
+ </xsd:element>
35
+ </xsd:schema>`)
36
+ writeFixture(t, root, "contract.wsdl", `
37
+ <wsdl:definitions xmlns:wsdl="http://schemas.xmlsoap.org/wsdl/" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:tns="urn:booking" targetNamespace="urn:booking">
38
+ <wsdl:types><xsd:schema targetNamespace="urn:booking"><xsd:import namespace="urn:booking" schemaLocation="types.xsd"/></xsd:schema></wsdl:types>
39
+ <wsdl:message name="CancelRequestMessage"><wsdl:part name="body" element="tns:CancelRequest"/></wsdl:message>
40
+ <wsdl:message name="CancelResponseMessage"><wsdl:part name="body" element="tns:CancelResponse"/></wsdl:message>
41
+ <wsdl:message name="BookingFault"><wsdl:part name="reason" type="xsd:string"/></wsdl:message>
42
+ <wsdl:portType name="BookingPortType">
43
+ <wsdl:operation name="CancelBooking">
44
+ <wsdl:documentation>Cancels a booking.</wsdl:documentation>
45
+ <wsdl:input message="tns:CancelRequestMessage"/>
46
+ <wsdl:output message="tns:CancelResponseMessage"/>
47
+ <wsdl:fault name="BookingFault" message="tns:BookingFault"/>
48
+ </wsdl:operation>
49
+ </wsdl:portType>
50
+ </wsdl:definitions>`)
51
+ writeFixture(t, root, "service.wsdl", `
52
+ <wsdl:definitions xmlns:wsdl="http://schemas.xmlsoap.org/wsdl/" xmlns:soap="http://schemas.xmlsoap.org/wsdl/soap/" xmlns:soap12="http://schemas.xmlsoap.org/wsdl/soap12/" xmlns:tns="urn:booking" targetNamespace="urn:booking" name="BookingDefinitions">
53
+ <wsdl:import namespace="urn:booking" location="contract.wsdl"/>
54
+ <wsdl:binding name="BookingBinding" type="tns:BookingPortType">
55
+ <soap12:binding style="document" transport="http://schemas.xmlsoap.org/soap/http"/>
56
+ <wsdl:operation name="CancelBooking">
57
+ <soap12:operation soapAction="urn:booking:cancel"/>
58
+ <wsdl:input><soap12:body use="literal"/><soap12:header message="tns:BookingFault" part="reason" use="literal"/></wsdl:input>
59
+ </wsdl:operation>
60
+ </wsdl:binding>
61
+ <wsdl:binding name="BookingBinding11" type="tns:BookingPortType">
62
+ <soap:binding style="rpc" transport="http://schemas.xmlsoap.org/soap/http"/>
63
+ <wsdl:operation name="CancelBooking"><soap:operation soapAction="urn:booking:cancel:v1"/></wsdl:operation>
64
+ </wsdl:binding>
65
+ <wsdl:service name="BookingService">
66
+ <wsdl:documentation>Booking supplier API.</wsdl:documentation>
67
+ <wsdl:port name="BookingPort" binding="tns:BookingBinding"><soap12:address location="https://booking.example/soap"/></wsdl:port>
68
+ <wsdl:port name="BookingPort11" binding="tns:BookingBinding11"><soap:address location="https://booking.example/soap/v1"/></wsdl:port>
69
+ </wsdl:service>
70
+ </wsdl:definitions>`)
71
+
72
+ result, err := Read(root, "service.wsdl")
73
+ if err != nil {
74
+ t.Fatal(err)
75
+ }
76
+ if len(result.Contracts) != 1 {
77
+ t.Fatalf("contracts = %+v", result.Contracts)
78
+ }
79
+ contract := result.Contracts[0]
80
+ if contract.Name != "BookingService" || contract.Summary != "Booking supplier API." || contract.Source != "service.wsdl" {
81
+ t.Fatalf("contract = %+v", contract)
82
+ }
83
+ if len(contract.Interfaces) != 2 {
84
+ t.Fatalf("interfaces = %+v", contract.Interfaces)
85
+ }
86
+ var iface Interface
87
+ for _, candidate := range contract.Interfaces {
88
+ if candidate.Name == "BookingPort" {
89
+ iface = candidate
90
+ }
91
+ }
92
+ if iface.Name != "BookingPort" || iface.PortType != "BookingPortType" || iface.Binding != "BookingBinding" || iface.Version != "1.2" || iface.Style != "document" || iface.Endpoint != "https://booking.example/soap" {
93
+ t.Fatalf("interface = %+v", iface)
94
+ }
95
+ if len(iface.Operations) != 1 {
96
+ t.Fatalf("operations = %+v", iface.Operations)
97
+ }
98
+ op := iface.Operations[0]
99
+ if op.Action != "urn:booking:cancel" || op.Request != "CancelRequestMessage" || op.Response != "CancelResponseMessage" || strings.Join(op.Faults, ",") != "BookingFault" || strings.Join(op.Headers, ",") != "BookingFault.reason" {
100
+ t.Fatalf("operation = %+v", op)
101
+ }
102
+ messages := map[string]Message{}
103
+ for _, message := range iface.Messages {
104
+ messages[message.Name] = message
105
+ }
106
+ request := messages["CancelRequestMessage"]
107
+ if len(request.Fields) != 2 || request.Fields[0].Name != "bookingId" || request.Fields[0].Type != "string" || request.Fields[1].Type != "string[]" {
108
+ t.Fatalf("request = %+v", request)
109
+ }
110
+ response := messages["CancelResponseMessage"]
111
+ if len(response.Fields) != 1 || response.Fields[0].Name != "accepted" || response.Fields[0].Type != "boolean" {
112
+ t.Fatalf("response = %+v", response)
113
+ }
114
+ }
115
+
116
+ func TestRemoteImportsAreWarningsAndNeverFetched(t *testing.T) {
117
+ root := t.TempDir()
118
+ writeFixture(t, root, "service.wsdl", `
119
+ <definitions xmlns="http://schemas.xmlsoap.org/wsdl/" targetNamespace="urn:local">
120
+ <import namespace="urn:remote" location="https://example.invalid/remote.wsdl"/>
121
+ <portType name="Local"><operation name="Ping"/></portType>
122
+ </definitions>`)
123
+ result, err := Read(root, "service.wsdl")
124
+ if err != nil {
125
+ t.Fatal(err)
126
+ }
127
+ if len(result.Warnings) != 1 || !strings.Contains(result.Warnings[0], "remote import") {
128
+ t.Fatalf("warnings = %v", result.Warnings)
129
+ }
130
+ if len(result.Contracts) != 1 || len(result.Contracts[0].Interfaces) != 1 {
131
+ t.Fatalf("contracts = %+v", result.Contracts)
132
+ }
133
+ }
134
+
135
+ // A schema copied beside every document that uses it is one finding per
136
+ // namespace, naming the file whose declarations were kept and the files that
137
+ // repeat them. It goes out as a schema warning, not a resolution one, so only
138
+ // the extractor publishing the contracts reports it.
139
+ func TestDuplicateDeclarationsAreSummarisedWithTheirOrigin(t *testing.T) {
140
+ root := t.TempDir()
141
+ schema := `
142
+ <schema xmlns="http://www.w3.org/2001/XMLSchema" targetNamespace="urn:common">
143
+ <complexType name="CodeType"/>
144
+ <complexType name="AmountType"/>
145
+ <element name="Ping"/>
146
+ </schema>`
147
+ writeFixture(t, root, "a/common.xsd", schema)
148
+ writeFixture(t, root, "b/common.xsd", schema)
149
+ writeFixture(t, root, "c/common.xsd", schema)
150
+ writeFixture(t, root, "service.wsdl", `
151
+ <definitions xmlns="http://schemas.xmlsoap.org/wsdl/" xmlns:xs="http://www.w3.org/2001/XMLSchema" targetNamespace="urn:local">
152
+ <types>
153
+ <xs:schema><xs:import namespace="urn:common" schemaLocation="a/common.xsd"/></xs:schema>
154
+ <xs:schema><xs:import namespace="urn:common" schemaLocation="b/common.xsd"/></xs:schema>
155
+ <xs:schema><xs:import namespace="urn:common" schemaLocation="c/common.xsd"/></xs:schema>
156
+ </types>
157
+ <portType name="Local"><operation name="Ping"/></portType>
158
+ </definitions>`)
159
+ result, err := Read(root, "service.wsdl")
160
+ if err != nil {
161
+ t.Fatal(err)
162
+ }
163
+ if len(result.Warnings) != 0 {
164
+ t.Errorf("resolution warnings = %v, want none", result.Warnings)
165
+ }
166
+ want := "b/common.xsd: duplicate declaration CodeType and 2 more in namespace urn:common (also in c/common.xsd); the declarations in a/common.xsd are used"
167
+ if len(result.SchemaWarnings) != 1 || result.SchemaWarnings[0] != want {
168
+ t.Fatalf("schema warnings = %v\nwant [%s]", result.SchemaWarnings, want)
169
+ }
170
+ }
package/package.json CHANGED
@@ -2,7 +2,7 @@
2
2
  "name": "@shortlink-org/portolan",
3
3
  "description": "Generate a navigable architecture catalog from code and specifications.",
4
4
  "private": false,
5
- "version": "0.2.4",
5
+ "version": "0.4.0",
6
6
  "type": "module",
7
7
  "license": "MIT",
8
8
  "homepage": "https://github.com/shortlink-org/portolan#readme",
@@ -21,11 +21,23 @@
21
21
  },
22
22
  "files": [
23
23
  "cli",
24
+ "rules",
24
25
  "scripts",
25
26
  "src",
26
27
  "public",
27
28
  "schema",
29
+ "go.mod",
30
+ "go.sum",
31
+ "catalog",
32
+ "plugin",
33
+ "internal/gohttp",
34
+ "internal/gocall",
35
+ "internal/goscan",
36
+ "internal/wsdl",
28
37
  "plugins/*.wasm",
38
+ "plugins/cmd/portolan-http-clients",
39
+ "plugins/extract-http-clients",
40
+ "plugins/openapi",
29
41
  "plugins/*/*.py",
30
42
  "plugins/*/*.ts",
31
43
  "plugins/*/*.json",
@@ -50,7 +62,7 @@
50
62
  "build": "node cli/portolan.mjs build",
51
63
  "preview": "vite preview",
52
64
  "test": "vitest run",
53
- "test:go": "go test ./...",
65
+ "test:go": "GOFLAGS=-mod=mod go test ./...",
54
66
  "pretest:package": "npm run plugins:build",
55
67
  "test:package": "node scripts/package-smoke.mjs",
56
68
  "test:watch": "vitest",
@@ -58,7 +70,7 @@
58
70
  "likec4:gen": "node scripts/gen-likec4.mjs && likec4 gen react likec4 -o src/likec4/generated.jsx --no-use-dot",
59
71
  "likec4:validate": "likec4 validate likec4",
60
72
  "predev": "npm run likec4:gen",
61
- "plugins:build": "GOOS=wasip1 GOARCH=wasm go build -trimpath -ldflags=\"-s -w\" -o plugins/portolan-go.wasm ./plugins/cmd/portolan-go && javac --release 21 -d plugins/extract-java/build plugins/extract-java/src/org/portolan/extract/*.java",
73
+ "plugins:build": "GOFLAGS=-mod=mod GOOS=wasip1 GOARCH=wasm go build -trimpath -ldflags=\"-s -w\" -o plugins/portolan-go.wasm ./plugins/cmd/portolan-go && javac --release 21 -d plugins/extract-java/build plugins/extract-java/src/org/portolan/extract/*.java && dotnet build plugins/extract-csharp-ddd -c Release --nologo -v q",
62
74
  "schema": "npm run plugins:build && node scripts/schema.mjs",
63
75
  "schema:check": "npm run plugins:build && node scripts/schema.mjs --check",
64
76
  "gen": "npm run plugins:build && node scripts/gen.mjs",
@@ -67,46 +79,47 @@
67
79
  "prepack": "npm run plugins:build"
68
80
  },
69
81
  "dependencies": {
70
- "@ai-sdk/openai-compatible": "3.0.45",
71
- "@ai-sdk/react": "4.0.98",
82
+ "@ai-sdk/openai-compatible": "3.0.48",
83
+ "@ai-sdk/react": "4.0.102",
72
84
  "@asyncapi/react-component": "^3.1.8",
73
85
  "@clack/prompts": "^1.8.0",
74
86
  "@floating-ui/react": "^0.27.20",
75
87
  "@headlessui/react": "^2.2.10",
88
+ "@marcbachmann/cel-js": "^8.0.0",
76
89
  "@scalar/api-reference-react": "^0.9.66",
77
90
  "@tailwindcss/vite": "^4.0.0",
78
91
  "@tanstack/react-query": "^5.102.8",
79
92
  "@tanstack/react-table": "^9.2.4",
80
- "@tanstack/react-virtual": "^3.14.10",
93
+ "@tanstack/react-virtual": "^3.14.12",
81
94
  "@vitejs/plugin-react": "^6.1.1",
82
95
  "@xyflow/react": "^12.11.5",
83
- "ai": "7.0.95",
96
+ "ai": "7.0.99",
84
97
  "ajv": "^8.20.0",
85
98
  "elkjs": "^0.12.0",
86
99
  "html-to-image": "^1.11.13",
87
100
  "likec4": "^1.59.2",
88
101
  "lowlight": "^3.3.0",
89
- "lucide-react": "^1.38.0",
90
- "mermaid": "^11.17.2",
102
+ "lucide-react": "^1.45.0",
103
+ "mermaid": "^12.0.0",
91
104
  "motion": "^13.2.0",
92
105
  "oxc-parser": "^0.149.0",
93
106
  "oxc-resolver": "^11.24.2",
94
- "react": "^19.0.0",
95
- "react-dom": "^19.0.0",
107
+ "react": "^19.3.0",
108
+ "react-dom": "^19.3.0",
96
109
  "react-markdown": "^10.1.0",
97
110
  "react-resizable-panels": "^4.12.3",
98
111
  "react-router": "^8.3.1",
99
112
  "remark-gfm": "^4.0.0",
100
113
  "simple-icons": "^16.30.0",
101
114
  "tailwindcss": "^4.0.0",
102
- "vite": "^8.2.2",
115
+ "vite": "^8.3.0",
103
116
  "vite-plugin-node-polyfills": "^0.28.0",
104
117
  "zustand": "^5.0.15"
105
118
  },
106
119
  "devDependencies": {
107
- "@types/node": "^26.5.0",
108
- "@types/react": "^19.0.0",
109
- "@types/react-dom": "^19.0.0",
120
+ "@types/node": "^26.5.1",
121
+ "@types/react": "^19.3.0",
122
+ "@types/react-dom": "^19.3.0",
110
123
  "typescript": "^7.0.2",
111
124
  "vitest": "^5.0.0"
112
125
  }
@@ -0,0 +1,156 @@
1
+ // Describing a plugin: what it is called, where it belongs in a run, and what
2
+ // it can be told.
3
+ //
4
+ // The options a plugin takes are facts the source does not carry - which
5
+ // bounded context a Go module belongs to, whose proto package a client calls.
6
+ // Only the plugin knows them, and until it could say so the manifest was a
7
+ // file you wrote by reading somebody's struct. A misspelled key was silently
8
+ // dropped, because that is what encoding/json does with a field it does not
9
+ // recognise.
10
+ //
11
+ // So a plugin answers a second question now. The schema it hands back is
12
+ // composed into schema/portolan.schema.json, which an editor reads while the
13
+ // manifest is being written and the host checks before it runs anything.
14
+
15
+ package plugin
16
+
17
+ import (
18
+ "encoding/json"
19
+ "fmt"
20
+ "io"
21
+ )
22
+
23
+ // KindDescribe asks a plugin what it is rather than asking it to work.
24
+ const KindDescribe = "describe"
25
+
26
+ // Phases a step can be declared under in the manifest. A plugin names the ones
27
+ // it belongs in, which is what stops the schema from offering an extractor
28
+ // where a renderer goes.
29
+ const (
30
+ PhaseExtract = "extract"
31
+ PhaseVerify = "verify"
32
+ PhaseGenerate = "generate"
33
+ )
34
+
35
+ // Categories a plugin sorts itself under on the plugin index: what it reads,
36
+ // or what it makes. A phase says where in a run a plugin goes; a category
37
+ // says what kind of fact it is after, which is the question a reader choosing
38
+ // extractors for an estate is asking.
39
+ const (
40
+ // CategoryCode reads a language: the aggregates, events and use cases a
41
+ // service declares in its source.
42
+ CategoryCode = "code"
43
+ // CategoryContracts reads an interface description - OpenAPI, protobuf,
44
+ // GraphQL, WSDL, AsyncAPI, a registry's schemas.
45
+ CategoryContracts = "contracts"
46
+ // CategoryMessaging reads queues, subjects, topics and the jobs on them.
47
+ CategoryMessaging = "messaging"
48
+ // CategoryData reads the stores a service keeps and their shape.
49
+ CategoryData = "data"
50
+ // CategoryInfrastructure reads what the service is deployed on - the
51
+ // queues, topics, functions and stores a Terraform module declares, and
52
+ // the wiring between them that only the infrastructure knows.
53
+ CategoryInfrastructure = "infrastructure"
54
+ // CategoryRepository reads what the repository says about itself: its
55
+ // metadata, its task runners.
56
+ CategoryRepository = "repository"
57
+ // CategoryDocuments reads what people wrote by hand: decisions, glossaries,
58
+ // flows.
59
+ CategoryDocuments = "documents"
60
+ // CategoryEvidence checks the catalog against something outside the code:
61
+ // traces, ownership rules.
62
+ CategoryEvidence = "evidence"
63
+ // CategorySources brings in trees from elsewhere for the extractors to read.
64
+ CategorySources = "sources"
65
+ // CategoryExports turns the catalog into something else.
66
+ CategoryExports = "exports"
67
+ )
68
+
69
+ // Descriptor is a plugin's answer to KindDescribe.
70
+ type Descriptor struct {
71
+ // Name is what the plugin calls itself. The manifest is free to declare it
72
+ // under another name - the schema is keyed by the declared one - so this is
73
+ // for the reader, not for lookup.
74
+ Name string `json:"name"`
75
+
76
+ // Summary is one line, and it is the line an editor shows beside the step.
77
+ Summary string `json:"summary,omitempty"`
78
+
79
+ // Category is one of the Category constants: what the plugin reads or
80
+ // makes, which is how the plugin index groups it.
81
+ Category string `json:"category"`
82
+
83
+ // Phases are the manifest sections this plugin belongs in.
84
+ Phases []string `json:"phases"`
85
+
86
+ // Options is a JSON Schema for what the manifest may pass in `options`.
87
+ // It must close itself with `additionalProperties: false`: the misspelled
88
+ // key is the error worth catching, and a schema that accepts anything
89
+ // catches nothing.
90
+ Options json.RawMessage `json:"options,omitempty"`
91
+
92
+ // Needs names what the host must put in the request beyond the tree: a
93
+ // fact a sandboxed module cannot reach on its own. The one need so far is
94
+ // NeedHistory (portolan.0007). A host that cannot supply a need leaves
95
+ // the field out, and the plugin says what it could not do without it.
96
+ Needs []string `json:"needs,omitempty"`
97
+ }
98
+
99
+ // NeedHistory asks the host for Input.History: when each file under the root
100
+ // was first committed and last changed, read from git by the host so that the
101
+ // plugin need not run anything.
102
+ const NeedHistory = "history"
103
+
104
+ // Serve reads one request and writes one response - the whole of a plugin's
105
+ // main, apart from the work itself.
106
+ //
107
+ // Every plugin had this loop written out, and each copy had to remember the
108
+ // describe branch once there was one. The options type is the only thing that
109
+ // differs, so it is the only thing the caller supplies.
110
+ func Serve[O any](stdin io.Reader, stdout io.Writer, d Descriptor, run func(Request, O) (Response, error)) error {
111
+ in, err := io.ReadAll(stdin)
112
+ if err != nil {
113
+ return fmt.Errorf("reading the request: %w", err)
114
+ }
115
+
116
+ var req Request
117
+ if err := json.Unmarshal(in, &req); err != nil {
118
+ return fmt.Errorf("the request is not a portolan plugin request: %w", err)
119
+ }
120
+ if req.PortolanVersion != "" && req.PortolanVersion != Version {
121
+ return fmt.Errorf("unsupported portolan protocol %q (plugin supports %s)", req.PortolanVersion, Version)
122
+ }
123
+
124
+ resp := Response{}
125
+ switch req.Kind {
126
+ case KindDescribe:
127
+ // Files is named rather than left nil because the host reads it on
128
+ // every response, and a describe that answers `"files": null` is a
129
+ // plugin that looks broken the first time anyone asks it anything.
130
+ resp = Response{Files: []File{}, Describe: &d}
131
+ case "":
132
+ var opts O
133
+ if len(req.Options) > 0 {
134
+ if err := json.Unmarshal(req.Options, &opts); err != nil {
135
+ return fmt.Errorf("options: %w", err)
136
+ }
137
+ }
138
+
139
+ if resp, err = run(req, opts); err != nil {
140
+ return err
141
+ }
142
+ default:
143
+ return fmt.Errorf("unknown request kind %q", req.Kind)
144
+ }
145
+
146
+ out, err := json.Marshal(resp)
147
+ if err != nil {
148
+ return fmt.Errorf("encoding the response: %w", err)
149
+ }
150
+
151
+ if _, err := stdout.Write(out); err != nil {
152
+ return fmt.Errorf("writing the response: %w", err)
153
+ }
154
+
155
+ return nil
156
+ }
@@ -0,0 +1,114 @@
1
+ package plugin_test
2
+
3
+ import (
4
+ "bytes"
5
+ "encoding/json"
6
+ "strings"
7
+ "testing"
8
+
9
+ "github.com/shortlink-org/portolan/plugin"
10
+ )
11
+
12
+ type options struct {
13
+ Context string `json:"context"`
14
+ }
15
+
16
+ func descriptor() plugin.Descriptor {
17
+ return plugin.Descriptor{
18
+ Name: "example",
19
+ Phases: []string{plugin.PhaseExtract},
20
+ Options: json.RawMessage(`{"type":"object","additionalProperties":false}`),
21
+ }
22
+ }
23
+
24
+ func TestServeDescribesWithoutWorking(t *testing.T) {
25
+ worked := false
26
+ out := &bytes.Buffer{}
27
+
28
+ err := plugin.Serve(strings.NewReader(`{"kind":"describe"}`), out, descriptor(),
29
+ func(plugin.Request, options) (plugin.Response, error) {
30
+ worked = true
31
+
32
+ return plugin.Response{}, nil
33
+ })
34
+ if err != nil {
35
+ t.Fatalf("describe: %v", err)
36
+ }
37
+
38
+ if worked {
39
+ t.Error("a describe ran the plugin, which is the one thing it must not do")
40
+ }
41
+
42
+ var resp plugin.Response
43
+ if err := json.Unmarshal(out.Bytes(), &resp); err != nil {
44
+ t.Fatalf("the answer is not a response: %v", err)
45
+ }
46
+
47
+ if resp.Describe == nil {
48
+ t.Fatal("a describe answered without a descriptor")
49
+ }
50
+ if resp.Describe.Name != "example" {
51
+ t.Errorf("descriptor names %q", resp.Describe.Name)
52
+ }
53
+
54
+ // The host reads files on every response, so a describe that answers null
55
+ // is a plugin that looks broken the first time anyone asks it anything.
56
+ if !strings.Contains(out.String(), `"files":[]`) {
57
+ t.Errorf("a describe answered %s, which has no files list", out)
58
+ }
59
+ }
60
+
61
+ func TestServeReadsOptions(t *testing.T) {
62
+ var seen options
63
+ out := &bytes.Buffer{}
64
+
65
+ err := plugin.Serve(
66
+ strings.NewReader(`{"input":{"root":"examples/auth"},"options":{"context":"auth"}}`),
67
+ out, descriptor(),
68
+ func(req plugin.Request, opts options) (plugin.Response, error) {
69
+ seen = opts
70
+
71
+ return plugin.Response{Files: []plugin.File{{Name: req.Input.Root + ".json"}}}, nil
72
+ })
73
+ if err != nil {
74
+ t.Fatalf("run: %v", err)
75
+ }
76
+
77
+ if seen.Context != "auth" {
78
+ t.Errorf("options came through as %+v", seen)
79
+ }
80
+ if !strings.Contains(out.String(), "examples/auth.json") {
81
+ t.Errorf("the response is %s", out)
82
+ }
83
+ }
84
+
85
+ func TestServeRefusesAnUnknownKind(t *testing.T) {
86
+ err := plugin.Serve(strings.NewReader(`{"kind":"explain"}`), &bytes.Buffer{}, descriptor(),
87
+ func(plugin.Request, options) (plugin.Response, error) {
88
+ return plugin.Response{}, nil
89
+ })
90
+
91
+ if err == nil || !strings.Contains(err.Error(), "explain") {
92
+ t.Errorf("an unknown kind gave %v", err)
93
+ }
94
+ }
95
+
96
+ func TestServeRefusesAnIncompatibleProtocol(t *testing.T) {
97
+ err := plugin.Serve(strings.NewReader(`{"portolanVersion":"9.0.0","kind":"describe"}`), &bytes.Buffer{}, descriptor(),
98
+ func(plugin.Request, options) (plugin.Response, error) { return plugin.Response{}, nil })
99
+ if err == nil || !strings.Contains(err.Error(), "unsupported portolan protocol") {
100
+ t.Errorf("incompatible version gave %v", err)
101
+ }
102
+ }
103
+
104
+ func TestWarningsAreNotAResponseProperty(t *testing.T) {
105
+ b := &plugin.Builder{}
106
+ b.Warn("fixture", "not represented")
107
+ out, err := json.Marshal(b.Response())
108
+ if err != nil {
109
+ t.Fatal(err)
110
+ }
111
+ if strings.Contains(string(out), "diagnostics") || strings.Contains(string(out), "warning") {
112
+ t.Errorf("warning leaked into wire response: %s", out)
113
+ }
114
+ }