@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,610 @@
|
|
|
1
|
+
import { validateCatalog } from "../catalog";
|
|
2
|
+
import type { Catalog } from "../catalog";
|
|
3
|
+
import { enrichCatalog } from "../enrich";
|
|
4
|
+
import { mergeCatalogs } from "../merge";
|
|
5
|
+
import type { CatalogSource, SourceCatalog } from "../merge";
|
|
6
|
+
|
|
7
|
+
const API_VERSION = "2026-03-10";
|
|
8
|
+
const PAGE_SIZE = 100;
|
|
9
|
+
const MAX_REF_PAGES = 10;
|
|
10
|
+
const MAX_TREE_PAGES = 100;
|
|
11
|
+
const MAX_SOURCE_BYTES = 5 * 1024 * 1024;
|
|
12
|
+
const MAX_CATALOG_BYTES = 25 * 1024 * 1024;
|
|
13
|
+
const CATALOG_CACHE = "portolan-runtime-catalogs-v1";
|
|
14
|
+
const CATALOG_CACHE_LIMIT = 8;
|
|
15
|
+
const CACHED_AT = "x-portolan-cached-at";
|
|
16
|
+
let lastCacheOrder = 0;
|
|
17
|
+
|
|
18
|
+
export type GitHubRepo = {
|
|
19
|
+
provider: "github";
|
|
20
|
+
owner: string;
|
|
21
|
+
repo: string;
|
|
22
|
+
webUrl: string;
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
export type GitLabRepo = {
|
|
26
|
+
provider: "gitlab";
|
|
27
|
+
origin: string;
|
|
28
|
+
project: string;
|
|
29
|
+
webUrl: string;
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
export type ForgeRepo = GitHubRepo | GitLabRepo;
|
|
33
|
+
|
|
34
|
+
export type ForgeAccess = { token?: string };
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* A name the forge resolves to a commit: a branch head as it is now, or a
|
|
38
|
+
* tag. Both are what a reader compares against; the kind is kept because a
|
|
39
|
+
* tag does not move and a branch does, and the picker groups them apart.
|
|
40
|
+
*/
|
|
41
|
+
export type ForgeRef = {
|
|
42
|
+
name: string;
|
|
43
|
+
commit: string;
|
|
44
|
+
protected: boolean;
|
|
45
|
+
kind: "branch" | "tag";
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
export type GitHubBranch = ForgeRef;
|
|
49
|
+
export type ForgeBranch = ForgeRef;
|
|
50
|
+
|
|
51
|
+
type TreeItem = {
|
|
52
|
+
path: string;
|
|
53
|
+
type: "blob" | "tree" | "commit";
|
|
54
|
+
sha?: string;
|
|
55
|
+
size?: number;
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
type TreeResponse = {
|
|
59
|
+
tree: TreeItem[];
|
|
60
|
+
truncated: boolean;
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
/** A github.com repository page, reduced to the two API path segments. */
|
|
64
|
+
export function githubRepoFromUrl(value: string): GitHubRepo | null {
|
|
65
|
+
try {
|
|
66
|
+
const url = new URL(value);
|
|
67
|
+
if (url.protocol !== "https:" || url.hostname.toLowerCase() !== "github.com") {
|
|
68
|
+
return null;
|
|
69
|
+
}
|
|
70
|
+
const parts = url.pathname.split("/").filter(Boolean);
|
|
71
|
+
if (parts.length !== 2) return null;
|
|
72
|
+
const [owner, rawRepo] = parts;
|
|
73
|
+
const repo = rawRepo?.replace(/\.git$/, "");
|
|
74
|
+
if (!owner || !repo) return null;
|
|
75
|
+
return {
|
|
76
|
+
provider: "github",
|
|
77
|
+
owner,
|
|
78
|
+
repo,
|
|
79
|
+
webUrl: `https://github.com/${owner}/${repo}`,
|
|
80
|
+
};
|
|
81
|
+
} catch {
|
|
82
|
+
return null;
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/** A GitLab project page, including projects nested under subgroups. */
|
|
87
|
+
export function gitlabRepoFromUrl(value: string, allowSelfHosted = false): GitLabRepo | null {
|
|
88
|
+
try {
|
|
89
|
+
const url = new URL(value);
|
|
90
|
+
const host = url.hostname.toLowerCase();
|
|
91
|
+
if (url.protocol !== "https:" || (!allowSelfHosted && host !== "gitlab.com")) {
|
|
92
|
+
return null;
|
|
93
|
+
}
|
|
94
|
+
const parts = url.pathname.split("/").filter(Boolean);
|
|
95
|
+
if (parts.length < 2 || parts.includes("-")) return null;
|
|
96
|
+
const last = parts.at(-1)?.replace(/\.git$/, "");
|
|
97
|
+
if (!last) return null;
|
|
98
|
+
parts[parts.length - 1] = last;
|
|
99
|
+
const project = parts.join("/");
|
|
100
|
+
return {
|
|
101
|
+
provider: "gitlab",
|
|
102
|
+
origin: url.origin,
|
|
103
|
+
project,
|
|
104
|
+
webUrl: `${url.origin}/${project}`,
|
|
105
|
+
};
|
|
106
|
+
} catch {
|
|
107
|
+
return null;
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/** The forge repository behind this build. BUILD_FORGE enables private GitLab hosts. */
|
|
112
|
+
export function forgeRepoFromUrl(
|
|
113
|
+
value: string,
|
|
114
|
+
hint?: "github" | "gitlab",
|
|
115
|
+
): ForgeRepo | null {
|
|
116
|
+
if (hint === "github") return githubRepoFromUrl(value);
|
|
117
|
+
if (hint === "gitlab") return gitlabRepoFromUrl(value, true);
|
|
118
|
+
return githubRepoFromUrl(value) ?? gitlabRepoFromUrl(value);
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
function repoKey(repo: ForgeRepo): string {
|
|
122
|
+
return repo.provider === "github"
|
|
123
|
+
? `github:${repo.owner.toLowerCase()}/${repo.repo.toLowerCase()}`
|
|
124
|
+
: `gitlab:${repo.origin.toLowerCase()}/${repo.project.toLowerCase()}`;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
function apiUrl(repo: GitHubRepo, path: string): string {
|
|
128
|
+
return `https://api.github.com/repos/${encodeURIComponent(repo.owner)}/${encodeURIComponent(repo.repo)}${path}`;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
function rawUrl(repo: GitHubRepo, sha: string, path: string): string {
|
|
132
|
+
const encoded = path.split("/").map(encodeURIComponent).join("/");
|
|
133
|
+
return `https://raw.githubusercontent.com/${encodeURIComponent(repo.owner)}/${encodeURIComponent(repo.repo)}/${sha}/${encoded}`;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
function gitlabApiUrl(repo: GitLabRepo, path: string): string {
|
|
137
|
+
return `${repo.origin}/api/v4/projects/${encodeURIComponent(repo.project)}${path}`;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
function cacheKey(repo: ForgeRepo, sha: string): string {
|
|
141
|
+
return `https://portolan.invalid/catalog/${encodeURIComponent(repoKey(repo))}/${sha}`;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
function nextCacheOrder(): string {
|
|
145
|
+
lastCacheOrder = Math.max(Date.now() * 1000, lastCacheOrder + 1);
|
|
146
|
+
return String(lastCacheOrder);
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
async function cachedCatalog(repo: ForgeRepo, sha: string): Promise<Catalog | null> {
|
|
150
|
+
if (typeof caches === "undefined") return null;
|
|
151
|
+
try {
|
|
152
|
+
const store = await caches.open(CATALOG_CACHE);
|
|
153
|
+
const response = await store.match(cacheKey(repo, sha));
|
|
154
|
+
if (!response) return null;
|
|
155
|
+
try {
|
|
156
|
+
const catalog = validateCatalog(await response.json() as Catalog);
|
|
157
|
+
// A hit becomes the newest entry, so pruning below is genuinely LRU
|
|
158
|
+
// rather than just removing the oldest commit we happened to fetch.
|
|
159
|
+
await rememberCatalog(repo, sha, catalog);
|
|
160
|
+
return catalog;
|
|
161
|
+
} catch {
|
|
162
|
+
await store.delete(cacheKey(repo, sha));
|
|
163
|
+
return null;
|
|
164
|
+
}
|
|
165
|
+
} catch {
|
|
166
|
+
// Private browsing and quota policies may disable Cache Storage. Runtime
|
|
167
|
+
// comparison still works; it just has to ask GitHub again next time.
|
|
168
|
+
return null;
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
async function rememberCatalog(repo: ForgeRepo, sha: string, catalog: Catalog): Promise<void> {
|
|
173
|
+
if (typeof caches === "undefined") return;
|
|
174
|
+
try {
|
|
175
|
+
const store = await caches.open(CATALOG_CACHE);
|
|
176
|
+
await store.put(
|
|
177
|
+
cacheKey(repo, sha),
|
|
178
|
+
new Response(JSON.stringify(catalog), {
|
|
179
|
+
headers: {
|
|
180
|
+
"content-type": "application/json",
|
|
181
|
+
[CACHED_AT]: nextCacheOrder(),
|
|
182
|
+
},
|
|
183
|
+
}),
|
|
184
|
+
);
|
|
185
|
+
|
|
186
|
+
const keys = await store.keys();
|
|
187
|
+
if (keys.length <= CATALOG_CACHE_LIMIT) return;
|
|
188
|
+
const dated = await Promise.all(keys.map(async (request) => ({
|
|
189
|
+
request,
|
|
190
|
+
at: Number((await store.match(request))?.headers.get(CACHED_AT)) || 0,
|
|
191
|
+
})));
|
|
192
|
+
dated.sort((a, b) => b.at - a.at);
|
|
193
|
+
await Promise.all(dated.slice(CATALOG_CACHE_LIMIT).map(({ request }) => store.delete(request)));
|
|
194
|
+
} catch {
|
|
195
|
+
// A full or unavailable cache must never turn a successful diff into an
|
|
196
|
+
// error. The in-memory cache still covers the current page load.
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
function githubHeaders(token = "", accept = "application/vnd.github+json"): HeadersInit {
|
|
201
|
+
return {
|
|
202
|
+
Accept: accept,
|
|
203
|
+
"X-GitHub-Api-Version": API_VERSION,
|
|
204
|
+
...(token ? { Authorization: `Bearer ${token}` } : {}),
|
|
205
|
+
};
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
function gitlabHeaders(token = ""): HeadersInit {
|
|
209
|
+
return token ? { "PRIVATE-TOKEN": token } : {};
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
async function githubJson<T>(url: string, token = ""): Promise<T> {
|
|
213
|
+
const response = await fetch(url, {
|
|
214
|
+
headers: githubHeaders(token),
|
|
215
|
+
});
|
|
216
|
+
if (!response.ok) throw githubError(response);
|
|
217
|
+
return response.json() as Promise<T>;
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
async function gitlabJson<T>(url: string, token = ""): Promise<T> {
|
|
221
|
+
const response = await fetch(url, { headers: gitlabHeaders(token) });
|
|
222
|
+
if (!response.ok) throw gitlabError(response);
|
|
223
|
+
return response.json() as Promise<T>;
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
function githubError(response: Response): Error {
|
|
227
|
+
if (response.status === 401) {
|
|
228
|
+
return new Error("GitHub rejected the access token. Use a token with read access to repository contents.");
|
|
229
|
+
}
|
|
230
|
+
if (response.status === 404) {
|
|
231
|
+
return new Error("GitHub repository or branch was not found. A private repository needs an access token with Contents: read.");
|
|
232
|
+
}
|
|
233
|
+
if (response.status === 403 || response.status === 429) {
|
|
234
|
+
if (response.status === 403 && response.headers.get("x-ratelimit-remaining") !== "0") {
|
|
235
|
+
return new Error("GitHub denied access. Check that the token can read this repository's contents.");
|
|
236
|
+
}
|
|
237
|
+
const reset = Number(response.headers.get("x-ratelimit-reset"));
|
|
238
|
+
const when = Number.isFinite(reset) && reset > 0
|
|
239
|
+
? ` until ${new Date(reset * 1000).toLocaleTimeString()}`
|
|
240
|
+
: "";
|
|
241
|
+
return new Error(`GitHub API rate limit reached${when}.`);
|
|
242
|
+
}
|
|
243
|
+
return new Error(`GitHub API request failed (${response.status}).`);
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
function gitlabError(response: Response): Error {
|
|
247
|
+
if (response.status === 401) {
|
|
248
|
+
return new Error("GitLab rejected the access token. Use a token with read_api or read_repository access.");
|
|
249
|
+
}
|
|
250
|
+
if (response.status === 403) {
|
|
251
|
+
return new Error("GitLab denied access. Check that the token can read this project.");
|
|
252
|
+
}
|
|
253
|
+
if (response.status === 404) {
|
|
254
|
+
return new Error("GitLab project, branch, or file was not found. A private project needs an access token.");
|
|
255
|
+
}
|
|
256
|
+
if (response.status === 429) {
|
|
257
|
+
const reset = Number(response.headers.get("ratelimit-reset"));
|
|
258
|
+
const when = Number.isFinite(reset) && reset > 0
|
|
259
|
+
? ` until ${new Date(reset * 1000).toLocaleTimeString()}`
|
|
260
|
+
: "";
|
|
261
|
+
return new Error(`GitLab API rate limit reached${when}.`);
|
|
262
|
+
}
|
|
263
|
+
return new Error(`GitLab API request failed (${response.status}).`);
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
async function fetchText(
|
|
267
|
+
url: string,
|
|
268
|
+
label: string,
|
|
269
|
+
init?: RequestInit,
|
|
270
|
+
errorFor?: (response: Response) => Error,
|
|
271
|
+
): Promise<string> {
|
|
272
|
+
const response = await fetch(url, init);
|
|
273
|
+
if (!response.ok) {
|
|
274
|
+
if (errorFor && response.status !== 404) throw errorFor(response);
|
|
275
|
+
if (response.status === 404) {
|
|
276
|
+
throw new Error(`${label} does not exist at this commit.`);
|
|
277
|
+
}
|
|
278
|
+
throw new Error(`Could not read ${label} (${response.status}).`);
|
|
279
|
+
}
|
|
280
|
+
const length = Number(response.headers.get("content-length"));
|
|
281
|
+
if (Number.isFinite(length) && length > MAX_SOURCE_BYTES) {
|
|
282
|
+
throw new Error(`${label} is larger than 5 MB.`);
|
|
283
|
+
}
|
|
284
|
+
const text = await response.text();
|
|
285
|
+
if (new TextEncoder().encode(text).byteLength > MAX_SOURCE_BYTES) {
|
|
286
|
+
throw new Error(`${label} is larger than 5 MB.`);
|
|
287
|
+
}
|
|
288
|
+
return text;
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
/** Branch heads as they exist on GitHub now, not when the site was built. */
|
|
292
|
+
/**
|
|
293
|
+
* Every page of a ref listing. Sharing one listing between readers is the
|
|
294
|
+
* query layer's job (see lib/queries); this only walks the pages.
|
|
295
|
+
*/
|
|
296
|
+
async function listRefs(
|
|
297
|
+
page: (index: number) => Promise<ForgeRef[]>,
|
|
298
|
+
): Promise<ForgeRef[]> {
|
|
299
|
+
const refs: ForgeRef[] = [];
|
|
300
|
+
for (let index = 1; index <= MAX_REF_PAGES; index++) {
|
|
301
|
+
const found = await page(index);
|
|
302
|
+
refs.push(...found);
|
|
303
|
+
if (found.length < PAGE_SIZE) break;
|
|
304
|
+
}
|
|
305
|
+
return refs;
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
export function listGitHubBranches(
|
|
309
|
+
repo: GitHubRepo,
|
|
310
|
+
access: ForgeAccess = {},
|
|
311
|
+
): Promise<GitHubBranch[]> {
|
|
312
|
+
return listRefs(async (page) => {
|
|
313
|
+
const found = await githubJson<Array<{
|
|
314
|
+
name: string;
|
|
315
|
+
commit: { sha: string };
|
|
316
|
+
protected: boolean;
|
|
317
|
+
}>>(apiUrl(repo, `/branches?per_page=${PAGE_SIZE}&page=${page}`), access.token);
|
|
318
|
+
return found.map((branch) => ({
|
|
319
|
+
name: branch.name,
|
|
320
|
+
commit: branch.commit.sha,
|
|
321
|
+
protected: branch.protected,
|
|
322
|
+
kind: "branch" as const,
|
|
323
|
+
}));
|
|
324
|
+
});
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
/** Branch heads as they exist on GitLab now, including subgroup projects. */
|
|
328
|
+
export function listGitLabBranches(
|
|
329
|
+
repo: GitLabRepo,
|
|
330
|
+
access: ForgeAccess = {},
|
|
331
|
+
): Promise<ForgeBranch[]> {
|
|
332
|
+
return listRefs(async (page) => {
|
|
333
|
+
const found = await gitlabJson<Array<{
|
|
334
|
+
name: string;
|
|
335
|
+
protected: boolean;
|
|
336
|
+
commit: { id: string };
|
|
337
|
+
}>>(
|
|
338
|
+
gitlabApiUrl(repo, `/repository/branches?per_page=${PAGE_SIZE}&page=${page}`),
|
|
339
|
+
access.token,
|
|
340
|
+
);
|
|
341
|
+
return found.map((branch) => ({
|
|
342
|
+
name: branch.name,
|
|
343
|
+
commit: branch.commit.id,
|
|
344
|
+
protected: branch.protected,
|
|
345
|
+
kind: "branch" as const,
|
|
346
|
+
}));
|
|
347
|
+
});
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
export function listForgeBranches(
|
|
351
|
+
repo: ForgeRepo,
|
|
352
|
+
access: ForgeAccess = {},
|
|
353
|
+
): Promise<ForgeBranch[]> {
|
|
354
|
+
return repo.provider === "github"
|
|
355
|
+
? listGitHubBranches(repo, access)
|
|
356
|
+
: listGitLabBranches(repo, access);
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
/**
|
|
360
|
+
* Tags, with the commit each one points at. GitHub's tag listing peels an
|
|
361
|
+
* annotated tag to its commit already; the tag object's own sha is not what
|
|
362
|
+
* a tree is read at. Nothing on GitHub says whether a tag is protected, so
|
|
363
|
+
* none is.
|
|
364
|
+
*/
|
|
365
|
+
export function listGitHubTags(
|
|
366
|
+
repo: GitHubRepo,
|
|
367
|
+
access: ForgeAccess = {},
|
|
368
|
+
): Promise<ForgeRef[]> {
|
|
369
|
+
return listRefs(async (page) => {
|
|
370
|
+
const found = await githubJson<Array<{
|
|
371
|
+
name: string;
|
|
372
|
+
commit: { sha: string };
|
|
373
|
+
}>>(apiUrl(repo, `/tags?per_page=${PAGE_SIZE}&page=${page}`), access.token);
|
|
374
|
+
return found.map((tag) => ({
|
|
375
|
+
name: tag.name,
|
|
376
|
+
commit: tag.commit.sha,
|
|
377
|
+
protected: false,
|
|
378
|
+
kind: "tag" as const,
|
|
379
|
+
}));
|
|
380
|
+
});
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
export function listGitLabTags(
|
|
384
|
+
repo: GitLabRepo,
|
|
385
|
+
access: ForgeAccess = {},
|
|
386
|
+
): Promise<ForgeRef[]> {
|
|
387
|
+
return listRefs(async (page) => {
|
|
388
|
+
const found = await gitlabJson<Array<{
|
|
389
|
+
name: string;
|
|
390
|
+
protected?: boolean;
|
|
391
|
+
commit: { id: string };
|
|
392
|
+
}>>(
|
|
393
|
+
gitlabApiUrl(repo, `/repository/tags?per_page=${PAGE_SIZE}&page=${page}`),
|
|
394
|
+
access.token,
|
|
395
|
+
);
|
|
396
|
+
return found.map((tag) => ({
|
|
397
|
+
name: tag.name,
|
|
398
|
+
commit: tag.commit.id,
|
|
399
|
+
protected: Boolean(tag.protected),
|
|
400
|
+
kind: "tag" as const,
|
|
401
|
+
}));
|
|
402
|
+
});
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
export function listForgeTags(
|
|
406
|
+
repo: ForgeRepo,
|
|
407
|
+
access: ForgeAccess = {},
|
|
408
|
+
): Promise<ForgeRef[]> {
|
|
409
|
+
return repo.provider === "github"
|
|
410
|
+
? listGitHubTags(repo, access)
|
|
411
|
+
: listGitLabTags(repo, access);
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
/** Branches and tags together, branches first, as one list to pick from. */
|
|
415
|
+
export async function listForgeRefs(
|
|
416
|
+
repo: ForgeRepo,
|
|
417
|
+
access: ForgeAccess = {},
|
|
418
|
+
): Promise<ForgeRef[]> {
|
|
419
|
+
const [branches, tags] = await Promise.all([
|
|
420
|
+
listForgeBranches(repo, access),
|
|
421
|
+
listForgeTags(repo, access),
|
|
422
|
+
]);
|
|
423
|
+
return [...branches, ...tags];
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
/** A manifest glob as an anchored regular expression. */
|
|
427
|
+
export function catalogGlob(pattern: string): RegExp {
|
|
428
|
+
let out = "";
|
|
429
|
+
for (let i = 0; i < pattern.length; i++) {
|
|
430
|
+
const char = pattern[i] ?? "";
|
|
431
|
+
if (char === "*") {
|
|
432
|
+
if (pattern[i + 1] === "*") {
|
|
433
|
+
out += ".*";
|
|
434
|
+
i++;
|
|
435
|
+
} else {
|
|
436
|
+
out += "[^/]*";
|
|
437
|
+
}
|
|
438
|
+
continue;
|
|
439
|
+
}
|
|
440
|
+
out += /[a-zA-Z0-9/_-]/.test(char) ? char : `\\${char}`;
|
|
441
|
+
}
|
|
442
|
+
return new RegExp(`^${out}$`);
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
function parseJson(text: string, label: string): unknown {
|
|
446
|
+
try {
|
|
447
|
+
return JSON.parse(text) as unknown;
|
|
448
|
+
} catch {
|
|
449
|
+
throw new Error(`${label} is not valid JSON.`);
|
|
450
|
+
}
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
async function inBatches<T, R>(
|
|
454
|
+
values: T[],
|
|
455
|
+
size: number,
|
|
456
|
+
task: (value: T) => Promise<R>,
|
|
457
|
+
): Promise<R[]> {
|
|
458
|
+
const results: R[] = [];
|
|
459
|
+
for (let i = 0; i < values.length; i += size) {
|
|
460
|
+
results.push(...await Promise.all(values.slice(i, i + size).map(task)));
|
|
461
|
+
}
|
|
462
|
+
return results;
|
|
463
|
+
}
|
|
464
|
+
|
|
465
|
+
/**
|
|
466
|
+
* The merged catalog at an immutable commit. Public catalogs may use Cache
|
|
467
|
+
* Storage; authenticated catalogs are kept only in this page load, by the
|
|
468
|
+
* query cache.
|
|
469
|
+
*/
|
|
470
|
+
export async function loadGitHubCatalog(
|
|
471
|
+
repo: GitHubRepo,
|
|
472
|
+
sha: string,
|
|
473
|
+
access: ForgeAccess = {},
|
|
474
|
+
): Promise<Catalog> {
|
|
475
|
+
if (!/^[0-9a-f]{40}$/i.test(sha)) {
|
|
476
|
+
throw new Error("GitHub returned an invalid commit SHA.");
|
|
477
|
+
}
|
|
478
|
+
const remembered = access.token ? null : await cachedCatalog(repo, sha);
|
|
479
|
+
if (remembered) return remembered;
|
|
480
|
+
|
|
481
|
+
const manifestText = access.token
|
|
482
|
+
? await fetchText(
|
|
483
|
+
apiUrl(repo, `/contents/portolan.json?ref=${encodeURIComponent(sha)}`),
|
|
484
|
+
"portolan.json",
|
|
485
|
+
{ headers: githubHeaders(access.token, "application/vnd.github.raw+json") },
|
|
486
|
+
githubError,
|
|
487
|
+
)
|
|
488
|
+
: await fetchText(rawUrl(repo, sha, "portolan.json"), "portolan.json");
|
|
489
|
+
const manifest = parseJson(manifestText, "portolan.json") as { sources?: unknown };
|
|
490
|
+
if (!Array.isArray(manifest.sources) || !manifest.sources.every((source) => typeof source === "string")) {
|
|
491
|
+
throw new Error("portolan.json has no valid sources list.");
|
|
492
|
+
}
|
|
493
|
+
const patterns = (manifest.sources as string[]).map(catalogGlob);
|
|
494
|
+
const tree = await githubJson<TreeResponse>(
|
|
495
|
+
apiUrl(repo, `/git/trees/${sha}?recursive=1`),
|
|
496
|
+
access.token,
|
|
497
|
+
);
|
|
498
|
+
if (tree.truncated) {
|
|
499
|
+
throw new Error("The GitHub tree is too large to load safely; its recursive response was truncated.");
|
|
500
|
+
}
|
|
501
|
+
const files = tree.tree
|
|
502
|
+
.filter((item) => item.type === "blob" && patterns.some((pattern) => pattern.test(item.path)))
|
|
503
|
+
.sort((a, b) => a.path.localeCompare(b.path));
|
|
504
|
+
if (files.length === 0) {
|
|
505
|
+
throw new Error("This branch has no catalog sources matching portolan.json.");
|
|
506
|
+
}
|
|
507
|
+
const declaredBytes = files.reduce((total, file) => total + (file.size ?? 0), 0);
|
|
508
|
+
if (declaredBytes > MAX_CATALOG_BYTES) {
|
|
509
|
+
throw new Error("The catalog is larger than the 25 MB runtime limit.");
|
|
510
|
+
}
|
|
511
|
+
|
|
512
|
+
const sources = await inBatches(files, 8, async (file): Promise<CatalogSource> => {
|
|
513
|
+
const text = access.token
|
|
514
|
+
? file.sha
|
|
515
|
+
? await fetchText(
|
|
516
|
+
apiUrl(repo, `/git/blobs/${file.sha}`),
|
|
517
|
+
file.path,
|
|
518
|
+
{ headers: githubHeaders(access.token, "application/vnd.github.raw+json") },
|
|
519
|
+
githubError,
|
|
520
|
+
)
|
|
521
|
+
: await Promise.reject(new Error(`GitHub returned no blob SHA for ${file.path}.`))
|
|
522
|
+
: await fetchText(rawUrl(repo, sha, file.path), file.path);
|
|
523
|
+
return { path: file.path, catalog: parseJson(text, file.path) as SourceCatalog };
|
|
524
|
+
});
|
|
525
|
+
const merged = mergeCatalogs(sources);
|
|
526
|
+
const enriched = enrichCatalog(merged.catalog);
|
|
527
|
+
const catalog = validateCatalog(enriched.catalog);
|
|
528
|
+
if (!access.token) await rememberCatalog(repo, sha, catalog);
|
|
529
|
+
return catalog;
|
|
530
|
+
}
|
|
531
|
+
|
|
532
|
+
/** A validated catalog read from GitLab's repository API at one commit. */
|
|
533
|
+
export async function loadGitLabCatalog(
|
|
534
|
+
repo: GitLabRepo,
|
|
535
|
+
sha: string,
|
|
536
|
+
access: ForgeAccess = {},
|
|
537
|
+
): Promise<Catalog> {
|
|
538
|
+
if (!/^[0-9a-f]{40}$/i.test(sha)) {
|
|
539
|
+
throw new Error("GitLab returned an invalid commit SHA.");
|
|
540
|
+
}
|
|
541
|
+
const remembered = access.token ? null : await cachedCatalog(repo, sha);
|
|
542
|
+
if (remembered) return remembered;
|
|
543
|
+
|
|
544
|
+
const encodedSha = encodeURIComponent(sha);
|
|
545
|
+
const manifestText = await fetchText(
|
|
546
|
+
gitlabApiUrl(repo, `/repository/files/${encodeURIComponent("portolan.json")}/raw?ref=${encodedSha}`),
|
|
547
|
+
"portolan.json",
|
|
548
|
+
{ headers: gitlabHeaders(access.token) },
|
|
549
|
+
gitlabError,
|
|
550
|
+
);
|
|
551
|
+
const manifest = parseJson(manifestText, "portolan.json") as { sources?: unknown };
|
|
552
|
+
if (!Array.isArray(manifest.sources) || !manifest.sources.every((source) => typeof source === "string")) {
|
|
553
|
+
throw new Error("portolan.json has no valid sources list.");
|
|
554
|
+
}
|
|
555
|
+
const patterns = (manifest.sources as string[]).map(catalogGlob);
|
|
556
|
+
|
|
557
|
+
const tree: TreeItem[] = [];
|
|
558
|
+
for (let page = 1; page <= MAX_TREE_PAGES; page++) {
|
|
559
|
+
const found = await gitlabJson<Array<{
|
|
560
|
+
id: string;
|
|
561
|
+
path: string;
|
|
562
|
+
type: "blob" | "tree";
|
|
563
|
+
}>>(
|
|
564
|
+
gitlabApiUrl(repo, `/repository/tree?recursive=true&ref=${encodedSha}&per_page=${PAGE_SIZE}&page=${page}`),
|
|
565
|
+
access.token,
|
|
566
|
+
);
|
|
567
|
+
tree.push(...found.map((item) => ({ path: item.path, type: item.type, sha: item.id })));
|
|
568
|
+
if (found.length < PAGE_SIZE) break;
|
|
569
|
+
if (page === MAX_TREE_PAGES) {
|
|
570
|
+
throw new Error("The GitLab repository tree is larger than the runtime comparison limit.");
|
|
571
|
+
}
|
|
572
|
+
}
|
|
573
|
+
|
|
574
|
+
const files = tree
|
|
575
|
+
.filter((item) => item.type === "blob" && patterns.some((pattern) => pattern.test(item.path)))
|
|
576
|
+
.sort((a, b) => a.path.localeCompare(b.path));
|
|
577
|
+
if (files.length === 0) {
|
|
578
|
+
throw new Error("This branch has no catalog sources matching portolan.json.");
|
|
579
|
+
}
|
|
580
|
+
|
|
581
|
+
let loadedBytes = 0;
|
|
582
|
+
const sources = await inBatches(files, 8, async (file): Promise<CatalogSource> => {
|
|
583
|
+
const text = await fetchText(
|
|
584
|
+
gitlabApiUrl(repo, `/repository/files/${encodeURIComponent(file.path)}/raw?ref=${encodedSha}`),
|
|
585
|
+
file.path,
|
|
586
|
+
{ headers: gitlabHeaders(access.token) },
|
|
587
|
+
gitlabError,
|
|
588
|
+
);
|
|
589
|
+
loadedBytes += new TextEncoder().encode(text).byteLength;
|
|
590
|
+
if (loadedBytes > MAX_CATALOG_BYTES) {
|
|
591
|
+
throw new Error("The catalog is larger than the 25 MB runtime limit.");
|
|
592
|
+
}
|
|
593
|
+
return { path: file.path, catalog: parseJson(text, file.path) as SourceCatalog };
|
|
594
|
+
});
|
|
595
|
+
const merged = mergeCatalogs(sources);
|
|
596
|
+
const enriched = enrichCatalog(merged.catalog);
|
|
597
|
+
const catalog = validateCatalog(enriched.catalog);
|
|
598
|
+
if (!access.token) await rememberCatalog(repo, sha, catalog);
|
|
599
|
+
return catalog;
|
|
600
|
+
}
|
|
601
|
+
|
|
602
|
+
export function loadForgeCatalog(
|
|
603
|
+
repo: ForgeRepo,
|
|
604
|
+
sha: string,
|
|
605
|
+
access: ForgeAccess = {},
|
|
606
|
+
): Promise<Catalog> {
|
|
607
|
+
return repo.provider === "github"
|
|
608
|
+
? loadGitHubCatalog(repo, sha, access)
|
|
609
|
+
: loadGitLabCatalog(repo, sha, access);
|
|
610
|
+
}
|