@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,405 +0,0 @@
|
|
|
1
|
-
package main
|
|
2
|
-
|
|
3
|
-
import (
|
|
4
|
-
"strings"
|
|
5
|
-
"testing"
|
|
6
|
-
|
|
7
|
-
"github.com/shortlink-org/portolan/catalog"
|
|
8
|
-
)
|
|
9
|
-
|
|
10
|
-
const sample = `# auth.0003 — Session expiry publishes no event
|
|
11
|
-
|
|
12
|
-
- **Status:** accepted
|
|
13
|
-
- **Date:** 2026-08-22
|
|
14
|
-
- **Scope:** auth.auth
|
|
15
|
-
- **Supersedes:** auth.0001, auth.0002
|
|
16
|
-
- **Relates:** auth.auth.session.SessionEnded, shop.cart, checkout
|
|
17
|
-
- **Note:** how a revocation is kept out of the cache was decided again in
|
|
18
|
-
auth.0010; the drop turned out not to be enough.
|
|
19
|
-
|
|
20
|
-
## Context and Problem Statement
|
|
21
|
-
|
|
22
|
-
A session stops being usable at ` + "`ExpiresAt`" + `.
|
|
23
|
-
|
|
24
|
-
### An aside
|
|
25
|
-
|
|
26
|
-
Kept as written.
|
|
27
|
-
|
|
28
|
-
## Decision Outcome
|
|
29
|
-
|
|
30
|
-
No event.
|
|
31
|
-
`
|
|
32
|
-
|
|
33
|
-
func parsed(t *testing.T, src string) catalog.Adr {
|
|
34
|
-
t.Helper()
|
|
35
|
-
|
|
36
|
-
adr, errs := parseAdr("examples/auth/docs/adr/0003-expiry-publishes-nothing.md", src, defaults{})
|
|
37
|
-
if len(errs) > 0 {
|
|
38
|
-
t.Fatalf("errors: %s", strings.Join(errs, "\n"))
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
return adr
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
func refused(t *testing.T, src string) string {
|
|
45
|
-
t.Helper()
|
|
46
|
-
|
|
47
|
-
_, errs := parseAdr("examples/auth/docs/adr/0003-expiry-publishes-nothing.md", src, defaults{})
|
|
48
|
-
if len(errs) == 0 {
|
|
49
|
-
t.Fatal("parsed without complaint")
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
return strings.Join(errs, "\n")
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
// Replaces one meta bullet with another, so a test says what it is about and
|
|
56
|
-
// nothing else.
|
|
57
|
-
func with(bullet, replacement string) string {
|
|
58
|
-
return strings.Replace(sample, bullet, replacement, 1)
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
func TestTheTitleCarriesTheIDAndTheTitle(t *testing.T) {
|
|
62
|
-
adr := parsed(t, sample)
|
|
63
|
-
if adr.ID != "auth.0003" || adr.Number != 3 || adr.Title != "Session expiry publishes no event" {
|
|
64
|
-
t.Errorf("id %q, number %d, title %q", adr.ID, adr.Number, adr.Title)
|
|
65
|
-
}
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
// The slug is the id with its dots opened out, then the file's own kebab: the
|
|
69
|
-
// id says which record this is and the file's name says what it was about,
|
|
70
|
-
// which is the slug the catalog carried when these were written by hand.
|
|
71
|
-
func TestTheSlugIsTheIDAndTheFileName(t *testing.T) {
|
|
72
|
-
adr := parsed(t, sample)
|
|
73
|
-
if adr.Slug != "auth-0003-expiry-publishes-nothing" {
|
|
74
|
-
t.Errorf("slug = %q", adr.Slug)
|
|
75
|
-
}
|
|
76
|
-
if adr.Source != "examples/auth/docs/adr/0003-expiry-publishes-nothing.md" {
|
|
77
|
-
t.Errorf("source = %q", adr.Source)
|
|
78
|
-
}
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
func TestAFileNumberedAwayFromItsRecordIsRefused(t *testing.T) {
|
|
82
|
-
_, errs := parseAdr("docs/adr/0009-expiry-publishes-nothing.md", sample, defaults{})
|
|
83
|
-
if len(errs) != 1 || !strings.Contains(errs[0], "numbered 0009") {
|
|
84
|
-
t.Errorf("errors: %v", errs)
|
|
85
|
-
}
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
func TestTheMetaBulletsAreRead(t *testing.T) {
|
|
89
|
-
adr := parsed(t, sample)
|
|
90
|
-
if adr.Status != catalog.AdrAccepted || adr.Date != "2026-08-22" {
|
|
91
|
-
t.Errorf("status %q, date %q", adr.Status, adr.Date)
|
|
92
|
-
}
|
|
93
|
-
if adr.Scope != (catalog.AdrScope{Kind: "service", Service: "auth.auth"}) {
|
|
94
|
-
t.Errorf("scope = %+v", adr.Scope)
|
|
95
|
-
}
|
|
96
|
-
if strings.Join(adr.Supersedes, ",") != "auth.0001,auth.0002" {
|
|
97
|
-
t.Errorf("supersedes = %v", adr.Supersedes)
|
|
98
|
-
}
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
// A bullet wrapped onto the next line is one bullet: the break is the author's
|
|
102
|
-
// line width and carries no meaning, so it closes up into a space.
|
|
103
|
-
func TestAWrappedBulletIsOneValue(t *testing.T) {
|
|
104
|
-
adr := parsed(t, sample)
|
|
105
|
-
want := "how a revocation is kept out of the cache was decided again in auth.0010; the drop turned out not to be enough."
|
|
106
|
-
if adr.Note != want {
|
|
107
|
-
t.Errorf("note = %q", adr.Note)
|
|
108
|
-
}
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
// The scope's shape says which of the three kinds it is, and nothing else can:
|
|
112
|
-
// an extractor sees one service's tree and has no catalog to look a name up in.
|
|
113
|
-
func TestScopeKindComesFromTheNumberOfSegments(t *testing.T) {
|
|
114
|
-
for _, c := range []struct {
|
|
115
|
-
written string
|
|
116
|
-
want catalog.AdrScope
|
|
117
|
-
}{
|
|
118
|
-
{"org", catalog.AdrScope{Kind: "org"}},
|
|
119
|
-
{"", catalog.AdrScope{Kind: "org"}},
|
|
120
|
-
{"payments", catalog.AdrScope{Kind: "context", Context: "payments"}},
|
|
121
|
-
{"shop.cart", catalog.AdrScope{Kind: "service", Service: "shop.cart"}},
|
|
122
|
-
} {
|
|
123
|
-
adr := parsed(t, with("- **Scope:** auth.auth", "- **Scope:** "+c.written))
|
|
124
|
-
if adr.Scope != c.want {
|
|
125
|
-
t.Errorf("%q read as %+v, want %+v", c.written, adr.Scope, c.want)
|
|
126
|
-
}
|
|
127
|
-
}
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
// One list in the file, three in the catalog. An author should not have to
|
|
131
|
-
// remember which of the three a name belongs in, so the name's shape says.
|
|
132
|
-
func TestRelatesIsSortedByShape(t *testing.T) {
|
|
133
|
-
adr := parsed(t, sample)
|
|
134
|
-
if strings.Join(adr.Relates.Events, ",") != "auth.auth.session.SessionEnded" {
|
|
135
|
-
t.Errorf("events = %v", adr.Relates.Events)
|
|
136
|
-
}
|
|
137
|
-
if strings.Join(adr.Relates.Services, ",") != "shop.cart" {
|
|
138
|
-
t.Errorf("services = %v", adr.Relates.Services)
|
|
139
|
-
}
|
|
140
|
-
if strings.Join(adr.Relates.Flows, ",") != "checkout" {
|
|
141
|
-
t.Errorf("flows = %v", adr.Relates.Flows)
|
|
142
|
-
}
|
|
143
|
-
}
|
|
144
|
-
|
|
145
|
-
func TestARelationOfNoRecognisableShapeIsRefused(t *testing.T) {
|
|
146
|
-
errs := refused(t, with("- **Relates:** auth.auth.session.SessionEnded, shop.cart, checkout", "- **Relates:** auth.auth.session"))
|
|
147
|
-
if !strings.Contains(errs, "not the shape of an event, a service or a flow") {
|
|
148
|
-
t.Errorf("errors: %s", errs)
|
|
149
|
-
}
|
|
150
|
-
}
|
|
151
|
-
|
|
152
|
-
// The body is frozen history. It starts at the first `##` and is carried into
|
|
153
|
-
// the catalog exactly as written, headings and all, because nothing on an ADR
|
|
154
|
-
// page is ever regenerated from the model as it stands now.
|
|
155
|
-
func TestTheBodyStartsAtTheFirstSectionAndIsUnedited(t *testing.T) {
|
|
156
|
-
adr := parsed(t, sample)
|
|
157
|
-
if !strings.HasPrefix(adr.Body, "## Context and Problem Statement\n") {
|
|
158
|
-
t.Errorf("body starts %q", adr.Body[:40])
|
|
159
|
-
}
|
|
160
|
-
if !strings.Contains(adr.Body, "### An aside") || !strings.Contains(adr.Body, "`ExpiresAt`") {
|
|
161
|
-
t.Errorf("body = %q", adr.Body)
|
|
162
|
-
}
|
|
163
|
-
if !strings.HasSuffix(adr.Body, "No event.\n") {
|
|
164
|
-
t.Errorf("body ends %q", adr.Body[len(adr.Body)-20:])
|
|
165
|
-
}
|
|
166
|
-
}
|
|
167
|
-
|
|
168
|
-
func TestWhatARecordCannotBeReadWithout(t *testing.T) {
|
|
169
|
-
for key, bullet := range map[string]string{
|
|
170
|
-
"Status": "- **Status:** accepted",
|
|
171
|
-
"Date": "- **Date:** 2026-08-22",
|
|
172
|
-
"Scope": "- **Scope:** auth.auth",
|
|
173
|
-
} {
|
|
174
|
-
errs := refused(t, strings.Replace(sample, bullet+"\n", "", 1))
|
|
175
|
-
if !strings.Contains(errs, "says no \""+key+"\"") {
|
|
176
|
-
t.Errorf("dropping %s gave: %s", key, errs)
|
|
177
|
-
}
|
|
178
|
-
}
|
|
179
|
-
}
|
|
180
|
-
|
|
181
|
-
func TestAStatusOrDateThatIsNeitherIsRefused(t *testing.T) {
|
|
182
|
-
if errs := refused(t, with("- **Status:** accepted", "- **Status:** agreed")); !strings.Contains(errs, "is not a status") {
|
|
183
|
-
t.Errorf("errors: %s", errs)
|
|
184
|
-
}
|
|
185
|
-
if errs := refused(t, with("- **Date:** 2026-08-22", "- **Date:** last August")); !strings.Contains(errs, "is not a date") {
|
|
186
|
-
t.Errorf("errors: %s", errs)
|
|
187
|
-
}
|
|
188
|
-
}
|
|
189
|
-
|
|
190
|
-
// Supersession is a two-way fact, and half of it recorded is a bug. Both
|
|
191
|
-
// halves that live in one file are held against each other here; the halves in
|
|
192
|
-
// two files are held against each other by extract.
|
|
193
|
-
func TestHalfARecordedSupersessionIsRefused(t *testing.T) {
|
|
194
|
-
errs := refused(t, with("- **Status:** accepted", "- **Status:** superseded"))
|
|
195
|
-
if !strings.Contains(errs, "superseded and says by what") {
|
|
196
|
-
t.Errorf("errors: %s", errs)
|
|
197
|
-
}
|
|
198
|
-
|
|
199
|
-
errs = refused(t, with("- **Supersedes:** auth.0001, auth.0002", "- **Superseded by:** auth.0007"))
|
|
200
|
-
if !strings.Contains(errs, `its status is "accepted"`) {
|
|
201
|
-
t.Errorf("errors: %s", errs)
|
|
202
|
-
}
|
|
203
|
-
}
|
|
204
|
-
|
|
205
|
-
func TestABulletTheFormatDoesNotHaveIsRefused(t *testing.T) {
|
|
206
|
-
errs := refused(t, with("- **Note:**", "- **Owner:**"))
|
|
207
|
-
if !strings.Contains(errs, "is not one of the bullets a record carries") {
|
|
208
|
-
t.Errorf("errors: %s", errs)
|
|
209
|
-
}
|
|
210
|
-
}
|
|
211
|
-
|
|
212
|
-
// The head is bullets and nothing else. A paragraph that drifted above the
|
|
213
|
-
// first `##` would be read by a person and dropped by the extractor, and the
|
|
214
|
-
// record on the page would quietly be missing its opening.
|
|
215
|
-
func TestProseBetweenTheTitleAndTheRecordIsRefused(t *testing.T) {
|
|
216
|
-
errs := refused(t, with("## Context and Problem Statement", "A stray paragraph.\n\n## Context and Problem Statement"))
|
|
217
|
-
if !strings.Contains(errs, "only meta bullets belong") {
|
|
218
|
-
t.Errorf("errors: %s", errs)
|
|
219
|
-
}
|
|
220
|
-
}
|
|
221
|
-
|
|
222
|
-
func TestARecordWithNoBodyIsRefused(t *testing.T) {
|
|
223
|
-
errs := refused(t, "# auth.0003 — A title\n\n- **Status:** accepted\n- **Date:** 2026-08-22\n- **Scope:** auth.auth\n")
|
|
224
|
-
if !strings.Contains(errs, "no body") {
|
|
225
|
-
t.Errorf("errors: %s", errs)
|
|
226
|
-
}
|
|
227
|
-
}
|
|
228
|
-
|
|
229
|
-
func TestATitleThatIsNotOneIsRefused(t *testing.T) {
|
|
230
|
-
if errs := refused(t, strings.Replace(sample, "auth.0003 — Session", "auth.0003 - Session", 1)); !strings.Contains(errs, "em dash") {
|
|
231
|
-
t.Errorf("errors: %s", errs)
|
|
232
|
-
}
|
|
233
|
-
if errs := refused(t, strings.Replace(sample, "auth.0003 —", "auth.3 —", 1)); !strings.Contains(errs, "four padded digits") {
|
|
234
|
-
t.Errorf("errors: %s", errs)
|
|
235
|
-
}
|
|
236
|
-
}
|
|
237
|
-
|
|
238
|
-
// Every mistake in a header, not the first one: a file with two typos in it
|
|
239
|
-
// should be fixed once.
|
|
240
|
-
func TestEveryMistakeIsReported(t *testing.T) {
|
|
241
|
-
src := with("- **Status:** accepted", "- **Status:** agreed")
|
|
242
|
-
src = strings.Replace(src, "- **Date:** 2026-08-22", "- **Date:** last August", 1)
|
|
243
|
-
if _, errs := parseAdr("docs/adr/0003-expiry-publishes-nothing.md", src, defaults{}); len(errs) != 2 {
|
|
244
|
-
t.Errorf("errors: %v", errs)
|
|
245
|
-
}
|
|
246
|
-
}
|
|
247
|
-
|
|
248
|
-
// The other format: what adr-tools writes. The number and the title are on the
|
|
249
|
-
// title line, the date is a line of its own, and the status is the record's
|
|
250
|
-
// first section. The id's prefix and the scope come from the step, because
|
|
251
|
-
// the format has no place for either.
|
|
252
|
-
const toolsSample = `# 3. Use DTO
|
|
253
|
-
|
|
254
|
-
Date: 2024-09-10
|
|
255
|
-
|
|
256
|
-
## Status
|
|
257
|
-
|
|
258
|
-
Accepted
|
|
259
|
-
|
|
260
|
-
## Context
|
|
261
|
-
|
|
262
|
-
Every supplier answers in its own shape.
|
|
263
|
-
|
|
264
|
-
## Decision
|
|
265
|
-
|
|
266
|
-
Translate at the boundary.
|
|
267
|
-
`
|
|
268
|
-
|
|
269
|
-
func parsedTools(t *testing.T, src string, scope string) catalog.Adr {
|
|
270
|
-
t.Helper()
|
|
271
|
-
|
|
272
|
-
adr, errs := parseAdr("etg/aviasupp/docs/ADR/0003-use-dto.md", src, defaults{Scope: scope})
|
|
273
|
-
if len(errs) > 0 {
|
|
274
|
-
t.Fatalf("errors: %s", strings.Join(errs, "\n"))
|
|
275
|
-
}
|
|
276
|
-
|
|
277
|
-
return adr
|
|
278
|
-
}
|
|
279
|
-
|
|
280
|
-
func refusedTools(t *testing.T, src string) string {
|
|
281
|
-
t.Helper()
|
|
282
|
-
|
|
283
|
-
_, errs := parseAdr("etg/aviasupp/docs/ADR/0003-use-dto.md", src, defaults{Scope: "avia.aviasupp"})
|
|
284
|
-
if len(errs) == 0 {
|
|
285
|
-
t.Fatal("parsed without complaint")
|
|
286
|
-
}
|
|
287
|
-
|
|
288
|
-
return strings.Join(errs, "\n")
|
|
289
|
-
}
|
|
290
|
-
|
|
291
|
-
func TestAnAdrToolsRecordIsRead(t *testing.T) {
|
|
292
|
-
adr := parsedTools(t, toolsSample, "avia.aviasupp")
|
|
293
|
-
if adr.ID != "aviasupp.0003" || adr.Number != 3 || adr.Title != "Use DTO" {
|
|
294
|
-
t.Errorf("id %q, number %d, title %q", adr.ID, adr.Number, adr.Title)
|
|
295
|
-
}
|
|
296
|
-
if adr.Slug != "aviasupp-0003-use-dto" {
|
|
297
|
-
t.Errorf("slug = %q", adr.Slug)
|
|
298
|
-
}
|
|
299
|
-
if adr.Status != catalog.AdrAccepted || adr.Date != "2024-09-10" {
|
|
300
|
-
t.Errorf("status %q, date %q", adr.Status, adr.Date)
|
|
301
|
-
}
|
|
302
|
-
if adr.Scope.Kind != "service" || adr.Scope.Service != "avia.aviasupp" {
|
|
303
|
-
t.Errorf("scope = %+v", adr.Scope)
|
|
304
|
-
}
|
|
305
|
-
// The status section is part of the record, and the record is kept whole.
|
|
306
|
-
if !strings.HasPrefix(adr.Body, "## Status\n\nAccepted\n\n## Context") {
|
|
307
|
-
t.Errorf("body = %q", adr.Body)
|
|
308
|
-
}
|
|
309
|
-
}
|
|
310
|
-
|
|
311
|
-
// The prefix follows the scope the way the estate's own records do: the
|
|
312
|
-
// service, the context, or "org".
|
|
313
|
-
func TestTheIDPrefixComesFromTheScope(t *testing.T) {
|
|
314
|
-
for scope, id := range map[string]string{
|
|
315
|
-
"avia.aviasupp": "aviasupp.0003",
|
|
316
|
-
"payments": "payments.0003",
|
|
317
|
-
"org": "org.0003",
|
|
318
|
-
"": "org.0003",
|
|
319
|
-
} {
|
|
320
|
-
if adr := parsedTools(t, toolsSample, scope); adr.ID != id {
|
|
321
|
-
t.Errorf("scope %q: id = %q", scope, adr.ID)
|
|
322
|
-
}
|
|
323
|
-
}
|
|
324
|
-
if adr := parsedTools(t, toolsSample, ""); adr.Scope.Kind != "org" {
|
|
325
|
-
t.Errorf("no scope: %+v", adr.Scope)
|
|
326
|
-
}
|
|
327
|
-
}
|
|
328
|
-
|
|
329
|
-
// Supersession is written the way adr-tools writes it: in the Status section,
|
|
330
|
-
// as a link to the other record, and the later statement wins.
|
|
331
|
-
func TestAnAdrToolsSupersessionIsRead(t *testing.T) {
|
|
332
|
-
src := strings.Replace(toolsSample, "Accepted\n", "Accepted\n\nSuperseded by [5. Use schemas](0005-use-schemas.md)\n", 1)
|
|
333
|
-
adr := parsedTools(t, src, "avia.aviasupp")
|
|
334
|
-
if adr.Status != catalog.AdrSuperseded || adr.SupersededBy != "aviasupp.0005" {
|
|
335
|
-
t.Errorf("status %q, superseded by %q", adr.Status, adr.SupersededBy)
|
|
336
|
-
}
|
|
337
|
-
|
|
338
|
-
src = strings.Replace(toolsSample, "Accepted\n", "Accepted\n\nSupersedes [1. Record decisions](0001-record.md), ADR-0002\n", 1)
|
|
339
|
-
adr = parsedTools(t, src, "avia.aviasupp")
|
|
340
|
-
if strings.Join(adr.Supersedes, " ") != "aviasupp.0001" {
|
|
341
|
-
t.Errorf("supersedes = %v", adr.Supersedes)
|
|
342
|
-
}
|
|
343
|
-
|
|
344
|
-
src = strings.Replace(toolsSample, "Accepted\n", "Accepted\n\nSupersedes ADR-0001 and 2\n", 1)
|
|
345
|
-
if adr = parsedTools(t, src, "avia.aviasupp"); strings.Join(adr.Supersedes, " ") != "aviasupp.0001 aviasupp.0002" {
|
|
346
|
-
t.Errorf("bare numbers: supersedes = %v", adr.Supersedes)
|
|
347
|
-
}
|
|
348
|
-
}
|
|
349
|
-
|
|
350
|
-
func TestAStatusWordIsReadWhateverItsCase(t *testing.T) {
|
|
351
|
-
for _, word := range []string{"accepted", "ACCEPTED", "Accepted."} {
|
|
352
|
-
if adr := parsedTools(t, strings.Replace(toolsSample, "Accepted\n", word+"\n", 1), ""); adr.Status != catalog.AdrAccepted {
|
|
353
|
-
t.Errorf("%q read as %q", word, adr.Status)
|
|
354
|
-
}
|
|
355
|
-
}
|
|
356
|
-
}
|
|
357
|
-
|
|
358
|
-
func TestWhatAnAdrToolsRecordCannotBeReadWithout(t *testing.T) {
|
|
359
|
-
if errs := refusedTools(t, strings.Replace(toolsSample, "Date: 2024-09-10\n", "", 1)); !strings.Contains(errs, `no "Date:"`) {
|
|
360
|
-
t.Errorf("errors: %s", errs)
|
|
361
|
-
}
|
|
362
|
-
if errs := refusedTools(t, strings.Replace(toolsSample, "## Status\n\nAccepted\n", "", 1)); !strings.Contains(errs, "no `## Status` section") {
|
|
363
|
-
t.Errorf("errors: %s", errs)
|
|
364
|
-
}
|
|
365
|
-
if errs := refusedTools(t, strings.Replace(toolsSample, "Accepted", "Agreed", 1)); !strings.Contains(errs, `"Agreed" is not a status`) {
|
|
366
|
-
t.Errorf("errors: %s", errs)
|
|
367
|
-
}
|
|
368
|
-
if errs := refusedTools(t, strings.Replace(toolsSample, "Accepted", "Superseded by a later one", 1)); !strings.Contains(errs, "says by which one record") {
|
|
369
|
-
t.Errorf("errors: %s", errs)
|
|
370
|
-
}
|
|
371
|
-
if errs := refusedTools(t, strings.Replace(toolsSample, "Date: 2024-09-10", "Date: 2024-09-10\n\nWritten in a hurry.", 1)); !strings.Contains(errs, `only "Date:" belongs`) {
|
|
372
|
-
t.Errorf("errors: %s", errs)
|
|
373
|
-
}
|
|
374
|
-
}
|
|
375
|
-
|
|
376
|
-
// A MADR record that says no Scope is about whatever the step says its tree
|
|
377
|
-
// is about, and only a record with neither is refused.
|
|
378
|
-
func TestAMissingScopeFallsBackToTheStep(t *testing.T) {
|
|
379
|
-
src := with("- **Scope:** auth.auth\n", "")
|
|
380
|
-
adr, errs := parseAdr("examples/auth/docs/adr/0003-expiry-publishes-nothing.md", src, defaults{Scope: "auth"})
|
|
381
|
-
if len(errs) > 0 {
|
|
382
|
-
t.Fatalf("errors: %s", strings.Join(errs, "\n"))
|
|
383
|
-
}
|
|
384
|
-
if adr.Scope.Kind != "context" || adr.Scope.Context != "auth" {
|
|
385
|
-
t.Errorf("scope = %+v", adr.Scope)
|
|
386
|
-
}
|
|
387
|
-
if errs := refused(t, src); !strings.Contains(errs, `no "Scope"`) {
|
|
388
|
-
t.Errorf("errors: %s", errs)
|
|
389
|
-
}
|
|
390
|
-
}
|
|
391
|
-
|
|
392
|
-
// A record with no number in its title at all - written by hand into an
|
|
393
|
-
// adr-tools tree - is numbered by its file. A MADR title with the wrong dash
|
|
394
|
-
// is not one of these: it opens with an id, and stays refused as a typo.
|
|
395
|
-
func TestATitleWithNoNumberIsNumberedByItsFile(t *testing.T) {
|
|
396
|
-
adr := parsedTools(t, strings.Replace(toolsSample, "# 3. Use DTO", "# Use DTO", 1), "avia.aviasupp")
|
|
397
|
-
if adr.ID != "aviasupp.0003" || adr.Number != 3 || adr.Title != "Use DTO" {
|
|
398
|
-
t.Errorf("id %q, number %d, title %q", adr.ID, adr.Number, adr.Title)
|
|
399
|
-
}
|
|
400
|
-
|
|
401
|
-
_, errs := parseAdr("docs/ADR/river.md", strings.Replace(toolsSample, "# 3. Use DTO", "# River", 1), defaults{})
|
|
402
|
-
if len(errs) == 0 || !strings.Contains(errs[0], "nothing numbers the record") {
|
|
403
|
-
t.Errorf("errors: %v", errs)
|
|
404
|
-
}
|
|
405
|
-
}
|
|
@@ -1,19 +0,0 @@
|
|
|
1
|
-
package main
|
|
2
|
-
|
|
3
|
-
import (
|
|
4
|
-
_ "embed"
|
|
5
|
-
|
|
6
|
-
"github.com/shortlink-org/portolan/plugin"
|
|
7
|
-
)
|
|
8
|
-
|
|
9
|
-
//go:embed options.schema.json
|
|
10
|
-
var optionsSchema []byte
|
|
11
|
-
|
|
12
|
-
func descriptor() plugin.Descriptor {
|
|
13
|
-
return plugin.Descriptor{
|
|
14
|
-
Name: "extract-asyncapi",
|
|
15
|
-
Summary: "Reads an AsyncAPI document and answers with the channels a service publishes on and listens to.",
|
|
16
|
-
Phases: []string{plugin.PhaseExtract},
|
|
17
|
-
Options: optionsSchema,
|
|
18
|
-
}
|
|
19
|
-
}
|