@shortlink-org/portolan 0.1.0 → 0.2.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/README.md +69 -205
- package/cli/init.mjs +285 -0
- package/cli/init.test.mjs +158 -0
- package/cli/portolan.mjs +37 -73
- package/index.html +19 -0
- package/package.json +5 -10
- package/plugins/README.md +66 -27
- package/plugins/portolan-go.wasm +0 -0
- package/portolan.json +52 -73
- package/public/landing/cats/bug-hunter.webp +0 -0
- package/public/landing/cats/compass-nap.webp +0 -0
- package/public/landing/cats/diagrammer.webp +0 -0
- package/public/landing/cats/docs-reader.webp +0 -0
- package/public/landing/cats/laptop.webp +0 -0
- package/public/landing/cats/map-inspector.webp +0 -0
- package/public/landing/cats/server-break.webp +0 -0
- package/public/landing/cats/star-mapper.webp +0 -0
- package/public/landing/cats/system-builder.webp +0 -0
- package/public/landing/cats/thread-tangle.webp +0 -0
- package/public/og.png +0 -0
- package/public/readme-header.webp +0 -0
- package/schema/portolan.schema.json +180 -0
- package/scripts/builtin-plugins.mjs +14 -0
- package/scripts/delivery-presets.mjs +353 -0
- package/scripts/gen.mjs +37 -4
- package/scripts/history.mjs +126 -0
- package/scripts/history.test.mjs +110 -0
- package/scripts/host-plugins/fetch-bsr.mjs +351 -0
- package/{plugins/fetch-bsr/options.schema.json → scripts/host-plugins/fetch-bsr.options.json} +4 -0
- package/scripts/host-plugins/fetch-bsr.test.mjs +212 -0
- package/scripts/host-plugins/fetch-csr.mjs +386 -0
- package/scripts/host-plugins/fetch-csr.test.mjs +178 -0
- package/scripts/host-plugins/fetch-git.mjs +379 -0
- package/scripts/host-plugins/fetch-git.test.mjs +210 -0
- package/scripts/local-api.mjs +22 -4
- package/scripts/local-api.test.mjs +84 -2
- package/scripts/manifest.mjs +3 -2
- package/scripts/package-smoke.mjs +6 -2
- package/scripts/plugin-host.mjs +77 -18
- package/scripts/plugin-host.test.mjs +50 -1
- package/scripts/plugin-wasm-worker.mjs +6 -3
- package/scripts/run-builtin.mjs +6 -26
- package/scripts/schema.mjs +6 -1
- package/src/app/App.tsx +24 -429
- package/src/app/Breadcrumbs.test.ts +7 -0
- package/src/app/Breadcrumbs.tsx +15 -2
- package/src/app/CatalogApp.tsx +436 -0
- package/src/app/Sidebar.tsx +0 -1
- package/src/chat/ChatPanel.tsx +98 -41
- package/src/chat/Composer.tsx +4 -2
- package/src/chat/Conversation.tsx +79 -15
- package/src/chat/Header.tsx +43 -9
- package/src/chat/Starter.tsx +64 -3
- package/src/chat/Steps.tsx +1 -1
- package/src/chat/flags.test.ts +51 -14
- package/src/chat/flags.ts +8 -2
- package/src/chat/page-context.test.ts +51 -0
- package/src/chat/page-context.ts +128 -0
- package/src/chat/prompt.test.ts +49 -1
- package/src/chat/prompt.ts +42 -1
- package/src/chat/tools.ts +7 -2
- package/src/chat/transport.ts +16 -4
- package/src/components/ProblemRow.tsx +182 -0
- package/src/data.ts +20 -5
- package/src/er/ErCanvas.tsx +1 -1
- package/src/graph/DependencyGraph.tsx +10 -1
- package/src/graph/FocusedEventGraph.tsx +1 -1
- package/src/graph/GraphToolbar.tsx +2 -4
- package/src/index.css +220 -2
- package/src/landing/DraggableReveal.tsx +168 -0
- package/src/landing/EstateGraph.tsx +28 -0
- package/src/landing/FlowPlayback.tsx +282 -0
- package/src/landing/HeroMap.tsx +128 -0
- package/src/landing/LandingPage.tsx +757 -0
- package/src/landing/ProductFrame.tsx +68 -0
- package/src/landing/ProductTour.tsx +371 -0
- package/src/landing/catalog.ts +25 -0
- package/src/landing/motion.tsx +52 -0
- package/src/lib/all-problems.ts +28 -0
- package/src/lib/local-api.ts +35 -1
- package/src/lib/setup-info.test.ts +8 -0
- package/src/lib/setup-info.ts +3 -2
- package/src/lib/source-code.ts +2 -1
- package/src/likec4/C4View.tsx +5 -0
- package/src/likec4/InteractiveView.tsx +5 -1
- package/src/map/ContextMapGraph.tsx +47 -20
- package/src/pages/ContextMap.tsx +5 -1
- package/src/pages/FlowDetail.tsx +24 -7
- package/src/pages/Overview.tsx +68 -12
- package/src/pages/Problems.tsx +6 -193
- package/src/pages/Settings.tsx +133 -108
- package/src/pages/settings/DeliverySettings.tsx +229 -0
- package/src/pages/settings/PreferencesSettings.tsx +101 -0
- package/src/routes.test.ts +20 -0
- package/src/routes.ts +13 -1
- package/vite.config.ts +12 -8
- package/catalog/enum_test.go +0 -46
- package/catalog/model.go +0 -932
- package/catalog/roundtrip_test.go +0 -185
- package/catalog/via_test.go +0 -38
- package/go.mod +0 -14
- package/go.sum +0 -14
- package/internal/commands/cargo.go +0 -154
- package/internal/commands/commands.go +0 -143
- package/internal/commands/commands_test.go +0 -323
- package/internal/commands/gradle.go +0 -77
- package/internal/commands/justfile.go +0 -82
- package/internal/commands/makefile.go +0 -106
- package/internal/commands/maven.go +0 -93
- package/internal/commands/packagejson.go +0 -159
- package/internal/commands/pyproject.go +0 -169
- package/internal/commands/taskfile.go +0 -101
- package/internal/commands/testdata/estate/.cargo/config.toml +0 -7
- package/internal/commands/testdata/estate/Makefile +0 -33
- package/internal/commands/testdata/estate/Taskfile.yml +0 -21
- package/internal/commands/testdata/estate/build.gradle.kts +0 -21
- package/internal/commands/testdata/estate/justfile +0 -20
- package/internal/commands/testdata/estate/package.json +0 -13
- package/internal/commands/testdata/estate/pom.xml +0 -29
- package/internal/commands/testdata/estate/pyproject.toml +0 -27
- package/internal/commands/testdata/estate/xtask/src/main.rs +0 -27
- package/internal/commands/testdata/golden/commands.json +0 -305
- package/internal/gohttp/analyze.go +0 -2500
- package/internal/gohttp/endpoints.go +0 -1067
- package/internal/gohttp/roots.go +0 -320
- package/internal/gohttp/typed.go +0 -143
- package/internal/goscan/constants.go +0 -85
- package/internal/goscan/goscan_test.go +0 -227
- package/internal/goscan/names.go +0 -52
- package/internal/goscan/parse_test.go +0 -11
- package/internal/goscan/source.go +0 -36
- package/internal/goscan/tree.go +0 -155
- package/internal/goscan/types.go +0 -99
- package/internal/wsdl/ids.go +0 -127
- package/internal/wsdl/ids_test.go +0 -21
- package/internal/wsdl/model.go +0 -62
- package/internal/wsdl/parse.go +0 -920
- package/internal/wsdl/parse_test.go +0 -133
- package/plugin/describe.go +0 -107
- package/plugin/describe_test.go +0 -114
- package/plugin/protocol.go +0 -117
- package/plugin/schematest/schematest.go +0 -126
- package/plugins/extract-adr/describe.go +0 -19
- package/plugins/extract-adr/describe_test.go +0 -11
- package/plugins/extract-adr/extract.go +0 -153
- package/plugins/extract-adr/extract_test.go +0 -350
- package/plugins/extract-adr/history.go +0 -99
- package/plugins/extract-adr/main.go +0 -65
- package/plugins/extract-adr/parse.go +0 -642
- package/plugins/extract-adr/parse_test.go +0 -405
- package/plugins/extract-asyncapi/describe.go +0 -19
- package/plugins/extract-asyncapi/describe_test.go +0 -11
- package/plugins/extract-asyncapi/extract.go +0 -315
- package/plugins/extract-asyncapi/extract_test.go +0 -197
- package/plugins/extract-asyncapi/main.go +0 -48
- package/plugins/extract-asyncapi/spec.go +0 -151
- package/plugins/extract-commands/describe.go +0 -19
- package/plugins/extract-commands/describe_test.go +0 -11
- package/plugins/extract-commands/extract.go +0 -79
- package/plugins/extract-commands/extract_test.go +0 -92
- package/plugins/extract-commands/main.go +0 -55
- package/plugins/extract-csr/avro.go +0 -258
- package/plugins/extract-csr/describe.go +0 -19
- package/plugins/extract-csr/describe_test.go +0 -11
- package/plugins/extract-csr/extract.go +0 -338
- package/plugins/extract-csr/extract_test.go +0 -374
- package/plugins/extract-csr/jsonschema.go +0 -343
- package/plugins/extract-csr/lock.go +0 -25
- package/plugins/extract-csr/main.go +0 -84
- package/plugins/extract-csr/subject.go +0 -102
- package/plugins/extract-flows/describe.go +0 -19
- package/plugins/extract-flows/describe_test.go +0 -11
- package/plugins/extract-flows/extract.go +0 -91
- package/plugins/extract-flows/main.go +0 -45
- package/plugins/extract-flows/parse.go +0 -593
- package/plugins/extract-flows/parse_test.go +0 -204
- package/plugins/extract-glossary/describe.go +0 -19
- package/plugins/extract-glossary/describe_test.go +0 -11
- package/plugins/extract-glossary/extract.go +0 -115
- package/plugins/extract-glossary/extract_test.go +0 -220
- package/plugins/extract-glossary/main.go +0 -59
- package/plugins/extract-glossary/parse.go +0 -214
- package/plugins/extract-glossary/parse_test.go +0 -203
- package/plugins/extract-go/aggregate.go +0 -214
- package/plugins/extract-go/client.go +0 -409
- package/plugins/extract-go/client_test.go +0 -266
- package/plugins/extract-go/describe.go +0 -19
- package/plugins/extract-go/describe_test.go +0 -11
- package/plugins/extract-go/enum.go +0 -195
- package/plugins/extract-go/enum_test.go +0 -82
- package/plugins/extract-go/event.go +0 -99
- package/plugins/extract-go/extract.go +0 -191
- package/plugins/extract-go/extract_test.go +0 -261
- package/plugins/extract-go/flow.go +0 -1441
- package/plugins/extract-go/flow_test.go +0 -609
- package/plugins/extract-go/httpclient.go +0 -174
- package/plugins/extract-go/httpclient_test.go +0 -296
- package/plugins/extract-go/ids.go +0 -92
- package/plugins/extract-go/layout.go +0 -225
- package/plugins/extract-go/layout_test.go +0 -125
- package/plugins/extract-go/lifecycle.go +0 -324
- package/plugins/extract-go/lifecycle_test.go +0 -108
- package/plugins/extract-go/main.go +0 -79
- package/plugins/extract-go/operation.go +0 -157
- package/plugins/extract-go/source.go +0 -316
- package/plugins/extract-go/transport.go +0 -321
- package/plugins/extract-go/transport_test.go +0 -145
- package/plugins/extract-go/wiring.go +0 -434
- package/plugins/extract-go-nats/describe.go +0 -19
- package/plugins/extract-go-nats/describe_test.go +0 -11
- package/plugins/extract-go-nats/extract.go +0 -177
- package/plugins/extract-go-nats/extract_test.go +0 -389
- package/plugins/extract-go-nats/index.go +0 -379
- package/plugins/extract-go-nats/main.go +0 -42
- package/plugins/extract-go-nats/resolve.go +0 -161
- package/plugins/extract-go-nats/sites.go +0 -224
- package/plugins/extract-graphql/describe.go +0 -19
- package/plugins/extract-graphql/describe_test.go +0 -11
- package/plugins/extract-graphql/extract.go +0 -433
- package/plugins/extract-graphql/extract_test.go +0 -256
- package/plugins/extract-graphql/ids.go +0 -49
- package/plugins/extract-graphql/lex.go +0 -237
- package/plugins/extract-graphql/main.go +0 -51
- package/plugins/extract-graphql/parse.go +0 -621
- package/plugins/extract-graphql/parse_test.go +0 -122
- package/plugins/extract-http-clients/describe.go +0 -19
- package/plugins/extract-http-clients/describe_test.go +0 -11
- package/plugins/extract-http-clients/extract.go +0 -705
- package/plugins/extract-http-clients/extract_test.go +0 -1263
- package/plugins/extract-http-clients/main.go +0 -42
- package/plugins/extract-openapi/describe.go +0 -19
- package/plugins/extract-openapi/describe_test.go +0 -11
- package/plugins/extract-openapi/discover.go +0 -243
- package/plugins/extract-openapi/extract.go +0 -526
- package/plugins/extract-openapi/extract_test.go +0 -545
- package/plugins/extract-openapi/main.go +0 -75
- package/plugins/extract-openapi/spec.go +0 -350
- package/plugins/extract-project/describe.go +0 -19
- package/plugins/extract-project/describe_test.go +0 -11
- package/plugins/extract-project/extract.go +0 -221
- package/plugins/extract-project/extract_test.go +0 -109
- package/plugins/extract-project/main.go +0 -41
- package/plugins/extract-proto/ast.go +0 -125
- package/plugins/extract-proto/consumes.go +0 -77
- package/plugins/extract-proto/describe.go +0 -19
- package/plugins/extract-proto/extract.go +0 -293
- package/plugins/extract-proto/extract_test.go +0 -459
- package/plugins/extract-proto/ids.go +0 -89
- package/plugins/extract-proto/ids_test.go +0 -57
- package/plugins/extract-proto/lex.go +0 -285
- package/plugins/extract-proto/main.go +0 -100
- package/plugins/extract-proto/module.go +0 -120
- package/plugins/extract-proto/parse.go +0 -720
- package/plugins/extract-proto/parse_test.go +0 -307
- package/plugins/extract-proto/provides.go +0 -236
- package/plugins/extract-proto/resolve.go +0 -222
- package/plugins/extract-proto/resolve_test.go +0 -119
- package/plugins/extract-redis/describe.go +0 -19
- package/plugins/extract-redis/describe_test.go +0 -11
- package/plugins/extract-redis/extract.go +0 -183
- package/plugins/extract-redis/extract_test.go +0 -168
- package/plugins/extract-redis/keyspaces.go +0 -469
- package/plugins/extract-redis/main.go +0 -44
- package/plugins/extract-river/describe.go +0 -19
- package/plugins/extract-river/describe_test.go +0 -11
- package/plugins/extract-river/extract.go +0 -507
- package/plugins/extract-river/extract_test.go +0 -132
- package/plugins/extract-river/main.go +0 -42
- package/plugins/extract-sql/ddl.go +0 -893
- package/plugins/extract-sql/ddl_test.go +0 -401
- package/plugins/extract-sql/describe.go +0 -19
- package/plugins/extract-sql/describe_test.go +0 -11
- package/plugins/extract-sql/layout.go +0 -221
- package/plugins/extract-sql/layout_test.go +0 -55
- package/plugins/extract-sql/lineage.go +0 -107
- package/plugins/extract-sql/main.go +0 -141
- package/plugins/extract-sql/maps.go +0 -564
- package/plugins/extract-sql/maps_java.go +0 -117
- package/plugins/extract-sql/maps_rust.go +0 -333
- package/plugins/extract-sql/maps_rust_test.go +0 -70
- package/plugins/extract-sql/maps_test.go +0 -204
- package/plugins/extract-sql/maps_ts.go +0 -398
- package/plugins/extract-sql/maps_ts_test.go +0 -136
- package/plugins/extract-sql/projection.go +0 -70
- package/plugins/extract-sql/projection_test.go +0 -50
- package/plugins/extract-sql/store.go +0 -420
- package/plugins/extract-sql/store_test.go +0 -233
- package/plugins/extract-sql/view.go +0 -295
- package/plugins/extract-watermill/describe.go +0 -19
- package/plugins/extract-watermill/describe_test.go +0 -11
- package/plugins/extract-watermill/extract.go +0 -1228
- package/plugins/extract-watermill/extract_test.go +0 -234
- package/plugins/extract-watermill/main.go +0 -41
- package/plugins/extract-wsdl/describe.go +0 -19
- package/plugins/extract-wsdl/describe_test.go +0 -11
- package/plugins/extract-wsdl/extract.go +0 -148
- package/plugins/extract-wsdl/extract_test.go +0 -45
- package/plugins/extract-wsdl/main.go +0 -53
- package/plugins/fetch-bsr/auth.go +0 -114
- package/plugins/fetch-bsr/bsr.go +0 -240
- package/plugins/fetch-bsr/cache.go +0 -66
- package/plugins/fetch-bsr/describe.go +0 -19
- package/plugins/fetch-bsr/fetch.go +0 -192
- package/plugins/fetch-bsr/fetch_test.go +0 -465
- package/plugins/fetch-bsr/lock.go +0 -71
- package/plugins/fetch-bsr/main.go +0 -117
- package/plugins/fetch-csr/auth.go +0 -95
- package/plugins/fetch-csr/cache.go +0 -69
- package/plugins/fetch-csr/describe.go +0 -19
- package/plugins/fetch-csr/describe_test.go +0 -11
- package/plugins/fetch-csr/fetch.go +0 -254
- package/plugins/fetch-csr/fetch_test.go +0 -494
- package/plugins/fetch-csr/lock.go +0 -95
- package/plugins/fetch-csr/main.go +0 -122
- package/plugins/fetch-csr/registry.go +0 -209
- package/plugins/fetch-git/cache.go +0 -65
- package/plugins/fetch-git/describe.go +0 -19
- package/plugins/fetch-git/describe_test.go +0 -11
- package/plugins/fetch-git/fetch.go +0 -181
- package/plugins/fetch-git/fetch_test.go +0 -332
- package/plugins/fetch-git/git.go +0 -169
- package/plugins/fetch-git/lock.go +0 -72
- package/plugins/fetch-git/main.go +0 -127
- package/plugins/fetch-git/offline.go +0 -39
- package/plugins/fetch-git/pin.go +0 -90
- package/plugins/fetch-git/pin_test.go +0 -74
- package/plugins/gen-backstage/describe.go +0 -17
- package/plugins/gen-backstage/main.go +0 -22
- package/plugins/gen-backstage/plugin.go +0 -473
- package/plugins/gen-backstage/plugin_test.go +0 -145
- package/plugins/gen-backstage.wasm +0 -0
- package/plugins/gen-markdown/adr.go +0 -162
- package/plugins/gen-markdown/aggregate.go +0 -362
- package/plugins/gen-markdown/canonical.go +0 -93
- package/plugins/gen-markdown/context.go +0 -90
- package/plugins/gen-markdown/coverage_test.go +0 -89
- package/plugins/gen-markdown/describe.go +0 -19
- package/plugins/gen-markdown/describe_test.go +0 -11
- package/plugins/gen-markdown/external.go +0 -71
- package/plugins/gen-markdown/flow.go +0 -277
- package/plugins/gen-markdown/glossary.go +0 -76
- package/plugins/gen-markdown/glossary_test.go +0 -148
- package/plugins/gen-markdown/llms.go +0 -302
- package/plugins/gen-markdown/main.go +0 -22
- package/plugins/gen-markdown/markdown.go +0 -254
- package/plugins/gen-markdown/markdown_test.go +0 -100
- package/plugins/gen-markdown/module.go +0 -107
- package/plugins/gen-markdown/plugin.go +0 -39
- package/plugins/gen-markdown/quality_test.go +0 -176
- package/plugins/gen-markdown/redis_test.go +0 -38
- package/plugins/gen-markdown/render.go +0 -344
- package/plugins/gen-markdown/render_test.go +0 -186
- package/plugins/gen-markdown/service.go +0 -456
- package/plugins/gen-markdown/source.go +0 -177
- package/plugins/gen-markdown/store.go +0 -201
- package/plugins/gen-markdown.wasm +0 -0
- package/plugins/gen-mermaid/describe.go +0 -17
- package/plugins/gen-mermaid/main.go +0 -22
- package/plugins/gen-mermaid/plugin.go +0 -104
- package/plugins/gen-mermaid/plugin_test.go +0 -33
- package/plugins/gen-mermaid.wasm +0 -0
- package/plugins/openapi/ids.go +0 -261
- package/plugins/openapi/ids_test.go +0 -98
- package/plugins/verify-codeowners/describe.go +0 -19
- package/plugins/verify-codeowners/describe_test.go +0 -11
- package/plugins/verify-codeowners/main.go +0 -75
- package/plugins/verify-codeowners/match.go +0 -85
- package/plugins/verify-codeowners/match_test.go +0 -47
- package/plugins/verify-codeowners/owners.go +0 -164
- package/plugins/verify-codeowners/owners_test.go +0 -225
- package/plugins/verify-codeowners/parse.go +0 -90
- package/plugins/verify-codeowners/parse_test.go +0 -62
- package/plugins/verify-otel/describe.go +0 -19
- package/plugins/verify-otel/describe_test.go +0 -11
- package/plugins/verify-otel/main.go +0 -53
- package/plugins/verify-otel/match.go +0 -336
- package/plugins/verify-otel/otlp.go +0 -200
- package/plugins/verify-otel/verify.go +0 -734
- package/plugins/verify-otel/verify_test.go +0 -460
- /package/{plugins/fetch-csr/options.schema.json → scripts/host-plugins/fetch-csr.options.json} +0 -0
- /package/{plugins/fetch-git/options.schema.json → scripts/host-plugins/fetch-git.options.json} +0 -0
|
@@ -1,494 +0,0 @@
|
|
|
1
|
-
package main
|
|
2
|
-
|
|
3
|
-
// The fetcher, against a recorded registry. NEVER against a live one: a test
|
|
4
|
-
// that reaches a Confluent endpoint would fail on a train, and the whole point
|
|
5
|
-
// of this plugin's design is that the build does not depend on a registry
|
|
6
|
-
// being up.
|
|
7
|
-
|
|
8
|
-
import (
|
|
9
|
-
"encoding/base64"
|
|
10
|
-
"encoding/json"
|
|
11
|
-
"net/http"
|
|
12
|
-
"net/http/httptest"
|
|
13
|
-
"os"
|
|
14
|
-
"path/filepath"
|
|
15
|
-
"sort"
|
|
16
|
-
"strings"
|
|
17
|
-
"testing"
|
|
18
|
-
|
|
19
|
-
"github.com/shortlink-org/portolan/plugin"
|
|
20
|
-
)
|
|
21
|
-
|
|
22
|
-
const (
|
|
23
|
-
valueSubject = "shop.oms.order-value"
|
|
24
|
-
partySubject = "shop.oms.Party"
|
|
25
|
-
)
|
|
26
|
-
|
|
27
|
-
// The value schema references the party one, which is how a registry says a
|
|
28
|
-
// record is shared between subjects.
|
|
29
|
-
var (
|
|
30
|
-
orderSchema = `{"type":"record","namespace":"shop.oms","name":"OrderPlaced","fields":[{"name":"order_id","type":"string"},{"name":"buyer","type":"shop.oms.Party"}]}`
|
|
31
|
-
partySchema = `{"type":"record","namespace":"shop.oms","name":"Party","fields":[{"name":"id","type":"string"}]}`
|
|
32
|
-
)
|
|
33
|
-
|
|
34
|
-
// registry serves the one call this plugin makes, from recorded shapes.
|
|
35
|
-
func registry(t *testing.T, seen *[]string) *httptest.Server {
|
|
36
|
-
t.Helper()
|
|
37
|
-
|
|
38
|
-
answers := map[string]registration{
|
|
39
|
-
"/subjects/shop.oms.order-value/versions/3": {
|
|
40
|
-
Subject: valueSubject, Version: 3, ID: 100021, GUID: "8f0d",
|
|
41
|
-
SchemaType: "AVRO", Schema: orderSchema,
|
|
42
|
-
References: []Reference{{Name: "shop.oms.Party", Subject: partySubject, Version: 1}},
|
|
43
|
-
},
|
|
44
|
-
"/subjects/shop.oms.order-value/versions/latest": {
|
|
45
|
-
Subject: valueSubject, Version: 3, ID: 100021, GUID: "8f0d",
|
|
46
|
-
SchemaType: "AVRO", Schema: orderSchema,
|
|
47
|
-
References: []Reference{{Name: "shop.oms.Party", Subject: partySubject, Version: 1}},
|
|
48
|
-
},
|
|
49
|
-
"/subjects/shop.oms.Party/versions/1": {
|
|
50
|
-
Subject: partySubject, Version: 1, ID: 100014,
|
|
51
|
-
SchemaType: "AVRO", Schema: partySchema,
|
|
52
|
-
},
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
return httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
56
|
-
if seen != nil {
|
|
57
|
-
*seen = append(*seen, r.Header.Get("Authorization"))
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
w.Header().Set("Content-Type", "application/json")
|
|
61
|
-
|
|
62
|
-
answer, known := answers[r.URL.Path]
|
|
63
|
-
if !known {
|
|
64
|
-
w.WriteHeader(http.StatusNotFound)
|
|
65
|
-
_, _ = w.Write([]byte(`{"error_code":40401,"message":"Subject '` + r.URL.Path + `' not found."}`))
|
|
66
|
-
|
|
67
|
-
return
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
_ = json.NewEncoder(w).Encode(answer)
|
|
71
|
-
}))
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
func options(server *httptest.Server, cache string, subjects ...Subject) Options {
|
|
75
|
-
return Options{Registry: server.URL, Cache: cache, Subjects: subjects}
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
// names is the file list, sorted, so a test can say what a run produced
|
|
79
|
-
// without depending on the order the queue happened to drain in.
|
|
80
|
-
func names(resp plugin.Response) []string {
|
|
81
|
-
var out []string
|
|
82
|
-
for _, f := range resp.Files {
|
|
83
|
-
out = append(out, f.Name)
|
|
84
|
-
}
|
|
85
|
-
sort.Strings(out)
|
|
86
|
-
|
|
87
|
-
return out
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
func contents(t *testing.T, resp plugin.Response, name string) string {
|
|
91
|
-
t.Helper()
|
|
92
|
-
|
|
93
|
-
for _, f := range resp.Files {
|
|
94
|
-
if f.Name == name {
|
|
95
|
-
return f.Contents
|
|
96
|
-
}
|
|
97
|
-
}
|
|
98
|
-
t.Fatalf("no file named %s in %v", name, names(resp))
|
|
99
|
-
|
|
100
|
-
return ""
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
// write is the host's half: it puts a response into a tree, so the next run
|
|
104
|
-
// can replay from it the way a real one replays from what was committed.
|
|
105
|
-
func write(t *testing.T, root string, resp plugin.Response) {
|
|
106
|
-
t.Helper()
|
|
107
|
-
|
|
108
|
-
for _, f := range resp.Files {
|
|
109
|
-
at := filepath.Join(root, filepath.FromSlash(f.Name))
|
|
110
|
-
if err := os.MkdirAll(filepath.Dir(at), 0o755); err != nil {
|
|
111
|
-
t.Fatal(err)
|
|
112
|
-
}
|
|
113
|
-
if err := os.WriteFile(at, []byte(f.Contents), 0o644); err != nil {
|
|
114
|
-
t.Fatal(err)
|
|
115
|
-
}
|
|
116
|
-
}
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
func offlineOff(t *testing.T) {
|
|
120
|
-
t.Helper()
|
|
121
|
-
t.Setenv(OfflineEnv, "")
|
|
122
|
-
t.Setenv("CI", "")
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
func TestFetchWritesSchemaAndLock(t *testing.T) {
|
|
126
|
-
offlineOff(t)
|
|
127
|
-
server := registry(t, nil)
|
|
128
|
-
defer server.Close()
|
|
129
|
-
|
|
130
|
-
cache := t.TempDir()
|
|
131
|
-
resp, err := fetch(options(server, cache, Subject{Subject: valueSubject, Version: 3}))
|
|
132
|
-
if err != nil {
|
|
133
|
-
t.Fatal(err)
|
|
134
|
-
}
|
|
135
|
-
|
|
136
|
-
want := []string{
|
|
137
|
-
"shop.oms.Party/csr.lock.json",
|
|
138
|
-
"shop.oms.Party/v1.avsc",
|
|
139
|
-
"shop.oms.order-value/csr.lock.json",
|
|
140
|
-
"shop.oms.order-value/v3.avsc",
|
|
141
|
-
}
|
|
142
|
-
if got := names(resp); !equal(got, want) {
|
|
143
|
-
t.Errorf("files:\n got %v\nwant %v", got, want)
|
|
144
|
-
}
|
|
145
|
-
|
|
146
|
-
// The reference was followed without the manifest naming it: the referring
|
|
147
|
-
// schema pinned it, which is the whole argument for following one at all.
|
|
148
|
-
if got := contents(t, resp, "shop.oms.Party/v1.avsc"); !strings.Contains(got, `"name": "Party"`) {
|
|
149
|
-
t.Errorf("the referenced schema is not the one the registry served:\n%s", got)
|
|
150
|
-
}
|
|
151
|
-
|
|
152
|
-
// Indented rather than the one line the registry answers with, so a
|
|
153
|
-
// version bump is a diff a person can read.
|
|
154
|
-
schema := contents(t, resp, "shop.oms.order-value/v3.avsc")
|
|
155
|
-
if !strings.Contains(schema, "\n \"type\": \"record\"") {
|
|
156
|
-
t.Errorf("the schema was not indented:\n%s", schema)
|
|
157
|
-
}
|
|
158
|
-
if !strings.HasSuffix(schema, "\n") {
|
|
159
|
-
t.Error("the schema does not end in a newline")
|
|
160
|
-
}
|
|
161
|
-
// json.Indent reformats without reordering, so the file still says what
|
|
162
|
-
// the registry said in the order it said it.
|
|
163
|
-
if strings.Index(schema, `"namespace"`) > strings.Index(schema, `"name"`) {
|
|
164
|
-
t.Errorf("the schema's keys were reordered:\n%s", schema)
|
|
165
|
-
}
|
|
166
|
-
|
|
167
|
-
var lock Lock
|
|
168
|
-
if err := json.Unmarshal([]byte(contents(t, resp, "shop.oms.order-value/csr.lock.json")), &lock); err != nil {
|
|
169
|
-
t.Fatal(err)
|
|
170
|
-
}
|
|
171
|
-
if lock.Registry != server.URL {
|
|
172
|
-
t.Errorf("lock names registry %q, want %q", lock.Registry, server.URL)
|
|
173
|
-
}
|
|
174
|
-
if len(lock.Subjects) != 1 {
|
|
175
|
-
t.Fatalf("lock names %d subjects, want 1", len(lock.Subjects))
|
|
176
|
-
}
|
|
177
|
-
|
|
178
|
-
entry := lock.Subjects[0]
|
|
179
|
-
if entry.Subject != valueSubject || entry.Version != 3 || entry.ID != 100021 {
|
|
180
|
-
t.Errorf("lock entry: %+v", entry)
|
|
181
|
-
}
|
|
182
|
-
if entry.SchemaType != "AVRO" {
|
|
183
|
-
t.Errorf("schema type %q, want AVRO", entry.SchemaType)
|
|
184
|
-
}
|
|
185
|
-
if len(entry.References) != 1 || entry.References[0].Subject != partySubject {
|
|
186
|
-
t.Errorf("references: %+v", entry.References)
|
|
187
|
-
}
|
|
188
|
-
if len(entry.Files) != 1 || entry.Files[0].Path != "v3.avsc" {
|
|
189
|
-
t.Fatalf("files: %+v", entry.Files)
|
|
190
|
-
}
|
|
191
|
-
// The digest is over the bytes as written, so verifying needs no
|
|
192
|
-
// reformatting of anything.
|
|
193
|
-
if entry.Files[0].SHA256 != digestOf([]byte(schema)) {
|
|
194
|
-
t.Error("the digest is not over the bytes that were written")
|
|
195
|
-
}
|
|
196
|
-
if entry.Files[0].Size != len(schema) {
|
|
197
|
-
t.Errorf("size %d, want %d", entry.Files[0].Size, len(schema))
|
|
198
|
-
}
|
|
199
|
-
}
|
|
200
|
-
|
|
201
|
-
func TestAbsentSchemaTypeIsAvro(t *testing.T) {
|
|
202
|
-
offlineOff(t)
|
|
203
|
-
|
|
204
|
-
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
|
|
205
|
-
// A registry that predates the field says nothing, and that silence
|
|
206
|
-
// means Avro rather than "unknown".
|
|
207
|
-
_, _ = w.Write([]byte(`{"subject":"legacy-value","version":1,"id":7,"schema":` +
|
|
208
|
-
mustQuote(partySchema) + `}`))
|
|
209
|
-
}))
|
|
210
|
-
defer server.Close()
|
|
211
|
-
|
|
212
|
-
resp, err := fetch(options(server, t.TempDir(), Subject{Subject: "legacy-value", Version: 1}))
|
|
213
|
-
if err != nil {
|
|
214
|
-
t.Fatal(err)
|
|
215
|
-
}
|
|
216
|
-
if got := names(resp); !equal(got, []string{"legacy-value/csr.lock.json", "legacy-value/v1.avsc"}) {
|
|
217
|
-
t.Errorf("files: %v", got)
|
|
218
|
-
}
|
|
219
|
-
}
|
|
220
|
-
|
|
221
|
-
func TestUnpinnedResolvesLatestAndSaysSo(t *testing.T) {
|
|
222
|
-
offlineOff(t)
|
|
223
|
-
server := registry(t, nil)
|
|
224
|
-
defer server.Close()
|
|
225
|
-
|
|
226
|
-
resp, err := fetch(options(server, t.TempDir(), Subject{Subject: valueSubject}))
|
|
227
|
-
if err != nil {
|
|
228
|
-
t.Fatal(err)
|
|
229
|
-
}
|
|
230
|
-
if got := contents(t, resp, "shop.oms.order-value/v3.avsc"); got == "" {
|
|
231
|
-
t.Error("latest resolved to nothing")
|
|
232
|
-
}
|
|
233
|
-
|
|
234
|
-
warnings := resp.Warnings()
|
|
235
|
-
if len(warnings) != 1 || !strings.Contains(warnings[0].Message, "not pinned") {
|
|
236
|
-
t.Errorf("an unpinned subject was not reported: %+v", warnings)
|
|
237
|
-
}
|
|
238
|
-
}
|
|
239
|
-
|
|
240
|
-
func TestOfflineReplaysWhatWasCommitted(t *testing.T) {
|
|
241
|
-
offlineOff(t)
|
|
242
|
-
server := registry(t, nil)
|
|
243
|
-
|
|
244
|
-
cache := t.TempDir()
|
|
245
|
-
opts := options(server, cache, Subject{Subject: valueSubject, Version: 3})
|
|
246
|
-
|
|
247
|
-
first, err := fetch(opts)
|
|
248
|
-
if err != nil {
|
|
249
|
-
t.Fatal(err)
|
|
250
|
-
}
|
|
251
|
-
write(t, cache, first)
|
|
252
|
-
|
|
253
|
-
// The registry is gone AND the plugin is told not to look, which is CI.
|
|
254
|
-
server.Close()
|
|
255
|
-
t.Setenv(OfflineEnv, "1")
|
|
256
|
-
|
|
257
|
-
second, err := fetch(opts)
|
|
258
|
-
if err != nil {
|
|
259
|
-
t.Fatal(err)
|
|
260
|
-
}
|
|
261
|
-
|
|
262
|
-
// Byte-identical, or `gen:check` would go red on a build that changed
|
|
263
|
-
// nothing.
|
|
264
|
-
if !sameFiles(first, second) {
|
|
265
|
-
t.Errorf("the offline run differs:\n online %v\noffline %v", names(first), names(second))
|
|
266
|
-
}
|
|
267
|
-
// The reference was followed from the lock rather than from the registry.
|
|
268
|
-
if got := contents(t, second, "shop.oms.Party/v1.avsc"); got == "" {
|
|
269
|
-
t.Error("the referenced subject was not replayed")
|
|
270
|
-
}
|
|
271
|
-
}
|
|
272
|
-
|
|
273
|
-
func TestOfflineRefusesAnUnpinnedSubject(t *testing.T) {
|
|
274
|
-
t.Setenv(OfflineEnv, "1")
|
|
275
|
-
server := registry(t, nil)
|
|
276
|
-
defer server.Close()
|
|
277
|
-
|
|
278
|
-
_, err := fetch(options(server, t.TempDir(), Subject{Subject: valueSubject}))
|
|
279
|
-
if err == nil || !strings.Contains(err.Error(), "not pinned") {
|
|
280
|
-
t.Errorf("an unpinned subject was replayed offline: %v", err)
|
|
281
|
-
}
|
|
282
|
-
}
|
|
283
|
-
|
|
284
|
-
func TestAFailedFetchFallsBackToTheTree(t *testing.T) {
|
|
285
|
-
offlineOff(t)
|
|
286
|
-
server := registry(t, nil)
|
|
287
|
-
|
|
288
|
-
cache := t.TempDir()
|
|
289
|
-
opts := options(server, cache, Subject{Subject: valueSubject, Version: 3})
|
|
290
|
-
|
|
291
|
-
first, err := fetch(opts)
|
|
292
|
-
if err != nil {
|
|
293
|
-
t.Fatal(err)
|
|
294
|
-
}
|
|
295
|
-
write(t, cache, first)
|
|
296
|
-
|
|
297
|
-
// Rule 2: the registry is down, the tree still holds a good copy, so the
|
|
298
|
-
// output is unchanged.
|
|
299
|
-
server.Close()
|
|
300
|
-
|
|
301
|
-
second, err := fetch(opts)
|
|
302
|
-
if err != nil {
|
|
303
|
-
t.Fatal(err)
|
|
304
|
-
}
|
|
305
|
-
if !sameFiles(first, second) {
|
|
306
|
-
t.Error("a fetch that failed over to the tree produced different files")
|
|
307
|
-
}
|
|
308
|
-
if len(second.Warnings()) == 0 {
|
|
309
|
-
t.Error("falling back to the tree was not reported")
|
|
310
|
-
}
|
|
311
|
-
}
|
|
312
|
-
|
|
313
|
-
func TestAFailedFetchWithNoTreeIsAnError(t *testing.T) {
|
|
314
|
-
offlineOff(t)
|
|
315
|
-
server := registry(t, nil)
|
|
316
|
-
server.Close()
|
|
317
|
-
|
|
318
|
-
// Rule 3: never a short file list. The host deletes what a step stops
|
|
319
|
-
// naming, and a laptop going offline must not empty the vendored schemas.
|
|
320
|
-
_, err := fetch(options(server, t.TempDir(), Subject{Subject: valueSubject, Version: 3}))
|
|
321
|
-
if err == nil {
|
|
322
|
-
t.Fatal("a fetch with nothing to fall back to succeeded")
|
|
323
|
-
}
|
|
324
|
-
if !strings.Contains(err.Error(), "no usable copy") {
|
|
325
|
-
t.Errorf("the error does not say the tree is empty: %v", err)
|
|
326
|
-
}
|
|
327
|
-
}
|
|
328
|
-
|
|
329
|
-
func TestAnEditedVendoredCopyIsReported(t *testing.T) {
|
|
330
|
-
offlineOff(t)
|
|
331
|
-
server := registry(t, nil)
|
|
332
|
-
|
|
333
|
-
cache := t.TempDir()
|
|
334
|
-
opts := options(server, cache, Subject{Subject: valueSubject, Version: 3})
|
|
335
|
-
|
|
336
|
-
first, err := fetch(opts)
|
|
337
|
-
if err != nil {
|
|
338
|
-
t.Fatal(err)
|
|
339
|
-
}
|
|
340
|
-
write(t, cache, first)
|
|
341
|
-
|
|
342
|
-
at := filepath.Join(cache, "shop.oms.order-value", "v3.avsc")
|
|
343
|
-
if err := os.WriteFile(at, []byte("{}\n"), 0o644); err != nil {
|
|
344
|
-
t.Fatal(err)
|
|
345
|
-
}
|
|
346
|
-
|
|
347
|
-
server.Close()
|
|
348
|
-
t.Setenv(OfflineEnv, "1")
|
|
349
|
-
|
|
350
|
-
_, err = fetch(opts)
|
|
351
|
-
if err == nil || !strings.Contains(err.Error(), "edited by hand") {
|
|
352
|
-
t.Errorf("an edited vendored copy was accepted: %v", err)
|
|
353
|
-
}
|
|
354
|
-
}
|
|
355
|
-
|
|
356
|
-
func TestAMovedPinIsReportedRatherThanReplayed(t *testing.T) {
|
|
357
|
-
offlineOff(t)
|
|
358
|
-
server := registry(t, nil)
|
|
359
|
-
|
|
360
|
-
cache := t.TempDir()
|
|
361
|
-
first, err := fetch(options(server, cache, Subject{Subject: valueSubject, Version: 3}))
|
|
362
|
-
if err != nil {
|
|
363
|
-
t.Fatal(err)
|
|
364
|
-
}
|
|
365
|
-
write(t, cache, first)
|
|
366
|
-
|
|
367
|
-
server.Close()
|
|
368
|
-
t.Setenv(OfflineEnv, "1")
|
|
369
|
-
|
|
370
|
-
_, err = fetch(options(server, cache, Subject{Subject: valueSubject, Version: 4}))
|
|
371
|
-
if err == nil || !strings.Contains(err.Error(), "holds version 3") {
|
|
372
|
-
t.Errorf("a moved pin was replayed from the old version: %v", err)
|
|
373
|
-
}
|
|
374
|
-
}
|
|
375
|
-
|
|
376
|
-
func TestOneSubjectAtTwoVersionsIsRefused(t *testing.T) {
|
|
377
|
-
offlineOff(t)
|
|
378
|
-
server := registry(t, nil)
|
|
379
|
-
defer server.Close()
|
|
380
|
-
|
|
381
|
-
_, err := fetch(options(server, t.TempDir(),
|
|
382
|
-
Subject{Subject: valueSubject, Version: 3},
|
|
383
|
-
Subject{Subject: valueSubject, Version: 2},
|
|
384
|
-
))
|
|
385
|
-
if err == nil || !strings.Contains(err.Error(), "one version per subject") {
|
|
386
|
-
t.Errorf("two versions of one subject were fetched into one directory: %v", err)
|
|
387
|
-
}
|
|
388
|
-
}
|
|
389
|
-
|
|
390
|
-
func TestAMissingSubjectSaysWhatTheRegistrySaid(t *testing.T) {
|
|
391
|
-
offlineOff(t)
|
|
392
|
-
server := registry(t, nil)
|
|
393
|
-
defer server.Close()
|
|
394
|
-
|
|
395
|
-
_, err := fetch(options(server, t.TempDir(), Subject{Subject: "nope-value", Version: 1}))
|
|
396
|
-
if err == nil || !strings.Contains(err.Error(), "not found") {
|
|
397
|
-
t.Errorf("the registry's own message was lost: %v", err)
|
|
398
|
-
}
|
|
399
|
-
}
|
|
400
|
-
|
|
401
|
-
func TestTheCredentialComesFromTheEnvironment(t *testing.T) {
|
|
402
|
-
offlineOff(t)
|
|
403
|
-
|
|
404
|
-
var seen []string
|
|
405
|
-
server := registry(t, &seen)
|
|
406
|
-
defer server.Close()
|
|
407
|
-
|
|
408
|
-
t.Setenv(TokenEnv, "")
|
|
409
|
-
t.Setenv(ShortKeyEnv, "KEY")
|
|
410
|
-
t.Setenv(ShortSecretEnv, "SECRET")
|
|
411
|
-
t.Setenv(KeyEnv, "")
|
|
412
|
-
t.Setenv(SecretEnv, "")
|
|
413
|
-
|
|
414
|
-
if _, err := fetch(options(server, t.TempDir(), Subject{Subject: valueSubject, Version: 3})); err != nil {
|
|
415
|
-
t.Fatal(err)
|
|
416
|
-
}
|
|
417
|
-
|
|
418
|
-
want := "Basic " + base64.StdEncoding.EncodeToString([]byte("KEY:SECRET"))
|
|
419
|
-
for _, got := range seen {
|
|
420
|
-
if got != want {
|
|
421
|
-
t.Fatalf("Authorization %q, want %q", got, want)
|
|
422
|
-
}
|
|
423
|
-
}
|
|
424
|
-
if len(seen) == 0 {
|
|
425
|
-
t.Fatal("the registry was never called")
|
|
426
|
-
}
|
|
427
|
-
}
|
|
428
|
-
|
|
429
|
-
func TestABareTokenIsGivenAScheme(t *testing.T) {
|
|
430
|
-
t.Setenv(TokenEnv, "abc123")
|
|
431
|
-
if got := authorization(); got != "Bearer abc123" {
|
|
432
|
-
t.Errorf("authorization %q", got)
|
|
433
|
-
}
|
|
434
|
-
|
|
435
|
-
// One that already names its scheme is passed through: guessing on the
|
|
436
|
-
// caller's behalf is how a working token starts failing.
|
|
437
|
-
t.Setenv(TokenEnv, "Negotiate abc123")
|
|
438
|
-
if got := authorization(); got != "Negotiate abc123" {
|
|
439
|
-
t.Errorf("authorization %q", got)
|
|
440
|
-
}
|
|
441
|
-
}
|
|
442
|
-
|
|
443
|
-
func TestSlugOfKeepsTheSubjectReadable(t *testing.T) {
|
|
444
|
-
for subject, want := range map[string]string{
|
|
445
|
-
"shop.oms.order-value": "shop.oms.order-value",
|
|
446
|
-
"shop.oms.OrderPlaced": "shop.oms.OrderPlaced",
|
|
447
|
-
"a/b:c value": "a_b_c_value",
|
|
448
|
-
"...": "_",
|
|
449
|
-
} {
|
|
450
|
-
if got := slugOf(subject); got != want {
|
|
451
|
-
t.Errorf("slugOf(%q) = %q, want %q", subject, got, want)
|
|
452
|
-
}
|
|
453
|
-
}
|
|
454
|
-
}
|
|
455
|
-
|
|
456
|
-
func equal(a, b []string) bool {
|
|
457
|
-
if len(a) != len(b) {
|
|
458
|
-
return false
|
|
459
|
-
}
|
|
460
|
-
for i := range a {
|
|
461
|
-
if a[i] != b[i] {
|
|
462
|
-
return false
|
|
463
|
-
}
|
|
464
|
-
}
|
|
465
|
-
|
|
466
|
-
return true
|
|
467
|
-
}
|
|
468
|
-
|
|
469
|
-
func sameFiles(a, b plugin.Response) bool {
|
|
470
|
-
if len(a.Files) != len(b.Files) {
|
|
471
|
-
return false
|
|
472
|
-
}
|
|
473
|
-
|
|
474
|
-
held := map[string]string{}
|
|
475
|
-
for _, f := range a.Files {
|
|
476
|
-
held[f.Name] = f.Contents
|
|
477
|
-
}
|
|
478
|
-
for _, f := range b.Files {
|
|
479
|
-
if held[f.Name] != f.Contents {
|
|
480
|
-
return false
|
|
481
|
-
}
|
|
482
|
-
}
|
|
483
|
-
|
|
484
|
-
return true
|
|
485
|
-
}
|
|
486
|
-
|
|
487
|
-
func mustQuote(s string) string {
|
|
488
|
-
out, err := json.Marshal(s)
|
|
489
|
-
if err != nil {
|
|
490
|
-
panic(err)
|
|
491
|
-
}
|
|
492
|
-
|
|
493
|
-
return string(out)
|
|
494
|
-
}
|
|
@@ -1,95 +0,0 @@
|
|
|
1
|
-
package main
|
|
2
|
-
|
|
3
|
-
// What was fetched, so it can be checked without fetching again.
|
|
4
|
-
//
|
|
5
|
-
// One lock per subject directory rather than one for the whole step, so a
|
|
6
|
-
// vendored subject is self-describing: the schema, the registry it came from,
|
|
7
|
-
// the version it was pinned to and the subjects it references sit together,
|
|
8
|
-
// and the reader finds the lock by looking beside the file it is already
|
|
9
|
-
// reading.
|
|
10
|
-
|
|
11
|
-
import (
|
|
12
|
-
"crypto/sha256"
|
|
13
|
-
"encoding/hex"
|
|
14
|
-
"encoding/json"
|
|
15
|
-
"sort"
|
|
16
|
-
)
|
|
17
|
-
|
|
18
|
-
// LockName is the file written beside each subject's schema.
|
|
19
|
-
const LockName = "csr.lock.json"
|
|
20
|
-
|
|
21
|
-
type Lock struct {
|
|
22
|
-
// Registry is where these bytes came from. A module's registry-global name
|
|
23
|
-
// carries its registry; a subject's name does not, so it is written down
|
|
24
|
-
// here or the vendored copy cannot say where it belongs.
|
|
25
|
-
Registry string `json:"registry"`
|
|
26
|
-
|
|
27
|
-
Subjects []LockSubject `json:"subjects"`
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
type LockSubject struct {
|
|
31
|
-
Subject string `json:"subject"`
|
|
32
|
-
|
|
33
|
-
// Version is what was actually fetched. A registered version is immutable,
|
|
34
|
-
// so a pinned download is byte-reproducible and this is what makes
|
|
35
|
-
// replaying from disk equivalent to fetching again.
|
|
36
|
-
Version int `json:"version"`
|
|
37
|
-
|
|
38
|
-
// ID is the registry-global schema id, and GUID the wider identifier for
|
|
39
|
-
// the same schema. Neither is needed to replay; both are what a message on
|
|
40
|
-
// the wire actually carries, so an incident that has a schema id and
|
|
41
|
-
// nothing else can be traced back to this file.
|
|
42
|
-
ID int `json:"id"`
|
|
43
|
-
GUID string `json:"guid,omitempty"`
|
|
44
|
-
|
|
45
|
-
SchemaType string `json:"schemaType"`
|
|
46
|
-
|
|
47
|
-
// References are the subjects this schema depends on, pinned by the schema
|
|
48
|
-
// itself. Each is fetched into its own directory, so this list is also how
|
|
49
|
-
// an offline replay knows what else to read.
|
|
50
|
-
References []Reference `json:"references,omitempty"`
|
|
51
|
-
|
|
52
|
-
Files []LockEntry `json:"files"`
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
type LockEntry struct {
|
|
56
|
-
Path string `json:"path"`
|
|
57
|
-
|
|
58
|
-
// SHA256 is over the bytes as written. It is what turns a cache into
|
|
59
|
-
// something checkable: a vendored copy someone edited by hand stops
|
|
60
|
-
// matching, which is exactly the drift docs/adr/org.0001.md wants visible.
|
|
61
|
-
SHA256 string `json:"sha256"`
|
|
62
|
-
Size int `json:"size"`
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
func digestOf(content []byte) string {
|
|
66
|
-
sum := sha256.Sum256(content)
|
|
67
|
-
|
|
68
|
-
return hex.EncodeToString(sum[:])
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
// encode writes the lock the way every generated file in this repository is
|
|
72
|
-
// written: sorted, indented, newline-terminated. It is committed and compared
|
|
73
|
-
// by `gen:check`, so an unstable order would rewrite it on every run.
|
|
74
|
-
//
|
|
75
|
-
// References are sorted too. The registry answers them in registration order,
|
|
76
|
-
// which is stable for a pinned version, but they are a lookup from name to
|
|
77
|
-
// subject rather than a sequence, and sorting says so.
|
|
78
|
-
func (l Lock) encode() (string, error) {
|
|
79
|
-
for i := range l.Subjects {
|
|
80
|
-
sort.Slice(l.Subjects[i].Files, func(a, b int) bool {
|
|
81
|
-
return l.Subjects[i].Files[a].Path < l.Subjects[i].Files[b].Path
|
|
82
|
-
})
|
|
83
|
-
sort.Slice(l.Subjects[i].References, func(a, b int) bool {
|
|
84
|
-
return l.Subjects[i].References[a].Name < l.Subjects[i].References[b].Name
|
|
85
|
-
})
|
|
86
|
-
}
|
|
87
|
-
sort.Slice(l.Subjects, func(a, b int) bool { return l.Subjects[a].Subject < l.Subjects[b].Subject })
|
|
88
|
-
|
|
89
|
-
out, err := json.MarshalIndent(l, "", " ")
|
|
90
|
-
if err != nil {
|
|
91
|
-
return "", err
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
return string(out) + "\n", nil
|
|
95
|
-
}
|