diff --git a/.github/workflows/playwright.yml b/.github/workflows/playwright.yml new file mode 100644 index 0000000..d0c87f1 --- /dev/null +++ b/.github/workflows/playwright.yml @@ -0,0 +1,56 @@ +name: Playwright Tests + +on: + push: + branches: ["main"] + pull_request: + branches: ["main"] + workflow_dispatch: + +permissions: + contents: read + +jobs: + test: + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Setup Ruby + uses: ruby/setup-ruby@b90be12699fdfcbee4440c2bba85f6f460446bb0 # v1.279.0 + with: + ruby-version: "3.1" + bundler-cache: true + + - name: Build Jekyll site + run: bundle exec jekyll build + env: + JEKYLL_ENV: production + PAGES_REPO_NWO: go-coldbrew/docs.coldbrew.cloud + JEKYLL_GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + + - name: Setup Node + uses: actions/setup-node@v4 + with: + node-version: "20" + cache: "npm" + + - name: Install dependencies + run: npm ci + + - name: Install Playwright Chromium + run: npx playwright install chromium + + - name: Run Playwright tests + run: npx playwright test + env: + CI: true + + - name: Upload test report + uses: actions/upload-artifact@v4 + if: ${{ !cancelled() }} + with: + name: playwright-report + path: playwright-report/ + retention-days: 14 diff --git a/.gitignore b/.gitignore index ca35be0..f7a2ddf 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,4 @@ _site +node_modules +test-results +playwright-report diff --git a/FAQ.md b/FAQ.md index ef1b9be..64c879f 100644 --- a/FAQ.md +++ b/FAQ.md @@ -128,10 +128,14 @@ You can register cleanup callbacks and customize shutdown behavior. See the [Sig Set the `SENTRY_DSN` environment variable and use the errors package: ```go -import "github.com/go-coldbrew/errors" +import ( + "context" + "github.com/go-coldbrew/errors/notifier" +) // This notifies Sentry asynchronously (bounded, won't leak goroutines) -errors.NotifyAsync(err, severity, args...) +ctx := context.Background() +notifier.Notify(err, ctx) ``` See the [Errors How-To](/howto/errors) and [Integrations](/integrations) for full setup instructions. diff --git a/Gemfile b/Gemfile index 41bbdda..7166b60 100644 --- a/Gemfile +++ b/Gemfile @@ -7,3 +7,10 @@ gem "jekyll-github-metadata", ">= 2.15" gem "webrick", "~> 1.7" gem 'jekyll-target-blank' gem "jekyll-sitemap" + +# Required for Ruby 4.0+ (removed from default gems) +gem "logger" +gem "csv" +gem "ostruct" +gem "base64" +gem "bigdecimal" diff --git a/Gemfile.lock b/Gemfile.lock index aae4e1e..c65332a 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -3,8 +3,11 @@ GEM specs: addressable (2.8.4) public_suffix (>= 2.0.2, < 6.0) + base64 (0.3.0) + bigdecimal (4.0.1) colorator (1.1.0) concurrent-ruby (1.2.2) + csv (3.3.5) em-websocket (0.5.3) eventmachine (>= 0.12.9) http_parser.rb (~> 0) @@ -15,9 +18,7 @@ GEM faraday-net_http (3.0.2) ffi (1.15.5) forwardable-extended (2.6.0) - google-protobuf (3.22.3-arm64-darwin) - google-protobuf (3.22.3-x86_64-darwin) - google-protobuf (3.22.3-x86_64-linux) + google-protobuf (3.22.3) http_parser.rb (0.8.0) i18n (1.12.0) concurrent-ruby (~> 1.0) @@ -65,16 +66,16 @@ GEM listen (3.8.0) rb-fsevent (~> 0.10, >= 0.10.3) rb-inotify (~> 0.9, >= 0.9.10) + logger (1.7.0) mercenary (0.4.0) - nokogiri (1.13.10-arm64-darwin) - racc (~> 1.4) - nokogiri (1.13.10-x86_64-darwin) - racc (~> 1.4) - nokogiri (1.13.10-x86_64-linux) + mini_portile2 (2.8.9) + nokogiri (1.13.10) + mini_portile2 (~> 2.8.0) racc (~> 1.4) octokit (4.25.1) faraday (>= 1, < 3) sawyer (~> 0.9) + ostruct (0.6.3) pathutil (0.16.2) forwardable-extended (~> 2.6) public_suffix (5.0.1) @@ -87,9 +88,12 @@ GEM rouge (3.30.0) ruby2_keywords (0.0.5) safe_yaml (1.0.5) - sass-embedded (1.58.3) + sass-embedded (1.58.3-arm64-darwin) + google-protobuf (~> 3.21) + sass-embedded (1.58.3-x86_64-darwin) + google-protobuf (~> 3.21) + sass-embedded (1.58.3-x86_64-linux-gnu) google-protobuf (~> 3.21) - rake (>= 10.0.0) sawyer (0.9.2) addressable (>= 2.3.5) faraday (>= 0.17.3, < 3) @@ -100,16 +104,22 @@ GEM PLATFORMS arm64-darwin-22 + arm64-darwin-25 x86_64-darwin-21 x86_64-linux DEPENDENCIES + base64 + bigdecimal + csv jekyll (~> 4.3) jekyll-default-layout jekyll-github-metadata (>= 2.15) jekyll-sitemap jekyll-target-blank just-the-docs (= 0.5.0) + logger + ostruct webrick (~> 1.7) BUNDLED WITH diff --git a/Index.md b/Index.md index dd40455..7f40d25 100644 --- a/Index.md +++ b/Index.md @@ -55,8 +55,8 @@ Your service starts with all of these endpoints ready: | `localhost:9090` | gRPC server | | `localhost:9091` | HTTP/REST gateway (auto-mapped from gRPC) | | `localhost:9091/metrics` | Prometheus metrics | -| `localhost:9091/healthcheck` | Kubernetes liveness probe | -| `localhost:9091/readycheck` | Kubernetes readiness probe | +| `localhost:9091/healthcheck` | Liveness probe — returns build/version info as JSON | +| `localhost:9091/readycheck` | Readiness probe — returns version JSON when ready | | `localhost:9091/swagger/` | Swagger UI | | `localhost:9091/debug/pprof/` | Go pprof profiling | diff --git a/Packages.md b/Packages.md index 1ff94d0..ffaacf4 100644 --- a/Packages.md +++ b/Packages.md @@ -3,6 +3,7 @@ layout: default title: Packages description: "ColdBrew packages documentation" permalink: /packages +nav_order: 9 --- # Packages {: .no_toc } @@ -19,7 +20,7 @@ The core module is the base module and provides the base implementation for Cold Documentation can be found at [core-docs] ### [Config] -Coldbrew config package contains the configuration for the core package. It uses [envconfig] to load the configuration from the environment variables. +ColdBrew config package contains the configuration for the core package. It uses [envconfig] to load the configuration from the environment variables. Documentation can be found at [config-docs] @@ -85,3 +86,4 @@ Documentation can be found at [data-builder-docs] [grpcpool-docs]: https://pkg.go.dev/github.com/go-coldbrew/grpcpool [Data Builder]: https://github.com/go-coldbrew/data-builder/tree/main#readme [data-builder-docs]: https://pkg.go.dev/github.com/go-coldbrew/data-builder +[envconfig]: https://github.com/kelseyhightower/envconfig diff --git a/USING.md b/USING.md index 954cfc9..3396f11 100644 --- a/USING.md +++ b/USING.md @@ -23,12 +23,19 @@ A ColdBrew project generated from the [cookiecutter template](https://github.com ``` MyApp/ proto/ # Protocol buffer definitions - helloworld.proto - server/ # gRPC service implementation - server.go + myapp.proto + service/ # gRPC service implementation + service.go + service_test.go + healthcheck.go + healthcheck_test.go + config/ + config.go # Configuration via environment variables + version/ + version.go # Build-time version info main.go # Entry point - Makefile # Build, test, lint, run targets - Dockerfile # Production container + Makefile # Build, test, lint, run targets + Dockerfile # Production container go.mod ``` @@ -51,12 +58,12 @@ The `google.api.http` annotation automatically creates a REST endpoint via grpc- After editing your proto file, regenerate the Go code: ```shell -make gen +make generate ``` ## Implementing Your Service -Implement the generated gRPC interface in your `server/server.go`: +Implement the generated gRPC interface in your `service/service.go`: ```go func (s *svcNameImpl) SayHello(ctx context.Context, req *pb.SayHelloRequest) (*pb.SayHelloResponse, error) { diff --git a/_config.yml b/_config.yml index 6637ddb..35d29dc 100644 --- a/_config.yml +++ b/_config.yml @@ -21,6 +21,8 @@ defaults: permalink: pretty +search_enabled: true + theme: just-the-docs callouts_level: quiet diff --git a/architecture.md b/architecture.md index 8c2ded8..e7fb6be 100644 --- a/architecture.md +++ b/architecture.md @@ -216,8 +216,9 @@ A typical ColdBrew service exposes two ports: ColdBrew is designed for Kubernetes deployments: -- **Liveness probe:** `GET /healthcheck` — returns `SERVING` when healthy -- **Readiness probe:** `GET /readycheck` — returns `SERVING` when ready for traffic +- **Liveness probe:** `GET /healthcheck` — returns build/version info as JSON (git commit, version, build date, Go version, OS/arch) +- **Readiness probe:** `GET /readycheck` — returns the same version JSON when ready for traffic, or an error if the service hasn't called `SetReady()` yet +- **gRPC health protocol:** Implements `grpc.health.v1.Health` ([standard gRPC health checking](https://github.com/grpc/grpc/blob/master/doc/health-checking.md)) on the gRPC port — used by gRPC load balancers, Envoy, Istio, and other service meshes for native health checking - **Graceful shutdown:** On SIGTERM, the service marks itself as not ready, drains in-flight requests, then exits cleanly - **Metrics scraping:** Prometheus scrapes `/metrics` on the HTTP port diff --git a/data-builder-example/go.mod b/data-builder-example/go.mod index 252fa6a..5d32d06 100644 --- a/data-builder-example/go.mod +++ b/data-builder-example/go.mod @@ -1,6 +1,6 @@ module example -go 1.19 +go 1.25.0 require github.com/go-coldbrew/data-builder v0.0.11 @@ -9,16 +9,16 @@ require ( github.com/go-coldbrew/tracing v0.0.1 // indirect github.com/goccy/go-graphviz v0.0.9 // indirect github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 // indirect - github.com/golang/protobuf v1.5.2 // indirect + github.com/golang/protobuf v1.5.4 // indirect github.com/newrelic/go-agent/v3 v3.9.0 // indirect github.com/opentracing/opentracing-go v1.2.0 // indirect github.com/pkg/errors v0.9.1 // indirect - golang.org/x/image v0.0.0-20200119044424-58c23975cae1 // indirect - golang.org/x/net v0.8.0 // indirect - golang.org/x/sys v0.6.0 // indirect - golang.org/x/text v0.8.0 // indirect + golang.org/x/image v0.38.0 // indirect + golang.org/x/net v0.52.0 // indirect + golang.org/x/sys v0.42.0 // indirect + golang.org/x/text v0.35.0 // indirect google.golang.org/genproto v0.0.0-20220519153652-3a47de7e79bd // indirect - google.golang.org/grpc v1.46.2 // indirect - google.golang.org/protobuf v1.28.0 // indirect + google.golang.org/grpc v1.79.3 // indirect + google.golang.org/protobuf v1.36.10 // indirect k8s.io/apimachinery v0.23.3 // indirect ) diff --git a/data-builder-example/go.sum b/data-builder-example/go.sum index 440a0a8..0858de7 100644 --- a/data-builder-example/go.sum +++ b/data-builder-example/go.sum @@ -38,8 +38,11 @@ github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+Ce github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs= github.com/bketelsen/crypt v0.0.3-0.20200106085610-5cbc8cc4026c/go.mod h1:MKsuJmJgSg28kpZDP6UIiPt0e0Oz0kqKNGyRaWEPv84= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= +github.com/cespare/xxhash v1.1.0 h1:a6HrQnmkObjyL+Gs60czilIUGqrzKutQD6XZog3p+ko= github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= +github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/cheggaaa/pb v2.0.7+incompatible/go.mod h1:pQciLPpbU0oxA0h+VJYYLxO+XeDQb5pZijXscXHm81s= github.com/cheggaaa/pb/v3 v3.0.4/go.mod h1:7rgWxLrAUcFMkvJuv09+DYi7mMUYi8nO9iOWcvGJPfw= github.com/cheggaaa/pb/v3 v3.0.8/go.mod h1:UICbiLec/XO6Hw6k+BHEtHeQFzzBH4i2/qk/ow1EJTA= @@ -109,6 +112,10 @@ github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V github.com/go-logr/logr v0.1.0/go.mod h1:ixOQHD9gLJUVQQ2ZOR7zLEifBX6tGkNJF4QyIY7sIas= github.com/go-logr/logr v0.2.0/go.mod h1:z6/tIYblkpsD+a4lm/fGIIU9mZ+XfAiaFtq7xTgseGU= github.com/go-logr/logr v1.2.0/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= +github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI= +github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= +github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= +github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= github.com/go-openapi/jsonpointer v0.19.3/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg= github.com/go-openapi/jsonpointer v0.19.5/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg= github.com/go-openapi/jsonreference v0.19.3/go.mod h1:rjx6GuL8TTa9VaixXglHmQmIL98+wF9xc8zWvFonSJ8= @@ -139,8 +146,9 @@ github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QD github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= -github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw= github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= +github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= +github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= @@ -150,7 +158,8 @@ github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/ github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= +github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= +github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/gofuzz v1.1.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= @@ -158,6 +167,8 @@ github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OI github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= +github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= github.com/googleapis/gnostic v0.5.1/go.mod h1:6U4PtQXGIEt/Z3h5MAT7FNofLnw9vXk2cUuW7uA/OeU= @@ -344,6 +355,7 @@ github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81P github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.8.2 h1:+h33VjcLVPDHtOdpUCuF+7gSuG3yGIftsP1YvFihtJ8= +github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw= github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= github.com/ugorji/go v1.1.4/go.mod h1:uQMGLiO92mf5W77hV/PUCpI3pbzQx3CRekS0kk+RGrc= @@ -359,9 +371,22 @@ github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1 go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU= go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8= +go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64= +go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y= +go.opentelemetry.io/otel v1.39.0 h1:8yPrr/S0ND9QEfTfdP9V+SiwT4E0G7Y5MO7p85nis48= +go.opentelemetry.io/otel v1.39.0/go.mod h1:kLlFTywNWrFyEdH0oj2xK0bFYZtHRYUdv1NklR/tgc8= +go.opentelemetry.io/otel/metric v1.39.0 h1:d1UzonvEZriVfpNKEVmHXbdf909uGTOQjA0HF0Ls5Q0= +go.opentelemetry.io/otel/metric v1.39.0/go.mod h1:jrZSWL33sD7bBxg1xjrqyDjnuzTUB0x1nBERXd7Ftcs= +go.opentelemetry.io/otel/sdk v1.39.0 h1:nMLYcjVsvdui1B/4FRkwjzoRVsMK8uL/cj0OyhKzt18= +go.opentelemetry.io/otel/sdk v1.39.0/go.mod h1:vDojkC4/jsTJsE+kh+LXYQlbL8CgrEcwmt1ENZszdJE= +go.opentelemetry.io/otel/sdk/metric v1.39.0 h1:cXMVVFVgsIf2YL6QkRF4Urbr/aMInf+2WKg+sEJTtB8= +go.opentelemetry.io/otel/sdk/metric v1.39.0/go.mod h1:xq9HEVH7qeX69/JnwEfp6fVq5wosJsY1mt4lLfYdVew= +go.opentelemetry.io/otel/trace v1.39.0 h1:2d2vfpEDmCJ5zVYz7ijaJdOF59xLomrvj7bjt6/qCJI= +go.opentelemetry.io/otel/trace v1.39.0/go.mod h1:88w4/PnZSazkGzz/w84VHpQafiU4EtqqlVdxWy+rNOA= go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI= go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= go.uber.org/goleak v1.1.12 h1:gZAh5/EyT/HQwlpkCy6wTpqfH9H8Lz8zbm3dZh+OyzA= +go.uber.org/goleak v1.1.12/go.mod h1:cwTWslyiVhfpKIDGSZEM2HlOvcqm+tG4zioyIeLoqMQ= go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0= go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= @@ -384,8 +409,9 @@ golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm0 golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY= golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= -golang.org/x/image v0.0.0-20200119044424-58c23975cae1 h1:5h3ngYt7+vXCDZCup/HkCQgW5XwmSvR/nA2JmJ0RErg= golang.org/x/image v0.0.0-20200119044424-58c23975cae1/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= +golang.org/x/image v0.38.0 h1:5l+q+Y9JDC7mBOMjo4/aPhMDcxEptsX+Tt3GgRQRPuE= +golang.org/x/image v0.38.0/go.mod h1:/3f6vaXC+6CEanU4KJxbcUZyEePbyKbaLoDOe4ehFYY= golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= @@ -427,8 +453,8 @@ golang.org/x/net v0.0.0-20210326060303-6b1517762897/go.mod h1:uSPa2vr4CLtc/ILN5o golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= golang.org/x/net v0.0.0-20210520170846-37e1c6afe023/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20211209124913-491a49abca63/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= -golang.org/x/net v0.8.0 h1:Zrh2ngAOFYneWTAIAPethzeaQLuHwhuBkuV6ZiRnUaQ= -golang.org/x/net v0.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc= +golang.org/x/net v0.52.0 h1:He/TN1l0e4mmR3QqHMT2Xab3Aj3L9qjbhRm78/6jrW0= +golang.org/x/net v0.52.0/go.mod h1:R1MAz7uMZxVMualyPXb+VaqGSa3LIaUqk0eEt3w36Sw= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= @@ -482,8 +508,8 @@ golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210514084401-e8d321eab015/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210831042530-f4d43177bf5e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.6.0 h1:MVltZSvRTcU2ljQOhs94SXPftV6DCNnZViHeQps87pQ= -golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.42.0 h1:omrd2nAlyT5ESRdCLYdm3+fMfNFE/+Rf4bDIQImRJeo= +golang.org/x/sys v0.42.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210503060354-a79de5458b56/go.mod h1:tfny5GFUkzUvx4ps4ajbZsCe5lw1metzhBm9T3x7oIY= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= @@ -493,8 +519,8 @@ golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= -golang.org/x/text v0.8.0 h1:57P1ETyNKtuIjB4SRd15iJxuhj8Gc416Y78H3qgMh68= -golang.org/x/text v0.8.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= +golang.org/x/text v0.35.0 h1:JOVx6vVDFokkpaq1AEptVzLTpDe9KGpj5tR4/X+ybL8= +golang.org/x/text v0.35.0/go.mod h1:khi/HExzZJ2pGnjenulevKNX1W67CUy0AsXcNubPGCA= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= @@ -525,6 +551,8 @@ golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8T golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +gonum.org/v1/gonum v0.16.0 h1:5+ul4Swaf3ESvrOnidPp4GZbzf0mxVQpDCYUQE7OJfk= +gonum.org/v1/gonum v0.16.0/go.mod h1:fef3am4MQ93R2HHpKnLk4/Tbh/s0+wqD5nfa6Pnwy4E= google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE= google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M= google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= @@ -558,8 +586,8 @@ google.golang.org/grpc v1.33.1/go.mod h1:fr5YgcSWrqhRRxogOsw7RzIpsmvOZ6IcH4kBYTp google.golang.org/grpc v1.34.0/go.mod h1:WotjhfgOW/POjDeRt8vscBtXq+2VjORFy659qA51WJ8= google.golang.org/grpc v1.36.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= google.golang.org/grpc v1.46.0/go.mod h1:vN9eftEi1UMyUsIF80+uQXhHjbXYbm0uXoFCACuMGWk= -google.golang.org/grpc v1.46.2 h1:u+MLGgVf7vRdjEYZ8wDFhAVNmhkbJ5hmrA1LMWK1CAQ= -google.golang.org/grpc v1.46.2/go.mod h1:vN9eftEi1UMyUsIF80+uQXhHjbXYbm0uXoFCACuMGWk= +google.golang.org/grpc v1.79.3 h1:sybAEdRIEtvcD68Gx7dmnwjZKlyfuc61Dyo9pGXXkKE= +google.golang.org/grpc v1.79.3/go.mod h1:KmT0Kjez+0dde/v2j9vzwoAScgEPx/Bw1CYChhHLrHQ= google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= @@ -573,8 +601,9 @@ google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlba google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= -google.golang.org/protobuf v1.28.0 h1:w43yiav+6bVFTBQFZX0r7ipe9JQ1QsbMgHwbBziscLw= google.golang.org/protobuf v1.28.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= +google.golang.org/protobuf v1.36.10 h1:AYd7cD/uASjIL6Q9LiTjz8JLcrh/88q5UObnmY3aOOE= +google.golang.org/protobuf v1.36.10/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco= gopkg.in/VividCortex/ewma.v1 v1.1.1/go.mod h1:TekXuFipeiHWiAlO1+wSS23vTcyFau5u3rxXUSXj710= gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= @@ -610,6 +639,7 @@ gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= diff --git a/howto/APIs.md b/howto/APIs.md index 5169f96..4850e1c 100644 --- a/howto/APIs.md +++ b/howto/APIs.md @@ -11,9 +11,9 @@ parent: "How To" ## Introduction -Coldbrew is gRPC first, which means that gRPC APIs are the primary APIs and HTTP/JSON APIs are generated from the gRPC APIs. This approach is different from other frameworks where HTTP/JSON APIs are independent from gRPC APIs. +ColdBrew is gRPC first, which means that gRPC APIs are the primary APIs and HTTP/JSON APIs are generated from the gRPC APIs. This approach is different from other frameworks where HTTP/JSON APIs are independent from gRPC APIs. -Coldbrew uses [grpc-gateway] to generate HTTP/JSON APIs from gRPC APIs. It reads protobuf service definitions and generates a reverse-proxy server which translates a RESTful HTTP API into gRPC. This server is generated according to the [google.api.http annotations] in your service definitions. +ColdBrew uses [grpc-gateway] to generate HTTP/JSON APIs from gRPC APIs. It reads protobuf service definitions and generates a reverse-proxy server which translates a RESTful HTTP API into gRPC. This server is generated according to the [google.api.http annotations] in your service definitions. {: .note} To learn more about HTTP to gRPC API mapping please refer to [gRPC Gateway mapping] examples. @@ -47,7 +47,7 @@ message UpperResponse{ The above example adds a new API endpoint to the service which converts the input string to upper case. The endpoint is available at `/api/v1/example/upper` on the HTTP port and `example.v1.MySvc/Upper` on the gRPC port. -Run `make generate` (for [Coldbrew cookiecutter]) or `protoc`/`buf` with [grpc-gateway plugin] for others to generate the gRPC and HTTP code. +Run `make generate` (for [ColdBrew cookiecutter]) or `protoc`/`buf` with [grpc-gateway plugin] for others to generate the gRPC and HTTP code. In your service implement the gRPC server interface @@ -60,7 +60,7 @@ func (s *svc) Upper(_ context.Context, req *proto.UpperRequest) (*proto.UpperRes } ``` -Run your server (`make run` for [Coldbrew cookiecutter]) and send a request to the HTTP endpoint: +Run your server (`make run` for [ColdBrew cookiecutter]) and send a request to the HTTP endpoint: ```bash $ curl -X POST -d '{"msg":"hello"}' -i http://localhost:9091/api/v1/example/upper @@ -84,7 +84,7 @@ $ grpcurl -plaintext -d '{"msg": "hello"}' localhost:9090 example.v1.MySvc/Upper ## HTTP Content-Type -Coldbrew supports multiple content-types for requests and responses. The default content-type is `application/json`. The following content-types are supported by default: +ColdBrew supports multiple content-types for requests and responses. The default content-type is `application/json`. The following content-types are supported by default: - `application/json` - `application/proto` @@ -211,7 +211,7 @@ message Status { ``` ### gRPC status codes and HTTP status codes mapping -gRPC status codes can be easlity translated to HTTP status codes. The following table shows the mapping between the canonical error codes and HTTP status codes: +gRPC status codes can be easily translated to HTTP status codes. The following table shows the mapping between the canonical error codes and HTTP status codes: | gRPC status code | HTTP status code | | -------------------- | ---------------- | @@ -372,9 +372,9 @@ $ curl -X GET localhost:8080/v1/books/ } ``` -### Using Coldbrew errors package +### Using ColdBrew errors package -All the above examples can be used with the [Coldbrew errors package] by using the functions `NewWithStatus/WrapWithStatus` +All the above examples can be used with the [ColdBrew errors package] by using the functions `NewWithStatus/WrapWithStatus` ```go import ( @@ -399,7 +399,7 @@ func (s *server) GetBook(ctx context.Context, req *pb.GetBookRequest) (*pb.Book, Using the `errors.WrapWithStatus` function has the same effect as `errors.Wrap` but it also sets the status code of the error to the status code of the `google.rpc.Status` message. Similarly, the `errors.NewWithStatus` function has the same effect as `errors.New` but it also sets the status code of the error to the status code of the `google.rpc.Status` message. -Coldbrew errors package also provides stack trace support for errors, which can make debugging easier. For more information see Coldbrew [errors package]. +ColdBrew errors package also provides stack trace support for errors, which can make debugging easier. For more information see ColdBrew [errors package]. ## Customizing HTTP Error Responses @@ -535,13 +535,13 @@ For more advanced customization options, refer to the [grpc-gateway customizatio --- [google/rpc/status.proto]: https://github.com/googleapis/googleapis/blob/master/google/rpc/status.proto [google/rpc/code.proto]: https://github.com/googleapis/googleapis/blob/master/google/rpc/code.proto -[Coldbrew errors package]: https://pkg.go.dev/github.com/go-coldbrew/errors#NewWithStatus +[ColdBrew errors package]: https://pkg.go.dev/github.com/go-coldbrew/errors#NewWithStatus [errors package]: https://pkg.go.dev/github.com/go-coldbrew/errors [envconfig]: https://github.com/kelseyhightower/envconfig -[Coldbrew]: https://docs.coldbrew.cloud +[ColdBrew]: https://docs.coldbrew.cloud [google.api.http annotations]: https://cloud.google.com/endpoints/docs/grpc/transcoding [grpc-gateway]: https://grpc-ecosystem.github.io/grpc-gateway/ [gRPC Gateway mapping]: https://grpc-ecosystem.github.io/grpc-gateway/docs/mapping/examples/ [grpc-gateway plugin]: https://grpc-ecosystem.github.io/grpc-gateway/docs/tutorials/generating_stubs/ -[Coldbrew cookiecutter]: /getting-started#using-the-coldbrew-cookiecutter-template +[ColdBrew cookiecutter]: /getting-started#using-the-coldbrew-cookiecutter-template [grpc-gateway customization guide]: https://grpc-ecosystem.github.io/grpc-gateway/docs/mapping/customizing_your_gateway/ diff --git a/howto/Debugging.md b/howto/Debugging.md index e373e59..16b8432 100644 --- a/howto/Debugging.md +++ b/howto/Debugging.md @@ -2,6 +2,7 @@ layout: default title: "Debugging" parent: "How To" +description: "Debugging ColdBrew services with pprof and log overrides" --- ## Table of contents {: .no_toc .text-delta } @@ -39,7 +40,7 @@ Showing top 5 nodes out of 45 ### Analyzing profiles -The `go tool pprof` command can be also be used analyze profiles to find the root cause of performance issues. For more information, please refer to the [pprof walkthrough] and the [diagnostics doc]. +The `go tool pprof` command can also be used to analyze profiles to find the root cause of performance issues. For more information, please refer to the [pprof walkthrough] and the [diagnostics doc]. {: .important } Its recommended that you go though the [pprof walkthrough] to get a better understanding of how to use the pprof. diff --git a/howto/Log.md b/howto/Log.md index df87fc3..2e5f266 100644 --- a/howto/Log.md +++ b/howto/Log.md @@ -2,6 +2,7 @@ layout: default title: "Log" parent: "How To" +description: "Context-aware logging and trace ID propagation in ColdBrew" --- ## Table of contents {: .no_toc .text-delta } diff --git a/howto/Metrics.md b/howto/Metrics.md index 2386375..7e79bea 100644 --- a/howto/Metrics.md +++ b/howto/Metrics.md @@ -2,6 +2,7 @@ layout: default title: "Metrics" parent: "How To" +description: "Prometheus metrics and custom metrics in ColdBrew" --- ## Table of contents {: .no_toc .text-delta } @@ -9,9 +10,9 @@ parent: "How To" 1. TOC {:toc} -## How Metrics Work in Coldbrew +## How Metrics Work in ColdBrew -Coldbrew uses [Prometheus](https://prometheus.io/) to collect service metrics. By Default, Coldbrew will expose a `/metrics` endpoint that will return the metrics in the [Prometheus exposition format](https://prometheus.io/docs/instrumenting/exposition_formats/) on the configured [HTTP port]. +ColdBrew uses [Prometheus](https://prometheus.io/) to collect service metrics. By default, ColdBrew will expose a `/metrics` endpoint that will return the metrics in the [Prometheus exposition format](https://prometheus.io/docs/instrumenting/exposition_formats/) on the configured [HTTP port]. A collection of metrics are collected by default, including: * Golang runtime metrics (e.g. memory usage, goroutine count, etc.) @@ -45,7 +46,7 @@ func myFunction() { } ``` -These metrics will be automatically collected and exposed by Coldbrew on the `/metrics` endpoint. +These metrics will be automatically collected and exposed by ColdBrew on the `/metrics` endpoint. {: .note .note-info } To learn more about the Prometheus and the data types it supports, see [here](https://prometheus.io/docs/concepts/metric_types/) diff --git a/howto/Tracing.md b/howto/Tracing.md index 0bef011..2663397 100644 --- a/howto/Tracing.md +++ b/howto/Tracing.md @@ -11,7 +11,7 @@ parent: "How To" ## Overview -Coldbrew provides a way to add tracing to your functions using the [go-coldbrew/tracing] package. The Package implements multiple tracing backends (e.g. [New Relic] / [Opentelemetry] / [Jaeger]) which enables you to switch between them without changing your code. +ColdBrew provides a way to add tracing to your functions using the [go-coldbrew/tracing] package. The package implements multiple tracing backends (e.g. [New Relic] / [Opentelemetry] / [Jaeger]) which enables you to switch between them without changing your code. {: .note .note-info } Its possible for you to have multiple backends enabled at the same time, for example you can have both [New Relic] and [Opentelemetry] enabled at the same time in the same span and they will both receive the same trace. diff --git a/howto/data-builder.md b/howto/data-builder.md index ec09cb7..d3774e7 100644 --- a/howto/data-builder.md +++ b/howto/data-builder.md @@ -76,7 +76,7 @@ func BuildAppResponse(_ context.Context, grossPrice GrossPrice, priceAdjustment Note that the builder function signatures must satisfy the following requirements: 1. The first argument is a context.Context -2. All subsequent arguments are stucts +2. All subsequent arguments are structs 3. There are two return values: a struct and an error @@ -120,7 +120,7 @@ During compilation we resolve all dependencies and build an execution plan. Note After compilation we can also inspect the dependency graph visually by calling `BuildGraph`: -![dependency graph](../../assets/images/data-builder.svg) +![dependency graph](/assets/images/data-builder.svg) ### Running the execution plan and retrieving the results diff --git a/howto/errors.md b/howto/errors.md index c03a806..b0e46f0 100644 --- a/howto/errors.md +++ b/howto/errors.md @@ -184,7 +184,7 @@ name := notifier.GetTraceHeaderName() ## Examples -### Sending errors to provides +### Sending errors to providers The ColdBrew [notifier package] can be used to send errors to different providers like [Sentry], [Airbrake], [Rollbar] etc. diff --git a/howto/gRPC.md b/howto/gRPC.md index 55bee02..1a8afbe 100644 --- a/howto/gRPC.md +++ b/howto/gRPC.md @@ -2,7 +2,7 @@ layout: default title: "gRPC" parent: "How To" -description: "How to use gRPC with Coldbrew" +description: "How to use gRPC with ColdBrew" --- ## Table of contents {: .no_toc .text-delta } @@ -14,17 +14,17 @@ description: "How to use gRPC with Coldbrew" {: .note} If you are not familiar with gRPC, you can learn more about it at [grpc.io](https://grpc.io/). -## Using gRPC with Coldbrew +## Using gRPC with ColdBrew -Coldbrew is gRPC first, which means that gRPC APIs are the primary APIs and HTTP/JSON APIs are generated from the gRPC APIs. This approach is different from other frameworks where HTTP/JSON APIs are independent from gRPC APIs. +ColdBrew is gRPC first, which means that gRPC APIs are the primary APIs and HTTP/JSON APIs are generated from the gRPC APIs. This approach is different from other frameworks where HTTP/JSON APIs are independent from gRPC APIs. -Best way to get started with gRPC in Coldbrew is to use the [Coldbrew cookiecutter] to generate a new project. The cookiecutter will generate a project with a sample gRPC service and a sample HTTP/JSON service. You can use the sample gRPC service as a template to create your own gRPC service. +The best way to get started with gRPC in ColdBrew is to use the [ColdBrew cookiecutter] to generate a new project. The cookiecutter will generate a project with a sample gRPC service and a sample HTTP/JSON service. You can use the sample gRPC service as a template to create your own gRPC service. You can than follow the `README.md` in the project or [Building and Configuring APIs] how to see how to use the generated service. -## Client side connection pool +## Client-side connection pool -Coldbrew provides a simple gRPC connection pool implementation called [grpcpool]. You can use this package to create a connection pool for your gRPC services. +ColdBrew provides a simple gRPC connection pool implementation called [grpcpool]. You can use this package to create a connection pool for your gRPC services. The package provides a [grpcpool.Dial] function that can be used to create a connection pool for a gRPC service. The function takes a `grpc.DialOption` as an argument. You can use this option to configure the gRPC client connection. For example, you can use this option to configure TLS, authentication, etc. @@ -35,12 +35,13 @@ The following example shows how to create a connection pool for a gRPC service: import ( "github.com/go-coldbrew/grpcpool" "google.golang.org/grpc" + "google.golang.org/grpc/credentials/insecure" ) func main() { // Create a connection pool for a gRPC service with 3 connections. - pool, err := grpcpool.Dial("localhost:50051", 3, grpc.WithInsecure()) + pool, err := grpcpool.Dial("localhost:50051", 3, grpc.WithTransportCredentials(insecure.NewCredentials())) if err != nil { // Handle error. } @@ -60,13 +61,17 @@ func main() { ```go import ( + "context" + "fmt" + "github.com/go-coldbrew/grpcpool" "google.golang.org/grpc" + "google.golang.org/grpc/credentials/insecure" ) func main() { // Create a connection pool for a gRPC service with 2 connections. - pool, err := grpcpool.Dial("localhost:50051", 2, grpc.WithInsecure()) + pool, err := grpcpool.Dial("localhost:50051", 2, grpc.WithTransportCredentials(insecure.NewCredentials())) if err != nil { // Handle error. } @@ -81,8 +86,8 @@ func main() { } fmt.Println(resp.Message) - // Close the connection. - conn.Close() + // Close the pool. + pool.Close() } ``` @@ -91,13 +96,17 @@ You can also use existing gRPC connections with [grpcpool] by wrapping it with [ ```go import ( + "context" + "fmt" + "github.com/go-coldbrew/grpcpool" "google.golang.org/grpc" + "google.golang.org/grpc/credentials/insecure" ) func main() { // Create a gRPC connection. - conn, err := grpc.Dial("localhost:50051", grpc.WithInsecure()) + conn, err := grpc.NewClient("localhost:50051", grpc.WithTransportCredentials(insecure.NewCredentials())) if err != nil { // Handle error. } @@ -121,7 +130,7 @@ func main() { ``` --- -[Coldbrew cookiecutter]: /getting-started +[ColdBrew cookiecutter]: /getting-started [Building and Configuring APIs]: /howto/APIs [grpcpool]: https://pkg.go.dev/github.com/go-coldbrew/grpcpool [grpcpool.Dial]: https://pkg.go.dev/github.com/go-coldbrew/grpcpool#Dial diff --git a/howto/index.md b/howto/index.md index e0b4581..ef701e4 100644 --- a/howto/index.md +++ b/howto/index.md @@ -9,7 +9,7 @@ has_toc: true --- # How To -This section contains a collection of how to guides for Coldbrew. +This section contains a collection of how-to guides for ColdBrew. If you have a How To that you would like to share, please [open an issue](https://github.com/go-coldbrew/docs.coldbrew.cloud/issues) diff --git a/howto/interceptors.md b/howto/interceptors.md index 7d78381..9f4b716 100644 --- a/howto/interceptors.md +++ b/howto/interceptors.md @@ -2,6 +2,7 @@ layout: default title: "Interceptors" parent: "How To" +description: "Configuring gRPC interceptors in ColdBrew" --- ## Table of contents {: .no_toc .text-delta } @@ -17,7 +18,7 @@ To disable coldbrew provided interceptors you can call the function [UseColdBrew ## Response Time Logging -Coldbrew uses interceptors to implement response time logging in [ResponseTimeLoggingInterceptor]. The interceptor is enabled by default and logs the response time of each request in the following format: +ColdBrew uses interceptors to implement response time logging in [ResponseTimeLoggingInterceptor]. The interceptor is enabled by default and logs the response time of each request in the following format: ```json {"@timestamp":"2023-04-23T22:07:38.857192+08:00","caller":"interceptors@v0.1.7/interceptors.go:248","error":null,"grpcMethod":"/com.github.ankurs.MySvc/Echo","level":"info","took":"49.542µs","trace":"50337410-4bcd-48ce-b8d4-6b42f2ac5503"} @@ -25,7 +26,7 @@ Coldbrew uses interceptors to implement response time logging in [ResponseTimeLo ### Filtering response time logs -Its possible to filter out response time logs message by using a [FilterFunc], Coldbrew provides a [default filter function] implementation that filter out common logs like healthchek, readycheck, server reflection, etc. +It's possible to filter out response time log messages by using a [FilterFunc]. ColdBrew provides a [default filter function] implementation that filters out common logs like healthcheck, readycheck, server reflection, etc. You can add more methods to filter out by appending to the default [FilterMethods] list. For example, to filter out all methods that starts with `com.github.ankurs.MySvc/`: @@ -94,13 +95,14 @@ import ( "github.com/go-coldbrew/interceptors" "github.com/go-coldbrew/log" "google.golang.org/grpc" + "google.golang.org/grpc/credentials/insecure" ) func main() { ctx := context.Background() - conn, err := grpc.Dial( + conn, err := grpc.NewClient( "localhost:8080", - grpc.WithInsecure(), + grpc.WithTransportCredentials(insecure.NewCredentials()), // Add the ColdBrew interceptors to your gRPC client to add tracing/metrics to your gRPC client calls grpc.WithChainUnaryInterceptor(interceptors.DefaultClientInterceptors()...), ) diff --git a/howto/signals.md b/howto/signals.md index e88b131..dc9a43a 100644 --- a/howto/signals.md +++ b/howto/signals.md @@ -2,7 +2,7 @@ layout: default title: "Signal Handling and Graceful Shutdown" parent: "How To" -description: "How POSIX signals handling works in Coldbrew" +description: "How POSIX signal handling works in ColdBrew" --- ## Table of contents {: .no_toc .text-delta } @@ -12,23 +12,23 @@ description: "How POSIX signals handling works in Coldbrew" {: .important} -This page is only aplicable to applications using `go-coldbrew/core` package and applications created by [Coldbrew cookiecutter]. +This page is only applicable to applications using `go-coldbrew/core` package and applications created by [ColdBrew cookiecutter]. ## Overview -Coldbrew applications are built on top of [go-coldbrew/core] package, have the ability to handle [POSIX signals]. +ColdBrew applications are built on top of [go-coldbrew/core] package, have the ability to handle [POSIX signals]. This is useful when you want to gracefully shutdown your application, specially when you are running your application on a platform like [Kubernetes] that expects your application to gracefully shutdown during a rolling update or scale down. ## How it works -When you start your application, Coldbrew will register a signal handler for `SIGINT` and `SIGTERM` signals. When the application receives one of these signals, it will start a graceful shutdown process. +When you start your application, ColdBrew will register a signal handler for `SIGINT` and `SIGTERM` signals. When the application receives one of these signals, it will start a graceful shutdown process. ## Graceful shutdown When the application receives a signal, it will start a graceful shutdown process. This process will do the following: - 1. Fail the readyness check + 1. Fail the readiness check 2. Wait for all requests to finish 3. Shutdown the application when all requests are finished or after a timeout @@ -37,30 +37,30 @@ When the application receives a signal, it will start a graceful shutdown proces Configuring the shutdown process is done by setting the [config] values: - `SHUTDOWN_DURATION_IN_SECONDS` - The duration in seconds to wait for all requests to finish before shutting down the application. -- `GRPC_GRACEFUL_DURATION_IN_SECONDS` - The duration in seconds for which Coldbrew will wait for propogation of readyness check failure to the load balancer. -- `DISABLE_SIGNAL_HANDLER` - If set to `true`, Coldbrew will not register a signal handler (this is usefule when you want to handle signals yourself). +- `GRPC_GRACEFUL_DURATION_IN_SECONDS` - The duration in seconds for which ColdBrew will wait for propagation of readiness check failure to the load balancer. +- `DISABLE_SIGNAL_HANDLER` - If set to `true`, ColdBrew will not register a signal handler (this is useful when you want to handle signals yourself). ## Cleanup before shutdown -If you service implements [CBStopper] interface, Coldbrew will call the `Stop` method when the application is shutting down. This is useful if you want to perform some cleanup before the application shuts down. +If your service implements [CBStopper] interface, ColdBrew will call the `Stop` method when the application is shutting down. This is useful if you want to perform some cleanup before the application shuts down. {: .important} -Coldbrew will call the `Stop` method after all requests have finished or after the `SHUTDOWN_DURATION_IN_SECONDS` timeout has expired. +ColdBrew will call the `Stop` method after all requests have finished or after the `SHUTDOWN_DURATION_IN_SECONDS` timeout has expired. -## Kubernetes liveness and readyness probes +## Kubernetes liveness and readiness probes When you are running your application on [Kubernetes], you can configure your `livenessProbe` and `readinessProbe` to use the `/healthcheck` and `/readycheck` endpoints. This will ensure that your application is restarted if it is not responding to requests and that your application is not sent new requests when it is shutting down. ## Why do I see 5xx errors when my application is shutting down? -When you shutdown your application, Coldbrew will fail the readyness check. This will cause the load balancer to stop sending new requests to your application. However, there might be some requests that are already in flight. These requests will still be processed by your application. +When you shut down your application, ColdBrew will fail the readiness check. This will cause the load balancer to stop sending new requests to your application. However, there might be some requests that are already in flight. These requests will still be processed by your application. If you want to avoid this, you can set the `SHUTDOWN_DURATION_IN_SECONDS` to a value that is greater than the maximum time it takes to process a request. This will ensure that all requests are finished before the application shuts down. However, this will also increase the time it takes to shutdown the application. -Make sure you configure your load balancer to stop sending new requests to your application after readyness check fails. This will ensure that no new requests are sent to your application when it is shutting down. +Make sure you configure your load balancer to stop sending new requests to your application after readiness check fails. This will ensure that no new requests are sent to your application when it is shutting down. --- -[Coldbrew cookiecutter]: /getting-started#using-the-coldbrew-cookiecutter-template +[ColdBrew cookiecutter]: /getting-started#using-the-coldbrew-cookiecutter-template [go-coldbrew/core]: https://pkg.go.dev/github.com/go-coldbrew/core [config]: https://pkg.go.dev/github.com/go-coldbrew/core/config#Config [CBStopper]: https://pkg.go.dev/github.com/go-coldbrew/core#CBStopper diff --git a/howto/swagger.md b/howto/swagger.md index 841493a..aa535bd 100644 --- a/howto/swagger.md +++ b/howto/swagger.md @@ -2,7 +2,7 @@ layout: default title: "Swagger / Open API Support" parent: "How To" -description: "How Coldbrew supports Swagger / Open API" +description: "How ColdBrew supports Swagger / Open API" --- ## Table of contents {: .no_toc .text-delta } @@ -11,15 +11,15 @@ description: "How Coldbrew supports Swagger / Open API" {:toc} {: .important} -This page is only aplicable to applications using `go-coldbrew/core` package and applications created by [Coldbrew cookiecutter]. +This page is only applicable to applications using `go-coldbrew/core` package and applications created by [ColdBrew cookiecutter]. ## Overview -Coldbrew supports [Swagger](https://swagger.io/) / [Open API](https://www.openapis.org/) out of the box. Coldbrew automatically generates Swagger / Open API specification for your APIs. +ColdBrew supports [Swagger](https://swagger.io/) / [Open API](https://www.openapis.org/) out of the box. ColdBrew automatically generates Swagger / Open API specification for your APIs. -This makes it easy to use tools like [Swagger UI](https://swagger.io/tools/swagger-ui/) to explore and test your APIs. Coldbrew also bundles [Swagger UI](https://swagger.io/tools/swagger-ui/) and serves it at the `/swagger/` URL on the Coldbrew server. +This makes it easy to use tools like [Swagger UI](https://swagger.io/tools/swagger-ui/) to explore and test your APIs. ColdBrew also bundles [Swagger UI](https://swagger.io/tools/swagger-ui/) and serves it at the `/swagger/` URL on the ColdBrew server. -Since Coldbrew using grpc-gateway to generate RESTful APIs, the generated Swagger / Open API specification is based on the [grpc-gateway's Open API specification] documentation. +Since ColdBrew uses grpc-gateway to generate RESTful APIs, the generated Swagger / Open API specification is based on the [grpc-gateway's Open API specification] documentation. ## Adding OpenAPI annotations to your APIs @@ -27,9 +27,9 @@ To learn how to add OpenAPI annotations to your APIs, please refer to [grpc-gate ## How to access the Swagger / Open API specification -You can access the generated Swagger / Open API specification at the `/swagger/` URL on the Coldbrew server. For example, if your Coldbrew server is running on `http://localhost:9091`, you can access the Swagger at [http://localhost:9091/swagger/](http://localhost:9091/swagger/) and Open API specification [http://localhost:9091/swagger/myapp.swagger.json](http://localhost:9091/swagger/myapp.swagger.json) +You can access the generated Swagger / Open API specification at the `/swagger/` URL on the ColdBrew server. For example, if your ColdBrew server is running on `http://localhost:9091`, you can access the Swagger at [http://localhost:9091/swagger/](http://localhost:9091/swagger/) and Open API specification [http://localhost:9091/swagger/myapp.swagger.json](http://localhost:9091/swagger/myapp.swagger.json) -![](../../assets/images/swagger.png) +![Swagger UI example page](/assets/images/swagger.png) ## Configuration @@ -84,4 +84,4 @@ func main() { [Config]: https://pkg.go.dev/github.com/go-coldbrew/core/config#Config [SetOpenAPIHandler]: https://pkg.go.dev/github.com/go-coldbrew/core#CB [grpc-gateway's Open API specification]: https://grpc-ecosystem.github.io/grpc-gateway/docs/mapping/customizing_openapi_output/ -[Coldbrew cookiecutter]: /getting-started +[ColdBrew cookiecutter]: /getting-started diff --git a/integrations.md b/integrations.md index 8cf2c48..0f63f6a 100644 --- a/integrations.md +++ b/integrations.md @@ -7,11 +7,11 @@ nav_order: 7 --- # Integrations {: .no_toc } -Coldbrew is designed to be very thin wrappers over other services and tools. This page lists the services and tools that Coldbrew integrates with. +ColdBrew provides thin wrappers over other services and tools. This page lists the services and tools that ColdBrew integrates with. -These integrations are optional and you can choose to use them or not. You can also choose to use other services and tools instead of the ones listed here. Coldbrew is designed to be flexible and easy to integrate with other services and tools. +These integrations are optional and you can choose to use them or not. You can also choose to use other services and tools instead of the ones listed here. ColdBrew is designed to be flexible and easy to integrate with other services and tools. -If you want to integrate Coldbrew with a service or tool that is not listed here, please [open an issue]. +If you want to integrate ColdBrew with a service or tool that is not listed here, please [open an issue]. ## Table of contents {: .no_toc .text-delta } @@ -21,9 +21,9 @@ If you want to integrate Coldbrew with a service or tool that is not listed here ## GRPC Gateway -Coldbrew relies on [GRPC Gateway] to expose the gRPC API as a REST API. The gateway is a proxy that translates a RESTful HTTP API into gRPC. It's a great tool to expose gRPC services to the web and mobile clients. +ColdBrew relies on [GRPC Gateway] to expose the gRPC API as a REST API. The gateway is a proxy that translates a RESTful HTTP API into gRPC. It's a great tool to expose gRPC services to the web and mobile clients. -To see how it works in Coldbrew, check out the [gRPC Gateway example]. +To see how it works in ColdBrew, check out the [gRPC Gateway example]. ## New Relic @@ -40,19 +40,19 @@ To configure New Relic, set the following environment variables as defined in [C ### Initialising -If you app is using [Coldbrew cookiecutter] template, initialisation is done automatically. +If your app is using [ColdBrew cookiecutter] template, initialisation is done automatically. -If you are using Coldbrew packages in your app, you need to initialise New Relic manually. To initialise New Relic use the [SetupNewRelic] function and to initialise New Relic Opentelemetry use the [SetupNROpenTelemetry] function from the `go-coldbrew/core` package. +If you are using ColdBrew packages in your app, you need to initialise New Relic manually. To initialise New Relic use the [SetupNewRelic] function and to initialise New Relic Opentelemetry use the [SetupNROpenTelemetry] function from the `go-coldbrew/core` package. ### Using -To use New Relic tracing in your app, use the Coldbrew [tracing] and [interceptors] packages. They will setup the New Relic tracing provider and add the tracing middleware to the gRPC and HTTP servers. +To use New Relic tracing in your app, use the ColdBrew [tracing] and [interceptors] packages. They will setup the New Relic tracing provider and add the tracing middleware to the gRPC and HTTP servers. You can also add more tracing to your app by [adding tracing] to your functions. ## Prometheus -Coldbrew uses [Prometheus] to collect metrics from the services. Prometheus is an open-source systems monitoring and alerting toolkit originally built at SoundCloud. It includes a time series database, a query language, and a visualization UI. +ColdBrew uses [Prometheus] to collect metrics from the services. Prometheus is an open-source systems monitoring and alerting toolkit originally built at SoundCloud. It includes a time series database, a query language, and a visualization UI. ### Configuring @@ -62,13 +62,13 @@ To configure Prometheus, set the following environment variables as defined in [ ### Initialising -If you app is using [Coldbrew cookiecutter] template, initialisation is done automatically. +If your app is using [ColdBrew cookiecutter] template, initialisation is done automatically. -If you are using Coldbrew packages in your app, you need to initialise Prometheus manually. Make sure you expose Prometheus `/metrics` endpoint in your app and add the [interceptors] to your gRPC and HTTP servers. +If you are using ColdBrew packages in your app, you need to initialise Prometheus manually. Make sure you expose Prometheus `/metrics` endpoint in your app and add the [interceptors] to your gRPC and HTTP servers. ### Using -Coldbrew uses the [prometheus/client_golang] package to collect metrics. To see how to use it check out the [metrics documentation]. +ColdBrew uses the [prometheus/client_golang] package to collect metrics. To see how to use it check out the [metrics documentation]. ## Sentry @@ -84,9 +84,9 @@ To configure Sentry, set the following environment variables as defined in [Conf ### Initialising -If you app is using [Coldbrew cookiecutter] template, initialisation is done automatically. +If your app is using [ColdBrew cookiecutter] template, initialisation is done automatically. -If you are using Coldbrew packages in your app, you need to initialise Sentry manually. To initialise Sentry use the [SetupSentry] function from the `go-coldbrew/core` package. +If you are using ColdBrew packages in your app, you need to initialise Sentry manually. To initialise Sentry use the [SetupSentry] function from the `go-coldbrew/core` package. ### Using @@ -98,15 +98,15 @@ To use Sentry in your app, have a look at the [errors documentation]. ### Initialising -If you app is using [Coldbrew cookiecutter] template, initialisation is done automatically. +If your app is using [ColdBrew cookiecutter] template, initialisation is done automatically. -If you are using Coldbrew packages in your app, you need to initialise Opentelemetry manually. To initialise Opentelemetry follow the [Opentelemetry documentation] and configure the otel exporter to send the data. +If you are using ColdBrew packages in your app, you need to initialise Opentelemetry manually. To initialise Opentelemetry follow the [Opentelemetry documentation] and configure the otel exporter to send the data. To initialise New Relic Opentelemetry use the [SetupNROpenTelemetry] function from the `go-coldbrew/core` package. ### Using -To use Opentelemetry tracing in your app, use the Coldbrew [tracing] and [interceptors] packages. +To use Opentelemetry tracing in your app, use the ColdBrew [tracing] and [interceptors] packages. You can also add more tracing to your app by [adding tracing] to your functions. @@ -114,7 +114,7 @@ You can also add more tracing to your app by [adding tracing] to your functions. [Buf] is a tool for managing protocol buffers. It can be used to generate code, lint proto files, and more. Buf simplifies the process of managing proto files and helps to keep them consistent across the team. It also helps to avoid common mistakes and helps to keep the proto files up to date. -[Coldbrew cookiecutter] template includes a `buf.yaml` file that configures Buf to generate code for the gRPC service. The code generation config is stored in the `buf.gen.yaml` file. +[ColdBrew cookiecutter] template includes a `buf.yaml` file that configures Buf to generate code for the gRPC service. The code generation config is stored in the `buf.gen.yaml` file. ## Logger @@ -128,9 +128,9 @@ To configure the logger, set the following environment variables as defined in [ ### Initialising -If your app is using [Coldbrew cookiecutter] template, initialisation is done automatically. +If your app is using [ColdBrew cookiecutter] template, initialisation is done automatically. -If you are using Coldbrew packages in your app, you need to initialise the logger manually: +If you are using ColdBrew packages in your app, you need to initialise the logger manually: ```go import "github.com/go-coldbrew/core" @@ -224,9 +224,9 @@ Hystrix-Go (`afex/hystrix-go`) is unmaintained (last updated 2018). Consider mig ### Initialising -If your app is using [Coldbrew cookiecutter] template, initialisation is done automatically. +If your app is using [ColdBrew cookiecutter] template, initialisation is done automatically. -If you are using Coldbrew packages in your app, you need to initialise Hystrix Prometheus manually: +If you are using ColdBrew packages in your app, you need to initialise Hystrix Prometheus manually: ```go import "github.com/go-coldbrew/core" @@ -255,19 +255,19 @@ func main() { } ``` -## Coldbrew packages +## ColdBrew packages -All Coldbrew packages are designed to be used as standalone packages. They can be used in any Go project. They are not tied to Coldbrew and can be used in any Go project. +All ColdBrew packages are designed to be used as standalone packages. They can be used in any Go project. They are not tied to ColdBrew and can be used in any Go project. -When you build your service using [Coldbrew cookiecutter] template, it includes the [Core] package which initialises all the packages and sets up the service. You can use the [Core] package in any Go project to set up the service. +When you build your service using [ColdBrew cookiecutter] template, it includes the [Core] package which initialises all the packages and sets up the service. You can use the [Core] package in any Go project to set up the service. -To see all the Coldbrew packages, check out the [Coldbrew packages] page. +To see all the ColdBrew packages, check out the [ColdBrew packages] page. --- [GRPC Gateway]: https://grpc-ecosystem.github.io/grpc-gateway/ [gRPC Gateway example]: /howto/APIs/#adding-a-new-api-to-your-service [Buf]: https://buf.build/ -[Coldbrew cookiecutter]: /getting-started#using-the-coldbrew-cookiecutter-template +[ColdBrew cookiecutter]: /getting-started#using-the-coldbrew-cookiecutter-template [Prometheus]: https://prometheus.io/ [metrics documentation]: /howto/Metrics/ [New Relic]: https://newrelic.com/ @@ -277,7 +277,7 @@ To see all the Coldbrew packages, check out the [Coldbrew packages] page. [Hystrix-Go]: https://pkg.go.dev/github.com/afex/hystrix-go/hystrix [Go-grpc-middleware]: https://github.com/grpc-ecosystem/go-grpc-middleware [Core]: https://github.com/go-coldbrew/core/tree/main#readme -[Coldbrew packages]: /packages +[ColdBrew packages]: /packages [Config]: https://pkg.go.dev/github.com/go-coldbrew/core/config#Config [adding tracing]: /howto/Tracing/#adding-tracing-to-your-functions [SetupNewRelic]: https://pkg.go.dev/github.com/go-coldbrew/core#SetupNewRelic @@ -295,3 +295,4 @@ To see all the Coldbrew packages, check out the [Coldbrew packages] page. [failsafe-go]: https://github.com/failsafe-go/failsafe-go [SetupEnvironment]: https://pkg.go.dev/github.com/go-coldbrew/core#SetupEnvironment [SetupReleaseName]: https://pkg.go.dev/github.com/go-coldbrew/core#SetupReleaseName +[open an issue]: https://github.com/go-coldbrew/core/issues diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000..54cdf0e --- /dev/null +++ b/package-lock.json @@ -0,0 +1,1119 @@ +{ + "name": "docs-coldbrew-cloud", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "docs-coldbrew-cloud", + "devDependencies": { + "@playwright/test": "^1.52.0", + "serve": "^14.2.4" + } + }, + "node_modules/@playwright/test": { + "version": "1.58.2", + "resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.58.2.tgz", + "integrity": "sha512-akea+6bHYBBfA9uQqSYmlJXn61cTa+jbO87xVLCWbTqbWadRVmhxlXATaOjOgcBaWU4ePo0wB41KMFv3o35IXA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "playwright": "1.58.2" + }, + "bin": { + "playwright": "cli.js" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@zeit/schemas": { + "version": "2.36.0", + "resolved": "https://registry.npmjs.org/@zeit/schemas/-/schemas-2.36.0.tgz", + "integrity": "sha512-7kjMwcChYEzMKjeex9ZFXkt1AyNov9R5HZtjBKVsmVpw7pa7ZtlCGvCBC2vnnXctaYN+aRI61HjIqeetZW5ROg==", + "dev": true, + "license": "MIT" + }, + "node_modules/ajv": { + "version": "8.18.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.18.0.tgz", + "integrity": "sha512-PlXPeEWMXMZ7sPYOHqmDyCJzcfNrUr3fGNKtezX14ykXOEIvyK81d+qydx89KY5O71FKMPaQ2vBfBFI5NHR63A==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.3", + "fast-uri": "^3.0.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/ansi-align": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/ansi-align/-/ansi-align-3.0.1.tgz", + "integrity": "sha512-IOfwwBF5iczOjp/WeY4YxyjqAFMQoZufdQWDd19SEExbVLNXqvpzSJ/M7Za4/sCPmQ0+GRquoA7bGcINcxew6w==", + "dev": true, + "license": "ISC", + "dependencies": { + "string-width": "^4.1.0" + } + }, + "node_modules/ansi-align/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/ansi-align/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true, + "license": "MIT" + }, + "node_modules/ansi-align/node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/ansi-align/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/ansi-regex": { + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.2.2.tgz", + "integrity": "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, + "node_modules/ansi-styles": { + "version": "6.2.3", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.3.tgz", + "integrity": "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/arch": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/arch/-/arch-2.2.0.tgz", + "integrity": "sha512-Of/R0wqp83cgHozfIYLbBMnej79U/SVGOOyuB3VVFv1NRM/PSFMK12x9KVtiYzJqmnU5WR2qp0Z5rHb7sWGnFQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/arg": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/arg/-/arg-5.0.2.tgz", + "integrity": "sha512-PYjyFOLKQ9y57JvQ6QLo8dAgNqswh8M1RMJYdQduT6xbWSgK36P/Z/v+p888pM69jMMfS8Xd8F6I1kQ/I9HUGg==", + "dev": true, + "license": "MIT" + }, + "node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true, + "license": "MIT" + }, + "node_modules/boxen": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/boxen/-/boxen-7.0.0.tgz", + "integrity": "sha512-j//dBVuyacJbvW+tvZ9HuH03fZ46QcaKvvhZickZqtB271DxJ7SNRSNxrV/dZX0085m7hISRZWbzWlJvx/rHSg==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-align": "^3.0.1", + "camelcase": "^7.0.0", + "chalk": "^5.0.1", + "cli-boxes": "^3.0.0", + "string-width": "^5.1.2", + "type-fest": "^2.13.0", + "widest-line": "^4.0.1", + "wrap-ansi": "^8.0.1" + }, + "engines": { + "node": ">=14.16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/brace-expansion": { + "version": "1.1.12", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", + "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/bytes": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", + "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/camelcase": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-7.0.1.tgz", + "integrity": "sha512-xlx1yCK2Oc1APsPXDL2LdlNP6+uu8OCDdhOBSVT279M/S+y75O30C2VuD8T2ogdePBBl7PfPF4504tnLgX3zfw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14.16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/chalk": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.0.1.tgz", + "integrity": "sha512-Fo07WOYGqMfCWHOzSXOt2CxDbC6skS/jO9ynEcmpANMoPrD+W1r1K6Vx7iNm+AQmETU1Xr2t+n8nzkV9t6xh3w==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.17.0 || ^14.13 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/chalk-template": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/chalk-template/-/chalk-template-0.4.0.tgz", + "integrity": "sha512-/ghrgmhfY8RaSdeo43hNXxpoHAtxdbskUHjPpfqUWGttFgycUhYPGx3YZBCnUCvOa7Doivn1IZec3DEGFoMgLg==", + "dev": true, + "license": "MIT", + "dependencies": { + "chalk": "^4.1.2" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/chalk-template?sponsor=1" + } + }, + "node_modules/chalk-template/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/chalk-template/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/cli-boxes": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/cli-boxes/-/cli-boxes-3.0.0.tgz", + "integrity": "sha512-/lzGpEWL/8PfI0BmBOPRwp0c/wFNX1RdUML3jK/RcSBA9T8mZDdQpqYBKtCFTOfQbwPqWEOpjqW+Fnayc0969g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/clipboardy": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/clipboardy/-/clipboardy-3.0.0.tgz", + "integrity": "sha512-Su+uU5sr1jkUy1sGRpLKjKrvEOVXgSgiSInwa/qeID6aJ07yh+5NWc3h2QfjHjBnfX4LhtFcuAWKUsJ3r+fjbg==", + "dev": true, + "license": "MIT", + "dependencies": { + "arch": "^2.2.0", + "execa": "^5.1.1", + "is-wsl": "^2.2.0" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true, + "license": "MIT" + }, + "node_modules/compressible": { + "version": "2.0.18", + "resolved": "https://registry.npmjs.org/compressible/-/compressible-2.0.18.tgz", + "integrity": "sha512-AF3r7P5dWxL8MxyITRMlORQNaOA2IkAFaTr4k7BUumjPtRpGDTZpl0Pb1XCO6JeDCBdp126Cgs9sMxqSjgYyRg==", + "dev": true, + "license": "MIT", + "dependencies": { + "mime-db": ">= 1.43.0 < 2" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/compression": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/compression/-/compression-1.8.1.tgz", + "integrity": "sha512-9mAqGPHLakhCLeNyxPkK4xVo746zQ/czLH1Ky+vkitMnWfWZps8r0qXuwhwizagCRttsL4lfG4pIOvaWLpAP0w==", + "dev": true, + "license": "MIT", + "dependencies": { + "bytes": "3.1.2", + "compressible": "~2.0.18", + "debug": "2.6.9", + "negotiator": "~0.6.4", + "on-headers": "~1.1.0", + "safe-buffer": "5.2.1", + "vary": "~1.1.2" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", + "dev": true, + "license": "MIT" + }, + "node_modules/content-disposition": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.2.tgz", + "integrity": "sha512-kRGRZw3bLlFISDBgwTSA1TMBFN6J6GWDeubmDE3AF+3+yXL8hTWv8r5rkLbqYXY4RjPk/EzHnClI3zQf1cFmHA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/cross-spawn": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", + "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", + "dev": true, + "license": "MIT", + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/deep-extend": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz", + "integrity": "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4.0.0" + } + }, + "node_modules/eastasianwidth": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", + "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==", + "dev": true, + "license": "MIT" + }, + "node_modules/emoji-regex": { + "version": "9.2.2", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", + "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==", + "dev": true, + "license": "MIT" + }, + "node_modules/execa": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/execa/-/execa-5.1.1.tgz", + "integrity": "sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==", + "dev": true, + "license": "MIT", + "dependencies": { + "cross-spawn": "^7.0.3", + "get-stream": "^6.0.0", + "human-signals": "^2.1.0", + "is-stream": "^2.0.0", + "merge-stream": "^2.0.0", + "npm-run-path": "^4.0.1", + "onetime": "^5.1.2", + "signal-exit": "^3.0.3", + "strip-final-newline": "^2.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sindresorhus/execa?sponsor=1" + } + }, + "node_modules/fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/fast-uri": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.0.tgz", + "integrity": "sha512-iPeeDKJSWf4IEOasVVrknXpaBV0IApz/gp7S2bb7Z4Lljbl2MGJRqInZiUrQwV16cpzw/D3S5j5Julj/gT52AA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], + "license": "BSD-3-Clause" + }, + "node_modules/fsevents": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", + "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/get-stream": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz", + "integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/human-signals": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-2.1.0.tgz", + "integrity": "sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=10.17.0" + } + }, + "node_modules/ini": { + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz", + "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==", + "dev": true, + "license": "ISC" + }, + "node_modules/is-docker": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/is-docker/-/is-docker-2.2.1.tgz", + "integrity": "sha512-F+i2BKsFrH66iaUFc0woD8sLy8getkwTwtOBjvs56Cx4CgJDeKQeqfz8wAYiSb8JOprWhHH5p77PbmYCvvUuXQ==", + "dev": true, + "license": "MIT", + "bin": { + "is-docker": "cli.js" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/is-port-reachable": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/is-port-reachable/-/is-port-reachable-4.0.0.tgz", + "integrity": "sha512-9UoipoxYmSk6Xy7QFgRv2HDyaysmgSG75TFQs6S+3pDM7ZhKTF/bskZV+0UlABHzKjNVhPjYCLfeZUEg1wXxig==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-stream": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", + "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-wsl": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-2.2.0.tgz", + "integrity": "sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-docker": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "dev": true, + "license": "ISC" + }, + "node_modules/json-schema-traverse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", + "dev": true, + "license": "MIT" + }, + "node_modules/merge-stream": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", + "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==", + "dev": true, + "license": "MIT" + }, + "node_modules/mime-db": { + "version": "1.54.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.54.0.tgz", + "integrity": "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "2.1.18", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.18.tgz", + "integrity": "sha512-lc/aahn+t4/SWV/qcmumYjymLsWfN3ELhpmVuUFjgsORruuZPVSwAQryq+HHGvO/SI2KVX26bx+En+zhM8g8hQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "mime-db": "~1.33.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types/node_modules/mime-db": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.33.0.tgz", + "integrity": "sha512-BHJ/EKruNIqJf/QahvxwQZXKygOQ256myeN/Ew+THcAa5q+PjyTTMMeNQC4DZw5AwfvelsUrA6B67NKMqXDbzQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mimic-fn": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", + "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/minimatch": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", + "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/minimist": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", + "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "dev": true, + "license": "MIT" + }, + "node_modules/negotiator": { + "version": "0.6.4", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.4.tgz", + "integrity": "sha512-myRT3DiWPHqho5PrJaIRyaMv2kgYf0mUVgBNOYMuCH5Ki1yEiQaf/ZJuQ62nvpc44wL5WDbTX7yGJi1Neevw8w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/npm-run-path": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz", + "integrity": "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==", + "dev": true, + "license": "MIT", + "dependencies": { + "path-key": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/on-headers": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/on-headers/-/on-headers-1.1.0.tgz", + "integrity": "sha512-737ZY3yNnXy37FHkQxPzt4UZ2UWPWiCZWLvFZ4fu5cueciegX0zGPnrlY6bwRg4FdQOe9YU8MkmJwGhoMybl8A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/onetime": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", + "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", + "dev": true, + "license": "MIT", + "dependencies": { + "mimic-fn": "^2.1.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/path-is-inside": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/path-is-inside/-/path-is-inside-1.0.2.tgz", + "integrity": "sha512-DUWJr3+ULp4zXmol/SZkFf3JGsS9/SIv+Y3Rt93/UjPpDpklB5f1er4O3POIbUuUJ3FXgqte2Q7SrU6zAqwk8w==", + "dev": true, + "license": "(WTFPL OR MIT)" + }, + "node_modules/path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/path-to-regexp": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-3.3.0.tgz", + "integrity": "sha512-qyCH421YQPS2WFDxDjftfc1ZR5WKQzVzqsp4n9M2kQhVOo/ByahFoUNJfl58kOcEGfQ//7weFTDhm+ss8Ecxgw==", + "dev": true, + "license": "MIT" + }, + "node_modules/playwright": { + "version": "1.58.2", + "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.58.2.tgz", + "integrity": "sha512-vA30H8Nvkq/cPBnNw4Q8TWz1EJyqgpuinBcHET0YVJVFldr8JDNiU9LaWAE1KqSkRYazuaBhTpB5ZzShOezQ6A==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "playwright-core": "1.58.2" + }, + "bin": { + "playwright": "cli.js" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "fsevents": "2.3.2" + } + }, + "node_modules/playwright-core": { + "version": "1.58.2", + "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.58.2.tgz", + "integrity": "sha512-yZkEtftgwS8CsfYo7nm0KE8jsvm6i/PTgVtB8DL726wNf6H2IMsDuxCpJj59KDaxCtSnrWan2AeDqM7JBaultg==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "playwright-core": "cli.js" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/range-parser": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.0.tgz", + "integrity": "sha512-kA5WQoNVo4t9lNx2kQNFCxKeBl5IbbSNBl1M/tLkw9WCn+hxNBAW5Qh8gdhs63CJnhjJ2zQWFoqPJP2sK1AV5A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/rc": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/rc/-/rc-1.2.8.tgz", + "integrity": "sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==", + "dev": true, + "license": "(BSD-2-Clause OR MIT OR Apache-2.0)", + "dependencies": { + "deep-extend": "^0.6.0", + "ini": "~1.3.0", + "minimist": "^1.2.0", + "strip-json-comments": "~2.0.1" + }, + "bin": { + "rc": "cli.js" + } + }, + "node_modules/registry-auth-token": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/registry-auth-token/-/registry-auth-token-3.3.2.tgz", + "integrity": "sha512-JL39c60XlzCVgNrO+qq68FoNb56w/m7JYvGR2jT5iR1xBrUA3Mfx5Twk5rqTThPmQKMWydGmq8oFtDlxfrmxnQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "rc": "^1.1.6", + "safe-buffer": "^5.0.1" + } + }, + "node_modules/registry-url": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/registry-url/-/registry-url-3.1.0.tgz", + "integrity": "sha512-ZbgR5aZEdf4UKZVBPYIgaglBmSF2Hi94s2PcIHhRGFjKYu+chjJdYfHn4rt3hB6eCKLJ8giVIIfgMa1ehDfZKA==", + "dev": true, + "license": "MIT", + "dependencies": { + "rc": "^1.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/require-from-string": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", + "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/serve": { + "version": "14.2.6", + "resolved": "https://registry.npmjs.org/serve/-/serve-14.2.6.tgz", + "integrity": "sha512-QEjUSA+sD4Rotm1znR8s50YqA3kYpRGPmtd5GlFxbaL9n/FdUNbqMhxClqdditSk0LlZyA/dhud6XNRTOC9x2Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@zeit/schemas": "2.36.0", + "ajv": "8.18.0", + "arg": "5.0.2", + "boxen": "7.0.0", + "chalk": "5.0.1", + "chalk-template": "0.4.0", + "clipboardy": "3.0.0", + "compression": "1.8.1", + "is-port-reachable": "4.0.0", + "serve-handler": "6.1.7", + "update-check": "1.5.4" + }, + "bin": { + "serve": "build/main.js" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/serve-handler": { + "version": "6.1.7", + "resolved": "https://registry.npmjs.org/serve-handler/-/serve-handler-6.1.7.tgz", + "integrity": "sha512-CinAq1xWb0vR3twAv9evEU8cNWkXCb9kd5ePAHUKJBkOsUpR1wt/CvGdeca7vqumL1U5cSaeVQ6zZMxiJ3yWsg==", + "dev": true, + "license": "MIT", + "dependencies": { + "bytes": "3.0.0", + "content-disposition": "0.5.2", + "mime-types": "2.1.18", + "minimatch": "3.1.5", + "path-is-inside": "1.0.2", + "path-to-regexp": "3.3.0", + "range-parser": "1.2.0" + } + }, + "node_modules/serve-handler/node_modules/bytes": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.0.0.tgz", + "integrity": "sha512-pMhOfFDPiv9t5jjIXkHosWmkSyQbvsgEVNkz0ERHbuLh2T/7j4Mqqpz523Fe8MVY89KC6Sh/QfS2sM+SjgFDcw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dev": true, + "license": "MIT", + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/signal-exit": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", + "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/string-width": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", + "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==", + "dev": true, + "license": "MIT", + "dependencies": { + "eastasianwidth": "^0.2.0", + "emoji-regex": "^9.2.2", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/strip-ansi": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.2.0.tgz", + "integrity": "sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^6.2.2" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } + }, + "node_modules/strip-final-newline": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-2.0.0.tgz", + "integrity": "sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/strip-json-comments": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz", + "integrity": "sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/type-fest": { + "version": "2.19.0", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-2.19.0.tgz", + "integrity": "sha512-RAH822pAdBgcNMAfWnCBU3CFZcfZ/i1eZjwFU/dsLKumyuuP3niueg2UAukXYF0E2AAoc82ZSSf9J0WQBinzHA==", + "dev": true, + "license": "(MIT OR CC0-1.0)", + "engines": { + "node": ">=12.20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/update-check": { + "version": "1.5.4", + "resolved": "https://registry.npmjs.org/update-check/-/update-check-1.5.4.tgz", + "integrity": "sha512-5YHsflzHP4t1G+8WGPlvKbJEbAJGCgw+Em+dGR1KmBUbr1J36SJBqlHLjR7oob7sco5hWHGQVcr9B2poIVDDTQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "registry-auth-token": "3.3.2", + "registry-url": "3.1.0" + } + }, + "node_modules/vary": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", + "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "license": "ISC", + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/widest-line": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/widest-line/-/widest-line-4.0.1.tgz", + "integrity": "sha512-o0cyEG0e8GPzT4iGHphIOh0cJOV8fivsXxddQasHPHfoZf1ZexrfeA21w2NaEN1RHE+fXlfISmOE8R9N3u3Qig==", + "dev": true, + "license": "MIT", + "dependencies": { + "string-width": "^5.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/wrap-ansi": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz", + "integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^6.1.0", + "string-width": "^5.0.1", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + } + } +} diff --git a/package.json b/package.json new file mode 100644 index 0000000..936fffc --- /dev/null +++ b/package.json @@ -0,0 +1,13 @@ +{ + "name": "docs-coldbrew-cloud", + "private": true, + "scripts": { + "build": "bundle exec jekyll build", + "test": "npm run build && npx playwright test", + "test:live": "BASE_URL=https://docs.coldbrew.cloud npx playwright test" + }, + "devDependencies": { + "@playwright/test": "^1.52.0", + "serve": "^14.2.4" + } +} diff --git a/playwright.config.ts b/playwright.config.ts new file mode 100644 index 0000000..724d9fd --- /dev/null +++ b/playwright.config.ts @@ -0,0 +1,28 @@ +import { defineConfig } from "@playwright/test"; + +const baseURL = process.env.BASE_URL || "http://localhost:4000"; +const isLocal = baseURL.includes("localhost"); + +export default defineConfig({ + testDir: "./tests", + timeout: 30_000, + retries: process.env.CI ? 1 : 0, + reporter: process.env.CI ? "html" : "list", + use: { + baseURL, + ignoreHTTPSErrors: true, + }, + projects: [ + { + name: "chromium", + use: { browserName: "chromium" }, + }, + ], + ...(isLocal && { + webServer: { + command: "npx serve _site -l 4000", + port: 4000, + reuseExistingServer: !process.env.CI, + }, + }), +}); diff --git a/quickstart.md b/quickstart.md index 13fefcd..780d2fe 100644 --- a/quickstart.md +++ b/quickstart.md @@ -41,8 +41,8 @@ project_short_description [A Golang project.]: My first ColdBrew service docker_image [alpine:latest]: docker_build_image [golang]: Select docker_build_image_version: -1 - 1.25 -2 - 1.26 +1 - 1.26 +2 - 1.25 Choose from 1, 2 [1]: 1 ``` @@ -98,18 +98,20 @@ Open a new terminal and test each endpoint: ### Health Check (Kubernetes liveness probe) ```bash -curl -s localhost:9091/healthcheck +$ curl -s localhost:9091/healthcheck +{"git_commit":"f470560c0a361839763c2abdac8a01b495bfd908","version":"0.1.0","build_date":"2026-03-24-09:44:05","go_version":"go1.26.1","os_arch":"darwin arm64","app":"myapp","branch":"main"} ``` -Expected: `SERVING` (the service is healthy) +The healthcheck returns build and version information as JSON — useful for quickly identifying which version of your service is running in any environment. ### Ready Check (Kubernetes readiness probe) ```bash -curl -s localhost:9091/readycheck +$ curl -s localhost:9091/readycheck +{"git_commit":"f470560c0a361839763c2abdac8a01b495bfd908","version":"0.1.0","build_date":"2026-03-24-09:44:05","go_version":"go1.26.1","os_arch":"darwin arm64","app":"myapp","branch":"main"} ``` -Expected: `SERVING` (the service is ready to receive traffic) +Returns the same version JSON when the service is ready to receive traffic. Returns an error if the service hasn't called `SetReady()` yet. ### Echo Endpoint (your demo API) diff --git a/tests/content.spec.ts b/tests/content.spec.ts new file mode 100644 index 0000000..cbd3f96 --- /dev/null +++ b/tests/content.spec.ts @@ -0,0 +1,105 @@ +import { test, expect } from "@playwright/test"; + +test.describe("Code Blocks", () => { + test("home page renders code blocks", async ({ page }) => { + await page.goto("/"); + const codeBlocks = page.locator("pre code"); + expect(await codeBlocks.count()).toBeGreaterThan(0); + }); + + test("APIs howto renders proto and Go code blocks", async ({ page }) => { + await page.goto("/howto/APIs/"); + const codeBlocks = page.locator("pre code"); + expect(await codeBlocks.count()).toBeGreaterThanOrEqual(5); + }); + + test("gRPC howto renders code blocks", async ({ page }) => { + await page.goto("/howto/gRPC/"); + const codeBlocks = page.locator("pre code"); + expect(await codeBlocks.count()).toBeGreaterThanOrEqual(2); + }); +}); + +test.describe("Tables", () => { + test("home page has feature table", async ({ page }) => { + await page.goto("/"); + const tables = page.locator("table"); + expect(await tables.count()).toBeGreaterThanOrEqual(2); + }); + + test("APIs page has gRPC status code mapping table", async ({ page }) => { + await page.goto("/howto/APIs/"); + const tables = page.locator("table"); + expect(await tables.count()).toBeGreaterThanOrEqual(1); + // Check that the status code table exists + const pageText = await page.locator("main, .main-content").textContent(); + expect(pageText).toContain("gRPC status code"); + }); + + test("integrations page renders content", async ({ page }) => { + await page.goto("/integrations/"); + const pageText = await page.locator("main, .main-content").textContent(); + expect(pageText).toContain("Prometheus"); + expect(pageText).toContain("New Relic"); + }); +}); + +test.describe("Images", () => { + test("home page logo loads", async ({ page }) => { + await page.goto("/"); + // just-the-docs may render the logo as an tag or CSS background + // depending on theme version — check for either + const imgLogo = page.locator('img[src*="coldbrew"]'); + const cssLogo = page.locator('.site-logo, .site-title img'); + const hasLogo = (await imgLogo.count()) > 0 || (await cssLogo.count()) > 0; + expect(hasLogo).toBe(true); + }); + + test("swagger image loads on swagger howto", async ({ page }) => { + await page.goto("/howto/swagger/"); + const img = page.locator('img[src*="swagger"]'); + expect(await img.count()).toBeGreaterThan(0); + await expect(img.first()).toBeVisible(); + const loaded = await img.first().evaluate((el: HTMLImageElement) => { + return el.complete && el.naturalWidth > 0; + }); + expect(loaded).toBe(true); + }); + + test("data-builder SVG loads on data-builder howto", async ({ page }) => { + await page.goto("/howto/data-builder/"); + const img = page.locator('img[src*="data-builder"]'); + expect(await img.count()).toBeGreaterThan(0); + await expect(img.first()).toBeVisible(); + }); +}); + +test.describe("Callouts", () => { + test("signals page has callout", async ({ page }) => { + await page.goto("/howto/signals/"); + // just-the-docs renders callouts as blockquotes with specific classes + const callout = page.locator("blockquote, .important, .warning, .note"); + expect(await callout.count()).toBeGreaterThan(0); + }); + + test("metrics page has warning about hystrix", async ({ page }) => { + await page.goto("/howto/Metrics/"); + const pageText = await page.locator("main, .main-content").textContent(); + expect(pageText).toContain("unmaintained"); + }); +}); + +test.describe("ASCII Diagrams", () => { + test("home page renders architecture diagram in pre block", async ({ + page, + }) => { + await page.goto("/"); + const pre = page.locator("pre"); + const preTexts = await pre.allTextContents(); + const hasDiagram = preTexts.some( + (text) => + text.includes("ColdBrew Core") || text.includes("Interceptor Chain") + ); + expect(hasDiagram).toBe(true); + }); +}); diff --git a/tests/links.spec.ts b/tests/links.spec.ts new file mode 100644 index 0000000..d021d1c --- /dev/null +++ b/tests/links.spec.ts @@ -0,0 +1,107 @@ +import { test, expect, Page } from "@playwright/test"; + +/** + * Collect all unique internal links from a page. + */ +async function getInternalLinks( + page: Page, + baseURL: string +): Promise { + const links = await page.locator("a[href]").evaluateAll( + (els: HTMLAnchorElement[], base: string) => + els + .map((el) => el.href) + .filter( + (href) => + href.startsWith(base) && !href.includes("#") && href !== base + ), + baseURL + ); + return [...new Set(links)]; +} + +test.describe("Internal Links", () => { + const pagesToCrawl = ["/", "/howto/APIs/", "/integrations/", "/packages/"]; + + for (const pagePath of pagesToCrawl) { + test(`all internal links on ${pagePath} resolve without errors`, async ({ + page, + request, + }) => { + await page.goto(pagePath); + const baseURL = new URL(page.url()).origin; + const links = await getInternalLinks(page, baseURL); + + const broken: string[] = []; + for (const link of links) { + if (link === "#" || link.startsWith("javascript:")) continue; + + try { + const response = await request.get(link); + if (response.status() >= 400) { + broken.push(`${link} => ${response.status()}`); + } + } catch { + broken.push(`${link} => failed to fetch`); + } + } + + expect( + broken, + `Broken links on ${pagePath}:\n${broken.join("\n")}` + ).toHaveLength(0); + }); + } +}); + +test.describe("Anchor Links", () => { + const pagesWithAnchors = [ + { path: "/integrations/", anchor: "prometheus" }, + { path: "/integrations/", anchor: "new-relic" }, + { path: "/integrations/", anchor: "sentry" }, + ]; + + for (const { path, anchor } of pagesWithAnchors) { + test(`anchor #${anchor} exists on ${path}`, async ({ page }) => { + await page.goto(path); + const element = page.locator(`[id="${anchor}"]`); + await expect(element).toBeAttached(); + }); + } +}); + +test.describe("External Links (sample)", () => { + test("pkg.go.dev links for core packages are reachable", async ({ + request, + }) => { + const pkgLinks = [ + "https://pkg.go.dev/github.com/go-coldbrew/core", + "https://pkg.go.dev/github.com/go-coldbrew/errors", + "https://pkg.go.dev/github.com/go-coldbrew/log", + ]; + + for (const url of pkgLinks) { + const response = await request.get(url); + expect( + response.status(), + `${url} returned ${response.status()}` + ).toBeLessThan(400); + } + }); + + test("GitHub repos are reachable", async ({ request }) => { + const repos = [ + "https://github.com/go-coldbrew/core", + "https://github.com/go-coldbrew/errors", + "https://github.com/go-coldbrew/interceptors", + ]; + + for (const url of repos) { + const response = await request.get(url); + expect( + response.status(), + `${url} returned ${response.status()}` + ).toBeLessThan(400); + } + }); +}); diff --git a/tests/navigation.spec.ts b/tests/navigation.spec.ts new file mode 100644 index 0000000..a60e83d --- /dev/null +++ b/tests/navigation.spec.ts @@ -0,0 +1,119 @@ +import { test, expect } from "@playwright/test"; + +const topLevelPages = [ + { path: "/", title: "ColdBrew" }, + { path: "/quickstart/", title: "Quickstart" }, + { path: "/getting-started/", title: "Cookiecutter Setup" }, + { path: "/using/", title: "Using ColdBrew" }, + { path: "/architecture/", title: "Architecture" }, + { path: "/howto/", title: "How To" }, + { path: "/integrations/", title: "Integrations" }, + { path: "/faq/", title: "Frequently Asked Questions" }, + { path: "/packages/", title: "Packages" }, +]; + +const howtoPages = [ + "/howto/APIs/", + "/howto/gRPC/", + "/howto/Log/", + "/howto/errors/", + "/howto/Tracing/", + "/howto/Metrics/", + "/howto/interceptors/", + "/howto/Debugging/", + "/howto/signals/", + "/howto/swagger/", + "/howto/data-builder/", +]; + +test.describe("Page Loading", () => { + for (const page of topLevelPages) { + test(`top-level page loads: ${page.path}`, async ({ page: p }) => { + const response = await p.goto(page.path); + expect(response?.status()).toBe(200); + await expect(p.locator("h1").first()).toContainText(page.title); + }); + } + + for (const path of howtoPages) { + test(`howto page loads: ${path}`, async ({ page }) => { + const response = await page.goto(path); + expect(response?.status()).toBe(200); + await expect(page.locator("h1, h2").first()).toBeVisible(); + }); + } +}); + +test.describe("Navigation Sidebar", () => { + test("sidebar contains top-level nav items", async ({ page }) => { + await page.goto("/"); + const nav = page.locator("nav.site-nav, .site-nav"); + await expect(nav).toBeVisible(); + + // Check a subset that should always be present + for (const link of ["Home", "How To", "Integrations"]) { + await expect( + nav.getByText(link, { exact: false }).first() + ).toBeVisible(); + } + }); + + test("How To section has child pages in nav", async ({ page }) => { + // Navigate to a howto child page so the nav section is expanded + await page.goto("/howto/APIs/"); + const nav = page.locator("nav.site-nav, .site-nav"); + + // These should be visible because the How To nav is expanded on a child page + for (const child of ["gRPC", "Log", "Errors", "Tracing", "Metrics"]) { + await expect( + nav.getByText(child, { exact: false }).first() + ).toBeVisible(); + } + }); +}); + +test.describe("Table of Contents", () => { + const pagesWithToc = ["/faq/", "/integrations/"]; + + for (const path of pagesWithToc) { + test(`TOC renders on ${path}`, async ({ page }) => { + await page.goto(path); + // TOC generates internal anchor links + const anchorLinks = page.locator('a[href^="#"]'); + expect(await anchorLinks.count()).toBeGreaterThan(0); + }); + } +}); + +test.describe("Home Page CTAs", () => { + test("Get Started button links to getting-started", async ({ page }) => { + await page.goto("/"); + const mainContent = page.locator("#main-content, .main-content, main"); + const btn = mainContent.getByRole("link", { name: /Get Started/i }); + await expect(btn).toBeVisible(); + await expect(btn).toHaveAttribute("href", /getting-started/); + }); + + test("View Packages button links to packages", async ({ page }) => { + await page.goto("/"); + const mainContent = page.locator("#main-content, .main-content, main"); + const btn = mainContent.getByRole("link", { name: /View Packages/i }); + await expect(btn).toBeVisible(); + await expect(btn).toHaveAttribute("href", /packages/); + }); + + test("How To button links to howto", async ({ page }) => { + await page.goto("/"); + const mainContent = page.locator("#main-content, .main-content, main"); + const btn = mainContent.getByRole("link", { name: "How To" }); + await expect(btn).toBeVisible(); + await expect(btn).toHaveAttribute("href", /howto/); + }); + + test("GitHub button links to go-coldbrew org", async ({ page }) => { + await page.goto("/"); + const btn = page.locator('a.btn[href*="github.com/go-coldbrew"]'); + await expect(btn).toBeVisible(); + await expect(btn).toHaveAttribute("href", /github\.com\/go-coldbrew/); + }); +}); diff --git a/tests/search.spec.ts b/tests/search.spec.ts new file mode 100644 index 0000000..2c9a2b1 --- /dev/null +++ b/tests/search.spec.ts @@ -0,0 +1,38 @@ +import { test, expect } from "@playwright/test"; + +test.describe("Search", () => { + test("search input is visible", async ({ page }) => { + await page.goto("/"); + const searchInput = page.locator("#search-input"); + await expect(searchInput).toBeVisible(); + }); + + test("typing a query populates search results", async ({ page }) => { + await page.goto("/"); + + // Focus and type into search using pressSequentially to trigger JS key handlers + const searchInput = page.locator("#search-input"); + await searchInput.click(); + await searchInput.pressSequentially("interceptor", { delay: 50 }); + + // just-the-docs renders results as child elements inside #search-results + const resultLink = page.locator("#search-results a, #search-results li, #search-results p"); + await expect(resultLink.first()).toBeAttached({ timeout: 10000 }); + }); + + test("search results contain clickable links", async ({ page }) => { + await page.goto("/"); + + const searchInput = page.locator("#search-input"); + await searchInput.click(); + await searchInput.pressSequentially("tracing", { delay: 50 }); + + // Wait for result links + const resultLink = page.locator("#search-results a"); + await expect(resultLink.first()).toBeAttached({ timeout: 10000 }); + + // Verify at least one link exists + const count = await resultLink.count(); + expect(count).toBeGreaterThan(0); + }); +});