@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,73 @@
|
|
|
1
|
+
// A service whose way in is a graph.
|
|
2
|
+
//
|
|
3
|
+
// The fixture beside this has no domain, no store and no use case: a schema,
|
|
4
|
+
// resolvers under it, ports of their own, and assembly. What is worth pinning
|
|
5
|
+
// is that the reader still finds the way through - the field a client asks
|
|
6
|
+
// for, the port the resolver holds, the adapter assembly bound to it, and the
|
|
7
|
+
// peer at the end.
|
|
8
|
+
import { describe, expect, it } from "vitest";
|
|
9
|
+
import type { Flow, Step } from "../../src/catalog.ts";
|
|
10
|
+
import { extract } from "./extract.ts";
|
|
11
|
+
|
|
12
|
+
const ROOT = "plugins/extract-ts/testdata/storefront";
|
|
13
|
+
const options = { context: "storefront", service: "bff", graphql: "src/schema", peers: { "auth.v1": "auth.auth" } };
|
|
14
|
+
|
|
15
|
+
function run() {
|
|
16
|
+
return extract({ root: ROOT, commit: "abc1234", generatedAt: "2026-09-05T00:00:00Z" }, options);
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
function fragment() {
|
|
20
|
+
return JSON.parse(run().files[0]!.contents) as {
|
|
21
|
+
contexts: { services: { consumes: { id: string; peer: string }[]; aggregates: unknown[] }[] }[];
|
|
22
|
+
flows: Flow[];
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function flow(slug: string): Flow {
|
|
27
|
+
const found = fragment().flows.find((f) => f.slug === slug);
|
|
28
|
+
if (!found) throw new Error(`no flow ${slug} in ${fragment().flows.map((f) => f.slug).join(", ")}`);
|
|
29
|
+
|
|
30
|
+
return found;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
describe("a service read through its resolvers", () => {
|
|
34
|
+
// The field is what the client asks for, and it is named the way
|
|
35
|
+
// extract-graphql names the method it read out of the schema - `Query.viewer`
|
|
36
|
+
// - so the two meet in the merge.
|
|
37
|
+
it("opens a flow on the field", () => {
|
|
38
|
+
const [first] = flow("bff-query-viewer").steps as Step[];
|
|
39
|
+
|
|
40
|
+
expect(first).toMatchObject({ from: "client", to: "storefront.bff", kind: "rpc", label: "Query.viewer" });
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
// `ctx.sessions.current(...)` is to a resolver what `this.sessions.validate(...)`
|
|
44
|
+
// is to a use case: the port is held, and what fills it is assembly's word.
|
|
45
|
+
it("follows the port on the context to the peer behind it", () => {
|
|
46
|
+
const [, second] = flow("bff-query-viewer").steps as Step[];
|
|
47
|
+
|
|
48
|
+
expect(second).toMatchObject({ from: "storefront.bff", to: "auth.auth", ref: "auth.v1.Sessions/validateSession" });
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
it("names the call among what the service consumes", () => {
|
|
52
|
+
const { consumes } = fragment().contexts[0]!.services[0]!;
|
|
53
|
+
|
|
54
|
+
expect(consumes).toEqual([{ id: "auth.v1.Sessions/validateSession", peer: "auth.auth", status: "declared", source: `${ROOT}/src/infrastructure/auth/gen/openapi.yaml` }]);
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
// A resolver that answers out of its arguments reaches nothing, and the flow
|
|
58
|
+
// says so by having nothing after the ask.
|
|
59
|
+
it("draws one step for a field that reaches nothing", () => {
|
|
60
|
+
expect(flow("bff-mutation-add-item").steps).toHaveLength(1);
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
it("takes the summary from the resolver's doc comment", () => {
|
|
64
|
+
expect(flow("bff-query-viewer").summary).toBe("Who the request belongs to.");
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
// A BFF has no model, and saying so every run would be a warning nobody can
|
|
68
|
+
// act on. The option that names the schema is what says to expect none.
|
|
69
|
+
it("does not report the missing domain", () => {
|
|
70
|
+
expect(fragment().contexts[0]!.services[0]!.aggregates).toEqual([]);
|
|
71
|
+
expect(run().warnings).toEqual([]);
|
|
72
|
+
});
|
|
73
|
+
});
|
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
// Resolvers, read off src/schema.
|
|
2
|
+
//
|
|
3
|
+
// The third way into a service, beside HTTP and gRPC. Over HTTP the document's
|
|
4
|
+
// operationIds name the handlers and over gRPC the vendored contract does;
|
|
5
|
+
// over GraphQL the layout does, because the generator that scaffolds a
|
|
6
|
+
// resolver puts it at `schema/<module>/resolvers/<Root>/<field>.ts`, and that
|
|
7
|
+
// path is the field's name in the schema. `Query.basket` is what
|
|
8
|
+
// `extract-graphql` calls the method it read out of the SDL, so the two meet
|
|
9
|
+
// without this reader parsing a schema of its own.
|
|
10
|
+
//
|
|
11
|
+
// What a resolver holds is not a constructor's parameters - it has none - but
|
|
12
|
+
// the context it is handed. Its type is never written in the resolver: the
|
|
13
|
+
// signature comes from the generated resolver type, so the parameter is typed
|
|
14
|
+
// by where it sits and not by anything a reader of that file can see. The
|
|
15
|
+
// context is one interface for the whole service, and this reads it by the
|
|
16
|
+
// name codegen's own `contextType` conventionally points at - `GraphQLContext`
|
|
17
|
+
// - which is the same fact a use case states in its constructor, written in
|
|
18
|
+
// the one place GraphQL leaves for it.
|
|
19
|
+
|
|
20
|
+
import { existsSync, readdirSync, statSync } from "node:fs";
|
|
21
|
+
import { basename, join } from "node:path";
|
|
22
|
+
import { readSource, at, bareType, jsdoc, type Source } from "./source.ts";
|
|
23
|
+
import { isArrow, isBlock, isExportNamed, isIdent, isPropertySig, isSourceFile, isVarDecl, keyName, paramIdent, typeText, type BlockStatement, type Node } from "./ast.ts";
|
|
24
|
+
import type { WarningSink } from "./domain.ts";
|
|
25
|
+
|
|
26
|
+
/** The three types a client may open a request on. */
|
|
27
|
+
const ROOTS = ["Query", "Mutation", "Subscription"];
|
|
28
|
+
|
|
29
|
+
export interface Resolver {
|
|
30
|
+
/** `Query.basket`: what extract-graphql calls the method this answers. */
|
|
31
|
+
id: string;
|
|
32
|
+
/** The source of the field's body, and where it is. */
|
|
33
|
+
src: Source;
|
|
34
|
+
body: BlockStatement;
|
|
35
|
+
line: string;
|
|
36
|
+
/** The parameter the ports arrive on, and what each of them is declared as. */
|
|
37
|
+
self: string;
|
|
38
|
+
ports: Map<string, string>;
|
|
39
|
+
/** The doc comment above it, which is the flow's summary. */
|
|
40
|
+
doc: string;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Every resolver under a schema directory, in the order a reader meets them:
|
|
45
|
+
* ask, change, keep listening, and alphabetically within each.
|
|
46
|
+
*
|
|
47
|
+
* A directory that is not one of the three root types is skipped rather than
|
|
48
|
+
* reported: a resolver for a field of a returned type is a legitimate thing to
|
|
49
|
+
* write, and it is not a way into the service.
|
|
50
|
+
*/
|
|
51
|
+
export function readResolvers(schemaDir: string, srcDir: string, rel: (abs: string) => string, b: WarningSink): Resolver[] {
|
|
52
|
+
if (!existsSync(schemaDir)) return [];
|
|
53
|
+
|
|
54
|
+
const ports = readContext(srcDir, schemaDir);
|
|
55
|
+
if (ports.size === 0) {
|
|
56
|
+
b.warn("", `no exported \`${CONTEXT}\` interface under ${rel(srcDir)}; the resolvers are read as reaching nothing`);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
const out: Resolver[] = [];
|
|
60
|
+
for (const root of ROOTS) {
|
|
61
|
+
const found: Resolver[] = [];
|
|
62
|
+
for (const module of readdirSync(schemaDir).sort()) {
|
|
63
|
+
const dir = join(schemaDir, module, "resolvers", root);
|
|
64
|
+
if (!existsSync(dir) || !statSync(dir).isDirectory()) continue;
|
|
65
|
+
|
|
66
|
+
for (const name of readdirSync(dir).sort()) {
|
|
67
|
+
if (!isSourceFile(name)) continue;
|
|
68
|
+
const resolver = readResolver(join(dir, name), `${root}.${name.replace(/\.[cm]?[jt]sx?$/, "")}`, ports, rel, b);
|
|
69
|
+
if (resolver) found.push(resolver);
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
out.push(...found);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
return out;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
function readResolver(file: string, id: string, ports: Map<string, string>, rel: (abs: string) => string, b: WarningSink): Resolver | undefined {
|
|
79
|
+
const src = readSource(file);
|
|
80
|
+
if (!src) return undefined;
|
|
81
|
+
|
|
82
|
+
const fn = exportedResolver(src, basename(file).replace(/\.[cm]?[jt]sx?$/, ""));
|
|
83
|
+
if (!fn) {
|
|
84
|
+
b.warn(id, `${rel(file)}: no resolver is exported under the field's name; the field is left out of the flows`);
|
|
85
|
+
return undefined;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
// (parent, args, context): the third parameter is the one with the ports on
|
|
89
|
+
// it. A resolver that ignores it - `_ctx` - reaches nothing, and a flow that
|
|
90
|
+
// opens on it has one step, which is the truth about that field.
|
|
91
|
+
const context = fn.params[2];
|
|
92
|
+
const self = context ? paramIdent(context)?.name : undefined;
|
|
93
|
+
const line = at(src, fn.body, rel);
|
|
94
|
+
if (!context || !self || self.startsWith("_")) return { id, src, body: fn.body, line, self: "", ports: new Map(), doc: fn.doc };
|
|
95
|
+
|
|
96
|
+
return { id, src, body: fn.body, line, self, ports, doc: fn.doc };
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* The function the field is answered by.
|
|
101
|
+
*
|
|
102
|
+
* A field is a function; a subscription is an object with a `subscribe` that
|
|
103
|
+
* is one, because a subscription has to be able to say how it stops as well as
|
|
104
|
+
* what it yields. Both are exported under the field's own name.
|
|
105
|
+
*/
|
|
106
|
+
function exportedResolver(src: Source, name: string): { params: Node[]; body: BlockStatement; doc: string } | undefined {
|
|
107
|
+
for (const stmt of src.parsed.program.body) {
|
|
108
|
+
const decl = isExportNamed(stmt) ? stmt.declaration : undefined;
|
|
109
|
+
if (!isVarDecl(decl)) continue;
|
|
110
|
+
for (const d of decl.declarations) {
|
|
111
|
+
if (!isIdent(d.id) || d.id.name !== name || !d.init) continue;
|
|
112
|
+
const fn = functionOf(d.init);
|
|
113
|
+
if (fn) return { ...fn, doc: jsdoc(src, decl, stmt) };
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
return undefined;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
function functionOf(node: Node): { params: Node[]; body: BlockStatement } | undefined {
|
|
121
|
+
if (isArrow(node) && isBlock(node.body)) return { params: node.params, body: node.body };
|
|
122
|
+
// `{ subscribe: async function* (...) { ... } }`
|
|
123
|
+
const properties = (node as { properties?: Node[] }).properties ?? [];
|
|
124
|
+
for (const property of properties) {
|
|
125
|
+
const key = (property as { key?: Node }).key;
|
|
126
|
+
const value = (property as { value?: Node }).value;
|
|
127
|
+
if (!key || keyName(key) !== "subscribe" || !value) continue;
|
|
128
|
+
const body = (value as { body?: Node }).body;
|
|
129
|
+
const params = (value as { params?: Node[] }).params ?? [];
|
|
130
|
+
if (isArrow(value) && isBlock(body)) return { params, body };
|
|
131
|
+
if (isBlock(body)) return { params, body };
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
return undefined;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
/** The name the one context interface goes by; codegen's `contextType` points at it. */
|
|
138
|
+
const CONTEXT = "GraphQLContext";
|
|
139
|
+
|
|
140
|
+
/**
|
|
141
|
+
* The ports a resolver may reach: the members of the context interface.
|
|
142
|
+
*
|
|
143
|
+
* A property is a port - `baskets: Baskets` - and its type is what assembly
|
|
144
|
+
* binds. Anything else on the context is what the transport put there for the
|
|
145
|
+
* resolvers to read, `bearer` and its like, and a call on one of those is not
|
|
146
|
+
* a hop because nothing is at the other end of it.
|
|
147
|
+
*/
|
|
148
|
+
function readContext(srcDir: string, schemaDir: string): Map<string, string> {
|
|
149
|
+
const out = new Map<string, string>();
|
|
150
|
+
const file = findContext(srcDir, schemaDir);
|
|
151
|
+
if (!file) return out;
|
|
152
|
+
|
|
153
|
+
const src = readSource(file);
|
|
154
|
+
const iface = src?.interfaces.get(CONTEXT);
|
|
155
|
+
if (!src || !iface) return out;
|
|
156
|
+
|
|
157
|
+
for (const member of iface.node.body.body) {
|
|
158
|
+
if (!isPropertySig(member)) continue;
|
|
159
|
+
const name = keyName(member.key);
|
|
160
|
+
if (name) out.set(name, bareType(typeText(iface.p, member.typeAnnotation)));
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
return out;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
function findContext(dir: string, schemaDir: string): string | undefined {
|
|
167
|
+
if (!existsSync(dir) || dir === schemaDir) return undefined;
|
|
168
|
+
|
|
169
|
+
for (const name of readdirSync(dir).sort()) {
|
|
170
|
+
const path = join(dir, name);
|
|
171
|
+
if (statSync(path).isDirectory()) {
|
|
172
|
+
const found = findContext(path, schemaDir);
|
|
173
|
+
if (found) return found;
|
|
174
|
+
continue;
|
|
175
|
+
}
|
|
176
|
+
if (!isSourceFile(name)) continue;
|
|
177
|
+
if (readSource(path)?.interfaces.has(CONTEXT)) return path;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
return undefined;
|
|
181
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { describe, expect, it } from "vitest";
|
|
2
|
+
import { camel, slug, title } from "./ids.ts";
|
|
3
|
+
|
|
4
|
+
describe("ids", () => {
|
|
5
|
+
it("slugs the way extract-go does", () => {
|
|
6
|
+
expect(slug("PriceList")).toBe("price-list");
|
|
7
|
+
expect(slug("Address")).toBe("address");
|
|
8
|
+
expect(slug("ID")).toBe("id");
|
|
9
|
+
expect(slug("email.Address")).toBe("email-address");
|
|
10
|
+
expect(slug("HTTPServer")).toBe("http-server");
|
|
11
|
+
expect(slug("orderLine")).toBe("order-line");
|
|
12
|
+
});
|
|
13
|
+
it("camels a directory name into an operation id", () => {
|
|
14
|
+
expect(camel("change_password")).toBe("ChangePassword");
|
|
15
|
+
expect(camel("add-item")).toBe("AddItem");
|
|
16
|
+
});
|
|
17
|
+
it("titles a directory name for a page", () => {
|
|
18
|
+
expect(title("price_list")).toBe("Price List");
|
|
19
|
+
expect(title("basket")).toBe("Basket");
|
|
20
|
+
});
|
|
21
|
+
});
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
// How a name in the source becomes an id in the catalog. These are the rules
|
|
2
|
+
// extract-go lives by, spelled the same, so a TypeScript service and a Go
|
|
3
|
+
// service with the same aggregate get the same id.
|
|
4
|
+
|
|
5
|
+
/** PriceList → price-list, Address → address, ID → id, email.Address → email-address. */
|
|
6
|
+
export function slug(name: string): string {
|
|
7
|
+
let out = "";
|
|
8
|
+
const chars = [...name];
|
|
9
|
+
chars.forEach((c, i) => {
|
|
10
|
+
const upper = c >= "A" && c <= "Z";
|
|
11
|
+
if (upper && i > 0) {
|
|
12
|
+
const prev = chars[i - 1]!;
|
|
13
|
+
const next = chars[i + 1];
|
|
14
|
+
const prevLower = prev >= "a" && prev <= "z";
|
|
15
|
+
const nextLower = next !== undefined && next >= "a" && next <= "z";
|
|
16
|
+
if (prevLower || nextLower) out += "-";
|
|
17
|
+
}
|
|
18
|
+
let r = upper ? c.toLowerCase() : c;
|
|
19
|
+
if (r === "_" || r === ".") r = "-";
|
|
20
|
+
out += r;
|
|
21
|
+
});
|
|
22
|
+
while (out.includes("--")) out = out.replaceAll("--", "-");
|
|
23
|
+
return out.replace(/^-+|-+$/g, "");
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/** change_password → ChangePassword: the operation id a directory name becomes. */
|
|
27
|
+
export function camel(name: string): string {
|
|
28
|
+
return name
|
|
29
|
+
.split(/[_-]+/)
|
|
30
|
+
.filter(Boolean)
|
|
31
|
+
.map((w) => w[0]!.toUpperCase() + w.slice(1))
|
|
32
|
+
.join("");
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/** price_list → Price List: the human name for a directory. */
|
|
36
|
+
export function title(name: string): string {
|
|
37
|
+
return name
|
|
38
|
+
.split(/[_-]+/)
|
|
39
|
+
.filter(Boolean)
|
|
40
|
+
.map((w) => w[0]!.toUpperCase() + w.slice(1))
|
|
41
|
+
.join(" ");
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/** A directory name in PascalCase, which is what its root class is called. */
|
|
45
|
+
export function pascal(name: string): string {
|
|
46
|
+
return camel(name);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export const serviceID = (context: string, service: string) => `${context}.${service}`;
|
|
50
|
+
export const aggregateID = (service: string, aggregate: string) => `${service}.${aggregate}`;
|
|
51
|
+
export const blockID = (aggregate: string, block: string) => `${aggregate}.${block}`;
|
|
52
|
+
export const eventID = (aggregate: string, name: string) => `${aggregate}.${name}`;
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
// The root's lifecycle, read off two things the code already has: a table
|
|
2
|
+
// that says where a status can go, and the one method that changes it.
|
|
3
|
+
//
|
|
4
|
+
// export const TRANSITIONS = { open: ["checked-out", "abandoned"], "checked-out": [], abandoned: [] };
|
|
5
|
+
// private moveTo(next: BasketStatus, now: Date) { … this.status = next; … }
|
|
6
|
+
// checkout(…): BasketCheckedOut { this.moveTo("checked-out", now); … }
|
|
7
|
+
//
|
|
8
|
+
// The table gives the states, in its own order, and the edges. The method
|
|
9
|
+
// that assigns `this.status` is the mover; every public method that calls it
|
|
10
|
+
// with a literal makes the edges into that state, and hands back whatever
|
|
11
|
+
// event its return type names. Nothing is inferred beyond that: a status
|
|
12
|
+
// assigned anywhere else is reported, and an edge in the table no method
|
|
13
|
+
// makes is reported, because the table is a claim and the claim should be
|
|
14
|
+
// kept.
|
|
15
|
+
import { existsSync, readdirSync } from "node:fs";
|
|
16
|
+
import { join } from "node:path";
|
|
17
|
+
import type { Lifecycle, Transition } from "../../src/catalog.ts";
|
|
18
|
+
import { readSource, at, bareType, type ClassInfo } from "./source.ts";
|
|
19
|
+
import { isArray, isAssign, isCall, isExportNamed, isIdent, isMember, isObject, isProp, isSourceFile, isString, isVarDecl, keyName, thisMember, unwrap, walk, type Node, type ObjectExpression } from "./ast.ts";
|
|
20
|
+
import type { WarningSink } from "./domain.ts";
|
|
21
|
+
|
|
22
|
+
/** The exported constant the table is looked for under. */
|
|
23
|
+
export const TABLE = "TRANSITIONS";
|
|
24
|
+
|
|
25
|
+
export function readLifecycle(dir: string, root: ClassInfo, rootFile: string, events: Map<string, string>, id: string, rel: (abs: string) => string, b: WarningSink): Lifecycle | undefined {
|
|
26
|
+
const table = readTable(dir);
|
|
27
|
+
if (!table) return undefined;
|
|
28
|
+
const states = [...table.keys()];
|
|
29
|
+
|
|
30
|
+
const rootSrc = readSource(rootFile);
|
|
31
|
+
if (!rootSrc) return undefined;
|
|
32
|
+
const mover = moverOf(root);
|
|
33
|
+
if (!mover) {
|
|
34
|
+
b.warn(id, `${rel(rootFile)}: ${root.name} has a ${TABLE} table but no method assigns this.status, so nothing is read as moving along it`);
|
|
35
|
+
return { states, transitions: [] };
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
const transitions: Transition[] = [];
|
|
39
|
+
const made = new Set<string>();
|
|
40
|
+
for (const [name, m] of root.methods) {
|
|
41
|
+
if (name === mover || m.isStatic || !m.body) continue;
|
|
42
|
+
const returned = bareType(m.returns);
|
|
43
|
+
const emits = events.get(returned);
|
|
44
|
+
walk(m.body, (node) => {
|
|
45
|
+
// this.moveTo("checked-out", …): the edges into that state, made here.
|
|
46
|
+
const to = moveCall(node, mover);
|
|
47
|
+
if (to !== undefined) {
|
|
48
|
+
if (!table.has(to)) {
|
|
49
|
+
b.warn(id, `${at(rootSrc, node, rel)}: ${name} moves to "${to}", which is not a state in ${TABLE}`);
|
|
50
|
+
return;
|
|
51
|
+
}
|
|
52
|
+
for (const [from, targets] of table) {
|
|
53
|
+
if (!targets.includes(to)) continue;
|
|
54
|
+
made.add(`${from}→${to}`);
|
|
55
|
+
const t: Transition = { from, to, on: name, source: at(rootSrc, node, rel) };
|
|
56
|
+
if (emits) t.emits = emits;
|
|
57
|
+
transitions.push(t);
|
|
58
|
+
}
|
|
59
|
+
return;
|
|
60
|
+
}
|
|
61
|
+
// this.status = …, anywhere but the mover: a move the table cannot see.
|
|
62
|
+
if (assignsStatus(node)) {
|
|
63
|
+
b.warn(id, `${at(rootSrc, node, rel)}: ${name} assigns this.status directly; a move outside ${mover} is not in the lifecycle`);
|
|
64
|
+
}
|
|
65
|
+
});
|
|
66
|
+
}
|
|
67
|
+
for (const [from, targets] of table) {
|
|
68
|
+
for (const to of targets) {
|
|
69
|
+
if (!made.has(`${from}→${to}`)) b.warn(id, `${TABLE} allows ${from} → ${to}, and no method of ${root.name} makes that move`);
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
// In table order, so the page reads the way the table does.
|
|
73
|
+
const order = new Map(states.map((s, i) => [s, i]));
|
|
74
|
+
transitions.sort((x, y) => (order.get(x.from)! - order.get(y.from)!) || (order.get(x.to)! - order.get(y.to)!));
|
|
75
|
+
|
|
76
|
+
return { states, transitions };
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/** `export const TRANSITIONS = { a: ["b"], b: [] }` in any file of the directory. */
|
|
80
|
+
function readTable(dir: string): Map<string, string[]> | undefined {
|
|
81
|
+
if (!existsSync(dir)) return undefined;
|
|
82
|
+
for (const name of readdirSync(dir).sort()) {
|
|
83
|
+
if (!isSourceFile(name)) continue;
|
|
84
|
+
const src = readSource(join(dir, name));
|
|
85
|
+
if (!src) continue;
|
|
86
|
+
for (const stmt of src.parsed.program.body) {
|
|
87
|
+
const decl = isExportNamed(stmt) ? stmt.declaration : stmt;
|
|
88
|
+
if (!isVarDecl(decl)) continue;
|
|
89
|
+
for (const d of decl.declarations) {
|
|
90
|
+
if (!isIdent(d.id) || d.id.name !== TABLE || !d.init) continue;
|
|
91
|
+
const init = unwrap(d.init);
|
|
92
|
+
if (isObject(init)) return tableOf(init);
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
return undefined;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
function tableOf(obj: ObjectExpression): Map<string, string[]> | undefined {
|
|
100
|
+
const out = new Map<string, string[]>();
|
|
101
|
+
for (const prop of obj.properties) {
|
|
102
|
+
if (!isProp(prop) || prop.computed) return undefined;
|
|
103
|
+
const key = keyName(prop.key);
|
|
104
|
+
const value = unwrap(prop.value);
|
|
105
|
+
if (key === undefined || !isArray(value)) return undefined;
|
|
106
|
+
const targets: string[] = [];
|
|
107
|
+
for (const el of value.elements) {
|
|
108
|
+
if (!isString(el)) return undefined;
|
|
109
|
+
targets.push(el.value);
|
|
110
|
+
}
|
|
111
|
+
out.set(key, targets);
|
|
112
|
+
}
|
|
113
|
+
return out;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/** The method whose body assigns `this.status`: the one way the status changes. */
|
|
117
|
+
function moverOf(root: ClassInfo): string | undefined {
|
|
118
|
+
for (const [name, m] of root.methods) {
|
|
119
|
+
if (!m.body) continue;
|
|
120
|
+
let assigns = false;
|
|
121
|
+
walk(m.body, (node) => {
|
|
122
|
+
if (assignsStatus(node)) assigns = true;
|
|
123
|
+
});
|
|
124
|
+
if (assigns) return name;
|
|
125
|
+
}
|
|
126
|
+
return undefined;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
function assignsStatus(node: Node): boolean {
|
|
130
|
+
return isAssign(node) && node.operator === "=" && thisMember(node.left) === "status";
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
/** `this.<mover>("state", …)` → "state". */
|
|
134
|
+
function moveCall(node: Node, mover: string): string | undefined {
|
|
135
|
+
if (!isCall(node) || !isMember(node.callee) || thisMember(node.callee) !== mover) return undefined;
|
|
136
|
+
const first = node.arguments[0];
|
|
137
|
+
return isString(first) ? first.value : undefined;
|
|
138
|
+
}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
// portolan-extract-ts: a TypeScript service in, a catalog fragment out. The
|
|
2
|
+
// TypeScript twin of extract-go, on the same protocol: one JSON request on
|
|
3
|
+
// stdin, one JSON response on stdout, and a `describe` that answers with what
|
|
4
|
+
// the plugin is and what it can be told.
|
|
5
|
+
|
|
6
|
+
import { readFileSync } from "node:fs";
|
|
7
|
+
import { extract, type Input, type Options } from "./extract.ts";
|
|
8
|
+
|
|
9
|
+
const schema = JSON.parse(readFileSync(new URL("./options.schema.json", import.meta.url), "utf8")) as unknown;
|
|
10
|
+
|
|
11
|
+
export const descriptor = {
|
|
12
|
+
name: "extract-ts",
|
|
13
|
+
summary: "Reads a TypeScript service by its layout - aggregates, events, use cases, endpoints, policies, clients - into a catalog fragment.",
|
|
14
|
+
phases: ["extract"],
|
|
15
|
+
options: schema,
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
interface Request {
|
|
19
|
+
portolanVersion?: string;
|
|
20
|
+
kind?: string;
|
|
21
|
+
input?: Input;
|
|
22
|
+
options?: Options;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export function serve(raw: string): string {
|
|
26
|
+
const req = JSON.parse(raw) as Request;
|
|
27
|
+
if (req.portolanVersion && req.portolanVersion !== "0.1.0") {
|
|
28
|
+
throw new Error(`unsupported portolan protocol ${JSON.stringify(req.portolanVersion)} (plugin supports 0.1.0)`);
|
|
29
|
+
}
|
|
30
|
+
if (req.kind === "describe") return JSON.stringify({ files: [], describe: descriptor });
|
|
31
|
+
if (!req.input?.root) throw new Error("no input root: an extractor has nothing to read");
|
|
32
|
+
const { files, warnings } = extract(req.input, req.options ?? {});
|
|
33
|
+
for (const warning of warnings) process.stderr.write(`warning: ${warning.ref ? `${warning.ref}: ` : ""}${warning.message}\n`);
|
|
34
|
+
return JSON.stringify({ files }, null, 2);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
if (process.argv[1] && import.meta.url === new URL(`file://${process.argv[1]}`).href) {
|
|
38
|
+
const chunks: Buffer[] = [];
|
|
39
|
+
process.stdin.on("data", (c: Buffer) => chunks.push(c));
|
|
40
|
+
process.stdin.on("end", () => {
|
|
41
|
+
try {
|
|
42
|
+
process.stdout.write(serve(Buffer.concat(chunks).toString("utf8")));
|
|
43
|
+
} catch (err) {
|
|
44
|
+
process.stderr.write(`portolan-extract-ts: ${err instanceof Error ? err.message : String(err)}\n`);
|
|
45
|
+
process.exit(1);
|
|
46
|
+
}
|
|
47
|
+
});
|
|
48
|
+
}
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import { mkdtempSync, writeFileSync } from "node:fs";
|
|
2
|
+
import { tmpdir } from "node:os";
|
|
3
|
+
import { join } from "node:path";
|
|
4
|
+
import { describe, expect, it } from "vitest";
|
|
5
|
+
import { apiID, callID, documentApiID, findOperation, interfaceID, readSpec, tagTitle } from "./openapi.ts";
|
|
6
|
+
|
|
7
|
+
// The same cases plugins/openapi/ids_test.go holds the Go side to.
|
|
8
|
+
describe("openapi ids", () => {
|
|
9
|
+
it("names the api by title and major version", () => {
|
|
10
|
+
expect(apiID("Auth", "1.2.3")).toBe("auth.v1");
|
|
11
|
+
expect(apiID("", "")).toBe("api");
|
|
12
|
+
});
|
|
13
|
+
it("lets a vendored copy say what the estate calls it", () => {
|
|
14
|
+
expect(documentApiID(" stripe.v1 ", "Stripe API", "2026-08-26.dahlia")).toBe("stripe.v1");
|
|
15
|
+
expect(documentApiID("", "Stripe API", "2026-08-26.dahlia")).toBe("stripe-api.v2026-08-26");
|
|
16
|
+
});
|
|
17
|
+
it("titles tags and builds interface ids", () => {
|
|
18
|
+
expect(tagTitle("price_list")).toBe("PriceList");
|
|
19
|
+
expect(interfaceID("auth.v1", "sessions")).toBe("auth.v1.Sessions");
|
|
20
|
+
expect(interfaceID("auth.v1", "")).toBe("auth.v1");
|
|
21
|
+
});
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
const doc = `openapi: 3.0.3
|
|
25
|
+
info:
|
|
26
|
+
title: auth
|
|
27
|
+
version: 1.0.0
|
|
28
|
+
paths:
|
|
29
|
+
/v1/sessions:
|
|
30
|
+
post:
|
|
31
|
+
operationId: login
|
|
32
|
+
tags: [sessions]
|
|
33
|
+
/v1/users/{userId}:
|
|
34
|
+
get:
|
|
35
|
+
operationId: getUser
|
|
36
|
+
tags: [users]
|
|
37
|
+
/v1/health:
|
|
38
|
+
get: {}
|
|
39
|
+
`;
|
|
40
|
+
|
|
41
|
+
describe("readSpec and findOperation", () => {
|
|
42
|
+
const dir = mkdtempSync(join(tmpdir(), "extract-ts-"));
|
|
43
|
+
const path = join(dir, "openapi.yaml");
|
|
44
|
+
writeFileSync(path, doc);
|
|
45
|
+
const spec = readSpec(path);
|
|
46
|
+
|
|
47
|
+
it("reads the api and its operations", () => {
|
|
48
|
+
expect(spec.api).toBe("auth.v1");
|
|
49
|
+
expect(spec.operations).toHaveLength(3);
|
|
50
|
+
});
|
|
51
|
+
it("takes the api id from x-portolan-api when the copy carries one", () => {
|
|
52
|
+
const named = join(dir, "named.yaml");
|
|
53
|
+
writeFileSync(named, doc.replace(" version: 1.0.0\n", " version: 1.0.0\n x-portolan-api: sessions.v9\n"));
|
|
54
|
+
expect(readSpec(named).api).toBe("sessions.v9");
|
|
55
|
+
});
|
|
56
|
+
it("finds a route however the parameter is spelled", () => {
|
|
57
|
+
const login = findOperation(spec, "post", "/v1/sessions")!;
|
|
58
|
+
expect(callID(spec, login)).toBe("auth.v1.Sessions/login");
|
|
59
|
+
expect(callID(spec, findOperation(spec, "GET", "/v1/users/{userId}")!)).toBe("auth.v1.Users/getUser");
|
|
60
|
+
expect(callID(spec, findOperation(spec, "GET", "/v1/users/${id}")!)).toBe("auth.v1.Users/getUser");
|
|
61
|
+
expect(callID(spec, findOperation(spec, "GET", "/v1/health")!)).toBe("auth.v1/GET /v1/health");
|
|
62
|
+
expect(findOperation(spec, "DELETE", "/v1/sessions")).toBeUndefined();
|
|
63
|
+
});
|
|
64
|
+
});
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
// What two extractors agree on about an OpenAPI document: how its interfaces
|
|
2
|
+
// are named in the catalog, and which operation answers on which route. The
|
|
3
|
+
// same rules as plugins/openapi in Go - openapi.test.ts holds this to the
|
|
4
|
+
// same cases - so a call read here and a method read there share one id.
|
|
5
|
+
|
|
6
|
+
import { readFileSync } from "node:fs";
|
|
7
|
+
import { parse } from "yaml";
|
|
8
|
+
|
|
9
|
+
/** `auth` 1.0.0 → `auth.v1`. */
|
|
10
|
+
export function apiID(title: string, version: string): string {
|
|
11
|
+
const name = (title || "api").toLowerCase().replaceAll(" ", "-");
|
|
12
|
+
const major = version.split(".")[0];
|
|
13
|
+
return major ? `${name}.v${major}` : name;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* The id a document says it goes by in the estate, or the one built from its
|
|
18
|
+
* title and version when it says nothing. `x-portolan-api` in `info` is for a
|
|
19
|
+
* copy vendored from outside the estate, whose own title and version would put
|
|
20
|
+
* the third party's words on every arrow where the estate wants its own.
|
|
21
|
+
*/
|
|
22
|
+
export function documentApiID(declared: string, title: string, version: string): string {
|
|
23
|
+
const named = declared.trim();
|
|
24
|
+
return named ? named : apiID(title, version);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/** users → Users, price_list → PriceList. */
|
|
28
|
+
export function tagTitle(name: string): string {
|
|
29
|
+
return name
|
|
30
|
+
.split(/[_\- ]+/)
|
|
31
|
+
.filter(Boolean)
|
|
32
|
+
.map((w) => (w[0]! >= "a" && w[0]! <= "z" ? w[0]!.toUpperCase() + w.slice(1) : w))
|
|
33
|
+
.join("");
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export function interfaceID(api: string, tag: string): string {
|
|
37
|
+
return tag ? `${api}.${tagTitle(tag)}` : api;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export const VERBS = ["get", "put", "post", "delete", "options", "head", "patch", "trace"];
|
|
41
|
+
|
|
42
|
+
export interface Operation {
|
|
43
|
+
/** The operationId, or `VERB /path` when the document has none. */
|
|
44
|
+
id: string;
|
|
45
|
+
tag: string;
|
|
46
|
+
verb: string;
|
|
47
|
+
path: string;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export interface Spec {
|
|
51
|
+
api: string;
|
|
52
|
+
operations: Operation[];
|
|
53
|
+
/** Where it was read from, for a call's source. */
|
|
54
|
+
source: string;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
export function callID(spec: Spec, op: Operation): string {
|
|
58
|
+
return `${interfaceID(spec.api, op.tag)}/${op.id}`;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
export function readSpec(path: string): Spec {
|
|
62
|
+
const doc = parse(readFileSync(path, "utf8")) as Record<string, unknown> | null;
|
|
63
|
+
const info = (doc?.info ?? {}) as Record<string, unknown>;
|
|
64
|
+
const spec: Spec = {
|
|
65
|
+
api: documentApiID(String(info["x-portolan-api"] ?? ""), String(info.title ?? ""), String(info.version ?? "")),
|
|
66
|
+
operations: [],
|
|
67
|
+
source: path,
|
|
68
|
+
};
|
|
69
|
+
const paths = (doc?.paths ?? {}) as Record<string, Record<string, unknown>>;
|
|
70
|
+
for (const [route, item] of Object.entries(paths)) {
|
|
71
|
+
if (!item || typeof item !== "object") continue;
|
|
72
|
+
for (const verb of VERBS) {
|
|
73
|
+
const op = item[verb] as Record<string, unknown> | undefined;
|
|
74
|
+
if (!op || typeof op !== "object") continue;
|
|
75
|
+
const tags = Array.isArray(op.tags) ? (op.tags as string[]) : [];
|
|
76
|
+
spec.operations.push({
|
|
77
|
+
id: String(op.operationId ?? `${verb.toUpperCase()} ${route}`),
|
|
78
|
+
tag: tags[0] ?? "",
|
|
79
|
+
verb: verb.toUpperCase(),
|
|
80
|
+
path: route,
|
|
81
|
+
});
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
return spec;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/** Parameters are compared by position, not by name: `{userId}`, `%s` and `${id}` are one marker. */
|
|
88
|
+
function shape(path: string): string {
|
|
89
|
+
return path
|
|
90
|
+
.replace(/\/$/, "")
|
|
91
|
+
.split("/")
|
|
92
|
+
.map((s) => (s.startsWith("{") || s.startsWith("${") || s === "%s" || s === "%v" || s === "%d" ? "*" : s))
|
|
93
|
+
.join("/");
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
export function findOperation(spec: Spec, verb: string, path: string): Operation | undefined {
|
|
97
|
+
const want = `${verb.toUpperCase()} ${shape(path)}`;
|
|
98
|
+
return spec.operations.find((op) => `${op.verb} ${shape(op.path)}` === want);
|
|
99
|
+
}
|