@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,332 @@
|
|
|
1
|
+
package main
|
|
2
|
+
|
|
3
|
+
import (
|
|
4
|
+
"os"
|
|
5
|
+
"os/exec"
|
|
6
|
+
"path/filepath"
|
|
7
|
+
"sort"
|
|
8
|
+
"strings"
|
|
9
|
+
"testing"
|
|
10
|
+
|
|
11
|
+
"github.com/shortlink-org/portolan/plugin"
|
|
12
|
+
)
|
|
13
|
+
|
|
14
|
+
// A repository to fetch from, made on the spot: git itself is the fake, and
|
|
15
|
+
// what it serves is what a forge would serve.
|
|
16
|
+
type remote struct {
|
|
17
|
+
url string
|
|
18
|
+
commit string
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
func repository(t *testing.T) remote {
|
|
22
|
+
t.Helper()
|
|
23
|
+
if _, err := exec.LookPath("git"); err != nil {
|
|
24
|
+
t.Skip("no git on PATH")
|
|
25
|
+
}
|
|
26
|
+
dir := t.TempDir()
|
|
27
|
+
run := func(args ...string) string {
|
|
28
|
+
t.Helper()
|
|
29
|
+
cmd := exec.Command("git", append([]string{
|
|
30
|
+
"-c", "user.name=test", "-c", "user.email=test@example.com",
|
|
31
|
+
"-c", "commit.gpgsign=false", "-c", "init.defaultBranch=main",
|
|
32
|
+
}, args...)...)
|
|
33
|
+
cmd.Dir = dir
|
|
34
|
+
out, err := cmd.CombinedOutput()
|
|
35
|
+
if err != nil {
|
|
36
|
+
t.Fatalf("git %s: %v\n%s", strings.Join(args, " "), err, out)
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
return strings.TrimSpace(string(out))
|
|
40
|
+
}
|
|
41
|
+
write := func(rel, contents string) {
|
|
42
|
+
t.Helper()
|
|
43
|
+
if err := os.MkdirAll(filepath.Dir(filepath.Join(dir, rel)), 0o755); err != nil {
|
|
44
|
+
t.Fatal(err)
|
|
45
|
+
}
|
|
46
|
+
if err := os.WriteFile(filepath.Join(dir, rel), []byte(contents), 0o644); err != nil {
|
|
47
|
+
t.Fatal(err)
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
run("init", "--quiet")
|
|
52
|
+
write("services/oms/internal/domain/order/order.go", "package order\n")
|
|
53
|
+
write("services/oms/README.md", "# OMS\n")
|
|
54
|
+
write("proto/shop/v1/orders.proto", "syntax = \"proto3\";\n")
|
|
55
|
+
write("README.md", "# shop\n")
|
|
56
|
+
run("add", ".")
|
|
57
|
+
run("commit", "--quiet", "-m", "the estate")
|
|
58
|
+
// A forge lets a client fetch a commit by name; a plain file remote does
|
|
59
|
+
// not unless told to, and the fallback to branches is tested separately.
|
|
60
|
+
run("config", "uploadpack.allowAnySHA1InWant", "true")
|
|
61
|
+
|
|
62
|
+
return remote{url: "file://" + dir, commit: run("rev-parse", "HEAD")}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
func options(r remote, cache string, paths ...string) Options {
|
|
66
|
+
return Options{
|
|
67
|
+
Cache: cache,
|
|
68
|
+
Repos: []Repo{{Repo: r.url, Commit: r.commit, Paths: paths}},
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
// Every test decides for itself whether it is online.
|
|
73
|
+
func isolate(t *testing.T) {
|
|
74
|
+
t.Helper()
|
|
75
|
+
t.Setenv(OfflineEnv, "")
|
|
76
|
+
t.Setenv("CI", "")
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
func names(resp plugin.Response) []string {
|
|
80
|
+
out := make([]string, 0, len(resp.Files))
|
|
81
|
+
for _, f := range resp.Files {
|
|
82
|
+
out = append(out, f.Name)
|
|
83
|
+
}
|
|
84
|
+
sort.Strings(out)
|
|
85
|
+
|
|
86
|
+
return out
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
func contentsOf(resp plugin.Response, name string) string {
|
|
90
|
+
for _, f := range resp.Files {
|
|
91
|
+
if f.Name == name {
|
|
92
|
+
return f.Contents
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
return ""
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
// write does what the host does with a response.
|
|
100
|
+
func write(t *testing.T, dir string, resp plugin.Response) {
|
|
101
|
+
t.Helper()
|
|
102
|
+
for _, f := range resp.Files {
|
|
103
|
+
at := filepath.Join(dir, filepath.FromSlash(f.Name))
|
|
104
|
+
if err := os.MkdirAll(filepath.Dir(at), 0o755); err != nil {
|
|
105
|
+
t.Fatal(err)
|
|
106
|
+
}
|
|
107
|
+
if err := os.WriteFile(at, []byte(f.Contents), 0o644); err != nil {
|
|
108
|
+
t.Fatal(err)
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
func TestFetchWritesTheNarrowedCopyAndALock(t *testing.T) {
|
|
114
|
+
isolate(t)
|
|
115
|
+
r := repository(t)
|
|
116
|
+
|
|
117
|
+
resp, err := fetch(options(r, t.TempDir(), "services/oms", "proto"))
|
|
118
|
+
if err != nil {
|
|
119
|
+
t.Fatal(err)
|
|
120
|
+
}
|
|
121
|
+
// The directory is owner/name, read off the URL; the paths inside are
|
|
122
|
+
// the repository's own, so an extractor can read the copy as a checkout.
|
|
123
|
+
dir := filepath.Base(filepath.Dir(strings.TrimPrefix(r.url, "file://"))) + "/" + filepath.Base(strings.TrimPrefix(r.url, "file://"))
|
|
124
|
+
want := []string{
|
|
125
|
+
dir + "/git.lock.json",
|
|
126
|
+
dir + "/git.repo.json",
|
|
127
|
+
dir + "/proto/shop/v1/orders.proto",
|
|
128
|
+
dir + "/services/oms/README.md",
|
|
129
|
+
dir + "/services/oms/internal/domain/order/order.go",
|
|
130
|
+
}
|
|
131
|
+
if got := names(resp); strings.Join(got, "\n") != strings.Join(want, "\n") {
|
|
132
|
+
t.Fatalf("files = %v, want %v", got, want)
|
|
133
|
+
}
|
|
134
|
+
lock := contentsOf(resp, dir+"/git.lock.json")
|
|
135
|
+
if !strings.Contains(lock, `"commit": "`+r.commit+`"`) || !strings.Contains(lock, `"sha256"`) || !strings.Contains(lock, `"services/oms"`) {
|
|
136
|
+
t.Errorf("lock = %s", lock)
|
|
137
|
+
}
|
|
138
|
+
if len(resp.Warnings()) != 0 {
|
|
139
|
+
t.Errorf("diagnostics = %+v", resp.Warnings())
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
// The fragment is the lock's commit said in the shape the catalog reads,
|
|
143
|
+
// and it is a catalog: empty lists rather than absent ones, so the merge
|
|
144
|
+
// never has to tell "no contexts" from "not a source".
|
|
145
|
+
fragment := contentsOf(resp, dir+"/git.repo.json")
|
|
146
|
+
if !strings.Contains(fragment, `"commit": "`+r.commit+`"`) {
|
|
147
|
+
t.Errorf("fragment does not pin the fetched commit: %s", fragment)
|
|
148
|
+
}
|
|
149
|
+
for _, empty := range []string{`"contexts": []`, `"defs": {}`, `"flows": []`, `"adrs": []`} {
|
|
150
|
+
if !strings.Contains(fragment, empty) {
|
|
151
|
+
t.Errorf("fragment is missing %s: %s", empty, fragment)
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
func TestOfflineReplayIsByteIdentical(t *testing.T) {
|
|
157
|
+
isolate(t)
|
|
158
|
+
r := repository(t)
|
|
159
|
+
cache := t.TempDir()
|
|
160
|
+
|
|
161
|
+
online, err := fetch(options(r, cache, "services/oms"))
|
|
162
|
+
if err != nil {
|
|
163
|
+
t.Fatal(err)
|
|
164
|
+
}
|
|
165
|
+
write(t, cache, online)
|
|
166
|
+
|
|
167
|
+
t.Setenv(OfflineEnv, "1")
|
|
168
|
+
replayed, err := fetch(options(r, cache, "services/oms"))
|
|
169
|
+
if err != nil {
|
|
170
|
+
t.Fatal(err)
|
|
171
|
+
}
|
|
172
|
+
if strings.Join(names(online), "\n") != strings.Join(names(replayed), "\n") {
|
|
173
|
+
t.Fatalf("offline names = %v, online %v", names(replayed), names(online))
|
|
174
|
+
}
|
|
175
|
+
for _, f := range online.Files {
|
|
176
|
+
if contentsOf(replayed, f.Name) != f.Contents {
|
|
177
|
+
t.Errorf("%s differs offline", f.Name)
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
if len(replayed.Warnings()) != 1 || !strings.Contains(replayed.Warnings()[0].Message, "offline") {
|
|
181
|
+
t.Errorf("diagnostics = %+v, want the one saying the copy was used", replayed.Warnings())
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
func TestCiImpliesOffline(t *testing.T) {
|
|
186
|
+
isolate(t)
|
|
187
|
+
r := repository(t)
|
|
188
|
+
cache := t.TempDir()
|
|
189
|
+
online, err := fetch(options(r, cache, "proto"))
|
|
190
|
+
if err != nil {
|
|
191
|
+
t.Fatal(err)
|
|
192
|
+
}
|
|
193
|
+
write(t, cache, online)
|
|
194
|
+
|
|
195
|
+
t.Setenv("CI", "true")
|
|
196
|
+
replayed, err := fetch(options(r, cache, "proto"))
|
|
197
|
+
if err != nil {
|
|
198
|
+
t.Fatal(err)
|
|
199
|
+
}
|
|
200
|
+
if len(replayed.Warnings()) != 1 {
|
|
201
|
+
t.Errorf("CI should replay and say so: %+v", replayed.Warnings())
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
func TestEditedCacheIsReportedByPath(t *testing.T) {
|
|
206
|
+
isolate(t)
|
|
207
|
+
r := repository(t)
|
|
208
|
+
cache := t.TempDir()
|
|
209
|
+
online, err := fetch(options(r, cache, "services/oms"))
|
|
210
|
+
if err != nil {
|
|
211
|
+
t.Fatal(err)
|
|
212
|
+
}
|
|
213
|
+
write(t, cache, online)
|
|
214
|
+
|
|
215
|
+
var edited string
|
|
216
|
+
for _, name := range names(online) {
|
|
217
|
+
if strings.HasSuffix(name, "order.go") {
|
|
218
|
+
edited = name
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
if err := os.WriteFile(filepath.Join(cache, filepath.FromSlash(edited)), []byte("package order // edited\n"), 0o644); err != nil {
|
|
222
|
+
t.Fatal(err)
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
t.Setenv(OfflineEnv, "1")
|
|
226
|
+
_, err = fetch(options(r, cache, "services/oms"))
|
|
227
|
+
if err == nil || !strings.Contains(err.Error(), "order.go") || !strings.Contains(err.Error(), "edited by hand") {
|
|
228
|
+
t.Fatalf("err = %v, want the edited file named", err)
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
func TestOfflineWithNoCacheFailsRatherThanEmittingNothing(t *testing.T) {
|
|
233
|
+
isolate(t)
|
|
234
|
+
t.Setenv(OfflineEnv, "1")
|
|
235
|
+
resp, err := fetch(options(remote{url: "file:///nowhere/acme/shop", commit: "0000000000000000000000000000000000000000"}, t.TempDir()))
|
|
236
|
+
if err == nil || len(resp.Files) != 0 {
|
|
237
|
+
t.Fatalf("err = %v, files = %v", err, names(resp))
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
func TestFailedFetchWithNoCacheFails(t *testing.T) {
|
|
242
|
+
isolate(t)
|
|
243
|
+
_, err := fetch(options(remote{url: "file://" + filepath.Join(t.TempDir(), "acme", "gone"), commit: "0000000000000000000000000000000000000000"}, t.TempDir()))
|
|
244
|
+
if err == nil || !strings.Contains(err.Error(), "no usable copy") {
|
|
245
|
+
t.Fatalf("err = %v", err)
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
func TestFailedFetchFallsBackToTheCommittedCopy(t *testing.T) {
|
|
250
|
+
isolate(t)
|
|
251
|
+
r := repository(t)
|
|
252
|
+
cache := t.TempDir()
|
|
253
|
+
online, err := fetch(options(r, cache, "proto"))
|
|
254
|
+
if err != nil {
|
|
255
|
+
t.Fatal(err)
|
|
256
|
+
}
|
|
257
|
+
write(t, cache, online)
|
|
258
|
+
|
|
259
|
+
// The forge is gone; the URL is the same, so the copy is in the same place.
|
|
260
|
+
if err := os.RemoveAll(strings.TrimPrefix(r.url, "file://")); err != nil {
|
|
261
|
+
t.Fatal(err)
|
|
262
|
+
}
|
|
263
|
+
fallback, err := fetch(options(r, cache, "proto"))
|
|
264
|
+
if err != nil {
|
|
265
|
+
t.Fatal(err)
|
|
266
|
+
}
|
|
267
|
+
if strings.Join(names(fallback), "\n") != strings.Join(names(online), "\n") {
|
|
268
|
+
t.Errorf("fallback = %v", names(fallback))
|
|
269
|
+
}
|
|
270
|
+
if len(fallback.Warnings()) != 1 || !strings.Contains(fallback.Warnings()[0].Message, "not fetched") {
|
|
271
|
+
t.Errorf("diagnostics = %+v", fallback.Warnings())
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
func TestUnpinnedRepositoryIsResolvedOnlineAndRefusedOffline(t *testing.T) {
|
|
276
|
+
isolate(t)
|
|
277
|
+
r := repository(t)
|
|
278
|
+
opts := Options{Cache: t.TempDir(), Repos: []Repo{{Repo: r.url, Ref: "main", Paths: []string{"proto"}}}}
|
|
279
|
+
|
|
280
|
+
resp, err := fetch(opts)
|
|
281
|
+
if err != nil {
|
|
282
|
+
t.Fatal(err)
|
|
283
|
+
}
|
|
284
|
+
if len(resp.Warnings()) != 1 || !strings.Contains(resp.Warnings()[0].Message, "not pinned") || !strings.Contains(resp.Warnings()[0].Message, r.commit) {
|
|
285
|
+
t.Errorf("diagnostics = %+v", resp.Warnings())
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
t.Setenv(OfflineEnv, "1")
|
|
289
|
+
if _, err := fetch(opts); err == nil || !strings.Contains(err.Error(), "not pinned") {
|
|
290
|
+
t.Fatalf("err = %v", err)
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
// A remote that does not serve commits by name still serves its branches,
|
|
295
|
+
// and the commit is found among them.
|
|
296
|
+
func TestACommitIsFetchedThroughItsBranchWhenTheRemoteRefusesIt(t *testing.T) {
|
|
297
|
+
isolate(t)
|
|
298
|
+
r := repository(t)
|
|
299
|
+
cmd := exec.Command("git", "config", "--unset", "uploadpack.allowAnySHA1InWant")
|
|
300
|
+
cmd.Dir = strings.TrimPrefix(r.url, "file://")
|
|
301
|
+
if out, err := cmd.CombinedOutput(); err != nil {
|
|
302
|
+
t.Fatalf("%v: %s", err, out)
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
resp, err := fetch(options(r, t.TempDir(), "README.md"))
|
|
306
|
+
if err != nil {
|
|
307
|
+
t.Fatal(err)
|
|
308
|
+
}
|
|
309
|
+
// The file, the lock, and the fragment naming the commit.
|
|
310
|
+
if got := names(resp); len(got) != 3 {
|
|
311
|
+
t.Errorf("files = %v", got)
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
func TestSplitRepo(t *testing.T) {
|
|
316
|
+
cases := map[string]string{
|
|
317
|
+
"github.com/acme/shop": "https://github.com/acme/shop|acme/shop",
|
|
318
|
+
"https://gitlab.com/acme/shop.git": "https://gitlab.com/acme/shop.git|acme/shop",
|
|
319
|
+
"git@github.com:acme/shop.git": "git@github.com:acme/shop.git|acme/shop",
|
|
320
|
+
"ssh://git@github.com/acme/shop": "ssh://git@github.com/acme/shop|acme/shop",
|
|
321
|
+
"https://gitlab.com/org/group/shop": "https://gitlab.com/org/group/shop|group/shop",
|
|
322
|
+
}
|
|
323
|
+
for name, want := range cases {
|
|
324
|
+
url, dir, err := splitRepo(name)
|
|
325
|
+
if err != nil || url+"|"+dir != want {
|
|
326
|
+
t.Errorf("%s = %s|%s %v, want %s", name, url, dir, err, want)
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
if _, _, err := splitRepo("shop"); err == nil {
|
|
330
|
+
t.Error("a bare name has no owner and should be refused")
|
|
331
|
+
}
|
|
332
|
+
}
|
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
package main
|
|
2
|
+
|
|
3
|
+
// The only file that runs git.
|
|
4
|
+
//
|
|
5
|
+
// The host already needs a git binary to stamp fragments, so this leans on
|
|
6
|
+
// the same one rather than on a library: whatever git is configured to do
|
|
7
|
+
// about credentials and hosts, it does here too, and this plugin has no
|
|
8
|
+
// opinion about it. Everything happens in a directory that exists for one
|
|
9
|
+
// call: a fetch of the one commit, and an archive of the paths wanted, read
|
|
10
|
+
// straight into memory.
|
|
11
|
+
|
|
12
|
+
import (
|
|
13
|
+
"archive/tar"
|
|
14
|
+
"bytes"
|
|
15
|
+
"errors"
|
|
16
|
+
"fmt"
|
|
17
|
+
"io"
|
|
18
|
+
"os"
|
|
19
|
+
"os/exec"
|
|
20
|
+
"path"
|
|
21
|
+
"strings"
|
|
22
|
+
)
|
|
23
|
+
|
|
24
|
+
// splitRepo turns the name the manifest uses into a URL git can fetch and
|
|
25
|
+
// the directory the copy lives in: owner/name, the same shape as fetch-bsr.
|
|
26
|
+
func splitRepo(name string) (url, dir string, err error) {
|
|
27
|
+
name = strings.TrimSpace(name)
|
|
28
|
+
if name == "" {
|
|
29
|
+
return "", "", fmt.Errorf("a repo entry names no repository")
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
var where string
|
|
33
|
+
switch {
|
|
34
|
+
case strings.HasPrefix(name, "git@"):
|
|
35
|
+
// git@host:owner/name
|
|
36
|
+
url = name
|
|
37
|
+
_, where, _ = strings.Cut(name, ":")
|
|
38
|
+
case strings.Contains(name, "://"):
|
|
39
|
+
url = name
|
|
40
|
+
_, where, _ = strings.Cut(name, "://")
|
|
41
|
+
if i := strings.Index(where, "/"); i >= 0 {
|
|
42
|
+
where = where[i+1:]
|
|
43
|
+
}
|
|
44
|
+
default:
|
|
45
|
+
// host/owner/name, the way go.mod spells it.
|
|
46
|
+
url = "https://" + name
|
|
47
|
+
if i := strings.Index(name, "/"); i >= 0 {
|
|
48
|
+
where = name[i+1:]
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
where = strings.TrimSuffix(strings.Trim(where, "/"), ".git")
|
|
53
|
+
segments := strings.Split(where, "/")
|
|
54
|
+
if len(segments) < 2 {
|
|
55
|
+
return "", "", fmt.Errorf("%q does not name an owner and a repository", name)
|
|
56
|
+
}
|
|
57
|
+
dir = path.Join(segments[len(segments)-2], segments[len(segments)-1])
|
|
58
|
+
|
|
59
|
+
return url, dir, nil
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
// git runs one command and answers with its output, or with what it printed
|
|
63
|
+
// on stderr when it failed. Prompts are off: a fetch that needs a credential
|
|
64
|
+
// git does not have is a failure to report, not a question to hang on.
|
|
65
|
+
func git(dir string, args ...string) ([]byte, error) {
|
|
66
|
+
cmd := exec.Command("git", args...)
|
|
67
|
+
cmd.Dir = dir
|
|
68
|
+
cmd.Env = append(os.Environ(), "GIT_TERMINAL_PROMPT=0")
|
|
69
|
+
var stderr bytes.Buffer
|
|
70
|
+
cmd.Stderr = &stderr
|
|
71
|
+
out, err := cmd.Output()
|
|
72
|
+
if err != nil {
|
|
73
|
+
msg := strings.TrimSpace(stderr.String())
|
|
74
|
+
if msg == "" {
|
|
75
|
+
msg = err.Error()
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
return nil, errors.New("git " + args[0] + ": " + firstLine(msg))
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
return out, nil
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
func firstLine(s string) string {
|
|
85
|
+
if i := strings.IndexByte(s, '\n'); i >= 0 {
|
|
86
|
+
return s[:i]
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
return s
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
// resolve asks the remote what a ref points at right now.
|
|
93
|
+
func resolve(url, ref string) (string, error) {
|
|
94
|
+
args := []string{"ls-remote", "--quiet", url}
|
|
95
|
+
if ref != "" {
|
|
96
|
+
args = append(args, ref)
|
|
97
|
+
} else {
|
|
98
|
+
args = append(args, "HEAD")
|
|
99
|
+
}
|
|
100
|
+
out, err := git("", args...)
|
|
101
|
+
if err != nil {
|
|
102
|
+
return "", err
|
|
103
|
+
}
|
|
104
|
+
for _, line := range strings.Split(strings.TrimSpace(string(out)), "\n") {
|
|
105
|
+
fields := strings.Fields(line)
|
|
106
|
+
if len(fields) >= 1 && len(fields[0]) == 40 {
|
|
107
|
+
return fields[0], nil
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
return "", fmt.Errorf("%s has no ref %q", url, firstNonEmpty(ref, "HEAD"))
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
// download fetches one commit and reads the wanted paths out of it.
|
|
115
|
+
//
|
|
116
|
+
// The commit is fetched by name where the forge allows it - GitHub and GitLab
|
|
117
|
+
// do - and by its branches where it does not, which is the case for a plain
|
|
118
|
+
// file:// remote; either way what is read is verified to be that commit.
|
|
119
|
+
func download(url, commit string, paths []string) (map[string][]byte, error) {
|
|
120
|
+
tmp, err := os.MkdirTemp("", "portolan-fetch-git-")
|
|
121
|
+
if err != nil {
|
|
122
|
+
return nil, err
|
|
123
|
+
}
|
|
124
|
+
defer os.RemoveAll(tmp)
|
|
125
|
+
|
|
126
|
+
if _, err := git(tmp, "init", "--quiet"); err != nil {
|
|
127
|
+
return nil, err
|
|
128
|
+
}
|
|
129
|
+
if _, err := git(tmp, "fetch", "--quiet", "--depth", "1", url, commit); err != nil {
|
|
130
|
+
if _, fallback := git(tmp, "fetch", "--quiet", "--depth", "1", url, "+refs/heads/*:refs/remotes/src/*"); fallback != nil {
|
|
131
|
+
return nil, err
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
if _, err := git(tmp, "cat-file", "-e", commit+"^{commit}"); err != nil {
|
|
135
|
+
return nil, fmt.Errorf("%s is not a commit %s has, or not one reachable from a branch", commit, url)
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
args := []string{"archive", "--format=tar", commit}
|
|
139
|
+
if len(paths) > 0 {
|
|
140
|
+
args = append(args, "--")
|
|
141
|
+
args = append(args, paths...)
|
|
142
|
+
}
|
|
143
|
+
archive, err := git(tmp, args...)
|
|
144
|
+
if err != nil {
|
|
145
|
+
return nil, err
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
files := map[string][]byte{}
|
|
149
|
+
reader := tar.NewReader(bytes.NewReader(archive))
|
|
150
|
+
for {
|
|
151
|
+
header, err := reader.Next()
|
|
152
|
+
if errors.Is(err, io.EOF) {
|
|
153
|
+
break
|
|
154
|
+
}
|
|
155
|
+
if err != nil {
|
|
156
|
+
return nil, err
|
|
157
|
+
}
|
|
158
|
+
if header.Typeflag != tar.TypeReg {
|
|
159
|
+
continue
|
|
160
|
+
}
|
|
161
|
+
content, err := io.ReadAll(reader)
|
|
162
|
+
if err != nil {
|
|
163
|
+
return nil, err
|
|
164
|
+
}
|
|
165
|
+
files[path.Clean(header.Name)] = content
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
return files, nil
|
|
169
|
+
}
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
package main
|
|
2
|
+
|
|
3
|
+
// What was fetched, so it can be checked without fetching again.
|
|
4
|
+
//
|
|
5
|
+
// One lock per repository directory rather than one for the whole step, so a
|
|
6
|
+
// vendored copy is self-describing: the files and the statement of where they
|
|
7
|
+
// came from sit together.
|
|
8
|
+
|
|
9
|
+
import (
|
|
10
|
+
"crypto/sha256"
|
|
11
|
+
"encoding/hex"
|
|
12
|
+
"encoding/json"
|
|
13
|
+
"sort"
|
|
14
|
+
)
|
|
15
|
+
|
|
16
|
+
// LockName is the file written beside each repository's copy.
|
|
17
|
+
const LockName = "git.lock.json"
|
|
18
|
+
|
|
19
|
+
type Lock struct {
|
|
20
|
+
Repos []LockRepo `json:"repos"`
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
type LockRepo struct {
|
|
24
|
+
Repo string `json:"repo"`
|
|
25
|
+
|
|
26
|
+
// Commit is what was actually fetched. A commit is immutable, so a pinned
|
|
27
|
+
// fetch is byte-reproducible and this is what makes replaying from disk
|
|
28
|
+
// equivalent to fetching again.
|
|
29
|
+
Commit string `json:"commit"`
|
|
30
|
+
|
|
31
|
+
// Paths is the narrowing the copy was made with, so a wider or narrower
|
|
32
|
+
// manifest is a change to the copy rather than a mystery.
|
|
33
|
+
Paths []string `json:"paths,omitempty"`
|
|
34
|
+
|
|
35
|
+
Files []LockEntry `json:"files"`
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
type LockEntry struct {
|
|
39
|
+
Path string `json:"path"`
|
|
40
|
+
|
|
41
|
+
// SHA256 is over the bytes as written. It is what turns a cache into
|
|
42
|
+
// something checkable: a vendored copy someone edited by hand stops
|
|
43
|
+
// matching, which is exactly the drift docs/adr/org.0001.md wants visible.
|
|
44
|
+
SHA256 string `json:"sha256"`
|
|
45
|
+
Size int `json:"size"`
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
func digestOf(content []byte) string {
|
|
49
|
+
sum := sha256.Sum256(content)
|
|
50
|
+
|
|
51
|
+
return hex.EncodeToString(sum[:])
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
// encode writes the lock the way every generated file in this repository is
|
|
55
|
+
// written: sorted, indented, newline-terminated. It is committed and compared
|
|
56
|
+
// by `gen:check`, so an unstable order would rewrite it on every run.
|
|
57
|
+
func (l Lock) encode() (string, error) {
|
|
58
|
+
for i := range l.Repos {
|
|
59
|
+
sort.Slice(l.Repos[i].Files, func(a, b int) bool {
|
|
60
|
+
return l.Repos[i].Files[a].Path < l.Repos[i].Files[b].Path
|
|
61
|
+
})
|
|
62
|
+
sort.Strings(l.Repos[i].Paths)
|
|
63
|
+
}
|
|
64
|
+
sort.Slice(l.Repos, func(a, b int) bool { return l.Repos[a].Repo < l.Repos[b].Repo })
|
|
65
|
+
|
|
66
|
+
out, err := json.MarshalIndent(l, "", " ")
|
|
67
|
+
if err != nil {
|
|
68
|
+
return "", err
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
return string(out) + "\n", nil
|
|
72
|
+
}
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
// Package main is portolan-fetch-git: a git repository on one side, a narrowed
|
|
2
|
+
// copy of it committed to this repository on the other.
|
|
3
|
+
//
|
|
4
|
+
// It exists for the estate whose services live in repositories of their own.
|
|
5
|
+
// An extractor reads one tree, and reads it as a pure function of what is on
|
|
6
|
+
// disk; fetching is the step that can fail, need a credential, or come back
|
|
7
|
+
// with something different than it did yesterday. Keeping the two apart is
|
|
8
|
+
// what lets CI verify an estate it never clones.
|
|
9
|
+
//
|
|
10
|
+
// The fetched files are the plugin's Response.Files, not a side effect. The
|
|
11
|
+
// host writes them, so they get a manifest entry, are compared by `gen:check`
|
|
12
|
+
// like any other generated file, and are removed when the step stops naming
|
|
13
|
+
// them. Paths inside the copy are the repository's own, so the extract step
|
|
14
|
+
// that follows points its `in` at the vendored service and reads it exactly
|
|
15
|
+
// as it would read the service's own checkout. Refreshing a pin then produces
|
|
16
|
+
// ONE pull request holding the pin bump, the source diff, the lock diff and
|
|
17
|
+
// the fragment diff - which is the review worth having.
|
|
18
|
+
//
|
|
19
|
+
// Two files land beside each copy. `git.lock.json` is for the next run of this
|
|
20
|
+
// step - the commit and every file's digest, which is what makes replaying it
|
|
21
|
+
// equivalent to fetching again. `git.repo.json` is for the estate: a catalog
|
|
22
|
+
// fragment naming the repository and the commit, which is the only way that
|
|
23
|
+
// fact reaches a page. Nothing downstream can work it out - a service says
|
|
24
|
+
// which repository it lives in, and an extractor reads a directory as a pure
|
|
25
|
+
// function of what is on disk - so without it a vendored service's source
|
|
26
|
+
// paths are text nobody can open. It has to be matched by `sources` in the
|
|
27
|
+
// manifest, and by SOURCE_GLOBS in src/data.ts, to be read.
|
|
28
|
+
//
|
|
29
|
+
// It is not wired into portolan.json until there is a second repository. The
|
|
30
|
+
// manifest it expects:
|
|
31
|
+
//
|
|
32
|
+
// {
|
|
33
|
+
// "sources": ["data/*.json", "vendor/repos/*/*/git.repo.json"],
|
|
34
|
+
// "plugins": [
|
|
35
|
+
// { "name": "git", "process": { "command": "go", "args": ["run", "./plugins/fetch-git"] } }
|
|
36
|
+
// ],
|
|
37
|
+
// "extract": [
|
|
38
|
+
// {
|
|
39
|
+
// "plugin": "git",
|
|
40
|
+
// "in": "vendor",
|
|
41
|
+
// "out": "vendor/repos",
|
|
42
|
+
// "options": {
|
|
43
|
+
// "cache": "vendor/repos",
|
|
44
|
+
// "repos": [
|
|
45
|
+
// {
|
|
46
|
+
// "repo": "github.com/acme/shop",
|
|
47
|
+
// "commit": "c1d2e3f4a5b6c7d8e9f0a1b2c3d4e5f6a7b8c9d0",
|
|
48
|
+
// "paths": ["services/oms", "proto"]
|
|
49
|
+
// }
|
|
50
|
+
// ]
|
|
51
|
+
// }
|
|
52
|
+
// },
|
|
53
|
+
// {
|
|
54
|
+
// "plugin": "go-domain",
|
|
55
|
+
// "in": "vendor/repos/acme/shop/services/oms",
|
|
56
|
+
// "out": "data/shop",
|
|
57
|
+
// "options": { "context": "shop", "service": "oms", "store": "pg" }
|
|
58
|
+
// }
|
|
59
|
+
// ]
|
|
60
|
+
// }
|
|
61
|
+
//
|
|
62
|
+
// The git step is declared FIRST: extract steps run in list order, so the copy
|
|
63
|
+
// and its lock are on disk by the time the extractors read them.
|
|
64
|
+
//
|
|
65
|
+
// In CI, set PORTOLAN_OFFLINE=1. The step then replays the committed copies,
|
|
66
|
+
// checks them against their locks and emits an identical file list, so a fork's
|
|
67
|
+
// pull request needs no credential and a forge being down cannot turn the tree
|
|
68
|
+
// red.
|
|
69
|
+
package main
|
|
70
|
+
|
|
71
|
+
import (
|
|
72
|
+
"fmt"
|
|
73
|
+
"io"
|
|
74
|
+
"os"
|
|
75
|
+
|
|
76
|
+
"github.com/shortlink-org/portolan/plugin"
|
|
77
|
+
)
|
|
78
|
+
|
|
79
|
+
// Options are what the manifest tells the fetcher.
|
|
80
|
+
//
|
|
81
|
+
// No credential, ever. portolan.json is committed; git reaches the forge with
|
|
82
|
+
// whatever it is configured with - a credential helper, a netrc entry, an ssh
|
|
83
|
+
// agent - and this plugin reads none of it.
|
|
84
|
+
type Options struct {
|
|
85
|
+
Repos []Repo `json:"repos"`
|
|
86
|
+
|
|
87
|
+
// Cache is where the last run's files were written, repo-relative, and
|
|
88
|
+
// should be the same path as the step's `out`.
|
|
89
|
+
//
|
|
90
|
+
// Stated twice because a plugin is never told where its output goes. That
|
|
91
|
+
// is the whole shape of the protocol - a plugin returns files and the host
|
|
92
|
+
// decides what to do with them - and not an oversight to work around.
|
|
93
|
+
Cache string `json:"cache"`
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
type Repo struct {
|
|
97
|
+
// Repo is where the repository is: "github.com/acme/shop", or any URL git
|
|
98
|
+
// accepts - https://, ssh://, git@host:owner/name.
|
|
99
|
+
Repo string `json:"repo"`
|
|
100
|
+
|
|
101
|
+
// Commit pins it. A commit is immutable, so a pinned fetch is
|
|
102
|
+
// byte-reproducible - which is the only reason replaying from disk is
|
|
103
|
+
// equivalent to fetching again. Leaving it out is allowed and warned about;
|
|
104
|
+
// offline it is refused, because there is nothing to replay against.
|
|
105
|
+
Commit string `json:"commit,omitempty"`
|
|
106
|
+
|
|
107
|
+
// Ref is the branch or tag to resolve when there is no commit. Defaults to
|
|
108
|
+
// the remote's HEAD.
|
|
109
|
+
Ref string `json:"ref,omitempty"`
|
|
110
|
+
|
|
111
|
+
// Paths narrows the copy to the directories actually read, which is how a
|
|
112
|
+
// vendored repository stays small. Left out, the whole tree.
|
|
113
|
+
Paths []string `json:"paths,omitempty"`
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
func main() {
|
|
117
|
+
if err := run(os.Stdin, os.Stdout); err != nil {
|
|
118
|
+
fmt.Fprintln(os.Stderr, "portolan-fetch-git:", err)
|
|
119
|
+
os.Exit(1)
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
func run(stdin io.Reader, stdout io.Writer) error {
|
|
124
|
+
return plugin.Serve(stdin, stdout, descriptor(), func(_ plugin.Request, opts Options) (plugin.Response, error) {
|
|
125
|
+
return fetch(opts)
|
|
126
|
+
})
|
|
127
|
+
}
|