@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,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-adr",
|
|
15
|
+
Summary: "Reads decision records written by hand - MADR markdown with a title, meta bullets and the record itself, or the numbered records adr-tools writes - into a catalog fragment.",
|
|
16
|
+
Phases: []string{plugin.PhaseExtract},
|
|
17
|
+
Options: optionsSchema,
|
|
18
|
+
}
|
|
19
|
+
}
|
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
package main
|
|
2
|
+
|
|
3
|
+
import (
|
|
4
|
+
"encoding/json"
|
|
5
|
+
"errors"
|
|
6
|
+
"os"
|
|
7
|
+
"path/filepath"
|
|
8
|
+
"sort"
|
|
9
|
+
"strings"
|
|
10
|
+
|
|
11
|
+
"github.com/shortlink-org/portolan/catalog"
|
|
12
|
+
"github.com/shortlink-org/portolan/plugin"
|
|
13
|
+
)
|
|
14
|
+
|
|
15
|
+
// extract reads every record the options name and answers with one fragment
|
|
16
|
+
// holding them.
|
|
17
|
+
//
|
|
18
|
+
// A file that does not parse, or that declares a record another file already
|
|
19
|
+
// declared, is left out of the fragment with a warning that names it and the
|
|
20
|
+
// line, and the rest of the tree is read: one record written differently is
|
|
21
|
+
// not a reason to document nothing. What is refused whole is a supersession
|
|
22
|
+
// with only one half recorded, because `src/catalog.ts` refuses to load a
|
|
23
|
+
// catalog that says it, and either record alone would say something untrue.
|
|
24
|
+
func extract(in plugin.Input, opts Options) (plugin.Response, error) {
|
|
25
|
+
b := &plugin.Builder{}
|
|
26
|
+
|
|
27
|
+
patterns := opts.Files
|
|
28
|
+
if len(patterns) == 0 {
|
|
29
|
+
patterns = []string{"docs/adr/*.md"}
|
|
30
|
+
}
|
|
31
|
+
var files []string
|
|
32
|
+
seen := map[string]bool{}
|
|
33
|
+
for _, pattern := range patterns {
|
|
34
|
+
matches, err := filepath.Glob(filepath.Join(in.Root, pattern))
|
|
35
|
+
if err != nil {
|
|
36
|
+
return plugin.Response{}, err
|
|
37
|
+
}
|
|
38
|
+
for _, match := range matches {
|
|
39
|
+
// A README in a directory of records is its index - written by
|
|
40
|
+
// hand or generated elsewhere - and is not itself a decision.
|
|
41
|
+
if strings.EqualFold(filepath.Base(match), "README.md") {
|
|
42
|
+
continue
|
|
43
|
+
}
|
|
44
|
+
if !seen[match] {
|
|
45
|
+
seen[match] = true
|
|
46
|
+
files = append(files, match)
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
sort.Strings(files)
|
|
51
|
+
if len(files) == 0 {
|
|
52
|
+
b.Warn(in.Root, "no decision records matched "+strings.Join(patterns, ", ")+"; the fragment holds none")
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
history := newHistory(in.Root, opts.History, b)
|
|
56
|
+
|
|
57
|
+
adrs := []catalog.Adr{}
|
|
58
|
+
ids := map[string]string{}
|
|
59
|
+
slugs := map[string]string{}
|
|
60
|
+
for _, file := range files {
|
|
61
|
+
src, err := os.ReadFile(file)
|
|
62
|
+
if err != nil {
|
|
63
|
+
return plugin.Response{}, err
|
|
64
|
+
}
|
|
65
|
+
rel := filepath.ToSlash(file)
|
|
66
|
+
adr, errs := parseAdr(rel, string(src), defaults{Scope: opts.Scope})
|
|
67
|
+
if len(errs) > 0 {
|
|
68
|
+
b.Warn(rel, "left out of the fragment: "+strings.Join(errs, "; "))
|
|
69
|
+
|
|
70
|
+
continue
|
|
71
|
+
}
|
|
72
|
+
if other, taken := ids[adr.ID]; taken {
|
|
73
|
+
b.Warn(rel, "left out of the fragment: "+adr.ID+" is already declared in "+other)
|
|
74
|
+
|
|
75
|
+
continue
|
|
76
|
+
}
|
|
77
|
+
if other, taken := slugs[adr.Slug]; taken {
|
|
78
|
+
b.Warn(rel, "left out of the fragment: the slug "+adr.Slug+" is already taken by "+other)
|
|
79
|
+
|
|
80
|
+
continue
|
|
81
|
+
}
|
|
82
|
+
ids[adr.ID] = rel
|
|
83
|
+
slugs[adr.Slug] = rel
|
|
84
|
+
adr.Created, adr.Revised = history.of(file)
|
|
85
|
+
adrs = append(adrs, adr)
|
|
86
|
+
}
|
|
87
|
+
if problems := supersessions(adrs, ids); len(problems) > 0 {
|
|
88
|
+
return plugin.Response{}, errors.New(strings.Join(problems, "\n"))
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
fragment := catalog.Catalog{
|
|
92
|
+
GeneratedAt: in.GeneratedAt,
|
|
93
|
+
Commit: in.Commit,
|
|
94
|
+
Contexts: []catalog.BoundedContext{},
|
|
95
|
+
Defs: map[string]catalog.TypeDef{},
|
|
96
|
+
Flows: []catalog.Flow{},
|
|
97
|
+
Adrs: adrs,
|
|
98
|
+
}
|
|
99
|
+
encoded, err := json.MarshalIndent(fragment, "", " ")
|
|
100
|
+
if err != nil {
|
|
101
|
+
return plugin.Response{}, err
|
|
102
|
+
}
|
|
103
|
+
out := opts.Out
|
|
104
|
+
if out == "" {
|
|
105
|
+
out = "adr.json"
|
|
106
|
+
}
|
|
107
|
+
b.File(out, string(encoded)+"\n")
|
|
108
|
+
|
|
109
|
+
return b.Response(), nil
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
// supersessions holds the two halves of a supersession against each other.
|
|
113
|
+
//
|
|
114
|
+
// Only the halves that are both here can be checked: a record superseded by
|
|
115
|
+
// one in another service's tree is a claim this step has no way to look up,
|
|
116
|
+
// and the validator, which sees the merged catalog, reports that one. What
|
|
117
|
+
// this catches is the common case - a record superseded by its neighbour,
|
|
118
|
+
// with only one of the two files edited.
|
|
119
|
+
func supersessions(adrs []catalog.Adr, ids map[string]string) []string {
|
|
120
|
+
var problems []string
|
|
121
|
+
|
|
122
|
+
by := map[string]*catalog.Adr{}
|
|
123
|
+
for i := range adrs {
|
|
124
|
+
by[adrs[i].ID] = &adrs[i]
|
|
125
|
+
}
|
|
126
|
+
names := func(list []string, id string) bool {
|
|
127
|
+
for _, entry := range list {
|
|
128
|
+
if entry == id {
|
|
129
|
+
return true
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
return false
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
for i := range adrs {
|
|
137
|
+
adr := &adrs[i]
|
|
138
|
+
where := ids[adr.ID] + ": "
|
|
139
|
+
if successor, here := by[adr.SupersededBy]; here && !names(successor.Supersedes, adr.ID) {
|
|
140
|
+
problems = append(problems, where+adr.ID+" is superseded by "+successor.ID+", which does not say it supersedes it")
|
|
141
|
+
}
|
|
142
|
+
for _, id := range adr.Supersedes {
|
|
143
|
+
if predecessor, here := by[id]; here && predecessor.SupersededBy != adr.ID {
|
|
144
|
+
problems = append(problems, where+adr.ID+" supersedes "+id+", which is not marked superseded by it")
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
if adr.SupersededBy == adr.ID {
|
|
148
|
+
problems = append(problems, where+adr.ID+" supersedes itself")
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
return problems
|
|
153
|
+
}
|
|
@@ -0,0 +1,350 @@
|
|
|
1
|
+
package main
|
|
2
|
+
|
|
3
|
+
// The mapping: records in a tree, a catalog fragment out.
|
|
4
|
+
//
|
|
5
|
+
// Shaped like plugins/extract-proto/extract_test.go, which is the house style
|
|
6
|
+
// for an extractor test - one behaviour per test, and a whole-fragment golden
|
|
7
|
+
// underneath so an unintended field change is a diff rather than an assertion
|
|
8
|
+
// nobody thought to write.
|
|
9
|
+
|
|
10
|
+
import (
|
|
11
|
+
"encoding/json"
|
|
12
|
+
"flag"
|
|
13
|
+
"os"
|
|
14
|
+
"os/exec"
|
|
15
|
+
"path/filepath"
|
|
16
|
+
"strconv"
|
|
17
|
+
"strings"
|
|
18
|
+
"testing"
|
|
19
|
+
|
|
20
|
+
"github.com/shortlink-org/portolan/catalog"
|
|
21
|
+
"github.com/shortlink-org/portolan/plugin"
|
|
22
|
+
)
|
|
23
|
+
|
|
24
|
+
var update = flag.Bool("update", false, "rewrite the golden fragment instead of comparing against it")
|
|
25
|
+
|
|
26
|
+
const goldenPath = "testdata/golden/adr.json"
|
|
27
|
+
|
|
28
|
+
func input(root string) plugin.Input {
|
|
29
|
+
// The stamp comes from the host, which derives it from git. A plugin that
|
|
30
|
+
// read a clock would write a different file every run.
|
|
31
|
+
return plugin.Input{
|
|
32
|
+
Root: root,
|
|
33
|
+
Commit: "abc1234",
|
|
34
|
+
GeneratedAt: "2024-01-01T00:00:00Z",
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
func response(t *testing.T) plugin.Response {
|
|
39
|
+
t.Helper()
|
|
40
|
+
|
|
41
|
+
resp, err := extract(input("testdata/estate"), Options{History: "none"})
|
|
42
|
+
if err != nil {
|
|
43
|
+
t.Fatal(err)
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
return resp
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
func fragment(t *testing.T) catalog.Catalog {
|
|
50
|
+
t.Helper()
|
|
51
|
+
|
|
52
|
+
var cat catalog.Catalog
|
|
53
|
+
if err := json.Unmarshal([]byte(response(t).Files[0].Contents), &cat); err != nil {
|
|
54
|
+
t.Fatal(err)
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
return cat
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
func TestEveryRecordInTheTreeIsRead(t *testing.T) {
|
|
61
|
+
var ids []string
|
|
62
|
+
for _, adr := range fragment(t).Adrs {
|
|
63
|
+
ids = append(ids, adr.ID)
|
|
64
|
+
}
|
|
65
|
+
if strings.Join(ids, " ") != "acme.0001 payments.0002 acme.0003 org.0004" {
|
|
66
|
+
t.Errorf("ids = %v", ids)
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
// A README in a directory of records is its index, not a decision. Reading it
|
|
71
|
+
// as one would put a table of contents on the site as a record with no status.
|
|
72
|
+
func TestAnIndexIsNotARecord(t *testing.T) {
|
|
73
|
+
for _, adr := range fragment(t).Adrs {
|
|
74
|
+
if strings.HasSuffix(adr.Source, "README.md") {
|
|
75
|
+
t.Errorf("%q was read as a record", adr.Source)
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
// The fragment is one source among several, so it carries the empty lists of
|
|
81
|
+
// everything it says nothing about rather than leaving the merge to guess.
|
|
82
|
+
func TestTheFragmentSaysNothingItDidNotRead(t *testing.T) {
|
|
83
|
+
cat := fragment(t)
|
|
84
|
+
if len(cat.Contexts) != 0 || len(cat.Flows) != 0 || len(cat.Defs) != 0 {
|
|
85
|
+
t.Errorf("the fragment claims more than its records: %+v", cat)
|
|
86
|
+
}
|
|
87
|
+
if cat.Commit != "abc1234" || cat.GeneratedAt != "2024-01-01T00:00:00Z" {
|
|
88
|
+
t.Errorf("stamp = %q %q", cat.Commit, cat.GeneratedAt)
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
func TestTheFileIsNamedByTheOptions(t *testing.T) {
|
|
93
|
+
resp, err := extract(input("testdata/estate"), Options{History: "none", Out: "decisions.json"})
|
|
94
|
+
if err != nil {
|
|
95
|
+
t.Fatal(err)
|
|
96
|
+
}
|
|
97
|
+
if resp.Files[0].Name != "decisions.json" {
|
|
98
|
+
t.Errorf("name = %q", resp.Files[0].Name)
|
|
99
|
+
}
|
|
100
|
+
if response(t).Files[0].Name != "adr.json" {
|
|
101
|
+
t.Errorf("the default is not adr.json")
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
// A root with no records is worth saying out loud. A step pointed at the wrong
|
|
106
|
+
// directory answers with an empty fragment either way, and the warning is the
|
|
107
|
+
// only thing that tells the two apart.
|
|
108
|
+
func TestARootWithNoRecordsWarns(t *testing.T) {
|
|
109
|
+
resp, err := extract(input("testdata"), Options{History: "none"})
|
|
110
|
+
if err != nil {
|
|
111
|
+
t.Fatal(err)
|
|
112
|
+
}
|
|
113
|
+
if !warned(resp, "no decision records matched") {
|
|
114
|
+
t.Errorf("diagnostics = %+v", resp.Warnings())
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
// A supersession with one half recorded is refused whole: src/catalog.ts
|
|
119
|
+
// fails the app on it, and either record alone would say something untrue.
|
|
120
|
+
func TestAHalfRecordedSupersessionIsRefusedWhole(t *testing.T) {
|
|
121
|
+
resp, err := extract(input("testdata/broken"), Options{History: "none"})
|
|
122
|
+
if err == nil {
|
|
123
|
+
t.Fatal("the broken tree produced a fragment")
|
|
124
|
+
}
|
|
125
|
+
if len(resp.Files) != 0 {
|
|
126
|
+
t.Errorf("files were named anyway: %+v", resp.Files)
|
|
127
|
+
}
|
|
128
|
+
if want := "acme.0001 is superseded by acme.0002, which does not say it supersedes it"; !strings.Contains(err.Error(), want) {
|
|
129
|
+
t.Errorf("no %q in:\n%s", want, err)
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
// A tree kept with adr-tools, with the mistakes such a tree collects: two
|
|
134
|
+
// records numbered the same by two branches, and a file of notes among them.
|
|
135
|
+
// Each is left out with a warning that names it, and the rest is read.
|
|
136
|
+
func TestATreeOfAdrToolsRecordsIsReadAndItsMistakesAreLeftOut(t *testing.T) {
|
|
137
|
+
resp, err := extract(input("testdata/lenient"), Options{Scope: "avia.aviasupp", Files: []string{"docs/adr/*.md"}, History: "none"})
|
|
138
|
+
if err != nil {
|
|
139
|
+
t.Fatal(err)
|
|
140
|
+
}
|
|
141
|
+
var cat catalog.Catalog
|
|
142
|
+
if err := json.Unmarshal([]byte(resp.Files[0].Contents), &cat); err != nil {
|
|
143
|
+
t.Fatal(err)
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
var ids []string
|
|
147
|
+
for _, adr := range cat.Adrs {
|
|
148
|
+
ids = append(ids, adr.ID)
|
|
149
|
+
}
|
|
150
|
+
if strings.Join(ids, " ") != "aviasupp.0001 aviasupp.0003 aviasupp.0004 aviasupp.0005 acme.0007" {
|
|
151
|
+
t.Errorf("ids = %v", ids)
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
for _, want := range []string{
|
|
155
|
+
"0004-use-river.md: left out of the fragment: aviasupp.0004 is already declared in",
|
|
156
|
+
"0006-a-note-that-is-not-a-record.md: left out of the fragment: ",
|
|
157
|
+
"0007-declared-once.md: left out of the fragment: acme.0007 is already declared in",
|
|
158
|
+
} {
|
|
159
|
+
if !warnedAt(resp, want) {
|
|
160
|
+
t.Errorf("no warning %q among %+v", want, resp.Warnings())
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
by := map[string]catalog.Adr{}
|
|
165
|
+
for _, adr := range cat.Adrs {
|
|
166
|
+
by[adr.ID] = adr
|
|
167
|
+
}
|
|
168
|
+
if dto := by["aviasupp.0003"]; dto.Status != catalog.AdrSuperseded || dto.SupersededBy != "aviasupp.0005" {
|
|
169
|
+
t.Errorf("aviasupp.0003 = %+v", dto)
|
|
170
|
+
}
|
|
171
|
+
if schemas := by["aviasupp.0005"]; strings.Join(schemas.Supersedes, " ") != "aviasupp.0003" {
|
|
172
|
+
t.Errorf("aviasupp.0005 supersedes %v", schemas.Supersedes)
|
|
173
|
+
}
|
|
174
|
+
if action := by["aviasupp.0004"]; action.Status != catalog.AdrProposed || action.Title != "Call action" {
|
|
175
|
+
t.Errorf("aviasupp.0004 = %+v", action)
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
func warnedAt(resp plugin.Response, substring string) bool {
|
|
180
|
+
for _, d := range resp.Warnings() {
|
|
181
|
+
if strings.Contains(d.Ref+": "+d.Message, substring) {
|
|
182
|
+
return true
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
return false
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
// The fragment is committed and compared by gen:check. A map iterated in Go's
|
|
190
|
+
// order would rewrite the file every run and turn every build into a diff.
|
|
191
|
+
func TestOutputIsByteIdentical(t *testing.T) {
|
|
192
|
+
first, err := extract(input("testdata/estate"), Options{History: "none"})
|
|
193
|
+
if err != nil {
|
|
194
|
+
t.Fatal(err)
|
|
195
|
+
}
|
|
196
|
+
for i := 0; i < 5; i++ {
|
|
197
|
+
again, err := extract(input("testdata/estate"), Options{History: "none"})
|
|
198
|
+
if err != nil {
|
|
199
|
+
t.Fatal(err)
|
|
200
|
+
}
|
|
201
|
+
if again.Files[0].Contents != first.Files[0].Contents {
|
|
202
|
+
t.Fatal("two runs over the same tree produced different fragments")
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
// The whole fragment, so a field that changes shape shows up as a diff rather
|
|
208
|
+
// than slipping past every assertion above.
|
|
209
|
+
func TestGoldenFragment(t *testing.T) {
|
|
210
|
+
got := response(t).Files[0].Contents
|
|
211
|
+
|
|
212
|
+
if *update {
|
|
213
|
+
if err := os.WriteFile(goldenPath, []byte(got), 0o644); err != nil {
|
|
214
|
+
t.Fatal(err)
|
|
215
|
+
}
|
|
216
|
+
t.Log("golden fragment rewritten")
|
|
217
|
+
|
|
218
|
+
return
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
want, err := os.ReadFile(goldenPath)
|
|
222
|
+
if err != nil {
|
|
223
|
+
t.Fatalf("reading the golden fragment: %v (run `go test ./plugins/extract-adr -update`)", err)
|
|
224
|
+
}
|
|
225
|
+
if string(want) != got {
|
|
226
|
+
t.Errorf("the fragment differs from the golden file\n%s", firstDifference(string(want), got))
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
func warned(resp plugin.Response, substring string) bool {
|
|
231
|
+
for _, d := range resp.Warnings() {
|
|
232
|
+
if strings.Contains(d.Message, substring) {
|
|
233
|
+
return true
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
return false
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
func firstDifference(want, got string) string {
|
|
241
|
+
a := strings.Split(want, "\n")
|
|
242
|
+
b := strings.Split(got, "\n")
|
|
243
|
+
for i := 0; i < len(a) && i < len(b); i++ {
|
|
244
|
+
if a[i] != b[i] {
|
|
245
|
+
return "line " + strconv.Itoa(i+1) + ":\n want: " + a[i] + "\n got: " + b[i]
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
return "want " + strconv.Itoa(len(a)) + " lines, got " + strconv.Itoa(len(b))
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
// Who wrote a record down and when comes from the file's own commits: the
|
|
253
|
+
// first commit is when it was created, the last when it was revised, and a
|
|
254
|
+
// file committed once has no revision. A tree that is not a checkout says
|
|
255
|
+
// nothing, once.
|
|
256
|
+
func TestWhoCommittedARecordComesFromGit(t *testing.T) {
|
|
257
|
+
root := t.TempDir()
|
|
258
|
+
run := func(args ...string) {
|
|
259
|
+
t.Helper()
|
|
260
|
+
cmd := exec.Command("git", append([]string{"-C", root}, args...)...)
|
|
261
|
+
cmd.Env = append(os.Environ(),
|
|
262
|
+
"GIT_AUTHOR_NAME=Ada Lovelace", "GIT_AUTHOR_EMAIL=ada@example.com",
|
|
263
|
+
"GIT_COMMITTER_NAME=Ada Lovelace", "GIT_COMMITTER_EMAIL=ada@example.com",
|
|
264
|
+
"GIT_COMMITTER_DATE=2026-01-01T09:00:00Z", "GIT_AUTHOR_DATE=2026-01-01T09:00:00Z",
|
|
265
|
+
)
|
|
266
|
+
if out, err := cmd.CombinedOutput(); err != nil {
|
|
267
|
+
t.Fatalf("git %v: %v\n%s", args, err, out)
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
write := func(name, contents string) {
|
|
271
|
+
t.Helper()
|
|
272
|
+
if err := os.MkdirAll(filepath.Join(root, "docs", "adr"), 0o755); err != nil {
|
|
273
|
+
t.Fatal(err)
|
|
274
|
+
}
|
|
275
|
+
if err := os.WriteFile(filepath.Join(root, "docs", "adr", name), []byte(contents), 0o644); err != nil {
|
|
276
|
+
t.Fatal(err)
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
record := func(title, body string) string {
|
|
280
|
+
return "# " + title + "\n\nDate: 2026-01-01\n\n## Status\n\nAccepted\n\n## Context\n\n" + body + "\n"
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
run("init", "-q")
|
|
284
|
+
write("0001-first.md", record("1. First", "As written."))
|
|
285
|
+
write("0002-second.md", record("2. Second", "As written."))
|
|
286
|
+
run("add", ".")
|
|
287
|
+
run("commit", "-q", "-m", "two records")
|
|
288
|
+
|
|
289
|
+
write("0002-second.md", record("2. Second", "Reworded."))
|
|
290
|
+
cmd := exec.Command("git", "-C", root, "commit", "-q", "-am", "reword")
|
|
291
|
+
cmd.Env = append(os.Environ(),
|
|
292
|
+
"GIT_AUTHOR_NAME=Grace Hopper", "GIT_AUTHOR_EMAIL=grace@example.com",
|
|
293
|
+
"GIT_COMMITTER_NAME=Grace Hopper", "GIT_COMMITTER_EMAIL=grace@example.com",
|
|
294
|
+
"GIT_COMMITTER_DATE=2026-01-03T17:30:00Z", "GIT_AUTHOR_DATE=2026-01-03T17:30:00Z",
|
|
295
|
+
)
|
|
296
|
+
if out, err := cmd.CombinedOutput(); err != nil {
|
|
297
|
+
t.Fatalf("git commit: %v\n%s", err, out)
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
resp, err := extract(input(root), Options{Scope: "org"})
|
|
301
|
+
if err != nil {
|
|
302
|
+
t.Fatal(err)
|
|
303
|
+
}
|
|
304
|
+
var cat catalog.Catalog
|
|
305
|
+
if err := json.Unmarshal([]byte(resp.Files[0].Contents), &cat); err != nil {
|
|
306
|
+
t.Fatal(err)
|
|
307
|
+
}
|
|
308
|
+
if len(cat.Adrs) != 2 {
|
|
309
|
+
t.Fatalf("adrs = %+v", cat.Adrs)
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
first, second := cat.Adrs[0], cat.Adrs[1]
|
|
313
|
+
if first.Created == nil || first.Created.Author != "Ada Lovelace" || first.Created.Date != "2026-01-01T09:00:00Z" || len(first.Created.Commit) != 40 {
|
|
314
|
+
t.Errorf("first created = %+v", first.Created)
|
|
315
|
+
}
|
|
316
|
+
if first.Revised != nil {
|
|
317
|
+
t.Errorf("a file committed once was revised: %+v", first.Revised)
|
|
318
|
+
}
|
|
319
|
+
if second.Created == nil || second.Created.Author != "Ada Lovelace" {
|
|
320
|
+
t.Errorf("second created = %+v", second.Created)
|
|
321
|
+
}
|
|
322
|
+
if second.Revised == nil || second.Revised.Author != "Grace Hopper" || second.Revised.Date != "2026-01-03T17:30:00Z" || second.Revised.Commit == second.Created.Commit {
|
|
323
|
+
t.Errorf("second revised = %+v", second.Revised)
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
// Told not to look, the extractor does not.
|
|
327
|
+
resp, err = extract(input(root), Options{Scope: "org", History: "none"})
|
|
328
|
+
if err != nil {
|
|
329
|
+
t.Fatal(err)
|
|
330
|
+
}
|
|
331
|
+
if strings.Contains(resp.Files[0].Contents, "\"created\"") {
|
|
332
|
+
t.Error("history \"none\" still read git")
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
// A tree with no checkout around it says so once and carries on.
|
|
336
|
+
bare := t.TempDir()
|
|
337
|
+
if err := os.MkdirAll(filepath.Join(bare, "docs", "adr"), 0o755); err != nil {
|
|
338
|
+
t.Fatal(err)
|
|
339
|
+
}
|
|
340
|
+
if err := os.WriteFile(filepath.Join(bare, "docs", "adr", "0001-first.md"), []byte(record("1. First", "As written.")), 0o644); err != nil {
|
|
341
|
+
t.Fatal(err)
|
|
342
|
+
}
|
|
343
|
+
resp, err = extract(input(bare), Options{Scope: "org"})
|
|
344
|
+
if err != nil {
|
|
345
|
+
t.Fatal(err)
|
|
346
|
+
}
|
|
347
|
+
if !warned(resp, "not inside a git checkout") {
|
|
348
|
+
t.Errorf("warnings = %+v", resp.Warnings())
|
|
349
|
+
}
|
|
350
|
+
}
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
package main
|
|
2
|
+
|
|
3
|
+
import (
|
|
4
|
+
"os/exec"
|
|
5
|
+
"path/filepath"
|
|
6
|
+
"strings"
|
|
7
|
+
|
|
8
|
+
"github.com/shortlink-org/portolan/catalog"
|
|
9
|
+
"github.com/shortlink-org/portolan/plugin"
|
|
10
|
+
)
|
|
11
|
+
|
|
12
|
+
// The markdown says when a decision was taken. Git says when it was written
|
|
13
|
+
// down and by whom, and when it was last changed - the other half of "who
|
|
14
|
+
// decided this", and one the author never has to type. Read from the file's
|
|
15
|
+
// own commits, following renames, so a record moved into docs/adr keeps the
|
|
16
|
+
// day it was first committed.
|
|
17
|
+
//
|
|
18
|
+
// This is the one place an extractor reads history rather than the tree. It
|
|
19
|
+
// is still a function of the checkout: the same commit gives the same answer
|
|
20
|
+
// on every machine, which is what lets the fragment be committed and checked.
|
|
21
|
+
|
|
22
|
+
type history struct {
|
|
23
|
+
root string
|
|
24
|
+
// repo is the working tree the root belongs to, or "" when there is none
|
|
25
|
+
// and every record is answered with nothing.
|
|
26
|
+
repo string
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
func newHistory(root, mode string, b *plugin.Builder) *history {
|
|
30
|
+
h := &history{root: root}
|
|
31
|
+
if mode == "none" {
|
|
32
|
+
return h
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
top, err := git(root, "rev-parse", "--show-toplevel")
|
|
36
|
+
if err != nil || top == "" {
|
|
37
|
+
b.Warn(root, "not inside a git checkout, so the records say nothing about who committed them; set `history` to \"none\" to stop hearing this")
|
|
38
|
+
|
|
39
|
+
return h
|
|
40
|
+
}
|
|
41
|
+
h.repo = top
|
|
42
|
+
|
|
43
|
+
return h
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// of answers with the commit that first added the file and the one that last
|
|
47
|
+
// touched it, or nothing when the file has no history - it is new, or the
|
|
48
|
+
// root is not a checkout.
|
|
49
|
+
func (h *history) of(file string) (created, revised *catalog.AdrCommit) {
|
|
50
|
+
if h.repo == "" {
|
|
51
|
+
return nil, nil
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
// The file is named from where the extractor runs; git is asked from the
|
|
55
|
+
// top of the checkout, so the path is made absolute rather than left to
|
|
56
|
+
// be read against a directory it was not written for.
|
|
57
|
+
abs, err := filepath.Abs(file)
|
|
58
|
+
if err != nil {
|
|
59
|
+
return nil, nil
|
|
60
|
+
}
|
|
61
|
+
out, err := git(h.repo, "log", "--follow", "--format=%H%x1f%an%x1f%cI", "--", abs)
|
|
62
|
+
if err != nil || out == "" {
|
|
63
|
+
return nil, nil
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
lines := strings.Split(out, "\n")
|
|
67
|
+
first := commitOf(lines[len(lines)-1])
|
|
68
|
+
last := commitOf(lines[0])
|
|
69
|
+
if first == nil {
|
|
70
|
+
return nil, nil
|
|
71
|
+
}
|
|
72
|
+
if last != nil && last.Commit != first.Commit {
|
|
73
|
+
return first, last
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
return first, nil
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
func commitOf(line string) *catalog.AdrCommit {
|
|
80
|
+
parts := strings.Split(line, "\x1f")
|
|
81
|
+
if len(parts) != 3 || parts[0] == "" {
|
|
82
|
+
return nil
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
return &catalog.AdrCommit{Commit: parts[0], Author: parts[1], Date: parts[2]}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
// git runs one command in the directory and answers with its trimmed output.
|
|
89
|
+
// Its stderr is not this run's diagnostics: a directory that is not a
|
|
90
|
+
// checkout is a fact newHistory reports once, in its own words.
|
|
91
|
+
func git(dir string, args ...string) (string, error) {
|
|
92
|
+
cmd := exec.Command("git", append([]string{"-C", filepath.Clean(dir)}, args...)...)
|
|
93
|
+
out, err := cmd.Output()
|
|
94
|
+
if err != nil {
|
|
95
|
+
return "", err
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
return strings.TrimSpace(string(out)), nil
|
|
99
|
+
}
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
// Command extract-adr reads decision records written by hand, as the MADR
|
|
2
|
+
// markdown they already are, and answers with a catalog fragment.
|
|
3
|
+
//
|
|
4
|
+
// A decision is written down once, in a file beside the code it constrains,
|
|
5
|
+
// and it is read there by whoever changed that code. Typing it a second time
|
|
6
|
+
// into the catalog's JSON - as an id, a slug, a number, a scope and a body
|
|
7
|
+
// with every newline escaped - makes the JSON the source and the markdown a
|
|
8
|
+
// copy, which is the arrow pointing the wrong way: the copy is the one that
|
|
9
|
+
// goes stale, and the three records that lived in `data/catalog.json` before
|
|
10
|
+
// this plugin existed all pointed at files that were never there.
|
|
11
|
+
//
|
|
12
|
+
// So the markdown is the source and the fragment is the output. What the
|
|
13
|
+
// format cannot say - which service a record belongs to, whether the record
|
|
14
|
+
// is still in force - it says in bullets under the title, and everything from
|
|
15
|
+
// the first `##` onward is the record itself, carried through untouched. The
|
|
16
|
+
// format is described in plugins/README.md and held to by parse_test.go.
|
|
17
|
+
package main
|
|
18
|
+
|
|
19
|
+
import (
|
|
20
|
+
"fmt"
|
|
21
|
+
"io"
|
|
22
|
+
"os"
|
|
23
|
+
|
|
24
|
+
"github.com/shortlink-org/portolan/plugin"
|
|
25
|
+
)
|
|
26
|
+
|
|
27
|
+
// Options are what the manifest tells the extractor.
|
|
28
|
+
type Options struct {
|
|
29
|
+
// Files are the record files to read, as globs relative to the input
|
|
30
|
+
// root. Left out, every `docs/adr/*.md`, which is where a service keeps
|
|
31
|
+
// them; an estate-wide step points its own glob somewhere else.
|
|
32
|
+
Files []string `json:"files,omitempty"`
|
|
33
|
+
|
|
34
|
+
// Scope is what a record is about when it does not say so itself: a
|
|
35
|
+
// service, `avia.aviasupp`, a context, or the organisation. A record
|
|
36
|
+
// written by adr-tools has no such field, and its id takes its prefix
|
|
37
|
+
// from here. A MADR record with a Scope bullet keeps its own.
|
|
38
|
+
Scope string `json:"scope,omitempty"`
|
|
39
|
+
|
|
40
|
+
// History says where a record's author and dates come from: "git", the
|
|
41
|
+
// default, reads the file's commits - who first added it and who last
|
|
42
|
+
// touched it; "none" reads nothing, for a tree that is not a checkout
|
|
43
|
+
// or a fragment that must not change when the history does.
|
|
44
|
+
History string `json:"history,omitempty"`
|
|
45
|
+
|
|
46
|
+
// Out names the fragment file.
|
|
47
|
+
Out string `json:"out,omitempty"`
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
func main() {
|
|
51
|
+
if err := run(os.Stdin, os.Stdout); err != nil {
|
|
52
|
+
fmt.Fprintln(os.Stderr, "portolan-extract-adr:", err)
|
|
53
|
+
os.Exit(1)
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
func run(stdin io.Reader, stdout io.Writer) error {
|
|
58
|
+
return plugin.Serve(stdin, stdout, descriptor(), func(req plugin.Request, opts Options) (plugin.Response, error) {
|
|
59
|
+
if req.Input.Root == "" {
|
|
60
|
+
return plugin.Response{}, fmt.Errorf("no input root: an extractor has nothing to read")
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
return extract(req.Input, opts)
|
|
64
|
+
})
|
|
65
|
+
}
|