@shortlink-org/portolan 0.1.0 → 0.2.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.
- package/README.md +69 -205
- package/cli/init.mjs +285 -0
- package/cli/init.test.mjs +158 -0
- package/cli/portolan.mjs +37 -73
- package/index.html +19 -0
- package/package.json +5 -10
- package/plugins/README.md +66 -27
- package/plugins/portolan-go.wasm +0 -0
- package/portolan.json +52 -73
- package/public/landing/cats/bug-hunter.webp +0 -0
- package/public/landing/cats/compass-nap.webp +0 -0
- package/public/landing/cats/diagrammer.webp +0 -0
- package/public/landing/cats/docs-reader.webp +0 -0
- package/public/landing/cats/laptop.webp +0 -0
- package/public/landing/cats/map-inspector.webp +0 -0
- package/public/landing/cats/server-break.webp +0 -0
- package/public/landing/cats/star-mapper.webp +0 -0
- package/public/landing/cats/system-builder.webp +0 -0
- package/public/landing/cats/thread-tangle.webp +0 -0
- package/public/og.png +0 -0
- package/public/readme-header.webp +0 -0
- package/schema/portolan.schema.json +180 -0
- package/scripts/builtin-plugins.mjs +14 -0
- package/scripts/delivery-presets.mjs +353 -0
- package/scripts/gen.mjs +37 -4
- package/scripts/history.mjs +126 -0
- package/scripts/history.test.mjs +110 -0
- package/scripts/host-plugins/fetch-bsr.mjs +351 -0
- package/{plugins/fetch-bsr/options.schema.json → scripts/host-plugins/fetch-bsr.options.json} +4 -0
- package/scripts/host-plugins/fetch-bsr.test.mjs +212 -0
- package/scripts/host-plugins/fetch-csr.mjs +386 -0
- package/scripts/host-plugins/fetch-csr.test.mjs +178 -0
- package/scripts/host-plugins/fetch-git.mjs +379 -0
- package/scripts/host-plugins/fetch-git.test.mjs +210 -0
- package/scripts/local-api.mjs +22 -4
- package/scripts/local-api.test.mjs +84 -2
- package/scripts/manifest.mjs +3 -2
- package/scripts/package-smoke.mjs +6 -2
- package/scripts/plugin-host.mjs +77 -18
- package/scripts/plugin-host.test.mjs +50 -1
- package/scripts/plugin-wasm-worker.mjs +6 -3
- package/scripts/run-builtin.mjs +6 -26
- package/scripts/schema.mjs +6 -1
- package/src/app/App.tsx +24 -429
- package/src/app/Breadcrumbs.test.ts +7 -0
- package/src/app/Breadcrumbs.tsx +15 -2
- package/src/app/CatalogApp.tsx +436 -0
- package/src/app/Sidebar.tsx +0 -1
- package/src/chat/ChatPanel.tsx +98 -41
- package/src/chat/Composer.tsx +4 -2
- package/src/chat/Conversation.tsx +79 -15
- package/src/chat/Header.tsx +43 -9
- package/src/chat/Starter.tsx +64 -3
- package/src/chat/Steps.tsx +1 -1
- package/src/chat/flags.test.ts +51 -14
- package/src/chat/flags.ts +8 -2
- package/src/chat/page-context.test.ts +51 -0
- package/src/chat/page-context.ts +128 -0
- package/src/chat/prompt.test.ts +49 -1
- package/src/chat/prompt.ts +42 -1
- package/src/chat/tools.ts +7 -2
- package/src/chat/transport.ts +16 -4
- package/src/components/ProblemRow.tsx +182 -0
- package/src/data.ts +20 -5
- package/src/er/ErCanvas.tsx +1 -1
- package/src/graph/DependencyGraph.tsx +10 -1
- package/src/graph/FocusedEventGraph.tsx +1 -1
- package/src/graph/GraphToolbar.tsx +2 -4
- package/src/index.css +220 -2
- package/src/landing/DraggableReveal.tsx +168 -0
- package/src/landing/EstateGraph.tsx +28 -0
- package/src/landing/FlowPlayback.tsx +282 -0
- package/src/landing/HeroMap.tsx +128 -0
- package/src/landing/LandingPage.tsx +757 -0
- package/src/landing/ProductFrame.tsx +68 -0
- package/src/landing/ProductTour.tsx +371 -0
- package/src/landing/catalog.ts +25 -0
- package/src/landing/motion.tsx +52 -0
- package/src/lib/all-problems.ts +28 -0
- package/src/lib/local-api.ts +35 -1
- package/src/lib/setup-info.test.ts +8 -0
- package/src/lib/setup-info.ts +3 -2
- package/src/lib/source-code.ts +2 -1
- package/src/likec4/C4View.tsx +5 -0
- package/src/likec4/InteractiveView.tsx +5 -1
- package/src/map/ContextMapGraph.tsx +47 -20
- package/src/pages/ContextMap.tsx +5 -1
- package/src/pages/FlowDetail.tsx +24 -7
- package/src/pages/Overview.tsx +68 -12
- package/src/pages/Problems.tsx +6 -193
- package/src/pages/Settings.tsx +133 -108
- package/src/pages/settings/DeliverySettings.tsx +229 -0
- package/src/pages/settings/PreferencesSettings.tsx +101 -0
- package/src/routes.test.ts +20 -0
- package/src/routes.ts +13 -1
- package/vite.config.ts +12 -8
- package/catalog/enum_test.go +0 -46
- package/catalog/model.go +0 -932
- package/catalog/roundtrip_test.go +0 -185
- package/catalog/via_test.go +0 -38
- package/go.mod +0 -14
- package/go.sum +0 -14
- package/internal/commands/cargo.go +0 -154
- package/internal/commands/commands.go +0 -143
- package/internal/commands/commands_test.go +0 -323
- package/internal/commands/gradle.go +0 -77
- package/internal/commands/justfile.go +0 -82
- package/internal/commands/makefile.go +0 -106
- package/internal/commands/maven.go +0 -93
- package/internal/commands/packagejson.go +0 -159
- package/internal/commands/pyproject.go +0 -169
- package/internal/commands/taskfile.go +0 -101
- package/internal/commands/testdata/estate/.cargo/config.toml +0 -7
- package/internal/commands/testdata/estate/Makefile +0 -33
- package/internal/commands/testdata/estate/Taskfile.yml +0 -21
- package/internal/commands/testdata/estate/build.gradle.kts +0 -21
- package/internal/commands/testdata/estate/justfile +0 -20
- package/internal/commands/testdata/estate/package.json +0 -13
- package/internal/commands/testdata/estate/pom.xml +0 -29
- package/internal/commands/testdata/estate/pyproject.toml +0 -27
- package/internal/commands/testdata/estate/xtask/src/main.rs +0 -27
- package/internal/commands/testdata/golden/commands.json +0 -305
- package/internal/gohttp/analyze.go +0 -2500
- package/internal/gohttp/endpoints.go +0 -1067
- package/internal/gohttp/roots.go +0 -320
- package/internal/gohttp/typed.go +0 -143
- package/internal/goscan/constants.go +0 -85
- package/internal/goscan/goscan_test.go +0 -227
- package/internal/goscan/names.go +0 -52
- package/internal/goscan/parse_test.go +0 -11
- package/internal/goscan/source.go +0 -36
- package/internal/goscan/tree.go +0 -155
- package/internal/goscan/types.go +0 -99
- package/internal/wsdl/ids.go +0 -127
- package/internal/wsdl/ids_test.go +0 -21
- package/internal/wsdl/model.go +0 -62
- package/internal/wsdl/parse.go +0 -920
- package/internal/wsdl/parse_test.go +0 -133
- package/plugin/describe.go +0 -107
- package/plugin/describe_test.go +0 -114
- package/plugin/protocol.go +0 -117
- package/plugin/schematest/schematest.go +0 -126
- package/plugins/extract-adr/describe.go +0 -19
- package/plugins/extract-adr/describe_test.go +0 -11
- package/plugins/extract-adr/extract.go +0 -153
- package/plugins/extract-adr/extract_test.go +0 -350
- package/plugins/extract-adr/history.go +0 -99
- package/plugins/extract-adr/main.go +0 -65
- package/plugins/extract-adr/parse.go +0 -642
- package/plugins/extract-adr/parse_test.go +0 -405
- package/plugins/extract-asyncapi/describe.go +0 -19
- package/plugins/extract-asyncapi/describe_test.go +0 -11
- package/plugins/extract-asyncapi/extract.go +0 -315
- package/plugins/extract-asyncapi/extract_test.go +0 -197
- package/plugins/extract-asyncapi/main.go +0 -48
- package/plugins/extract-asyncapi/spec.go +0 -151
- package/plugins/extract-commands/describe.go +0 -19
- package/plugins/extract-commands/describe_test.go +0 -11
- package/plugins/extract-commands/extract.go +0 -79
- package/plugins/extract-commands/extract_test.go +0 -92
- package/plugins/extract-commands/main.go +0 -55
- package/plugins/extract-csr/avro.go +0 -258
- package/plugins/extract-csr/describe.go +0 -19
- package/plugins/extract-csr/describe_test.go +0 -11
- package/plugins/extract-csr/extract.go +0 -338
- package/plugins/extract-csr/extract_test.go +0 -374
- package/plugins/extract-csr/jsonschema.go +0 -343
- package/plugins/extract-csr/lock.go +0 -25
- package/plugins/extract-csr/main.go +0 -84
- package/plugins/extract-csr/subject.go +0 -102
- package/plugins/extract-flows/describe.go +0 -19
- package/plugins/extract-flows/describe_test.go +0 -11
- package/plugins/extract-flows/extract.go +0 -91
- package/plugins/extract-flows/main.go +0 -45
- package/plugins/extract-flows/parse.go +0 -593
- package/plugins/extract-flows/parse_test.go +0 -204
- package/plugins/extract-glossary/describe.go +0 -19
- package/plugins/extract-glossary/describe_test.go +0 -11
- package/plugins/extract-glossary/extract.go +0 -115
- package/plugins/extract-glossary/extract_test.go +0 -220
- package/plugins/extract-glossary/main.go +0 -59
- package/plugins/extract-glossary/parse.go +0 -214
- package/plugins/extract-glossary/parse_test.go +0 -203
- package/plugins/extract-go/aggregate.go +0 -214
- package/plugins/extract-go/client.go +0 -409
- package/plugins/extract-go/client_test.go +0 -266
- package/plugins/extract-go/describe.go +0 -19
- package/plugins/extract-go/describe_test.go +0 -11
- package/plugins/extract-go/enum.go +0 -195
- package/plugins/extract-go/enum_test.go +0 -82
- package/plugins/extract-go/event.go +0 -99
- package/plugins/extract-go/extract.go +0 -191
- package/plugins/extract-go/extract_test.go +0 -261
- package/plugins/extract-go/flow.go +0 -1441
- package/plugins/extract-go/flow_test.go +0 -609
- package/plugins/extract-go/httpclient.go +0 -174
- package/plugins/extract-go/httpclient_test.go +0 -296
- package/plugins/extract-go/ids.go +0 -92
- package/plugins/extract-go/layout.go +0 -225
- package/plugins/extract-go/layout_test.go +0 -125
- package/plugins/extract-go/lifecycle.go +0 -324
- package/plugins/extract-go/lifecycle_test.go +0 -108
- package/plugins/extract-go/main.go +0 -79
- package/plugins/extract-go/operation.go +0 -157
- package/plugins/extract-go/source.go +0 -316
- package/plugins/extract-go/transport.go +0 -321
- package/plugins/extract-go/transport_test.go +0 -145
- package/plugins/extract-go/wiring.go +0 -434
- package/plugins/extract-go-nats/describe.go +0 -19
- package/plugins/extract-go-nats/describe_test.go +0 -11
- package/plugins/extract-go-nats/extract.go +0 -177
- package/plugins/extract-go-nats/extract_test.go +0 -389
- package/plugins/extract-go-nats/index.go +0 -379
- package/plugins/extract-go-nats/main.go +0 -42
- package/plugins/extract-go-nats/resolve.go +0 -161
- package/plugins/extract-go-nats/sites.go +0 -224
- package/plugins/extract-graphql/describe.go +0 -19
- package/plugins/extract-graphql/describe_test.go +0 -11
- package/plugins/extract-graphql/extract.go +0 -433
- package/plugins/extract-graphql/extract_test.go +0 -256
- package/plugins/extract-graphql/ids.go +0 -49
- package/plugins/extract-graphql/lex.go +0 -237
- package/plugins/extract-graphql/main.go +0 -51
- package/plugins/extract-graphql/parse.go +0 -621
- package/plugins/extract-graphql/parse_test.go +0 -122
- package/plugins/extract-http-clients/describe.go +0 -19
- package/plugins/extract-http-clients/describe_test.go +0 -11
- package/plugins/extract-http-clients/extract.go +0 -705
- package/plugins/extract-http-clients/extract_test.go +0 -1263
- package/plugins/extract-http-clients/main.go +0 -42
- package/plugins/extract-openapi/describe.go +0 -19
- package/plugins/extract-openapi/describe_test.go +0 -11
- package/plugins/extract-openapi/discover.go +0 -243
- package/plugins/extract-openapi/extract.go +0 -526
- package/plugins/extract-openapi/extract_test.go +0 -545
- package/plugins/extract-openapi/main.go +0 -75
- package/plugins/extract-openapi/spec.go +0 -350
- package/plugins/extract-project/describe.go +0 -19
- package/plugins/extract-project/describe_test.go +0 -11
- package/plugins/extract-project/extract.go +0 -221
- package/plugins/extract-project/extract_test.go +0 -109
- package/plugins/extract-project/main.go +0 -41
- package/plugins/extract-proto/ast.go +0 -125
- package/plugins/extract-proto/consumes.go +0 -77
- package/plugins/extract-proto/describe.go +0 -19
- package/plugins/extract-proto/extract.go +0 -293
- package/plugins/extract-proto/extract_test.go +0 -459
- package/plugins/extract-proto/ids.go +0 -89
- package/plugins/extract-proto/ids_test.go +0 -57
- package/plugins/extract-proto/lex.go +0 -285
- package/plugins/extract-proto/main.go +0 -100
- package/plugins/extract-proto/module.go +0 -120
- package/plugins/extract-proto/parse.go +0 -720
- package/plugins/extract-proto/parse_test.go +0 -307
- package/plugins/extract-proto/provides.go +0 -236
- package/plugins/extract-proto/resolve.go +0 -222
- package/plugins/extract-proto/resolve_test.go +0 -119
- package/plugins/extract-redis/describe.go +0 -19
- package/plugins/extract-redis/describe_test.go +0 -11
- package/plugins/extract-redis/extract.go +0 -183
- package/plugins/extract-redis/extract_test.go +0 -168
- package/plugins/extract-redis/keyspaces.go +0 -469
- package/plugins/extract-redis/main.go +0 -44
- package/plugins/extract-river/describe.go +0 -19
- package/plugins/extract-river/describe_test.go +0 -11
- package/plugins/extract-river/extract.go +0 -507
- package/plugins/extract-river/extract_test.go +0 -132
- package/plugins/extract-river/main.go +0 -42
- package/plugins/extract-sql/ddl.go +0 -893
- package/plugins/extract-sql/ddl_test.go +0 -401
- package/plugins/extract-sql/describe.go +0 -19
- package/plugins/extract-sql/describe_test.go +0 -11
- package/plugins/extract-sql/layout.go +0 -221
- package/plugins/extract-sql/layout_test.go +0 -55
- package/plugins/extract-sql/lineage.go +0 -107
- package/plugins/extract-sql/main.go +0 -141
- package/plugins/extract-sql/maps.go +0 -564
- package/plugins/extract-sql/maps_java.go +0 -117
- package/plugins/extract-sql/maps_rust.go +0 -333
- package/plugins/extract-sql/maps_rust_test.go +0 -70
- package/plugins/extract-sql/maps_test.go +0 -204
- package/plugins/extract-sql/maps_ts.go +0 -398
- package/plugins/extract-sql/maps_ts_test.go +0 -136
- package/plugins/extract-sql/projection.go +0 -70
- package/plugins/extract-sql/projection_test.go +0 -50
- package/plugins/extract-sql/store.go +0 -420
- package/plugins/extract-sql/store_test.go +0 -233
- package/plugins/extract-sql/view.go +0 -295
- package/plugins/extract-watermill/describe.go +0 -19
- package/plugins/extract-watermill/describe_test.go +0 -11
- package/plugins/extract-watermill/extract.go +0 -1228
- package/plugins/extract-watermill/extract_test.go +0 -234
- package/plugins/extract-watermill/main.go +0 -41
- package/plugins/extract-wsdl/describe.go +0 -19
- package/plugins/extract-wsdl/describe_test.go +0 -11
- package/plugins/extract-wsdl/extract.go +0 -148
- package/plugins/extract-wsdl/extract_test.go +0 -45
- package/plugins/extract-wsdl/main.go +0 -53
- package/plugins/fetch-bsr/auth.go +0 -114
- package/plugins/fetch-bsr/bsr.go +0 -240
- package/plugins/fetch-bsr/cache.go +0 -66
- package/plugins/fetch-bsr/describe.go +0 -19
- package/plugins/fetch-bsr/fetch.go +0 -192
- package/plugins/fetch-bsr/fetch_test.go +0 -465
- package/plugins/fetch-bsr/lock.go +0 -71
- package/plugins/fetch-bsr/main.go +0 -117
- package/plugins/fetch-csr/auth.go +0 -95
- package/plugins/fetch-csr/cache.go +0 -69
- package/plugins/fetch-csr/describe.go +0 -19
- package/plugins/fetch-csr/describe_test.go +0 -11
- package/plugins/fetch-csr/fetch.go +0 -254
- package/plugins/fetch-csr/fetch_test.go +0 -494
- package/plugins/fetch-csr/lock.go +0 -95
- package/plugins/fetch-csr/main.go +0 -122
- package/plugins/fetch-csr/registry.go +0 -209
- package/plugins/fetch-git/cache.go +0 -65
- package/plugins/fetch-git/describe.go +0 -19
- package/plugins/fetch-git/describe_test.go +0 -11
- package/plugins/fetch-git/fetch.go +0 -181
- package/plugins/fetch-git/fetch_test.go +0 -332
- package/plugins/fetch-git/git.go +0 -169
- package/plugins/fetch-git/lock.go +0 -72
- package/plugins/fetch-git/main.go +0 -127
- package/plugins/fetch-git/offline.go +0 -39
- package/plugins/fetch-git/pin.go +0 -90
- package/plugins/fetch-git/pin_test.go +0 -74
- package/plugins/gen-backstage/describe.go +0 -17
- package/plugins/gen-backstage/main.go +0 -22
- package/plugins/gen-backstage/plugin.go +0 -473
- package/plugins/gen-backstage/plugin_test.go +0 -145
- package/plugins/gen-backstage.wasm +0 -0
- package/plugins/gen-markdown/adr.go +0 -162
- package/plugins/gen-markdown/aggregate.go +0 -362
- package/plugins/gen-markdown/canonical.go +0 -93
- package/plugins/gen-markdown/context.go +0 -90
- package/plugins/gen-markdown/coverage_test.go +0 -89
- package/plugins/gen-markdown/describe.go +0 -19
- package/plugins/gen-markdown/describe_test.go +0 -11
- package/plugins/gen-markdown/external.go +0 -71
- package/plugins/gen-markdown/flow.go +0 -277
- package/plugins/gen-markdown/glossary.go +0 -76
- package/plugins/gen-markdown/glossary_test.go +0 -148
- package/plugins/gen-markdown/llms.go +0 -302
- package/plugins/gen-markdown/main.go +0 -22
- package/plugins/gen-markdown/markdown.go +0 -254
- package/plugins/gen-markdown/markdown_test.go +0 -100
- package/plugins/gen-markdown/module.go +0 -107
- package/plugins/gen-markdown/plugin.go +0 -39
- package/plugins/gen-markdown/quality_test.go +0 -176
- package/plugins/gen-markdown/redis_test.go +0 -38
- package/plugins/gen-markdown/render.go +0 -344
- package/plugins/gen-markdown/render_test.go +0 -186
- package/plugins/gen-markdown/service.go +0 -456
- package/plugins/gen-markdown/source.go +0 -177
- package/plugins/gen-markdown/store.go +0 -201
- package/plugins/gen-markdown.wasm +0 -0
- package/plugins/gen-mermaid/describe.go +0 -17
- package/plugins/gen-mermaid/main.go +0 -22
- package/plugins/gen-mermaid/plugin.go +0 -104
- package/plugins/gen-mermaid/plugin_test.go +0 -33
- package/plugins/gen-mermaid.wasm +0 -0
- package/plugins/openapi/ids.go +0 -261
- package/plugins/openapi/ids_test.go +0 -98
- package/plugins/verify-codeowners/describe.go +0 -19
- package/plugins/verify-codeowners/describe_test.go +0 -11
- package/plugins/verify-codeowners/main.go +0 -75
- package/plugins/verify-codeowners/match.go +0 -85
- package/plugins/verify-codeowners/match_test.go +0 -47
- package/plugins/verify-codeowners/owners.go +0 -164
- package/plugins/verify-codeowners/owners_test.go +0 -225
- package/plugins/verify-codeowners/parse.go +0 -90
- package/plugins/verify-codeowners/parse_test.go +0 -62
- package/plugins/verify-otel/describe.go +0 -19
- package/plugins/verify-otel/describe_test.go +0 -11
- package/plugins/verify-otel/main.go +0 -53
- package/plugins/verify-otel/match.go +0 -336
- package/plugins/verify-otel/otlp.go +0 -200
- package/plugins/verify-otel/verify.go +0 -734
- package/plugins/verify-otel/verify_test.go +0 -460
- /package/{plugins/fetch-csr/options.schema.json → scripts/host-plugins/fetch-csr.options.json} +0 -0
- /package/{plugins/fetch-git/options.schema.json → scripts/host-plugins/fetch-git.options.json} +0 -0
|
@@ -1,107 +0,0 @@
|
|
|
1
|
-
package main
|
|
2
|
-
|
|
3
|
-
import (
|
|
4
|
-
"strings"
|
|
5
|
-
|
|
6
|
-
"github.com/shortlink-org/portolan/catalog"
|
|
7
|
-
)
|
|
8
|
-
|
|
9
|
-
// renderModules gives schema registries a first-class place in the generated
|
|
10
|
-
// documentation. A module is shared by its publisher and every vendored
|
|
11
|
-
// consumer, so hiding it inside one service loses the relationship readers
|
|
12
|
-
// usually need it for.
|
|
13
|
-
func (s *site) renderModules() {
|
|
14
|
-
if len(s.cat.Modules) == 0 {
|
|
15
|
-
return
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
const self = "modules/README.md"
|
|
19
|
-
var b strings.Builder
|
|
20
|
-
b.WriteString("# Schema modules\n\n")
|
|
21
|
-
b.WriteString(s.stamp() + "\n")
|
|
22
|
-
rows := make([][]string, 0, len(s.cat.Modules))
|
|
23
|
-
for i := range s.cat.Modules {
|
|
24
|
-
module := &s.cat.Modules[i]
|
|
25
|
-
rows = append(rows, []string{
|
|
26
|
-
s.ref(self, module.ID, orDefault(module.Name, module.ID)),
|
|
27
|
-
s.ref(self, module.Owner, module.Owner),
|
|
28
|
-
module.Registry,
|
|
29
|
-
module.Commit,
|
|
30
|
-
plural(len(module.Packages), "package"),
|
|
31
|
-
})
|
|
32
|
-
}
|
|
33
|
-
b.WriteString(table([]string{"Module", "Publisher", "Registry", "Commit", "Packages"}, rows))
|
|
34
|
-
s.b.file(self, b.String())
|
|
35
|
-
|
|
36
|
-
for i := range s.cat.Modules {
|
|
37
|
-
s.renderModule(&s.cat.Modules[i])
|
|
38
|
-
}
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
func (s *site) renderModule(module *catalog.ProtoModule) {
|
|
42
|
-
self := s.pathOf[module.ID]
|
|
43
|
-
owner := s.services[module.Owner]
|
|
44
|
-
var b strings.Builder
|
|
45
|
-
b.WriteString("# " + orDefault(module.Name, module.ID) + "\n\n")
|
|
46
|
-
b.WriteString(s.stamp() + "\n")
|
|
47
|
-
b.WriteString(defList([][]string{
|
|
48
|
-
{"Id", code(module.ID)},
|
|
49
|
-
{"Registry", module.Registry},
|
|
50
|
-
{"Publisher", s.ref(self, module.Owner, module.Owner)},
|
|
51
|
-
{"Commit", code(module.Commit)},
|
|
52
|
-
{"Digest", code(module.Digest)},
|
|
53
|
-
{"Source", s.source(self, module.Source, owner)},
|
|
54
|
-
}))
|
|
55
|
-
|
|
56
|
-
packages := make([][]string, 0, len(module.Packages))
|
|
57
|
-
for _, pkg := range module.Packages {
|
|
58
|
-
packages = append(packages, []string{code(pkg)})
|
|
59
|
-
}
|
|
60
|
-
section(&b, "Packages", table([]string{"Package"}, packages))
|
|
61
|
-
|
|
62
|
-
files := make([][]string, 0, len(module.Files))
|
|
63
|
-
for _, file := range module.Files {
|
|
64
|
-
files = append(files, []string{s.source(self, file, owner)})
|
|
65
|
-
}
|
|
66
|
-
section(&b, "Files", table([]string{"File"}, files))
|
|
67
|
-
|
|
68
|
-
deps := make([][]string, 0, len(module.Deps))
|
|
69
|
-
for _, id := range module.Deps {
|
|
70
|
-
deps = append(deps, []string{s.ref(self, id, id)})
|
|
71
|
-
}
|
|
72
|
-
section(&b, "Dependencies", table([]string{"Module"}, deps))
|
|
73
|
-
|
|
74
|
-
users := make([][]string, 0)
|
|
75
|
-
for i := range s.cat.Contexts {
|
|
76
|
-
for j := range s.cat.Contexts[i].Services {
|
|
77
|
-
svc := &s.cat.Contexts[i].Services[j]
|
|
78
|
-
for _, id := range svc.Modules {
|
|
79
|
-
if id != module.ID {
|
|
80
|
-
continue
|
|
81
|
-
}
|
|
82
|
-
access := "reads"
|
|
83
|
-
if svc.ID == module.Owner {
|
|
84
|
-
access = "publishes"
|
|
85
|
-
}
|
|
86
|
-
users = append(users, []string{s.ref(self, svc.ID, svc.Name), access})
|
|
87
|
-
}
|
|
88
|
-
}
|
|
89
|
-
}
|
|
90
|
-
section(&b, "Used by", table([]string{"Service", "Access"}, users))
|
|
91
|
-
|
|
92
|
-
var interfaces strings.Builder
|
|
93
|
-
for i := range s.cat.Contexts {
|
|
94
|
-
for j := range s.cat.Contexts[i].Services {
|
|
95
|
-
svc := &s.cat.Contexts[i].Services[j]
|
|
96
|
-
for k := range svc.Provides {
|
|
97
|
-
provided := &svc.Provides[k]
|
|
98
|
-
if provided.Module == module.ID {
|
|
99
|
-
interfaces.WriteString(s.providesBlock(self, []catalog.RpcService{*provided}, svc))
|
|
100
|
-
}
|
|
101
|
-
}
|
|
102
|
-
}
|
|
103
|
-
}
|
|
104
|
-
section(&b, "Interfaces", interfaces.String())
|
|
105
|
-
|
|
106
|
-
s.b.file(self, b.String())
|
|
107
|
-
}
|
|
@@ -1,39 +0,0 @@
|
|
|
1
|
-
// Package main is portolan-gen-markdown: a catalog in, a directory of markdown
|
|
2
|
-
// out.
|
|
3
|
-
//
|
|
4
|
-
// The plugin never touches the filesystem. It is handed a catalog on stdin and
|
|
5
|
-
// answers with the files it would like written; the host writes them. That is
|
|
6
|
-
// what lets the same binary run as a sandboxed wasm module with no directory
|
|
7
|
-
// preopened at all, and it is what makes `--check` cheap: the host compares
|
|
8
|
-
// what came back against what is on disk without the plugin knowing there is a
|
|
9
|
-
// disk.
|
|
10
|
-
package main
|
|
11
|
-
|
|
12
|
-
import (
|
|
13
|
-
"fmt"
|
|
14
|
-
|
|
15
|
-
"github.com/shortlink-org/portolan/plugin"
|
|
16
|
-
)
|
|
17
|
-
|
|
18
|
-
// Options are this plugin's own settings, passed through from the manifest.
|
|
19
|
-
type Options struct {
|
|
20
|
-
// Title heads the index page. The catalog does not name the estate, so
|
|
21
|
-
// this is the one thing the plugin cannot derive.
|
|
22
|
-
Title string `json:"title,omitempty"`
|
|
23
|
-
// SourceBaseURL is the stable tree URL for this repository, such as a
|
|
24
|
-
// default branch. External repositories still use their immutable RepoPin.
|
|
25
|
-
SourceBaseURL string `json:"sourceBaseUrl,omitempty"`
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
// builder is plugin.Builder with the formatting the render code wants. The
|
|
29
|
-
// shared one deliberately takes a plain string: a protocol type that knows
|
|
30
|
-
// about printf verbs is a protocol type doing somebody else's job.
|
|
31
|
-
type builder struct {
|
|
32
|
-
plugin.Builder
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
func (b *builder) file(name, contents string) { b.File(name, contents) }
|
|
36
|
-
|
|
37
|
-
func (b *builder) warn(ref, format string, args ...any) {
|
|
38
|
-
b.Warn(ref, fmt.Sprintf(format, args...))
|
|
39
|
-
}
|
|
@@ -1,176 +0,0 @@
|
|
|
1
|
-
package main
|
|
2
|
-
|
|
3
|
-
import (
|
|
4
|
-
"encoding/json"
|
|
5
|
-
"net/url"
|
|
6
|
-
"os"
|
|
7
|
-
"path"
|
|
8
|
-
"regexp"
|
|
9
|
-
"slices"
|
|
10
|
-
"strings"
|
|
11
|
-
"testing"
|
|
12
|
-
|
|
13
|
-
"github.com/shortlink-org/portolan/catalog"
|
|
14
|
-
"github.com/shortlink-org/portolan/plugin"
|
|
15
|
-
)
|
|
16
|
-
|
|
17
|
-
var localLink = regexp.MustCompile(`\]\(([^\s)]+)(?:\s+[^)]*)?\)`)
|
|
18
|
-
var explicitAnchor = regexp.MustCompile(`<a id="([^"]+)"></a>`)
|
|
19
|
-
|
|
20
|
-
func fixtureCatalog(t *testing.T) catalog.Catalog {
|
|
21
|
-
t.Helper()
|
|
22
|
-
raw, err := os.ReadFile("testdata/catalog.json")
|
|
23
|
-
if err != nil {
|
|
24
|
-
t.Fatal(err)
|
|
25
|
-
}
|
|
26
|
-
var cat catalog.Catalog
|
|
27
|
-
if err := json.Unmarshal(raw, &cat); err != nil {
|
|
28
|
-
t.Fatal(err)
|
|
29
|
-
}
|
|
30
|
-
return cat
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
func renderedFiles(resp plugin.Response) map[string]string {
|
|
34
|
-
out := make(map[string]string, len(resp.Files))
|
|
35
|
-
for _, file := range resp.Files {
|
|
36
|
-
out[file.Name] = file.Contents
|
|
37
|
-
}
|
|
38
|
-
return out
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
func TestEveryGeneratedLinkAndAnchorResolves(t *testing.T) {
|
|
42
|
-
files := renderedFiles(render(plugin.Request{Catalog: fixtureCatalog(t)}, Options{}))
|
|
43
|
-
for name, contents := range files {
|
|
44
|
-
// This concatenation keeps each page's original relative links and names
|
|
45
|
-
// the page in an HTML comment; it is not itself a linkable directory.
|
|
46
|
-
if name == llmsFullPage {
|
|
47
|
-
continue
|
|
48
|
-
}
|
|
49
|
-
for _, match := range localLink.FindAllStringSubmatch(contents, -1) {
|
|
50
|
-
target := match[1]
|
|
51
|
-
if strings.HasPrefix(target, "#") {
|
|
52
|
-
target = name + target
|
|
53
|
-
}
|
|
54
|
-
if parsed, err := url.Parse(target); err != nil || parsed.IsAbs() {
|
|
55
|
-
continue
|
|
56
|
-
}
|
|
57
|
-
parts := strings.SplitN(target, "#", 2)
|
|
58
|
-
file := path.Clean(path.Join(path.Dir(name), parts[0]))
|
|
59
|
-
body, ok := files[file]
|
|
60
|
-
if !ok {
|
|
61
|
-
t.Errorf("%s links to missing %s", name, target)
|
|
62
|
-
continue
|
|
63
|
-
}
|
|
64
|
-
if len(parts) == 2 && parts[1] != "" && !hasAnchor(body, parts[1]) {
|
|
65
|
-
t.Errorf("%s links to missing anchor %s in %s", name, parts[1], file)
|
|
66
|
-
}
|
|
67
|
-
}
|
|
68
|
-
}
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
func hasAnchor(markdown, wanted string) bool {
|
|
72
|
-
for _, match := range explicitAnchor.FindAllStringSubmatch(markdown, -1) {
|
|
73
|
-
if match[1] == wanted {
|
|
74
|
-
return true
|
|
75
|
-
}
|
|
76
|
-
}
|
|
77
|
-
for _, line := range strings.Split(markdown, "\n") {
|
|
78
|
-
if heading := strings.TrimLeft(line, "#"); heading != line && strings.HasPrefix(heading, " ") && anchor(strings.TrimSpace(heading)) == wanted {
|
|
79
|
-
return true
|
|
80
|
-
}
|
|
81
|
-
}
|
|
82
|
-
return false
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
func TestRenderingIgnoresNonSemanticEntityOrder(t *testing.T) {
|
|
86
|
-
cat := fixtureCatalog(t)
|
|
87
|
-
want := renderedFiles(render(plugin.Request{Catalog: cat}, Options{}))
|
|
88
|
-
slices.Reverse(cat.Contexts)
|
|
89
|
-
for i := range cat.Contexts {
|
|
90
|
-
slices.Reverse(cat.Contexts[i].Services)
|
|
91
|
-
for j := range cat.Contexts[i].Services {
|
|
92
|
-
slices.Reverse(cat.Contexts[i].Services[j].Aggregates)
|
|
93
|
-
slices.Reverse(cat.Contexts[i].Services[j].Provides)
|
|
94
|
-
slices.Reverse(cat.Contexts[i].Services[j].Consumes)
|
|
95
|
-
}
|
|
96
|
-
}
|
|
97
|
-
slices.Reverse(cat.Stores)
|
|
98
|
-
slices.Reverse(cat.Modules)
|
|
99
|
-
slices.Reverse(cat.Flows)
|
|
100
|
-
slices.Reverse(cat.Adrs)
|
|
101
|
-
slices.Reverse(cat.Terms)
|
|
102
|
-
slices.Reverse(cat.Externals)
|
|
103
|
-
got := renderedFiles(render(plugin.Request{Catalog: cat}, Options{}))
|
|
104
|
-
if !reflectFiles(want, got) {
|
|
105
|
-
t.Fatal("permuting entity sets changed the generated bytes")
|
|
106
|
-
}
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
func reflectFiles(a, b map[string]string) bool {
|
|
110
|
-
if len(a) != len(b) {
|
|
111
|
-
return false
|
|
112
|
-
}
|
|
113
|
-
for name, value := range a {
|
|
114
|
-
if b[name] != value {
|
|
115
|
-
return false
|
|
116
|
-
}
|
|
117
|
-
}
|
|
118
|
-
return true
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
func TestSemanticFieldsAndImmutableSourcesAreRendered(t *testing.T) {
|
|
122
|
-
repo := "https://github.com/example/billing/blob/main"
|
|
123
|
-
cat := fixtureCatalog(t)
|
|
124
|
-
cat.Flows[0].Trigger = &catalog.FlowTrigger{Kind: "callback", Label: "POST /callbacks/paid", Confidence: "high"}
|
|
125
|
-
files := renderedFiles(render(plugin.Request{Catalog: cat}, Options{SourceBaseURL: repo}))
|
|
126
|
-
service := files["billing/invoices/README.md"]
|
|
127
|
-
for _, want := range []string{"Schema modules", "Channels", "GET", "RaiseRequest", "server", "deprecated", "flow.raise-invoice#s3", repo + "/examples/billing/api/invoices.proto"} {
|
|
128
|
-
if !strings.Contains(service, want) {
|
|
129
|
-
t.Errorf("service page omits %q", want)
|
|
130
|
-
}
|
|
131
|
-
}
|
|
132
|
-
if !strings.Contains(files["modules/example-billing.md"], "Billing contracts") {
|
|
133
|
-
t.Error("module page was not generated")
|
|
134
|
-
}
|
|
135
|
-
flow := files["flows/"+cat.Flows[0].Slug+".md"]
|
|
136
|
-
if !strings.Contains(flow, "**Trigger:** `callback` · POST /callbacks/paid") || !strings.Contains(flow, "**Root confidence:** high") {
|
|
137
|
-
t.Error("flow trigger evidence was not rendered")
|
|
138
|
-
}
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
func TestNeutralComponentDoesNotPretendToHaveADomainModel(t *testing.T) {
|
|
142
|
-
cat := catalog.Catalog{
|
|
143
|
-
GeneratedAt: "2026-09-06T00:00:00Z",
|
|
144
|
-
Commit: "abc",
|
|
145
|
-
Contexts: []catalog.BoundedContext{{
|
|
146
|
-
ID: "avia", Slug: "avia", Name: "Avia", Kind: catalog.GroupKindSystem,
|
|
147
|
-
Services: []catalog.Service{{
|
|
148
|
-
ID: "avia.support", Slug: "support", Name: "Support", Kind: catalog.ComponentKindApplication,
|
|
149
|
-
Technologies: []string{"Go", "Kafka"}, Provides: []catalog.RpcService{}, Consumes: []catalog.RpcCall{}, Aggregates: []catalog.Aggregate{},
|
|
150
|
-
}},
|
|
151
|
-
}},
|
|
152
|
-
Defs: map[string]catalog.TypeDef{}, Flows: []catalog.Flow{}, Adrs: []catalog.Adr{},
|
|
153
|
-
}
|
|
154
|
-
files := renderedFiles(render(plugin.Request{Catalog: cat}, Options{}))
|
|
155
|
-
group := files["avia/README.md"]
|
|
156
|
-
component := files["avia/support/README.md"]
|
|
157
|
-
if !strings.Contains(group, "## Components") || !strings.Contains(group, "Technologies") {
|
|
158
|
-
t.Fatal(group)
|
|
159
|
-
}
|
|
160
|
-
if !strings.Contains(component, "**Kind:** application") || !strings.Contains(component, "Go, Kafka") {
|
|
161
|
-
t.Fatal(component)
|
|
162
|
-
}
|
|
163
|
-
if strings.Contains(component, "## Aggregates") {
|
|
164
|
-
t.Fatal("neutral component gained an empty DDD section:\n" + component)
|
|
165
|
-
}
|
|
166
|
-
}
|
|
167
|
-
|
|
168
|
-
func TestExternalSourceUsesItsImmutablePin(t *testing.T) {
|
|
169
|
-
owner := &catalog.Service{Repo: "gitlab.example/acme/payments", Path: "vendor/repos/acme/payments"}
|
|
170
|
-
s := &site{cat: catalog.Catalog{Repos: []catalog.RepoPin{{Repo: owner.Repo, Commit: strings.Repeat("b", 40)}}}, opts: Options{SourceBaseURL: "https://github.com/example/billing/blob/main"}}
|
|
171
|
-
got := s.source("billing/README.md", "vendor/repos/acme/payments/api/openapi.yaml:12", owner)
|
|
172
|
-
want := "https://gitlab.example/acme/payments/-/blob/" + strings.Repeat("b", 40) + "/api/openapi.yaml#L12"
|
|
173
|
-
if !strings.Contains(got, want) {
|
|
174
|
-
t.Fatalf("source link = %s", got)
|
|
175
|
-
}
|
|
176
|
-
}
|
|
@@ -1,38 +0,0 @@
|
|
|
1
|
-
package main
|
|
2
|
-
|
|
3
|
-
import (
|
|
4
|
-
"strings"
|
|
5
|
-
"testing"
|
|
6
|
-
|
|
7
|
-
"github.com/shortlink-org/portolan/catalog"
|
|
8
|
-
"github.com/shortlink-org/portolan/plugin"
|
|
9
|
-
)
|
|
10
|
-
|
|
11
|
-
func TestRendersRedisKeyPatterns(t *testing.T) {
|
|
12
|
-
cat := catalog.Catalog{
|
|
13
|
-
Contexts: []catalog.BoundedContext{{
|
|
14
|
-
ID: "sales", Slug: "sales", Name: "Sales", Services: []catalog.Service{{
|
|
15
|
-
ID: "sales.catalog", Slug: "catalog", Name: "Catalog", Provides: []catalog.RpcService{}, Consumes: []catalog.RpcCall{}, Aggregates: []catalog.Aggregate{}, Stores: []string{"sales.catalog.redis"},
|
|
16
|
-
}},
|
|
17
|
-
}},
|
|
18
|
-
Defs: map[string]catalog.TypeDef{}, Flows: []catalog.Flow{}, Adrs: []catalog.Adr{},
|
|
19
|
-
Stores: []catalog.Store{{
|
|
20
|
-
ID: "sales.catalog.redis", Slug: "redis", Name: "Catalog Redis", Kind: catalog.StoreKindRedis, Owner: "sales.catalog", Tables: []catalog.Table{},
|
|
21
|
-
Keyspaces: []catalog.RedisKeyspace{{Pattern: "session:{id}", Operations: []catalog.RedisOperation{catalog.RedisOperationRead, catalog.RedisOperationWrite}, TTL: "24h", Value: "Session", Source: "cache.go:20"}},
|
|
22
|
-
}},
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
response := render(plugin.Request{Catalog: cat}, Options{})
|
|
26
|
-
var page string
|
|
27
|
-
for _, file := range response.Files {
|
|
28
|
-
if file.Name == "sales/catalog/stores/redis.md" {
|
|
29
|
-
page = file.Contents
|
|
30
|
-
break
|
|
31
|
-
}
|
|
32
|
-
}
|
|
33
|
-
for _, want := range []string{"## Redis key patterns", "`session:{id}`", "read, write", "`Session`", "`24h`", "cache.go"} {
|
|
34
|
-
if !strings.Contains(page, want) {
|
|
35
|
-
t.Fatalf("Redis store page does not contain %q:\n%s", want, page)
|
|
36
|
-
}
|
|
37
|
-
}
|
|
38
|
-
}
|
|
@@ -1,344 +0,0 @@
|
|
|
1
|
-
package main
|
|
2
|
-
|
|
3
|
-
import (
|
|
4
|
-
"sort"
|
|
5
|
-
"strings"
|
|
6
|
-
|
|
7
|
-
"github.com/shortlink-org/portolan/catalog"
|
|
8
|
-
"github.com/shortlink-org/portolan/plugin"
|
|
9
|
-
)
|
|
10
|
-
|
|
11
|
-
// site is the whole render: the catalog, the place every entity's page will
|
|
12
|
-
// live, and the lookups that turn an id in the data into a link on a page.
|
|
13
|
-
//
|
|
14
|
-
// The layout is computed BEFORE anything is written. A page that mentions an
|
|
15
|
-
// aggregate has to know where the aggregate's page will be, and working that
|
|
16
|
-
// out at the moment of writing is how two pages end up disagreeing.
|
|
17
|
-
type site struct {
|
|
18
|
-
cat catalog.Catalog
|
|
19
|
-
opts Options
|
|
20
|
-
b *builder
|
|
21
|
-
|
|
22
|
-
// pathOf maps a catalog id to the file that documents it. An id absent
|
|
23
|
-
// from this map belongs to another source, which is a fact worth saying
|
|
24
|
-
// rather than a link worth guessing.
|
|
25
|
-
pathOf map[string]string
|
|
26
|
-
|
|
27
|
-
services map[string]*catalog.Service
|
|
28
|
-
contextOf map[string]*catalog.BoundedContext
|
|
29
|
-
stores map[string]*catalog.Store
|
|
30
|
-
modules map[string]*catalog.ProtoModule
|
|
31
|
-
// eventPage maps an event id to the aggregate that publishes it, which is
|
|
32
|
-
// the page that actually documents it.
|
|
33
|
-
eventPage map[string]string
|
|
34
|
-
// wireEvent resolves the message name used by an AsyncAPI channel to the
|
|
35
|
-
// event page that documents that message's versions.
|
|
36
|
-
wireEvent map[string]string
|
|
37
|
-
// relationStore and relationName resolve a table or view id, which is what
|
|
38
|
-
// foreign keys and lineage are written in, to the store that holds it and
|
|
39
|
-
// the heading that documents it.
|
|
40
|
-
relationStore map[string]string
|
|
41
|
-
relationName map[string]string
|
|
42
|
-
// adrsFor is keyed by what an ADR is scoped to: "org", a context id, or a
|
|
43
|
-
// service id.
|
|
44
|
-
adrsFor map[string][]*catalog.Adr
|
|
45
|
-
// termsOf is a context's vocabulary, alphabetical. Keyed by context and
|
|
46
|
-
// not by service: a glossary belongs to the boundary, and the file it was
|
|
47
|
-
// read from happens to sit beside one of the services inside it.
|
|
48
|
-
termsOf map[string][]*catalog.Term
|
|
49
|
-
// methodOf is every interface method by "<interface>/<method>", which is
|
|
50
|
-
// how a flow step names the call it makes.
|
|
51
|
-
methodOf map[string]catalog.RpcMethod
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
func render(req plugin.Request, opts Options) plugin.Response {
|
|
55
|
-
s := &site{
|
|
56
|
-
cat: canonicalCatalog(req.Catalog),
|
|
57
|
-
opts: opts,
|
|
58
|
-
b: &builder{},
|
|
59
|
-
pathOf: map[string]string{},
|
|
60
|
-
services: map[string]*catalog.Service{},
|
|
61
|
-
contextOf: map[string]*catalog.BoundedContext{},
|
|
62
|
-
stores: map[string]*catalog.Store{},
|
|
63
|
-
modules: map[string]*catalog.ProtoModule{},
|
|
64
|
-
eventPage: map[string]string{},
|
|
65
|
-
wireEvent: map[string]string{},
|
|
66
|
-
adrsFor: map[string][]*catalog.Adr{},
|
|
67
|
-
termsOf: map[string][]*catalog.Term{},
|
|
68
|
-
methodOf: map[string]catalog.RpcMethod{},
|
|
69
|
-
|
|
70
|
-
relationStore: map[string]string{},
|
|
71
|
-
relationName: map[string]string{},
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
s.layout()
|
|
75
|
-
s.renderIndex()
|
|
76
|
-
s.renderTypes()
|
|
77
|
-
s.renderModules()
|
|
78
|
-
for i := range s.cat.Contexts {
|
|
79
|
-
s.renderContext(&s.cat.Contexts[i])
|
|
80
|
-
}
|
|
81
|
-
for i := range s.cat.Stores {
|
|
82
|
-
s.renderStore(&s.cat.Stores[i])
|
|
83
|
-
}
|
|
84
|
-
s.renderExternals()
|
|
85
|
-
s.renderFlows()
|
|
86
|
-
s.renderAdrs()
|
|
87
|
-
s.renderLlms()
|
|
88
|
-
|
|
89
|
-
return s.b.Response()
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
// layout decides where everything goes and builds the lookups. Nothing is
|
|
93
|
-
// rendered here.
|
|
94
|
-
func (s *site) layout() {
|
|
95
|
-
for i := range s.cat.Contexts {
|
|
96
|
-
ctx := &s.cat.Contexts[i]
|
|
97
|
-
s.pathOf[ctx.ID] = ctx.Slug + "/README.md"
|
|
98
|
-
|
|
99
|
-
for j := range ctx.Services {
|
|
100
|
-
svc := &ctx.Services[j]
|
|
101
|
-
dir := ctx.Slug + "/" + svc.Slug
|
|
102
|
-
s.pathOf[svc.ID] = dir + "/README.md"
|
|
103
|
-
s.services[svc.ID] = svc
|
|
104
|
-
for j := range svc.Provides {
|
|
105
|
-
provided := &svc.Provides[j]
|
|
106
|
-
for k := range provided.Methods {
|
|
107
|
-
s.methodOf[provided.ID+"/"+provided.Methods[k].Name] = provided.Methods[k]
|
|
108
|
-
}
|
|
109
|
-
}
|
|
110
|
-
s.contextOf[svc.ID] = ctx
|
|
111
|
-
|
|
112
|
-
for k := range svc.Aggregates {
|
|
113
|
-
agg := &svc.Aggregates[k]
|
|
114
|
-
s.pathOf[agg.ID] = dir + "/aggregates/" + agg.Slug + ".md"
|
|
115
|
-
|
|
116
|
-
for e := range agg.Events {
|
|
117
|
-
s.eventPage[agg.Events[e].ID] = agg.ID
|
|
118
|
-
if wire := agg.Events[e].Wire; wire != nil && wire.Name != "" {
|
|
119
|
-
s.wireEvent[wire.Name] = agg.Events[e].ID
|
|
120
|
-
}
|
|
121
|
-
}
|
|
122
|
-
}
|
|
123
|
-
}
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
for i := range s.cat.Modules {
|
|
127
|
-
module := &s.cat.Modules[i]
|
|
128
|
-
s.modules[module.ID] = module
|
|
129
|
-
s.pathOf[module.ID] = "modules/" + module.Slug + ".md"
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
for i := range s.cat.Stores {
|
|
133
|
-
store := &s.cat.Stores[i]
|
|
134
|
-
s.stores[store.ID] = store
|
|
135
|
-
|
|
136
|
-
for j := range store.Tables {
|
|
137
|
-
s.relationStore[store.Tables[j].ID] = store.ID
|
|
138
|
-
s.relationName[store.Tables[j].ID] = store.Tables[j].Name
|
|
139
|
-
}
|
|
140
|
-
for j := range store.Views {
|
|
141
|
-
s.relationStore[store.Views[j].ID] = store.ID
|
|
142
|
-
s.relationName[store.Views[j].ID] = store.Views[j].Name
|
|
143
|
-
}
|
|
144
|
-
|
|
145
|
-
// A store lives under the service that owns it. When the owner is in
|
|
146
|
-
// another source there is no such directory, so it sits at the root
|
|
147
|
-
// and the service page that would have linked it says why.
|
|
148
|
-
if ownerPath, ok := s.pathOf[store.Owner]; ok {
|
|
149
|
-
s.pathOf[store.ID] = parent(ownerPath) + "/stores/" + store.Slug + ".md"
|
|
150
|
-
} else {
|
|
151
|
-
s.pathOf[store.ID] = "stores/" + store.ID + ".md"
|
|
152
|
-
s.b.warn(store.ID, "store %q is owned by %q, which is not in this catalog; its page sits at the root", store.ID, store.Owner)
|
|
153
|
-
}
|
|
154
|
-
}
|
|
155
|
-
|
|
156
|
-
// An external sits at the root of the tree as it sits at the root of the
|
|
157
|
-
// catalog: no context owns it, so no context's directory holds it.
|
|
158
|
-
for i := range s.cat.Externals {
|
|
159
|
-
ext := &s.cat.Externals[i]
|
|
160
|
-
s.pathOf[ext.ID] = "externals/" + ext.Slug + ".md"
|
|
161
|
-
for j := range ext.Provides {
|
|
162
|
-
provided := &ext.Provides[j]
|
|
163
|
-
for k := range provided.Methods {
|
|
164
|
-
s.methodOf[provided.ID+"/"+provided.Methods[k].Name] = provided.Methods[k]
|
|
165
|
-
}
|
|
166
|
-
}
|
|
167
|
-
}
|
|
168
|
-
|
|
169
|
-
for i := range s.cat.Flows {
|
|
170
|
-
s.pathOf[s.cat.Flows[i].ID] = "flows/" + s.cat.Flows[i].Slug + ".md"
|
|
171
|
-
}
|
|
172
|
-
|
|
173
|
-
for i := range s.cat.Terms {
|
|
174
|
-
term := &s.cat.Terms[i]
|
|
175
|
-
s.termsOf[term.Context] = append(s.termsOf[term.Context], term)
|
|
176
|
-
}
|
|
177
|
-
for _, terms := range s.termsOf {
|
|
178
|
-
sort.Slice(terms, func(a, b int) bool {
|
|
179
|
-
return strings.ToLower(terms[a].Name) < strings.ToLower(terms[b].Name)
|
|
180
|
-
})
|
|
181
|
-
}
|
|
182
|
-
|
|
183
|
-
for i := range s.cat.Adrs {
|
|
184
|
-
adr := &s.cat.Adrs[i]
|
|
185
|
-
s.pathOf[adr.ID] = "adr/" + adr.ID + ".md"
|
|
186
|
-
|
|
187
|
-
switch adr.Scope.Kind {
|
|
188
|
-
case "context":
|
|
189
|
-
s.adrsFor[adr.Scope.Context] = append(s.adrsFor[adr.Scope.Context], adr)
|
|
190
|
-
case "service":
|
|
191
|
-
s.adrsFor[adr.Scope.Service] = append(s.adrsFor[adr.Scope.Service], adr)
|
|
192
|
-
default:
|
|
193
|
-
s.adrsFor["org"] = append(s.adrsFor["org"], adr)
|
|
194
|
-
}
|
|
195
|
-
}
|
|
196
|
-
}
|
|
197
|
-
|
|
198
|
-
// ref renders a link to another page, or plain code when the target is not in
|
|
199
|
-
// this catalog. The second case is the normal one for a merged estate: an
|
|
200
|
-
// event consumer, an rpc peer or a store owner routinely lives in a source
|
|
201
|
-
// this render never saw.
|
|
202
|
-
func (s *site) ref(from, id, text string) string {
|
|
203
|
-
if text == "" {
|
|
204
|
-
text = id
|
|
205
|
-
}
|
|
206
|
-
if to, ok := s.pathOf[id]; ok {
|
|
207
|
-
return link(text, from, to)
|
|
208
|
-
}
|
|
209
|
-
|
|
210
|
-
return code(text)
|
|
211
|
-
}
|
|
212
|
-
|
|
213
|
-
func (s *site) renderIndex() {
|
|
214
|
-
const self = "README.md"
|
|
215
|
-
|
|
216
|
-
var b strings.Builder
|
|
217
|
-
b.WriteString("# " + s.title() + "\n\n")
|
|
218
|
-
b.WriteString(s.stamp() + "\n")
|
|
219
|
-
|
|
220
|
-
rows := make([][]string, 0, len(s.cat.Contexts))
|
|
221
|
-
for i := range s.cat.Contexts {
|
|
222
|
-
ctx := &s.cat.Contexts[i]
|
|
223
|
-
names := make([]string, 0, len(ctx.Services))
|
|
224
|
-
for j := range ctx.Services {
|
|
225
|
-
names = append(names, s.ref(self, ctx.Services[j].ID, ctx.Services[j].Name))
|
|
226
|
-
}
|
|
227
|
-
rows = append(rows, []string{
|
|
228
|
-
s.ref(self, ctx.ID, ctx.Name),
|
|
229
|
-
string(ctx.Classification),
|
|
230
|
-
strings.Join(names, ", "),
|
|
231
|
-
firstLine(ctx.Summary),
|
|
232
|
-
})
|
|
233
|
-
}
|
|
234
|
-
section(&b, "Contexts", table([]string{"Context", "Class", "Services", "Summary"}, rows))
|
|
235
|
-
|
|
236
|
-
// The systems the estate calls and does not build, beside the contexts
|
|
237
|
-
// rather than inside one: the reader asking "what do we depend on out
|
|
238
|
-
// there" should not have to open every service to find out.
|
|
239
|
-
externals := make([][]string, 0, len(s.cat.Externals))
|
|
240
|
-
for i := range s.cat.Externals {
|
|
241
|
-
ext := &s.cat.Externals[i]
|
|
242
|
-
interfaces := make([]string, 0, len(ext.Provides))
|
|
243
|
-
for j := range ext.Provides {
|
|
244
|
-
interfaces = append(interfaces, code(ext.Provides[j].ID))
|
|
245
|
-
}
|
|
246
|
-
externals = append(externals, []string{
|
|
247
|
-
s.ref(self, ext.ID, orDefault(ext.Name, ext.ID)),
|
|
248
|
-
strings.Join(interfaces, ", "),
|
|
249
|
-
firstLine(ext.Summary),
|
|
250
|
-
})
|
|
251
|
-
}
|
|
252
|
-
section(&b, "Outside the estate", table([]string{"System", "Interfaces", "Summary"}, externals))
|
|
253
|
-
|
|
254
|
-
modules := make([][]string, 0, len(s.cat.Modules))
|
|
255
|
-
for i := range s.cat.Modules {
|
|
256
|
-
module := &s.cat.Modules[i]
|
|
257
|
-
modules = append(modules, []string{
|
|
258
|
-
s.ref(self, module.ID, orDefault(module.Name, module.ID)),
|
|
259
|
-
s.ref(self, module.Owner, module.Owner),
|
|
260
|
-
module.Registry,
|
|
261
|
-
plural(len(module.Packages), "package"),
|
|
262
|
-
})
|
|
263
|
-
}
|
|
264
|
-
section(&b, "Schema modules", table([]string{"Module", "Publisher", "Registry", "Packages"}, modules))
|
|
265
|
-
|
|
266
|
-
flows := make([][]string, 0, len(s.cat.Flows))
|
|
267
|
-
for i := range s.cat.Flows {
|
|
268
|
-
flow := &s.cat.Flows[i]
|
|
269
|
-
owner := "—"
|
|
270
|
-
if flow.Owner != "" {
|
|
271
|
-
owner = s.ref(self, flow.Owner, flow.Owner)
|
|
272
|
-
}
|
|
273
|
-
flows = append(flows, []string{
|
|
274
|
-
s.ref(self, flow.ID, flow.Name),
|
|
275
|
-
owner,
|
|
276
|
-
firstLine(flow.Summary),
|
|
277
|
-
})
|
|
278
|
-
}
|
|
279
|
-
section(&b, "Flows", table([]string{"Flow", "Owner", "Summary"}, flows))
|
|
280
|
-
|
|
281
|
-
adrs := make([][]string, 0, len(s.cat.Adrs))
|
|
282
|
-
for i := range s.cat.Adrs {
|
|
283
|
-
adr := &s.cat.Adrs[i]
|
|
284
|
-
adrs = append(adrs, []string{
|
|
285
|
-
s.ref(self, adr.ID, adr.ID),
|
|
286
|
-
adr.Title,
|
|
287
|
-
string(adr.Status),
|
|
288
|
-
adr.Date,
|
|
289
|
-
})
|
|
290
|
-
}
|
|
291
|
-
section(&b, "Decisions", table([]string{"ADR", "Title", "Status", "Date"}, adrs))
|
|
292
|
-
|
|
293
|
-
s.b.file(self, b.String())
|
|
294
|
-
}
|
|
295
|
-
|
|
296
|
-
func (s *site) title() string {
|
|
297
|
-
if s.opts.Title != "" {
|
|
298
|
-
return s.opts.Title
|
|
299
|
-
}
|
|
300
|
-
|
|
301
|
-
return "Architecture catalog"
|
|
302
|
-
}
|
|
303
|
-
|
|
304
|
-
// stamp is the provenance line every page carries. A generated page that does
|
|
305
|
-
// not say what it was generated from is indistinguishable from a hand-written
|
|
306
|
-
// one that has gone stale.
|
|
307
|
-
func (s *site) stamp() string {
|
|
308
|
-
parts := []string{"Generated from the portolan catalog"}
|
|
309
|
-
if s.cat.Commit != "" {
|
|
310
|
-
parts = append(parts, "commit "+code(s.cat.Commit))
|
|
311
|
-
}
|
|
312
|
-
if s.cat.GeneratedAt != "" {
|
|
313
|
-
parts = append(parts, "at "+s.cat.GeneratedAt)
|
|
314
|
-
}
|
|
315
|
-
|
|
316
|
-
return "*" + strings.Join(parts, " · ") + ". Do not edit by hand.*\n"
|
|
317
|
-
}
|
|
318
|
-
|
|
319
|
-
func firstLine(s string) string {
|
|
320
|
-
if i := strings.Index(s, "\n"); i >= 0 {
|
|
321
|
-
return strings.TrimSpace(s[:i])
|
|
322
|
-
}
|
|
323
|
-
|
|
324
|
-
return strings.TrimSpace(s)
|
|
325
|
-
}
|
|
326
|
-
|
|
327
|
-
// parent is the directory a page sits in, as a slash path.
|
|
328
|
-
func parent(page string) string {
|
|
329
|
-
if i := strings.LastIndex(page, "/"); i >= 0 {
|
|
330
|
-
return page[:i]
|
|
331
|
-
}
|
|
332
|
-
|
|
333
|
-
return "."
|
|
334
|
-
}
|
|
335
|
-
|
|
336
|
-
func sortedDefKeys(defs map[string]catalog.TypeDef) []string {
|
|
337
|
-
keys := make([]string, 0, len(defs))
|
|
338
|
-
for k := range defs {
|
|
339
|
-
keys = append(keys, k)
|
|
340
|
-
}
|
|
341
|
-
sort.Strings(keys)
|
|
342
|
-
|
|
343
|
-
return keys
|
|
344
|
-
}
|