@shortlink-org/portolan 0.1.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/LICENSE +21 -0
- package/README.md +387 -0
- package/catalog/enum_test.go +46 -0
- package/catalog/model.go +932 -0
- package/catalog/roundtrip_test.go +185 -0
- package/catalog/via_test.go +38 -0
- package/cli/portolan.mjs +368 -0
- package/go.mod +14 -0
- package/go.sum +14 -0
- package/index.html +13 -0
- package/internal/commands/cargo.go +154 -0
- package/internal/commands/commands.go +143 -0
- package/internal/commands/commands_test.go +323 -0
- package/internal/commands/gradle.go +77 -0
- package/internal/commands/justfile.go +82 -0
- package/internal/commands/makefile.go +106 -0
- package/internal/commands/maven.go +93 -0
- package/internal/commands/packagejson.go +159 -0
- package/internal/commands/pyproject.go +169 -0
- package/internal/commands/taskfile.go +101 -0
- package/internal/commands/testdata/estate/.cargo/config.toml +7 -0
- package/internal/commands/testdata/estate/Makefile +33 -0
- package/internal/commands/testdata/estate/Taskfile.yml +21 -0
- package/internal/commands/testdata/estate/build.gradle.kts +21 -0
- package/internal/commands/testdata/estate/justfile +20 -0
- package/internal/commands/testdata/estate/package.json +13 -0
- package/internal/commands/testdata/estate/pom.xml +29 -0
- package/internal/commands/testdata/estate/pyproject.toml +27 -0
- package/internal/commands/testdata/estate/xtask/src/main.rs +27 -0
- package/internal/commands/testdata/golden/commands.json +305 -0
- package/internal/gohttp/analyze.go +2500 -0
- package/internal/gohttp/endpoints.go +1067 -0
- package/internal/gohttp/roots.go +320 -0
- package/internal/gohttp/typed.go +143 -0
- package/internal/goscan/constants.go +85 -0
- package/internal/goscan/goscan_test.go +227 -0
- package/internal/goscan/names.go +52 -0
- package/internal/goscan/parse_test.go +11 -0
- package/internal/goscan/source.go +36 -0
- package/internal/goscan/tree.go +155 -0
- package/internal/goscan/types.go +99 -0
- package/internal/wsdl/ids.go +127 -0
- package/internal/wsdl/ids_test.go +21 -0
- package/internal/wsdl/model.go +62 -0
- package/internal/wsdl/parse.go +920 -0
- package/internal/wsdl/parse_test.go +133 -0
- package/package.json +117 -0
- package/plugin/describe.go +107 -0
- package/plugin/describe_test.go +114 -0
- package/plugin/protocol.go +117 -0
- package/plugin/schematest/schematest.go +126 -0
- package/plugins/README.md +1160 -0
- package/plugins/extract-adr/describe.go +19 -0
- package/plugins/extract-adr/describe_test.go +11 -0
- package/plugins/extract-adr/extract.go +153 -0
- package/plugins/extract-adr/extract_test.go +350 -0
- package/plugins/extract-adr/history.go +99 -0
- package/plugins/extract-adr/main.go +65 -0
- package/plugins/extract-adr/options.schema.json +26 -0
- package/plugins/extract-adr/parse.go +642 -0
- package/plugins/extract-adr/parse_test.go +405 -0
- package/plugins/extract-asyncapi/describe.go +19 -0
- package/plugins/extract-asyncapi/describe_test.go +11 -0
- package/plugins/extract-asyncapi/extract.go +315 -0
- package/plugins/extract-asyncapi/extract_test.go +197 -0
- package/plugins/extract-asyncapi/main.go +48 -0
- package/plugins/extract-asyncapi/options.schema.json +26 -0
- package/plugins/extract-asyncapi/spec.go +151 -0
- package/plugins/extract-celery/README.md +169 -0
- package/plugins/extract-celery/extract.py +346 -0
- package/plugins/extract-celery/extract_test.py +192 -0
- package/plugins/extract-celery/main.py +66 -0
- package/plugins/extract-celery/options.py +31 -0
- package/plugins/extract-celery/options.schema.json +31 -0
- package/plugins/extract-celery/producers.py +156 -0
- package/plugins/extract-celery/routes_test.py +106 -0
- package/plugins/extract-commands/describe.go +19 -0
- package/plugins/extract-commands/describe_test.go +11 -0
- package/plugins/extract-commands/extract.go +79 -0
- package/plugins/extract-commands/extract_test.go +92 -0
- package/plugins/extract-commands/main.go +55 -0
- package/plugins/extract-commands/options.schema.json +20 -0
- package/plugins/extract-csr/avro.go +258 -0
- package/plugins/extract-csr/describe.go +19 -0
- package/plugins/extract-csr/describe_test.go +11 -0
- package/plugins/extract-csr/extract.go +338 -0
- package/plugins/extract-csr/extract_test.go +374 -0
- package/plugins/extract-csr/jsonschema.go +343 -0
- package/plugins/extract-csr/lock.go +25 -0
- package/plugins/extract-csr/main.go +84 -0
- package/plugins/extract-csr/options.schema.json +43 -0
- package/plugins/extract-csr/subject.go +102 -0
- package/plugins/extract-django/README.md +386 -0
- package/plugins/extract-django/apps.py +100 -0
- package/plugins/extract-django/auth.py +302 -0
- package/plugins/extract-django/auth_test.py +188 -0
- package/plugins/extract-django/clients.py +121 -0
- package/plugins/extract-django/contracts.py +458 -0
- package/plugins/extract-django/database.py +119 -0
- package/plugins/extract-django/domain.py +318 -0
- package/plugins/extract-django/events.py +86 -0
- package/plugins/extract-django/extract.py +601 -0
- package/plugins/extract-django/extract_test.py +397 -0
- package/plugins/extract-django/flows.py +1278 -0
- package/plugins/extract-django/flows_test.py +338 -0
- package/plugins/extract-django/ids.py +42 -0
- package/plugins/extract-django/ids_test.py +30 -0
- package/plugins/extract-django/lifecycle.py +222 -0
- package/plugins/extract-django/lifecycle_test.py +76 -0
- package/plugins/extract-django/main.py +66 -0
- package/plugins/extract-django/openapi.py +229 -0
- package/plugins/extract-django/openapi_test.py +60 -0
- package/plugins/extract-django/operations.py +96 -0
- package/plugins/extract-django/options.py +61 -0
- package/plugins/extract-django/options.schema.json +126 -0
- package/plugins/extract-django/routing.py +214 -0
- package/plugins/extract-django/routing_test.py +198 -0
- package/plugins/extract-django/serializers.py +558 -0
- package/plugins/extract-django/store.py +434 -0
- package/plugins/extract-django/transport.py +242 -0
- package/plugins/extract-flows/describe.go +19 -0
- package/plugins/extract-flows/describe_test.go +11 -0
- package/plugins/extract-flows/extract.go +91 -0
- package/plugins/extract-flows/main.go +45 -0
- package/plugins/extract-flows/options.schema.json +16 -0
- package/plugins/extract-flows/parse.go +593 -0
- package/plugins/extract-flows/parse_test.go +204 -0
- package/plugins/extract-glossary/describe.go +19 -0
- package/plugins/extract-glossary/describe_test.go +11 -0
- package/plugins/extract-glossary/extract.go +115 -0
- package/plugins/extract-glossary/extract_test.go +220 -0
- package/plugins/extract-glossary/main.go +59 -0
- package/plugins/extract-glossary/options.schema.json +21 -0
- package/plugins/extract-glossary/parse.go +214 -0
- package/plugins/extract-glossary/parse_test.go +203 -0
- package/plugins/extract-go/aggregate.go +214 -0
- package/plugins/extract-go/client.go +409 -0
- package/plugins/extract-go/client_test.go +266 -0
- package/plugins/extract-go/describe.go +19 -0
- package/plugins/extract-go/describe_test.go +11 -0
- package/plugins/extract-go/enum.go +195 -0
- package/plugins/extract-go/enum_test.go +82 -0
- package/plugins/extract-go/event.go +99 -0
- package/plugins/extract-go/extract.go +191 -0
- package/plugins/extract-go/extract_test.go +261 -0
- package/plugins/extract-go/flow.go +1441 -0
- package/plugins/extract-go/flow_test.go +609 -0
- package/plugins/extract-go/httpclient.go +174 -0
- package/plugins/extract-go/httpclient_test.go +296 -0
- package/plugins/extract-go/ids.go +92 -0
- package/plugins/extract-go/layout.go +225 -0
- package/plugins/extract-go/layout_test.go +125 -0
- package/plugins/extract-go/lifecycle.go +324 -0
- package/plugins/extract-go/lifecycle_test.go +108 -0
- package/plugins/extract-go/main.go +79 -0
- package/plugins/extract-go/operation.go +157 -0
- package/plugins/extract-go/options.schema.json +66 -0
- package/plugins/extract-go/source.go +316 -0
- package/plugins/extract-go/transport.go +321 -0
- package/plugins/extract-go/transport_test.go +145 -0
- package/plugins/extract-go/wiring.go +434 -0
- package/plugins/extract-go-nats/describe.go +19 -0
- package/plugins/extract-go-nats/describe_test.go +11 -0
- package/plugins/extract-go-nats/extract.go +177 -0
- package/plugins/extract-go-nats/extract_test.go +389 -0
- package/plugins/extract-go-nats/index.go +379 -0
- package/plugins/extract-go-nats/main.go +42 -0
- package/plugins/extract-go-nats/options.schema.json +21 -0
- package/plugins/extract-go-nats/resolve.go +161 -0
- package/plugins/extract-go-nats/sites.go +224 -0
- package/plugins/extract-graphql/describe.go +19 -0
- package/plugins/extract-graphql/describe_test.go +11 -0
- package/plugins/extract-graphql/extract.go +433 -0
- package/plugins/extract-graphql/extract_test.go +256 -0
- package/plugins/extract-graphql/ids.go +49 -0
- package/plugins/extract-graphql/lex.go +237 -0
- package/plugins/extract-graphql/main.go +51 -0
- package/plugins/extract-graphql/options.schema.json +31 -0
- package/plugins/extract-graphql/parse.go +621 -0
- package/plugins/extract-graphql/parse_test.go +122 -0
- package/plugins/extract-http-clients/describe.go +19 -0
- package/plugins/extract-http-clients/describe_test.go +11 -0
- package/plugins/extract-http-clients/extract.go +705 -0
- package/plugins/extract-http-clients/extract_test.go +1263 -0
- package/plugins/extract-http-clients/main.go +42 -0
- package/plugins/extract-http-clients/options.schema.json +33 -0
- package/plugins/extract-java/README.md +190 -0
- package/plugins/extract-java/build/org/portolan/extract/Catalog.class +0 -0
- package/plugins/extract-java/build/org/portolan/extract/Clients$1.class +0 -0
- package/plugins/extract-java/build/org/portolan/extract/Clients$2.class +0 -0
- package/plugins/extract-java/build/org/portolan/extract/Clients$Call.class +0 -0
- package/plugins/extract-java/build/org/portolan/extract/Clients$Client.class +0 -0
- package/plugins/extract-java/build/org/portolan/extract/Clients$Route.class +0 -0
- package/plugins/extract-java/build/org/portolan/extract/Clients.class +0 -0
- package/plugins/extract-java/build/org/portolan/extract/Domain$Aggregate.class +0 -0
- package/plugins/extract-java/build/org/portolan/extract/Domain.class +0 -0
- package/plugins/extract-java/build/org/portolan/extract/Events$Found.class +0 -0
- package/plugins/extract-java/build/org/portolan/extract/Events.class +0 -0
- package/plugins/extract-java/build/org/portolan/extract/Extract.class +0 -0
- package/plugins/extract-java/build/org/portolan/extract/Flows$1.class +0 -0
- package/plugins/extract-java/build/org/portolan/extract/Flows$Binding.class +0 -0
- package/plugins/extract-java/build/org/portolan/extract/Flows$Draft.class +0 -0
- package/plugins/extract-java/build/org/portolan/extract/Flows$Frame.class +0 -0
- package/plugins/extract-java/build/org/portolan/extract/Flows$Options.class +0 -0
- package/plugins/extract-java/build/org/portolan/extract/Flows.class +0 -0
- package/plugins/extract-java/build/org/portolan/extract/Ids.class +0 -0
- package/plugins/extract-java/build/org/portolan/extract/Json$Reader.class +0 -0
- package/plugins/extract-java/build/org/portolan/extract/Json.class +0 -0
- package/plugins/extract-java/build/org/portolan/extract/Lifecycle$1.class +0 -0
- package/plugins/extract-java/build/org/portolan/extract/Lifecycle$Move.class +0 -0
- package/plugins/extract-java/build/org/portolan/extract/Lifecycle.class +0 -0
- package/plugins/extract-java/build/org/portolan/extract/Main.class +0 -0
- package/plugins/extract-java/build/org/portolan/extract/OpenApi$Operation.class +0 -0
- package/plugins/extract-java/build/org/portolan/extract/OpenApi$Spec.class +0 -0
- package/plugins/extract-java/build/org/portolan/extract/OpenApi.class +0 -0
- package/plugins/extract-java/build/org/portolan/extract/Operations$1.class +0 -0
- package/plugins/extract-java/build/org/portolan/extract/Operations$UseCase.class +0 -0
- package/plugins/extract-java/build/org/portolan/extract/Operations.class +0 -0
- package/plugins/extract-java/build/org/portolan/extract/Proto$Rpc.class +0 -0
- package/plugins/extract-java/build/org/portolan/extract/Proto$Service.class +0 -0
- package/plugins/extract-java/build/org/portolan/extract/Proto.class +0 -0
- package/plugins/extract-java/build/org/portolan/extract/Protocol$Builder.class +0 -0
- package/plugins/extract-java/build/org/portolan/extract/Protocol$Input.class +0 -0
- package/plugins/extract-java/build/org/portolan/extract/Protocol$Options.class +0 -0
- package/plugins/extract-java/build/org/portolan/extract/Protocol.class +0 -0
- package/plugins/extract-java/build/org/portolan/extract/Source$Project.class +0 -0
- package/plugins/extract-java/build/org/portolan/extract/Source$Unit.class +0 -0
- package/plugins/extract-java/build/org/portolan/extract/Source.class +0 -0
- package/plugins/extract-java/build/org/portolan/extract/Transport$Endpoint.class +0 -0
- package/plugins/extract-java/build/org/portolan/extract/Transport.class +0 -0
- package/plugins/extract-java/build/org/portolan/extract/Yaml$Line.class +0 -0
- package/plugins/extract-java/build/org/portolan/extract/Yaml$Reader.class +0 -0
- package/plugins/extract-java/build/org/portolan/extract/Yaml.class +0 -0
- package/plugins/extract-java/options.schema.json +70 -0
- package/plugins/extract-openapi/describe.go +19 -0
- package/plugins/extract-openapi/describe_test.go +11 -0
- package/plugins/extract-openapi/discover.go +243 -0
- package/plugins/extract-openapi/extract.go +526 -0
- package/plugins/extract-openapi/extract_test.go +545 -0
- package/plugins/extract-openapi/main.go +75 -0
- package/plugins/extract-openapi/options.schema.json +62 -0
- package/plugins/extract-openapi/spec.go +350 -0
- package/plugins/extract-project/README.md +22 -0
- package/plugins/extract-project/describe.go +19 -0
- package/plugins/extract-project/describe_test.go +11 -0
- package/plugins/extract-project/extract.go +221 -0
- package/plugins/extract-project/extract_test.go +109 -0
- package/plugins/extract-project/main.go +41 -0
- package/plugins/extract-project/options.schema.json +48 -0
- package/plugins/extract-proto/ast.go +125 -0
- package/plugins/extract-proto/consumes.go +77 -0
- package/plugins/extract-proto/describe.go +19 -0
- package/plugins/extract-proto/extract.go +293 -0
- package/plugins/extract-proto/extract_test.go +459 -0
- package/plugins/extract-proto/ids.go +89 -0
- package/plugins/extract-proto/ids_test.go +57 -0
- package/plugins/extract-proto/lex.go +285 -0
- package/plugins/extract-proto/main.go +100 -0
- package/plugins/extract-proto/module.go +120 -0
- package/plugins/extract-proto/options.schema.json +51 -0
- package/plugins/extract-proto/parse.go +720 -0
- package/plugins/extract-proto/parse_test.go +307 -0
- package/plugins/extract-proto/provides.go +236 -0
- package/plugins/extract-proto/resolve.go +222 -0
- package/plugins/extract-proto/resolve_test.go +119 -0
- package/plugins/extract-python-kafka/README.md +36 -0
- package/plugins/extract-python-kafka/extract.py +211 -0
- package/plugins/extract-python-kafka/extract_test.py +68 -0
- package/plugins/extract-python-kafka/main.py +59 -0
- package/plugins/extract-python-kafka/options.py +29 -0
- package/plugins/extract-python-kafka/options.schema.json +31 -0
- package/plugins/extract-redis/README.md +11 -0
- package/plugins/extract-redis/describe.go +19 -0
- package/plugins/extract-redis/describe_test.go +11 -0
- package/plugins/extract-redis/extract.go +183 -0
- package/plugins/extract-redis/extract_test.go +168 -0
- package/plugins/extract-redis/keyspaces.go +469 -0
- package/plugins/extract-redis/main.go +44 -0
- package/plugins/extract-redis/options.schema.json +30 -0
- package/plugins/extract-river/describe.go +19 -0
- package/plugins/extract-river/describe_test.go +11 -0
- package/plugins/extract-river/extract.go +507 -0
- package/plugins/extract-river/extract_test.go +132 -0
- package/plugins/extract-river/main.go +42 -0
- package/plugins/extract-river/options.schema.json +21 -0
- package/plugins/extract-rust/Cargo.lock +133 -0
- package/plugins/extract-rust/Cargo.toml +23 -0
- package/plugins/extract-rust/README.md +204 -0
- package/plugins/extract-rust/options.schema.json +65 -0
- package/plugins/extract-rust/rustfmt.toml +1 -0
- package/plugins/extract-sql/ddl.go +893 -0
- package/plugins/extract-sql/ddl_test.go +401 -0
- package/plugins/extract-sql/describe.go +19 -0
- package/plugins/extract-sql/describe_test.go +11 -0
- package/plugins/extract-sql/layout.go +221 -0
- package/plugins/extract-sql/layout_test.go +55 -0
- package/plugins/extract-sql/lineage.go +107 -0
- package/plugins/extract-sql/main.go +141 -0
- package/plugins/extract-sql/maps.go +564 -0
- package/plugins/extract-sql/maps_java.go +117 -0
- package/plugins/extract-sql/maps_rust.go +333 -0
- package/plugins/extract-sql/maps_rust_test.go +70 -0
- package/plugins/extract-sql/maps_test.go +204 -0
- package/plugins/extract-sql/maps_ts.go +398 -0
- package/plugins/extract-sql/maps_ts_test.go +136 -0
- package/plugins/extract-sql/options.schema.json +53 -0
- package/plugins/extract-sql/projection.go +70 -0
- package/plugins/extract-sql/projection_test.go +50 -0
- package/plugins/extract-sql/store.go +420 -0
- package/plugins/extract-sql/store_test.go +233 -0
- package/plugins/extract-sql/view.go +295 -0
- package/plugins/extract-ts/README.md +306 -0
- package/plugins/extract-ts/ast.test.ts +178 -0
- package/plugins/extract-ts/ast.ts +648 -0
- package/plugins/extract-ts/clients.ts +185 -0
- package/plugins/extract-ts/domain.ts +164 -0
- package/plugins/extract-ts/extract.test.ts +208 -0
- package/plugins/extract-ts/extract.ts +200 -0
- package/plugins/extract-ts/flows.ts +783 -0
- package/plugins/extract-ts/graphql.test.ts +73 -0
- package/plugins/extract-ts/graphql.ts +181 -0
- package/plugins/extract-ts/ids.test.ts +21 -0
- package/plugins/extract-ts/ids.ts +52 -0
- package/plugins/extract-ts/lifecycle.ts +138 -0
- package/plugins/extract-ts/main.ts +48 -0
- package/plugins/extract-ts/openapi.test.ts +64 -0
- package/plugins/extract-ts/openapi.ts +99 -0
- package/plugins/extract-ts/operations.ts +93 -0
- package/plugins/extract-ts/options.schema.json +96 -0
- package/plugins/extract-ts/source.test.ts +174 -0
- package/plugins/extract-ts/source.ts +465 -0
- package/plugins/extract-ts/transport.ts +147 -0
- package/plugins/extract-ts/tsconfig.json +13 -0
- package/plugins/extract-ts/wiring.ts +190 -0
- package/plugins/extract-watermill/describe.go +19 -0
- package/plugins/extract-watermill/describe_test.go +11 -0
- package/plugins/extract-watermill/extract.go +1228 -0
- package/plugins/extract-watermill/extract_test.go +234 -0
- package/plugins/extract-watermill/main.go +41 -0
- package/plugins/extract-watermill/options.schema.json +21 -0
- package/plugins/extract-wsdl/describe.go +19 -0
- package/plugins/extract-wsdl/describe_test.go +11 -0
- package/plugins/extract-wsdl/extract.go +148 -0
- package/plugins/extract-wsdl/extract_test.go +45 -0
- package/plugins/extract-wsdl/main.go +53 -0
- package/plugins/extract-wsdl/options.schema.json +59 -0
- package/plugins/fetch-bsr/auth.go +114 -0
- package/plugins/fetch-bsr/bsr.go +240 -0
- package/plugins/fetch-bsr/cache.go +66 -0
- package/plugins/fetch-bsr/describe.go +19 -0
- package/plugins/fetch-bsr/fetch.go +192 -0
- package/plugins/fetch-bsr/fetch_test.go +465 -0
- package/plugins/fetch-bsr/lock.go +71 -0
- package/plugins/fetch-bsr/main.go +117 -0
- package/plugins/fetch-bsr/options.schema.json +42 -0
- package/plugins/fetch-csr/auth.go +95 -0
- package/plugins/fetch-csr/cache.go +69 -0
- package/plugins/fetch-csr/describe.go +19 -0
- package/plugins/fetch-csr/describe_test.go +11 -0
- package/plugins/fetch-csr/fetch.go +254 -0
- package/plugins/fetch-csr/fetch_test.go +494 -0
- package/plugins/fetch-csr/lock.go +95 -0
- package/plugins/fetch-csr/main.go +122 -0
- package/plugins/fetch-csr/options.schema.json +42 -0
- package/plugins/fetch-csr/registry.go +209 -0
- package/plugins/fetch-git/cache.go +65 -0
- package/plugins/fetch-git/describe.go +19 -0
- package/plugins/fetch-git/describe_test.go +11 -0
- package/plugins/fetch-git/fetch.go +181 -0
- package/plugins/fetch-git/fetch_test.go +332 -0
- package/plugins/fetch-git/git.go +169 -0
- package/plugins/fetch-git/lock.go +72 -0
- package/plugins/fetch-git/main.go +127 -0
- package/plugins/fetch-git/offline.go +39 -0
- package/plugins/fetch-git/options.schema.json +42 -0
- package/plugins/fetch-git/pin.go +90 -0
- package/plugins/fetch-git/pin_test.go +74 -0
- package/plugins/gen-backstage/describe.go +17 -0
- package/plugins/gen-backstage/main.go +22 -0
- package/plugins/gen-backstage/options.schema.json +30 -0
- package/plugins/gen-backstage/plugin.go +473 -0
- package/plugins/gen-backstage/plugin_test.go +145 -0
- package/plugins/gen-backstage.wasm +0 -0
- package/plugins/gen-markdown/adr.go +162 -0
- package/plugins/gen-markdown/aggregate.go +362 -0
- package/plugins/gen-markdown/canonical.go +93 -0
- package/plugins/gen-markdown/context.go +90 -0
- package/plugins/gen-markdown/coverage_test.go +89 -0
- package/plugins/gen-markdown/describe.go +19 -0
- package/plugins/gen-markdown/describe_test.go +11 -0
- package/plugins/gen-markdown/external.go +71 -0
- package/plugins/gen-markdown/flow.go +277 -0
- package/plugins/gen-markdown/glossary.go +76 -0
- package/plugins/gen-markdown/glossary_test.go +148 -0
- package/plugins/gen-markdown/llms.go +302 -0
- package/plugins/gen-markdown/main.go +22 -0
- package/plugins/gen-markdown/markdown.go +254 -0
- package/plugins/gen-markdown/markdown_test.go +100 -0
- package/plugins/gen-markdown/module.go +107 -0
- package/plugins/gen-markdown/options.schema.json +16 -0
- package/plugins/gen-markdown/plugin.go +39 -0
- package/plugins/gen-markdown/quality_test.go +176 -0
- package/plugins/gen-markdown/redis_test.go +38 -0
- package/plugins/gen-markdown/render.go +344 -0
- package/plugins/gen-markdown/render_test.go +186 -0
- package/plugins/gen-markdown/service.go +456 -0
- package/plugins/gen-markdown/source.go +177 -0
- package/plugins/gen-markdown/store.go +201 -0
- package/plugins/gen-markdown.wasm +0 -0
- package/plugins/gen-mermaid/describe.go +17 -0
- package/plugins/gen-mermaid/main.go +22 -0
- package/plugins/gen-mermaid/options.schema.json +10 -0
- package/plugins/gen-mermaid/plugin.go +104 -0
- package/plugins/gen-mermaid/plugin_test.go +33 -0
- package/plugins/gen-mermaid.wasm +0 -0
- package/plugins/openapi/ids.go +261 -0
- package/plugins/openapi/ids_test.go +98 -0
- package/plugins/pyplugin/catalog.py +209 -0
- package/plugins/pyplugin/celery_conf.py +436 -0
- package/plugins/pyplugin/celery_tasks.py +76 -0
- package/plugins/pyplugin/kafka.py +570 -0
- package/plugins/pyplugin/names.py +38 -0
- package/plugins/pyplugin/protocol.py +79 -0
- package/plugins/pyplugin/source.py +285 -0
- package/plugins/verify-codeowners/describe.go +19 -0
- package/plugins/verify-codeowners/describe_test.go +11 -0
- package/plugins/verify-codeowners/main.go +75 -0
- package/plugins/verify-codeowners/match.go +85 -0
- package/plugins/verify-codeowners/match_test.go +47 -0
- package/plugins/verify-codeowners/options.schema.json +15 -0
- package/plugins/verify-codeowners/owners.go +164 -0
- package/plugins/verify-codeowners/owners_test.go +225 -0
- package/plugins/verify-codeowners/parse.go +90 -0
- package/plugins/verify-codeowners/parse_test.go +62 -0
- package/plugins/verify-otel/describe.go +19 -0
- package/plugins/verify-otel/describe_test.go +11 -0
- package/plugins/verify-otel/main.go +53 -0
- package/plugins/verify-otel/match.go +336 -0
- package/plugins/verify-otel/options.schema.json +30 -0
- package/plugins/verify-otel/otlp.go +200 -0
- package/plugins/verify-otel/verify.go +734 -0
- package/plugins/verify-otel/verify_test.go +460 -0
- package/portolan.json +1073 -0
- package/public/favicon.svg +12 -0
- package/schema/portolan.schema.json +2558 -0
- package/scripts/README.md +53 -0
- package/scripts/build-report.mjs +60 -0
- package/scripts/build-report.test.mjs +44 -0
- package/scripts/builtin-plugins.mjs +49 -0
- package/scripts/catalog-sources.mjs +67 -0
- package/scripts/diff.mjs +315 -0
- package/scripts/diff.test.mjs +151 -0
- package/scripts/forge-comment.mjs +144 -0
- package/scripts/forge-comment.test.mjs +151 -0
- package/scripts/forge-release.mjs +138 -0
- package/scripts/forge-release.test.mjs +106 -0
- package/scripts/forge.mjs +61 -0
- package/scripts/gen-likec4.mjs +854 -0
- package/scripts/gen-likec4.test.mjs +258 -0
- package/scripts/gen.mjs +603 -0
- package/scripts/generated-docs.test.mjs +66 -0
- package/scripts/local-api.mjs +1192 -0
- package/scripts/local-api.test.mjs +387 -0
- package/scripts/manifest.mjs +214 -0
- package/scripts/manifest.test.mjs +148 -0
- package/scripts/output-path.mjs +79 -0
- package/scripts/output-path.test.mjs +45 -0
- package/scripts/package-smoke.mjs +37 -0
- package/scripts/plugin-host.mjs +419 -0
- package/scripts/plugin-host.test.mjs +109 -0
- package/scripts/plugin-wasm-worker.mjs +34 -0
- package/scripts/run-builtin.mjs +81 -0
- package/scripts/schema.mjs +357 -0
- package/scripts/site-docs.mjs +295 -0
- package/scripts/site-docs.test.mjs +181 -0
- package/scripts/vendor-lock.mjs +58 -0
- package/scripts/vendor-lock.test.mjs +69 -0
- package/src/README.md +38 -0
- package/src/app/App.tsx +448 -0
- package/src/app/BranchPicker.test.ts +46 -0
- package/src/app/BranchPicker.tsx +153 -0
- package/src/app/Breadcrumbs.test.ts +38 -0
- package/src/app/Breadcrumbs.tsx +223 -0
- package/src/app/BuildStamp.tsx +146 -0
- package/src/app/CommandPalette.tsx +292 -0
- package/src/app/HashScroll.tsx +45 -0
- package/src/app/Sidebar.tsx +1742 -0
- package/src/app/TopBar.tsx +393 -0
- package/src/app/density.tsx +64 -0
- package/src/app/forge-access.test.ts +27 -0
- package/src/app/forge-access.tsx +80 -0
- package/src/app/panels.tsx +141 -0
- package/src/app/pins.tsx +242 -0
- package/src/app/query-client.ts +24 -0
- package/src/app/responsive.ts +55 -0
- package/src/app/shortcuts.test.ts +39 -0
- package/src/app/shortcuts.tsx +282 -0
- package/src/app/theme.tsx +51 -0
- package/src/app/title.test.ts +14 -0
- package/src/app/title.ts +30 -0
- package/src/app/toast.tsx +59 -0
- package/src/app/tree-keys.test.ts +80 -0
- package/src/app/tree-keys.ts +87 -0
- package/src/app/ui-store.ts +52 -0
- package/src/catalog-error.test.ts +105 -0
- package/src/catalog-profile.test.ts +59 -0
- package/src/catalog-profile.ts +101 -0
- package/src/catalog-stores.test.ts +431 -0
- package/src/catalog.test.ts +1215 -0
- package/src/catalog.ts +3125 -0
- package/src/chat/Cards.tsx +408 -0
- package/src/chat/ChatPanel.tsx +79 -0
- package/src/chat/ChatSettings.tsx +219 -0
- package/src/chat/Composer.tsx +99 -0
- package/src/chat/Conversation.tsx +281 -0
- package/src/chat/Header.tsx +46 -0
- package/src/chat/Message.tsx +49 -0
- package/src/chat/Notice.tsx +195 -0
- package/src/chat/Starter.tsx +67 -0
- package/src/chat/Steps.tsx +45 -0
- package/src/chat/Waiting.tsx +87 -0
- package/src/chat/flags.test.ts +59 -0
- package/src/chat/flags.ts +155 -0
- package/src/chat/linkify.test.ts +30 -0
- package/src/chat/linkify.ts +45 -0
- package/src/chat/prefs.ts +56 -0
- package/src/chat/prompt.test.ts +43 -0
- package/src/chat/prompt.ts +157 -0
- package/src/chat/store.ts +35 -0
- package/src/chat/tools.ts +58 -0
- package/src/chat/transport.ts +70 -0
- package/src/components/AdrRow.tsx +29 -0
- package/src/components/AnchorLink.tsx +67 -0
- package/src/components/ApiReference.tsx +134 -0
- package/src/components/AsyncApiReference.tsx +120 -0
- package/src/components/CatalogStamp.tsx +155 -0
- package/src/components/ChannelRows.tsx +132 -0
- package/src/components/CommandRows.tsx +139 -0
- package/src/components/DiagramSkeleton.tsx +71 -0
- package/src/components/EditorLink.tsx +85 -0
- package/src/components/FieldTree.tsx +305 -0
- package/src/components/FlowTrigger.tsx +34 -0
- package/src/components/Ident.tsx +89 -0
- package/src/components/Integrations.tsx +243 -0
- package/src/components/LifecycleDiagram.tsx +110 -0
- package/src/components/MachineDocs.tsx +42 -0
- package/src/components/Markdown.tsx +173 -0
- package/src/components/MarkdownTable.tsx +206 -0
- package/src/components/Mermaid.tsx +211 -0
- package/src/components/MethodRows.tsx +309 -0
- package/src/components/Overlay.tsx +125 -0
- package/src/components/PageHeader.tsx +189 -0
- package/src/components/RowActions.tsx +97 -0
- package/src/components/SchemaDocument.tsx +89 -0
- package/src/components/Select.tsx +126 -0
- package/src/components/ShapeRows.tsx +118 -0
- package/src/components/SourceDoc.tsx +82 -0
- package/src/components/SourcePreview.tsx +441 -0
- package/src/components/TabRow.tsx +182 -0
- package/src/components/TechIcon.tsx +34 -0
- package/src/components/Toc.tsx +132 -0
- package/src/components/WhatLinksHere.tsx +273 -0
- package/src/components/WsdlReference.tsx +84 -0
- package/src/components/ddd-icons.tsx +107 -0
- package/src/components/kind.tsx +157 -0
- package/src/components/logo.tsx +61 -0
- package/src/components/primitives.tsx +264 -0
- package/src/data.test.ts +18 -0
- package/src/data.ts +202 -0
- package/src/enrich.test.ts +1150 -0
- package/src/enrich.ts +672 -0
- package/src/er/ErCanvas.tsx +533 -0
- package/src/er/RedisSchema.tsx +74 -0
- package/src/er/RelationCard.tsx +267 -0
- package/src/er/StoreHeader.tsx +155 -0
- package/src/er/TableNode.tsx +84 -0
- package/src/er/ViewNode.tsx +83 -0
- package/src/er/layout.test.ts +115 -0
- package/src/er/layout.ts +77 -0
- package/src/er/lineage.test.ts +140 -0
- package/src/er/lineage.ts +107 -0
- package/src/er/markers.tsx +79 -0
- package/src/er/spec.test.ts +344 -0
- package/src/er/spec.ts +426 -0
- package/src/flow/ChainList.tsx +162 -0
- package/src/flow/FlowTable.tsx +123 -0
- package/src/flow/FlowToolbar.tsx +372 -0
- package/src/flow/StepDetail.tsx +380 -0
- package/src/flow/StepRail.tsx +428 -0
- package/src/flow/answers.test.ts +50 -0
- package/src/flow/answers.ts +58 -0
- package/src/flow/chain.test.ts +383 -0
- package/src/flow/chain.ts +353 -0
- package/src/flow/chapters.test.ts +185 -0
- package/src/flow/chapters.ts +225 -0
- package/src/flow/continues.test.ts +102 -0
- package/src/flow/continues.ts +65 -0
- package/src/flow/cross-context.test.ts +97 -0
- package/src/flow/cross-context.ts +35 -0
- package/src/flow/mermaid.test.ts +22 -0
- package/src/flow/mermaid.ts +141 -0
- package/src/flow/outline.test.ts +212 -0
- package/src/flow/outline.ts +170 -0
- package/src/flow/paths.test.ts +129 -0
- package/src/flow/paths.ts +147 -0
- package/src/flow/prefs.test.ts +80 -0
- package/src/flow/prefs.ts +104 -0
- package/src/graph/DependencyGraph.tsx +283 -0
- package/src/graph/DependencyNodes.tsx +162 -0
- package/src/graph/EdgeLabel.tsx +61 -0
- package/src/graph/FocusedEventGraph.tsx +211 -0
- package/src/graph/GraphToolbar.tsx +187 -0
- package/src/graph/RoutedEdge.tsx +80 -0
- package/src/graph/dependency-layout.test.ts +360 -0
- package/src/graph/dependency-layout.ts +444 -0
- package/src/graph/elk.test.ts +70 -0
- package/src/graph/elk.ts +287 -0
- package/src/graph/nodes.tsx +97 -0
- package/src/graph/theme.ts +81 -0
- package/src/graph/useElkFlow.ts +125 -0
- package/src/index.css +1547 -0
- package/src/language/InLanguage.tsx +40 -0
- package/src/language/TermCard.tsx +132 -0
- package/src/language/cards.test.ts +114 -0
- package/src/language/cards.ts +111 -0
- package/src/lib/admonition.test.ts +49 -0
- package/src/lib/admonition.ts +48 -0
- package/src/lib/adr.test.ts +131 -0
- package/src/lib/adr.ts +68 -0
- package/src/lib/api.test.ts +122 -0
- package/src/lib/api.ts +135 -0
- package/src/lib/backlinks.test.ts +330 -0
- package/src/lib/backlinks.ts +567 -0
- package/src/lib/branch-compare.ts +16 -0
- package/src/lib/build-info.test.ts +76 -0
- package/src/lib/build-info.ts +80 -0
- package/src/lib/catalog-diff.test.ts +275 -0
- package/src/lib/catalog-diff.ts +655 -0
- package/src/lib/clipboard.ts +43 -0
- package/src/lib/comparison-memory.test.ts +40 -0
- package/src/lib/comparison-memory.ts +39 -0
- package/src/lib/context-color.ts +27 -0
- package/src/lib/context-map.test.ts +148 -0
- package/src/lib/context-map.ts +399 -0
- package/src/lib/data-model.test.ts +245 -0
- package/src/lib/data-model.ts +399 -0
- package/src/lib/data-problems.test.ts +178 -0
- package/src/lib/data-problems.ts +314 -0
- package/src/lib/derive.test.ts +251 -0
- package/src/lib/derive.ts +515 -0
- package/src/lib/editor-link.test.ts +125 -0
- package/src/lib/editor-link.ts +96 -0
- package/src/lib/editor-prefs.ts +27 -0
- package/src/lib/event-graph.test.ts +237 -0
- package/src/lib/event-graph.ts +327 -0
- package/src/lib/export-canvas.ts +37 -0
- package/src/lib/export-file.test.ts +30 -0
- package/src/lib/export-file.ts +20 -0
- package/src/lib/flow-tree.test.ts +177 -0
- package/src/lib/flow-tree.ts +160 -0
- package/src/lib/forge-refs.test.ts +50 -0
- package/src/lib/forge-refs.ts +60 -0
- package/src/lib/format.test.ts +35 -0
- package/src/lib/format.ts +64 -0
- package/src/lib/github-catalog.test.ts +301 -0
- package/src/lib/github-catalog.ts +610 -0
- package/src/lib/integrations.test.ts +119 -0
- package/src/lib/integrations.ts +133 -0
- package/src/lib/kinds.test.ts +91 -0
- package/src/lib/kinds.ts +204 -0
- package/src/lib/lifecycle.test.ts +93 -0
- package/src/lib/lifecycle.ts +165 -0
- package/src/lib/local-api.ts +194 -0
- package/src/lib/motion-features.ts +11 -0
- package/src/lib/motion.test.ts +120 -0
- package/src/lib/motion.tsx +269 -0
- package/src/lib/palette-recent.test.ts +80 -0
- package/src/lib/palette-recent.ts +90 -0
- package/src/lib/palette.test.ts +365 -0
- package/src/lib/palette.ts +593 -0
- package/src/lib/pins.test.ts +139 -0
- package/src/lib/pins.ts +130 -0
- package/src/lib/problems.test.ts +112 -0
- package/src/lib/project-preview.ts +5 -0
- package/src/lib/proto-problems.test.ts +121 -0
- package/src/lib/proto-problems.ts +65 -0
- package/src/lib/queries.test.ts +113 -0
- package/src/lib/queries.ts +131 -0
- package/src/lib/registry.test.ts +219 -0
- package/src/lib/registry.ts +208 -0
- package/src/lib/scenarios.ts +547 -0
- package/src/lib/setup-info.test.ts +199 -0
- package/src/lib/setup-info.ts +415 -0
- package/src/lib/shape.test.ts +289 -0
- package/src/lib/shape.ts +454 -0
- package/src/lib/sidebar-prefs.test.ts +32 -0
- package/src/lib/sidebar-prefs.ts +75 -0
- package/src/lib/source-code.test.ts +159 -0
- package/src/lib/source-code.ts +286 -0
- package/src/lib/source-doc.test.ts +131 -0
- package/src/lib/source-doc.ts +103 -0
- package/src/lib/source-highlight.test.ts +29 -0
- package/src/lib/source-highlight.ts +24 -0
- package/src/lib/source-link.test.ts +153 -0
- package/src/lib/source-link.ts +201 -0
- package/src/lib/spec-files.test.ts +24 -0
- package/src/lib/spec-files.ts +23 -0
- package/src/lib/table-directive.test.ts +139 -0
- package/src/lib/table-directive.ts +106 -0
- package/src/lib/tech.test.ts +120 -0
- package/src/lib/tech.ts +421 -0
- package/src/lib/terms.test.ts +67 -0
- package/src/lib/terms.ts +179 -0
- package/src/lib/wire-problems.test.ts +263 -0
- package/src/lib/wire-problems.ts +281 -0
- package/src/likec4/C4View.tsx +55 -0
- package/src/likec4/CanvasBridge.tsx +187 -0
- package/src/likec4/FlowView.tsx +235 -0
- package/src/likec4/InteractiveView.tsx +144 -0
- package/src/likec4/canvas-viewport.test.ts +149 -0
- package/src/likec4/canvas-viewport.ts +135 -0
- package/src/likec4/flow-edges.test.ts +51 -0
- package/src/likec4/flow-edges.ts +89 -0
- package/src/likec4/frame-theme.test.ts +76 -0
- package/src/likec4/frame-theme.ts +101 -0
- package/src/likec4/highlight-css.test.ts +49 -0
- package/src/likec4/highlight-css.ts +52 -0
- package/src/likec4/ids.test.ts +151 -0
- package/src/likec4/ids.ts +115 -0
- package/src/likec4/levels.tsx +70 -0
- package/src/likec4/mapping.test.ts +66 -0
- package/src/likec4/mapping.ts +54 -0
- package/src/likec4/reserved.json +141 -0
- package/src/likec4/view-index.ts +78 -0
- package/src/main.tsx +13 -0
- package/src/map/ContextMapGraph.tsx +274 -0
- package/src/map/ContextNode.tsx +70 -0
- package/src/map/FloatingEdge.tsx +86 -0
- package/src/merge.test.ts +1038 -0
- package/src/merge.ts +1004 -0
- package/src/pages/AdrDetail.tsx +242 -0
- package/src/pages/AdrIndex.tsx +114 -0
- package/src/pages/AggregatePage.tsx +727 -0
- package/src/pages/BlockPage.tsx +478 -0
- package/src/pages/CatalogFailure.tsx +111 -0
- package/src/pages/Changes.tsx +313 -0
- package/src/pages/ContextMap.tsx +534 -0
- package/src/pages/ContextPage.tsx +346 -0
- package/src/pages/EnumPage.tsx +282 -0
- package/src/pages/EventPage.tsx +686 -0
- package/src/pages/ExternalPage.tsx +204 -0
- package/src/pages/FlowDetail.tsx +663 -0
- package/src/pages/FlowIndex.tsx +303 -0
- package/src/pages/GraphPage.tsx +217 -0
- package/src/pages/Language.tsx +257 -0
- package/src/pages/ModulePage.tsx +366 -0
- package/src/pages/NotFound.tsx +54 -0
- package/src/pages/Overview.tsx +344 -0
- package/src/pages/Problems.tsx +360 -0
- package/src/pages/RegistryIndex.tsx +118 -0
- package/src/pages/ServicePage.tsx +746 -0
- package/src/pages/Settings.tsx +975 -0
- package/src/pages/StorePage.tsx +114 -0
- package/src/peek/PeekCard.test.ts +35 -0
- package/src/peek/PeekCard.tsx +118 -0
- package/src/peek/PeekLayer.tsx +147 -0
- package/src/peek/model.test.ts +156 -0
- package/src/peek/model.ts +253 -0
- package/src/routes.test.ts +222 -0
- package/src/routes.ts +449 -0
- package/src/selection/DetailPanel.tsx +1407 -0
- package/src/selection/hash.test.ts +73 -0
- package/src/selection/hash.ts +58 -0
- package/src/selection/model.test.ts +199 -0
- package/src/selection/model.ts +473 -0
- package/src/selection/pages.test.ts +115 -0
- package/src/selection/pages.ts +268 -0
- package/src/selection/store.test.ts +62 -0
- package/src/selection/store.ts +46 -0
- package/src/selection/sync.tsx +99 -0
- package/src/table/ColumnsMenu.tsx +72 -0
- package/src/table/DataTable.tsx +780 -0
- package/src/table/Facets.tsx +54 -0
- package/src/table/GroupMenu.tsx +69 -0
- package/src/table/Toolbar.tsx +190 -0
- package/src/table/cells.tsx +117 -0
- package/src/table/compare.test.ts +117 -0
- package/src/table/compare.ts +196 -0
- package/src/table/export.test.ts +88 -0
- package/src/table/export.ts +66 -0
- package/src/table/facet-groups.test.ts +74 -0
- package/src/table/facet-groups.ts +56 -0
- package/src/table/grouping.test.ts +84 -0
- package/src/table/grouping.ts +82 -0
- package/src/table/infer.test.ts +57 -0
- package/src/table/infer.ts +50 -0
- package/src/table/persist.test.ts +76 -0
- package/src/table/persist.ts +112 -0
- package/src/table/sort-url.test.ts +75 -0
- package/src/table/sort-url.ts +52 -0
- package/src/table/types.ts +80 -0
- package/src/table/useTableState.ts +220 -0
- package/src/test-catalog.ts +7 -0
- package/src/testing/estate/data__catalog.json +2847 -0
- package/src/testing/estate/data__flows.json +1724 -0
- package/src/testing/estate/examples__auth__portolan__api.json +212 -0
- package/src/testing/estate/examples__auth__portolan__domain.json +1204 -0
- package/src/testing/estate/examples__auth__portolan__glossary.json +226 -0
- package/src/testing/estate/examples__auth__portolan__observed.json +757 -0
- package/src/testing/estate/examples__auth__portolan__stores.json +195 -0
- package/src/testing/estate/examples__shop__cart__portolan__api.json +234 -0
- package/src/testing/estate/examples__shop__cart__portolan__commands.json +86 -0
- package/src/testing/estate/examples__shop__cart__portolan__domain.json +966 -0
- package/src/testing/estate/examples__shop__cart__portolan__observed.json +498 -0
- package/src/testing/estate/examples__shop__cart__portolan__stores.json +208 -0
- package/src/testing/estate/examples__shop__oms__portolan__glossary.json +98 -0
- package/src/testing/estate/examples__shop__oms__portolan__proto.json +186 -0
- package/src/testing/estate.ts +49 -0
- package/src/trail/Trail.tsx +81 -0
- package/src/trail/model.test.ts +157 -0
- package/src/trail/model.ts +189 -0
- package/src/trail/record.tsx +32 -0
- package/src/trail/store.test.ts +66 -0
- package/src/trail/store.ts +102 -0
- package/tsconfig.json +35 -0
- package/vite.config.ts +171 -0
|
@@ -0,0 +1,225 @@
|
|
|
1
|
+
// Chapters: the rail's second level, above steps and below the flow.
|
|
2
|
+
//
|
|
3
|
+
// A forty-five step rail is a wall. What breaks it up is already in the data —
|
|
4
|
+
// every alt, parallel and loop is a self-contained episode with a name of its
|
|
5
|
+
// own, and the plain steps between two of them are the connective tissue that
|
|
6
|
+
// gets the sequence from one to the next. So a chapter is exactly one of those
|
|
7
|
+
// two things:
|
|
8
|
+
//
|
|
9
|
+
// * a top-level frame — the alt, the par, the loop, named by its own title;
|
|
10
|
+
// * a run of top-level plain steps, named by the first and last of them.
|
|
11
|
+
//
|
|
12
|
+
// Only TOP-LEVEL nodes make chapters. A frame nested inside another frame is
|
|
13
|
+
// part of its parent's episode, not an episode of its own, and promoting it
|
|
14
|
+
// would put the same steps in two chapters at once.
|
|
15
|
+
//
|
|
16
|
+
// Chapters are derived from the FLOW, never from the filtered rail: switching
|
|
17
|
+
// the cross-context filter must not renumber or rename them. What the filter
|
|
18
|
+
// changes is which chapters have any rows left to show, and that is decided
|
|
19
|
+
// where the rows are grouped rather than here.
|
|
20
|
+
|
|
21
|
+
import type { Flow, FlowNode, Status, Step } from "../catalog";
|
|
22
|
+
import { walkSteps } from "../catalog";
|
|
23
|
+
import { contextResolver } from "./cross-context";
|
|
24
|
+
import type { OutlineRow } from "./outline";
|
|
25
|
+
|
|
26
|
+
export type ChapterKind = "alt" | "par" | "loop" | "steps";
|
|
27
|
+
|
|
28
|
+
export interface Chapter {
|
|
29
|
+
/** Stable across renders and filters: the frame's id, or the first step's. */
|
|
30
|
+
id: string;
|
|
31
|
+
kind: ChapterKind;
|
|
32
|
+
/**
|
|
33
|
+
* What this episode is called. The frame's own title for a frame — an alt
|
|
34
|
+
* takes its first branch's condition, which is the question being asked —
|
|
35
|
+
* and "first → last" for a run of plain steps.
|
|
36
|
+
*/
|
|
37
|
+
title: string;
|
|
38
|
+
/** Step numbers this chapter spans, 1-based over the whole flow. */
|
|
39
|
+
from: number;
|
|
40
|
+
to: number;
|
|
41
|
+
stepIds: string[];
|
|
42
|
+
/** How many steps of each status. The header's summary dots. */
|
|
43
|
+
status: Record<Status, number>;
|
|
44
|
+
/** Contexts these steps touch, in the order they are first touched. */
|
|
45
|
+
contexts: string[];
|
|
46
|
+
/** An alt whose opening branch ends the flow rather than rejoining it. */
|
|
47
|
+
terminal?: boolean;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/** A chapter and the rail rows that fall under it, in rail order. */
|
|
51
|
+
export interface ChapterGroup {
|
|
52
|
+
chapter: Chapter;
|
|
53
|
+
rows: OutlineRow[];
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/** What a step is called when it has to name something. */
|
|
57
|
+
export function stepTitle(step: Step): string {
|
|
58
|
+
return step.label ?? step.ref ?? step.kind;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
const NO_STATUS: Record<Status, number> = {
|
|
62
|
+
verified: 0,
|
|
63
|
+
declared: 0,
|
|
64
|
+
unresolved: 0,
|
|
65
|
+
};
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* The chapters of one flow, in walk order. A flow with no frames yields
|
|
69
|
+
* exactly one chapter, so callers never need a special case for the flat
|
|
70
|
+
* shape — the rail draws one header and every step under it.
|
|
71
|
+
*/
|
|
72
|
+
export function buildChapters(flow: Flow): Chapter[] {
|
|
73
|
+
const contextOf = contextResolver(flow);
|
|
74
|
+
|
|
75
|
+
// Numbering runs over every step of the flow, frames included, and matches
|
|
76
|
+
// the rail's own counter because both walk the tree the same way.
|
|
77
|
+
const numberOf = new Map<string, number>();
|
|
78
|
+
walkSteps(flow.steps).forEach((step, i) => numberOf.set(step.id, i + 1));
|
|
79
|
+
|
|
80
|
+
const chapters: Chapter[] = [];
|
|
81
|
+
|
|
82
|
+
const describe = (
|
|
83
|
+
id: string,
|
|
84
|
+
kind: ChapterKind,
|
|
85
|
+
title: string,
|
|
86
|
+
steps: Step[],
|
|
87
|
+
terminal?: boolean,
|
|
88
|
+
): void => {
|
|
89
|
+
if (steps.length === 0) return;
|
|
90
|
+
const status = { ...NO_STATUS };
|
|
91
|
+
const contexts: string[] = [];
|
|
92
|
+
for (const step of steps) {
|
|
93
|
+
status[step.status] += 1;
|
|
94
|
+
for (const participant of [step.from, step.to]) {
|
|
95
|
+
const context = contextOf(participant);
|
|
96
|
+
if (context && !contexts.includes(context)) contexts.push(context);
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
const first = steps[0];
|
|
100
|
+
const last = steps[steps.length - 1];
|
|
101
|
+
chapters.push({
|
|
102
|
+
id,
|
|
103
|
+
kind,
|
|
104
|
+
title,
|
|
105
|
+
from: (first && numberOf.get(first.id)) ?? 0,
|
|
106
|
+
to: (last && numberOf.get(last.id)) ?? 0,
|
|
107
|
+
stepIds: steps.map((s) => s.id),
|
|
108
|
+
status,
|
|
109
|
+
contexts,
|
|
110
|
+
...(terminal ? { terminal: true } : {}),
|
|
111
|
+
});
|
|
112
|
+
};
|
|
113
|
+
|
|
114
|
+
/** The plain steps seen since the last frame, waiting for a name. */
|
|
115
|
+
let run: Step[] = [];
|
|
116
|
+
const flushRun = (): void => {
|
|
117
|
+
if (run.length === 0) return;
|
|
118
|
+
const first = run[0];
|
|
119
|
+
const last = run[run.length - 1];
|
|
120
|
+
if (!first || !last) {
|
|
121
|
+
run = [];
|
|
122
|
+
return;
|
|
123
|
+
}
|
|
124
|
+
const title =
|
|
125
|
+
run.length === 1
|
|
126
|
+
? stepTitle(first)
|
|
127
|
+
: `${stepTitle(first)} → ${stepTitle(last)}`;
|
|
128
|
+
describe(`steps:${first.id}`, "steps", title, run);
|
|
129
|
+
run = [];
|
|
130
|
+
};
|
|
131
|
+
|
|
132
|
+
for (const node of flow.steps as FlowNode[]) {
|
|
133
|
+
if (node.type === "step") {
|
|
134
|
+
run.push(node);
|
|
135
|
+
continue;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
flushRun();
|
|
139
|
+
|
|
140
|
+
if (node.type === "alt") {
|
|
141
|
+
// The alt's name is the question it asks, and the first branch is where
|
|
142
|
+
// that question is written down. The other conditions are one row below
|
|
143
|
+
// as `else` frames, so repeating them in the header buys nothing.
|
|
144
|
+
const opening = node.branches[0];
|
|
145
|
+
describe(
|
|
146
|
+
node.id,
|
|
147
|
+
"alt",
|
|
148
|
+
opening?.title ?? "alt",
|
|
149
|
+
walkSteps([node]),
|
|
150
|
+
opening?.terminal,
|
|
151
|
+
);
|
|
152
|
+
continue;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
if (node.type === "parallel") {
|
|
156
|
+
describe(node.id, "par", node.title ?? "in parallel", walkSteps([node]));
|
|
157
|
+
continue;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
describe(node.id, "loop", node.title, walkSteps([node]));
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
flushRun();
|
|
164
|
+
return chapters;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
/**
|
|
168
|
+
* The rail's rows, cut into their chapters.
|
|
169
|
+
*
|
|
170
|
+
* A frame row belongs to the chapter of the first step underneath it: a frame
|
|
171
|
+
* header with no steps left after filtering is not a chapter of its own, it is
|
|
172
|
+
* a header for nothing, and it goes wherever its body went. Chapters whose
|
|
173
|
+
* every step was filtered away are dropped entirely rather than drawn empty.
|
|
174
|
+
*/
|
|
175
|
+
export function groupRows(
|
|
176
|
+
rows: readonly OutlineRow[],
|
|
177
|
+
chapters: readonly Chapter[],
|
|
178
|
+
): ChapterGroup[] {
|
|
179
|
+
const chapterOf = new Map<string, Chapter>();
|
|
180
|
+
for (const chapter of chapters) {
|
|
181
|
+
for (const stepId of chapter.stepIds) chapterOf.set(stepId, chapter);
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
// Resolved back to front: a frame's chapter is the chapter of the next step
|
|
185
|
+
// row, and reading backwards is what makes "next" available without a scan.
|
|
186
|
+
const owner: (Chapter | undefined)[] = new Array(rows.length);
|
|
187
|
+
let ahead: Chapter | undefined;
|
|
188
|
+
for (let i = rows.length - 1; i >= 0; i -= 1) {
|
|
189
|
+
const row = rows[i];
|
|
190
|
+
if (row?.type === "step") ahead = chapterOf.get(row.step.id);
|
|
191
|
+
owner[i] = ahead;
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
const groups: ChapterGroup[] = [];
|
|
195
|
+
rows.forEach((row, i) => {
|
|
196
|
+
const chapter = owner[i];
|
|
197
|
+
if (!chapter) return;
|
|
198
|
+
const last = groups[groups.length - 1];
|
|
199
|
+
if (last && last.chapter.id === chapter.id) last.rows.push(row);
|
|
200
|
+
else groups.push({ chapter, rows: [row] });
|
|
201
|
+
});
|
|
202
|
+
|
|
203
|
+
return groups;
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
/**
|
|
207
|
+
* The rows a chapter actually draws.
|
|
208
|
+
*
|
|
209
|
+
* A frame chapter's header already says the keyword and the condition, so the
|
|
210
|
+
* frame row that opens it would say them twice in a row. It is dropped — but
|
|
211
|
+
* only when it really is the same row: under the cross-context filter the
|
|
212
|
+
* opening branch can vanish and the NEXT one is promoted to head the frame, and
|
|
213
|
+
* then the row carries a condition the header does not have and has to stay.
|
|
214
|
+
*/
|
|
215
|
+
export function railRows(group: ChapterGroup): OutlineRow[] {
|
|
216
|
+
const [first, ...rest] = group.rows;
|
|
217
|
+
if (
|
|
218
|
+
group.chapter.kind !== "steps" &&
|
|
219
|
+
first?.type === "frame" &&
|
|
220
|
+
first.title === group.chapter.title
|
|
221
|
+
) {
|
|
222
|
+
return rest;
|
|
223
|
+
}
|
|
224
|
+
return group.rows;
|
|
225
|
+
}
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
import { describe, expect, it } from "vitest";
|
|
2
|
+
import { catalog } from "../data";
|
|
3
|
+
import { walkSteps } from "../catalog";
|
|
4
|
+
import type { Flow, Step } from "../catalog";
|
|
5
|
+
import { continuationIndex, continuationsOf, openingRef } from "./continues";
|
|
6
|
+
|
|
7
|
+
/** A step carrying `ref`; the participants are not what any of this is about. */
|
|
8
|
+
function step(id: string, ref?: string): Step {
|
|
9
|
+
return {
|
|
10
|
+
type: "step",
|
|
11
|
+
id,
|
|
12
|
+
from: "a",
|
|
13
|
+
to: "b",
|
|
14
|
+
kind: ref ? "event" : "call",
|
|
15
|
+
...(ref ? { ref } : {}),
|
|
16
|
+
status: "declared",
|
|
17
|
+
};
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
function makeFlow(slug: string, steps: Step[]): Flow {
|
|
21
|
+
return {
|
|
22
|
+
id: `test.${slug}`,
|
|
23
|
+
slug,
|
|
24
|
+
name: slug.replace(/-/g, " "),
|
|
25
|
+
summary: "",
|
|
26
|
+
owner: "shop",
|
|
27
|
+
participants: [],
|
|
28
|
+
steps,
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
// The fixture estate has no flow that opens on an event — every one of them
|
|
33
|
+
// starts with an inbound call — so the seam this module is about is built by
|
|
34
|
+
// hand here. That is the honest way round: the rule is what is under test, and
|
|
35
|
+
// a catalog that happens not to exercise it must not silently skip it.
|
|
36
|
+
const emits = makeFlow("emits", [step("s1"), step("s2", "shop.oms.Order.Placed")]);
|
|
37
|
+
const opens = makeFlow("opens", [
|
|
38
|
+
step("t1", "shop.oms.Order.Placed"),
|
|
39
|
+
step("t2", "shop.oms.Order.Priced"),
|
|
40
|
+
]);
|
|
41
|
+
const mentions = makeFlow("mentions", [
|
|
42
|
+
step("u1"),
|
|
43
|
+
step("u2", "shop.oms.Order.Placed"),
|
|
44
|
+
]);
|
|
45
|
+
const flows = [emits, opens, mentions];
|
|
46
|
+
|
|
47
|
+
describe("openingRef", () => {
|
|
48
|
+
it("is the ref of the flow's first step, in walk order", () => {
|
|
49
|
+
expect(openingRef(opens)).toBe("shop.oms.Order.Placed");
|
|
50
|
+
expect(openingRef(emits)).toBeUndefined();
|
|
51
|
+
for (const f of catalog.flows) {
|
|
52
|
+
expect(openingRef(f)).toBe(walkSteps(f.steps)[0]?.ref);
|
|
53
|
+
}
|
|
54
|
+
});
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
describe("continuationsOf", () => {
|
|
58
|
+
it("finds the flow a step's event opens", () => {
|
|
59
|
+
const hits = continuationsOf(step("s2", "shop.oms.Order.Placed"), emits, flows);
|
|
60
|
+
expect(hits.map((h) => h.slug)).toEqual(["opens"]);
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
it("ignores an event another flow merely mentions in its middle", () => {
|
|
64
|
+
// `mentions` carries the same ref, but not first, so it is another reader
|
|
65
|
+
// of the event rather than the continuation of this one.
|
|
66
|
+
const hits = continuationsOf(step("s2", "shop.oms.Order.Placed"), emits, flows);
|
|
67
|
+
expect(hits.map((h) => h.slug)).not.toContain("mentions");
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
it("never points a flow at itself", () => {
|
|
71
|
+
const hits = continuationsOf(step("t1", "shop.oms.Order.Placed"), opens, flows);
|
|
72
|
+
expect(hits).toEqual([]);
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
it("says nothing for a step that carries no ref", () => {
|
|
76
|
+
expect(continuationsOf(step("s1"), emits, flows)).toEqual([]);
|
|
77
|
+
});
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
describe("continuationIndex", () => {
|
|
81
|
+
it("agrees with the per-step answer", () => {
|
|
82
|
+
const index = continuationIndex(emits, flows);
|
|
83
|
+
for (const s of walkSteps(emits.steps)) {
|
|
84
|
+
expect(index.get(s.id) ?? []).toEqual(continuationsOf(s, emits, flows));
|
|
85
|
+
}
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
it("holds an entry only for steps that actually continue somewhere", () => {
|
|
89
|
+
const index = continuationIndex(emits, flows);
|
|
90
|
+
expect([...index.keys()]).toEqual(["s2"]);
|
|
91
|
+
for (const [, hits] of index) expect(hits.length).toBeGreaterThan(0);
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
it("agrees with the per-step answer across the real catalog too", () => {
|
|
95
|
+
for (const f of catalog.flows) {
|
|
96
|
+
const index = continuationIndex(f, catalog.flows);
|
|
97
|
+
for (const s of walkSteps(f.steps)) {
|
|
98
|
+
expect(index.get(s.id) ?? []).toEqual(continuationsOf(s, f, catalog.flows));
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
});
|
|
102
|
+
});
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
// Where a flow hands off to another one.
|
|
2
|
+
//
|
|
3
|
+
// A step that publishes an event is often the last thing one flow says and the
|
|
4
|
+
// first thing another one hears. Nothing in the catalog states that seam — it
|
|
5
|
+
// is not a field, it is a coincidence of two refs — but it is the single most
|
|
6
|
+
// useful link a rail can carry, because it is the answer to "and then what?"
|
|
7
|
+
// that the flow itself cannot give.
|
|
8
|
+
//
|
|
9
|
+
// The rule is deliberately strict: only the FIRST step of the other flow
|
|
10
|
+
// counts. A flow that merely mentions the same event somewhere in its middle
|
|
11
|
+
// is not continuing this one, it is another reader of the same event, and the
|
|
12
|
+
// event page already says so.
|
|
13
|
+
|
|
14
|
+
import type { Flow, Step } from "../catalog";
|
|
15
|
+
import { walkSteps } from "../catalog";
|
|
16
|
+
|
|
17
|
+
export interface Continuation {
|
|
18
|
+
slug: string;
|
|
19
|
+
name: string;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/** The ref the flow opens with, or undefined for a flow that opens with none. */
|
|
23
|
+
export function openingRef(flow: Flow): string | undefined {
|
|
24
|
+
return walkSteps(flow.steps)[0]?.ref;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Flows that begin where this step ends. `from` is the flow the step belongs
|
|
29
|
+
* to and is never returned: a flow does not continue in itself.
|
|
30
|
+
*/
|
|
31
|
+
export function continuationsOf(
|
|
32
|
+
step: Step,
|
|
33
|
+
from: Flow,
|
|
34
|
+
flows: readonly Flow[],
|
|
35
|
+
): Continuation[] {
|
|
36
|
+
if (!step.ref) return [];
|
|
37
|
+
return flows
|
|
38
|
+
.filter((flow) => flow.slug !== from.slug && openingRef(flow) === step.ref)
|
|
39
|
+
.map((flow) => ({ slug: flow.slug, name: flow.name }));
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/** Continuations for every step of a flow at once, keyed by step id. */
|
|
43
|
+
export function continuationIndex(
|
|
44
|
+
flow: Flow,
|
|
45
|
+
flows: readonly Flow[],
|
|
46
|
+
): Map<string, Continuation[]> {
|
|
47
|
+
// One pass over the other flows rather than one per step: a flow with forty
|
|
48
|
+
// steps would otherwise walk the whole catalog forty times.
|
|
49
|
+
const byRef = new Map<string, Continuation[]>();
|
|
50
|
+
for (const other of flows) {
|
|
51
|
+
if (other.slug === flow.slug) continue;
|
|
52
|
+
const ref = openingRef(other);
|
|
53
|
+
if (!ref) continue;
|
|
54
|
+
const list = byRef.get(ref) ?? [];
|
|
55
|
+
list.push({ slug: other.slug, name: other.name });
|
|
56
|
+
byRef.set(ref, list);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
const out = new Map<string, Continuation[]>();
|
|
60
|
+
for (const step of walkSteps(flow.steps)) {
|
|
61
|
+
const hits = step.ref ? byRef.get(step.ref) : undefined;
|
|
62
|
+
if (hits && hits.length > 0) out.set(step.id, hits);
|
|
63
|
+
}
|
|
64
|
+
return out;
|
|
65
|
+
}
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
import { describe, expect, it } from "vitest";
|
|
2
|
+
import { readFileSync } from "node:fs";
|
|
3
|
+
import { catalog } from "../testing/estate";
|
|
4
|
+
// Same split as src/likec4/ids.test.ts: the predicate is checked against the
|
|
5
|
+
// frozen fixture, and the generated views against the estate they were
|
|
6
|
+
// generated from.
|
|
7
|
+
import { catalog as shipped } from "../data";
|
|
8
|
+
import { walkSteps } from "../catalog";
|
|
9
|
+
import {
|
|
10
|
+
contextResolver,
|
|
11
|
+
hiddenStepIds,
|
|
12
|
+
isCrossContext,
|
|
13
|
+
} from "./cross-context";
|
|
14
|
+
import { flowCrossViewId, flowViewId } from "../likec4/ids";
|
|
15
|
+
|
|
16
|
+
const checkout = catalog.flows.find((f) => f.slug === "checkout");
|
|
17
|
+
if (!checkout) throw new Error("fixture missing checkout flow");
|
|
18
|
+
|
|
19
|
+
describe("isCrossContext", () => {
|
|
20
|
+
it("drops internal calls, self messages and same-context hops", () => {
|
|
21
|
+
expect([...hiddenStepIds(checkout)].sort()).toEqual([
|
|
22
|
+
"s10",
|
|
23
|
+
"s11",
|
|
24
|
+
"s13",
|
|
25
|
+
"s17",
|
|
26
|
+
"s18",
|
|
27
|
+
"s2",
|
|
28
|
+
"s23",
|
|
29
|
+
"s3",
|
|
30
|
+
"s33",
|
|
31
|
+
"s4",
|
|
32
|
+
"s41",
|
|
33
|
+
"s6",
|
|
34
|
+
"s8",
|
|
35
|
+
]);
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
it("keeps hops touching a null-context lane", () => {
|
|
39
|
+
const contextOf = contextResolver(checkout);
|
|
40
|
+
const s12 = walkSteps(checkout.steps).find((s) => s.id === "s12");
|
|
41
|
+
if (!s12) throw new Error("no s12");
|
|
42
|
+
// shop.oms (shop) -> bus (null)
|
|
43
|
+
expect(isCrossContext(s12, contextOf)).toBe(true);
|
|
44
|
+
});
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
describe("generated LikeC4 views agree with the predicate", () => {
|
|
48
|
+
const views = readFileSync("likec4/views.c4", "utf8");
|
|
49
|
+
|
|
50
|
+
it("declares both a full and a cross view for every flow", () => {
|
|
51
|
+
for (const flow of shipped.flows) {
|
|
52
|
+
expect(views, flow.slug).toContain(`dynamic view ${flowViewId(flow)} {`);
|
|
53
|
+
expect(views, flow.slug).toContain(
|
|
54
|
+
`dynamic view ${flowCrossViewId(flow)} {`,
|
|
55
|
+
);
|
|
56
|
+
}
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
it("omits exactly the hidden steps from a cross view", () => {
|
|
60
|
+
// Whichever shipped flow hides the most: the point is the predicate, and
|
|
61
|
+
// pinning one flow by name is how this test broke when that flow left.
|
|
62
|
+
const flow = [...shipped.flows].sort(
|
|
63
|
+
(a, b) => hiddenStepIds(b).size - hiddenStepIds(a).size,
|
|
64
|
+
)[0];
|
|
65
|
+
if (!flow) throw new Error("no flows");
|
|
66
|
+
const hidden = hiddenStepIds(flow);
|
|
67
|
+
expect(hidden.size).toBeGreaterThan(0);
|
|
68
|
+
|
|
69
|
+
const start = views.indexOf(`dynamic view ${flowCrossViewId(flow)} {`);
|
|
70
|
+
expect(start, flow.slug).toBeGreaterThan(-1);
|
|
71
|
+
const section = views.slice(
|
|
72
|
+
start,
|
|
73
|
+
views.indexOf("dynamic view", start + 10),
|
|
74
|
+
);
|
|
75
|
+
|
|
76
|
+
const labelOf = (id: string): string => {
|
|
77
|
+
const step = walkSteps(flow.steps).find((s) => s.id === id);
|
|
78
|
+
if (!step) throw new Error(`no step ${id}`);
|
|
79
|
+
return step.label ?? step.ref ?? step.kind;
|
|
80
|
+
};
|
|
81
|
+
const shown = new Set(
|
|
82
|
+
walkSteps(flow.steps)
|
|
83
|
+
.filter((s) => !hidden.has(s.id))
|
|
84
|
+
.map((s) => labelOf(s.id)),
|
|
85
|
+
);
|
|
86
|
+
for (const label of shown) {
|
|
87
|
+
expect(section, `${flow.slug}: ${label}`).toContain(label);
|
|
88
|
+
}
|
|
89
|
+
// A hop the predicate hides is in no cross view - unless a hop that stays
|
|
90
|
+
// happens to carry the same label, which is not this rule's business.
|
|
91
|
+
for (const id of hidden) {
|
|
92
|
+
const label = labelOf(id);
|
|
93
|
+
if (shown.has(label)) continue;
|
|
94
|
+
expect(section, `${flow.slug}: ${label}`).not.toContain(label);
|
|
95
|
+
}
|
|
96
|
+
});
|
|
97
|
+
});
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
// The cross-context predicate. Data logic, not rendering: the LikeC4 generator
|
|
2
|
+
// applies it when emitting the *_cross views, and the step rail applies it to
|
|
3
|
+
// stay in step with whichever view is on screen.
|
|
4
|
+
|
|
5
|
+
import type { Flow, Step } from "../catalog";
|
|
6
|
+
import { walkSteps } from "../catalog";
|
|
7
|
+
|
|
8
|
+
export function isCrossContext(
|
|
9
|
+
step: Step,
|
|
10
|
+
contextOf: (participantId: string) => string | null,
|
|
11
|
+
): boolean {
|
|
12
|
+
if (step.kind === "call") return false;
|
|
13
|
+
if (step.from === step.to) return false;
|
|
14
|
+
const a = contextOf(step.from);
|
|
15
|
+
const b = contextOf(step.to);
|
|
16
|
+
if (a !== null && a === b) return false;
|
|
17
|
+
return true;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export function contextResolver(
|
|
21
|
+
flow: Flow,
|
|
22
|
+
): (participantId: string) => string | null {
|
|
23
|
+
const contexts = new Map(flow.participants.map((p) => [p.id, p.context]));
|
|
24
|
+
return (id) => contexts.get(id) ?? null;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/** Ids of the steps a cross-context view leaves out. */
|
|
28
|
+
export function hiddenStepIds(flow: Flow): Set<string> {
|
|
29
|
+
const contextOf = contextResolver(flow);
|
|
30
|
+
return new Set(
|
|
31
|
+
walkSteps(flow.steps)
|
|
32
|
+
.filter((step) => !isCrossContext(step, contextOf))
|
|
33
|
+
.map((step) => step.id),
|
|
34
|
+
);
|
|
35
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { readFileSync } from "node:fs";
|
|
2
|
+
import { describe, expect, it } from "vitest";
|
|
3
|
+
import { catalog, index } from "../data";
|
|
4
|
+
import { flowAnswers } from "./answers";
|
|
5
|
+
import { flowMermaid } from "./mermaid";
|
|
6
|
+
|
|
7
|
+
/** The fenced mermaid block of a generated flow page. */
|
|
8
|
+
function generated(slug: string): string {
|
|
9
|
+
const page = readFileSync(`docs/flows/${slug}.md`, "utf8");
|
|
10
|
+
const match = /```mermaid\n([\s\S]*?)```/.exec(page);
|
|
11
|
+
if (!match) throw new Error(`no mermaid block in docs/flows/${slug}.md`);
|
|
12
|
+
return match[1]!;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
describe("flowMermaid", () => {
|
|
16
|
+
// The Go generator and this port draw the same diagram, or one of them is
|
|
17
|
+
// wrong; the generated pages are the record of what the Go one drew.
|
|
18
|
+
it.each(catalog.flows.map((f) => f.slug))("draws %s as gen-markdown does", (slug) => {
|
|
19
|
+
const flow = catalog.flows.find((f) => f.slug === slug)!;
|
|
20
|
+
expect(flowMermaid(flow, flowAnswers(index, flow))).toBe(generated(slug));
|
|
21
|
+
});
|
|
22
|
+
});
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
// The flow as a Mermaid sequence diagram, for copying out.
|
|
2
|
+
//
|
|
3
|
+
// This is the same picture plugins/gen-markdown/flow.go draws into docs/, line
|
|
4
|
+
// for line, and mermaid.test.ts holds the two to each other: a reader who
|
|
5
|
+
// pastes this into a wiki and a reader of the generated page see one diagram.
|
|
6
|
+
// Participants are aliased p0, p1, … because Mermaid wants an identifier where
|
|
7
|
+
// the catalog has a dotted service id.
|
|
8
|
+
|
|
9
|
+
import type { Flow, FlowNode, Step } from "../catalog";
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* `answers` is what each step's callee hands back, by step id - see
|
|
13
|
+
* `answers.ts`. It is passed in rather than looked up here so that this stays
|
|
14
|
+
* a function of the flow, and so the Go generator, which has the catalog and
|
|
15
|
+
* not the index, can hand over the same map.
|
|
16
|
+
*/
|
|
17
|
+
export function flowMermaid(
|
|
18
|
+
flow: Flow,
|
|
19
|
+
answers: ReadonlyMap<string, string> = new Map(),
|
|
20
|
+
): string {
|
|
21
|
+
const alias = new Map<string, string>();
|
|
22
|
+
flow.participants.forEach((p, i) => alias.set(p.id, `p${i}`));
|
|
23
|
+
const of = (id: string) => alias.get(id) ?? id;
|
|
24
|
+
|
|
25
|
+
const lines: string[] = ["sequenceDiagram", " autonumber"];
|
|
26
|
+
for (const p of flow.participants) {
|
|
27
|
+
const keyword = p.kind === "actor" ? "actor" : "participant";
|
|
28
|
+
lines.push(` ${keyword} ${of(p.id)} as ${text(p.label || p.id)}`);
|
|
29
|
+
}
|
|
30
|
+
emit(flow.steps, of, 1, lines, answers);
|
|
31
|
+
return `${lines.join("\n")}\n`;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function emit(
|
|
35
|
+
nodes: FlowNode[],
|
|
36
|
+
of: (id: string) => string,
|
|
37
|
+
depth: number,
|
|
38
|
+
out: string[],
|
|
39
|
+
answers: ReadonlyMap<string, string>,
|
|
40
|
+
): void {
|
|
41
|
+
const indent = " ".repeat(depth);
|
|
42
|
+
for (const node of nodes) {
|
|
43
|
+
switch (node.type) {
|
|
44
|
+
case "step": {
|
|
45
|
+
// An event is drawn with the async arrow. A reader who cannot tell a
|
|
46
|
+
// call from a publication is reading a different flow.
|
|
47
|
+
const arrow = node.kind === "event" ? "-)" : "->>";
|
|
48
|
+
// The reply rides on the hop's own label rather than coming back as a
|
|
49
|
+
// second message: `autonumber` counts messages, and the numbers here
|
|
50
|
+
// are the numbers of the step list beside the diagram.
|
|
51
|
+
out.push(`${indent}${of(node.from)}${arrow}${of(node.to)}: ${text(labelWithAnswer(node, answers))}`);
|
|
52
|
+
break;
|
|
53
|
+
}
|
|
54
|
+
case "parallel": {
|
|
55
|
+
out.push(`${indent}par ${text(node.title?.trim() ? node.title : "in parallel")}`);
|
|
56
|
+
node.branches.forEach((branch, i) => {
|
|
57
|
+
if (i > 0) out.push(`${indent}and`);
|
|
58
|
+
emit(branch, of, depth + 1, out, answers);
|
|
59
|
+
});
|
|
60
|
+
out.push(`${indent}end`);
|
|
61
|
+
break;
|
|
62
|
+
}
|
|
63
|
+
case "alt": {
|
|
64
|
+
node.branches.forEach((branch, i) => {
|
|
65
|
+
out.push(`${indent}${i === 0 ? "alt " : "else "}${text(branch.title)}`);
|
|
66
|
+
emit(branch.steps, of, depth + 1, out, answers);
|
|
67
|
+
// A branch that ends the flow has to say so inside the diagram, or
|
|
68
|
+
// the steps drawn after the alt read as if they follow it too.
|
|
69
|
+
if (branch.terminal) {
|
|
70
|
+
const last = lastParticipant(branch.steps);
|
|
71
|
+
if (last)
|
|
72
|
+
out.push(`${" ".repeat(depth + 1)}Note over ${of(last)}: flow ends here`);
|
|
73
|
+
}
|
|
74
|
+
});
|
|
75
|
+
out.push(`${indent}end`);
|
|
76
|
+
break;
|
|
77
|
+
}
|
|
78
|
+
case "loop": {
|
|
79
|
+
out.push(`${indent}loop ${text(node.title)}`);
|
|
80
|
+
emit(node.steps, of, depth + 1, out, answers);
|
|
81
|
+
out.push(`${indent}end`);
|
|
82
|
+
break;
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
function stepLabel(step: Step): string {
|
|
89
|
+
return step.label || step.ref || step.kind;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
function lastParticipant(nodes: FlowNode[]): string {
|
|
93
|
+
for (let i = nodes.length - 1; i >= 0; i--) {
|
|
94
|
+
const node = nodes[i]!;
|
|
95
|
+
switch (node.type) {
|
|
96
|
+
case "step":
|
|
97
|
+
return node.to;
|
|
98
|
+
case "parallel":
|
|
99
|
+
for (let j = node.branches.length - 1; j >= 0; j--) {
|
|
100
|
+
const p = lastParticipant(node.branches[j]!);
|
|
101
|
+
if (p) return p;
|
|
102
|
+
}
|
|
103
|
+
break;
|
|
104
|
+
case "alt":
|
|
105
|
+
for (let j = node.branches.length - 1; j >= 0; j--) {
|
|
106
|
+
const p = lastParticipant(node.branches[j]!.steps);
|
|
107
|
+
if (p) return p;
|
|
108
|
+
}
|
|
109
|
+
break;
|
|
110
|
+
case "loop": {
|
|
111
|
+
const p = lastParticipant(node.steps);
|
|
112
|
+
if (p) return p;
|
|
113
|
+
break;
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
return "";
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/** The step's label, and what comes back when a contract says so. */
|
|
121
|
+
export function labelWithAnswer(
|
|
122
|
+
step: Step,
|
|
123
|
+
answers: ReadonlyMap<string, string>,
|
|
124
|
+
): string {
|
|
125
|
+
const answer = answers.get(step.id);
|
|
126
|
+
return answer ? `${stepLabel(step)} → ${answer}` : stepLabel(step);
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
/**
|
|
130
|
+
* A label made safe on a Mermaid line: a newline ends the statement, so does
|
|
131
|
+
* a semicolon, and a bare # opens an entity code. Semicolons go first, or the
|
|
132
|
+
* escape for # is mangled into text.
|
|
133
|
+
*/
|
|
134
|
+
function text(s: string): string {
|
|
135
|
+
return s
|
|
136
|
+
.replace(/\r\n/g, "\n")
|
|
137
|
+
.replace(/\n/g, " ")
|
|
138
|
+
.replace(/;/g, ",")
|
|
139
|
+
.replace(/#/g, "#35;")
|
|
140
|
+
.trim();
|
|
141
|
+
}
|