@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,401 @@
|
|
|
1
|
+
package main
|
|
2
|
+
|
|
3
|
+
import (
|
|
4
|
+
"fmt"
|
|
5
|
+
"strings"
|
|
6
|
+
"testing"
|
|
7
|
+
|
|
8
|
+
"github.com/shortlink-org/portolan/catalog"
|
|
9
|
+
)
|
|
10
|
+
|
|
11
|
+
func read(t *testing.T, sql string) ([]relation, []string) {
|
|
12
|
+
t.Helper()
|
|
13
|
+
|
|
14
|
+
relations, _, unread, err := readDDL(sql, "test.sql")
|
|
15
|
+
if err != nil {
|
|
16
|
+
t.Fatal(err)
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
return relations, unread
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
func TestColumnsKeysAndNullability(t *testing.T) {
|
|
23
|
+
relations, _ := read(t, `
|
|
24
|
+
CREATE TABLE users (
|
|
25
|
+
id text PRIMARY KEY,
|
|
26
|
+
email text NOT NULL,
|
|
27
|
+
nickname text,
|
|
28
|
+
version bigint NOT NULL
|
|
29
|
+
);`)
|
|
30
|
+
|
|
31
|
+
if len(relations) != 1 {
|
|
32
|
+
t.Fatalf("expected one table, got %d", len(relations))
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
got := map[string]catalog.Column{}
|
|
36
|
+
for _, column := range relations[0].table.Columns {
|
|
37
|
+
got[column.Name] = column
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
if !got["id"].PK {
|
|
41
|
+
t.Error("id is the primary key")
|
|
42
|
+
}
|
|
43
|
+
// A primary key is not null whether or not anybody wrote it down.
|
|
44
|
+
if got["id"].Nullable {
|
|
45
|
+
t.Error("a primary key is never nullable")
|
|
46
|
+
}
|
|
47
|
+
if got["email"].Nullable {
|
|
48
|
+
t.Error("email is NOT NULL")
|
|
49
|
+
}
|
|
50
|
+
if !got["nickname"].Nullable {
|
|
51
|
+
t.Error("nickname has no constraint, so it is nullable")
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
// The grammar normalises the standard names to PostgreSQL's internal ones. The
|
|
56
|
+
// catalog asks for the type as declared, so they are put back.
|
|
57
|
+
func TestTypesKeepTheirWrittenSpelling(t *testing.T) {
|
|
58
|
+
relations, _ := read(t, `
|
|
59
|
+
CREATE TABLE t (
|
|
60
|
+
a bigint,
|
|
61
|
+
b timestamptz,
|
|
62
|
+
c boolean,
|
|
63
|
+
d char(3),
|
|
64
|
+
e jsonb
|
|
65
|
+
);`)
|
|
66
|
+
|
|
67
|
+
want := map[string]string{
|
|
68
|
+
"a": "bigint",
|
|
69
|
+
"b": "timestamptz",
|
|
70
|
+
"c": "boolean",
|
|
71
|
+
"d": "char(3)",
|
|
72
|
+
"e": "jsonb",
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
for _, column := range relations[0].table.Columns {
|
|
76
|
+
if got := column.Type; got != want[column.Name] {
|
|
77
|
+
t.Errorf("%s: type = %q, want %q", column.Name, got, want[column.Name])
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
func TestTableLevelPrimaryKey(t *testing.T) {
|
|
83
|
+
relations, _ := read(t, `
|
|
84
|
+
CREATE TABLE order_items (
|
|
85
|
+
order_id uuid NOT NULL,
|
|
86
|
+
line_no integer NOT NULL,
|
|
87
|
+
PRIMARY KEY (order_id, line_no)
|
|
88
|
+
);`)
|
|
89
|
+
|
|
90
|
+
for _, column := range relations[0].table.Columns {
|
|
91
|
+
if !column.PK {
|
|
92
|
+
t.Errorf("%s is part of the composite key", column.Name)
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
func TestForeignKey(t *testing.T) {
|
|
98
|
+
relations, _ := read(t, `
|
|
99
|
+
CREATE TABLE lines (
|
|
100
|
+
id uuid PRIMARY KEY,
|
|
101
|
+
order_id uuid NOT NULL REFERENCES orders (id) ON DELETE CASCADE
|
|
102
|
+
);`)
|
|
103
|
+
|
|
104
|
+
var fk *catalog.FK
|
|
105
|
+
for _, column := range relations[0].table.Columns {
|
|
106
|
+
if column.Name == "order_id" {
|
|
107
|
+
fk = column.FK
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
if fk == nil {
|
|
112
|
+
t.Fatal("order_id has a foreign key")
|
|
113
|
+
}
|
|
114
|
+
if fk.Table != "orders" || fk.Column != "id" || fk.OnDelete != "cascade" {
|
|
115
|
+
t.Errorf("fk = %+v", fk)
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
func TestIndexesCarryTheirColumnsAndUniqueness(t *testing.T) {
|
|
120
|
+
relations, _ := read(t, `
|
|
121
|
+
CREATE TABLE sessions (id text PRIMARY KEY, user_id text, issued_at timestamptz);
|
|
122
|
+
CREATE UNIQUE INDEX sessions_token_key ON sessions (id);
|
|
123
|
+
CREATE INDEX sessions_user_id_idx ON sessions (user_id, issued_at);`)
|
|
124
|
+
|
|
125
|
+
indexes := relations[0].indexes
|
|
126
|
+
if len(indexes) != 2 {
|
|
127
|
+
t.Fatalf("expected two indexes, got %d", len(indexes))
|
|
128
|
+
}
|
|
129
|
+
if !indexes[0].Unique || indexes[0].Name != "sessions_token_key" {
|
|
130
|
+
t.Errorf("first index = %+v", indexes[0])
|
|
131
|
+
}
|
|
132
|
+
if indexes[1].Unique || strings.Join(indexes[1].Columns, ",") != "user_id,issued_at" {
|
|
133
|
+
t.Errorf("second index = %+v", indexes[1])
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
// A migration this reader does not model is reported. Quietly skipping one
|
|
138
|
+
// would leave the catalog confidently wrong about the schema.
|
|
139
|
+
func TestUnreadStatementsAreReported(t *testing.T) {
|
|
140
|
+
relations, unread := read(t, `
|
|
141
|
+
CREATE TABLE t (id text PRIMARY KEY);
|
|
142
|
+
ALTER TABLE t ADD COLUMN extra text;
|
|
143
|
+
CREATE INDEX elsewhere_idx ON other_table (id);`)
|
|
144
|
+
|
|
145
|
+
if len(unread) != 1 {
|
|
146
|
+
t.Fatalf("expected one note, got %v", unread)
|
|
147
|
+
}
|
|
148
|
+
if got := relations[0].table.Columns[1].Name; got != "extra" {
|
|
149
|
+
t.Errorf("ALTER ADD COLUMN was not applied: %q", got)
|
|
150
|
+
}
|
|
151
|
+
if !strings.Contains(strings.Join(unread, " "), "other_table") {
|
|
152
|
+
t.Errorf("an index on a table from another file should be named: %v", unread)
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
func TestMigrationsBuildFinalSchemaState(t *testing.T) {
|
|
157
|
+
state := newDDLState()
|
|
158
|
+
steps := []string{
|
|
159
|
+
`CREATE TABLE accounts (id uuid PRIMARY KEY, email text, obsolete text);`,
|
|
160
|
+
`ALTER TABLE accounts ADD COLUMN version integer NOT NULL;
|
|
161
|
+
ALTER TABLE accounts ALTER COLUMN email SET NOT NULL;
|
|
162
|
+
CREATE UNIQUE INDEX accounts_email_key ON accounts (email);`,
|
|
163
|
+
`ALTER TABLE accounts RENAME COLUMN email TO login;
|
|
164
|
+
ALTER TABLE accounts ALTER COLUMN version TYPE bigint;
|
|
165
|
+
ALTER TABLE accounts DROP COLUMN obsolete;
|
|
166
|
+
ALTER TABLE accounts RENAME TO users;`,
|
|
167
|
+
}
|
|
168
|
+
for i, sql := range steps {
|
|
169
|
+
unread, err := state.apply(sql, fmt.Sprintf("%03d.sql", i+1))
|
|
170
|
+
if err != nil {
|
|
171
|
+
t.Fatal(err)
|
|
172
|
+
}
|
|
173
|
+
if len(unread) != 0 {
|
|
174
|
+
t.Fatalf("migration %d left unread DDL: %v", i+1, unread)
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
if len(state.relations) != 1 || state.relations[0].table.Name != "users" {
|
|
179
|
+
t.Fatalf("relations = %+v", state.relations)
|
|
180
|
+
}
|
|
181
|
+
r := state.relations[0]
|
|
182
|
+
if len(r.table.Columns) != 3 {
|
|
183
|
+
t.Fatalf("columns = %+v", r.table.Columns)
|
|
184
|
+
}
|
|
185
|
+
want := map[string]string{"id": "uuid", "login": "text", "version": "bigint"}
|
|
186
|
+
for _, column := range r.table.Columns {
|
|
187
|
+
if column.Type != want[column.Name] {
|
|
188
|
+
t.Errorf("column %s type = %q, want %q", column.Name, column.Type, want[column.Name])
|
|
189
|
+
}
|
|
190
|
+
if column.Name == "login" && column.Nullable {
|
|
191
|
+
t.Error("renamed login column lost NOT NULL")
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
if len(r.indexes) != 1 || strings.Join(r.indexes[0].Columns, ",") != "login" {
|
|
195
|
+
t.Errorf("index did not follow renamed column: %+v", r.indexes)
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
func TestPostgresSchemasKeepRelationsDistinct(t *testing.T) {
|
|
200
|
+
state := newDDLState()
|
|
201
|
+
unread, err := state.apply(`
|
|
202
|
+
CREATE SCHEMA sales;
|
|
203
|
+
CREATE SCHEMA audit;
|
|
204
|
+
CREATE TABLE sales.orders (id uuid PRIMARY KEY, tenant_id uuid NOT NULL);
|
|
205
|
+
CREATE TABLE audit.orders (id uuid PRIMARY KEY, payload jsonb NOT NULL);
|
|
206
|
+
CREATE TABLE sales.order_lines (
|
|
207
|
+
id uuid PRIMARY KEY,
|
|
208
|
+
order_id uuid NOT NULL REFERENCES sales.orders (id)
|
|
209
|
+
);
|
|
210
|
+
CREATE INDEX orders_tenant_idx ON sales.orders (tenant_id);
|
|
211
|
+
CREATE VIEW sales.order_ids AS SELECT id FROM sales.orders;
|
|
212
|
+
`, "001.sql")
|
|
213
|
+
if err != nil {
|
|
214
|
+
t.Fatal(err)
|
|
215
|
+
}
|
|
216
|
+
if len(unread) != 0 {
|
|
217
|
+
t.Fatalf("nothing should be left unread: %v", unread)
|
|
218
|
+
}
|
|
219
|
+
if len(state.relations) != 3 {
|
|
220
|
+
t.Fatalf("relations = %+v", state.relations)
|
|
221
|
+
}
|
|
222
|
+
if state.relations[state.relationAt["sales.orders"]].table.Name != "sales.orders" ||
|
|
223
|
+
state.relations[state.relationAt["audit.orders"]].table.Name != "audit.orders" {
|
|
224
|
+
t.Fatalf("schema-qualified relations were merged: %+v", state.relationAt)
|
|
225
|
+
}
|
|
226
|
+
if got := state.relations[state.relationAt["sales.orders"]].indexes; len(got) != 1 || got[0].Name != "orders_tenant_idx" {
|
|
227
|
+
t.Errorf("schema-qualified index was not attached: %+v", got)
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
unread, err = state.apply(`ALTER TABLE sales.orders SET SCHEMA archive;`, "002.sql")
|
|
231
|
+
if err != nil {
|
|
232
|
+
t.Fatal(err)
|
|
233
|
+
}
|
|
234
|
+
if len(unread) != 0 {
|
|
235
|
+
t.Fatalf("SET SCHEMA should be applied: %v", unread)
|
|
236
|
+
}
|
|
237
|
+
if _, exists := state.relationAt["sales.orders"]; exists {
|
|
238
|
+
t.Error("old qualified table name still exists")
|
|
239
|
+
}
|
|
240
|
+
if _, exists := state.relationAt["archive.orders"]; !exists {
|
|
241
|
+
t.Fatalf("moved table is missing: %+v", state.relationAt)
|
|
242
|
+
}
|
|
243
|
+
lines := state.relations[state.relationAt["sales.order_lines"]]
|
|
244
|
+
if fk := lines.table.Columns[1].FK; fk == nil || fk.Table != "archive.orders" {
|
|
245
|
+
t.Errorf("foreign key did not follow SET SCHEMA: %+v", fk)
|
|
246
|
+
}
|
|
247
|
+
if got := strings.Join(state.views[0].reads, ","); got != "archive.orders" {
|
|
248
|
+
t.Errorf("view lineage did not follow SET SCHEMA: %q", got)
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
func TestPostgresEnumsAreRenderedInColumnTypes(t *testing.T) {
|
|
253
|
+
state := newDDLState()
|
|
254
|
+
steps := []string{
|
|
255
|
+
`CREATE SCHEMA billing;
|
|
256
|
+
CREATE TYPE billing.payment_status AS ENUM ('pending', 'paid');
|
|
257
|
+
CREATE TABLE billing.payments (
|
|
258
|
+
id uuid PRIMARY KEY,
|
|
259
|
+
status billing.payment_status NOT NULL,
|
|
260
|
+
history billing.payment_status[] NOT NULL
|
|
261
|
+
);`,
|
|
262
|
+
`ALTER TYPE billing.payment_status ADD VALUE 'authorized' BEFORE 'paid';
|
|
263
|
+
ALTER TYPE billing.payment_status RENAME VALUE 'pending' TO 'created';`,
|
|
264
|
+
`ALTER TYPE billing.payment_status SET SCHEMA public;`,
|
|
265
|
+
}
|
|
266
|
+
for i, sql := range steps {
|
|
267
|
+
unread, err := state.apply(sql, fmt.Sprintf("%03d.sql", i+1))
|
|
268
|
+
if err != nil {
|
|
269
|
+
t.Fatal(err)
|
|
270
|
+
}
|
|
271
|
+
if len(unread) != 0 {
|
|
272
|
+
t.Fatalf("migration %d left unread DDL: %v", i+1, unread)
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
table := state.relations[state.relationAt["billing.payments"]].table
|
|
277
|
+
got := map[string]string{}
|
|
278
|
+
for _, column := range table.Columns {
|
|
279
|
+
got[column.Name] = state.renderType(column.Type)
|
|
280
|
+
}
|
|
281
|
+
if got["status"] != "payment_status enum(created | authorized | paid)" {
|
|
282
|
+
t.Errorf("status type = %q", got["status"])
|
|
283
|
+
}
|
|
284
|
+
if got["history"] != "payment_status enum(created | authorized | paid)[]" {
|
|
285
|
+
t.Errorf("history type = %q", got["history"])
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
// A view is the other half of what a migration builds, and the half a table
|
|
290
|
+
// cannot express: no key, no constraints, and what it reads instead.
|
|
291
|
+
func TestReadsAViewAndWhereItsColumnsComeFrom(t *testing.T) {
|
|
292
|
+
sql := `
|
|
293
|
+
CREATE TABLE payments (id text PRIMARY KEY, order_id text NOT NULL, amount_minor bigint NOT NULL);
|
|
294
|
+
CREATE TABLE refunds (id text PRIMARY KEY, payment_id text NOT NULL, amount_minor bigint NOT NULL);
|
|
295
|
+
CREATE VIEW v_payment_state AS
|
|
296
|
+
SELECT p.id AS payment_id, p.order_id, coalesce(sum(r.amount_minor), 0) AS refunded_minor
|
|
297
|
+
FROM payments p
|
|
298
|
+
LEFT JOIN refunds r ON r.payment_id = p.id
|
|
299
|
+
GROUP BY p.id;
|
|
300
|
+
`
|
|
301
|
+
|
|
302
|
+
_, views, unread, err := readDDL(sql, "test.sql")
|
|
303
|
+
if err != nil {
|
|
304
|
+
t.Fatal(err)
|
|
305
|
+
}
|
|
306
|
+
if len(unread) != 0 {
|
|
307
|
+
t.Fatalf("nothing should be left unread: %v", unread)
|
|
308
|
+
}
|
|
309
|
+
if len(views) != 1 {
|
|
310
|
+
t.Fatalf("want one view, got %d", len(views))
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
view := views[0]
|
|
314
|
+
if view.name != "v_payment_state" {
|
|
315
|
+
t.Errorf("name %q", view.name)
|
|
316
|
+
}
|
|
317
|
+
if strings.Join(view.reads, ",") != "payments,refunds" {
|
|
318
|
+
t.Errorf("reads %v", view.reads)
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
want := map[string]string{
|
|
322
|
+
"payment_id": "payments.id",
|
|
323
|
+
"order_id": "payments.order_id",
|
|
324
|
+
"refunded_minor": "refunds.amount_minor",
|
|
325
|
+
}
|
|
326
|
+
if len(view.columns) != len(want) {
|
|
327
|
+
t.Fatalf("columns %v", view.columns)
|
|
328
|
+
}
|
|
329
|
+
for _, column := range view.columns {
|
|
330
|
+
if strings.Join(column.from, ",") != want[column.name] {
|
|
331
|
+
t.Errorf("%s comes from %v, want %q", column.name, column.from, want[column.name])
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
// The definition is the SQL somebody wrote, not the tree printed back.
|
|
336
|
+
if !strings.HasPrefix(view.definition, "CREATE VIEW v_payment_state AS") || !strings.HasSuffix(view.definition, ";") {
|
|
337
|
+
t.Errorf("definition %q", view.definition)
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
// A column copied from another service's row is a fact the migration declares,
|
|
342
|
+
// because the statement that creates the column cannot show it.
|
|
343
|
+
func TestReadsWhereACopiedColumnCameFrom(t *testing.T) {
|
|
344
|
+
sql := `
|
|
345
|
+
CREATE TABLE packages (
|
|
346
|
+
id text NOT NULL PRIMARY KEY,
|
|
347
|
+
-- from: shop.oms.pg.orders.ship_to
|
|
348
|
+
ship_to text NOT NULL,
|
|
349
|
+
-- from: packages.id
|
|
350
|
+
label_of text NOT NULL,
|
|
351
|
+
weight_g integer NOT NULL
|
|
352
|
+
);
|
|
353
|
+
`
|
|
354
|
+
|
|
355
|
+
copies := readCopies(sql, "delivery.core.pg")
|
|
356
|
+
|
|
357
|
+
if got := strings.Join(copies["packages"]["ship_to"], ","); got != "shop.oms.pg.orders.ship_to" {
|
|
358
|
+
t.Errorf("ship_to came from %q", got)
|
|
359
|
+
}
|
|
360
|
+
// A column of this store may be named without it.
|
|
361
|
+
if got := strings.Join(copies["packages"]["label_of"], ","); got != "delivery.core.pg.packages.id" {
|
|
362
|
+
t.Errorf("label_of came from %q", got)
|
|
363
|
+
}
|
|
364
|
+
if _, said := copies["packages"]["weight_g"]; said {
|
|
365
|
+
t.Error("a column with no comment above it says nothing")
|
|
366
|
+
}
|
|
367
|
+
if _, said := copies["packages"]["id"]; said {
|
|
368
|
+
t.Error("the comment belongs to the column under it, not the one before")
|
|
369
|
+
}
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
// The other spelling of a view: the grammar calls it a CREATE TABLE AS whose
|
|
373
|
+
// object is a matview, and the difference a reader needs is that the rows are
|
|
374
|
+
// kept and can be stale.
|
|
375
|
+
func TestReadsAMaterializedView(t *testing.T) {
|
|
376
|
+
sql := `
|
|
377
|
+
CREATE TABLE routes (id text PRIMARY KEY, planned_for date NOT NULL);
|
|
378
|
+
CREATE TABLE route_stops (route_id text NOT NULL, seq integer NOT NULL);
|
|
379
|
+
CREATE MATERIALIZED VIEW mv_route_load AS
|
|
380
|
+
SELECT r.id AS route_id, count(s.seq) AS stops
|
|
381
|
+
FROM routes r LEFT JOIN route_stops s ON s.route_id = r.id
|
|
382
|
+
GROUP BY r.id;
|
|
383
|
+
`
|
|
384
|
+
|
|
385
|
+
_, views, unread, err := readDDL(sql, "test.sql")
|
|
386
|
+
if err != nil {
|
|
387
|
+
t.Fatal(err)
|
|
388
|
+
}
|
|
389
|
+
if len(unread) != 0 {
|
|
390
|
+
t.Fatalf("nothing should be left unread: %v", unread)
|
|
391
|
+
}
|
|
392
|
+
if len(views) != 1 || !views[0].materialized {
|
|
393
|
+
t.Fatalf("views = %+v", views)
|
|
394
|
+
}
|
|
395
|
+
if strings.Join(views[0].reads, ",") != "routes,route_stops" {
|
|
396
|
+
t.Errorf("reads %v", views[0].reads)
|
|
397
|
+
}
|
|
398
|
+
if !strings.HasPrefix(views[0].definition, "CREATE MATERIALIZED VIEW mv_route_load AS") {
|
|
399
|
+
t.Errorf("definition %q", views[0].definition)
|
|
400
|
+
}
|
|
401
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
package main
|
|
2
|
+
|
|
3
|
+
import (
|
|
4
|
+
_ "embed"
|
|
5
|
+
|
|
6
|
+
"github.com/shortlink-org/portolan/plugin"
|
|
7
|
+
)
|
|
8
|
+
|
|
9
|
+
//go:embed options.schema.json
|
|
10
|
+
var optionsSchema []byte
|
|
11
|
+
|
|
12
|
+
func descriptor() plugin.Descriptor {
|
|
13
|
+
return plugin.Descriptor{
|
|
14
|
+
Name: "extract-sql",
|
|
15
|
+
Summary: "Reads a service's migrations and answers with the store it builds: tables, columns, keys, and which aggregate each table persists.",
|
|
16
|
+
Phases: []string{plugin.PhaseExtract},
|
|
17
|
+
Options: optionsSchema,
|
|
18
|
+
}
|
|
19
|
+
}
|
|
@@ -0,0 +1,221 @@
|
|
|
1
|
+
package main
|
|
2
|
+
|
|
3
|
+
import (
|
|
4
|
+
"io/fs"
|
|
5
|
+
"os"
|
|
6
|
+
"path"
|
|
7
|
+
"path/filepath"
|
|
8
|
+
"sort"
|
|
9
|
+
"strings"
|
|
10
|
+
)
|
|
11
|
+
|
|
12
|
+
type storagePackage struct {
|
|
13
|
+
name string
|
|
14
|
+
dir string
|
|
15
|
+
migrations string
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
type storageLayout struct {
|
|
19
|
+
repositories []storagePackage
|
|
20
|
+
projectors []storagePackage
|
|
21
|
+
source string
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
// discoverStorageLayout keeps filesystem conventions in one place. Explicit
|
|
25
|
+
// roots retain the original "one child package per aggregate" contract, while
|
|
26
|
+
// omitted roots discover both horizontal and feature-sliced Go layouts.
|
|
27
|
+
func discoverStorageLayout(root, repositories, projectors string) storageLayout {
|
|
28
|
+
var layout storageLayout
|
|
29
|
+
|
|
30
|
+
if repositories != "" {
|
|
31
|
+
layout.repositories = packagesUnder(root, repositories, "repository")
|
|
32
|
+
} else {
|
|
33
|
+
layout.repositories = discoverPackages(root, "repository")
|
|
34
|
+
}
|
|
35
|
+
if projectors != "" {
|
|
36
|
+
layout.projectors = packagesUnder(root, projectors, "projector")
|
|
37
|
+
} else {
|
|
38
|
+
layout.projectors = discoverPackages(root, "projector")
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
if repositories != "" {
|
|
42
|
+
layout.source = filepath.ToSlash(filepath.Join(root, filepath.FromSlash(repositories)))
|
|
43
|
+
return layout
|
|
44
|
+
}
|
|
45
|
+
if projectors != "" {
|
|
46
|
+
layout.source = filepath.ToSlash(filepath.Join(root, filepath.FromSlash(projectors)))
|
|
47
|
+
return layout
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
dirs := make([]string, 0, len(layout.repositories)+len(layout.projectors))
|
|
51
|
+
for _, pkg := range layout.repositories {
|
|
52
|
+
dirs = append(dirs, pkg.dir)
|
|
53
|
+
}
|
|
54
|
+
for _, pkg := range layout.projectors {
|
|
55
|
+
dirs = append(dirs, pkg.dir)
|
|
56
|
+
}
|
|
57
|
+
layout.source = storageSource(root, dirs)
|
|
58
|
+
|
|
59
|
+
return layout
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
func packagesUnder(root, base, kind string) []storagePackage {
|
|
63
|
+
base = filepath.ToSlash(filepath.Clean(base))
|
|
64
|
+
var out []storagePackage
|
|
65
|
+
|
|
66
|
+
// A feature slice points directly at its repository adapter, with
|
|
67
|
+
// migrations immediately below it.
|
|
68
|
+
if hasUpMigrations(root, path.Join(base, "migrations")) {
|
|
69
|
+
name := packageName(strings.Split(base, "/"), kind)
|
|
70
|
+
if name == "" {
|
|
71
|
+
name = path.Base(base)
|
|
72
|
+
}
|
|
73
|
+
return []storagePackage{{name: name, dir: base, migrations: path.Join(base, "migrations")}}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
for _, child := range subdirs(root, base) {
|
|
77
|
+
dir := path.Join(base, child)
|
|
78
|
+
migrations := path.Join(dir, "migrations")
|
|
79
|
+
if hasUpMigrations(root, migrations) {
|
|
80
|
+
out = append(out, storagePackage{name: child, dir: dir, migrations: migrations})
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
sortStoragePackages(out)
|
|
85
|
+
return out
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
func discoverPackages(root, kind string) []storagePackage {
|
|
89
|
+
base := filepath.Join(root, "internal")
|
|
90
|
+
seen := map[string]bool{}
|
|
91
|
+
var out []storagePackage
|
|
92
|
+
|
|
93
|
+
_ = filepath.WalkDir(base, func(filename string, entry fs.DirEntry, err error) error {
|
|
94
|
+
if err != nil {
|
|
95
|
+
return nil
|
|
96
|
+
}
|
|
97
|
+
if !entry.IsDir() {
|
|
98
|
+
return nil
|
|
99
|
+
}
|
|
100
|
+
if filename != base && strings.HasPrefix(entry.Name(), ".") {
|
|
101
|
+
return filepath.SkipDir
|
|
102
|
+
}
|
|
103
|
+
if entry.Name() != "migrations" {
|
|
104
|
+
return nil
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
rel, err := filepath.Rel(root, filename)
|
|
108
|
+
if err != nil {
|
|
109
|
+
return filepath.SkipDir
|
|
110
|
+
}
|
|
111
|
+
migrations := filepath.ToSlash(rel)
|
|
112
|
+
if !hasUpMigrations(root, migrations) {
|
|
113
|
+
return filepath.SkipDir
|
|
114
|
+
}
|
|
115
|
+
dir := path.Dir(migrations)
|
|
116
|
+
parts := strings.Split(dir, "/")
|
|
117
|
+
name := packageName(parts, kind)
|
|
118
|
+
if name == "" || seen[dir] {
|
|
119
|
+
return filepath.SkipDir
|
|
120
|
+
}
|
|
121
|
+
seen[dir] = true
|
|
122
|
+
out = append(out, storagePackage{name: name, dir: dir, migrations: migrations})
|
|
123
|
+
|
|
124
|
+
return filepath.SkipDir
|
|
125
|
+
})
|
|
126
|
+
|
|
127
|
+
sortStoragePackages(out)
|
|
128
|
+
return out
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
// packageName accepts these shapes and ignores migrations belonging to the
|
|
132
|
+
// other adapter kind:
|
|
133
|
+
//
|
|
134
|
+
// internal/infrastructure/repository/user
|
|
135
|
+
// internal/user/infrastructure/repository
|
|
136
|
+
// internal/user/infrastructure/projector/directory
|
|
137
|
+
func packageName(parts []string, kind string) string {
|
|
138
|
+
for i, part := range parts {
|
|
139
|
+
if part != kind {
|
|
140
|
+
continue
|
|
141
|
+
}
|
|
142
|
+
if i+1 < len(parts) {
|
|
143
|
+
return parts[i+1]
|
|
144
|
+
}
|
|
145
|
+
if i >= 2 && parts[i-1] == "infrastructure" {
|
|
146
|
+
return parts[i-2]
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
return ""
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
func hasUpMigrations(root, dir string) bool {
|
|
153
|
+
entries, err := os.ReadDir(filepath.Join(root, filepath.FromSlash(dir)))
|
|
154
|
+
if err != nil {
|
|
155
|
+
return false
|
|
156
|
+
}
|
|
157
|
+
for _, entry := range entries {
|
|
158
|
+
if !entry.IsDir() && strings.HasSuffix(entry.Name(), ".sql") && !strings.HasSuffix(entry.Name(), ".down.sql") {
|
|
159
|
+
return true
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
return false
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
func sortStoragePackages(packages []storagePackage) {
|
|
166
|
+
sort.Slice(packages, func(i, j int) bool {
|
|
167
|
+
if packages[i].name != packages[j].name {
|
|
168
|
+
return packages[i].name < packages[j].name
|
|
169
|
+
}
|
|
170
|
+
return packages[i].dir < packages[j].dir
|
|
171
|
+
})
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
func storageSource(root string, dirs []string) string {
|
|
175
|
+
if len(dirs) == 0 {
|
|
176
|
+
return ""
|
|
177
|
+
}
|
|
178
|
+
sort.Strings(dirs)
|
|
179
|
+
if len(dirs) == 1 {
|
|
180
|
+
return filepath.ToSlash(filepath.Join(root, filepath.FromSlash(dirs[0])))
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
parent := path.Dir(dirs[0])
|
|
184
|
+
sameParent := true
|
|
185
|
+
for _, dir := range dirs[1:] {
|
|
186
|
+
if path.Dir(dir) != parent {
|
|
187
|
+
sameParent = false
|
|
188
|
+
break
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
if sameParent {
|
|
192
|
+
return filepath.ToSlash(filepath.Join(root, filepath.FromSlash(parent)))
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
parts := strings.Split(dirs[0], "/")
|
|
196
|
+
for _, dir := range dirs[1:] {
|
|
197
|
+
other := strings.Split(dir, "/")
|
|
198
|
+
if len(other) != len(parts) {
|
|
199
|
+
return filepath.ToSlash(filepath.Join(root, "internal"))
|
|
200
|
+
}
|
|
201
|
+
for i := range parts {
|
|
202
|
+
if parts[i] != other[i] {
|
|
203
|
+
parts[i] = "*"
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
return filepath.ToSlash(filepath.Join(root, filepath.FromSlash(strings.Join(parts, "/"))))
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
// mapSourceDir accepts both supported repository shapes: a collection root
|
|
212
|
+
// with one child package per aggregate, and a feature repository package that
|
|
213
|
+
// is already the aggregate's source directory.
|
|
214
|
+
func mapSourceDir(root, repositoryDir, aggregate string) string {
|
|
215
|
+
child := path.Join(repositoryDir, aggregate)
|
|
216
|
+
if info, err := os.Stat(filepath.Join(root, filepath.FromSlash(child))); err == nil && info.IsDir() {
|
|
217
|
+
return child
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
return repositoryDir
|
|
221
|
+
}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
package main
|
|
2
|
+
|
|
3
|
+
import "testing"
|
|
4
|
+
|
|
5
|
+
func TestDiscoversHorizontalAndFeatureStoragePackages(t *testing.T) {
|
|
6
|
+
root := writeTree(t, map[string]string{
|
|
7
|
+
"internal/infrastructure/repository/quote/migrations/0001_quote.sql": `CREATE TABLE quotes (id text PRIMARY KEY);`,
|
|
8
|
+
"internal/user/infrastructure/repository/migrations/0001_user.sql": `CREATE TABLE users (id text PRIMARY KEY);`,
|
|
9
|
+
"internal/search/infrastructure/projector/results/migrations/0001_results.sql": `CREATE TABLE results (id text PRIMARY KEY);`,
|
|
10
|
+
})
|
|
11
|
+
|
|
12
|
+
layout := discoverStorageLayout(root, "", "")
|
|
13
|
+
if len(layout.repositories) != 2 {
|
|
14
|
+
t.Fatalf("repositories = %+v", layout.repositories)
|
|
15
|
+
}
|
|
16
|
+
if layout.repositories[0].name != "quote" || layout.repositories[0].dir != "internal/infrastructure/repository/quote" {
|
|
17
|
+
t.Errorf("horizontal repository = %+v", layout.repositories[0])
|
|
18
|
+
}
|
|
19
|
+
if layout.repositories[1].name != "user" || layout.repositories[1].dir != "internal/user/infrastructure/repository" {
|
|
20
|
+
t.Errorf("feature repository = %+v", layout.repositories[1])
|
|
21
|
+
}
|
|
22
|
+
if len(layout.projectors) != 1 || layout.projectors[0].name != "results" {
|
|
23
|
+
t.Errorf("projectors = %+v", layout.projectors)
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
func TestFeatureRepositoriesProduceAStableSourcePattern(t *testing.T) {
|
|
28
|
+
root := writeTree(t, map[string]string{
|
|
29
|
+
"internal/user/infrastructure/repository/migrations/0001_user.sql": `CREATE TABLE users (id text PRIMARY KEY);`,
|
|
30
|
+
"internal/session/infrastructure/repository/migrations/0001_session.sql": `CREATE TABLE sessions (id text PRIMARY KEY);`,
|
|
31
|
+
})
|
|
32
|
+
|
|
33
|
+
layout := discoverStorageLayout(root, "", "")
|
|
34
|
+
want := root + "/internal/*/infrastructure/repository"
|
|
35
|
+
if layout.source != want {
|
|
36
|
+
t.Errorf("source = %q, want %q", layout.source, want)
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
func TestExplicitRootAcceptsCollectionAndFeaturePackage(t *testing.T) {
|
|
41
|
+
root := writeTree(t, map[string]string{
|
|
42
|
+
"src/infrastructure/repository/order/migrations/0001_order.sql": `CREATE TABLE orders (id text PRIMARY KEY);`,
|
|
43
|
+
"internal/user/infrastructure/repository/migrations/0001_user.sql": `CREATE TABLE users (id text PRIMARY KEY);`,
|
|
44
|
+
})
|
|
45
|
+
|
|
46
|
+
collection := discoverStorageLayout(root, "src/infrastructure/repository", "missing")
|
|
47
|
+
if len(collection.repositories) != 1 || collection.repositories[0].name != "order" {
|
|
48
|
+
t.Errorf("collection = %+v", collection.repositories)
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
feature := discoverStorageLayout(root, "internal/user/infrastructure/repository", "missing")
|
|
52
|
+
if len(feature.repositories) != 1 || feature.repositories[0].name != "user" {
|
|
53
|
+
t.Errorf("feature = %+v", feature.repositories)
|
|
54
|
+
}
|
|
55
|
+
}
|