@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,20 @@
1
+ [package]
2
+ name = "portolan-extract-laravel"
3
+ version = "0.1.0"
4
+ edition = "2024"
5
+ description = "A Laravel application in, a catalog fragment out: the PHP twin of extract-django, on the same protocol."
6
+ publish = false
7
+
8
+ [lib]
9
+ name = "portolan_extract_laravel"
10
+ path = "src/lib.rs"
11
+
12
+ [[bin]]
13
+ name = "portolan-extract-laravel"
14
+ path = "src/main.rs"
15
+
16
+ [dependencies]
17
+ # The PHP tree as syntax, shared with every other PHP extractor.
18
+ phpscan = { path = "../phpscan" }
19
+ serde = { version = "1", features = ["derive"] }
20
+ serde_json = { version = "1", features = ["preserve_order"] }
@@ -0,0 +1,200 @@
1
+ # extract-laravel
2
+
3
+ A Laravel application in, a catalog fragment out - two, when its route files
4
+ prove an HTTP contract. The PHP twin of `extract-django`: like it, this reads
5
+ the framework rather than a layout somebody keeps by hand. A Laravel
6
+ application already says where the models are, what they hold, what answers a
7
+ request and who listens to what; nothing is annotated for the catalog, and
8
+ the framework's conventions are the claim.
9
+
10
+ Written in Rust and run as a process plugin, `cargo run --quiet
11
+ --manifest-path plugins/extract-laravel/Cargo.toml`, on [Mago](https://github.com/carthage-software/mago)'s
12
+ `mago-syntax`: the one maintained PHP parser in Rust, kept up with PHP 8.5
13
+ within weeks of each release, which is what a reader of a framework that moves
14
+ with the language needs. **The application is never run.** No `artisan`, no
15
+ container, no `composer install`: everything is read from syntax, and names
16
+ are resolved by namespace and by `use` line, the way `extract-ts` resolves
17
+ them without a type checker. A file that does not parse is read as far as it
18
+ parsed and reported.
19
+
20
+ ## The layout it reads
21
+
22
+ Two layouts, and the manifest's `modules` option can name either or a third:
23
+
24
+ ```
25
+ app/ a plain Laravel application: one module, named after the root
26
+ Models/*.php Eloquent models
27
+ Events/*.php events with a payload
28
+ Listeners/*.php what reacts to them
29
+ Providers/EventServiceProvider.php
30
+ Http/Controllers/**/*.php what answers a route
31
+ routes/*.php the route files, beside app/
32
+
33
+ packages/<Vendor>/<Name>/src/ a monolith built from packages (Bagisto, Concord, nwidart/modules):
34
+ Models/, Events/, Listeners/, Providers/, Http/Controllers/ one module per package, named <Name>
35
+ Routes/*.php, Http/routes.php the package's own routes
36
+ ```
37
+
38
+ `packages/*/*/src` is used when it matches anything, `app` otherwise. A
39
+ module is a source grouping: it becomes a `kind: "model-group"` aggregate,
40
+ `<service>.models-<module>`, the way a Django application does, because a
41
+ package is where the code lives and not a claim about a transactional
42
+ boundary. The service is the whole application; a monolith of forty packages
43
+ is one service with forty groups, and the events that cross between the
44
+ groups are the reason they are worth telling apart.
45
+
46
+ `vendor/`, tests, resources, storage and `Database/` (migrations, seeders,
47
+ factories) are not read.
48
+
49
+ ## What becomes what
50
+
51
+ **Model.** A concrete class extending Eloquent's `Model` - or `Pivot`,
52
+ `MorphPivot`, `Illuminate\Foundation\Auth\User` - directly or through a base
53
+ class of the tree's own. Its fields are what it writes down, in this order:
54
+ the primary key, `id` typed `int` unless `$primaryKey` and `$keyType` say
55
+ otherwise; `$casts` with the type as cast (the `casts()` method in Laravel 11
56
+ counts); `$fillable` and `$guarded` columns with no cast, typed `mixed`
57
+ because the model does not say; `$dates`; `created_at` and `updated_at`
58
+ unless `$timestamps` is off; and one field per relation method, `items: HasMany[OrderItem]` for
59
+ `$this->hasMany(OrderItem::class)`, the target read off the first argument -
60
+ `X::class`, a string, or Concord's `XProxy::modelClass()` with the `Proxy`
61
+ taken off. The doc is the class docblock's first paragraph. `$hidden`,
62
+ accessors and scopes are not fields.
63
+
64
+ **Enum.** A backed PHP `enum` anywhere in the module, its values the backing
65
+ values; and on a model, two or more string constants sharing a prefix,
66
+ `STATUS_PENDING = 'pending'` beside `STATUS_CLOSED = 'closed'`, as one set
67
+ named `<Model> <Prefix>` with id `<group>.<model>-<prefix>`. A value's doc is
68
+ the constant's or case's docblock; `@deprecated` in it, or a `#[Deprecated]`
69
+ attribute, marks the value.
70
+
71
+ **Event, two ways.** A class under `Events/` - or using `Dispatchable` - is an
72
+ event with a payload: its promoted constructor parameters, then its public
73
+ properties, with the types as written; the wire name is the class's full name.
74
+ A string handed to `Event::dispatch('sales.order.cancel.after', $order)` or
75
+ `event('...')` is an event too, one with a name and no declared shape, id
76
+ `<group>.SalesOrderCancelAfter`, wire name the string, and no fields - which
77
+ is reported once per module rather than invented. Leaving those out would hide
78
+ most of what a package-built monolith publishes.
79
+
80
+ A named event belongs to the module its name says: `sales.order.cancel.after`
81
+ is Sales's whoever dispatches it, by the first segment of the name or the
82
+ second. When no segment names a module it goes to the module that dispatches
83
+ it most. A class event belongs to the module that declares it.
84
+
85
+ **Consumer.** Everything Laravel accepts as a listener: an
86
+ `EventServiceProvider`'s `$listen` table, in each of its shapes
87
+ (`Listener::class`, `'Class@method'`, `[Class::class, 'method']`); its
88
+ `$subscribe` classes, read through their `subscribe()` - `$events->listen(...)`
89
+ or the returned `['event' => 'method']` map; `Event::listen(...)` anywhere;
90
+ and a class under `Listeners/` whose `handle(SomeEvent $e)` names an event of
91
+ the tree, which Laravel discovers on its own. Each is a consumer on the event,
92
+ `service` the application itself and `note` the handler, and a policy flow.
93
+
94
+ **Endpoint and inferred HTTP contract.** Every route file - anything under a
95
+ `Routes/` or `routes/` directory, or a `routes.php` - is read the way the
96
+ router would read it: `Route::get|post|put|patch|delete|options|any|match`,
97
+ inside `prefix`, `name`, `namespace`, `controller` and `group` calls that
98
+ apply to everything in their closure, in both the fluent and the
99
+ `Route::group(['prefix' => ...], fn)` spelling. `Route::resource` and
100
+ `apiResource` expand to the routes they stand for, with `only` and `except`
101
+ honoured and the parameter named the way Laravel names it; `update` is kept
102
+ as PUT, PATCH being the same operation. The action is read in every shape
103
+ Laravel takes: `'index'` under a `controller()` group, `[C::class, 'm']`,
104
+ `'C@m'`, `C::class` for an invokable, a closure. A route whose path or
105
+ action is computed at runtime is kept as unknown and reported rather than
106
+ guessed. A route without its own `name()` has none, even inside a named
107
+ group.
108
+
109
+ The routes of one module make one interface, `<service>.<module>`, with an
110
+ operation per route named after the route name (`shop.checkout.cart.index` →
111
+ `shop_checkout_cart_index`), or the controller and method when there is no
112
+ name, or the verb and path when there is neither; the doc is the controller
113
+ method's docblock. They also make an `openapi.inferred.yaml` OpenAPI 3.1
114
+ document, partial on purpose and marked `x-portolan-inferred` on every
115
+ operation: the route file proves the verb, the path, its parameters and which
116
+ method answers, while request and response schemas are not in what this
117
+ reads and stay absent. A route answering every verb is a path item with no
118
+ operation. A checked-in document remains `extract-openapi`'s richer source of
119
+ truth. A controller the routes name that is not in the tree is reported.
120
+
121
+ **Flow.** One per endpoint: the call in, then every event the handler
122
+ publishes - itself, or through the classes it holds, `$this->orders->create()`
123
+ reaching `OrderRepository::create` when the constructor promoted `$orders`
124
+ with that type, up to five calls deep. And one per listener method: the
125
+ events it reacts to, then what it publishes in turn. A step's `ref` is the
126
+ event's id when the tree dispatches it, and the step is unresolved with a
127
+ note when it does not - a framework event, or a name nobody in the tree
128
+ dispatches, which is reported.
129
+
130
+ **Store.** The migrations, replayed. `extract-sql` reads DDL and a Laravel
131
+ schema is not DDL, so `Schema::create('orders', function (Blueprint $table)
132
+ { ... })` is read here, one `$table->...` line at a time, in the order the
133
+ migration files sort - which is the order Laravel runs them - with
134
+ `Schema::table` alterations, `dropColumn`, `renameColumn`, `drop` and
135
+ `rename` applied on the way; only `up()` is read. Every column type Laravel's
136
+ blueprint has is mapped to its SQL spelling (`string` → `varchar(255)`,
137
+ `foreignId` → `bigint unsigned`, `decimal(12, 4)`, `enum('a','b')`,
138
+ `timestamps()` → two nullable timestamps), `nullable`, `unsigned`, `unique`,
139
+ `index`, `primary` and `comment` are honoured, and a foreign key is read
140
+ from `foreignId()->constrained()` and `foreign()->references()->on()`, with
141
+ its `onDelete`. The store is `<service>.<store>` (`db` unless the manifest
142
+ says), its kind read off config/database.php's default connection - mysql
143
+ when that cannot be read - and it goes into a second fragment, `stores.json`,
144
+ the way extract-django writes one. A table maps to the model whose `$table`
145
+ names it, or whose name Eloquent would pluralise to it (`OrderItem` →
146
+ `order_items`); its `persists` is that model's block and each column shared
147
+ with the model's fields carries `maps`. A table the code reaches that no
148
+ migration in the tree creates is kept with no columns, and reported.
149
+
150
+ **Table access.** `Order::create(...)`, `Order::where(...)->update(...)`,
151
+ `$this->model->find(...)` in a repository whose `model()` names the model -
152
+ or the Concord contract the model implements - and
153
+ `DB::table('orders')->insert(...)`: each is an access of the table, read,
154
+ write or delete by the strongest method on the chain, listed on the table
155
+ and drawn as a step to the store lane in every flow that reaches it.
156
+
157
+ **Job.** A class under `Jobs/` or implementing `ShouldQueue`. Every way one
158
+ is handed to the queue is read - `Job::dispatch(...)`, `dispatch(new Job)`,
159
+ `Bus::dispatch(new Job)`, the lists of `Bus::chain` and `Bus::batch` - and
160
+ the queue is the site's `onQueue('mail')`, else the job's own `$queue`, else
161
+ the queue its first dispatch site names, else `default`. Each queue is a
162
+ channel of kind `job` on the service, with a `send` message per job put on
163
+ it and a `receive` per job worked from it, the shape extract-celery writes
164
+ for a Celery queue; the transport is the connection config/queue.php
165
+ defaults to. A dispatch is a hop in the flow that makes it, a `call` to the
166
+ `Queue · mail` lane with a `job` handoff, and every job has a worker flow of
167
+ its own from the queue in through what `handle()` does.
168
+
169
+ ## What it does not read
170
+
171
+ Lifecycles; form requests and API resources as schemas; `$hidden`,
172
+ accessors, scopes; broadcasting (`ShouldBroadcast`), mail and notifications,
173
+ which go through the queue but are not jobs; `Cache::` and `Redis::`, which
174
+ are a keyspace and not a table; middleware and authorization. Each is a next
175
+ step, not an oversight.
176
+
177
+ ## Options
178
+
179
+ See `options.schema.json`. `context` and `service` default to the input
180
+ directory's name; `modules` picks the layout; `repo` defaults to
181
+ composer.json's `support.source` or `homepage` when either is a repository;
182
+ `store` and `storeKind` name the database; `out`, `openapiOut` and
183
+ `storesOut` name the three files.
184
+
185
+ ## Trying it on a real one
186
+
187
+ ```
188
+ git clone --depth 1 https://github.com/bagisto/bagisto
189
+ printf '%s' '{"input":{"root":"bagisto","output":"bagisto/portolan"},"options":{"context":"commerce","service":"bagisto"}}' \
190
+ | cargo run --quiet --manifest-path plugins/extract-laravel/Cargo.toml
191
+ ```
192
+
193
+ On Bagisto 2.4 that is 41 packages read in well under a second: 28 model
194
+ groups, 125 models, 22 enums, 309 events - most of them named, dispatched
195
+ from admin and storefront controllers and owned by the package their name
196
+ says, 50 of them with a listener - ten HTTP interfaces with 520 operations,
197
+ a database of 138 tables and 1300 columns replayed from 189 migrations with
198
+ 185 foreign keys and 120 places the code reads or writes them, 17 jobs on
199
+ one queue, 610 flows, and one controller the admin routes name that does not
200
+ exist.
@@ -0,0 +1,68 @@
1
+ {
2
+ "type": "object",
3
+ "additionalProperties": false,
4
+ "properties": {
5
+ "context": {
6
+ "type": "string",
7
+ "description": "Slug of the bounded context this application belongs to. A composer.json knows its name, not which context owns it. Defaults to the name of the input directory.",
8
+ "examples": ["shop"]
9
+ },
10
+ "contextName": {
11
+ "type": "string",
12
+ "description": "What the context is called on the page. Defaults to the slug, titled.",
13
+ "examples": ["Shop"]
14
+ },
15
+ "contextSummary": {
16
+ "type": "string",
17
+ "description": "A sentence or two on what the context is responsible for. Read on the context page and nowhere else."
18
+ },
19
+ "classification": {
20
+ "enum": ["core", "supporting", "generic"],
21
+ "description": "Whether the context is core to the business, supports it, or is generic and could be bought."
22
+ },
23
+ "service": {
24
+ "type": "string",
25
+ "description": "Slug of the service. Defaults to the name of the input directory.",
26
+ "examples": ["bagisto"]
27
+ },
28
+ "serviceName": {
29
+ "type": "string",
30
+ "description": "What the service is called on the page. Defaults to the title of its README, and failing that to the slug, titled."
31
+ },
32
+ "repo": {
33
+ "type": "string",
34
+ "description": "Where the source lives. Defaults to composer.json's support.source or homepage when either names a repository.",
35
+ "examples": ["github.com/bagisto/bagisto"]
36
+ },
37
+ "modules": {
38
+ "type": "string",
39
+ "description": "Where the application's modules are, as a directory pattern relative to the input root with `*` for one path segment: `packages/*/*/src` for a package-per-module monolith, `app` for a plain Laravel application. Each match is one module, named after the directory the pattern's last literal segment sits in. Defaults to `packages/*/*/src` when that matches anything, and to `app` otherwise.",
40
+ "examples": ["packages/*/*/src", "app", "modules/*"]
41
+ },
42
+ "store": {
43
+ "type": "string",
44
+ "description": "Slug of the database the migrations describe and the models read and write. The store's id is the service id and this slug.",
45
+ "default": "db",
46
+ "examples": ["mysql"]
47
+ },
48
+ "storeKind": {
49
+ "enum": ["postgres", "mysql", "sqlite", "other"],
50
+ "description": "Which database the migrations are the schema of. Read off config/database.php's default connection when left out - `env('DB_CONNECTION', 'mysql')` names it - and mysql when that cannot be read; given here, it wins."
51
+ },
52
+ "out": {
53
+ "type": "string",
54
+ "description": "Name of the fragment file, inside the step's output directory. One extractor writes one file, so that a fragment carries the provenance of the run that produced it.",
55
+ "default": "domain.json"
56
+ },
57
+ "storesOut": {
58
+ "type": "string",
59
+ "description": "Name of the store fragment, inside the step's output directory. Written when the migrations declare tables or the code reaches any.",
60
+ "default": "stores.json"
61
+ },
62
+ "openapiOut": {
63
+ "type": "string",
64
+ "description": "Name of the inferred OpenAPI 3.1 document, inside the step's output directory. Written when the route files expose HTTP operations.",
65
+ "default": "openapi.inferred.yaml"
66
+ }
67
+ }
68
+ }
@@ -0,0 +1 @@
1
+ max_width = 160