@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,212 @@
|
|
|
1
|
+
import { describe, expect, it } from "vitest";
|
|
2
|
+
import { rawCatalog as raw } from "../test-catalog";
|
|
3
|
+
import type { Catalog, Flow } from "../catalog";
|
|
4
|
+
import { stepFrames, walkSteps } from "../catalog";
|
|
5
|
+
import { hiddenStepIds } from "./cross-context";
|
|
6
|
+
import { flowPaths } from "./paths";
|
|
7
|
+
import { buildOutline, outlineSteps } from "./outline";
|
|
8
|
+
import type { OutlineFrame } from "./outline";
|
|
9
|
+
|
|
10
|
+
const catalog = raw as unknown as Catalog;
|
|
11
|
+
const checkout = catalog.flows.find((f) => f.slug === "checkout") as Flow;
|
|
12
|
+
|
|
13
|
+
const NO_FILTER = { hidden: new Set<string>(), crossOnly: false };
|
|
14
|
+
|
|
15
|
+
function frames(rows: ReturnType<typeof buildOutline>): OutlineFrame[] {
|
|
16
|
+
return rows.filter((r): r is OutlineFrame => r.type === "frame");
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
describe("buildOutline with a status filter", () => {
|
|
20
|
+
it("marks the steps of other statuses off, and keeps them", () => {
|
|
21
|
+
const rows = buildOutline(checkout, { ...NO_FILTER, statuses: new Set(["verified"]) });
|
|
22
|
+
const steps = outlineSteps(rows);
|
|
23
|
+
expect(steps.length).toBe(walkSteps(checkout.steps).length);
|
|
24
|
+
const off = steps.filter((r) => r.offStatus).map((r) => r.step.status);
|
|
25
|
+
expect(off.length).toBeGreaterThan(0);
|
|
26
|
+
expect(off.every((s) => s !== "verified")).toBe(true);
|
|
27
|
+
expect(steps.filter((r) => !r.offStatus).every((r) => r.step.status === "verified")).toBe(true);
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
it("marks nothing off when no status is chosen", () => {
|
|
31
|
+
const steps = outlineSteps(buildOutline(checkout, { ...NO_FILTER, statuses: null }));
|
|
32
|
+
expect(steps.some((r) => r.offStatus)).toBe(false);
|
|
33
|
+
});
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
describe("buildOutline", () => {
|
|
37
|
+
it("heads every frame with the keyword its shape calls for", () => {
|
|
38
|
+
const keywords = frames(buildOutline(checkout, NO_FILTER)).map(
|
|
39
|
+
(f) => f.keyword,
|
|
40
|
+
);
|
|
41
|
+
expect(keywords.length).toBeGreaterThan(0);
|
|
42
|
+
// A choice always opens with `alt` and continues with `else`; a parallel
|
|
43
|
+
// opens with `par` and continues with `and`. Neither continuation can
|
|
44
|
+
// appear before its opening, or the rail would show a branch of nothing.
|
|
45
|
+
const seenAlt = keywords.indexOf("alt");
|
|
46
|
+
const seenPar = keywords.indexOf("par");
|
|
47
|
+
expect(keywords.indexOf("else") === -1 || seenAlt !== -1).toBe(true);
|
|
48
|
+
expect(keywords.indexOf("and") === -1 || seenPar !== -1).toBe(true);
|
|
49
|
+
if (keywords.includes("else")) {
|
|
50
|
+
expect(seenAlt).toBeLessThan(keywords.indexOf("else"));
|
|
51
|
+
}
|
|
52
|
+
if (keywords.includes("and")) {
|
|
53
|
+
expect(seenPar).toBeLessThan(keywords.indexOf("and"));
|
|
54
|
+
}
|
|
55
|
+
for (const f of frames(buildOutline(checkout, NO_FILTER))) {
|
|
56
|
+
// Only `and` is nameless: it continues a parallel that is already named.
|
|
57
|
+
if (f.keyword !== "and") expect(f.title, f.keyword).toBeTruthy();
|
|
58
|
+
}
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
it("marks exactly the branches the catalog calls terminal", () => {
|
|
62
|
+
for (const flow of catalog.flows) {
|
|
63
|
+
const rows = buildOutline(flow, NO_FILTER);
|
|
64
|
+
const marked = frames(rows)
|
|
65
|
+
.filter((f) => f.terminal)
|
|
66
|
+
.map((f) => f.title);
|
|
67
|
+
const declared = [...stepFrames(flow.steps).values()]
|
|
68
|
+
.flat()
|
|
69
|
+
.filter((f) => f.terminal)
|
|
70
|
+
.map((f) => f.branch);
|
|
71
|
+
expect([...new Set(marked)].sort(), flow.slug).toEqual(
|
|
72
|
+
[...new Set(declared)].sort(),
|
|
73
|
+
);
|
|
74
|
+
}
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
it("indents every step by how many frames enclose it", () => {
|
|
78
|
+
for (const flow of catalog.flows) {
|
|
79
|
+
const enclosing = stepFrames(flow.steps);
|
|
80
|
+
for (const row of outlineSteps(buildOutline(flow, NO_FILTER))) {
|
|
81
|
+
expect(row.depth, `${flow.slug} ${row.step.id}`).toBe(
|
|
82
|
+
(enclosing.get(row.step.id) ?? []).length,
|
|
83
|
+
);
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
it("numbers over every step, filtered or not", () => {
|
|
89
|
+
const all = outlineSteps(buildOutline(checkout, NO_FILTER));
|
|
90
|
+
expect(all.map((r) => r.number)).toEqual(all.map((_, i) => i + 1));
|
|
91
|
+
|
|
92
|
+
const hidden = hiddenStepIds(checkout);
|
|
93
|
+
const filtered = outlineSteps(
|
|
94
|
+
buildOutline(checkout, { hidden, crossOnly: true }),
|
|
95
|
+
);
|
|
96
|
+
// The numbers survive the filter, so "step 7" means the same thing in both.
|
|
97
|
+
for (const row of filtered) {
|
|
98
|
+
expect(row.number).toBe(
|
|
99
|
+
all.find((r) => r.step.id === row.step.id)?.number,
|
|
100
|
+
);
|
|
101
|
+
}
|
|
102
|
+
expect(filtered.length).toBeLessThan(all.length);
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
it("drops a frame whose every step the filter removed", () => {
|
|
106
|
+
const first = walkSteps(checkout.steps)[0];
|
|
107
|
+
if (!first) throw new Error("fixture has no steps");
|
|
108
|
+
const hidden = new Set(
|
|
109
|
+
walkSteps(checkout.steps)
|
|
110
|
+
.map((s) => s.id)
|
|
111
|
+
.filter((id) => id !== first.id),
|
|
112
|
+
);
|
|
113
|
+
const rows = buildOutline(checkout, { hidden, crossOnly: true });
|
|
114
|
+
expect(frames(rows)).toEqual([]);
|
|
115
|
+
expect(outlineSteps(rows).map((r) => r.step.id)).toEqual([first.id]);
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
it("opens the frame at the first branch that survives the filter", () => {
|
|
119
|
+
// Hide every step of the first alt branch: the next surviving branch must
|
|
120
|
+
// read `alt`, not `else`, or the rail shows a choice with no opening arm.
|
|
121
|
+
const enclosing = stepFrames(checkout.steps);
|
|
122
|
+
const firstAlt = [...enclosing.values()]
|
|
123
|
+
.flat()
|
|
124
|
+
.find((f) => f.kind === "alt");
|
|
125
|
+
if (!firstAlt) throw new Error("fixture has no alt");
|
|
126
|
+
const firstBranch = firstAlt.branch;
|
|
127
|
+
const hidden = new Set(
|
|
128
|
+
walkSteps(checkout.steps)
|
|
129
|
+
.map((s) => s.id)
|
|
130
|
+
.filter((id) =>
|
|
131
|
+
(enclosing.get(id) ?? []).some(
|
|
132
|
+
(f) => f.id === firstAlt.id && f.branch === firstBranch,
|
|
133
|
+
),
|
|
134
|
+
),
|
|
135
|
+
);
|
|
136
|
+
const rows = buildOutline(checkout, { hidden, crossOnly: true });
|
|
137
|
+
const opening = frames(rows).find(
|
|
138
|
+
(f) => f.keyword === "alt" || f.keyword === "else",
|
|
139
|
+
);
|
|
140
|
+
expect(opening?.keyword).toBe("alt");
|
|
141
|
+
expect(opening?.title).not.toBe(firstBranch);
|
|
142
|
+
});
|
|
143
|
+
|
|
144
|
+
it("keeps only the steps of a chosen path", () => {
|
|
145
|
+
const path = flowPaths(checkout).paths.find((p) => p.terminal);
|
|
146
|
+
if (!path) throw new Error("fixture has no terminal path");
|
|
147
|
+
const all = walkSteps(checkout.steps).map((s) => s.id);
|
|
148
|
+
const rows = buildOutline(checkout, { ...NO_FILTER, path: path.stepIds });
|
|
149
|
+
|
|
150
|
+
expect(outlineSteps(rows).map((r) => r.step.id)).toEqual(
|
|
151
|
+
all.filter((id) => path.stepIds.has(id)),
|
|
152
|
+
);
|
|
153
|
+
expect(outlineSteps(rows).every((r) => !r.offPath)).toBe(true);
|
|
154
|
+
});
|
|
155
|
+
|
|
156
|
+
it("keeps only the branch frames selected by the path", () => {
|
|
157
|
+
const path = flowPaths(checkout).paths.find((p) => p.terminal);
|
|
158
|
+
if (!path) throw new Error("fixture has no terminal path");
|
|
159
|
+
const rows = buildOutline(checkout, { ...NO_FILTER, path: path.stepIds });
|
|
160
|
+
const choices = frames(rows).filter(
|
|
161
|
+
(f) => f.keyword === "alt" || f.keyword === "else",
|
|
162
|
+
);
|
|
163
|
+
|
|
164
|
+
expect(choices.map((f) => f.title)).toEqual(
|
|
165
|
+
path.choices.map((c) => c.title),
|
|
166
|
+
);
|
|
167
|
+
expect(choices.every((f) => !f.offPath)).toBe(true);
|
|
168
|
+
});
|
|
169
|
+
|
|
170
|
+
it("numbers every step by its place in the whole flow, path or not", () => {
|
|
171
|
+
const order = walkSteps(checkout.steps).map((s) => s.id);
|
|
172
|
+
const path = flowPaths(checkout).paths.find((p) => p.terminal);
|
|
173
|
+
if (!path) throw new Error("fixture has no terminal path");
|
|
174
|
+
const rows = buildOutline(checkout, { ...NO_FILTER, path: path.stepIds });
|
|
175
|
+
expect(outlineSteps(rows).map((r) => r.number)).toEqual(
|
|
176
|
+
order
|
|
177
|
+
.map((id, i) => ({ id, number: i + 1 }))
|
|
178
|
+
.filter(({ id }) => path.stepIds.has(id))
|
|
179
|
+
.map(({ number }) => number),
|
|
180
|
+
);
|
|
181
|
+
});
|
|
182
|
+
|
|
183
|
+
it("composes the path marking with the cross-context filter", () => {
|
|
184
|
+
const hidden = hiddenStepIds(checkout);
|
|
185
|
+
const path = flowPaths(checkout).paths.find((p) => p.terminal);
|
|
186
|
+
if (!path) throw new Error("fixture has no terminal path");
|
|
187
|
+
const rows = buildOutline(checkout, {
|
|
188
|
+
hidden,
|
|
189
|
+
crossOnly: true,
|
|
190
|
+
path: path.stepIds,
|
|
191
|
+
});
|
|
192
|
+
// Both filters remove their own set; the intersection is what remains.
|
|
193
|
+
expect(outlineSteps(rows).map((r) => r.step.id)).toEqual(
|
|
194
|
+
walkSteps(checkout.steps)
|
|
195
|
+
.map((s) => s.id)
|
|
196
|
+
.filter((id) => !hidden.has(id) && path.stepIds.has(id)),
|
|
197
|
+
);
|
|
198
|
+
expect(outlineSteps(rows).every((r) => !r.offPath)).toBe(true);
|
|
199
|
+
});
|
|
200
|
+
|
|
201
|
+
it("emits nothing but steps for a flow with no frames", () => {
|
|
202
|
+
const flat = catalog.flows.find(
|
|
203
|
+
(f) =>
|
|
204
|
+
stepFrames(f.steps).size > 0 &&
|
|
205
|
+
[...stepFrames(f.steps).values()].every((s) => s.length === 0),
|
|
206
|
+
);
|
|
207
|
+
if (!flat) throw new Error("fixture has no flat flow");
|
|
208
|
+
const rows = buildOutline(flat, NO_FILTER);
|
|
209
|
+
expect(frames(rows)).toEqual([]);
|
|
210
|
+
expect(rows).toHaveLength(outlineSteps(rows).length);
|
|
211
|
+
});
|
|
212
|
+
});
|
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
// The rail's shape. A flow is a tree of frames, but a rail is a list, and the
|
|
2
|
+
// flattening is where the meaning usually goes missing: indentation alone
|
|
3
|
+
// cannot tell an alternative apart from a step that merely runs later.
|
|
4
|
+
//
|
|
5
|
+
// So the frames are flattened INTO the list rather than out of it. Every alt,
|
|
6
|
+
// parallel and loop keeps a header row carrying its keyword and its condition,
|
|
7
|
+
// in the sequence-diagram convention — alt / else, par / and, loop — and the
|
|
8
|
+
// steps inside it are indented under that row.
|
|
9
|
+
|
|
10
|
+
import type { Flow, FlowNode, Status, Step } from "../catalog";
|
|
11
|
+
|
|
12
|
+
export type FrameKeyword = "alt" | "else" | "par" | "and" | "loop";
|
|
13
|
+
|
|
14
|
+
export interface OutlineFrame {
|
|
15
|
+
type: "frame";
|
|
16
|
+
key: string;
|
|
17
|
+
depth: number;
|
|
18
|
+
keyword: FrameKeyword;
|
|
19
|
+
/** The loop's title, the parallel's title, or the alt branch's condition. */
|
|
20
|
+
title?: string;
|
|
21
|
+
/** Alt branch that ends the flow instead of rejoining it. */
|
|
22
|
+
terminal?: boolean;
|
|
23
|
+
/** No step under this frame runs on the chosen path. */
|
|
24
|
+
offPath?: boolean;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export interface OutlineStep {
|
|
28
|
+
type: "step";
|
|
29
|
+
key: string;
|
|
30
|
+
depth: number;
|
|
31
|
+
step: Step;
|
|
32
|
+
/** Position in the flow's overall step order, 1-based, frames included. */
|
|
33
|
+
number: number;
|
|
34
|
+
/** True when the cross-context view would leave this step out. */
|
|
35
|
+
hidden: boolean;
|
|
36
|
+
/** Reserved for consumers that render an unfocused path as context. */
|
|
37
|
+
offPath: boolean;
|
|
38
|
+
/** True when the status filter is on and this step is not the status asked for. */
|
|
39
|
+
offStatus: boolean;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export type OutlineRow = OutlineFrame | OutlineStep;
|
|
43
|
+
|
|
44
|
+
export interface OutlineOptions {
|
|
45
|
+
/** Steps the cross-context view drops. */
|
|
46
|
+
hidden: ReadonlySet<string>;
|
|
47
|
+
/** When true, those steps are left out and empty frames go with them. */
|
|
48
|
+
crossOnly: boolean;
|
|
49
|
+
/**
|
|
50
|
+
* When set, only steps on this executable path remain. The unfiltered state
|
|
51
|
+
* already shows the whole decision tree; choosing a path is an explicit
|
|
52
|
+
* request to focus on one run through it, not merely recolour the tree.
|
|
53
|
+
*/
|
|
54
|
+
path?: ReadonlySet<string> | null;
|
|
55
|
+
/**
|
|
56
|
+
* When set, steps of other statuses are marked `offStatus`. Like `path`,
|
|
57
|
+
* the step stays and recedes: the filter answers "which hops have been seen
|
|
58
|
+
* running", and the answer only means something against the ones that have
|
|
59
|
+
* not.
|
|
60
|
+
*/
|
|
61
|
+
statuses?: ReadonlySet<Status> | null;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Rows for one flow, in walk order. Numbering runs over every step whether it
|
|
66
|
+
* is shown or not, so a filtered rail still says step 7 is the seventh step.
|
|
67
|
+
*/
|
|
68
|
+
export function buildOutline(
|
|
69
|
+
flow: Flow,
|
|
70
|
+
options: OutlineOptions,
|
|
71
|
+
): OutlineRow[] {
|
|
72
|
+
let counter = 0;
|
|
73
|
+
|
|
74
|
+
const emit = (nodes: FlowNode[], depth: number): OutlineRow[] => {
|
|
75
|
+
const rows: OutlineRow[] = [];
|
|
76
|
+
|
|
77
|
+
for (const node of nodes) {
|
|
78
|
+
switch (node.type) {
|
|
79
|
+
case "step": {
|
|
80
|
+
counter += 1;
|
|
81
|
+
const hidden = options.hidden.has(node.id);
|
|
82
|
+
if (options.crossOnly && hidden) break;
|
|
83
|
+
if (options.path && !options.path.has(node.id)) break;
|
|
84
|
+
rows.push({
|
|
85
|
+
type: "step",
|
|
86
|
+
key: node.id,
|
|
87
|
+
depth,
|
|
88
|
+
step: node,
|
|
89
|
+
number: counter,
|
|
90
|
+
hidden,
|
|
91
|
+
offPath: false,
|
|
92
|
+
offStatus: options.statuses
|
|
93
|
+
? !options.statuses.has(node.status)
|
|
94
|
+
: false,
|
|
95
|
+
});
|
|
96
|
+
break;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
case "parallel": {
|
|
100
|
+
// Branch bodies are built first: a frame whose every branch was
|
|
101
|
+
// filtered away has nothing left to head, and heading nothing reads
|
|
102
|
+
// as a frame that exists but is empty.
|
|
103
|
+
const bodies = node.branches.map((branch) => emit(branch, depth + 1));
|
|
104
|
+
let opened = false;
|
|
105
|
+
bodies.forEach((body, i) => {
|
|
106
|
+
if (body.length === 0) return;
|
|
107
|
+
rows.push({
|
|
108
|
+
type: "frame",
|
|
109
|
+
key: `${node.id}:${i}`,
|
|
110
|
+
depth,
|
|
111
|
+
keyword: opened ? "and" : "par",
|
|
112
|
+
title: opened ? undefined : node.title,
|
|
113
|
+
offPath: false,
|
|
114
|
+
});
|
|
115
|
+
opened = true;
|
|
116
|
+
rows.push(...body);
|
|
117
|
+
});
|
|
118
|
+
break;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
case "alt": {
|
|
122
|
+
const bodies = node.branches.map((branch) =>
|
|
123
|
+
emit(branch.steps, depth + 1),
|
|
124
|
+
);
|
|
125
|
+
let opened = false;
|
|
126
|
+
bodies.forEach((body, i) => {
|
|
127
|
+
const branch = node.branches[i];
|
|
128
|
+
if (body.length === 0 || !branch) return;
|
|
129
|
+
rows.push({
|
|
130
|
+
type: "frame",
|
|
131
|
+
key: `${node.id}:${i}`,
|
|
132
|
+
depth,
|
|
133
|
+
keyword: opened ? "else" : "alt",
|
|
134
|
+
title: branch.title,
|
|
135
|
+
terminal: branch.terminal,
|
|
136
|
+
offPath: false,
|
|
137
|
+
});
|
|
138
|
+
opened = true;
|
|
139
|
+
rows.push(...body);
|
|
140
|
+
});
|
|
141
|
+
break;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
case "loop": {
|
|
145
|
+
const body = emit(node.steps, depth + 1);
|
|
146
|
+
if (body.length === 0) break;
|
|
147
|
+
rows.push({
|
|
148
|
+
type: "frame",
|
|
149
|
+
key: node.id,
|
|
150
|
+
depth,
|
|
151
|
+
keyword: "loop",
|
|
152
|
+
title: node.title,
|
|
153
|
+
offPath: false,
|
|
154
|
+
});
|
|
155
|
+
rows.push(...body);
|
|
156
|
+
break;
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
return rows;
|
|
162
|
+
};
|
|
163
|
+
|
|
164
|
+
return emit(flow.steps, 0);
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
/** Just the step rows, for anything that walks the rail rather than draws it. */
|
|
168
|
+
export function outlineSteps(rows: readonly OutlineRow[]): OutlineStep[] {
|
|
169
|
+
return rows.filter((row): row is OutlineStep => row.type === "step");
|
|
170
|
+
}
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
import { describe, expect, it } from "vitest";
|
|
2
|
+
import { rawCatalog as raw } from "../test-catalog";
|
|
3
|
+
import type { Alt, Catalog, Flow, FlowNode } from "../catalog";
|
|
4
|
+
import { stepFrames, walkSteps } from "../catalog";
|
|
5
|
+
import { flowPaths } from "./paths";
|
|
6
|
+
|
|
7
|
+
const catalog = raw as unknown as Catalog;
|
|
8
|
+
const checkout = catalog.flows.find((f) => f.slug === "checkout") as Flow;
|
|
9
|
+
|
|
10
|
+
const ids = (flow: Flow): string[] => walkSteps(flow.steps).map((s) => s.id);
|
|
11
|
+
|
|
12
|
+
/** Every Alt in a flow, at any depth. */
|
|
13
|
+
function alts(nodes: FlowNode[], out: Alt[] = []): Alt[] {
|
|
14
|
+
for (const node of nodes) {
|
|
15
|
+
switch (node.type) {
|
|
16
|
+
case "step":
|
|
17
|
+
break;
|
|
18
|
+
case "alt":
|
|
19
|
+
out.push(node);
|
|
20
|
+
for (const b of node.branches) alts(b.steps, out);
|
|
21
|
+
break;
|
|
22
|
+
case "parallel":
|
|
23
|
+
for (const b of node.branches) alts(b, out);
|
|
24
|
+
break;
|
|
25
|
+
case "loop":
|
|
26
|
+
alts(node.steps, out);
|
|
27
|
+
break;
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
return out;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
describe("flowPaths", () => {
|
|
34
|
+
it("gives a flow with no alt exactly one path, with no choices", () => {
|
|
35
|
+
const flat = catalog.flows.filter((f) => alts(f.steps).length === 0);
|
|
36
|
+
expect(flat.length).toBeGreaterThan(0);
|
|
37
|
+
for (const flow of flat) {
|
|
38
|
+
const { paths, truncated } = flowPaths(flow);
|
|
39
|
+
expect(truncated, flow.slug).toBe(false);
|
|
40
|
+
expect(paths, flow.slug).toHaveLength(1);
|
|
41
|
+
expect(paths[0]?.choices, flow.slug).toEqual([]);
|
|
42
|
+
expect(paths[0]?.terminal, flow.slug).toBe(false);
|
|
43
|
+
expect([...(paths[0]?.stepIds ?? [])], flow.slug).toEqual(ids(flow));
|
|
44
|
+
}
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
it("forks checkout once per branch of every alt it reaches", () => {
|
|
48
|
+
const { paths, truncated } = flowPaths(checkout);
|
|
49
|
+
expect(truncated).toBe(false);
|
|
50
|
+
// Five alts down the sequence — quote, risk, currency, authorization,
|
|
51
|
+
// capture — three of which carry a branch that ends the flow. A terminal
|
|
52
|
+
// branch takes no further choices, which is what keeps this from being 72.
|
|
53
|
+
expect(alts(checkout.steps).map((a) => a.branches.length)).toEqual([
|
|
54
|
+
2, 3, 3, 2, 2,
|
|
55
|
+
]);
|
|
56
|
+
expect(paths).toHaveLength(30);
|
|
57
|
+
expect(new Set(paths.map((p) => p.id)).size).toBe(paths.length);
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
it("never puts two branches of the same alt on one path", () => {
|
|
61
|
+
for (const flow of catalog.flows) {
|
|
62
|
+
const frames = stepFrames(flow.steps);
|
|
63
|
+
for (const path of flowPaths(flow).paths) {
|
|
64
|
+
const chosen = new Map<string, string>();
|
|
65
|
+
for (const id of path.stepIds) {
|
|
66
|
+
for (const frame of frames.get(id) ?? []) {
|
|
67
|
+
if (frame.kind !== "alt") continue;
|
|
68
|
+
const seen = chosen.get(frame.id);
|
|
69
|
+
if (seen === undefined) chosen.set(frame.id, frame.branch ?? "");
|
|
70
|
+
else expect(seen, `${flow.slug} ${path.id}`).toBe(frame.branch);
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
it("stops a terminal path at the branch that ends it", () => {
|
|
78
|
+
for (const flow of catalog.flows) {
|
|
79
|
+
const order = ids(flow);
|
|
80
|
+
const frames = stepFrames(flow.steps);
|
|
81
|
+
for (const path of flowPaths(flow).paths) {
|
|
82
|
+
const ending = path.choices.find((c) => c.terminal);
|
|
83
|
+
expect(path.terminal, `${flow.slug} ${path.id}`).toBe(
|
|
84
|
+
ending !== undefined,
|
|
85
|
+
);
|
|
86
|
+
if (!ending) continue;
|
|
87
|
+
// Nothing outside the branch that ended it may survive past it.
|
|
88
|
+
const last = order.filter((id) => path.stepIds.has(id)).at(-1);
|
|
89
|
+
expect(
|
|
90
|
+
(frames.get(last ?? "") ?? []).some(
|
|
91
|
+
(f) => f.kind === "alt" && f.id === ending.altId,
|
|
92
|
+
),
|
|
93
|
+
`${flow.slug} ${path.id}`,
|
|
94
|
+
).toBe(true);
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
it("covers every step of the flow across all of its paths", () => {
|
|
100
|
+
for (const flow of catalog.flows) {
|
|
101
|
+
const union = new Set<string>();
|
|
102
|
+
for (const path of flowPaths(flow).paths) {
|
|
103
|
+
for (const id of path.stepIds) union.add(id);
|
|
104
|
+
}
|
|
105
|
+
expect([...union].sort(), flow.slug).toEqual([...ids(flow)].sort());
|
|
106
|
+
}
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
it("keeps every path a subsequence of the flow's own step order", () => {
|
|
110
|
+
for (const flow of catalog.flows) {
|
|
111
|
+
const order = ids(flow);
|
|
112
|
+
for (const path of flowPaths(flow).paths) {
|
|
113
|
+
const onPath = order.filter((id) => path.stepIds.has(id));
|
|
114
|
+
expect([...path.stepIds], `${flow.slug} ${path.id}`).toEqual(onPath);
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
it("names a path by the conditions it took, in order", () => {
|
|
120
|
+
for (const path of flowPaths(checkout).paths) {
|
|
121
|
+
expect(path.label).toBe(path.choices.map((c) => c.title).join(" · "));
|
|
122
|
+
expect(path.choices.map((c) => c.altId)).toEqual(
|
|
123
|
+
alts(checkout.steps)
|
|
124
|
+
.map((a) => a.id)
|
|
125
|
+
.filter((id) => path.choices.some((c) => c.altId === id)),
|
|
126
|
+
);
|
|
127
|
+
}
|
|
128
|
+
});
|
|
129
|
+
});
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
// A path through a flow: one way the sequence can actually run.
|
|
2
|
+
//
|
|
3
|
+
// The step tree unions every alt branch, which is right for asking "what does
|
|
4
|
+
// this flow touch" and wrong for asking "what happens". Those are different
|
|
5
|
+
// questions and this module answers the second one.
|
|
6
|
+
//
|
|
7
|
+
// Only `alt` forks. A `parallel` runs all of its branches and a `loop` runs its
|
|
8
|
+
// body, so neither multiplies the count; and a `terminal` alt branch ENDS the
|
|
9
|
+
// path, so nothing written after that alt — at any enclosing level — belongs to
|
|
10
|
+
// it. That last rule is the whole reason `AltBranch.terminal` exists.
|
|
11
|
+
|
|
12
|
+
import type { Alt, Flow, FlowNode } from "../catalog";
|
|
13
|
+
|
|
14
|
+
export interface PathChoice {
|
|
15
|
+
/** Id of the Alt this choice was made at. */
|
|
16
|
+
altId: string;
|
|
17
|
+
branchIndex: number;
|
|
18
|
+
/** The branch condition, verbatim. */
|
|
19
|
+
title: string;
|
|
20
|
+
/** This branch ends the flow rather than rejoining it. */
|
|
21
|
+
terminal?: boolean;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export interface FlowPath {
|
|
25
|
+
/** Stable across renders: the choices, in order, as "<altId>:<index>". */
|
|
26
|
+
id: string;
|
|
27
|
+
/** The conditions along the way, for a menu or a heading. */
|
|
28
|
+
label: string;
|
|
29
|
+
choices: PathChoice[];
|
|
30
|
+
/** Steps on this path, in walk order. */
|
|
31
|
+
stepIds: Set<string>;
|
|
32
|
+
/** The path stops at a terminal branch instead of running to the end. */
|
|
33
|
+
terminal: boolean;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export interface FlowPaths {
|
|
37
|
+
paths: FlowPath[];
|
|
38
|
+
/**
|
|
39
|
+
* Set when enumeration hit the ceiling. The paths listed are still real; there
|
|
40
|
+
* are simply more of them. Said out loud rather than truncated in silence,
|
|
41
|
+
* which would read as "these are all of them".
|
|
42
|
+
*/
|
|
43
|
+
truncated: boolean;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/** Enough for any flow a person is going to read; a guard, not a design limit. */
|
|
47
|
+
const MAX_PATHS = 64;
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Every way this flow can run. A flow with no alt yields exactly one path with
|
|
51
|
+
* no choices, so callers never need a special case for the flat shape.
|
|
52
|
+
*/
|
|
53
|
+
export function flowPaths(flow: Flow): FlowPaths {
|
|
54
|
+
const out: FlowPath[] = [];
|
|
55
|
+
let truncated = false;
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Walks `nodes`, carrying the choices and steps gathered so far, and calls
|
|
59
|
+
* `emit` once for every way the walk can finish. Whatever follows a frame is
|
|
60
|
+
* spliced onto the front of the recursion rather than visited afterwards, so
|
|
61
|
+
* a terminal branch simply never receives its siblings.
|
|
62
|
+
*/
|
|
63
|
+
const walk = (
|
|
64
|
+
nodes: FlowNode[],
|
|
65
|
+
choices: PathChoice[],
|
|
66
|
+
steps: string[],
|
|
67
|
+
emit: (choices: PathChoice[], steps: string[], terminal: boolean) => void,
|
|
68
|
+
): void => {
|
|
69
|
+
for (let i = 0; i < nodes.length; i += 1) {
|
|
70
|
+
const node = nodes[i];
|
|
71
|
+
if (!node) continue;
|
|
72
|
+
|
|
73
|
+
if (node.type === "step") {
|
|
74
|
+
steps.push(node.id);
|
|
75
|
+
continue;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
if (node.type === "loop") {
|
|
79
|
+
// The body always runs, so it neither forks nor is optional here.
|
|
80
|
+
const rest = nodes.slice(i + 1);
|
|
81
|
+
walk([...node.steps, ...rest], choices, steps, emit);
|
|
82
|
+
return;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
if (node.type === "parallel") {
|
|
86
|
+
const rest = nodes.slice(i + 1);
|
|
87
|
+
walk([...node.branches.flat(), ...rest], choices, steps, emit);
|
|
88
|
+
return;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
// An alt is the only fork. Each branch continues into whatever follows
|
|
92
|
+
// the alt — unless it is terminal, in which case nothing follows it.
|
|
93
|
+
const alt: Alt = node;
|
|
94
|
+
const rest = nodes.slice(i + 1);
|
|
95
|
+
alt.branches.forEach((branch, branchIndex) => {
|
|
96
|
+
const choice: PathChoice = {
|
|
97
|
+
altId: alt.id,
|
|
98
|
+
branchIndex,
|
|
99
|
+
title: branch.title,
|
|
100
|
+
...(branch.terminal ? { terminal: true } : {}),
|
|
101
|
+
};
|
|
102
|
+
const nextChoices = [...choices, choice];
|
|
103
|
+
const nextSteps = [...steps];
|
|
104
|
+
if (branch.terminal) {
|
|
105
|
+
walk(branch.steps, nextChoices, nextSteps, (c, s) =>
|
|
106
|
+
emit(c, s, true),
|
|
107
|
+
);
|
|
108
|
+
} else {
|
|
109
|
+
walk([...branch.steps, ...rest], nextChoices, nextSteps, emit);
|
|
110
|
+
}
|
|
111
|
+
});
|
|
112
|
+
return;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
emit(choices, steps, false);
|
|
116
|
+
};
|
|
117
|
+
|
|
118
|
+
walk(flow.steps, [], [], (choices, steps, terminal) => {
|
|
119
|
+
if (out.length >= MAX_PATHS) {
|
|
120
|
+
truncated = true;
|
|
121
|
+
return;
|
|
122
|
+
}
|
|
123
|
+
out.push({
|
|
124
|
+
id: choices.map((c) => `${c.altId}:${c.branchIndex}`).join("|"),
|
|
125
|
+
label: pathLabel(choices),
|
|
126
|
+
choices,
|
|
127
|
+
stepIds: new Set(steps),
|
|
128
|
+
terminal,
|
|
129
|
+
});
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
return { paths: out, truncated };
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
/** The conditions along a path, or a name for the one path a flat flow has. */
|
|
136
|
+
function pathLabel(choices: readonly PathChoice[]): string {
|
|
137
|
+
if (choices.length === 0) return "the only path";
|
|
138
|
+
return choices.map((c) => c.title).join(" · ");
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
/** The path with this id, if the flow still has one. */
|
|
142
|
+
export function findPath(
|
|
143
|
+
paths: readonly FlowPath[],
|
|
144
|
+
id: string,
|
|
145
|
+
): FlowPath | null {
|
|
146
|
+
return paths.find((p) => p.id === id) ?? null;
|
|
147
|
+
}
|