@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
package/scripts/gen.mjs
ADDED
|
@@ -0,0 +1,603 @@
|
|
|
1
|
+
// Runs the generators named in portolan.json.
|
|
2
|
+
//
|
|
3
|
+
// node scripts/gen.mjs write the output
|
|
4
|
+
// node scripts/gen.mjs --check fail if the output on disk is not what the
|
|
5
|
+
// generators produce now
|
|
6
|
+
//
|
|
7
|
+
// The check mode is what makes generated documentation reviewable: the files
|
|
8
|
+
// are committed, so a change to them shows up in a diff like any other, and CI
|
|
9
|
+
// refuses a commit where they no longer follow from the catalog.
|
|
10
|
+
|
|
11
|
+
import {
|
|
12
|
+
lstatSync,
|
|
13
|
+
readdirSync,
|
|
14
|
+
readFileSync,
|
|
15
|
+
rmSync,
|
|
16
|
+
statSync,
|
|
17
|
+
} from "node:fs";
|
|
18
|
+
import { execFileSync } from "node:child_process";
|
|
19
|
+
import { createHash } from "node:crypto";
|
|
20
|
+
import { join, relative, resolve } from "node:path";
|
|
21
|
+
|
|
22
|
+
import {
|
|
23
|
+
addBuildStep,
|
|
24
|
+
createBuildReport,
|
|
25
|
+
finishBuildReport,
|
|
26
|
+
writeBuildReport,
|
|
27
|
+
} from "./build-report.mjs";
|
|
28
|
+
import { loadCatalog } from "./catalog-sources.mjs";
|
|
29
|
+
import { loadManifest, stepKeys } from "./manifest.mjs";
|
|
30
|
+
import { runPlugin } from "./plugin-host.mjs";
|
|
31
|
+
import { vendoredCommit } from "./vendor-lock.mjs";
|
|
32
|
+
import {
|
|
33
|
+
removeOutputFile,
|
|
34
|
+
safeOutputPath,
|
|
35
|
+
writeOutputFile,
|
|
36
|
+
} from "./output-path.mjs";
|
|
37
|
+
import { builtinPlugin } from "./builtin-plugins.mjs";
|
|
38
|
+
|
|
39
|
+
const PORTOLAN_VERSION = "0.1.0";
|
|
40
|
+
const EVENTS = process.env.PORTOLAN_EVENTS === "1";
|
|
41
|
+
const EVENT_PREFIX = "::portolan-event::";
|
|
42
|
+
|
|
43
|
+
function event(value) {
|
|
44
|
+
if (EVENTS) process.stdout.write(`${EVENT_PREFIX}${JSON.stringify(value)}\n`);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
// Every file a generator wrote last time is listed here, so a page that stops
|
|
48
|
+
// being generated is deleted instead of lingering as documentation of a service
|
|
49
|
+
// that no longer exists.
|
|
50
|
+
//
|
|
51
|
+
// The listing is keyed by step, not by directory. Two extractors writing
|
|
52
|
+
// fragments side by side into the same directory is the normal case - one knows
|
|
53
|
+
// the domain, the other the API - and a directory-wide list would have each of
|
|
54
|
+
// them delete the other's work on every run. What names a step is decided in
|
|
55
|
+
// manifest.mjs: its plugin, and the file it names when two steps of one plugin
|
|
56
|
+
// share a directory.
|
|
57
|
+
//
|
|
58
|
+
// A key that no step of this run answers to belonged to a step that was taken
|
|
59
|
+
// out of the manifest or renamed. Its files are as stale as any other, and they
|
|
60
|
+
// are removed once every step has run - not before, because a renamed step
|
|
61
|
+
// writes the same files under its new key, and a file removed and written back
|
|
62
|
+
// in one run is drift that never happened.
|
|
63
|
+
const MANIFEST = ".portolan-manifest";
|
|
64
|
+
|
|
65
|
+
const check = process.argv.includes("--check");
|
|
66
|
+
const manifestSha256 = createHash("sha256")
|
|
67
|
+
.update(readFileSync("portolan.json"))
|
|
68
|
+
.digest("hex");
|
|
69
|
+
const report = createBuildReport({
|
|
70
|
+
mode: check ? "check" : "write",
|
|
71
|
+
manifestSha256,
|
|
72
|
+
});
|
|
73
|
+
let manifest = {};
|
|
74
|
+
let keys = stepKeys({});
|
|
75
|
+
let drifted = false;
|
|
76
|
+
|
|
77
|
+
// What each step wrote into each directory on this run, by key. The listing
|
|
78
|
+
// on disk says what was written last time; in check mode it is never updated,
|
|
79
|
+
// so the sweep at the end reads this rather than the file.
|
|
80
|
+
const wroteThisRun = new Map();
|
|
81
|
+
|
|
82
|
+
// Directories already swept this run; a second pass would report the same
|
|
83
|
+
// removals again in check mode.
|
|
84
|
+
const swept = new Set();
|
|
85
|
+
|
|
86
|
+
try {
|
|
87
|
+
persistReport();
|
|
88
|
+
await generate();
|
|
89
|
+
finishBuildReport(report, drifted ? "drifted" : "ok");
|
|
90
|
+
persistReport();
|
|
91
|
+
event({ type: "run-finished", status: report.status, durationMs: report.durationMs });
|
|
92
|
+
if (drifted) {
|
|
93
|
+
console.error("\nGenerated documentation is out of date. Run `npm run gen`.");
|
|
94
|
+
process.exitCode = 1;
|
|
95
|
+
}
|
|
96
|
+
} catch (cause) {
|
|
97
|
+
finishBuildReport(report, "failed");
|
|
98
|
+
persistReport();
|
|
99
|
+
event({ type: "run-finished", status: "failed", durationMs: report.durationMs, message: cause instanceof Error ? cause.message : String(cause) });
|
|
100
|
+
console.error(`portolan gen: ${cause instanceof Error ? cause.message : String(cause)}`);
|
|
101
|
+
process.exitCode = 1;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
async function generate() {
|
|
105
|
+
// Checked before anything runs, against the schema the plugins describe. A
|
|
106
|
+
// step with a misspelled option would otherwise run to completion and write a
|
|
107
|
+
// fragment missing whatever that option was for.
|
|
108
|
+
const loaded = loadManifest("portolan.json");
|
|
109
|
+
manifest = loaded.manifest;
|
|
110
|
+
if (loaded.problems.length > 0) {
|
|
111
|
+
throw new Error(
|
|
112
|
+
`portolan.json does not match schema/portolan.schema.json:\n${loaded.problems
|
|
113
|
+
.map((problem) => ` ${problem}`)
|
|
114
|
+
.join("\n")}`,
|
|
115
|
+
);
|
|
116
|
+
}
|
|
117
|
+
keys = stepKeys(manifest);
|
|
118
|
+
event({ type: "pipeline-ready", stepCount: (manifest.extract?.length ?? 0) + (manifest.verify?.length ?? 0) + (manifest.generate?.length ?? 0) });
|
|
119
|
+
|
|
120
|
+
// Extractors run first and write catalog fragments; only then is there a
|
|
121
|
+
// catalog for anything else to read.
|
|
122
|
+
for (const step of manifest.extract ?? []) {
|
|
123
|
+
const plugin = pluginNamed(step.plugin);
|
|
124
|
+
const stamp = stampFor(step.in, step.out);
|
|
125
|
+
await executeStep("extract", step, `${step.plugin} ← ${step.in}`, async () =>
|
|
126
|
+
runPlugin(plugin, {
|
|
127
|
+
portolanVersion: PORTOLAN_VERSION,
|
|
128
|
+
input: { root: step.in, output: step.out, commit: stamp.commit, generatedAt: stamp.generatedAt },
|
|
129
|
+
options: step.options ?? {},
|
|
130
|
+
}),
|
|
131
|
+
);
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
// Verifiers read observed evidence against the merged catalog while leaving
|
|
135
|
+
// their own previous output out of that evidence - and what a dropped
|
|
136
|
+
// extract step wrote, which is still on disk because the sweep runs once
|
|
137
|
+
// every step has written. Read into this merge, a service nobody extracts
|
|
138
|
+
// any more would be written back into an overlay, and the overlay would
|
|
139
|
+
// carry it into the generators and into the next run.
|
|
140
|
+
for (const step of manifest.verify ?? []) {
|
|
141
|
+
const plugin = pluginNamed(step.plugin);
|
|
142
|
+
const stamp = stampFor(step.in, step.out);
|
|
143
|
+
const own = (previous(step.out)[keys.keyOf(step)] ?? []).map((name) => join(step.out, name));
|
|
144
|
+
const { catalog } = await loadSources({ exclude: [...own, ...staleAll()] });
|
|
145
|
+
await executeStep("verify", step, `${step.plugin} ⇐ ${step.in}`, async () =>
|
|
146
|
+
runPlugin(plugin, {
|
|
147
|
+
portolanVersion: PORTOLAN_VERSION,
|
|
148
|
+
input: { root: step.in, output: step.out, commit: stamp.commit, generatedAt: stamp.generatedAt },
|
|
149
|
+
catalog,
|
|
150
|
+
options: step.options ?? {},
|
|
151
|
+
}),
|
|
152
|
+
);
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
// What a dropped extract step wrote is not part of the catalog, so it is
|
|
156
|
+
// swept before the catalog is read - not after, when the generators would
|
|
157
|
+
// already have documented it once more.
|
|
158
|
+
sweepAll();
|
|
159
|
+
|
|
160
|
+
const { catalog, sources, conflicts } = await loadSources();
|
|
161
|
+
console.log(
|
|
162
|
+
`catalog: ${sources.length} source${sources.length === 1 ? "" : "s"} — ${sources
|
|
163
|
+
.map((source) => `${source.path} @ ${source.commit || "?"}`)
|
|
164
|
+
.join(", ")}`,
|
|
165
|
+
);
|
|
166
|
+
for (const conflict of conflicts) {
|
|
167
|
+
console.warn(` conflict ${conflict.where}: ${conflict.message}`);
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
for (const step of manifest.generate ?? []) {
|
|
171
|
+
const plugin = pluginNamed(step.plugin);
|
|
172
|
+
const generatedCatalog = step.catalog
|
|
173
|
+
? (await loadSources({ profile: step.catalog })).catalog
|
|
174
|
+
: catalog;
|
|
175
|
+
await executeStep("generate", step, `${step.plugin} → ${step.out}`, async () =>
|
|
176
|
+
runPlugin(plugin, {
|
|
177
|
+
portolanVersion: PORTOLAN_VERSION,
|
|
178
|
+
catalog: generatedCatalog,
|
|
179
|
+
options: step.options ?? {},
|
|
180
|
+
}),
|
|
181
|
+
);
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
sweepAll();
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
function sweepAll() {
|
|
188
|
+
for (const out of wroteThisRun.keys()) {
|
|
189
|
+
if (swept.has(out)) continue;
|
|
190
|
+
swept.add(out);
|
|
191
|
+
const changes = sweep(out, check);
|
|
192
|
+
if (changes.length > 0) {
|
|
193
|
+
drifted = summarise(`${out}: steps no longer in portolan.json`, [], changes) || drifted;
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
/** Every file the sweep would remove, in every directory a step writes into. */
|
|
199
|
+
function staleAll() {
|
|
200
|
+
const outs = new Set(
|
|
201
|
+
[...(manifest.extract ?? []), ...(manifest.verify ?? []), ...(manifest.generate ?? [])].map(
|
|
202
|
+
(step) => step.out,
|
|
203
|
+
),
|
|
204
|
+
);
|
|
205
|
+
const stale = [];
|
|
206
|
+
for (const out of outs) {
|
|
207
|
+
for (const name of staleIn(out)) stale.push(join(out, name));
|
|
208
|
+
}
|
|
209
|
+
return stale;
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
/**
|
|
213
|
+
* What the steps no longer in the manifest wrote into a directory and no live
|
|
214
|
+
* step claims. A file a dead key listed and a live step wrote again this run
|
|
215
|
+
* is not stale - it was renamed, not dropped - and a live step that has not
|
|
216
|
+
* run yet is taken at its previous listing.
|
|
217
|
+
*/
|
|
218
|
+
function staleIn(out) {
|
|
219
|
+
const listing = previous(out);
|
|
220
|
+
const live = keys.liveIn(out);
|
|
221
|
+
const dead = Object.keys(listing).filter((key) => !live.has(key));
|
|
222
|
+
if (dead.length === 0) return [];
|
|
223
|
+
|
|
224
|
+
const kept = new Set();
|
|
225
|
+
for (const key of live) {
|
|
226
|
+
for (const name of wroteThisRun.get(out)?.get(key) ?? listing[key] ?? []) kept.add(name);
|
|
227
|
+
}
|
|
228
|
+
const stale = [];
|
|
229
|
+
for (const key of dead) {
|
|
230
|
+
for (const name of listing[key]) {
|
|
231
|
+
if (kept.has(name)) continue;
|
|
232
|
+
kept.add(name);
|
|
233
|
+
stale.push(name);
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
return stale;
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
async function executeStep(phase, step, label, work) {
|
|
240
|
+
const startedAt = Date.now();
|
|
241
|
+
event({ type: "step-started", ordinal: report.steps.length, phase, plugin: step.plugin, input: step.in, output: step.out });
|
|
242
|
+
try {
|
|
243
|
+
const { files, warnings = [] } = await work();
|
|
244
|
+
const changes = apply(files, step.out, keys.keyOf(step), check);
|
|
245
|
+
const changed = summarise(label, files, changes, warnings);
|
|
246
|
+
drifted = changed || drifted;
|
|
247
|
+
const result = {
|
|
248
|
+
phase,
|
|
249
|
+
plugin: step.plugin,
|
|
250
|
+
...(step.in ? { input: step.in } : {}),
|
|
251
|
+
output: step.out,
|
|
252
|
+
status: changed ? "drifted" : changes.length > 0 ? "written" : "up-to-date",
|
|
253
|
+
durationMs: Date.now() - startedAt,
|
|
254
|
+
fileCount: files.length,
|
|
255
|
+
changedCount: changes.length,
|
|
256
|
+
changes,
|
|
257
|
+
files: files.map((file) => join(step.out, file.name)),
|
|
258
|
+
// What the plugin could not read, in its own words. Already printed as
|
|
259
|
+
// it ran; kept here so the Settings page can list it beside the step.
|
|
260
|
+
warnings,
|
|
261
|
+
};
|
|
262
|
+
addBuildStep(report, result);
|
|
263
|
+
persistReport();
|
|
264
|
+
event({ type: "step-finished", ordinal: report.steps.length - 1, ...result });
|
|
265
|
+
} catch (cause) {
|
|
266
|
+
const result = {
|
|
267
|
+
phase,
|
|
268
|
+
plugin: step.plugin,
|
|
269
|
+
...(step.in ? { input: step.in } : {}),
|
|
270
|
+
output: step.out,
|
|
271
|
+
status: "failed",
|
|
272
|
+
durationMs: Date.now() - startedAt,
|
|
273
|
+
fileCount: 0,
|
|
274
|
+
changedCount: 0,
|
|
275
|
+
changes: [],
|
|
276
|
+
files: [],
|
|
277
|
+
warnings: [],
|
|
278
|
+
};
|
|
279
|
+
addBuildStep(report, result);
|
|
280
|
+
persistReport();
|
|
281
|
+
event({ type: "step-finished", ordinal: report.steps.length - 1, ...result, message: cause instanceof Error ? cause.message : String(cause) });
|
|
282
|
+
throw cause;
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
function persistReport() {
|
|
287
|
+
try {
|
|
288
|
+
writeBuildReport(report);
|
|
289
|
+
} catch (cause) {
|
|
290
|
+
console.warn(`portolan gen: could not write build report: ${cause instanceof Error ? cause.message : String(cause)}`);
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
function pluginNamed(name) {
|
|
295
|
+
const plugin = (manifest.plugins ?? []).find((p) => p.name === name);
|
|
296
|
+
const shipped = builtinPlugin(name);
|
|
297
|
+
if (!plugin && !shipped) {
|
|
298
|
+
fail(`portolan.json: a step names plugin "${name}", which is not declared`);
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
return plugin ?? shipped;
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
/** Prints what a step did, and says whether it left the tree out of date. */
|
|
305
|
+
function summarise(label, files, changes, warnings = []) {
|
|
306
|
+
const said = warnings.length > 0 ? `, ${warnings.length} warning${warnings.length === 1 ? "" : "s"}` : "";
|
|
307
|
+
const summary = `${label}: ${files.length} file${files.length === 1 ? "" : "s"}${said}`;
|
|
308
|
+
|
|
309
|
+
if (changes.length === 0) {
|
|
310
|
+
console.log(`${summary}, up to date`);
|
|
311
|
+
|
|
312
|
+
return false;
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
if (!check) {
|
|
316
|
+
console.log(`${summary}, ${changes.length} written`);
|
|
317
|
+
|
|
318
|
+
return false;
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
console.error(`${summary}, ${changes.length} out of date:`);
|
|
322
|
+
for (const change of changes.slice(0, 20)) console.error(` ${change.kind.padEnd(8)} ${change.path}`);
|
|
323
|
+
if (changes.length > 20) console.error(` ... and ${changes.length - 20} more`);
|
|
324
|
+
|
|
325
|
+
return true;
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
/**
|
|
329
|
+
* When the source a fragment describes last changed, and at which commit
|
|
330
|
+
* (portolan.0002).
|
|
331
|
+
*
|
|
332
|
+
* The host works this out rather than the extractor, for two reasons. A plugin
|
|
333
|
+
* that reads a clock produces a different fragment on every run, which cannot
|
|
334
|
+
* be committed and cannot be checked; and a plugin that shells out to git is a
|
|
335
|
+
* plugin that can never be sandboxed. Stamped from the last commit to touch the
|
|
336
|
+
* directory, a fragment changes exactly when its subject does.
|
|
337
|
+
*
|
|
338
|
+
* The output is excluded from that history, and it has to be: a fragment
|
|
339
|
+
* written beside the code it describes is inside the directory it is stamped
|
|
340
|
+
* from, so committing one would move the stamp, which would make the fragment
|
|
341
|
+
* out of date, which would rewrite it - and `--check` would never come back
|
|
342
|
+
* clean two runs in a row.
|
|
343
|
+
*/
|
|
344
|
+
function stampFor(root, out) {
|
|
345
|
+
// A copy of another repository is dated by the commit it is a copy OF, which
|
|
346
|
+
// the fetch step wrote down beside it. Read before git, and without touching
|
|
347
|
+
// git at all: the local history of a vendored directory says when somebody
|
|
348
|
+
// ran the fetch, which is a fact about this repository and not about the
|
|
349
|
+
// service the fragment describes.
|
|
350
|
+
//
|
|
351
|
+
// Short, like every other stamp, because a stamp is read rather than
|
|
352
|
+
// resolved. The full sha travels separately, in the pin the fetch also
|
|
353
|
+
// wrote, where a link is built from it.
|
|
354
|
+
const vendored = vendoredCommit(root);
|
|
355
|
+
if (vendored) {
|
|
356
|
+
// No date. Nothing here knows when that commit was made - the lock records
|
|
357
|
+
// what was fetched, not when it was authored - and a date invented from
|
|
358
|
+
// the local clock would make the merge call this the stalest source in the
|
|
359
|
+
// estate every time it ran.
|
|
360
|
+
return { commit: vendored.slice(0, 7), generatedAt: "" };
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
// The history read is the one the root lives in. A manifest pointed at a
|
|
364
|
+
// checkout elsewhere on the disk - the service being documented, not a copy
|
|
365
|
+
// of it vendored here - is stamped from that checkout's history, because the
|
|
366
|
+
// fragment describes that service and not the repository the manifest sits
|
|
367
|
+
// in. A root with no repository around it is stamped as uncommitted.
|
|
368
|
+
const repo = repositoryOf(root);
|
|
369
|
+
if (!repo) {
|
|
370
|
+
return { commit: "uncommitted", generatedAt: process.env.PORTOLAN_GENERATED_AT || new Date().toISOString() };
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
// A shallow clone has no history to read: the one commit that was fetched has
|
|
374
|
+
// no parent, so every path looks as though it changed there and every fragment
|
|
375
|
+
// is stamped with the checkout rather than with its subject. That is wrong
|
|
376
|
+
// quietly - the fragments regenerate, `--check` reports drift, and nothing
|
|
377
|
+
// says why - so it is refused here instead.
|
|
378
|
+
if (shallow(repo)) {
|
|
379
|
+
fail(
|
|
380
|
+
`${repo} is a shallow clone, where every path looks as though it changed in ` +
|
|
381
|
+
"the single commit that was fetched, so a fragment cannot be stamped " +
|
|
382
|
+
"with the commit it describes. Fetch the full history first " +
|
|
383
|
+
"(git fetch --unshallow, or actions/checkout with fetch-depth: 0).",
|
|
384
|
+
);
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
// Only an output INSIDE the root is excluded. An output beside it, or above
|
|
388
|
+
// it, is not in the root's history to begin with - and excluding a parent
|
|
389
|
+
// would exclude the root itself, leaving nothing to read and a stamp that
|
|
390
|
+
// moved on every commit.
|
|
391
|
+
const rootInRepo = relative(repo, resolve(root)) || ".";
|
|
392
|
+
const outInRepo = out ? relative(repo, resolve(out)) : "";
|
|
393
|
+
const inside = out && (outInRepo === rootInRepo || outInRepo.startsWith(`${rootInRepo.replace(/\/$/, "")}/`) || rootInRepo === ".");
|
|
394
|
+
const exclude = inside && outInRepo !== rootInRepo && !outInRepo.startsWith("..") ? [`:(exclude)${outInRepo}`] : [];
|
|
395
|
+
|
|
396
|
+
for (const args of [
|
|
397
|
+
["log", "-1", "--format=%h %cI", "--", rootInRepo, ...exclude],
|
|
398
|
+
["log", "-1", "--format=%h %cI"],
|
|
399
|
+
]) {
|
|
400
|
+
const answer = git(repo, args);
|
|
401
|
+
if (!answer) continue;
|
|
402
|
+
const [commit, generatedAt] = answer.split(" ");
|
|
403
|
+
if (commit && generatedAt) return { commit, generatedAt };
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
return { commit: "uncommitted", generatedAt: process.env.PORTOLAN_GENERATED_AT || new Date().toISOString() };
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
/** Runs git in a repository and answers with its trimmed output, or "" when it refused. */
|
|
410
|
+
function git(repo, args) {
|
|
411
|
+
try {
|
|
412
|
+
return execFileSync("git", ["-C", repo, ...args], {
|
|
413
|
+
encoding: "utf8",
|
|
414
|
+
stdio: ["ignore", "pipe", "ignore"],
|
|
415
|
+
}).trim();
|
|
416
|
+
} catch {
|
|
417
|
+
// Not a repository, or no commit touches this path yet. git has already
|
|
418
|
+
// said so on its own stderr, which is not this run's log.
|
|
419
|
+
return "";
|
|
420
|
+
}
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
/** The working tree a directory belongs to, or "" when no repository holds it. */
|
|
424
|
+
function repositoryOf(dir) {
|
|
425
|
+
return git(dir, ["rev-parse", "--show-toplevel"]);
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
/** Whether the history this runs against is truncated. */
|
|
429
|
+
function shallow(repo) {
|
|
430
|
+
return git(repo, ["rev-parse", "--is-shallow-repository"]) === "true";
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
/** Reads, merges and validates every source the manifest names. */
|
|
434
|
+
async function loadSources(options = {}) {
|
|
435
|
+
try {
|
|
436
|
+
return await loadCatalog("portolan.json", options);
|
|
437
|
+
} catch (cause) {
|
|
438
|
+
fail(cause.message);
|
|
439
|
+
}
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
/**
|
|
443
|
+
* Writes what the plugin asked for, and removes what it no longer asks for.
|
|
444
|
+
* Returns a line per file that differs; in check mode nothing is touched.
|
|
445
|
+
*/
|
|
446
|
+
function apply(files, out, key, checkOnly) {
|
|
447
|
+
const changes = [];
|
|
448
|
+
const written = new Set();
|
|
449
|
+
if (!wroteThisRun.has(out)) wroteThisRun.set(out, new Map());
|
|
450
|
+
wroteThisRun.get(out).set(key, written);
|
|
451
|
+
|
|
452
|
+
for (const file of files) {
|
|
453
|
+
const target = safeJoin(out, file.name);
|
|
454
|
+
written.add(file.name);
|
|
455
|
+
|
|
456
|
+
let current = null;
|
|
457
|
+
try {
|
|
458
|
+
current = readFileSync(target, "utf8");
|
|
459
|
+
} catch {
|
|
460
|
+
// Absent, which the comparison below reports as added.
|
|
461
|
+
}
|
|
462
|
+
|
|
463
|
+
if (current === file.contents) continue;
|
|
464
|
+
|
|
465
|
+
changes.push({ kind: current === null ? "added" : "changed", path: join(out, file.name) });
|
|
466
|
+
if (checkOnly) continue;
|
|
467
|
+
|
|
468
|
+
try {
|
|
469
|
+
writeOutputFile(out, file.name, file.contents);
|
|
470
|
+
} catch (cause) {
|
|
471
|
+
fail(cause.message);
|
|
472
|
+
}
|
|
473
|
+
}
|
|
474
|
+
|
|
475
|
+
const listing = previous(out);
|
|
476
|
+
const claimedByOthers = new Set(
|
|
477
|
+
Object.entries(listing)
|
|
478
|
+
.filter(([other]) => other !== key)
|
|
479
|
+
.flatMap(([, names]) => names),
|
|
480
|
+
);
|
|
481
|
+
|
|
482
|
+
for (const stale of (listing[key] ?? []).filter(
|
|
483
|
+
(name) => !written.has(name) && !claimedByOthers.has(name),
|
|
484
|
+
)) {
|
|
485
|
+
changes.push({ kind: "removed", path: join(out, stale) });
|
|
486
|
+
if (checkOnly) continue;
|
|
487
|
+
|
|
488
|
+
try {
|
|
489
|
+
removeOutputFile(out, stale);
|
|
490
|
+
} catch (cause) {
|
|
491
|
+
fail(cause.message);
|
|
492
|
+
}
|
|
493
|
+
}
|
|
494
|
+
|
|
495
|
+
if (!checkOnly) {
|
|
496
|
+
listing[key] = [...written].sort();
|
|
497
|
+
try {
|
|
498
|
+
writeOutputFile(out, MANIFEST, `${JSON.stringify(listing, null, 2)}\n`);
|
|
499
|
+
} catch (cause) {
|
|
500
|
+
fail(cause.message);
|
|
501
|
+
}
|
|
502
|
+
pruneEmptyDirs(out);
|
|
503
|
+
}
|
|
504
|
+
|
|
505
|
+
return changes;
|
|
506
|
+
}
|
|
507
|
+
|
|
508
|
+
/**
|
|
509
|
+
* Removes what the steps that are no longer in the manifest wrote into a
|
|
510
|
+
* directory, and forgets them. Runs after every step has written, so a file a
|
|
511
|
+
* dead key listed and a live step wrote again this run is kept - it was
|
|
512
|
+
* renamed, not dropped.
|
|
513
|
+
*/
|
|
514
|
+
function sweep(out, checkOnly) {
|
|
515
|
+
const changes = [];
|
|
516
|
+
const listing = previous(out);
|
|
517
|
+
const live = keys.liveIn(out);
|
|
518
|
+
const dead = Object.keys(listing).filter((key) => !live.has(key));
|
|
519
|
+
if (dead.length === 0) return changes;
|
|
520
|
+
|
|
521
|
+
for (const stale of staleIn(out)) {
|
|
522
|
+
changes.push({ kind: "removed", path: join(out, stale) });
|
|
523
|
+
if (checkOnly) continue;
|
|
524
|
+
|
|
525
|
+
try {
|
|
526
|
+
removeOutputFile(out, stale);
|
|
527
|
+
} catch (cause) {
|
|
528
|
+
fail(cause.message);
|
|
529
|
+
}
|
|
530
|
+
}
|
|
531
|
+
for (const key of dead) delete listing[key];
|
|
532
|
+
|
|
533
|
+
if (!checkOnly) {
|
|
534
|
+
try {
|
|
535
|
+
writeOutputFile(out, MANIFEST, `${JSON.stringify(listing, null, 2)}\n`);
|
|
536
|
+
} catch (cause) {
|
|
537
|
+
fail(cause.message);
|
|
538
|
+
}
|
|
539
|
+
pruneEmptyDirs(out);
|
|
540
|
+
}
|
|
541
|
+
|
|
542
|
+
return changes;
|
|
543
|
+
}
|
|
544
|
+
|
|
545
|
+
/** What each step wrote into this directory last time. */
|
|
546
|
+
function previous(out) {
|
|
547
|
+
try {
|
|
548
|
+
const parsed = JSON.parse(readFileSync(join(out, MANIFEST), "utf8"));
|
|
549
|
+
|
|
550
|
+
return typeof parsed === "object" && parsed !== null ? parsed : {};
|
|
551
|
+
} catch {
|
|
552
|
+
return {};
|
|
553
|
+
}
|
|
554
|
+
}
|
|
555
|
+
|
|
556
|
+
/**
|
|
557
|
+
* Joins a plugin-supplied name onto the output directory, refusing anything
|
|
558
|
+
* that would land outside it.
|
|
559
|
+
*
|
|
560
|
+
* This is the whole of a plugin's authority over the tree. A wasm plugin cannot
|
|
561
|
+
* open a file at all; a process plugin very much can, and the names it hands
|
|
562
|
+
* back are still not allowed to point anywhere they like.
|
|
563
|
+
*/
|
|
564
|
+
function safeJoin(out, name) {
|
|
565
|
+
try {
|
|
566
|
+
return safeOutputPath(out, name);
|
|
567
|
+
} catch (cause) {
|
|
568
|
+
fail(cause.message);
|
|
569
|
+
}
|
|
570
|
+
}
|
|
571
|
+
|
|
572
|
+
/** Removes directories left behind by files that are no longer generated. */
|
|
573
|
+
function pruneEmptyDirs(root) {
|
|
574
|
+
const walk = (dir) => {
|
|
575
|
+
let entries;
|
|
576
|
+
try {
|
|
577
|
+
entries = readdirSync(dir);
|
|
578
|
+
} catch {
|
|
579
|
+
return true;
|
|
580
|
+
}
|
|
581
|
+
|
|
582
|
+
let empty = true;
|
|
583
|
+
for (const entry of entries) {
|
|
584
|
+
const path = join(dir, entry);
|
|
585
|
+
if (lstatSync(path).isSymbolicLink()) {
|
|
586
|
+
empty = false;
|
|
587
|
+
} else if (statSync(path).isDirectory()) {
|
|
588
|
+
if (walk(path)) rmSync(path, { recursive: true, force: true });
|
|
589
|
+
else empty = false;
|
|
590
|
+
} else {
|
|
591
|
+
empty = false;
|
|
592
|
+
}
|
|
593
|
+
}
|
|
594
|
+
|
|
595
|
+
return empty;
|
|
596
|
+
};
|
|
597
|
+
|
|
598
|
+
walk(root);
|
|
599
|
+
}
|
|
600
|
+
|
|
601
|
+
function fail(message) {
|
|
602
|
+
throw new Error(message);
|
|
603
|
+
}
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import { existsSync, readFileSync } from "node:fs";
|
|
2
|
+
import { glob } from "node:fs/promises";
|
|
3
|
+
import { dirname, join, normalize } from "node:path";
|
|
4
|
+
import { describe, expect, it } from "vitest";
|
|
5
|
+
import DOMPurify from "dompurify";
|
|
6
|
+
|
|
7
|
+
// Mermaid's parser loads its browser sanitizer even when it only parses. The
|
|
8
|
+
// Node test needs the two no-DOM hooks, while the grammar remains Mermaid's.
|
|
9
|
+
DOMPurify.addHook ??= () => {};
|
|
10
|
+
DOMPurify.sanitize ??= (value) => value;
|
|
11
|
+
const { default: mermaid } = await import("mermaid");
|
|
12
|
+
|
|
13
|
+
async function markdownFiles() {
|
|
14
|
+
const files = [];
|
|
15
|
+
for await (const file of glob("docs/**/*.md")) files.push(file);
|
|
16
|
+
return files.sort();
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
function explicitAnchors(text) {
|
|
20
|
+
return new Set([...text.matchAll(/<a id="([^"]+)"><\/a>/g)].map((match) => match[1]));
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
describe("generated documentation quality", () => {
|
|
24
|
+
it("has no broken relative files or explicit anchors", async () => {
|
|
25
|
+
const problems = [];
|
|
26
|
+
for (const file of await markdownFiles()) {
|
|
27
|
+
const text = readFileSync(file, "utf8");
|
|
28
|
+
for (const match of text.matchAll(/\]\(([^\s)]+)(?:\s+[^)]*)?\)/g)) {
|
|
29
|
+
const target = match[1];
|
|
30
|
+
if (/^[a-z][a-z0-9+.-]*:/i.test(target) || target.startsWith("/")) continue;
|
|
31
|
+
const [relative, anchor] = target.split("#", 2);
|
|
32
|
+
const destination = relative ? normalize(join(dirname(file), relative)) : file;
|
|
33
|
+
if (!existsSync(destination)) {
|
|
34
|
+
problems.push(`${file}: missing ${target}`);
|
|
35
|
+
} else if (anchor?.startsWith("type-") || anchor?.startsWith("event-") || anchor?.startsWith("relation-") || anchor?.startsWith("step-")) {
|
|
36
|
+
if (!explicitAnchors(readFileSync(destination, "utf8")).has(anchor)) problems.push(`${file}: missing #${anchor} in ${destination}`);
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
expect(problems).toEqual([]);
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
it("parses every generated Mermaid block and standalone diagram", async () => {
|
|
44
|
+
const diagrams = [];
|
|
45
|
+
for (const file of await markdownFiles()) {
|
|
46
|
+
const text = readFileSync(file, "utf8");
|
|
47
|
+
for (const match of text.matchAll(/```mermaid\n([\s\S]*?)```/g)) diagrams.push([file, match[1]]);
|
|
48
|
+
}
|
|
49
|
+
for await (const file of glob("exports/mermaid/*.mmd")) diagrams.push([file, readFileSync(file, "utf8")]);
|
|
50
|
+
expect(diagrams.length).toBeGreaterThan(0);
|
|
51
|
+
for (const [file, diagram] of diagrams) {
|
|
52
|
+
try { await mermaid.parse(diagram); } catch (cause) { throw new Error(`${file}: ${cause instanceof Error ? cause.message : String(cause)}`); }
|
|
53
|
+
}
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
it("keeps the standalone Mermaid bundle identical to flow pages", async () => {
|
|
57
|
+
const mismatches = [];
|
|
58
|
+
for await (const file of glob("exports/mermaid/*.mmd")) {
|
|
59
|
+
const slug = file.split("/").at(-1).replace(/\.mmd$/, "");
|
|
60
|
+
const page = readFileSync(`docs/flows/${slug}.md`, "utf8");
|
|
61
|
+
const embedded = /```mermaid\n([\s\S]*?)```/.exec(page)?.[1];
|
|
62
|
+
if (embedded !== readFileSync(file, "utf8")) mismatches.push(slug);
|
|
63
|
+
}
|
|
64
|
+
expect(mismatches).toEqual([]);
|
|
65
|
+
});
|
|
66
|
+
});
|