From 86d4d1323b7c11b026dc434aca1b6dfcbcc42e0a Mon Sep 17 00:00:00 2001 From: Dave Protasowski Date: Mon, 26 Oct 2026 09:16:03 -0400 Subject: [PATCH 01/44] Add directory-scoped code sample CI and image updates --- .github/dependabot.yaml | 5 ++ .github/workflows/samples-test.yaml | 90 +++++++++++++++++++++++++++++ 2 files changed, 95 insertions(+) create mode 100644 .github/workflows/samples-test.yaml diff --git a/.github/dependabot.yaml b/.github/dependabot.yaml index ab066bc0be..8f75bcc4ee 100644 --- a/.github/dependabot.yaml +++ b/.github/dependabot.yaml @@ -9,6 +9,11 @@ updates: github-actions: patterns: - "*" +- package-ecosystem: "docker" + directories: + - "/code-samples/**/*" + schedule: + interval: "monthly" - package-ecosystem: "gomod" directory: "/" schedule: diff --git a/.github/workflows/samples-test.yaml b/.github/workflows/samples-test.yaml new file mode 100644 index 0000000000..29b8d44c6b --- /dev/null +++ b/.github/workflows/samples-test.yaml @@ -0,0 +1,90 @@ +name: samples-test +on: + pull_request: + paths: ['code-samples/**', '.github/workflows/samples-test.yaml'] +permissions: + contents: read +jobs: + discover: + runs-on: ubuntu-24.04 + timeout-minutes: 10 + outputs: + directories: ${{ steps.samples.outputs.directories }} + steps: + - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + with: + ref: ${{ github.event.pull_request.head.sha }} + fetch-depth: 0 + persist-credentials: false + - id: samples + name: Find changed sample directories + run: | + git diff --name-only --no-renames -z "${{ github.event.pull_request.base.sha }}"...HEAD -- code-samples/ > "$RUNNER_TEMP/changed-samples" + directories=() + while IFS= read -r -d '' path; do + directory=${path%/*} + while [[ "$directory" == code-samples/* && ! -f "$directory/ci.sh" ]]; do + directory=${directory%/*} + done + if [[ "$directory" == code-samples/* && -f "$directory/ci.sh" ]]; then + directories+=("$directory") + else + printf '%s\n' "NOT TESTED (no ci.sh): $path" >> "$GITHUB_STEP_SUMMARY" + fi + done < "$RUNNER_TEMP/changed-samples" + collisions=$(find code-samples -type f -name ci.sh -print0 | + jq -Rs 'split("\u0000")[:-1] | map(split("/")[-2]) | group_by(.) | map(select(length > 1) | .[0])') + echo "directories=$(jq -cn --argjson collisions "$collisions" --args ' + $ARGS.positional | unique | map(split("/") as $path | + {directory: ., name: (if $collisions | index($path[-1]) + then $path[1] + "/" + $path[-1] else $path[-1] end)})' "${directories[@]}")" >> "$GITHUB_OUTPUT" + + test: + name: ${{ matrix.name }} + needs: discover + if: needs.discover.outputs.directories != '[]' + runs-on: ubuntu-24.04 + timeout-minutes: 30 + strategy: + fail-fast: false + matrix: + include: ${{ fromJSON(needs.discover.outputs.directories) }} + steps: + - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + with: + ref: ${{ github.event.pull_request.head.sha }} + persist-credentials: false + - uses: chainguard-dev/actions/setup-kind@8f31e9482b447ff2c69eca944a09b4b31a4a85d0 + with: + k8s-version: 1.36.x + - name: Install latest Knative Serving YAMLs + run: | + kubectl apply -f https://github.com/knative/serving/releases/latest/download/serving-crds.yaml + kubectl apply -f https://github.com/knative/serving/releases/latest/download/serving-core.yaml + kubectl apply -f https://github.com/knative-extensions/net-kourier/releases/latest/download/kourier.yaml + kubectl patch cm config-network -n knative-serving --type merge -p '{"data":{"ingress-class":"kourier.ingress.networking.knative.dev"}}' + kubectl patch cm config-domain -n knative-serving --type merge -p '{"data":{"example.test":""}}' + kubectl wait deployment --all -n knative-serving --for=condition=Available --timeout=300s + kubectl wait deployment --all -n kourier-system --for=condition=Available --timeout=300s + - name: Test + working-directory: ${{ matrix.directory }} + run: | + kubectl -n kourier-system port-forward service/kourier 18080:80 & + forward=$! + trap 'kill "$forward" 2>/dev/null || true' EXIT + for _attempt in {1..30}; do + (true > /dev/tcp/127.0.0.1/18080) 2>/dev/null && break + sleep 1 + done + (true > /dev/tcp/127.0.0.1/18080) 2>/dev/null + source ./ci.sh + main + - name: Failure diagnostics + if: failure() + run: | + kubectl get ksvc,revisions,pods -A -o wide || true + kubectl get events -A --sort-by=.metadata.creationTimestamp || true + kubectl -n default logs --all-containers -l serving.knative.dev/service --tail=100 || true + - name: Delete disposable cluster + if: always() + run: kind delete cluster From cb40edba92ca2459f752922a83842ddaabcc1631 Mon Sep 17 00:00:00 2001 From: Dave Protasowski Date: Mon, 26 Oct 2026 09:16:03 -0400 Subject: [PATCH 02/44] Update and simplify the Clojure Hello World sample --- .../serving/helloworld-clojure/Dockerfile | 22 +-- .../serving/helloworld-clojure/README.md | 170 ++++-------------- .../serving/helloworld-clojure/ci.sh | 40 +++++ .../serving/helloworld-clojure/project.clj | 8 +- .../src/helloworld/core.clj | 17 +- 5 files changed, 88 insertions(+), 169 deletions(-) create mode 100755 code-samples/community/serving/helloworld-clojure/ci.sh diff --git a/code-samples/community/serving/helloworld-clojure/Dockerfile b/code-samples/community/serving/helloworld-clojure/Dockerfile index c7881b1ca1..d9c2a0741e 100644 --- a/code-samples/community/serving/helloworld-clojure/Dockerfile +++ b/code-samples/community/serving/helloworld-clojure/Dockerfile @@ -1,17 +1,11 @@ -# Use the official Clojure image. -# https://hub.docker.com/_/clojure -FROM clojure - -# Create the project and download dependencies. -WORKDIR /usr/src/app +FROM clojure:temurin-25-lein-2.13.0-noble AS builder +WORKDIR /app COPY project.clj . RUN lein deps +COPY src ./src +RUN lein uberjar -# Copy local code to the container image. -COPY . . - -# Build an uberjar release artifact. -RUN mv "$(lein uberjar | sed -n 's/^Created \(.*standalone\.jar\)/\1/p')" app-standalone.jar - -# Run the web service on container startup. -CMD ["java", "-jar", "app-standalone.jar"] +FROM eclipse-temurin:25-jre-noble +WORKDIR /app +COPY --from=builder /app/target/helloworld.jar . +CMD ["java", "-jar", "helloworld.jar"] diff --git a/code-samples/community/serving/helloworld-clojure/README.md b/code-samples/community/serving/helloworld-clojure/README.md index ef0cd6b734..9c4bdb56b0 100644 --- a/code-samples/community/serving/helloworld-clojure/README.md +++ b/code-samples/community/serving/helloworld-clojure/README.md @@ -1,154 +1,44 @@ # Hello World - Clojure -A simple web app written in Clojure that you can use for testing. It reads in an -env variable `TARGET` and prints "Hello \${TARGET}!". If TARGET is not -specified, it will use "World" as the TARGET. +A Clojure HTTP service using Ring and Jetty on Java 25. It returns +`Hello ${TARGET}!` followed by a newline. `TARGET` defaults to `World`, and +`PORT` defaults to `8080`. -## Prerequisites +The [source](src/helloworld/core.clj) and [dependencies](project.clj) are built +into an executable jar by the [Dockerfile](Dockerfile); the runtime image +contains only a Java 25 JRE and the application jar. -- A Kubernetes cluster with Knative installed and DNS configured. Follow the - [Knative installation instructions](https://knative.dev/docs/install/) if you need to create - one. -- [Docker](https://www.docker.com) installed and running on your local machine, - and a Docker Hub account configured (we'll use it for a container registry). +## Build and run locally -## Recreating the sample code +From this directory, with Docker running: -While you can clone all of the code from this directory, hello world apps are -generally more useful if you build them step-by-step. The following instructions -recreate the source files from this folder. - -1. Create a new file named `src/helloworld/core.clj` and paste the following - code. This code creates a basic web server which listens on port 8080: - - ```clojure - (ns helloworld.core - (:use ring.adapter.jetty) - (:gen-class)) - - (defn handler [request] - {:status 200 - :headers {"Content-Type" "text/html"} - :body (str "Hello " - (if-let [target (System/getenv "TARGET")] - target - "World") - "!\n")}) - - (defn -main [& args] - (run-jetty handler {:port (if-let [port (System/getenv "PORT")] - (Integer/parseInt port) - 8080)})) - ``` - -1. In your project directory, create a file named `project.clj` and copy the - code below into it. This code defines the project's dependencies and - entrypoint. - - ```clojure - (defproject helloworld "1.0.0-SNAPSHOT" - :description "Hello World - Clojure sample" - :dependencies [[org.clojure/clojure "1.9.0"] - [ring/ring-core "1.6.3"] - [ring/ring-jetty-adapter "1.6.3"]] - :main helloworld.core) - ``` - -1. In your project directory, create a file named `Dockerfile` and copy the code - block below into it. For detailed instructions on dockerizing a Clojure app, - see - [the clojure image documentation](https://github.com/docker-library/docs/tree/master/clojure). - - ```docker - # Use the official Clojure image. - # https://hub.docker.com/_/clojure - FROM clojure - - # Create the project and download dependencies. - WORKDIR /usr/src/app - COPY project.clj . - RUN lein deps - - # Copy local code to the container image. - COPY . . - - # Build an uberjar release artifact. - RUN mv "$(lein uberjar | sed -n 's/^Created \(.*standalone\.jar\)/\1/p')" app-standalone.jar - - # Run the web service on container startup. - CMD ["java", "-jar", "app-standalone.jar"] - ``` - -1. Create a new file, `service.yaml` and copy the following service definition - into the file. Make sure to replace `{username}` with your Docker Hub - username. - - ```yaml - apiVersion: serving.knative.dev/v1 - kind: Service - metadata: - name: helloworld-clojure - namespace: default - spec: - template: - spec: - containers: - - image: docker.io/{username}/helloworld-clojure - env: - - name: TARGET - value: "Clojure Sample v1" - ``` - -## Building and deploying the sample - -Once you have recreated the sample code files (or used the files in the sample -folder) you're ready to build and deploy the sample app. - -1. Use Docker to build the sample code into a container. To build and push with - Docker Hub, run these commands replacing `{username}` with your Docker Hub - username: - - ```bash - # Build and push the container on your local machine. - docker buildx build --platform linux/arm64,linux/amd64 -t "{username}/helloworld-clojure" --push . - ``` - -1. After the build has completed and the container is pushed to docker hub, you - can deploy the app into your cluster. Ensure that the container image value - in `service.yaml` matches the container you built in the previous step. Apply - the configuration using `kubectl`: - - ```bash - kubectl apply --filename service.yaml - ``` - -1. Now that your service is created, Knative will perform the following steps: - - - Create a new immutable revision for this version of the app. - - Network programming to create a route, ingress, service, and load balance - for your app. - - Automatically scale your pods up and down (including to zero active pods). - -1. To find the URL for your service, use +```bash +docker build -t helloworld-clojure . +docker run --rm -p 8080:8080 helloworld-clojure +# In another terminal: +curl http://localhost:8080 +# Hello World! +``` - ``` - kubectl get ksvc helloworld-clojure --output=custom-columns=NAME:.metadata.name,URL:.status.url - NAME URL - helloworld-clojure http://helloworld-clojure.default.1.2.3.4.sslip.io - ``` +Set `TARGET` or `PORT` with Docker's `-e` option. If you change `PORT`, also +update the container port in your `-p` mapping. -1. Now you can make a request to your app and see the result. Replace - the URL below with the URL returned in the previous command. +## Deploy to Knative - ```bash - curl http://helloworld-clojure.default.1.2.3.4.sslip.io - Hello World! - ``` +Use a cluster with [Knative Serving](https://knative.dev/docs/install/) and +networking configured, and a Docker Hub account. Replace `{username}` below +and in [service.yaml](service.yaml) with your Docker Hub username. -## Removing the sample app deployment +```bash +docker buildx build --platform linux/arm64,linux/amd64 -t "{username}/helloworld-clojure" --push . +kubectl apply -f service.yaml +kubectl wait --for=condition=Ready service.serving.knative.dev/helloworld-clojure --timeout=300s +curl "$(kubectl get ksvc helloworld-clojure -o jsonpath='{.status.url}')" +# Hello Clojure Sample v1! +``` -To remove the sample app from your cluster, delete the service record: +The manifest sets `TARGET` to `Clojure Sample v1`. Remove the sample with: ```bash -kubectl delete --filename service.yaml +kubectl delete -f service.yaml ``` diff --git a/code-samples/community/serving/helloworld-clojure/ci.sh b/code-samples/community/serving/helloworld-clojure/ci.sh new file mode 100755 index 0000000000..9055d7c3a0 --- /dev/null +++ b/code-samples/community/serving/helloworld-clojure/ci.sh @@ -0,0 +1,40 @@ +#!/usr/bin/env bash +# Source from this sample directory; the caller owns cluster setup and ingress forwarding. +IMAGE=dev.local/helloworld-clojure:ci +SAMPLE_URL=http://127.0.0.1:18080 + +main() ( + set -euo pipefail + build + deploy + test +) + +build() ( + set -euo pipefail + docker build -t "$IMAGE" . +) + +deploy() ( + set -euo pipefail + kind load docker-image "$IMAGE" + kubectl patch --local -f service.yaml --type=json -p "$( + jq -cn --arg image "$IMAGE" '[ + {op: "replace", path: "/spec/template/spec/containers/0/image", value: $image}, + {op: "add", path: "/spec/template/spec/containers/0/imagePullPolicy", value: "IfNotPresent"} + ]' + )" -o yaml | kubectl --namespace default apply -f - + kubectl --namespace default wait -f service.yaml --for=condition=Ready --timeout=300s +) + +test() ( + set -euo pipefail + local service_url service_host + service_url=$(kubectl --namespace default get -f service.yaml -o jsonpath='{.status.url}') + service_host=${service_url#*://} + [[ "$service_host" =~ ^[a-zA-Z0-9.-]+$ ]] + local body + body=$(curl -fsS --max-time 30 \ + -H "Host: $service_host" "$SAMPLE_URL/") + [[ $body == 'Hello Clojure Sample v1!' ]] +) diff --git a/code-samples/community/serving/helloworld-clojure/project.clj b/code-samples/community/serving/helloworld-clojure/project.clj index af0e473e1b..3ee441ab74 100644 --- a/code-samples/community/serving/helloworld-clojure/project.clj +++ b/code-samples/community/serving/helloworld-clojure/project.clj @@ -1,6 +1,6 @@ (defproject helloworld "1.0.0-SNAPSHOT" :description "Hello World - Clojure sample" - :dependencies [[org.clojure/clojure "1.9.0"] - [ring/ring-core "1.6.3"] - [ring/ring-jetty-adapter "1.6.3"]] - :main helloworld.core) + :dependencies [[org.clojure/clojure "1.12.6"] + [ring/ring-jetty-adapter "1.15.6"]] + :main helloworld.core + :uberjar-name "helloworld.jar") diff --git a/code-samples/community/serving/helloworld-clojure/src/helloworld/core.clj b/code-samples/community/serving/helloworld-clojure/src/helloworld/core.clj index 7688631e8b..6806f9010a 100644 --- a/code-samples/community/serving/helloworld-clojure/src/helloworld/core.clj +++ b/code-samples/community/serving/helloworld-clojure/src/helloworld/core.clj @@ -1,17 +1,12 @@ (ns helloworld.core - (:use ring.adapter.jetty) + (:require [ring.adapter.jetty :as jetty]) (:gen-class)) -(defn handler [request] +(defn handler [_request] {:status 200 :headers {"Content-Type" "text/html"} - :body (str "Hello " - (if-let [target (System/getenv "TARGET")] - target - "World") - "!\n")}) + :body (str "Hello " (or (System/getenv "TARGET") "World") "!\n")}) -(defn -main [& args] - (run-jetty handler {:port (if-let [port (System/getenv "PORT")] - (Integer/parseInt port) - 8080)})) +(defn -main [& _args] + (jetty/run-jetty handler + {:port (Integer/parseInt (or (System/getenv "PORT") "8080"))})) From 18d3e2422f1d8a7820f8cb903fc788879262aee0 Mon Sep 17 00:00:00 2001 From: Dave Protasowski Date: Mon, 26 Oct 2026 09:16:03 -0400 Subject: [PATCH 03/44] Update the Dart Hello World sample --- .../serving/helloworld-dart/README.md | 34 ++++++---------- .../serving/helloworld-dart/bin/server.dart | 3 +- .../community/serving/helloworld-dart/ci.sh | 40 +++++++++++++++++++ .../serving/helloworld-dart/pubspec.yaml | 2 - 4 files changed, 54 insertions(+), 25 deletions(-) create mode 100755 code-samples/community/serving/helloworld-dart/ci.sh diff --git a/code-samples/community/serving/helloworld-dart/README.md b/code-samples/community/serving/helloworld-dart/README.md index ec42da4e65..a20c0a261a 100644 --- a/code-samples/community/serving/helloworld-dart/README.md +++ b/code-samples/community/serving/helloworld-dart/README.md @@ -21,38 +21,28 @@ While you can clone all of the code from this directory, we recommend you create your hello world Dart application by using the `dart` developer tool. This takes just a few steps: -1. Create a new Dart app using the `server_shelf` template: +1. Create a new Dart app using the `server-shelf` template: ```shell - > dart create -t server-shelf helloworld-dart + dart create -t server-shelf helloworld-dart + cd helloworld-dart ``` +1. Replace the generated `bin/server.dart` with the [sample server](bin/server.dart) + and use the [sample Dockerfile](Dockerfile). The generated server does not + read `TARGET`, so this step is required for the greeting shown below. + 1. If you want to run locally, install dependencies. If you only want to run in Docker or Knative, you can skip this step. ```shell - > dart pub get + dart pub get ``` -1. Create a new file, `service.yaml` and copy the following service definition - into the file. Make sure to replace `{username}` with your Docker Hub - username. - - ```yaml - apiVersion: serving.knative.dev/v1 - kind: Service - metadata: - name: helloworld-dart - namespace: default - spec: - template: - spec: - containers: - - image: docker.io/{username}/helloworld-dart - env: - - name: TARGET - value: "Dart Sample v1" - ``` +1. Use the checked-in [Knative Service manifest](service.yaml). If you created + a new application directory, copy that file into it as `service.yaml`. + Replace `{username}` in its image reference with your Docker Hub username. + The manifest sets `TARGET` to `Dart Sample v1` for the greeting shown below. ## Building and deploying the sample diff --git a/code-samples/community/serving/helloworld-dart/bin/server.dart b/code-samples/community/serving/helloworld-dart/bin/server.dart index 87e8f26f28..19ffd736ca 100644 --- a/code-samples/community/serving/helloworld-dart/bin/server.dart +++ b/code-samples/community/serving/helloworld-dart/bin/server.dart @@ -10,7 +10,8 @@ final _router = Router() ..get('/echo/', _echoHandler); Response _rootHandler(Request req) { - return Response.ok('Hello, World!\n'); + final target = Platform.environment['TARGET'] ?? 'World'; + return Response.ok('Hello $target\n'); } Response _echoHandler(Request request) { diff --git a/code-samples/community/serving/helloworld-dart/ci.sh b/code-samples/community/serving/helloworld-dart/ci.sh new file mode 100755 index 0000000000..2340d23fb4 --- /dev/null +++ b/code-samples/community/serving/helloworld-dart/ci.sh @@ -0,0 +1,40 @@ +#!/usr/bin/env bash +# Source from this sample directory; the caller owns cluster setup and ingress forwarding. +IMAGE=dev.local/helloworld-dart:ci +SAMPLE_URL=http://127.0.0.1:18080 + +main() ( + set -euo pipefail + build + deploy + test +) + +build() ( + set -euo pipefail + docker build -t "$IMAGE" . +) + +deploy() ( + set -euo pipefail + kind load docker-image "$IMAGE" + kubectl patch --local -f service.yaml --type=json -p "$( + jq -cn --arg image "$IMAGE" '[ + {op: "replace", path: "/spec/template/spec/containers/0/image", value: $image}, + {op: "add", path: "/spec/template/spec/containers/0/imagePullPolicy", value: "IfNotPresent"} + ]' + )" -o yaml | kubectl --namespace default apply -f - + kubectl --namespace default wait -f service.yaml --for=condition=Ready --timeout=300s +) + +test() ( + set -euo pipefail + local service_url service_host + service_url=$(kubectl --namespace default get -f service.yaml -o jsonpath='{.status.url}') + service_host=${service_url#*://} + [[ "$service_host" =~ ^[a-zA-Z0-9.-]+$ ]] + local body + body=$(curl -fsS --max-time 30 \ + -H "Host: $service_host" "$SAMPLE_URL/") + [[ $body == 'Hello Dart Sample v1' ]] +) diff --git a/code-samples/community/serving/helloworld-dart/pubspec.yaml b/code-samples/community/serving/helloworld-dart/pubspec.yaml index 52303ec85e..069d2cad23 100644 --- a/code-samples/community/serving/helloworld-dart/pubspec.yaml +++ b/code-samples/community/serving/helloworld-dart/pubspec.yaml @@ -10,6 +10,4 @@ dependencies: shelf_router: ^1.1.0 dev_dependencies: - http: ^1.0.0 lints: ^4.0.0 - test: ^1.21.0 From 08b49aaad43b36f6dbde9a96f9d090f49e1e5a2b Mon Sep 17 00:00:00 2001 From: Dave Protasowski Date: Mon, 26 Oct 2026 09:16:03 -0400 Subject: [PATCH 04/44] Update and simplify the Elixir Hello World sample --- .../serving/helloworld-elixir/.formatter.exs | 3 + .../serving/helloworld-elixir/Dockerfile | 58 ++-- .../serving/helloworld-elixir/README.md | 302 ++---------------- .../helloworld-elixir/assets/brunch-config.js | 62 ---- .../helloworld-elixir/assets/css/app.css | 1 - .../helloworld-elixir/assets/css/phoenix.css | 77 ----- .../helloworld-elixir/assets/js/app.js | 21 -- .../helloworld-elixir/assets/js/socket.js | 62 ---- .../helloworld-elixir/assets/package.json | 18 -- .../assets/static/favicon.ico | Bin 1258 -> 0 bytes .../assets/static/images/phoenix.png | Bin 13900 -> 0 bytes .../assets/static/robots.txt | 5 - .../community/serving/helloworld-elixir/ci.sh | 42 +++ .../helloworld-elixir/config/config.exs | 27 +- .../serving/helloworld-elixir/config/dev.exs | 49 +-- .../serving/helloworld-elixir/config/prod.exs | 66 +--- .../config/prod.secret.exs.sample | 12 - .../helloworld-elixir/config/runtime.exs | 13 + .../serving/helloworld-elixir/config/test.exs | 10 - .../lib/hello/application.ex | 22 +- .../helloworld-elixir/lib/hello_web.ex | 55 +--- .../lib/hello_web/channels/user_socket.ex | 38 --- .../hello_web/controllers/page_controller.ex | 19 +- .../lib/hello_web/endpoint.ex | 63 +--- .../lib/hello_web/gettext.ex | 24 -- .../helloworld-elixir/lib/hello_web/router.ex | 18 +- .../hello_web/templates/layout/app.html.eex | 26 +- .../lib/hello_web/views/error_helpers.ex | 44 --- .../lib/hello_web/views/error_view.ex | 16 +- .../serving/helloworld-elixir/mix.exs | 37 +-- .../serving/helloworld-elixir/mix.lock | 28 +- .../priv/gettext/en/LC_MESSAGES/errors.po | 11 - .../helloworld-elixir/priv/gettext/errors.pot | 10 - .../helloworld-elixir/priv/static/css/app.css | 4 + .../serving/helloworld-elixir/rel/config.exs | 53 --- .../controllers/page_controller_test.exs | 8 - .../test/hello_web/views/error_view_test.exs | 16 - .../test/hello_web/views/layout_view_test.exs | 3 - .../test/hello_web/views/page_view_test.exs | 3 - .../test/support/channel_case.ex | 33 -- .../test/support/conn_case.ex | 34 -- .../helloworld-elixir/test/test_helper.exs | 2 - 42 files changed, 215 insertions(+), 1180 deletions(-) create mode 100644 code-samples/community/serving/helloworld-elixir/.formatter.exs delete mode 100644 code-samples/community/serving/helloworld-elixir/assets/brunch-config.js delete mode 100644 code-samples/community/serving/helloworld-elixir/assets/css/app.css delete mode 100644 code-samples/community/serving/helloworld-elixir/assets/css/phoenix.css delete mode 100644 code-samples/community/serving/helloworld-elixir/assets/js/app.js delete mode 100644 code-samples/community/serving/helloworld-elixir/assets/js/socket.js delete mode 100644 code-samples/community/serving/helloworld-elixir/assets/package.json delete mode 100644 code-samples/community/serving/helloworld-elixir/assets/static/favicon.ico delete mode 100644 code-samples/community/serving/helloworld-elixir/assets/static/images/phoenix.png delete mode 100644 code-samples/community/serving/helloworld-elixir/assets/static/robots.txt create mode 100755 code-samples/community/serving/helloworld-elixir/ci.sh delete mode 100644 code-samples/community/serving/helloworld-elixir/config/prod.secret.exs.sample create mode 100644 code-samples/community/serving/helloworld-elixir/config/runtime.exs delete mode 100644 code-samples/community/serving/helloworld-elixir/config/test.exs delete mode 100644 code-samples/community/serving/helloworld-elixir/lib/hello_web/channels/user_socket.ex delete mode 100644 code-samples/community/serving/helloworld-elixir/lib/hello_web/gettext.ex delete mode 100644 code-samples/community/serving/helloworld-elixir/lib/hello_web/views/error_helpers.ex delete mode 100644 code-samples/community/serving/helloworld-elixir/priv/gettext/en/LC_MESSAGES/errors.po delete mode 100644 code-samples/community/serving/helloworld-elixir/priv/gettext/errors.pot create mode 100644 code-samples/community/serving/helloworld-elixir/priv/static/css/app.css delete mode 100644 code-samples/community/serving/helloworld-elixir/rel/config.exs delete mode 100644 code-samples/community/serving/helloworld-elixir/test/hello_web/controllers/page_controller_test.exs delete mode 100644 code-samples/community/serving/helloworld-elixir/test/hello_web/views/error_view_test.exs delete mode 100644 code-samples/community/serving/helloworld-elixir/test/hello_web/views/layout_view_test.exs delete mode 100644 code-samples/community/serving/helloworld-elixir/test/hello_web/views/page_view_test.exs delete mode 100644 code-samples/community/serving/helloworld-elixir/test/support/channel_case.ex delete mode 100644 code-samples/community/serving/helloworld-elixir/test/support/conn_case.ex delete mode 100644 code-samples/community/serving/helloworld-elixir/test/test_helper.exs diff --git a/code-samples/community/serving/helloworld-elixir/.formatter.exs b/code-samples/community/serving/helloworld-elixir/.formatter.exs new file mode 100644 index 0000000000..d3e23b832e --- /dev/null +++ b/code-samples/community/serving/helloworld-elixir/.formatter.exs @@ -0,0 +1,3 @@ +[ + inputs: ["mix.exs", ".formatter.exs", "{config,lib}/**/*.{ex,exs}"] +] diff --git a/code-samples/community/serving/helloworld-elixir/Dockerfile b/code-samples/community/serving/helloworld-elixir/Dockerfile index dd8c663b17..cdfd698ef7 100644 --- a/code-samples/community/serving/helloworld-elixir/Dockerfile +++ b/code-samples/community/serving/helloworld-elixir/Dockerfile @@ -1,38 +1,26 @@ -FROM elixir:1.6.6-alpine +FROM hexpm/elixir:1.20.4-erlang-29.1.2-alpine-3.24.3 AS build -ARG APP_NAME=hello -ARG PHOENIX_SUBDIR=. -ENV MIX_ENV=prod REPLACE_OS_VARS=true TERM=xterm -WORKDIR /opt/app -RUN apk update \ - && apk --no-cache --update add nodejs nodejs-npm \ - && mix local.rebar --force \ - && mix local.hex --force -COPY . . - -RUN mix do deps.get, deps.compile, compile -RUN cd ${PHOENIX_SUBDIR}/assets \ - && npm install \ - && ./node_modules/brunch/bin/brunch build -p \ - && cd .. \ - && mix phx.digest -RUN mix release --env=prod --verbose \ - && mv _build/prod/rel/${APP_NAME} /opt/release \ - && mv /opt/release/bin/${APP_NAME} /opt/release/bin/start_server - -FROM alpine:latest -RUN apk update && apk --no-cache --update add bash openssl-dev ca-certificates - -RUN addgroup -g 1000 appuser && \ - adduser -S -u 1000 -G appuser appuser - -RUN mkdir -p /opt/app/var -RUN chown appuser /opt/app/var +ENV MIX_ENV=prod +WORKDIR /app +RUN apk add --no-cache build-base git \ + && mix local.hex --force \ + && mix local.rebar --force +COPY mix.exs mix.lock ./ +COPY config config +RUN mix deps.get --only prod && mix deps.compile +COPY lib lib +COPY priv priv +RUN mix compile --warnings-as-errors \ + && mix phx.digest \ + && mix release +# Same Alpine/OpenSSL ABI as the builder; no compiler, Node or Mix in the runtime. +FROM alpine:3.24.3 AS runtime +RUN apk add --no-cache libstdc++ ncurses-libs openssl ca-certificates \ + && addgroup -S appuser && adduser -S -G appuser appuser +WORKDIR /app +COPY --from=build --chown=appuser:appuser /app/_build/prod/rel/hello ./ USER appuser - -ENV MIX_ENV=prod REPLACE_OS_VARS=true -WORKDIR /opt/app -COPY --from=0 /opt/release . -ENV RUNNER_LOG_DIR /var/log -CMD ["/opt/app/bin/start_server", "foreground", "boot_var=/tmp"] +ENV HOME=/app LANG=C.UTF-8 +EXPOSE 8080 +CMD ["/app/bin/hello", "start"] diff --git a/code-samples/community/serving/helloworld-elixir/README.md b/code-samples/community/serving/helloworld-elixir/README.md index 62ec1612ae..a019cb299b 100644 --- a/code-samples/community/serving/helloworld-elixir/README.md +++ b/code-samples/community/serving/helloworld-elixir/README.md @@ -1,289 +1,47 @@ # Hello World - Elixir -A simple web application written in [Elixir](https://elixir-lang.org/) using the -[Phoenix Framework](https://phoenixframework.org/). The application prints all -environment variables to the main page. +A [Phoenix](https://www.phoenixframework.org/) web application for Knative Serving. +It displays a greeting and an HTML-escaped `TARGET`. Only `TARGET`, `PORT`, +`K_SERVICE`, `K_REVISION`, and `K_CONFIGURATION` appear in its environment list. -## Set up Elixir and Phoenix Locally +## Build and run -Following the -[Phoenix Installation Guide](https://hexdocs.pm/phoenix/installation.html) is -the best way to get your computer set up for developing, building, running, and -packaging Elixir Web applications. +Run from this sample directory: -## Running Locally - -To start your Phoenix server: - -- Install dependencies with `mix deps.get` -- Install Node.js dependencies with `cd assets && npm install` -- Start Phoenix endpoint with `mix phx.server` - -Now you can visit [`localhost:4000`](http://localhost:4000) from your browser. - -## Recreating the sample code - -1. Generate a new project. - - ```bash - mix phoenix.new helloelixir - ``` - - When asked, if you want to `Fetch and install dependencies? [Yn]` select `y` - -1. Follow the direction in the output to change directories into start your - local server with `mix phoenix.server` - -1. In the new directory, create a new Dockerfile for packaging your application - for deployment - - ```docker - # Start from a base image for elixir - # Phoenix works best on pre 1.7 at the moment. - FROM elixir:1.6.6-alpine - - # Set up Elixir and Phoenix - ARG APP_NAME=hello - ARG PHOENIX_SUBDIR=. - ENV MIX_ENV=prod REPLACE_OS_VARS=true TERM=xterm - WORKDIR /opt/app - - # Update nodejs, rebar, and hex. - RUN apk update \ - && apk --no-cache --update add nodejs nodejs-npm \ - && mix local.rebar --force \ - && mix local.hex --force - COPY . . - - # Download and compile dependencies, then compile Web app. - RUN mix do deps.get, deps.compile, compile - RUN cd ${PHOENIX_SUBDIR}/assets \ - && npm install \ - && ./node_modules/brunch/bin/brunch build -p \ - && cd .. \ - && mix phx.digest - - # Create a release version of the application - RUN mix release --env=prod --verbose \ - && mv _build/prod/rel/${APP_NAME} /opt/release \ - && mv /opt/release/bin/${APP_NAME} /opt/release/bin/start_server - - # Prepare final layer - FROM alpine:latest - RUN apk update && apk --no-cache --update add bash openssl-dev ca-certificates - - # Add a user so the server will run as a non-root user. - RUN addgroup -g 1000 appuser && \ - adduser -S -u 1000 -G appuser appuser - # Pre-create necessary temp directory for erlang and set permissions. - RUN mkdir -p /opt/app/var - RUN chown appuser /opt/app/var - # Run everything else as 'appuser' - USER appuser - - ENV MIX_ENV=prod REPLACE_OS_VARS=true - WORKDIR /opt/app - COPY --from=0 /opt/release . - ENV RUNNER_LOG_DIR /var/log - - # Command to execute the application. - CMD ["/opt/app/bin/start_server", "foreground", "boot_var=/tmp"] - ``` - -1. Create a new file, `service.yaml` and copy the following Service definition - into the file. Make sure to replace `{username}` with your Docker Hub - username. - - ```yaml - apiVersion: serving.knative.dev/v1 - kind: Service - metadata: - name: helloworld-elixir - namespace: default - spec: - template: - spec: - containers: - - image: docker.io/{username}/helloworld-elixir - env: - - name: TARGET - value: "elixir Sample v1" - ``` - -## Building and deploying the sample - -The sample in this directory is ready to build and deploy without changes. You -can deploy the sample as is, or use you created version following the directions -above. - -1. Generate a new `secret_key_base` in the `config/prod.secret.exs` file. - Phoenix applications use a secrets file on production deployments and, by - default, that file is not checked into source control. We have provides - shell of an example on `config/prod.secret.exs.sample` and you can use the - following command to generate a new prod secrets file. - - ```bash - SECRET_KEY_BASE=$(elixir -e ":crypto.strong_rand_bytes(48) |> Base.encode64 |> IO.puts") - sed "s|SECRET+KEY+BASE|$SECRET_KEY_BASE|" config/prod.secret.exs.sample >config/prod.secret.exs - ``` - -1. Use Docker to build the sample code into a container. To build and push with - Docker Hub, run these commands replacing `{username}` with your Docker Hub - username: - - ```bash - # Build and push the container on your local machine. - docker buildx build --platform linux/arm64,linux/amd64 -t "{username}/helloworld-elixir" --push . - ``` - -1. After the build has completed and the container is pushed to docker hub, you - can deploy the app into your cluster. Ensure that the container image value - in `service.yaml` matches the container you built in the previous step. - Apply the configuration using `kubectl`: - - ```bash - kubectl apply --filename service.yaml - ``` - -1. Now that your service is created, Knative will perform the following steps: - - - Create a new immutable revision for this version of the app. - - Network programming to create a route, ingress, service, and load balance - for your app. - - Automatically scale your pods up and down (including to zero active pods). - -1. To find the URL for your service, use - - ``` - kubectl get ksvc helloworld-elixir --output=custom-columns=NAME:.metadata.name,URL:.status.url - - NAME URL - helloworld-elixir http://helloworld-elixir.default.1.2.3.4.sslip.io - ``` - -1. Now you can make a request to your app to see the results. Replace - `{IP_ADDRESS}` with the address you see returned in the previous step. - - ```bash - curl http://helloworld-elixir.default.1.2.3.4.sslip.io - - ... - # HTML from your application is returned. - ``` - - Here is the HTML returned from our deployed sample application: - - ```HTML - - - - - - - - - - Hello Knative - - - - -
-
- -
+```bash +docker build -t helloworld-elixir . +docker run --rm -p 127.0.0.1:8080:8080 \ + -e TARGET="elixir Sample v1" helloworld-elixir +``` - - +In another terminal: -
+```bash +curl --fail http://localhost:8080/ +``` -
-

Welcome to Knative and Elixir

+The page includes `Welcome to Knative and Elixir` and `$TARGET = elixir Sample v1`. +The server listens on `PORT`, defaulting to `8080`. -

$TARGET = elixir Sample v1

-
+## Deploy to Knative -

Environment

-
    -
  • BINDIR = /opt/app/erts-9.3.2/bin
  • -
  • DEST_SYS_CONFIG_PATH = /opt/app/var/sys.config
  • -
  • DEST_VMARGS_PATH = /opt/app/var/vm.args
  • -
  • DISTILLERY_TASK = foreground
  • -
  • EMU = beam
  • -
  • ERL_LIBS = /opt/app/lib
  • -
  • ERL_OPTS =
  • -
  • ERTS_DIR = /opt/app/erts-9.3.2
  • -
  • ERTS_LIB_DIR = /opt/app/erts-9.3.2/../lib
  • -
  • ERTS_VSN = 9.3.2
  • -
  • HELLOWORLD_ELIXIR_00001_SERVICE_PORT = tcp://10.35.241.50:80
  • -
  • HELLOWORLD_ELIXIR_00001_SERVICE_PORT_80_TCP = tcp://10.35.241.50:80
  • -
  • HELLOWORLD_ELIXIR_00001_SERVICE_PORT_80_TCP_ADDR = 10.35.241.50
  • -
  • HELLOWORLD_ELIXIR_00001_SERVICE_PORT_80_TCP_PORT = 80
  • -
  • HELLOWORLD_ELIXIR_00001_SERVICE_PORT_80_TCP_PROTO = tcp
  • -
  • HELLOWORLD_ELIXIR_00001_SERVICE_SERVICE_HOST = 10.35.241.50
  • -
  • HELLOWORLD_ELIXIR_00001_SERVICE_SERVICE_PORT = 80
  • -
  • HELLOWORLD_ELIXIR_00001_SERVICE_SERVICE_PORT_HTTP = 80
  • -
  • HELLOWORLD_ELIXIR_PORT = tcp://10.35.253.90:80
  • -
  • HELLOWORLD_ELIXIR_PORT_80_TCP = tcp://10.35.253.90:80
  • -
  • HELLOWORLD_ELIXIR_PORT_80_TCP_ADDR = 10.35.253.90
  • -
  • HELLOWORLD_ELIXIR_PORT_80_TCP_PORT = 80
  • -
  • HELLOWORLD_ELIXIR_PORT_80_TCP_PROTO = tcp
  • -
  • HELLOWORLD_ELIXIR_SERVICE_HOST = 10.35.253.90
  • -
  • HELLOWORLD_ELIXIR_SERVICE_PORT = 80
  • -
  • HELLOWORLD_ELIXIR_SERVICE_PORT_HTTP = 80
  • -
  • HOME = /root
  • -
  • HOSTNAME = helloworld-elixir-00001-deployment-84f68946b4-76hcv
  • -
  • KUBERNETES_PORT = tcp://10.35.240.1:443
  • -
  • KUBERNETES_PORT_443_TCP = tcp://10.35.240.1:443
  • -
  • KUBERNETES_PORT_443_TCP_ADDR = 10.35.240.1
  • -
  • KUBERNETES_PORT_443_TCP_PORT = 443
  • -
  • KUBERNETES_PORT_443_TCP_PROTO = tcp
  • -
  • KUBERNETES_SERVICE_HOST = 10.35.240.1
  • -
  • KUBERNETES_SERVICE_PORT = 443
  • -
  • KUBERNETES_SERVICE_PORT_HTTPS = 443
  • -
  • LD_LIBRARY_PATH = /opt/app/erts-9.3.2/lib:
  • -
  • MIX_ENV = prod
  • -
  • NAME = hello@127.0.0.1
  • -
  • NAME_ARG = -name hello@127.0.0.1
  • -
  • NAME_TYPE = -name
  • -
  • OLDPWD = /opt/app
  • -
  • OTP_VER = 20
  • -
  • PATH = /opt/app/erts-9.3.2/bin:/opt/app/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
  • -
  • PORT = 8080
  • -
  • PROGNAME = opt/app/releases/0.0.1/hello.sh
  • -
  • PWD = /opt/app
  • -
  • RELEASES_DIR = /opt/app/releases
  • -
  • RELEASE_CONFIG_DIR = /opt/app
  • -
  • RELEASE_ROOT_DIR = /opt/app
  • -
  • REL_NAME = hello
  • -
  • REL_VSN = 0.0.1
  • -
  • REPLACE_OS_VARS = true
  • -
  • ROOTDIR = /opt/app
  • -
  • RUNNER_LOG_DIR = /var/log
  • -
  • RUN_ERL_ENV =
  • -
  • SHLVL = 1
  • -
  • SRC_SYS_CONFIG_PATH = /opt/app/releases/0.0.1/sys.config
  • -
  • SRC_VMARGS_PATH = /opt/app/releases/0.0.1/vm.args
  • -
  • SYS_CONFIG_PATH = /opt/app/var/sys.config
  • -
  • TARGET = elixir Sample v1
  • -
  • TERM = xterm
  • -
  • VMARGS_PATH = /opt/app/var/vm.args
  • -
-
+With Knative Serving installed, build and push your image, replacing `{username}`: -
- +```bash +docker buildx build --platform linux/arm64,linux/amd64 \ + -t "docker.io/{username}/helloworld-elixir" --push . +``` - - - ``` +Set the image in [service.yaml](service.yaml) to the same reference, then deploy: -## Removing the sample app deployment +```bash +kubectl apply --filename service.yaml +kubectl wait --for=condition=Ready ksvc/helloworld-elixir --timeout=180s +SERVICE_URL=$(kubectl get ksvc helloworld-elixir -o jsonpath='{.status.url}') +curl --fail "$SERVICE_URL" +``` -To remove the sample app from your cluster, delete the service record: +## Cleanup ```bash kubectl delete --filename service.yaml diff --git a/code-samples/community/serving/helloworld-elixir/assets/brunch-config.js b/code-samples/community/serving/helloworld-elixir/assets/brunch-config.js deleted file mode 100644 index b15df46007..0000000000 --- a/code-samples/community/serving/helloworld-elixir/assets/brunch-config.js +++ /dev/null @@ -1,62 +0,0 @@ -exports.config = { - // See http://brunch.io/#documentation for docs. - files: { - javascripts: { - joinTo: "js/app.js" - - // To use a separate vendor.js bundle, specify two files path - // http://brunch.io/docs/config#-files- - // joinTo: { - // "js/app.js": /^js/, - // "js/vendor.js": /^(?!js)/ - // } - // - // To change the order of concatenation of files, explicitly mention here - // order: { - // before: [ - // "vendor/js/jquery-2.1.1.js", - // "vendor/js/bootstrap.min.js" - // ] - // } - }, - stylesheets: { - joinTo: "css/app.css" - }, - templates: { - joinTo: "js/app.js" - } - }, - - conventions: { - // This option sets where we should place non-css and non-js assets in. - // By default, we set this to "/assets/static". Files in this directory - // will be copied to `paths.public`, which is "priv/static" by default. - assets: /^(static)/ - }, - - // Phoenix paths configuration - paths: { - // Dependencies and current project directories to watch - watched: ["static", "css", "js", "vendor"], - // Where to compile files to - public: "../priv/static" - }, - - // Configure your plugins - plugins: { - babel: { - // Do not use ES6 compiler in vendor code - ignore: [/vendor/] - } - }, - - modules: { - autoRequire: { - "js/app.js": ["js/app"] - } - }, - - npm: { - enabled: true - } -}; diff --git a/code-samples/community/serving/helloworld-elixir/assets/css/app.css b/code-samples/community/serving/helloworld-elixir/assets/css/app.css deleted file mode 100644 index 21d59de1dd..0000000000 --- a/code-samples/community/serving/helloworld-elixir/assets/css/app.css +++ /dev/null @@ -1 +0,0 @@ -/* This file is for your main application css. */ diff --git a/code-samples/community/serving/helloworld-elixir/assets/css/phoenix.css b/code-samples/community/serving/helloworld-elixir/assets/css/phoenix.css deleted file mode 100644 index 84d794427a..0000000000 --- a/code-samples/community/serving/helloworld-elixir/assets/css/phoenix.css +++ /dev/null @@ -1,77 +0,0 @@ -/* Includes Bootstrap as well as some default style for the starter - * application. This can be safely deleted to start fresh. - */ - -/*! - * Bootstrap v3.3.5 (http://getbootstrap.com) - * Copyright 2011-2015 Twitter, Inc. - * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) - *//*! normalize.css v3.0.3 | MIT License | github.com/necolas/normalize.css */html{font-family:sans-serif;-webkit-text-size-adjust:100%;-ms-text-size-adjust:100%}body{margin:0}article,aside,details,figcaption,figure,footer,header,hgroup,main,menu,nav,section,summary{display:block}audio,canvas,progress,video{display:inline-block;vertical-align:baseline}audio:not([controls]){display:none;height:0}[hidden],template{display:none}a{background-color:transparent}a:active,a:hover{outline:0}abbr[title]{border-bottom:1px dotted}b,strong{font-weight:700}dfn{font-style:italic}h1{margin:.67em 0;font-size:2em}mark{color:#000;background:#ff0}small{font-size:80%}sub,sup{position:relative;font-size:75%;line-height:0;vertical-align:baseline}sup{top:-.5em}sub{bottom:-.25em}img{border:0}svg:not(:root){overflow:hidden}figure{margin:1em 40px}hr{height:0;-webkit-box-sizing:content-box;-moz-box-sizing:content-box;box-sizing:content-box}pre{overflow:auto}code,kbd,pre,samp{font-family:monospace,monospace;font-size:1em}button,input,optgroup,select,textarea{margin:0;font:inherit;color:inherit}button{overflow:visible}button,select{text-transform:none}button,html input[type=button],input[type=reset],input[type=submit]{-webkit-appearance:button;cursor:pointer}button[disabled],html input[disabled]{cursor:default}button::-moz-focus-inner,input::-moz-focus-inner{padding:0;border:0}input{line-height:normal}input[type=checkbox],input[type=radio]{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box;padding:0}input[type=number]::-webkit-inner-spin-button,input[type=number]::-webkit-outer-spin-button{height:auto}input[type=search]{-webkit-box-sizing:content-box;-moz-box-sizing:content-box;box-sizing:content-box;-webkit-appearance:textfield}input[type=search]::-webkit-search-cancel-button,input[type=search]::-webkit-search-decoration{-webkit-appearance:none}fieldset{padding:.35em .625em .75em;margin:0 2px;border:1px solid silver}legend{padding:0;border:0}textarea{overflow:auto}optgroup{font-weight:700}table{border-spacing:0;border-collapse:collapse}td,th{padding:0}/*! Source: https://github.com/h5bp/html5-boilerplate/blob/master/src/css/main.css */@media print{*,:after,:before{color:#000!important;text-shadow:none!important;background:0 0!important;-webkit-box-shadow:none!important;box-shadow:none!important}a,a:visited{text-decoration:underline}a[href]:after{content:" (" attr(href) ")"}abbr[title]:after{content:" (" attr(title) ")"}a[href^="javascript:"]:after,a[href^="#"]:after{content:""}blockquote,pre{border:1px solid #999;page-break-inside:avoid}thead{display:table-header-group}img,tr{page-break-inside:avoid}img{max-width:100%!important}h2,h3,p{orphans:3;widows:3}h2,h3{page-break-after:avoid}.navbar{display:none}.btn>.caret,.dropup>.btn>.caret{border-top-color:#000!important}.label{border:1px solid #000}.table{border-collapse:collapse!important}.table td,.table th{background-color:#fff!important}.table-bordered td,.table-bordered th{border:1px solid #ddd!important}}@font-face{font-family:'Glyphicons Halflings';src:url(../fonts/glyphicons-halflings-regular.eot);src:url(../fonts/glyphicons-halflings-regular.eot?#iefix) format('embedded-opentype'),url(../fonts/glyphicons-halflings-regular.woff2) format('woff2'),url(../fonts/glyphicons-halflings-regular.woff) format('woff'),url(../fonts/glyphicons-halflings-regular.ttf) format('truetype'),url(../fonts/glyphicons-halflings-regular.svg#glyphicons_halflingsregular) format('svg')}.glyphicon{position:relative;top:1px;display:inline-block;font-family:'Glyphicons Halflings';font-style:normal;font-weight:400;line-height:1;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}.glyphicon-asterisk:before{content:"\2a"}.glyphicon-plus:before{content:"\2b"}.glyphicon-eur:before,.glyphicon-euro:before{content:"\20ac"}.glyphicon-minus:before{content:"\2212"}.glyphicon-cloud:before{content:"\2601"}.glyphicon-envelope:before{content:"\2709"}.glyphicon-pencil:before{content:"\270f"}.glyphicon-glass:before{content:"\e001"}.glyphicon-music:before{content:"\e002"}.glyphicon-search:before{content:"\e003"}.glyphicon-heart:before{content:"\e005"}.glyphicon-star:before{content:"\e006"}.glyphicon-star-empty:before{content:"\e007"}.glyphicon-user:before{content:"\e008"}.glyphicon-film:before{content:"\e009"}.glyphicon-th-large:before{content:"\e010"}.glyphicon-th:before{content:"\e011"}.glyphicon-th-list:before{content:"\e012"}.glyphicon-ok:before{content:"\e013"}.glyphicon-remove:before{content:"\e014"}.glyphicon-zoom-in:before{content:"\e015"}.glyphicon-zoom-out:before{content:"\e016"}.glyphicon-off:before{content:"\e017"}.glyphicon-signal:before{content:"\e018"}.glyphicon-cog:before{content:"\e019"}.glyphicon-trash:before{content:"\e020"}.glyphicon-home:before{content:"\e021"}.glyphicon-file:before{content:"\e022"}.glyphicon-time:before{content:"\e023"}.glyphicon-road:before{content:"\e024"}.glyphicon-download-alt:before{content:"\e025"}.glyphicon-download:before{content:"\e026"}.glyphicon-upload:before{content:"\e027"}.glyphicon-inbox:before{content:"\e028"}.glyphicon-play-circle:before{content:"\e029"}.glyphicon-repeat:before{content:"\e030"}.glyphicon-refresh:before{content:"\e031"}.glyphicon-list-alt:before{content:"\e032"}.glyphicon-lock:before{content:"\e033"}.glyphicon-flag:before{content:"\e034"}.glyphicon-headphones:before{content:"\e035"}.glyphicon-volume-off:before{content:"\e036"}.glyphicon-volume-down:before{content:"\e037"}.glyphicon-volume-up:before{content:"\e038"}.glyphicon-qrcode:before{content:"\e039"}.glyphicon-barcode:before{content:"\e040"}.glyphicon-tag:before{content:"\e041"}.glyphicon-tags:before{content:"\e042"}.glyphicon-book:before{content:"\e043"}.glyphicon-bookmark:before{content:"\e044"}.glyphicon-print:before{content:"\e045"}.glyphicon-camera:before{content:"\e046"}.glyphicon-font:before{content:"\e047"}.glyphicon-bold:before{content:"\e048"}.glyphicon-italic:before{content:"\e049"}.glyphicon-text-height:before{content:"\e050"}.glyphicon-text-width:before{content:"\e051"}.glyphicon-align-left:before{content:"\e052"}.glyphicon-align-center:before{content:"\e053"}.glyphicon-align-right:before{content:"\e054"}.glyphicon-align-justify:before{content:"\e055"}.glyphicon-list:before{content:"\e056"}.glyphicon-indent-left:before{content:"\e057"}.glyphicon-indent-right:before{content:"\e058"}.glyphicon-facetime-video:before{content:"\e059"}.glyphicon-picture:before{content:"\e060"}.glyphicon-map-marker:before{content:"\e062"}.glyphicon-adjust:before{content:"\e063"}.glyphicon-tint:before{content:"\e064"}.glyphicon-edit:before{content:"\e065"}.glyphicon-share:before{content:"\e066"}.glyphicon-check:before{content:"\e067"}.glyphicon-move:before{content:"\e068"}.glyphicon-step-backward:before{content:"\e069"}.glyphicon-fast-backward:before{content:"\e070"}.glyphicon-backward:before{content:"\e071"}.glyphicon-play:before{content:"\e072"}.glyphicon-pause:before{content:"\e073"}.glyphicon-stop:before{content:"\e074"}.glyphicon-forward:before{content:"\e075"}.glyphicon-fast-forward:before{content:"\e076"}.glyphicon-step-forward:before{content:"\e077"}.glyphicon-eject:before{content:"\e078"}.glyphicon-chevron-left:before{content:"\e079"}.glyphicon-chevron-right:before{content:"\e080"}.glyphicon-plus-sign:before{content:"\e081"}.glyphicon-minus-sign:before{content:"\e082"}.glyphicon-remove-sign:before{content:"\e083"}.glyphicon-ok-sign:before{content:"\e084"}.glyphicon-question-sign:before{content:"\e085"}.glyphicon-info-sign:before{content:"\e086"}.glyphicon-screenshot:before{content:"\e087"}.glyphicon-remove-circle:before{content:"\e088"}.glyphicon-ok-circle:before{content:"\e089"}.glyphicon-ban-circle:before{content:"\e090"}.glyphicon-arrow-left:before{content:"\e091"}.glyphicon-arrow-right:before{content:"\e092"}.glyphicon-arrow-up:before{content:"\e093"}.glyphicon-arrow-down:before{content:"\e094"}.glyphicon-share-alt:before{content:"\e095"}.glyphicon-resize-full:before{content:"\e096"}.glyphicon-resize-small:before{content:"\e097"}.glyphicon-exclamation-sign:before{content:"\e101"}.glyphicon-gift:before{content:"\e102"}.glyphicon-leaf:before{content:"\e103"}.glyphicon-fire:before{content:"\e104"}.glyphicon-eye-open:before{content:"\e105"}.glyphicon-eye-close:before{content:"\e106"}.glyphicon-warning-sign:before{content:"\e107"}.glyphicon-plane:before{content:"\e108"}.glyphicon-calendar:before{content:"\e109"}.glyphicon-random:before{content:"\e110"}.glyphicon-comment:before{content:"\e111"}.glyphicon-magnet:before{content:"\e112"}.glyphicon-chevron-up:before{content:"\e113"}.glyphicon-chevron-down:before{content:"\e114"}.glyphicon-retweet:before{content:"\e115"}.glyphicon-shopping-cart:before{content:"\e116"}.glyphicon-folder-close:before{content:"\e117"}.glyphicon-folder-open:before{content:"\e118"}.glyphicon-resize-vertical:before{content:"\e119"}.glyphicon-resize-horizontal:before{content:"\e120"}.glyphicon-hdd:before{content:"\e121"}.glyphicon-bullhorn:before{content:"\e122"}.glyphicon-bell:before{content:"\e123"}.glyphicon-certificate:before{content:"\e124"}.glyphicon-thumbs-up:before{content:"\e125"}.glyphicon-thumbs-down:before{content:"\e126"}.glyphicon-hand-right:before{content:"\e127"}.glyphicon-hand-left:before{content:"\e128"}.glyphicon-hand-up:before{content:"\e129"}.glyphicon-hand-down:before{content:"\e130"}.glyphicon-circle-arrow-right:before{content:"\e131"}.glyphicon-circle-arrow-left:before{content:"\e132"}.glyphicon-circle-arrow-up:before{content:"\e133"}.glyphicon-circle-arrow-down:before{content:"\e134"}.glyphicon-globe:before{content:"\e135"}.glyphicon-wrench:before{content:"\e136"}.glyphicon-tasks:before{content:"\e137"}.glyphicon-filter:before{content:"\e138"}.glyphicon-briefcase:before{content:"\e139"}.glyphicon-fullscreen:before{content:"\e140"}.glyphicon-dashboard:before{content:"\e141"}.glyphicon-paperclip:before{content:"\e142"}.glyphicon-heart-empty:before{content:"\e143"}.glyphicon-link:before{content:"\e144"}.glyphicon-phone:before{content:"\e145"}.glyphicon-pushpin:before{content:"\e146"}.glyphicon-usd:before{content:"\e148"}.glyphicon-gbp:before{content:"\e149"}.glyphicon-sort:before{content:"\e150"}.glyphicon-sort-by-alphabet:before{content:"\e151"}.glyphicon-sort-by-alphabet-alt:before{content:"\e152"}.glyphicon-sort-by-order:before{content:"\e153"}.glyphicon-sort-by-order-alt:before{content:"\e154"}.glyphicon-sort-by-attributes:before{content:"\e155"}.glyphicon-sort-by-attributes-alt:before{content:"\e156"}.glyphicon-unchecked:before{content:"\e157"}.glyphicon-expand:before{content:"\e158"}.glyphicon-collapse-down:before{content:"\e159"}.glyphicon-collapse-up:before{content:"\e160"}.glyphicon-log-in:before{content:"\e161"}.glyphicon-flash:before{content:"\e162"}.glyphicon-log-out:before{content:"\e163"}.glyphicon-new-window:before{content:"\e164"}.glyphicon-record:before{content:"\e165"}.glyphicon-save:before{content:"\e166"}.glyphicon-open:before{content:"\e167"}.glyphicon-saved:before{content:"\e168"}.glyphicon-import:before{content:"\e169"}.glyphicon-export:before{content:"\e170"}.glyphicon-send:before{content:"\e171"}.glyphicon-floppy-disk:before{content:"\e172"}.glyphicon-floppy-saved:before{content:"\e173"}.glyphicon-floppy-remove:before{content:"\e174"}.glyphicon-floppy-save:before{content:"\e175"}.glyphicon-floppy-open:before{content:"\e176"}.glyphicon-credit-card:before{content:"\e177"}.glyphicon-transfer:before{content:"\e178"}.glyphicon-cutlery:before{content:"\e179"}.glyphicon-header:before{content:"\e180"}.glyphicon-compressed:before{content:"\e181"}.glyphicon-earphone:before{content:"\e182"}.glyphicon-phone-alt:before{content:"\e183"}.glyphicon-tower:before{content:"\e184"}.glyphicon-stats:before{content:"\e185"}.glyphicon-sd-video:before{content:"\e186"}.glyphicon-hd-video:before{content:"\e187"}.glyphicon-subtitles:before{content:"\e188"}.glyphicon-sound-stereo:before{content:"\e189"}.glyphicon-sound-dolby:before{content:"\e190"}.glyphicon-sound-5-1:before{content:"\e191"}.glyphicon-sound-6-1:before{content:"\e192"}.glyphicon-sound-7-1:before{content:"\e193"}.glyphicon-copyright-mark:before{content:"\e194"}.glyphicon-registration-mark:before{content:"\e195"}.glyphicon-cloud-download:before{content:"\e197"}.glyphicon-cloud-upload:before{content:"\e198"}.glyphicon-tree-conifer:before{content:"\e199"}.glyphicon-tree-deciduous:before{content:"\e200"}.glyphicon-cd:before{content:"\e201"}.glyphicon-save-file:before{content:"\e202"}.glyphicon-open-file:before{content:"\e203"}.glyphicon-level-up:before{content:"\e204"}.glyphicon-copy:before{content:"\e205"}.glyphicon-paste:before{content:"\e206"}.glyphicon-alert:before{content:"\e209"}.glyphicon-equalizer:before{content:"\e210"}.glyphicon-king:before{content:"\e211"}.glyphicon-queen:before{content:"\e212"}.glyphicon-pawn:before{content:"\e213"}.glyphicon-bishop:before{content:"\e214"}.glyphicon-knight:before{content:"\e215"}.glyphicon-baby-formula:before{content:"\e216"}.glyphicon-tent:before{content:"\26fa"}.glyphicon-blackboard:before{content:"\e218"}.glyphicon-bed:before{content:"\e219"}.glyphicon-apple:before{content:"\f8ff"}.glyphicon-erase:before{content:"\e221"}.glyphicon-hourglass:before{content:"\231b"}.glyphicon-lamp:before{content:"\e223"}.glyphicon-duplicate:before{content:"\e224"}.glyphicon-piggy-bank:before{content:"\e225"}.glyphicon-scissors:before{content:"\e226"}.glyphicon-bitcoin:before{content:"\e227"}.glyphicon-btc:before{content:"\e227"}.glyphicon-xbt:before{content:"\e227"}.glyphicon-yen:before{content:"\00a5"}.glyphicon-jpy:before{content:"\00a5"}.glyphicon-ruble:before{content:"\20bd"}.glyphicon-rub:before{content:"\20bd"}.glyphicon-scale:before{content:"\e230"}.glyphicon-ice-lolly:before{content:"\e231"}.glyphicon-ice-lolly-tasted:before{content:"\e232"}.glyphicon-education:before{content:"\e233"}.glyphicon-option-horizontal:before{content:"\e234"}.glyphicon-option-vertical:before{content:"\e235"}.glyphicon-menu-hamburger:before{content:"\e236"}.glyphicon-modal-window:before{content:"\e237"}.glyphicon-oil:before{content:"\e238"}.glyphicon-grain:before{content:"\e239"}.glyphicon-sunglasses:before{content:"\e240"}.glyphicon-text-size:before{content:"\e241"}.glyphicon-text-color:before{content:"\e242"}.glyphicon-text-background:before{content:"\e243"}.glyphicon-object-align-top:before{content:"\e244"}.glyphicon-object-align-bottom:before{content:"\e245"}.glyphicon-object-align-horizontal:before{content:"\e246"}.glyphicon-object-align-left:before{content:"\e247"}.glyphicon-object-align-vertical:before{content:"\e248"}.glyphicon-object-align-right:before{content:"\e249"}.glyphicon-triangle-right:before{content:"\e250"}.glyphicon-triangle-left:before{content:"\e251"}.glyphicon-triangle-bottom:before{content:"\e252"}.glyphicon-triangle-top:before{content:"\e253"}.glyphicon-console:before{content:"\e254"}.glyphicon-superscript:before{content:"\e255"}.glyphicon-subscript:before{content:"\e256"}.glyphicon-menu-left:before{content:"\e257"}.glyphicon-menu-right:before{content:"\e258"}.glyphicon-menu-down:before{content:"\e259"}.glyphicon-menu-up:before{content:"\e260"}*{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}:after,:before{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}html{font-size:10px;-webkit-tap-highlight-color:rgba(0,0,0,0)}body{font-family:"Helvetica Neue",Helvetica,Arial,sans-serif;font-size:14px;line-height:1.42857143;color:#333;background-color:#fff}button,input,select,textarea{font-family:inherit;font-size:inherit;line-height:inherit}a{color:#337ab7;text-decoration:none}a:focus,a:hover{color:#23527c;text-decoration:underline}a:focus{outline:thin dotted;outline:5px auto -webkit-focus-ring-color;outline-offset:-2px}figure{margin:0}img{vertical-align:middle}.carousel-inner>.item>a>img,.carousel-inner>.item>img,.img-responsive,.thumbnail a>img,.thumbnail>img{display:block;max-width:100%;height:auto}.img-rounded{border-radius:6px}.img-thumbnail{display:inline-block;max-width:100%;height:auto;padding:4px;line-height:1.42857143;background-color:#fff;border:1px solid #ddd;border-radius:4px;-webkit-transition:all .2s ease-in-out;-o-transition:all .2s ease-in-out;transition:all .2s ease-in-out}.img-circle{border-radius:50%}hr{margin-top:20px;margin-bottom:20px;border:0;border-top:1px solid #eee}.sr-only{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0,0,0,0);border:0}.sr-only-focusable:active,.sr-only-focusable:focus{position:static;width:auto;height:auto;margin:0;overflow:visible;clip:auto}[role=button]{cursor:pointer}.h1,.h2,.h3,.h4,.h5,.h6,h1,h2,h3,h4,h5,h6{font-family:inherit;font-weight:500;line-height:1.1;color:inherit}.h1 .small,.h1 small,.h2 .small,.h2 small,.h3 .small,.h3 small,.h4 .small,.h4 small,.h5 .small,.h5 small,.h6 .small,.h6 small,h1 .small,h1 small,h2 .small,h2 small,h3 .small,h3 small,h4 .small,h4 small,h5 .small,h5 small,h6 .small,h6 small{font-weight:400;line-height:1;color:#777}.h1,.h2,.h3,h1,h2,h3{margin-top:20px;margin-bottom:10px}.h1 .small,.h1 small,.h2 .small,.h2 small,.h3 .small,.h3 small,h1 .small,h1 small,h2 .small,h2 small,h3 .small,h3 small{font-size:65%}.h4,.h5,.h6,h4,h5,h6{margin-top:10px;margin-bottom:10px}.h4 .small,.h4 small,.h5 .small,.h5 small,.h6 .small,.h6 small,h4 .small,h4 small,h5 .small,h5 small,h6 .small,h6 small{font-size:75%}.h1,h1{font-size:36px}.h2,h2{font-size:30px}.h3,h3{font-size:24px}.h4,h4{font-size:18px}.h5,h5{font-size:14px}.h6,h6{font-size:12px}p{margin:0 0 10px}.lead{margin-bottom:20px;font-size:16px;font-weight:300;line-height:1.4}@media (min-width:768px){.lead{font-size:21px}}.small,small{font-size:85%}.mark,mark{padding:.2em;background-color:#fcf8e3}.text-left{text-align:left}.text-right{text-align:right}.text-center{text-align:center}.text-justify{text-align:justify}.text-nowrap{white-space:nowrap}.text-lowercase{text-transform:lowercase}.text-uppercase{text-transform:uppercase}.text-capitalize{text-transform:capitalize}.text-muted{color:#777}.text-primary{color:#337ab7}a.text-primary:focus,a.text-primary:hover{color:#286090}.text-success{color:#3c763d}a.text-success:focus,a.text-success:hover{color:#2b542c}.text-info{color:#31708f}a.text-info:focus,a.text-info:hover{color:#245269}.text-warning{color:#8a6d3b}a.text-warning:focus,a.text-warning:hover{color:#66512c}.text-danger{color:#a94442}a.text-danger:focus,a.text-danger:hover{color:#843534}.bg-primary{color:#fff;background-color:#337ab7}a.bg-primary:focus,a.bg-primary:hover{background-color:#286090}.bg-success{background-color:#dff0d8}a.bg-success:focus,a.bg-success:hover{background-color:#c1e2b3}.bg-info{background-color:#d9edf7}a.bg-info:focus,a.bg-info:hover{background-color:#afd9ee}.bg-warning{background-color:#fcf8e3}a.bg-warning:focus,a.bg-warning:hover{background-color:#f7ecb5}.bg-danger{background-color:#f2dede}a.bg-danger:focus,a.bg-danger:hover{background-color:#e4b9b9}.page-header{padding-bottom:9px;margin:40px 0 20px;border-bottom:1px solid #eee}ol,ul{margin-top:0;margin-bottom:10px}ol ol,ol ul,ul ol,ul ul{margin-bottom:0}.list-unstyled{padding-left:0;list-style:none}.list-inline{padding-left:0;margin-left:-5px;list-style:none}.list-inline>li{display:inline-block;padding-right:5px;padding-left:5px}dl{margin-top:0;margin-bottom:20px}dd,dt{line-height:1.42857143}dt{font-weight:700}dd{margin-left:0}@media (min-width:768px){.dl-horizontal dt{float:left;width:160px;overflow:hidden;clear:left;text-align:right;text-overflow:ellipsis;white-space:nowrap}.dl-horizontal dd{margin-left:180px}}abbr[data-original-title],abbr[title]{cursor:help;border-bottom:1px dotted #777}.initialism{font-size:90%;text-transform:uppercase}blockquote{padding:10px 20px;margin:0 0 20px;font-size:17.5px;border-left:5px solid #eee}blockquote ol:last-child,blockquote p:last-child,blockquote ul:last-child{margin-bottom:0}blockquote .small,blockquote footer,blockquote small{display:block;font-size:80%;line-height:1.42857143;color:#777}blockquote .small:before,blockquote footer:before,blockquote small:before{content:'\2014 \00A0'}.blockquote-reverse,blockquote.pull-right{padding-right:15px;padding-left:0;text-align:right;border-right:5px solid #eee;border-left:0}.blockquote-reverse .small:before,.blockquote-reverse footer:before,.blockquote-reverse small:before,blockquote.pull-right .small:before,blockquote.pull-right footer:before,blockquote.pull-right small:before{content:''}.blockquote-reverse .small:after,.blockquote-reverse footer:after,.blockquote-reverse small:after,blockquote.pull-right .small:after,blockquote.pull-right footer:after,blockquote.pull-right small:after{content:'\00A0 \2014'}address{margin-bottom:20px;font-style:normal;line-height:1.42857143}code,kbd,pre,samp{font-family:Menlo,Monaco,Consolas,"Courier New",monospace}code{padding:2px 4px;font-size:90%;color:#c7254e;background-color:#f9f2f4;border-radius:4px}kbd{padding:2px 4px;font-size:90%;color:#fff;background-color:#333;border-radius:3px;-webkit-box-shadow:inset 0 -1px 0 rgba(0,0,0,.25);box-shadow:inset 0 -1px 0 rgba(0,0,0,.25)}kbd kbd{padding:0;font-size:100%;font-weight:700;-webkit-box-shadow:none;box-shadow:none}pre{display:block;padding:9.5px;margin:0 0 10px;font-size:13px;line-height:1.42857143;color:#333;word-break:break-all;word-wrap:break-word;background-color:#f5f5f5;border:1px solid #ccc;border-radius:4px}pre code{padding:0;font-size:inherit;color:inherit;white-space:pre-wrap;background-color:transparent;border-radius:0}.pre-scrollable{max-height:340px;overflow-y:scroll}.container{padding-right:15px;padding-left:15px;margin-right:auto;margin-left:auto}@media (min-width:768px){.container{width:750px}}@media (min-width:992px){.container{width:970px}}@media (min-width:1200px){.container{width:1170px}}.container-fluid{padding-right:15px;padding-left:15px;margin-right:auto;margin-left:auto}.row{margin-right:-15px;margin-left:-15px}.col-lg-1,.col-lg-10,.col-lg-11,.col-lg-12,.col-lg-2,.col-lg-3,.col-lg-4,.col-lg-5,.col-lg-6,.col-lg-7,.col-lg-8,.col-lg-9,.col-md-1,.col-md-10,.col-md-11,.col-md-12,.col-md-2,.col-md-3,.col-md-4,.col-md-5,.col-md-6,.col-md-7,.col-md-8,.col-md-9,.col-sm-1,.col-sm-10,.col-sm-11,.col-sm-12,.col-sm-2,.col-sm-3,.col-sm-4,.col-sm-5,.col-sm-6,.col-sm-7,.col-sm-8,.col-sm-9,.col-xs-1,.col-xs-10,.col-xs-11,.col-xs-12,.col-xs-2,.col-xs-3,.col-xs-4,.col-xs-5,.col-xs-6,.col-xs-7,.col-xs-8,.col-xs-9{position:relative;min-height:1px;padding-right:15px;padding-left:15px}.col-xs-1,.col-xs-10,.col-xs-11,.col-xs-12,.col-xs-2,.col-xs-3,.col-xs-4,.col-xs-5,.col-xs-6,.col-xs-7,.col-xs-8,.col-xs-9{float:left}.col-xs-12{width:100%}.col-xs-11{width:91.66666667%}.col-xs-10{width:83.33333333%}.col-xs-9{width:75%}.col-xs-8{width:66.66666667%}.col-xs-7{width:58.33333333%}.col-xs-6{width:50%}.col-xs-5{width:41.66666667%}.col-xs-4{width:33.33333333%}.col-xs-3{width:25%}.col-xs-2{width:16.66666667%}.col-xs-1{width:8.33333333%}.col-xs-pull-12{right:100%}.col-xs-pull-11{right:91.66666667%}.col-xs-pull-10{right:83.33333333%}.col-xs-pull-9{right:75%}.col-xs-pull-8{right:66.66666667%}.col-xs-pull-7{right:58.33333333%}.col-xs-pull-6{right:50%}.col-xs-pull-5{right:41.66666667%}.col-xs-pull-4{right:33.33333333%}.col-xs-pull-3{right:25%}.col-xs-pull-2{right:16.66666667%}.col-xs-pull-1{right:8.33333333%}.col-xs-pull-0{right:auto}.col-xs-push-12{left:100%}.col-xs-push-11{left:91.66666667%}.col-xs-push-10{left:83.33333333%}.col-xs-push-9{left:75%}.col-xs-push-8{left:66.66666667%}.col-xs-push-7{left:58.33333333%}.col-xs-push-6{left:50%}.col-xs-push-5{left:41.66666667%}.col-xs-push-4{left:33.33333333%}.col-xs-push-3{left:25%}.col-xs-push-2{left:16.66666667%}.col-xs-push-1{left:8.33333333%}.col-xs-push-0{left:auto}.col-xs-offset-12{margin-left:100%}.col-xs-offset-11{margin-left:91.66666667%}.col-xs-offset-10{margin-left:83.33333333%}.col-xs-offset-9{margin-left:75%}.col-xs-offset-8{margin-left:66.66666667%}.col-xs-offset-7{margin-left:58.33333333%}.col-xs-offset-6{margin-left:50%}.col-xs-offset-5{margin-left:41.66666667%}.col-xs-offset-4{margin-left:33.33333333%}.col-xs-offset-3{margin-left:25%}.col-xs-offset-2{margin-left:16.66666667%}.col-xs-offset-1{margin-left:8.33333333%}.col-xs-offset-0{margin-left:0}@media (min-width:768px){.col-sm-1,.col-sm-10,.col-sm-11,.col-sm-12,.col-sm-2,.col-sm-3,.col-sm-4,.col-sm-5,.col-sm-6,.col-sm-7,.col-sm-8,.col-sm-9{float:left}.col-sm-12{width:100%}.col-sm-11{width:91.66666667%}.col-sm-10{width:83.33333333%}.col-sm-9{width:75%}.col-sm-8{width:66.66666667%}.col-sm-7{width:58.33333333%}.col-sm-6{width:50%}.col-sm-5{width:41.66666667%}.col-sm-4{width:33.33333333%}.col-sm-3{width:25%}.col-sm-2{width:16.66666667%}.col-sm-1{width:8.33333333%}.col-sm-pull-12{right:100%}.col-sm-pull-11{right:91.66666667%}.col-sm-pull-10{right:83.33333333%}.col-sm-pull-9{right:75%}.col-sm-pull-8{right:66.66666667%}.col-sm-pull-7{right:58.33333333%}.col-sm-pull-6{right:50%}.col-sm-pull-5{right:41.66666667%}.col-sm-pull-4{right:33.33333333%}.col-sm-pull-3{right:25%}.col-sm-pull-2{right:16.66666667%}.col-sm-pull-1{right:8.33333333%}.col-sm-pull-0{right:auto}.col-sm-push-12{left:100%}.col-sm-push-11{left:91.66666667%}.col-sm-push-10{left:83.33333333%}.col-sm-push-9{left:75%}.col-sm-push-8{left:66.66666667%}.col-sm-push-7{left:58.33333333%}.col-sm-push-6{left:50%}.col-sm-push-5{left:41.66666667%}.col-sm-push-4{left:33.33333333%}.col-sm-push-3{left:25%}.col-sm-push-2{left:16.66666667%}.col-sm-push-1{left:8.33333333%}.col-sm-push-0{left:auto}.col-sm-offset-12{margin-left:100%}.col-sm-offset-11{margin-left:91.66666667%}.col-sm-offset-10{margin-left:83.33333333%}.col-sm-offset-9{margin-left:75%}.col-sm-offset-8{margin-left:66.66666667%}.col-sm-offset-7{margin-left:58.33333333%}.col-sm-offset-6{margin-left:50%}.col-sm-offset-5{margin-left:41.66666667%}.col-sm-offset-4{margin-left:33.33333333%}.col-sm-offset-3{margin-left:25%}.col-sm-offset-2{margin-left:16.66666667%}.col-sm-offset-1{margin-left:8.33333333%}.col-sm-offset-0{margin-left:0}}@media (min-width:992px){.col-md-1,.col-md-10,.col-md-11,.col-md-12,.col-md-2,.col-md-3,.col-md-4,.col-md-5,.col-md-6,.col-md-7,.col-md-8,.col-md-9{float:left}.col-md-12{width:100%}.col-md-11{width:91.66666667%}.col-md-10{width:83.33333333%}.col-md-9{width:75%}.col-md-8{width:66.66666667%}.col-md-7{width:58.33333333%}.col-md-6{width:50%}.col-md-5{width:41.66666667%}.col-md-4{width:33.33333333%}.col-md-3{width:25%}.col-md-2{width:16.66666667%}.col-md-1{width:8.33333333%}.col-md-pull-12{right:100%}.col-md-pull-11{right:91.66666667%}.col-md-pull-10{right:83.33333333%}.col-md-pull-9{right:75%}.col-md-pull-8{right:66.66666667%}.col-md-pull-7{right:58.33333333%}.col-md-pull-6{right:50%}.col-md-pull-5{right:41.66666667%}.col-md-pull-4{right:33.33333333%}.col-md-pull-3{right:25%}.col-md-pull-2{right:16.66666667%}.col-md-pull-1{right:8.33333333%}.col-md-pull-0{right:auto}.col-md-push-12{left:100%}.col-md-push-11{left:91.66666667%}.col-md-push-10{left:83.33333333%}.col-md-push-9{left:75%}.col-md-push-8{left:66.66666667%}.col-md-push-7{left:58.33333333%}.col-md-push-6{left:50%}.col-md-push-5{left:41.66666667%}.col-md-push-4{left:33.33333333%}.col-md-push-3{left:25%}.col-md-push-2{left:16.66666667%}.col-md-push-1{left:8.33333333%}.col-md-push-0{left:auto}.col-md-offset-12{margin-left:100%}.col-md-offset-11{margin-left:91.66666667%}.col-md-offset-10{margin-left:83.33333333%}.col-md-offset-9{margin-left:75%}.col-md-offset-8{margin-left:66.66666667%}.col-md-offset-7{margin-left:58.33333333%}.col-md-offset-6{margin-left:50%}.col-md-offset-5{margin-left:41.66666667%}.col-md-offset-4{margin-left:33.33333333%}.col-md-offset-3{margin-left:25%}.col-md-offset-2{margin-left:16.66666667%}.col-md-offset-1{margin-left:8.33333333%}.col-md-offset-0{margin-left:0}}@media (min-width:1200px){.col-lg-1,.col-lg-10,.col-lg-11,.col-lg-12,.col-lg-2,.col-lg-3,.col-lg-4,.col-lg-5,.col-lg-6,.col-lg-7,.col-lg-8,.col-lg-9{float:left}.col-lg-12{width:100%}.col-lg-11{width:91.66666667%}.col-lg-10{width:83.33333333%}.col-lg-9{width:75%}.col-lg-8{width:66.66666667%}.col-lg-7{width:58.33333333%}.col-lg-6{width:50%}.col-lg-5{width:41.66666667%}.col-lg-4{width:33.33333333%}.col-lg-3{width:25%}.col-lg-2{width:16.66666667%}.col-lg-1{width:8.33333333%}.col-lg-pull-12{right:100%}.col-lg-pull-11{right:91.66666667%}.col-lg-pull-10{right:83.33333333%}.col-lg-pull-9{right:75%}.col-lg-pull-8{right:66.66666667%}.col-lg-pull-7{right:58.33333333%}.col-lg-pull-6{right:50%}.col-lg-pull-5{right:41.66666667%}.col-lg-pull-4{right:33.33333333%}.col-lg-pull-3{right:25%}.col-lg-pull-2{right:16.66666667%}.col-lg-pull-1{right:8.33333333%}.col-lg-pull-0{right:auto}.col-lg-push-12{left:100%}.col-lg-push-11{left:91.66666667%}.col-lg-push-10{left:83.33333333%}.col-lg-push-9{left:75%}.col-lg-push-8{left:66.66666667%}.col-lg-push-7{left:58.33333333%}.col-lg-push-6{left:50%}.col-lg-push-5{left:41.66666667%}.col-lg-push-4{left:33.33333333%}.col-lg-push-3{left:25%}.col-lg-push-2{left:16.66666667%}.col-lg-push-1{left:8.33333333%}.col-lg-push-0{left:auto}.col-lg-offset-12{margin-left:100%}.col-lg-offset-11{margin-left:91.66666667%}.col-lg-offset-10{margin-left:83.33333333%}.col-lg-offset-9{margin-left:75%}.col-lg-offset-8{margin-left:66.66666667%}.col-lg-offset-7{margin-left:58.33333333%}.col-lg-offset-6{margin-left:50%}.col-lg-offset-5{margin-left:41.66666667%}.col-lg-offset-4{margin-left:33.33333333%}.col-lg-offset-3{margin-left:25%}.col-lg-offset-2{margin-left:16.66666667%}.col-lg-offset-1{margin-left:8.33333333%}.col-lg-offset-0{margin-left:0}}table{background-color:transparent}caption{padding-top:8px;padding-bottom:8px;color:#777;text-align:left}th{text-align:left}.table{width:100%;max-width:100%;margin-bottom:20px}.table>tbody>tr>td,.table>tbody>tr>th,.table>tfoot>tr>td,.table>tfoot>tr>th,.table>thead>tr>td,.table>thead>tr>th{padding:8px;line-height:1.42857143;vertical-align:top;border-top:1px solid #ddd}.table>thead>tr>th{vertical-align:bottom;border-bottom:2px solid #ddd}.table>caption+thead>tr:first-child>td,.table>caption+thead>tr:first-child>th,.table>colgroup+thead>tr:first-child>td,.table>colgroup+thead>tr:first-child>th,.table>thead:first-child>tr:first-child>td,.table>thead:first-child>tr:first-child>th{border-top:0}.table>tbody+tbody{border-top:2px solid #ddd}.table .table{background-color:#fff}.table-condensed>tbody>tr>td,.table-condensed>tbody>tr>th,.table-condensed>tfoot>tr>td,.table-condensed>tfoot>tr>th,.table-condensed>thead>tr>td,.table-condensed>thead>tr>th{padding:5px}.table-bordered{border:1px solid #ddd}.table-bordered>tbody>tr>td,.table-bordered>tbody>tr>th,.table-bordered>tfoot>tr>td,.table-bordered>tfoot>tr>th,.table-bordered>thead>tr>td,.table-bordered>thead>tr>th{border:1px solid #ddd}.table-bordered>thead>tr>td,.table-bordered>thead>tr>th{border-bottom-width:2px}.table-striped>tbody>tr:nth-of-type(odd){background-color:#f9f9f9}.table-hover>tbody>tr:hover{background-color:#f5f5f5}table col[class*=col-]{position:static;display:table-column;float:none}table td[class*=col-],table th[class*=col-]{position:static;display:table-cell;float:none}.table>tbody>tr.active>td,.table>tbody>tr.active>th,.table>tbody>tr>td.active,.table>tbody>tr>th.active,.table>tfoot>tr.active>td,.table>tfoot>tr.active>th,.table>tfoot>tr>td.active,.table>tfoot>tr>th.active,.table>thead>tr.active>td,.table>thead>tr.active>th,.table>thead>tr>td.active,.table>thead>tr>th.active{background-color:#f5f5f5}.table-hover>tbody>tr.active:hover>td,.table-hover>tbody>tr.active:hover>th,.table-hover>tbody>tr:hover>.active,.table-hover>tbody>tr>td.active:hover,.table-hover>tbody>tr>th.active:hover{background-color:#e8e8e8}.table>tbody>tr.success>td,.table>tbody>tr.success>th,.table>tbody>tr>td.success,.table>tbody>tr>th.success,.table>tfoot>tr.success>td,.table>tfoot>tr.success>th,.table>tfoot>tr>td.success,.table>tfoot>tr>th.success,.table>thead>tr.success>td,.table>thead>tr.success>th,.table>thead>tr>td.success,.table>thead>tr>th.success{background-color:#dff0d8}.table-hover>tbody>tr.success:hover>td,.table-hover>tbody>tr.success:hover>th,.table-hover>tbody>tr:hover>.success,.table-hover>tbody>tr>td.success:hover,.table-hover>tbody>tr>th.success:hover{background-color:#d0e9c6}.table>tbody>tr.info>td,.table>tbody>tr.info>th,.table>tbody>tr>td.info,.table>tbody>tr>th.info,.table>tfoot>tr.info>td,.table>tfoot>tr.info>th,.table>tfoot>tr>td.info,.table>tfoot>tr>th.info,.table>thead>tr.info>td,.table>thead>tr.info>th,.table>thead>tr>td.info,.table>thead>tr>th.info{background-color:#d9edf7}.table-hover>tbody>tr.info:hover>td,.table-hover>tbody>tr.info:hover>th,.table-hover>tbody>tr:hover>.info,.table-hover>tbody>tr>td.info:hover,.table-hover>tbody>tr>th.info:hover{background-color:#c4e3f3}.table>tbody>tr.warning>td,.table>tbody>tr.warning>th,.table>tbody>tr>td.warning,.table>tbody>tr>th.warning,.table>tfoot>tr.warning>td,.table>tfoot>tr.warning>th,.table>tfoot>tr>td.warning,.table>tfoot>tr>th.warning,.table>thead>tr.warning>td,.table>thead>tr.warning>th,.table>thead>tr>td.warning,.table>thead>tr>th.warning{background-color:#fcf8e3}.table-hover>tbody>tr.warning:hover>td,.table-hover>tbody>tr.warning:hover>th,.table-hover>tbody>tr:hover>.warning,.table-hover>tbody>tr>td.warning:hover,.table-hover>tbody>tr>th.warning:hover{background-color:#faf2cc}.table>tbody>tr.danger>td,.table>tbody>tr.danger>th,.table>tbody>tr>td.danger,.table>tbody>tr>th.danger,.table>tfoot>tr.danger>td,.table>tfoot>tr.danger>th,.table>tfoot>tr>td.danger,.table>tfoot>tr>th.danger,.table>thead>tr.danger>td,.table>thead>tr.danger>th,.table>thead>tr>td.danger,.table>thead>tr>th.danger{background-color:#f2dede}.table-hover>tbody>tr.danger:hover>td,.table-hover>tbody>tr.danger:hover>th,.table-hover>tbody>tr:hover>.danger,.table-hover>tbody>tr>td.danger:hover,.table-hover>tbody>tr>th.danger:hover{background-color:#ebcccc}.table-responsive{min-height:.01%;overflow-x:auto}@media screen and (max-width:767px){.table-responsive{width:100%;margin-bottom:15px;overflow-y:hidden;-ms-overflow-style:-ms-autohiding-scrollbar;border:1px solid #ddd}.table-responsive>.table{margin-bottom:0}.table-responsive>.table>tbody>tr>td,.table-responsive>.table>tbody>tr>th,.table-responsive>.table>tfoot>tr>td,.table-responsive>.table>tfoot>tr>th,.table-responsive>.table>thead>tr>td,.table-responsive>.table>thead>tr>th{white-space:nowrap}.table-responsive>.table-bordered{border:0}.table-responsive>.table-bordered>tbody>tr>td:first-child,.table-responsive>.table-bordered>tbody>tr>th:first-child,.table-responsive>.table-bordered>tfoot>tr>td:first-child,.table-responsive>.table-bordered>tfoot>tr>th:first-child,.table-responsive>.table-bordered>thead>tr>td:first-child,.table-responsive>.table-bordered>thead>tr>th:first-child{border-left:0}.table-responsive>.table-bordered>tbody>tr>td:last-child,.table-responsive>.table-bordered>tbody>tr>th:last-child,.table-responsive>.table-bordered>tfoot>tr>td:last-child,.table-responsive>.table-bordered>tfoot>tr>th:last-child,.table-responsive>.table-bordered>thead>tr>td:last-child,.table-responsive>.table-bordered>thead>tr>th:last-child{border-right:0}.table-responsive>.table-bordered>tbody>tr:last-child>td,.table-responsive>.table-bordered>tbody>tr:last-child>th,.table-responsive>.table-bordered>tfoot>tr:last-child>td,.table-responsive>.table-bordered>tfoot>tr:last-child>th{border-bottom:0}}fieldset{min-width:0;padding:0;margin:0;border:0}legend{display:block;width:100%;padding:0;margin-bottom:20px;font-size:21px;line-height:inherit;color:#333;border:0;border-bottom:1px solid #e5e5e5}label{display:inline-block;max-width:100%;margin-bottom:5px;font-weight:700}input[type=search]{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}input[type=checkbox],input[type=radio]{margin:4px 0 0;margin-top:1px\9;line-height:normal}input[type=file]{display:block}input[type=range]{display:block;width:100%}select[multiple],select[size]{height:auto}input[type=file]:focus,input[type=checkbox]:focus,input[type=radio]:focus{outline:thin dotted;outline:5px auto -webkit-focus-ring-color;outline-offset:-2px}output{display:block;padding-top:7px;font-size:14px;line-height:1.42857143;color:#555}.form-control{display:block;width:100%;height:34px;padding:6px 12px;font-size:14px;line-height:1.42857143;color:#555;background-color:#fff;background-image:none;border:1px solid #ccc;border-radius:4px;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.075);box-shadow:inset 0 1px 1px rgba(0,0,0,.075);-webkit-transition:border-color ease-in-out .15s,-webkit-box-shadow ease-in-out .15s;-o-transition:border-color ease-in-out .15s,box-shadow ease-in-out .15s;transition:border-color ease-in-out .15s,box-shadow ease-in-out .15s}.form-control:focus{border-color:#66afe9;outline:0;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 8px rgba(102,175,233,.6);box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 8px rgba(102,175,233,.6)}.form-control::-moz-placeholder{color:#999;opacity:1}.form-control:-ms-input-placeholder{color:#999}.form-control::-webkit-input-placeholder{color:#999}.form-control[disabled],.form-control[readonly],fieldset[disabled] .form-control{background-color:#eee;opacity:1}.form-control[disabled],fieldset[disabled] .form-control{cursor:not-allowed}textarea.form-control{height:auto}input[type=search]{-webkit-appearance:none}@media screen and (-webkit-min-device-pixel-ratio:0){input[type=date].form-control,input[type=time].form-control,input[type=datetime-local].form-control,input[type=month].form-control{line-height:34px}.input-group-sm input[type=date],.input-group-sm input[type=time],.input-group-sm input[type=datetime-local],.input-group-sm input[type=month],input[type=date].input-sm,input[type=time].input-sm,input[type=datetime-local].input-sm,input[type=month].input-sm{line-height:30px}.input-group-lg input[type=date],.input-group-lg input[type=time],.input-group-lg input[type=datetime-local],.input-group-lg input[type=month],input[type=date].input-lg,input[type=time].input-lg,input[type=datetime-local].input-lg,input[type=month].input-lg{line-height:46px}}.form-group{margin-bottom:15px}.checkbox,.radio{position:relative;display:block;margin-top:10px;margin-bottom:10px}.checkbox label,.radio label{min-height:20px;padding-left:20px;margin-bottom:0;font-weight:400;cursor:pointer}.checkbox input[type=checkbox],.checkbox-inline input[type=checkbox],.radio input[type=radio],.radio-inline input[type=radio]{position:absolute;margin-top:4px\9;margin-left:-20px}.checkbox+.checkbox,.radio+.radio{margin-top:-5px}.checkbox-inline,.radio-inline{position:relative;display:inline-block;padding-left:20px;margin-bottom:0;font-weight:400;vertical-align:middle;cursor:pointer}.checkbox-inline+.checkbox-inline,.radio-inline+.radio-inline{margin-top:0;margin-left:10px}fieldset[disabled] input[type=checkbox],fieldset[disabled] input[type=radio],input[type=checkbox].disabled,input[type=checkbox][disabled],input[type=radio].disabled,input[type=radio][disabled]{cursor:not-allowed}.checkbox-inline.disabled,.radio-inline.disabled,fieldset[disabled] .checkbox-inline,fieldset[disabled] .radio-inline{cursor:not-allowed}.checkbox.disabled label,.radio.disabled label,fieldset[disabled] .checkbox label,fieldset[disabled] .radio label{cursor:not-allowed}.form-control-static{min-height:34px;padding-top:7px;padding-bottom:7px;margin-bottom:0}.form-control-static.input-lg,.form-control-static.input-sm{padding-right:0;padding-left:0}.input-sm{height:30px;padding:5px 10px;font-size:12px;line-height:1.5;border-radius:3px}select.input-sm{height:30px;line-height:30px}select[multiple].input-sm,textarea.input-sm{height:auto}.form-group-sm .form-control{height:30px;padding:5px 10px;font-size:12px;line-height:1.5;border-radius:3px}.form-group-sm select.form-control{height:30px;line-height:30px}.form-group-sm select[multiple].form-control,.form-group-sm textarea.form-control{height:auto}.form-group-sm .form-control-static{height:30px;min-height:32px;padding:6px 10px;font-size:12px;line-height:1.5}.input-lg{height:46px;padding:10px 16px;font-size:18px;line-height:1.3333333;border-radius:6px}select.input-lg{height:46px;line-height:46px}select[multiple].input-lg,textarea.input-lg{height:auto}.form-group-lg .form-control{height:46px;padding:10px 16px;font-size:18px;line-height:1.3333333;border-radius:6px}.form-group-lg select.form-control{height:46px;line-height:46px}.form-group-lg select[multiple].form-control,.form-group-lg textarea.form-control{height:auto}.form-group-lg .form-control-static{height:46px;min-height:38px;padding:11px 16px;font-size:18px;line-height:1.3333333}.has-feedback{position:relative}.has-feedback .form-control{padding-right:42.5px}.form-control-feedback{position:absolute;top:0;right:0;z-index:2;display:block;width:34px;height:34px;line-height:34px;text-align:center;pointer-events:none}.form-group-lg .form-control+.form-control-feedback,.input-group-lg+.form-control-feedback,.input-lg+.form-control-feedback{width:46px;height:46px;line-height:46px}.form-group-sm .form-control+.form-control-feedback,.input-group-sm+.form-control-feedback,.input-sm+.form-control-feedback{width:30px;height:30px;line-height:30px}.has-success .checkbox,.has-success .checkbox-inline,.has-success .control-label,.has-success .help-block,.has-success .radio,.has-success .radio-inline,.has-success.checkbox label,.has-success.checkbox-inline label,.has-success.radio label,.has-success.radio-inline label{color:#3c763d}.has-success .form-control{border-color:#3c763d;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.075);box-shadow:inset 0 1px 1px rgba(0,0,0,.075)}.has-success .form-control:focus{border-color:#2b542c;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 6px #67b168;box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 6px #67b168}.has-success .input-group-addon{color:#3c763d;background-color:#dff0d8;border-color:#3c763d}.has-success .form-control-feedback{color:#3c763d}.has-warning .checkbox,.has-warning .checkbox-inline,.has-warning .control-label,.has-warning .help-block,.has-warning .radio,.has-warning .radio-inline,.has-warning.checkbox label,.has-warning.checkbox-inline label,.has-warning.radio label,.has-warning.radio-inline label{color:#8a6d3b}.has-warning .form-control{border-color:#8a6d3b;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.075);box-shadow:inset 0 1px 1px rgba(0,0,0,.075)}.has-warning .form-control:focus{border-color:#66512c;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 6px #c0a16b;box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 6px #c0a16b}.has-warning .input-group-addon{color:#8a6d3b;background-color:#fcf8e3;border-color:#8a6d3b}.has-warning .form-control-feedback{color:#8a6d3b}.has-error .checkbox,.has-error .checkbox-inline,.has-error .control-label,.has-error .help-block,.has-error .radio,.has-error .radio-inline,.has-error.checkbox label,.has-error.checkbox-inline label,.has-error.radio label,.has-error.radio-inline label{color:#a94442}.has-error .form-control{border-color:#a94442;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.075);box-shadow:inset 0 1px 1px rgba(0,0,0,.075)}.has-error .form-control:focus{border-color:#843534;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 6px #ce8483;box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 6px #ce8483}.has-error .input-group-addon{color:#a94442;background-color:#f2dede;border-color:#a94442}.has-error .form-control-feedback{color:#a94442}.has-feedback label~.form-control-feedback{top:25px}.has-feedback label.sr-only~.form-control-feedback{top:0}.help-block{display:block;margin-top:5px;margin-bottom:10px;color:#737373}@media (min-width:768px){.form-inline .form-group{display:inline-block;margin-bottom:0;vertical-align:middle}.form-inline .form-control{display:inline-block;width:auto;vertical-align:middle}.form-inline .form-control-static{display:inline-block}.form-inline .input-group{display:inline-table;vertical-align:middle}.form-inline .input-group .form-control,.form-inline .input-group .input-group-addon,.form-inline .input-group .input-group-btn{width:auto}.form-inline .input-group>.form-control{width:100%}.form-inline .control-label{margin-bottom:0;vertical-align:middle}.form-inline .checkbox,.form-inline .radio{display:inline-block;margin-top:0;margin-bottom:0;vertical-align:middle}.form-inline .checkbox label,.form-inline .radio label{padding-left:0}.form-inline .checkbox input[type=checkbox],.form-inline .radio input[type=radio]{position:relative;margin-left:0}.form-inline .has-feedback .form-control-feedback{top:0}}.form-horizontal .checkbox,.form-horizontal .checkbox-inline,.form-horizontal .radio,.form-horizontal .radio-inline{padding-top:7px;margin-top:0;margin-bottom:0}.form-horizontal .checkbox,.form-horizontal .radio{min-height:27px}.form-horizontal .form-group{margin-right:-15px;margin-left:-15px}@media (min-width:768px){.form-horizontal .control-label{padding-top:7px;margin-bottom:0;text-align:right}}.form-horizontal .has-feedback .form-control-feedback{right:15px}@media (min-width:768px){.form-horizontal .form-group-lg .control-label{padding-top:14.33px;font-size:18px}}@media (min-width:768px){.form-horizontal .form-group-sm .control-label{padding-top:6px;font-size:12px}}.btn{display:inline-block;padding:6px 12px;margin-bottom:0;font-size:14px;font-weight:400;line-height:1.42857143;text-align:center;white-space:nowrap;vertical-align:middle;-ms-touch-action:manipulation;touch-action:manipulation;cursor:pointer;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;background-image:none;border:1px solid transparent;border-radius:4px}.btn.active.focus,.btn.active:focus,.btn.focus,.btn:active.focus,.btn:active:focus,.btn:focus{outline:thin dotted;outline:5px auto -webkit-focus-ring-color;outline-offset:-2px}.btn.focus,.btn:focus,.btn:hover{color:#333;text-decoration:none}.btn.active,.btn:active{background-image:none;outline:0;-webkit-box-shadow:inset 0 3px 5px rgba(0,0,0,.125);box-shadow:inset 0 3px 5px rgba(0,0,0,.125)}.btn.disabled,.btn[disabled],fieldset[disabled] .btn{cursor:not-allowed;filter:alpha(opacity=65);-webkit-box-shadow:none;box-shadow:none;opacity:.65}a.btn.disabled,fieldset[disabled] a.btn{pointer-events:none}.btn-default{color:#333;background-color:#fff;border-color:#ccc}.btn-default.focus,.btn-default:focus{color:#333;background-color:#e6e6e6;border-color:#8c8c8c}.btn-default:hover{color:#333;background-color:#e6e6e6;border-color:#adadad}.btn-default.active,.btn-default:active,.open>.dropdown-toggle.btn-default{color:#333;background-color:#e6e6e6;border-color:#adadad}.btn-default.active.focus,.btn-default.active:focus,.btn-default.active:hover,.btn-default:active.focus,.btn-default:active:focus,.btn-default:active:hover,.open>.dropdown-toggle.btn-default.focus,.open>.dropdown-toggle.btn-default:focus,.open>.dropdown-toggle.btn-default:hover{color:#333;background-color:#d4d4d4;border-color:#8c8c8c}.btn-default.active,.btn-default:active,.open>.dropdown-toggle.btn-default{background-image:none}.btn-default.disabled,.btn-default.disabled.active,.btn-default.disabled.focus,.btn-default.disabled:active,.btn-default.disabled:focus,.btn-default.disabled:hover,.btn-default[disabled],.btn-default[disabled].active,.btn-default[disabled].focus,.btn-default[disabled]:active,.btn-default[disabled]:focus,.btn-default[disabled]:hover,fieldset[disabled] .btn-default,fieldset[disabled] .btn-default.active,fieldset[disabled] .btn-default.focus,fieldset[disabled] .btn-default:active,fieldset[disabled] .btn-default:focus,fieldset[disabled] .btn-default:hover{background-color:#fff;border-color:#ccc}.btn-default .badge{color:#fff;background-color:#333}.btn-primary{color:#fff;background-color:#337ab7;border-color:#2e6da4}.btn-primary.focus,.btn-primary:focus{color:#fff;background-color:#286090;border-color:#122b40}.btn-primary:hover{color:#fff;background-color:#286090;border-color:#204d74}.btn-primary.active,.btn-primary:active,.open>.dropdown-toggle.btn-primary{color:#fff;background-color:#286090;border-color:#204d74}.btn-primary.active.focus,.btn-primary.active:focus,.btn-primary.active:hover,.btn-primary:active.focus,.btn-primary:active:focus,.btn-primary:active:hover,.open>.dropdown-toggle.btn-primary.focus,.open>.dropdown-toggle.btn-primary:focus,.open>.dropdown-toggle.btn-primary:hover{color:#fff;background-color:#204d74;border-color:#122b40}.btn-primary.active,.btn-primary:active,.open>.dropdown-toggle.btn-primary{background-image:none}.btn-primary.disabled,.btn-primary.disabled.active,.btn-primary.disabled.focus,.btn-primary.disabled:active,.btn-primary.disabled:focus,.btn-primary.disabled:hover,.btn-primary[disabled],.btn-primary[disabled].active,.btn-primary[disabled].focus,.btn-primary[disabled]:active,.btn-primary[disabled]:focus,.btn-primary[disabled]:hover,fieldset[disabled] .btn-primary,fieldset[disabled] .btn-primary.active,fieldset[disabled] .btn-primary.focus,fieldset[disabled] .btn-primary:active,fieldset[disabled] .btn-primary:focus,fieldset[disabled] .btn-primary:hover{background-color:#337ab7;border-color:#2e6da4}.btn-primary .badge{color:#337ab7;background-color:#fff}.btn-success{color:#fff;background-color:#5cb85c;border-color:#4cae4c}.btn-success.focus,.btn-success:focus{color:#fff;background-color:#449d44;border-color:#255625}.btn-success:hover{color:#fff;background-color:#449d44;border-color:#398439}.btn-success.active,.btn-success:active,.open>.dropdown-toggle.btn-success{color:#fff;background-color:#449d44;border-color:#398439}.btn-success.active.focus,.btn-success.active:focus,.btn-success.active:hover,.btn-success:active.focus,.btn-success:active:focus,.btn-success:active:hover,.open>.dropdown-toggle.btn-success.focus,.open>.dropdown-toggle.btn-success:focus,.open>.dropdown-toggle.btn-success:hover{color:#fff;background-color:#398439;border-color:#255625}.btn-success.active,.btn-success:active,.open>.dropdown-toggle.btn-success{background-image:none}.btn-success.disabled,.btn-success.disabled.active,.btn-success.disabled.focus,.btn-success.disabled:active,.btn-success.disabled:focus,.btn-success.disabled:hover,.btn-success[disabled],.btn-success[disabled].active,.btn-success[disabled].focus,.btn-success[disabled]:active,.btn-success[disabled]:focus,.btn-success[disabled]:hover,fieldset[disabled] .btn-success,fieldset[disabled] .btn-success.active,fieldset[disabled] .btn-success.focus,fieldset[disabled] .btn-success:active,fieldset[disabled] .btn-success:focus,fieldset[disabled] .btn-success:hover{background-color:#5cb85c;border-color:#4cae4c}.btn-success .badge{color:#5cb85c;background-color:#fff}.btn-info{color:#fff;background-color:#5bc0de;border-color:#46b8da}.btn-info.focus,.btn-info:focus{color:#fff;background-color:#31b0d5;border-color:#1b6d85}.btn-info:hover{color:#fff;background-color:#31b0d5;border-color:#269abc}.btn-info.active,.btn-info:active,.open>.dropdown-toggle.btn-info{color:#fff;background-color:#31b0d5;border-color:#269abc}.btn-info.active.focus,.btn-info.active:focus,.btn-info.active:hover,.btn-info:active.focus,.btn-info:active:focus,.btn-info:active:hover,.open>.dropdown-toggle.btn-info.focus,.open>.dropdown-toggle.btn-info:focus,.open>.dropdown-toggle.btn-info:hover{color:#fff;background-color:#269abc;border-color:#1b6d85}.btn-info.active,.btn-info:active,.open>.dropdown-toggle.btn-info{background-image:none}.btn-info.disabled,.btn-info.disabled.active,.btn-info.disabled.focus,.btn-info.disabled:active,.btn-info.disabled:focus,.btn-info.disabled:hover,.btn-info[disabled],.btn-info[disabled].active,.btn-info[disabled].focus,.btn-info[disabled]:active,.btn-info[disabled]:focus,.btn-info[disabled]:hover,fieldset[disabled] .btn-info,fieldset[disabled] .btn-info.active,fieldset[disabled] .btn-info.focus,fieldset[disabled] .btn-info:active,fieldset[disabled] .btn-info:focus,fieldset[disabled] .btn-info:hover{background-color:#5bc0de;border-color:#46b8da}.btn-info .badge{color:#5bc0de;background-color:#fff}.btn-warning{color:#fff;background-color:#f0ad4e;border-color:#eea236}.btn-warning.focus,.btn-warning:focus{color:#fff;background-color:#ec971f;border-color:#985f0d}.btn-warning:hover{color:#fff;background-color:#ec971f;border-color:#d58512}.btn-warning.active,.btn-warning:active,.open>.dropdown-toggle.btn-warning{color:#fff;background-color:#ec971f;border-color:#d58512}.btn-warning.active.focus,.btn-warning.active:focus,.btn-warning.active:hover,.btn-warning:active.focus,.btn-warning:active:focus,.btn-warning:active:hover,.open>.dropdown-toggle.btn-warning.focus,.open>.dropdown-toggle.btn-warning:focus,.open>.dropdown-toggle.btn-warning:hover{color:#fff;background-color:#d58512;border-color:#985f0d}.btn-warning.active,.btn-warning:active,.open>.dropdown-toggle.btn-warning{background-image:none}.btn-warning.disabled,.btn-warning.disabled.active,.btn-warning.disabled.focus,.btn-warning.disabled:active,.btn-warning.disabled:focus,.btn-warning.disabled:hover,.btn-warning[disabled],.btn-warning[disabled].active,.btn-warning[disabled].focus,.btn-warning[disabled]:active,.btn-warning[disabled]:focus,.btn-warning[disabled]:hover,fieldset[disabled] .btn-warning,fieldset[disabled] .btn-warning.active,fieldset[disabled] .btn-warning.focus,fieldset[disabled] .btn-warning:active,fieldset[disabled] .btn-warning:focus,fieldset[disabled] .btn-warning:hover{background-color:#f0ad4e;border-color:#eea236}.btn-warning .badge{color:#f0ad4e;background-color:#fff}.btn-danger{color:#fff;background-color:#d9534f;border-color:#d43f3a}.btn-danger.focus,.btn-danger:focus{color:#fff;background-color:#c9302c;border-color:#761c19}.btn-danger:hover{color:#fff;background-color:#c9302c;border-color:#ac2925}.btn-danger.active,.btn-danger:active,.open>.dropdown-toggle.btn-danger{color:#fff;background-color:#c9302c;border-color:#ac2925}.btn-danger.active.focus,.btn-danger.active:focus,.btn-danger.active:hover,.btn-danger:active.focus,.btn-danger:active:focus,.btn-danger:active:hover,.open>.dropdown-toggle.btn-danger.focus,.open>.dropdown-toggle.btn-danger:focus,.open>.dropdown-toggle.btn-danger:hover{color:#fff;background-color:#ac2925;border-color:#761c19}.btn-danger.active,.btn-danger:active,.open>.dropdown-toggle.btn-danger{background-image:none}.btn-danger.disabled,.btn-danger.disabled.active,.btn-danger.disabled.focus,.btn-danger.disabled:active,.btn-danger.disabled:focus,.btn-danger.disabled:hover,.btn-danger[disabled],.btn-danger[disabled].active,.btn-danger[disabled].focus,.btn-danger[disabled]:active,.btn-danger[disabled]:focus,.btn-danger[disabled]:hover,fieldset[disabled] .btn-danger,fieldset[disabled] .btn-danger.active,fieldset[disabled] .btn-danger.focus,fieldset[disabled] .btn-danger:active,fieldset[disabled] .btn-danger:focus,fieldset[disabled] .btn-danger:hover{background-color:#d9534f;border-color:#d43f3a}.btn-danger .badge{color:#d9534f;background-color:#fff}.btn-link{font-weight:400;color:#337ab7;border-radius:0}.btn-link,.btn-link.active,.btn-link:active,.btn-link[disabled],fieldset[disabled] .btn-link{background-color:transparent;-webkit-box-shadow:none;box-shadow:none}.btn-link,.btn-link:active,.btn-link:focus,.btn-link:hover{border-color:transparent}.btn-link:focus,.btn-link:hover{color:#23527c;text-decoration:underline;background-color:transparent}.btn-link[disabled]:focus,.btn-link[disabled]:hover,fieldset[disabled] .btn-link:focus,fieldset[disabled] .btn-link:hover{color:#777;text-decoration:none}.btn-group-lg>.btn,.btn-lg{padding:10px 16px;font-size:18px;line-height:1.3333333;border-radius:6px}.btn-group-sm>.btn,.btn-sm{padding:5px 10px;font-size:12px;line-height:1.5;border-radius:3px}.btn-group-xs>.btn,.btn-xs{padding:1px 5px;font-size:12px;line-height:1.5;border-radius:3px}.btn-block{display:block;width:100%}.btn-block+.btn-block{margin-top:5px}input[type=button].btn-block,input[type=reset].btn-block,input[type=submit].btn-block{width:100%}.fade{opacity:0;-webkit-transition:opacity .15s linear;-o-transition:opacity .15s linear;transition:opacity .15s linear}.fade.in{opacity:1}.collapse{display:none}.collapse.in{display:block}tr.collapse.in{display:table-row}tbody.collapse.in{display:table-row-group}.collapsing{position:relative;height:0;overflow:hidden;-webkit-transition-timing-function:ease;-o-transition-timing-function:ease;transition-timing-function:ease;-webkit-transition-duration:.35s;-o-transition-duration:.35s;transition-duration:.35s;-webkit-transition-property:height,visibility;-o-transition-property:height,visibility;transition-property:height,visibility}.caret{display:inline-block;width:0;height:0;margin-left:2px;vertical-align:middle;border-top:4px dashed;border-top:4px solid\9;border-right:4px solid transparent;border-left:4px solid transparent}.dropdown,.dropup{position:relative}.dropdown-toggle:focus{outline:0}.dropdown-menu{position:absolute;top:100%;left:0;z-index:1000;display:none;float:left;min-width:160px;padding:5px 0;margin:2px 0 0;font-size:14px;text-align:left;list-style:none;background-color:#fff;-webkit-background-clip:padding-box;background-clip:padding-box;border:1px solid #ccc;border:1px solid rgba(0,0,0,.15);border-radius:4px;-webkit-box-shadow:0 6px 12px rgba(0,0,0,.175);box-shadow:0 6px 12px rgba(0,0,0,.175)}.dropdown-menu.pull-right{right:0;left:auto}.dropdown-menu .divider{height:1px;margin:9px 0;overflow:hidden;background-color:#e5e5e5}.dropdown-menu>li>a{display:block;padding:3px 20px;clear:both;font-weight:400;line-height:1.42857143;color:#333;white-space:nowrap}.dropdown-menu>li>a:focus,.dropdown-menu>li>a:hover{color:#262626;text-decoration:none;background-color:#f5f5f5}.dropdown-menu>.active>a,.dropdown-menu>.active>a:focus,.dropdown-menu>.active>a:hover{color:#fff;text-decoration:none;background-color:#337ab7;outline:0}.dropdown-menu>.disabled>a,.dropdown-menu>.disabled>a:focus,.dropdown-menu>.disabled>a:hover{color:#777}.dropdown-menu>.disabled>a:focus,.dropdown-menu>.disabled>a:hover{text-decoration:none;cursor:not-allowed;background-color:transparent;background-image:none;filter:progid:DXImageTransform.Microsoft.gradient(enabled=false)}.open>.dropdown-menu{display:block}.open>a{outline:0}.dropdown-menu-right{right:0;left:auto}.dropdown-menu-left{right:auto;left:0}.dropdown-header{display:block;padding:3px 20px;font-size:12px;line-height:1.42857143;color:#777;white-space:nowrap}.dropdown-backdrop{position:fixed;top:0;right:0;bottom:0;left:0;z-index:990}.pull-right>.dropdown-menu{right:0;left:auto}.dropup .caret,.navbar-fixed-bottom .dropdown .caret{content:"";border-top:0;border-bottom:4px dashed;border-bottom:4px solid\9}.dropup .dropdown-menu,.navbar-fixed-bottom .dropdown .dropdown-menu{top:auto;bottom:100%;margin-bottom:2px}@media (min-width:768px){.navbar-right .dropdown-menu{right:0;left:auto}.navbar-right .dropdown-menu-left{right:auto;left:0}}.btn-group,.btn-group-vertical{position:relative;display:inline-block;vertical-align:middle}.btn-group-vertical>.btn,.btn-group>.btn{position:relative;float:left}.btn-group-vertical>.btn.active,.btn-group-vertical>.btn:active,.btn-group-vertical>.btn:focus,.btn-group-vertical>.btn:hover,.btn-group>.btn.active,.btn-group>.btn:active,.btn-group>.btn:focus,.btn-group>.btn:hover{z-index:2}.btn-group .btn+.btn,.btn-group .btn+.btn-group,.btn-group .btn-group+.btn,.btn-group .btn-group+.btn-group{margin-left:-1px}.btn-toolbar{margin-left:-5px}.btn-toolbar .btn,.btn-toolbar .btn-group,.btn-toolbar .input-group{float:left}.btn-toolbar>.btn,.btn-toolbar>.btn-group,.btn-toolbar>.input-group{margin-left:5px}.btn-group>.btn:not(:first-child):not(:last-child):not(.dropdown-toggle){border-radius:0}.btn-group>.btn:first-child{margin-left:0}.btn-group>.btn:first-child:not(:last-child):not(.dropdown-toggle){border-top-right-radius:0;border-bottom-right-radius:0}.btn-group>.btn:last-child:not(:first-child),.btn-group>.dropdown-toggle:not(:first-child){border-top-left-radius:0;border-bottom-left-radius:0}.btn-group>.btn-group{float:left}.btn-group>.btn-group:not(:first-child):not(:last-child)>.btn{border-radius:0}.btn-group>.btn-group:first-child:not(:last-child)>.btn:last-child,.btn-group>.btn-group:first-child:not(:last-child)>.dropdown-toggle{border-top-right-radius:0;border-bottom-right-radius:0}.btn-group>.btn-group:last-child:not(:first-child)>.btn:first-child{border-top-left-radius:0;border-bottom-left-radius:0}.btn-group .dropdown-toggle:active,.btn-group.open .dropdown-toggle{outline:0}.btn-group>.btn+.dropdown-toggle{padding-right:8px;padding-left:8px}.btn-group>.btn-lg+.dropdown-toggle{padding-right:12px;padding-left:12px}.btn-group.open .dropdown-toggle{-webkit-box-shadow:inset 0 3px 5px rgba(0,0,0,.125);box-shadow:inset 0 3px 5px rgba(0,0,0,.125)}.btn-group.open .dropdown-toggle.btn-link{-webkit-box-shadow:none;box-shadow:none}.btn .caret{margin-left:0}.btn-lg .caret{border-width:5px 5px 0;border-bottom-width:0}.dropup .btn-lg .caret{border-width:0 5px 5px}.btn-group-vertical>.btn,.btn-group-vertical>.btn-group,.btn-group-vertical>.btn-group>.btn{display:block;float:none;width:100%;max-width:100%}.btn-group-vertical>.btn-group>.btn{float:none}.btn-group-vertical>.btn+.btn,.btn-group-vertical>.btn+.btn-group,.btn-group-vertical>.btn-group+.btn,.btn-group-vertical>.btn-group+.btn-group{margin-top:-1px;margin-left:0}.btn-group-vertical>.btn:not(:first-child):not(:last-child){border-radius:0}.btn-group-vertical>.btn:first-child:not(:last-child){border-top-right-radius:4px;border-bottom-right-radius:0;border-bottom-left-radius:0}.btn-group-vertical>.btn:last-child:not(:first-child){border-top-left-radius:0;border-top-right-radius:0;border-bottom-left-radius:4px}.btn-group-vertical>.btn-group:not(:first-child):not(:last-child)>.btn{border-radius:0}.btn-group-vertical>.btn-group:first-child:not(:last-child)>.btn:last-child,.btn-group-vertical>.btn-group:first-child:not(:last-child)>.dropdown-toggle{border-bottom-right-radius:0;border-bottom-left-radius:0}.btn-group-vertical>.btn-group:last-child:not(:first-child)>.btn:first-child{border-top-left-radius:0;border-top-right-radius:0}.btn-group-justified{display:table;width:100%;table-layout:fixed;border-collapse:separate}.btn-group-justified>.btn,.btn-group-justified>.btn-group{display:table-cell;float:none;width:1%}.btn-group-justified>.btn-group .btn{width:100%}.btn-group-justified>.btn-group .dropdown-menu{left:auto}[data-toggle=buttons]>.btn input[type=checkbox],[data-toggle=buttons]>.btn input[type=radio],[data-toggle=buttons]>.btn-group>.btn input[type=checkbox],[data-toggle=buttons]>.btn-group>.btn input[type=radio]{position:absolute;clip:rect(0,0,0,0);pointer-events:none}.input-group{position:relative;display:table;border-collapse:separate}.input-group[class*=col-]{float:none;padding-right:0;padding-left:0}.input-group .form-control{position:relative;z-index:2;float:left;width:100%;margin-bottom:0}.input-group-lg>.form-control,.input-group-lg>.input-group-addon,.input-group-lg>.input-group-btn>.btn{height:46px;padding:10px 16px;font-size:18px;line-height:1.3333333;border-radius:6px}select.input-group-lg>.form-control,select.input-group-lg>.input-group-addon,select.input-group-lg>.input-group-btn>.btn{height:46px;line-height:46px}select[multiple].input-group-lg>.form-control,select[multiple].input-group-lg>.input-group-addon,select[multiple].input-group-lg>.input-group-btn>.btn,textarea.input-group-lg>.form-control,textarea.input-group-lg>.input-group-addon,textarea.input-group-lg>.input-group-btn>.btn{height:auto}.input-group-sm>.form-control,.input-group-sm>.input-group-addon,.input-group-sm>.input-group-btn>.btn{height:30px;padding:5px 10px;font-size:12px;line-height:1.5;border-radius:3px}select.input-group-sm>.form-control,select.input-group-sm>.input-group-addon,select.input-group-sm>.input-group-btn>.btn{height:30px;line-height:30px}select[multiple].input-group-sm>.form-control,select[multiple].input-group-sm>.input-group-addon,select[multiple].input-group-sm>.input-group-btn>.btn,textarea.input-group-sm>.form-control,textarea.input-group-sm>.input-group-addon,textarea.input-group-sm>.input-group-btn>.btn{height:auto}.input-group .form-control,.input-group-addon,.input-group-btn{display:table-cell}.input-group .form-control:not(:first-child):not(:last-child),.input-group-addon:not(:first-child):not(:last-child),.input-group-btn:not(:first-child):not(:last-child){border-radius:0}.input-group-addon,.input-group-btn{width:1%;white-space:nowrap;vertical-align:middle}.input-group-addon{padding:6px 12px;font-size:14px;font-weight:400;line-height:1;color:#555;text-align:center;background-color:#eee;border:1px solid #ccc;border-radius:4px}.input-group-addon.input-sm{padding:5px 10px;font-size:12px;border-radius:3px}.input-group-addon.input-lg{padding:10px 16px;font-size:18px;border-radius:6px}.input-group-addon input[type=checkbox],.input-group-addon input[type=radio]{margin-top:0}.input-group .form-control:first-child,.input-group-addon:first-child,.input-group-btn:first-child>.btn,.input-group-btn:first-child>.btn-group>.btn,.input-group-btn:first-child>.dropdown-toggle,.input-group-btn:last-child>.btn-group:not(:last-child)>.btn,.input-group-btn:last-child>.btn:not(:last-child):not(.dropdown-toggle){border-top-right-radius:0;border-bottom-right-radius:0}.input-group-addon:first-child{border-right:0}.input-group .form-control:last-child,.input-group-addon:last-child,.input-group-btn:first-child>.btn-group:not(:first-child)>.btn,.input-group-btn:first-child>.btn:not(:first-child),.input-group-btn:last-child>.btn,.input-group-btn:last-child>.btn-group>.btn,.input-group-btn:last-child>.dropdown-toggle{border-top-left-radius:0;border-bottom-left-radius:0}.input-group-addon:last-child{border-left:0}.input-group-btn{position:relative;font-size:0;white-space:nowrap}.input-group-btn>.btn{position:relative}.input-group-btn>.btn+.btn{margin-left:-1px}.input-group-btn>.btn:active,.input-group-btn>.btn:focus,.input-group-btn>.btn:hover{z-index:2}.input-group-btn:first-child>.btn,.input-group-btn:first-child>.btn-group{margin-right:-1px}.input-group-btn:last-child>.btn,.input-group-btn:last-child>.btn-group{z-index:2;margin-left:-1px}.nav{padding-left:0;margin-bottom:0;list-style:none}.nav>li{position:relative;display:block}.nav>li>a{position:relative;display:block;padding:10px 15px}.nav>li>a:focus,.nav>li>a:hover{text-decoration:none;background-color:#eee}.nav>li.disabled>a{color:#777}.nav>li.disabled>a:focus,.nav>li.disabled>a:hover{color:#777;text-decoration:none;cursor:not-allowed;background-color:transparent}.nav .open>a,.nav .open>a:focus,.nav .open>a:hover{background-color:#eee;border-color:#337ab7}.nav .nav-divider{height:1px;margin:9px 0;overflow:hidden;background-color:#e5e5e5}.nav>li>a>img{max-width:none}.nav-tabs{border-bottom:1px solid #ddd}.nav-tabs>li{float:left;margin-bottom:-1px}.nav-tabs>li>a{margin-right:2px;line-height:1.42857143;border:1px solid transparent;border-radius:4px 4px 0 0}.nav-tabs>li>a:hover{border-color:#eee #eee #ddd}.nav-tabs>li.active>a,.nav-tabs>li.active>a:focus,.nav-tabs>li.active>a:hover{color:#555;cursor:default;background-color:#fff;border:1px solid #ddd;border-bottom-color:transparent}.nav-tabs.nav-justified{width:100%;border-bottom:0}.nav-tabs.nav-justified>li{float:none}.nav-tabs.nav-justified>li>a{margin-bottom:5px;text-align:center}.nav-tabs.nav-justified>.dropdown .dropdown-menu{top:auto;left:auto}@media (min-width:768px){.nav-tabs.nav-justified>li{display:table-cell;width:1%}.nav-tabs.nav-justified>li>a{margin-bottom:0}}.nav-tabs.nav-justified>li>a{margin-right:0;border-radius:4px}.nav-tabs.nav-justified>.active>a,.nav-tabs.nav-justified>.active>a:focus,.nav-tabs.nav-justified>.active>a:hover{border:1px solid #ddd}@media (min-width:768px){.nav-tabs.nav-justified>li>a{border-bottom:1px solid #ddd;border-radius:4px 4px 0 0}.nav-tabs.nav-justified>.active>a,.nav-tabs.nav-justified>.active>a:focus,.nav-tabs.nav-justified>.active>a:hover{border-bottom-color:#fff}}.nav-pills>li{float:left}.nav-pills>li>a{border-radius:4px}.nav-pills>li+li{margin-left:2px}.nav-pills>li.active>a,.nav-pills>li.active>a:focus,.nav-pills>li.active>a:hover{color:#fff;background-color:#337ab7}.nav-stacked>li{float:none}.nav-stacked>li+li{margin-top:2px;margin-left:0}.nav-justified{width:100%}.nav-justified>li{float:none}.nav-justified>li>a{margin-bottom:5px;text-align:center}.nav-justified>.dropdown .dropdown-menu{top:auto;left:auto}@media (min-width:768px){.nav-justified>li{display:table-cell;width:1%}.nav-justified>li>a{margin-bottom:0}}.nav-tabs-justified{border-bottom:0}.nav-tabs-justified>li>a{margin-right:0;border-radius:4px}.nav-tabs-justified>.active>a,.nav-tabs-justified>.active>a:focus,.nav-tabs-justified>.active>a:hover{border:1px solid #ddd}@media (min-width:768px){.nav-tabs-justified>li>a{border-bottom:1px solid #ddd;border-radius:4px 4px 0 0}.nav-tabs-justified>.active>a,.nav-tabs-justified>.active>a:focus,.nav-tabs-justified>.active>a:hover{border-bottom-color:#fff}}.tab-content>.tab-pane{display:none}.tab-content>.active{display:block}.nav-tabs .dropdown-menu{margin-top:-1px;border-top-left-radius:0;border-top-right-radius:0}.navbar{position:relative;min-height:50px;margin-bottom:20px;border:1px solid transparent}@media (min-width:768px){.navbar{border-radius:4px}}@media (min-width:768px){.navbar-header{float:left}}.navbar-collapse{padding-right:15px;padding-left:15px;overflow-x:visible;-webkit-overflow-scrolling:touch;border-top:1px solid transparent;-webkit-box-shadow:inset 0 1px 0 rgba(255,255,255,.1);box-shadow:inset 0 1px 0 rgba(255,255,255,.1)}.navbar-collapse.in{overflow-y:auto}@media (min-width:768px){.navbar-collapse{width:auto;border-top:0;-webkit-box-shadow:none;box-shadow:none}.navbar-collapse.collapse{display:block!important;height:auto!important;padding-bottom:0;overflow:visible!important}.navbar-collapse.in{overflow-y:visible}.navbar-fixed-bottom .navbar-collapse,.navbar-fixed-top .navbar-collapse,.navbar-static-top .navbar-collapse{padding-right:0;padding-left:0}}.navbar-fixed-bottom .navbar-collapse,.navbar-fixed-top .navbar-collapse{max-height:340px}@media (max-device-width:480px) and (orientation:landscape){.navbar-fixed-bottom .navbar-collapse,.navbar-fixed-top .navbar-collapse{max-height:200px}}.container-fluid>.navbar-collapse,.container-fluid>.navbar-header,.container>.navbar-collapse,.container>.navbar-header{margin-right:-15px;margin-left:-15px}@media (min-width:768px){.container-fluid>.navbar-collapse,.container-fluid>.navbar-header,.container>.navbar-collapse,.container>.navbar-header{margin-right:0;margin-left:0}}.navbar-static-top{z-index:1000;border-width:0 0 1px}@media (min-width:768px){.navbar-static-top{border-radius:0}}.navbar-fixed-bottom,.navbar-fixed-top{position:fixed;right:0;left:0;z-index:1030}@media (min-width:768px){.navbar-fixed-bottom,.navbar-fixed-top{border-radius:0}}.navbar-fixed-top{top:0;border-width:0 0 1px}.navbar-fixed-bottom{bottom:0;margin-bottom:0;border-width:1px 0 0}.navbar-brand{float:left;height:50px;padding:15px 15px;font-size:18px;line-height:20px}.navbar-brand:focus,.navbar-brand:hover{text-decoration:none}.navbar-brand>img{display:block}@media (min-width:768px){.navbar>.container .navbar-brand,.navbar>.container-fluid .navbar-brand{margin-left:-15px}}.navbar-toggle{position:relative;float:right;padding:9px 10px;margin-top:8px;margin-right:15px;margin-bottom:8px;background-color:transparent;background-image:none;border:1px solid transparent;border-radius:4px}.navbar-toggle:focus{outline:0}.navbar-toggle .icon-bar{display:block;width:22px;height:2px;border-radius:1px}.navbar-toggle .icon-bar+.icon-bar{margin-top:4px}@media (min-width:768px){.navbar-toggle{display:none}}.navbar-nav{margin:7.5px -15px}.navbar-nav>li>a{padding-top:10px;padding-bottom:10px;line-height:20px}@media (max-width:767px){.navbar-nav .open .dropdown-menu{position:static;float:none;width:auto;margin-top:0;background-color:transparent;border:0;-webkit-box-shadow:none;box-shadow:none}.navbar-nav .open .dropdown-menu .dropdown-header,.navbar-nav .open .dropdown-menu>li>a{padding:5px 15px 5px 25px}.navbar-nav .open .dropdown-menu>li>a{line-height:20px}.navbar-nav .open .dropdown-menu>li>a:focus,.navbar-nav .open .dropdown-menu>li>a:hover{background-image:none}}@media (min-width:768px){.navbar-nav{float:left;margin:0}.navbar-nav>li{float:left}.navbar-nav>li>a{padding-top:15px;padding-bottom:15px}}.navbar-form{padding:10px 15px;margin-top:8px;margin-right:-15px;margin-bottom:8px;margin-left:-15px;border-top:1px solid transparent;border-bottom:1px solid transparent;-webkit-box-shadow:inset 0 1px 0 rgba(255,255,255,.1),0 1px 0 rgba(255,255,255,.1);box-shadow:inset 0 1px 0 rgba(255,255,255,.1),0 1px 0 rgba(255,255,255,.1)}@media (min-width:768px){.navbar-form .form-group{display:inline-block;margin-bottom:0;vertical-align:middle}.navbar-form .form-control{display:inline-block;width:auto;vertical-align:middle}.navbar-form .form-control-static{display:inline-block}.navbar-form .input-group{display:inline-table;vertical-align:middle}.navbar-form .input-group .form-control,.navbar-form .input-group .input-group-addon,.navbar-form .input-group .input-group-btn{width:auto}.navbar-form .input-group>.form-control{width:100%}.navbar-form .control-label{margin-bottom:0;vertical-align:middle}.navbar-form .checkbox,.navbar-form .radio{display:inline-block;margin-top:0;margin-bottom:0;vertical-align:middle}.navbar-form .checkbox label,.navbar-form .radio label{padding-left:0}.navbar-form .checkbox input[type=checkbox],.navbar-form .radio input[type=radio]{position:relative;margin-left:0}.navbar-form .has-feedback .form-control-feedback{top:0}}@media (max-width:767px){.navbar-form .form-group{margin-bottom:5px}.navbar-form .form-group:last-child{margin-bottom:0}}@media (min-width:768px){.navbar-form{width:auto;padding-top:0;padding-bottom:0;margin-right:0;margin-left:0;border:0;-webkit-box-shadow:none;box-shadow:none}}.navbar-nav>li>.dropdown-menu{margin-top:0;border-top-left-radius:0;border-top-right-radius:0}.navbar-fixed-bottom .navbar-nav>li>.dropdown-menu{margin-bottom:0;border-top-left-radius:4px;border-top-right-radius:4px;border-bottom-right-radius:0;border-bottom-left-radius:0}.navbar-btn{margin-top:8px;margin-bottom:8px}.navbar-btn.btn-sm{margin-top:10px;margin-bottom:10px}.navbar-btn.btn-xs{margin-top:14px;margin-bottom:14px}.navbar-text{margin-top:15px;margin-bottom:15px}@media (min-width:768px){.navbar-text{float:left;margin-right:15px;margin-left:15px}}@media (min-width:768px){.navbar-left{float:left!important}.navbar-right{float:right!important;margin-right:-15px}.navbar-right~.navbar-right{margin-right:0}}.navbar-default{background-color:#f8f8f8;border-color:#e7e7e7}.navbar-default .navbar-brand{color:#777}.navbar-default .navbar-brand:focus,.navbar-default .navbar-brand:hover{color:#5e5e5e;background-color:transparent}.navbar-default .navbar-text{color:#777}.navbar-default .navbar-nav>li>a{color:#777}.navbar-default .navbar-nav>li>a:focus,.navbar-default .navbar-nav>li>a:hover{color:#333;background-color:transparent}.navbar-default .navbar-nav>.active>a,.navbar-default .navbar-nav>.active>a:focus,.navbar-default .navbar-nav>.active>a:hover{color:#555;background-color:#e7e7e7}.navbar-default .navbar-nav>.disabled>a,.navbar-default .navbar-nav>.disabled>a:focus,.navbar-default .navbar-nav>.disabled>a:hover{color:#ccc;background-color:transparent}.navbar-default .navbar-toggle{border-color:#ddd}.navbar-default .navbar-toggle:focus,.navbar-default .navbar-toggle:hover{background-color:#ddd}.navbar-default .navbar-toggle .icon-bar{background-color:#888}.navbar-default .navbar-collapse,.navbar-default .navbar-form{border-color:#e7e7e7}.navbar-default .navbar-nav>.open>a,.navbar-default .navbar-nav>.open>a:focus,.navbar-default .navbar-nav>.open>a:hover{color:#555;background-color:#e7e7e7}@media (max-width:767px){.navbar-default .navbar-nav .open .dropdown-menu>li>a{color:#777}.navbar-default .navbar-nav .open .dropdown-menu>li>a:focus,.navbar-default .navbar-nav .open .dropdown-menu>li>a:hover{color:#333;background-color:transparent}.navbar-default .navbar-nav .open .dropdown-menu>.active>a,.navbar-default .navbar-nav .open .dropdown-menu>.active>a:focus,.navbar-default .navbar-nav .open .dropdown-menu>.active>a:hover{color:#555;background-color:#e7e7e7}.navbar-default .navbar-nav .open .dropdown-menu>.disabled>a,.navbar-default .navbar-nav .open .dropdown-menu>.disabled>a:focus,.navbar-default .navbar-nav .open .dropdown-menu>.disabled>a:hover{color:#ccc;background-color:transparent}}.navbar-default .navbar-link{color:#777}.navbar-default .navbar-link:hover{color:#333}.navbar-default .btn-link{color:#777}.navbar-default .btn-link:focus,.navbar-default .btn-link:hover{color:#333}.navbar-default .btn-link[disabled]:focus,.navbar-default .btn-link[disabled]:hover,fieldset[disabled] .navbar-default .btn-link:focus,fieldset[disabled] .navbar-default .btn-link:hover{color:#ccc}.navbar-inverse{background-color:#222;border-color:#080808}.navbar-inverse .navbar-brand{color:#9d9d9d}.navbar-inverse .navbar-brand:focus,.navbar-inverse .navbar-brand:hover{color:#fff;background-color:transparent}.navbar-inverse .navbar-text{color:#9d9d9d}.navbar-inverse .navbar-nav>li>a{color:#9d9d9d}.navbar-inverse .navbar-nav>li>a:focus,.navbar-inverse .navbar-nav>li>a:hover{color:#fff;background-color:transparent}.navbar-inverse .navbar-nav>.active>a,.navbar-inverse .navbar-nav>.active>a:focus,.navbar-inverse .navbar-nav>.active>a:hover{color:#fff;background-color:#080808}.navbar-inverse .navbar-nav>.disabled>a,.navbar-inverse .navbar-nav>.disabled>a:focus,.navbar-inverse .navbar-nav>.disabled>a:hover{color:#444;background-color:transparent}.navbar-inverse .navbar-toggle{border-color:#333}.navbar-inverse .navbar-toggle:focus,.navbar-inverse .navbar-toggle:hover{background-color:#333}.navbar-inverse .navbar-toggle .icon-bar{background-color:#fff}.navbar-inverse .navbar-collapse,.navbar-inverse .navbar-form{border-color:#101010}.navbar-inverse .navbar-nav>.open>a,.navbar-inverse .navbar-nav>.open>a:focus,.navbar-inverse .navbar-nav>.open>a:hover{color:#fff;background-color:#080808}@media (max-width:767px){.navbar-inverse .navbar-nav .open .dropdown-menu>.dropdown-header{border-color:#080808}.navbar-inverse .navbar-nav .open .dropdown-menu .divider{background-color:#080808}.navbar-inverse .navbar-nav .open .dropdown-menu>li>a{color:#9d9d9d}.navbar-inverse .navbar-nav .open .dropdown-menu>li>a:focus,.navbar-inverse .navbar-nav .open .dropdown-menu>li>a:hover{color:#fff;background-color:transparent}.navbar-inverse .navbar-nav .open .dropdown-menu>.active>a,.navbar-inverse .navbar-nav .open .dropdown-menu>.active>a:focus,.navbar-inverse .navbar-nav .open .dropdown-menu>.active>a:hover{color:#fff;background-color:#080808}.navbar-inverse .navbar-nav .open .dropdown-menu>.disabled>a,.navbar-inverse .navbar-nav .open .dropdown-menu>.disabled>a:focus,.navbar-inverse .navbar-nav .open .dropdown-menu>.disabled>a:hover{color:#444;background-color:transparent}}.navbar-inverse .navbar-link{color:#9d9d9d}.navbar-inverse .navbar-link:hover{color:#fff}.navbar-inverse .btn-link{color:#9d9d9d}.navbar-inverse .btn-link:focus,.navbar-inverse .btn-link:hover{color:#fff}.navbar-inverse .btn-link[disabled]:focus,.navbar-inverse .btn-link[disabled]:hover,fieldset[disabled] .navbar-inverse .btn-link:focus,fieldset[disabled] .navbar-inverse .btn-link:hover{color:#444}.breadcrumb{padding:8px 15px;margin-bottom:20px;list-style:none;background-color:#f5f5f5;border-radius:4px}.breadcrumb>li{display:inline-block}.breadcrumb>li+li:before{padding:0 5px;color:#ccc;content:"/\00a0"}.breadcrumb>.active{color:#777}.pagination{display:inline-block;padding-left:0;margin:20px 0;border-radius:4px}.pagination>li{display:inline}.pagination>li>a,.pagination>li>span{position:relative;float:left;padding:6px 12px;margin-left:-1px;line-height:1.42857143;color:#337ab7;text-decoration:none;background-color:#fff;border:1px solid #ddd}.pagination>li:first-child>a,.pagination>li:first-child>span{margin-left:0;border-top-left-radius:4px;border-bottom-left-radius:4px}.pagination>li:last-child>a,.pagination>li:last-child>span{border-top-right-radius:4px;border-bottom-right-radius:4px}.pagination>li>a:focus,.pagination>li>a:hover,.pagination>li>span:focus,.pagination>li>span:hover{z-index:3;color:#23527c;background-color:#eee;border-color:#ddd}.pagination>.active>a,.pagination>.active>a:focus,.pagination>.active>a:hover,.pagination>.active>span,.pagination>.active>span:focus,.pagination>.active>span:hover{z-index:2;color:#fff;cursor:default;background-color:#337ab7;border-color:#337ab7}.pagination>.disabled>a,.pagination>.disabled>a:focus,.pagination>.disabled>a:hover,.pagination>.disabled>span,.pagination>.disabled>span:focus,.pagination>.disabled>span:hover{color:#777;cursor:not-allowed;background-color:#fff;border-color:#ddd}.pagination-lg>li>a,.pagination-lg>li>span{padding:10px 16px;font-size:18px;line-height:1.3333333}.pagination-lg>li:first-child>a,.pagination-lg>li:first-child>span{border-top-left-radius:6px;border-bottom-left-radius:6px}.pagination-lg>li:last-child>a,.pagination-lg>li:last-child>span{border-top-right-radius:6px;border-bottom-right-radius:6px}.pagination-sm>li>a,.pagination-sm>li>span{padding:5px 10px;font-size:12px;line-height:1.5}.pagination-sm>li:first-child>a,.pagination-sm>li:first-child>span{border-top-left-radius:3px;border-bottom-left-radius:3px}.pagination-sm>li:last-child>a,.pagination-sm>li:last-child>span{border-top-right-radius:3px;border-bottom-right-radius:3px}.pager{padding-left:0;margin:20px 0;text-align:center;list-style:none}.pager li{display:inline}.pager li>a,.pager li>span{display:inline-block;padding:5px 14px;background-color:#fff;border:1px solid #ddd;border-radius:15px}.pager li>a:focus,.pager li>a:hover{text-decoration:none;background-color:#eee}.pager .next>a,.pager .next>span{float:right}.pager .previous>a,.pager .previous>span{float:left}.pager .disabled>a,.pager .disabled>a:focus,.pager .disabled>a:hover,.pager .disabled>span{color:#777;cursor:not-allowed;background-color:#fff}.label{display:inline;padding:.2em .6em .3em;font-size:75%;font-weight:700;line-height:1;color:#fff;text-align:center;white-space:nowrap;vertical-align:baseline;border-radius:.25em}a.label:focus,a.label:hover{color:#fff;text-decoration:none;cursor:pointer}.label:empty{display:none}.btn .label{position:relative;top:-1px}.label-default{background-color:#777}.label-default[href]:focus,.label-default[href]:hover{background-color:#5e5e5e}.label-primary{background-color:#337ab7}.label-primary[href]:focus,.label-primary[href]:hover{background-color:#286090}.label-success{background-color:#5cb85c}.label-success[href]:focus,.label-success[href]:hover{background-color:#449d44}.label-info{background-color:#5bc0de}.label-info[href]:focus,.label-info[href]:hover{background-color:#31b0d5}.label-warning{background-color:#f0ad4e}.label-warning[href]:focus,.label-warning[href]:hover{background-color:#ec971f}.label-danger{background-color:#d9534f}.label-danger[href]:focus,.label-danger[href]:hover{background-color:#c9302c}.badge{display:inline-block;min-width:10px;padding:3px 7px;font-size:12px;font-weight:700;line-height:1;color:#fff;text-align:center;white-space:nowrap;vertical-align:middle;background-color:#777;border-radius:10px}.badge:empty{display:none}.btn .badge{position:relative;top:-1px}.btn-group-xs>.btn .badge,.btn-xs .badge{top:0;padding:1px 5px}a.badge:focus,a.badge:hover{color:#fff;text-decoration:none;cursor:pointer}.list-group-item.active>.badge,.nav-pills>.active>a>.badge{color:#337ab7;background-color:#fff}.list-group-item>.badge{float:right}.list-group-item>.badge+.badge{margin-right:5px}.nav-pills>li>a>.badge{margin-left:3px}.jumbotron{padding-top:30px;padding-bottom:30px;margin-bottom:30px;color:inherit;background-color:#eee}.jumbotron .h1,.jumbotron h1{color:inherit}.jumbotron p{margin-bottom:15px;font-size:21px;font-weight:200}.jumbotron>hr{border-top-color:#d5d5d5}.container .jumbotron,.container-fluid .jumbotron{border-radius:6px}.jumbotron .container{max-width:100%}@media screen and (min-width:768px){.jumbotron{padding-top:48px;padding-bottom:48px}.container .jumbotron,.container-fluid .jumbotron{padding-right:60px;padding-left:60px}.jumbotron .h1,.jumbotron h1{font-size:63px}}.thumbnail{display:block;padding:4px;margin-bottom:20px;line-height:1.42857143;background-color:#fff;border:1px solid #ddd;border-radius:4px;-webkit-transition:border .2s ease-in-out;-o-transition:border .2s ease-in-out;transition:border .2s ease-in-out}.thumbnail a>img,.thumbnail>img{margin-right:auto;margin-left:auto}a.thumbnail.active,a.thumbnail:focus,a.thumbnail:hover{border-color:#337ab7}.thumbnail .caption{padding:9px;color:#333}.alert{padding:15px;margin-bottom:20px;border:1px solid transparent;border-radius:4px}.alert h4{margin-top:0;color:inherit}.alert .alert-link{font-weight:700}.alert>p,.alert>ul{margin-bottom:0}.alert>p+p{margin-top:5px}.alert-dismissable,.alert-dismissible{padding-right:35px}.alert-dismissable .close,.alert-dismissible .close{position:relative;top:-2px;right:-21px;color:inherit}.alert-success{color:#3c763d;background-color:#dff0d8;border-color:#d6e9c6}.alert-success hr{border-top-color:#c9e2b3}.alert-success .alert-link{color:#2b542c}.alert-info{color:#31708f;background-color:#d9edf7;border-color:#bce8f1}.alert-info hr{border-top-color:#a6e1ec}.alert-info .alert-link{color:#245269}.alert-warning{color:#8a6d3b;background-color:#fcf8e3;border-color:#faebcc}.alert-warning hr{border-top-color:#f7e1b5}.alert-warning .alert-link{color:#66512c}.alert-danger{color:#a94442;background-color:#f2dede;border-color:#ebccd1}.alert-danger hr{border-top-color:#e4b9c0}.alert-danger .alert-link{color:#843534}@-webkit-keyframes progress-bar-stripes{from{background-position:40px 0}to{background-position:0 0}}@-o-keyframes progress-bar-stripes{from{background-position:40px 0}to{background-position:0 0}}@keyframes progress-bar-stripes{from{background-position:40px 0}to{background-position:0 0}}.progress{height:20px;margin-bottom:20px;overflow:hidden;background-color:#f5f5f5;border-radius:4px;-webkit-box-shadow:inset 0 1px 2px rgba(0,0,0,.1);box-shadow:inset 0 1px 2px rgba(0,0,0,.1)}.progress-bar{float:left;width:0;height:100%;font-size:12px;line-height:20px;color:#fff;text-align:center;background-color:#337ab7;-webkit-box-shadow:inset 0 -1px 0 rgba(0,0,0,.15);box-shadow:inset 0 -1px 0 rgba(0,0,0,.15);-webkit-transition:width .6s ease;-o-transition:width .6s ease;transition:width .6s ease}.progress-bar-striped,.progress-striped .progress-bar{background-image:-webkit-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-image:-o-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-image:linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);-webkit-background-size:40px 40px;background-size:40px 40px}.progress-bar.active,.progress.active .progress-bar{-webkit-animation:progress-bar-stripes 2s linear infinite;-o-animation:progress-bar-stripes 2s linear infinite;animation:progress-bar-stripes 2s linear infinite}.progress-bar-success{background-color:#5cb85c}.progress-striped .progress-bar-success{background-image:-webkit-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-image:-o-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-image:linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent)}.progress-bar-info{background-color:#5bc0de}.progress-striped .progress-bar-info{background-image:-webkit-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-image:-o-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-image:linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent)}.progress-bar-warning{background-color:#f0ad4e}.progress-striped .progress-bar-warning{background-image:-webkit-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-image:-o-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-image:linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent)}.progress-bar-danger{background-color:#d9534f}.progress-striped .progress-bar-danger{background-image:-webkit-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-image:-o-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-image:linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent)}.media{margin-top:15px}.media:first-child{margin-top:0}.media,.media-body{overflow:hidden;zoom:1}.media-body{width:10000px}.media-object{display:block}.media-object.img-thumbnail{max-width:none}.media-right,.media>.pull-right{padding-left:10px}.media-left,.media>.pull-left{padding-right:10px}.media-body,.media-left,.media-right{display:table-cell;vertical-align:top}.media-middle{vertical-align:middle}.media-bottom{vertical-align:bottom}.media-heading{margin-top:0;margin-bottom:5px}.media-list{padding-left:0;list-style:none}.list-group{padding-left:0;margin-bottom:20px}.list-group-item{position:relative;display:block;padding:10px 15px;margin-bottom:-1px;background-color:#fff;border:1px solid #ddd}.list-group-item:first-child{border-top-left-radius:4px;border-top-right-radius:4px}.list-group-item:last-child{margin-bottom:0;border-bottom-right-radius:4px;border-bottom-left-radius:4px}a.list-group-item,button.list-group-item{color:#555}a.list-group-item .list-group-item-heading,button.list-group-item .list-group-item-heading{color:#333}a.list-group-item:focus,a.list-group-item:hover,button.list-group-item:focus,button.list-group-item:hover{color:#555;text-decoration:none;background-color:#f5f5f5}button.list-group-item{width:100%;text-align:left}.list-group-item.disabled,.list-group-item.disabled:focus,.list-group-item.disabled:hover{color:#777;cursor:not-allowed;background-color:#eee}.list-group-item.disabled .list-group-item-heading,.list-group-item.disabled:focus .list-group-item-heading,.list-group-item.disabled:hover .list-group-item-heading{color:inherit}.list-group-item.disabled .list-group-item-text,.list-group-item.disabled:focus .list-group-item-text,.list-group-item.disabled:hover .list-group-item-text{color:#777}.list-group-item.active,.list-group-item.active:focus,.list-group-item.active:hover{z-index:2;color:#fff;background-color:#337ab7;border-color:#337ab7}.list-group-item.active .list-group-item-heading,.list-group-item.active .list-group-item-heading>.small,.list-group-item.active .list-group-item-heading>small,.list-group-item.active:focus .list-group-item-heading,.list-group-item.active:focus .list-group-item-heading>.small,.list-group-item.active:focus .list-group-item-heading>small,.list-group-item.active:hover .list-group-item-heading,.list-group-item.active:hover .list-group-item-heading>.small,.list-group-item.active:hover .list-group-item-heading>small{color:inherit}.list-group-item.active .list-group-item-text,.list-group-item.active:focus .list-group-item-text,.list-group-item.active:hover .list-group-item-text{color:#c7ddef}.list-group-item-success{color:#3c763d;background-color:#dff0d8}a.list-group-item-success,button.list-group-item-success{color:#3c763d}a.list-group-item-success .list-group-item-heading,button.list-group-item-success .list-group-item-heading{color:inherit}a.list-group-item-success:focus,a.list-group-item-success:hover,button.list-group-item-success:focus,button.list-group-item-success:hover{color:#3c763d;background-color:#d0e9c6}a.list-group-item-success.active,a.list-group-item-success.active:focus,a.list-group-item-success.active:hover,button.list-group-item-success.active,button.list-group-item-success.active:focus,button.list-group-item-success.active:hover{color:#fff;background-color:#3c763d;border-color:#3c763d}.list-group-item-info{color:#31708f;background-color:#d9edf7}a.list-group-item-info,button.list-group-item-info{color:#31708f}a.list-group-item-info .list-group-item-heading,button.list-group-item-info .list-group-item-heading{color:inherit}a.list-group-item-info:focus,a.list-group-item-info:hover,button.list-group-item-info:focus,button.list-group-item-info:hover{color:#31708f;background-color:#c4e3f3}a.list-group-item-info.active,a.list-group-item-info.active:focus,a.list-group-item-info.active:hover,button.list-group-item-info.active,button.list-group-item-info.active:focus,button.list-group-item-info.active:hover{color:#fff;background-color:#31708f;border-color:#31708f}.list-group-item-warning{color:#8a6d3b;background-color:#fcf8e3}a.list-group-item-warning,button.list-group-item-warning{color:#8a6d3b}a.list-group-item-warning .list-group-item-heading,button.list-group-item-warning .list-group-item-heading{color:inherit}a.list-group-item-warning:focus,a.list-group-item-warning:hover,button.list-group-item-warning:focus,button.list-group-item-warning:hover{color:#8a6d3b;background-color:#faf2cc}a.list-group-item-warning.active,a.list-group-item-warning.active:focus,a.list-group-item-warning.active:hover,button.list-group-item-warning.active,button.list-group-item-warning.active:focus,button.list-group-item-warning.active:hover{color:#fff;background-color:#8a6d3b;border-color:#8a6d3b}.list-group-item-danger{color:#a94442;background-color:#f2dede}a.list-group-item-danger,button.list-group-item-danger{color:#a94442}a.list-group-item-danger .list-group-item-heading,button.list-group-item-danger .list-group-item-heading{color:inherit}a.list-group-item-danger:focus,a.list-group-item-danger:hover,button.list-group-item-danger:focus,button.list-group-item-danger:hover{color:#a94442;background-color:#ebcccc}a.list-group-item-danger.active,a.list-group-item-danger.active:focus,a.list-group-item-danger.active:hover,button.list-group-item-danger.active,button.list-group-item-danger.active:focus,button.list-group-item-danger.active:hover{color:#fff;background-color:#a94442;border-color:#a94442}.list-group-item-heading{margin-top:0;margin-bottom:5px}.list-group-item-text{margin-bottom:0;line-height:1.3}.panel{margin-bottom:20px;background-color:#fff;border:1px solid transparent;border-radius:4px;-webkit-box-shadow:0 1px 1px rgba(0,0,0,.05);box-shadow:0 1px 1px rgba(0,0,0,.05)}.panel-body{padding:15px}.panel-heading{padding:10px 15px;border-bottom:1px solid transparent;border-top-left-radius:3px;border-top-right-radius:3px}.panel-heading>.dropdown .dropdown-toggle{color:inherit}.panel-title{margin-top:0;margin-bottom:0;font-size:16px;color:inherit}.panel-title>.small,.panel-title>.small>a,.panel-title>a,.panel-title>small,.panel-title>small>a{color:inherit}.panel-footer{padding:10px 15px;background-color:#f5f5f5;border-top:1px solid #ddd;border-bottom-right-radius:3px;border-bottom-left-radius:3px}.panel>.list-group,.panel>.panel-collapse>.list-group{margin-bottom:0}.panel>.list-group .list-group-item,.panel>.panel-collapse>.list-group .list-group-item{border-width:1px 0;border-radius:0}.panel>.list-group:first-child .list-group-item:first-child,.panel>.panel-collapse>.list-group:first-child .list-group-item:first-child{border-top:0;border-top-left-radius:3px;border-top-right-radius:3px}.panel>.list-group:last-child .list-group-item:last-child,.panel>.panel-collapse>.list-group:last-child .list-group-item:last-child{border-bottom:0;border-bottom-right-radius:3px;border-bottom-left-radius:3px}.panel>.panel-heading+.panel-collapse>.list-group .list-group-item:first-child{border-top-left-radius:0;border-top-right-radius:0}.panel-heading+.list-group .list-group-item:first-child{border-top-width:0}.list-group+.panel-footer{border-top-width:0}.panel>.panel-collapse>.table,.panel>.table,.panel>.table-responsive>.table{margin-bottom:0}.panel>.panel-collapse>.table caption,.panel>.table caption,.panel>.table-responsive>.table caption{padding-right:15px;padding-left:15px}.panel>.table-responsive:first-child>.table:first-child,.panel>.table:first-child{border-top-left-radius:3px;border-top-right-radius:3px}.panel>.table-responsive:first-child>.table:first-child>tbody:first-child>tr:first-child,.panel>.table-responsive:first-child>.table:first-child>thead:first-child>tr:first-child,.panel>.table:first-child>tbody:first-child>tr:first-child,.panel>.table:first-child>thead:first-child>tr:first-child{border-top-left-radius:3px;border-top-right-radius:3px}.panel>.table-responsive:first-child>.table:first-child>tbody:first-child>tr:first-child td:first-child,.panel>.table-responsive:first-child>.table:first-child>tbody:first-child>tr:first-child th:first-child,.panel>.table-responsive:first-child>.table:first-child>thead:first-child>tr:first-child td:first-child,.panel>.table-responsive:first-child>.table:first-child>thead:first-child>tr:first-child th:first-child,.panel>.table:first-child>tbody:first-child>tr:first-child td:first-child,.panel>.table:first-child>tbody:first-child>tr:first-child th:first-child,.panel>.table:first-child>thead:first-child>tr:first-child td:first-child,.panel>.table:first-child>thead:first-child>tr:first-child th:first-child{border-top-left-radius:3px}.panel>.table-responsive:first-child>.table:first-child>tbody:first-child>tr:first-child td:last-child,.panel>.table-responsive:first-child>.table:first-child>tbody:first-child>tr:first-child th:last-child,.panel>.table-responsive:first-child>.table:first-child>thead:first-child>tr:first-child td:last-child,.panel>.table-responsive:first-child>.table:first-child>thead:first-child>tr:first-child th:last-child,.panel>.table:first-child>tbody:first-child>tr:first-child td:last-child,.panel>.table:first-child>tbody:first-child>tr:first-child th:last-child,.panel>.table:first-child>thead:first-child>tr:first-child td:last-child,.panel>.table:first-child>thead:first-child>tr:first-child th:last-child{border-top-right-radius:3px}.panel>.table-responsive:last-child>.table:last-child,.panel>.table:last-child{border-bottom-right-radius:3px;border-bottom-left-radius:3px}.panel>.table-responsive:last-child>.table:last-child>tbody:last-child>tr:last-child,.panel>.table-responsive:last-child>.table:last-child>tfoot:last-child>tr:last-child,.panel>.table:last-child>tbody:last-child>tr:last-child,.panel>.table:last-child>tfoot:last-child>tr:last-child{border-bottom-right-radius:3px;border-bottom-left-radius:3px}.panel>.table-responsive:last-child>.table:last-child>tbody:last-child>tr:last-child td:first-child,.panel>.table-responsive:last-child>.table:last-child>tbody:last-child>tr:last-child th:first-child,.panel>.table-responsive:last-child>.table:last-child>tfoot:last-child>tr:last-child td:first-child,.panel>.table-responsive:last-child>.table:last-child>tfoot:last-child>tr:last-child th:first-child,.panel>.table:last-child>tbody:last-child>tr:last-child td:first-child,.panel>.table:last-child>tbody:last-child>tr:last-child th:first-child,.panel>.table:last-child>tfoot:last-child>tr:last-child td:first-child,.panel>.table:last-child>tfoot:last-child>tr:last-child th:first-child{border-bottom-left-radius:3px}.panel>.table-responsive:last-child>.table:last-child>tbody:last-child>tr:last-child td:last-child,.panel>.table-responsive:last-child>.table:last-child>tbody:last-child>tr:last-child th:last-child,.panel>.table-responsive:last-child>.table:last-child>tfoot:last-child>tr:last-child td:last-child,.panel>.table-responsive:last-child>.table:last-child>tfoot:last-child>tr:last-child th:last-child,.panel>.table:last-child>tbody:last-child>tr:last-child td:last-child,.panel>.table:last-child>tbody:last-child>tr:last-child th:last-child,.panel>.table:last-child>tfoot:last-child>tr:last-child td:last-child,.panel>.table:last-child>tfoot:last-child>tr:last-child th:last-child{border-bottom-right-radius:3px}.panel>.panel-body+.table,.panel>.panel-body+.table-responsive,.panel>.table+.panel-body,.panel>.table-responsive+.panel-body{border-top:1px solid #ddd}.panel>.table>tbody:first-child>tr:first-child td,.panel>.table>tbody:first-child>tr:first-child th{border-top:0}.panel>.table-bordered,.panel>.table-responsive>.table-bordered{border:0}.panel>.table-bordered>tbody>tr>td:first-child,.panel>.table-bordered>tbody>tr>th:first-child,.panel>.table-bordered>tfoot>tr>td:first-child,.panel>.table-bordered>tfoot>tr>th:first-child,.panel>.table-bordered>thead>tr>td:first-child,.panel>.table-bordered>thead>tr>th:first-child,.panel>.table-responsive>.table-bordered>tbody>tr>td:first-child,.panel>.table-responsive>.table-bordered>tbody>tr>th:first-child,.panel>.table-responsive>.table-bordered>tfoot>tr>td:first-child,.panel>.table-responsive>.table-bordered>tfoot>tr>th:first-child,.panel>.table-responsive>.table-bordered>thead>tr>td:first-child,.panel>.table-responsive>.table-bordered>thead>tr>th:first-child{border-left:0}.panel>.table-bordered>tbody>tr>td:last-child,.panel>.table-bordered>tbody>tr>th:last-child,.panel>.table-bordered>tfoot>tr>td:last-child,.panel>.table-bordered>tfoot>tr>th:last-child,.panel>.table-bordered>thead>tr>td:last-child,.panel>.table-bordered>thead>tr>th:last-child,.panel>.table-responsive>.table-bordered>tbody>tr>td:last-child,.panel>.table-responsive>.table-bordered>tbody>tr>th:last-child,.panel>.table-responsive>.table-bordered>tfoot>tr>td:last-child,.panel>.table-responsive>.table-bordered>tfoot>tr>th:last-child,.panel>.table-responsive>.table-bordered>thead>tr>td:last-child,.panel>.table-responsive>.table-bordered>thead>tr>th:last-child{border-right:0}.panel>.table-bordered>tbody>tr:first-child>td,.panel>.table-bordered>tbody>tr:first-child>th,.panel>.table-bordered>thead>tr:first-child>td,.panel>.table-bordered>thead>tr:first-child>th,.panel>.table-responsive>.table-bordered>tbody>tr:first-child>td,.panel>.table-responsive>.table-bordered>tbody>tr:first-child>th,.panel>.table-responsive>.table-bordered>thead>tr:first-child>td,.panel>.table-responsive>.table-bordered>thead>tr:first-child>th{border-bottom:0}.panel>.table-bordered>tbody>tr:last-child>td,.panel>.table-bordered>tbody>tr:last-child>th,.panel>.table-bordered>tfoot>tr:last-child>td,.panel>.table-bordered>tfoot>tr:last-child>th,.panel>.table-responsive>.table-bordered>tbody>tr:last-child>td,.panel>.table-responsive>.table-bordered>tbody>tr:last-child>th,.panel>.table-responsive>.table-bordered>tfoot>tr:last-child>td,.panel>.table-responsive>.table-bordered>tfoot>tr:last-child>th{border-bottom:0}.panel>.table-responsive{margin-bottom:0;border:0}.panel-group{margin-bottom:20px}.panel-group .panel{margin-bottom:0;border-radius:4px}.panel-group .panel+.panel{margin-top:5px}.panel-group .panel-heading{border-bottom:0}.panel-group .panel-heading+.panel-collapse>.list-group,.panel-group .panel-heading+.panel-collapse>.panel-body{border-top:1px solid #ddd}.panel-group .panel-footer{border-top:0}.panel-group .panel-footer+.panel-collapse .panel-body{border-bottom:1px solid #ddd}.panel-default{border-color:#ddd}.panel-default>.panel-heading{color:#333;background-color:#f5f5f5;border-color:#ddd}.panel-default>.panel-heading+.panel-collapse>.panel-body{border-top-color:#ddd}.panel-default>.panel-heading .badge{color:#f5f5f5;background-color:#333}.panel-default>.panel-footer+.panel-collapse>.panel-body{border-bottom-color:#ddd}.panel-primary{border-color:#337ab7}.panel-primary>.panel-heading{color:#fff;background-color:#337ab7;border-color:#337ab7}.panel-primary>.panel-heading+.panel-collapse>.panel-body{border-top-color:#337ab7}.panel-primary>.panel-heading .badge{color:#337ab7;background-color:#fff}.panel-primary>.panel-footer+.panel-collapse>.panel-body{border-bottom-color:#337ab7}.panel-success{border-color:#d6e9c6}.panel-success>.panel-heading{color:#3c763d;background-color:#dff0d8;border-color:#d6e9c6}.panel-success>.panel-heading+.panel-collapse>.panel-body{border-top-color:#d6e9c6}.panel-success>.panel-heading .badge{color:#dff0d8;background-color:#3c763d}.panel-success>.panel-footer+.panel-collapse>.panel-body{border-bottom-color:#d6e9c6}.panel-info{border-color:#bce8f1}.panel-info>.panel-heading{color:#31708f;background-color:#d9edf7;border-color:#bce8f1}.panel-info>.panel-heading+.panel-collapse>.panel-body{border-top-color:#bce8f1}.panel-info>.panel-heading .badge{color:#d9edf7;background-color:#31708f}.panel-info>.panel-footer+.panel-collapse>.panel-body{border-bottom-color:#bce8f1}.panel-warning{border-color:#faebcc}.panel-warning>.panel-heading{color:#8a6d3b;background-color:#fcf8e3;border-color:#faebcc}.panel-warning>.panel-heading+.panel-collapse>.panel-body{border-top-color:#faebcc}.panel-warning>.panel-heading .badge{color:#fcf8e3;background-color:#8a6d3b}.panel-warning>.panel-footer+.panel-collapse>.panel-body{border-bottom-color:#faebcc}.panel-danger{border-color:#ebccd1}.panel-danger>.panel-heading{color:#a94442;background-color:#f2dede;border-color:#ebccd1}.panel-danger>.panel-heading+.panel-collapse>.panel-body{border-top-color:#ebccd1}.panel-danger>.panel-heading .badge{color:#f2dede;background-color:#a94442}.panel-danger>.panel-footer+.panel-collapse>.panel-body{border-bottom-color:#ebccd1}.embed-responsive{position:relative;display:block;height:0;padding:0;overflow:hidden}.embed-responsive .embed-responsive-item,.embed-responsive embed,.embed-responsive iframe,.embed-responsive object,.embed-responsive video{position:absolute;top:0;bottom:0;left:0;width:100%;height:100%;border:0}.embed-responsive-16by9{padding-bottom:56.25%}.embed-responsive-4by3{padding-bottom:75%}.well{min-height:20px;padding:19px;margin-bottom:20px;background-color:#f5f5f5;border:1px solid #e3e3e3;border-radius:4px;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.05);box-shadow:inset 0 1px 1px rgba(0,0,0,.05)}.well blockquote{border-color:#ddd;border-color:rgba(0,0,0,.15)}.well-lg{padding:24px;border-radius:6px}.well-sm{padding:9px;border-radius:3px}.close{float:right;font-size:21px;font-weight:700;line-height:1;color:#000;text-shadow:0 1px 0 #fff;filter:alpha(opacity=20);opacity:.2}.close:focus,.close:hover{color:#000;text-decoration:none;cursor:pointer;filter:alpha(opacity=50);opacity:.5}button.close{-webkit-appearance:none;padding:0;cursor:pointer;background:0 0;border:0}.modal-open{overflow:hidden}.modal{position:fixed;top:0;right:0;bottom:0;left:0;z-index:1050;display:none;overflow:hidden;-webkit-overflow-scrolling:touch;outline:0}.modal.fade .modal-dialog{-webkit-transition:-webkit-transform .3s ease-out;-o-transition:-o-transform .3s ease-out;transition:transform .3s ease-out;-webkit-transform:translate(0,-25%);-ms-transform:translate(0,-25%);-o-transform:translate(0,-25%);transform:translate(0,-25%)}.modal.in .modal-dialog{-webkit-transform:translate(0,0);-ms-transform:translate(0,0);-o-transform:translate(0,0);transform:translate(0,0)}.modal-open .modal{overflow-x:hidden;overflow-y:auto}.modal-dialog{position:relative;width:auto;margin:10px}.modal-content{position:relative;background-color:#fff;-webkit-background-clip:padding-box;background-clip:padding-box;border:1px solid #999;border:1px solid rgba(0,0,0,.2);border-radius:6px;outline:0;-webkit-box-shadow:0 3px 9px rgba(0,0,0,.5);box-shadow:0 3px 9px rgba(0,0,0,.5)}.modal-backdrop{position:fixed;top:0;right:0;bottom:0;left:0;z-index:1040;background-color:#000}.modal-backdrop.fade{filter:alpha(opacity=0);opacity:0}.modal-backdrop.in{filter:alpha(opacity=50);opacity:.5}.modal-header{min-height:16.43px;padding:15px;border-bottom:1px solid #e5e5e5}.modal-header .close{margin-top:-2px}.modal-title{margin:0;line-height:1.42857143}.modal-body{position:relative;padding:15px}.modal-footer{padding:15px;text-align:right;border-top:1px solid #e5e5e5}.modal-footer .btn+.btn{margin-bottom:0;margin-left:5px}.modal-footer .btn-group .btn+.btn{margin-left:-1px}.modal-footer .btn-block+.btn-block{margin-left:0}.modal-scrollbar-measure{position:absolute;top:-9999px;width:50px;height:50px;overflow:scroll}@media (min-width:768px){.modal-dialog{width:600px;margin:30px auto}.modal-content{-webkit-box-shadow:0 5px 15px rgba(0,0,0,.5);box-shadow:0 5px 15px rgba(0,0,0,.5)}.modal-sm{width:300px}}@media (min-width:992px){.modal-lg{width:900px}}.tooltip{position:absolute;z-index:1070;display:block;font-family:"Helvetica Neue",Helvetica,Arial,sans-serif;font-size:12px;font-style:normal;font-weight:400;line-height:1.42857143;text-align:left;text-align:start;text-decoration:none;text-shadow:none;text-transform:none;letter-spacing:normal;word-break:normal;word-spacing:normal;word-wrap:normal;white-space:normal;filter:alpha(opacity=0);opacity:0;line-break:auto}.tooltip.in{filter:alpha(opacity=90);opacity:.9}.tooltip.top{padding:5px 0;margin-top:-3px}.tooltip.right{padding:0 5px;margin-left:3px}.tooltip.bottom{padding:5px 0;margin-top:3px}.tooltip.left{padding:0 5px;margin-left:-3px}.tooltip-inner{max-width:200px;padding:3px 8px;color:#fff;text-align:center;background-color:#000;border-radius:4px}.tooltip-arrow{position:absolute;width:0;height:0;border-color:transparent;border-style:solid}.tooltip.top .tooltip-arrow{bottom:0;left:50%;margin-left:-5px;border-width:5px 5px 0;border-top-color:#000}.tooltip.top-left .tooltip-arrow{right:5px;bottom:0;margin-bottom:-5px;border-width:5px 5px 0;border-top-color:#000}.tooltip.top-right .tooltip-arrow{bottom:0;left:5px;margin-bottom:-5px;border-width:5px 5px 0;border-top-color:#000}.tooltip.right .tooltip-arrow{top:50%;left:0;margin-top:-5px;border-width:5px 5px 5px 0;border-right-color:#000}.tooltip.left .tooltip-arrow{top:50%;right:0;margin-top:-5px;border-width:5px 0 5px 5px;border-left-color:#000}.tooltip.bottom .tooltip-arrow{top:0;left:50%;margin-left:-5px;border-width:0 5px 5px;border-bottom-color:#000}.tooltip.bottom-left .tooltip-arrow{top:0;right:5px;margin-top:-5px;border-width:0 5px 5px;border-bottom-color:#000}.tooltip.bottom-right .tooltip-arrow{top:0;left:5px;margin-top:-5px;border-width:0 5px 5px;border-bottom-color:#000}.popover{position:absolute;top:0;left:0;z-index:1060;display:none;max-width:276px;padding:1px;font-family:"Helvetica Neue",Helvetica,Arial,sans-serif;font-size:14px;font-style:normal;font-weight:400;line-height:1.42857143;text-align:left;text-align:start;text-decoration:none;text-shadow:none;text-transform:none;letter-spacing:normal;word-break:normal;word-spacing:normal;word-wrap:normal;white-space:normal;background-color:#fff;-webkit-background-clip:padding-box;background-clip:padding-box;border:1px solid #ccc;border:1px solid rgba(0,0,0,.2);border-radius:6px;-webkit-box-shadow:0 5px 10px rgba(0,0,0,.2);box-shadow:0 5px 10px rgba(0,0,0,.2);line-break:auto}.popover.top{margin-top:-10px}.popover.right{margin-left:10px}.popover.bottom{margin-top:10px}.popover.left{margin-left:-10px}.popover-title{padding:8px 14px;margin:0;font-size:14px;background-color:#f7f7f7;border-bottom:1px solid #ebebeb;border-radius:5px 5px 0 0}.popover-content{padding:9px 14px}.popover>.arrow,.popover>.arrow:after{position:absolute;display:block;width:0;height:0;border-color:transparent;border-style:solid}.popover>.arrow{border-width:11px}.popover>.arrow:after{content:"";border-width:10px}.popover.top>.arrow{bottom:-11px;left:50%;margin-left:-11px;border-top-color:#999;border-top-color:rgba(0,0,0,.25);border-bottom-width:0}.popover.top>.arrow:after{bottom:1px;margin-left:-10px;content:" ";border-top-color:#fff;border-bottom-width:0}.popover.right>.arrow{top:50%;left:-11px;margin-top:-11px;border-right-color:#999;border-right-color:rgba(0,0,0,.25);border-left-width:0}.popover.right>.arrow:after{bottom:-10px;left:1px;content:" ";border-right-color:#fff;border-left-width:0}.popover.bottom>.arrow{top:-11px;left:50%;margin-left:-11px;border-top-width:0;border-bottom-color:#999;border-bottom-color:rgba(0,0,0,.25)}.popover.bottom>.arrow:after{top:1px;margin-left:-10px;content:" ";border-top-width:0;border-bottom-color:#fff}.popover.left>.arrow{top:50%;right:-11px;margin-top:-11px;border-right-width:0;border-left-color:#999;border-left-color:rgba(0,0,0,.25)}.popover.left>.arrow:after{right:1px;bottom:-10px;content:" ";border-right-width:0;border-left-color:#fff}.carousel{position:relative}.carousel-inner{position:relative;width:100%;overflow:hidden}.carousel-inner>.item{position:relative;display:none;-webkit-transition:.6s ease-in-out left;-o-transition:.6s ease-in-out left;transition:.6s ease-in-out left}.carousel-inner>.item>a>img,.carousel-inner>.item>img{line-height:1}@media all and (transform-3d),(-webkit-transform-3d){.carousel-inner>.item{-webkit-transition:-webkit-transform .6s ease-in-out;-o-transition:-o-transform .6s ease-in-out;transition:transform .6s ease-in-out;-webkit-backface-visibility:hidden;backface-visibility:hidden;-webkit-perspective:1000px;perspective:1000px}.carousel-inner>.item.active.right,.carousel-inner>.item.next{left:0;-webkit-transform:translate3d(100%,0,0);transform:translate3d(100%,0,0)}.carousel-inner>.item.active.left,.carousel-inner>.item.prev{left:0;-webkit-transform:translate3d(-100%,0,0);transform:translate3d(-100%,0,0)}.carousel-inner>.item.active,.carousel-inner>.item.next.left,.carousel-inner>.item.prev.right{left:0;-webkit-transform:translate3d(0,0,0);transform:translate3d(0,0,0)}}.carousel-inner>.active,.carousel-inner>.next,.carousel-inner>.prev{display:block}.carousel-inner>.active{left:0}.carousel-inner>.next,.carousel-inner>.prev{position:absolute;top:0;width:100%}.carousel-inner>.next{left:100%}.carousel-inner>.prev{left:-100%}.carousel-inner>.next.left,.carousel-inner>.prev.right{left:0}.carousel-inner>.active.left{left:-100%}.carousel-inner>.active.right{left:100%}.carousel-control{position:absolute;top:0;bottom:0;left:0;width:15%;font-size:20px;color:#fff;text-align:center;text-shadow:0 1px 2px rgba(0,0,0,.6);filter:alpha(opacity=50);opacity:.5}.carousel-control.left{background-image:-webkit-linear-gradient(left,rgba(0,0,0,.5) 0,rgba(0,0,0,.0001) 100%);background-image:-o-linear-gradient(left,rgba(0,0,0,.5) 0,rgba(0,0,0,.0001) 100%);background-image:-webkit-gradient(linear,left top,right top,from(rgba(0,0,0,.5)),to(rgba(0,0,0,.0001)));background-image:linear-gradient(to right,rgba(0,0,0,.5) 0,rgba(0,0,0,.0001) 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#80000000', endColorstr='#00000000', GradientType=1);background-repeat:repeat-x}.carousel-control.right{right:0;left:auto;background-image:-webkit-linear-gradient(left,rgba(0,0,0,.0001) 0,rgba(0,0,0,.5) 100%);background-image:-o-linear-gradient(left,rgba(0,0,0,.0001) 0,rgba(0,0,0,.5) 100%);background-image:-webkit-gradient(linear,left top,right top,from(rgba(0,0,0,.0001)),to(rgba(0,0,0,.5)));background-image:linear-gradient(to right,rgba(0,0,0,.0001) 0,rgba(0,0,0,.5) 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#00000000', endColorstr='#80000000', GradientType=1);background-repeat:repeat-x}.carousel-control:focus,.carousel-control:hover{color:#fff;text-decoration:none;filter:alpha(opacity=90);outline:0;opacity:.9}.carousel-control .glyphicon-chevron-left,.carousel-control .glyphicon-chevron-right,.carousel-control .icon-next,.carousel-control .icon-prev{position:absolute;top:50%;z-index:5;display:inline-block;margin-top:-10px}.carousel-control .glyphicon-chevron-left,.carousel-control .icon-prev{left:50%;margin-left:-10px}.carousel-control .glyphicon-chevron-right,.carousel-control .icon-next{right:50%;margin-right:-10px}.carousel-control .icon-next,.carousel-control .icon-prev{width:20px;height:20px;font-family:serif;line-height:1}.carousel-control .icon-prev:before{content:'\2039'}.carousel-control .icon-next:before{content:'\203a'}.carousel-indicators{position:absolute;bottom:10px;left:50%;z-index:15;width:60%;padding-left:0;margin-left:-30%;text-align:center;list-style:none}.carousel-indicators li{display:inline-block;width:10px;height:10px;margin:1px;text-indent:-999px;cursor:pointer;background-color:#000\9;background-color:rgba(0,0,0,0);border:1px solid #fff;border-radius:10px}.carousel-indicators .active{width:12px;height:12px;margin:0;background-color:#fff}.carousel-caption{position:absolute;right:15%;bottom:20px;left:15%;z-index:10;padding-top:20px;padding-bottom:20px;color:#fff;text-align:center;text-shadow:0 1px 2px rgba(0,0,0,.6)}.carousel-caption .btn{text-shadow:none}@media screen and (min-width:768px){.carousel-control .glyphicon-chevron-left,.carousel-control .glyphicon-chevron-right,.carousel-control .icon-next,.carousel-control .icon-prev{width:30px;height:30px;margin-top:-15px;font-size:30px}.carousel-control .glyphicon-chevron-left,.carousel-control .icon-prev{margin-left:-15px}.carousel-control .glyphicon-chevron-right,.carousel-control .icon-next{margin-right:-15px}.carousel-caption{right:20%;left:20%;padding-bottom:30px}.carousel-indicators{bottom:20px}}.btn-group-vertical>.btn-group:after,.btn-group-vertical>.btn-group:before,.btn-toolbar:after,.btn-toolbar:before,.clearfix:after,.clearfix:before,.container-fluid:after,.container-fluid:before,.container:after,.container:before,.dl-horizontal dd:after,.dl-horizontal dd:before,.form-horizontal .form-group:after,.form-horizontal .form-group:before,.modal-footer:after,.modal-footer:before,.nav:after,.nav:before,.navbar-collapse:after,.navbar-collapse:before,.navbar-header:after,.navbar-header:before,.navbar:after,.navbar:before,.pager:after,.pager:before,.panel-body:after,.panel-body:before,.row:after,.row:before{display:table;content:" "}.btn-group-vertical>.btn-group:after,.btn-toolbar:after,.clearfix:after,.container-fluid:after,.container:after,.dl-horizontal dd:after,.form-horizontal .form-group:after,.modal-footer:after,.nav:after,.navbar-collapse:after,.navbar-header:after,.navbar:after,.pager:after,.panel-body:after,.row:after{clear:both}.center-block{display:block;margin-right:auto;margin-left:auto}.pull-right{float:right!important}.pull-left{float:left!important}.hide{display:none!important}.show{display:block!important}.invisible{visibility:hidden}.text-hide{font:0/0 a;color:transparent;text-shadow:none;background-color:transparent;border:0}.hidden{display:none!important}.affix{position:fixed}@-ms-viewport{width:device-width}.visible-lg,.visible-md,.visible-sm,.visible-xs{display:none!important}.visible-lg-block,.visible-lg-inline,.visible-lg-inline-block,.visible-md-block,.visible-md-inline,.visible-md-inline-block,.visible-sm-block,.visible-sm-inline,.visible-sm-inline-block,.visible-xs-block,.visible-xs-inline,.visible-xs-inline-block{display:none!important}@media (max-width:767px){.visible-xs{display:block!important}table.visible-xs{display:table!important}tr.visible-xs{display:table-row!important}td.visible-xs,th.visible-xs{display:table-cell!important}}@media (max-width:767px){.visible-xs-block{display:block!important}}@media (max-width:767px){.visible-xs-inline{display:inline!important}}@media (max-width:767px){.visible-xs-inline-block{display:inline-block!important}}@media (min-width:768px) and (max-width:991px){.visible-sm{display:block!important}table.visible-sm{display:table!important}tr.visible-sm{display:table-row!important}td.visible-sm,th.visible-sm{display:table-cell!important}}@media (min-width:768px) and (max-width:991px){.visible-sm-block{display:block!important}}@media (min-width:768px) and (max-width:991px){.visible-sm-inline{display:inline!important}}@media (min-width:768px) and (max-width:991px){.visible-sm-inline-block{display:inline-block!important}}@media (min-width:992px) and (max-width:1199px){.visible-md{display:block!important}table.visible-md{display:table!important}tr.visible-md{display:table-row!important}td.visible-md,th.visible-md{display:table-cell!important}}@media (min-width:992px) and (max-width:1199px){.visible-md-block{display:block!important}}@media (min-width:992px) and (max-width:1199px){.visible-md-inline{display:inline!important}}@media (min-width:992px) and (max-width:1199px){.visible-md-inline-block{display:inline-block!important}}@media (min-width:1200px){.visible-lg{display:block!important}table.visible-lg{display:table!important}tr.visible-lg{display:table-row!important}td.visible-lg,th.visible-lg{display:table-cell!important}}@media (min-width:1200px){.visible-lg-block{display:block!important}}@media (min-width:1200px){.visible-lg-inline{display:inline!important}}@media (min-width:1200px){.visible-lg-inline-block{display:inline-block!important}}@media (max-width:767px){.hidden-xs{display:none!important}}@media (min-width:768px) and (max-width:991px){.hidden-sm{display:none!important}}@media (min-width:992px) and (max-width:1199px){.hidden-md{display:none!important}}@media (min-width:1200px){.hidden-lg{display:none!important}}.visible-print{display:none!important}@media print{.visible-print{display:block!important}table.visible-print{display:table!important}tr.visible-print{display:table-row!important}td.visible-print,th.visible-print{display:table-cell!important}}.visible-print-block{display:none!important}@media print{.visible-print-block{display:block!important}}.visible-print-inline{display:none!important}@media print{.visible-print-inline{display:inline!important}}.visible-print-inline-block{display:none!important}@media print{.visible-print-inline-block{display:inline-block!important}}@media print{.hidden-print{display:none!important}} - -/* Space out content a bit */ -body, form, ul, table { - margin-top: 20px; - margin-bottom: 20px; -} - -/* Phoenix flash messages */ -.alert:empty { display: none; } - -/* Custom page header */ -.header { - border-bottom: 1px solid #e5e5e5; -} -.logo { - width: 519px; - height: 71px; - display: inline-block; - margin-bottom: 1em; - background-image: url("/images/phoenix.png"); - background-size: 519px 71px; -} - -/* Everything but the jumbotron gets side spacing for mobile first views */ -.header, -.marketing { - padding-right: 15px; - padding-left: 15px; -} - -/* Customize container */ -@media (min-width: 768px) { - .container { - max-width: 730px; - } -} -.container-narrow > hr { - margin: 30px 0; -} - -/* Main marketing message */ -.jumbotron { - text-align: center; - border-bottom: 1px solid #e5e5e5; -} - -/* Supporting marketing content */ -.marketing { - margin: 35px 0; -} - -/* Responsive: Portrait tablets and up */ -@media screen and (min-width: 768px) { - /* Remove the padding we set earlier */ - .header, - .marketing { - padding-right: 0; - padding-left: 0; - } - /* Space out the masthead */ - .header { - margin-bottom: 30px; - } - /* Remove the bottom border on the jumbotron for visual effect */ - .jumbotron { - border-bottom: 0; - } -} diff --git a/code-samples/community/serving/helloworld-elixir/assets/js/app.js b/code-samples/community/serving/helloworld-elixir/assets/js/app.js deleted file mode 100644 index e7549b9db4..0000000000 --- a/code-samples/community/serving/helloworld-elixir/assets/js/app.js +++ /dev/null @@ -1,21 +0,0 @@ -// Brunch automatically concatenates all files in your -// watched paths. Those paths can be configured at -// config.paths.watched in "brunch-config.js". -// -// However, those files will only be executed if -// explicitly imported. The only exception are files -// in vendor, which are never wrapped in imports and -// therefore are always executed. - -// Import dependencies -// -// If you no longer want to use a dependency, remember -// to also remove its path from "config.paths.watched". -import "phoenix_html" - -// Import local files -// -// Local files can be imported directly using relative -// paths "./socket" or full ones "web/static/js/socket". - -// import socket from "./socket" diff --git a/code-samples/community/serving/helloworld-elixir/assets/js/socket.js b/code-samples/community/serving/helloworld-elixir/assets/js/socket.js deleted file mode 100644 index 5c23a67f79..0000000000 --- a/code-samples/community/serving/helloworld-elixir/assets/js/socket.js +++ /dev/null @@ -1,62 +0,0 @@ -// NOTE: The contents of this file will only be executed if -// you uncomment its entry in "assets/js/app.js". - -// To use Phoenix channels, the first step is to import Socket -// and connect at the socket path in "lib/web/endpoint.ex": -import {Socket} from "phoenix" - -let socket = new Socket("/socket", {params: {token: window.userToken}}) - -// When you connect, you'll often need to authenticate the client. -// For example, imagine you have an authentication plug, `MyAuth`, -// which authenticates the session and assigns a `:current_user`. -// If the current user exists you can assign the user's token in -// the connection for use in the layout. -// -// In your "lib/web/router.ex": -// -// pipeline :browser do -// ... -// plug MyAuth -// plug :put_user_token -// end -// -// defp put_user_token(conn, _) do -// if current_user = conn.assigns[:current_user] do -// token = Phoenix.Token.sign(conn, "user socket", current_user.id) -// assign(conn, :user_token, token) -// else -// conn -// end -// end -// -// Now you need to pass this token to JavaScript. You can do so -// inside a script tag in "lib/web/templates/layout/app.html.eex": -// -// -// -// You will need to verify the user token in the "connect/2" function -// in "lib/web/channels/user_socket.ex": -// -// def connect(%{"token" => token}, socket) do -// # max_age: 1209600 is equivalent to two weeks in seconds -// case Phoenix.Token.verify(socket, "user socket", token, max_age: 1209600) do -// {:ok, user_id} -> -// {:ok, assign(socket, :user, user_id)} -// {:error, reason} -> -// :error -// end -// end -// -// Finally, pass the token on connect as below. Or remove it -// from connect if you don't care about authentication. - -socket.connect() - -// Now that you are connected, you can join channels with a topic: -let channel = socket.channel("topic:subtopic", {}) -channel.join() - .receive("ok", resp => { console.log("Joined successfully", resp) }) - .receive("error", resp => { console.log("Unable to join", resp) }) - -export default socket diff --git a/code-samples/community/serving/helloworld-elixir/assets/package.json b/code-samples/community/serving/helloworld-elixir/assets/package.json deleted file mode 100644 index 4cc3986978..0000000000 --- a/code-samples/community/serving/helloworld-elixir/assets/package.json +++ /dev/null @@ -1,18 +0,0 @@ -{ - "repository": {}, - "license": "MIT", - "scripts": { - "deploy": "brunch build --production", - "watch": "brunch watch --stdin" - }, - "dependencies": { - "phoenix": "file:../deps/phoenix", - "phoenix_html": "file:../deps/phoenix_html" - }, - "devDependencies": { - "babel-brunch": "6.1.1", - "brunch": "2.10.9", - "clean-css-brunch": "2.10.0", - "uglify-js-brunch": "2.10.0" - } -} diff --git a/code-samples/community/serving/helloworld-elixir/assets/static/favicon.ico b/code-samples/community/serving/helloworld-elixir/assets/static/favicon.ico deleted file mode 100644 index 73de524aaadcf60fbe9d32881db0aa86b58b5cb9..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1258 zcmbtUO>fgM7{=qN=;Mz_82;lvPEdVaxv-<-&=sZLwab?3I zBP>U*&(Hv<5n@9ZQ$vhg#|u$Zmtq8BV;+W*7(?jOx-{r?#TE&$Sdq77MbdJjD5`-q zMm_z(jLv3t>5NhzK{%aG(Yudfpjd3AFdKe2U7&zdepTe>^s(@!&0X8TJ`h+-I?84Ml# diff --git a/code-samples/community/serving/helloworld-elixir/assets/static/images/phoenix.png b/code-samples/community/serving/helloworld-elixir/assets/static/images/phoenix.png deleted file mode 100644 index 9c81075f63d2151e6f40e9aa66f665749a87cc6a..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 13900 zcmaL8WmsF?7A@RTTCBLc6?b=ccXxso4H~R1?gT4RtT+@6?yiLril%4@T7niU{_*z6 z{eIkY^CMY%XUs9jnrrU0pClu(+L}t3=w#^6o;|}(O%cy#x4LjZZH1q*$X;nePbVE4Ruj~ha0EO zKNwDso99#XvuEN`AWs{Bi@gtxt-YhOy9C{FXD=O%vz-K;k$?ubhNqmple2Q5m%Uz~ zramCh1t4NaCnZTE4ibGLaI^QZp#izMx_gU)Bn$}9dm*VB;%os*A`rzjVfzrR1HKOd)umm?RCh=|BP9K5_7PY4e00Cyi75Qn=r z{eKwb?Y#kB&YnKb9_}>%FxuF9`1(lDJt_Uy6x=-jOY83a?=n3Vj0LBly^W8Dm%fLG z>wl`K?d0L(;qBz%Nh7BxK%-#;aCZOa_%B{VLsZ4x+sDQoV6P%CLHESK>FjJL%Eu=o zC@9Y_#G@c6$it(+FQO9uXOy|HR6B0DRr--F^NOYxjR*h5u*lKds>A z`IK4S-pkp~-cHfW!;R+eltrEYw-$l_$@lMAyZ^04@PEc~J&ED^XJP+;3;mx{Pu=s+ z@V{;QbnxHCw|9T)cCV+l_Rhg0diIRBPeoovAGCCkhmu7!e=!0j%CIc1U{;0rzhnzj zRH%Ot=y$J%$R~ap!UOQPkR*PGC6W<##xjgp8{rXFTPGUhD7@5RKexzmd%We{#b|6i z`?lh2^&{jx)SK#0PhPgi&eUZ0vBcGiH`@-FoRy{i3j{L(leZ-WVvvA2{XVGbnr9s* zG$JW*Sqd>q(BQkwNG{TIu68tN%oQnb6^FFNR~xPl$I zm|>W*j{xhT(g3sl-2z1KY@&qA0a~--8mlbo6MSY3Sy29DZRC=_#b9K&IcW(xbn3qD zali;DIL*NQ2a>E?#=CXQMk;2IJDpfLGR5_w?UEM;`!OQP>sJa904@JRBdgqw<{A-f zPODilVldJY3tG8mjj<9Cq%HNX;km>BP=EQ!_>VT)lC6`dm~$b&B*aCJ*_t6bQD*XIIA zrrq#>z~6ik=?Q&P-|3PvgPI@=_MRFRi5f&qlac?_B_cT$A11<`f;&+p^s(QUcKGMS zNYwS6+Y109HVx5PCw$%fR|2X^WJR_R&T>NOOaXhEOOBl@ACRbf{Q38g%!l_W!fCv{ zyn=GMr7&FEFtoISlT(_%iFGOyAW*%LTFx{?IMb~HaOTxco0(xXa`wb0B-{sjpkZ9F zbnZMIZIc!;=Qqv2^WY_d{p1IDf88Rxts3(SLO{5`#Xi5aUOr5);GFV06(V2G0%QE` zw{cbL@W!uuqA3n1q)>mMxU?wl*Pwndp(E*^iJ@$Hm4EfeJ`y=_@(E_@&+FH@D;5#% z%5izR;P_>FEfS3Nmq*3SI-GpsAP~&&m$citnCRwyK%Fs4!m6qG(fj((-y-2~&7)oQ z4#JKn4nA=SUWP)V&DUvjP#Hz?-yUdXY;@ zNlmhBn0p;i0j^5OqhqN%)6E;;VN5UVdzE$GmIS%ZKVBDViH>uKNOQ&Uq5yG0Dlp-V zTpnO8cV6#UAk z)?vp{kNcLNu9V6yaw#|j*h9p`zNZJMyYcx_9Zx@es61Md4Nc*y09>UV7@wE@EGya!%G<~=$Cg%(LWWrD<&NXYR$#UpU; zl-N8X3auH&u_czz`2@`)@9^Q(Z%i7Hf=u*EDPZM>R2Fk4J#Q=0-x+Y2G~abPx7&Ra z2NL1RzJ6GzOMmMRqU6 z$VT^YqYCg33>3Q}C1=wdL-qO~RY!>-RljOAeEMmD^wu(R)f~VT!$Ug{0mvR$s&%fPY=gWk9kNN8m)<5-VE?(DW&De z_K7#3AU;h7d9k4~t}aji!~JOUAShjMOMAIETdSX?IMsgoD0hRthVvFz_Pv zdB+jF*ZW#({d2~{sX9F*h~py)k>5uVOoN%aFYVn4R`h41lz|0c2VZIB=nppL5y=g> zu!5%WhCXBkP}Z@2N_Vz!AzjR@qHsS0JYuj-#`U;&ZpDXpK_mAhyos?3Q{PNOL0pmg zC+VYZt}AEuYBcotKWk`m>a(=zjXxDB3#5Um zVOPP7@tHWfoJhBge!5gA4xHSVT7cu2&GC^pQ`A)wCChhgTf&%uxo`T!dK!h-3`){W zpvJr6%XD*gpM-&tSGPXMc(X9$3n{M4OiY7A9Xmh?(uP=TgDFkP-egM4nbFfm?^>b$ zOW3Npm^VN^_io|YL=pYnX73Ft-K|c|A1*#YT?(+WskD4SwQN8cBq))xT(;M{@0~D8 zL`ANR>lb0mKLRtNENx&SAp>P7857a%ZP{0S3snYW+tbd!X-*{GL}**b@G};C z)Q3bSoD}bG=Jx$POx1UDzM= z`-IZDl+GJgv`ehIT0``{&WDsH3nEG03F1%AU(!=nGsjuyzcneB{{lp{>#5)ndCUO;OINf(7fpu|jyopb#q zlcAO8B?*00y0gq?{w~Rm#QuV^oj)tPcv!7-@bCr?Zk?hlTDK)}c8r_PG$e2Sxtqkw znT9qczCHX17&fsDl3Vm2V-Aarj3y0gN1oyt+l*_2>We#0j5b%9+SO=cHnf?jhBVL* zc#p)VMKXMa?+hxBt}v^^v`27e&jC%v7U zYKYuMhjG$Ix{NA9pgZ+vM>wy}WFw4vHwJAgeD0=m%D2|9gU5(o73(HHxx~ z$`tS4W>`?peBKOuh2OZWrn>N15K@lt?#^(;0WnTZ?_LtcuN$kZ4>wSZ(5iUWZ$`jTC z_ci7nCc@Rp`ZOBltEe^pK#3|uV{VnV_K305Q3%H-7{5pCjN#f=F$6GY0!$*`&2k!S zIddNLT9i~PSY$C(Vk}fNjSg5anR_qHRGpDH-%`M=-M#Uy)$8I8o`groI|!?V_x3%D z*jIq7JKZ%3t7W0A9=PatJ(#|9PuiW+t}h-&qnBZ5P*GhxNr~gqcYtmMghEcf1;N$b z?-KJjMQTx=;qx4;2QzXIHdtmV{?c(qZn=JMuV7*~^o}L0PZRG-cNY-v$m+tCNWA;qfeK|Ja$ z?dtZ+=kKMyDZQ?#yBJCu@vCPRGRG#W=#Uqy7gWdT#9=CV-aUP``ekX{im2fj$(ICH zrqyj>sx@=@VhTUP^u8#smC#HX@iA!B1&~*#t~u+7Nq74FS*V0Q0?u(R5}(HKHeXU| zaX6UE!_YCc0<@~U?km)OK|HeGDJuLE1en`EE(|f3b_8Kc>^KoR$h}C4y*efcDc79k z)u3b4(j8swz`YC~>rtU}6ui^r7(E_B<4DBV|5_E&6Rp|K-w*sw)y8zPZhwG05z^^w zLRAg*Our%j74=A`>3&;5GjxWvxa*y0L3)y#_vIKsT*HJxThAl=kcG%Qs?J-inZbh@ zq`FJ)@rN?G3!zzcyL6$GtD~<-+L`H#r!{AWlr~}E%2bRDzO|+VWq4@vyEP<&_QmKI7yfHm7c|~ zkdcGa5KJs;WE|^Wm#k^lqqyS>>?&VZTzP8uAppMl3)U|MmG^Sp-h8%HE>eK^IF3|u z6blQxe|+599-P{(w9u$@#Po)>v4I0!Sh_Zp$De)M6#l5 zMLd&@Q!>%r&X>3(dy1Sy?PO++U1`I)&{?M@Uo z%#2bAa3&rk<63k``;b?*UQ=TG&ME|}*pK;D6(8EIW`d64<`Ai~rNBrJ{k%38h0VrZ z)(*?!ceIz6p#l3bgLvo%tKy^07Gr2rg@|ENO0eGhf^tf4;XC)3w)a9%k-CFMjbN)`@oRUehd@f#YrH`!qtJ(}CQ8lR z+MUwQHG!ZjF=2+LRco1w;NA)|e&(F=;@5@~YvQ*}WwH|1 zW{l!fpO$_sGYm*FDc`WXx|&tI;x;P(o+0HlocYS>GuQ0YJ}uF5G$wr!TF%IET{Q4|>d}!k>Q%%+Z{vc^)k{}BmP<=f)KU-84}F(W3?QXO?M&M_+fH%H zP1RGVhy8_TH3xc5er1$IF9!{db){AF1?8D6r6x6UC#X=y=*ObiCe zZ|cKVcuN6?)kxDj?`&dz$0gLFecX{V&Au;2g)e>UH(kt49)MhGU9UX2($=TV6dnKe zCR!eldvubP@OGmDCuf$w`Jo*ml6I!*Z&(Oa{eaWP`8m*aE|7#?ovVrug{PNqINSdu z@u72)Vd`WJ6OYNAB#+hOE$k8B(PtN)wdfZ;ELi6(7IlI>Ir~TU<;xx4Tn0^Lm885k z!2|CbsSv##hl_!eoJ#>wpS`2KtE(5CZ!Hf~l*~7UMiIR+&UO9*juK5%YYJjtkERgP zggP=dxb4%E8W((`2g)%g?g>E+RZW)7*L)HMnl}Lnu;J?<6ODpm3RLPGq6Vl;z|aNp z5*5uzK$K)Bp{dY?A*8crtu--(0(l+bO&*>5!u!KQD+;nt(a~g^`=2T;v-g>ul$x_u zLcQ{AV+YeSFP`@OYqz>QCGH1>^M==xc=@-W?jSBT@vfSWgAluU7WT?eutjJ2$9ZSdl;^rlm2JPtQ%6@Y$l7(6B9 zlqVdq@F&qdugX5%1MkA<3y`rQM$#0zn1``Jaacc^tu(EL=wALU?vJ70Xwx&+^%@ab z;OsbwDLNe;#0Iv-_)%@b(BG3aEi4P?nhDFaEm@06YtqSK88&-%%KNKLjXM)jlt$0d z(q8vr_pCL!w|MrQ((|ceeWT@-V(H#9J;(%sS2B8f8}xNox|N@GD5loR?9+n2fWKZY zc(Y*>gX85*ALqgajeA^)lhbXRioH>St-U3|TRjZd87wh*%kX(J1H3jQhhtV+p3fcPQ>XQUKsF9mm zoH!0Sr&YY;%y1%&bJqhNV_vk;?sx~5__YLXe|G`Bd!GququTI(0J-~}A@a(HCwYmO zWj>cDZ4_FKb}1f&lN4TD2*1zVVhK*wFN*D6oRC-~%)GsE{(N>owOd z%1cRV&^^^z@YP_}sI0j+rz_3|Zk9B;z|^}WEhV^Bpm;=Uf9IpY5Fn6A|FO@j7Z8&B z96ZFHGbnNB^C(Vfa20auH(3;B>~V!Yon}t?kpi_J#_}@sKCrK4uY_Xf`p7hv`XQ=8 zWNp{9H3nF%DY43p1+@_OnTmXtj z%WgVqwJ!5UnSrBy?rhLiXKT?d}y73{iOJdN@mhf#J?H_awxEp#WUbKF{0}s=woC6Y47);j* z8rB1{w*AVT>0NSmFtEae;*67g8T_nxO0c+ov@>{eu5n{@#RGTr>^Bb8=wBEbB;0`7 zz|!xSHUh-AuPL^G!?~=j#GR%GzgKr%icju#i74clZV*{+CP!VXw1lVu78LdOSdw{V z{4*;Lt7ier$fJSEz6+QygOA+}x_4ilo(2pO&gO2#M3YigPU!~HbZzFpPP(m(7_Dq( z6E$iYyBlF8m8$F1Cuz4}csC&yn=cM8WVgfaL&h75{Shd3)~!cR zCrAVcxl!YrKl=V^piF14E39&aLJVb9-eT+g2xImTQ%l7;}SHq_(LSbo^EM-HXXtZ0O zdW3nm2Xc86CsIwEsbP>@Q~2ojkx)cvw^BKDjB5;4cJZr2KyPiMdSz9LK~+wi4%NKr zbN2DsiY=l;nH8!iP250F?V2V~z(9!|pVCyX9mL_@_ zlcc-NP!BZ_1zEf>pRi=1_Kqh(3X+M9b?No%R8SQvDbofi&Fz$Vs(U!_CusVn+==X` z4cUNCy9%^!gq7dHZ(d7yf82(&o(5y7mF`*OIvT28jRocQywzcRqsbN4HuB~hLSmiP z1-e(k^;S23LfRT&ykT>g@~+hOx!lg!Sf~$2v?1w2ja>QgaJtM|?p@SM9&ls$0J<8;>A`IHQY5INUj<+t`aZ}v)4 zTMv2I_QwzEM=Wg(QohmrlBbJ|jcKc6rM(eJ>_{Ce7!j7Wl-87@z;z5`*K8^*wY?^P zXZWbVI~{|7l7A`bsQ034<(8h(+iSK&8}ijuX4p=^0dk;0zaKuYr~S&idu-;u+p3y# zh&LfPIM%YArf&^E-XlY^y8hl$%bp>Gi+MuNLb0pOLODZ47f-(U&F8UH%lFk)H3Pg8 zGX$RR8odn{YWkC>IU_o}?Bgs(hY9Wy8?sIR0}Vgrg%#6#9%R$r^539t@SnujcyONj zpE?(`U`-_m!Nt>6WU8?;PR;ou0f`wuvuj1xX4j}4+M{ZmBHI>~O54)>S3Z}=gNpD= z-B$ESnoSp)Ib~)v6o{j~ZKMpo4IJYIwwCY%v9+$k%2a=ut+ETf&f;R4JYriH_yjfh zcF16FMV7{Bm~xVwCmSeQ>{H^VpmBwKi?xX5tMS?s%PV;WKlk>RF2_ zaQ#KT_9dmokkCTOdHzpHF5DT*Q$Z=`2&Z8*iEw|IL>%}ep?*ArUV@HuU70}fr}vsu z7ct2;mYIn^8+D@M!HHQVZamDm4kufo_&Lv2PQ+;2qON&of3i4Z`6^WdW!GxVHw*o( z9RCu?86CO{>RZqmkKJi#IZw5A|C&P3R7~+e1O|KX>AO!{L~~2Q^j{VcJ?fn1_JtHu zo#68?Z;9QhCQ%>Wl+v*xbCBkOYksQ3ErxKmI#@o+=yEv*{noTagX`J);d!Sqs6~1- z_t3kU4AG&!bh}$vq8bSpCgNXZ%R$m zvOkBz6;t?`*dmP4KpQa6S(Tb1v2UM_yTrv=nIeEr4bEdkEf&tcKxgqz=0#_b6#}=d z<1+YBT8K_dgbVSiDuNBJv!Zzw;~H`1CnOI;NRH;M5O3aN0V4|fV%s{@tfO&#!{~vE zXkC?8J?SKAwT&lDA&ld*Yz*V@55gw}#xX07=)to%1He+@{4HiU*{$`=4_`dDSl!dE zrb@kaTRT7dc#5TRzxH}})^%cZIN6|2;?tLujjh6Ku4c*Pw+2LJ{e43$piypJ3@{zz z{ZyQ_eCg6H#lsA4@F@ubKQ?$Sr!)(1u-g0Y@!Y3D0$d`L8{h{xE*7}P)$8&a||XD*TfFRvL{%LTfbnlB1i z`xZ=4^3YZ0(&j19vpsX0>pdpp@?^hP1Lua|`g^OU4F@JZvt-JBeIhxTzTB`_7Ha(C zXpMKEgjelG#+Z1pH3QN?T{LaXLXs&7drY%!CjC6=jey#;hs!{-|i#z2tEed4Ti=&S3x@^6XZrGR|k} znjEuABs|D(T|wc}%1sHwoY(yB{a6Ys6`5RKt#YYI&kJ0bNGe4P*Uq9}0YZR`s>=o) z$^kQp3e)J59I>B@@PGAi_X6G%Sved~($wM_il`m%ViYFIyuN(JJ|msKAXrNRV#341 z1|2JQNES0Z;*5kT&$YHc%^PE`bnRw~uILz)Jn z)rtYuuV1r^>4a@XS-a!^ETgu|Hbj0rKjU`uCKq2mWUW!kEocyb*qm8%j`6#5FX;H5 zH}?G7Z?<6e>UQ1ZW!lOfGLsiJ6Cmv5nnJCrOjaP?lKh2^41eXWTy*hxjZKwSr_VJ}-~$&#D3 zzhiEKdrOMKKU0O4xvH7-t>i*p@I!2=k5-G?6tO+uraKwk8#JkfX*#Z{*%i}i_x~lXo^+A!ibrcM>WX|z89iEn| zyC2#BpijrGcW&p}+^3j>Wt$A*=Jrvh8ETLM8aKVsi0&;hlS@-###$Xy))F)OMv57; zZdh4t?c_)zrcUIaOVOUk1$;wMCE>D~-O=N0NFI9^e^C}x37OgGLo)!Q zl=io=P5JDB<$lI%4Y+J3XEphD`qO&Kd_8!yc<*ECCAvC#XTpXe+6u_cmTjEJ| znoqk>=_ZZ4uO5-(m)F08ceF!p<}!?TgW`7279=mKmj~~5tj;zg?PgUz-)5VMM%0j%)T?pU<0Uk|D3p5{2e??#5jMB{Y!BJEFH zuWNq7jM!7<2zWCvPQRj%cXAC#;y_}2ul?h8L$gjQfeIy;;;WXDudit7Uv|Z2b;SrX zfetgr<80WRG+xgFc;C!8+A#ako200^e2Q~AmM2ENwvrd`El^q3CVWk8#pR}l6cCg~ zUYS?4ylI87x!WdHAgi(~ry661S05Qi1wbZZh3H*x{Rw|u!|$*brVLWole{Fe)at#5 z&|6f+nmc3oc&?6vkxR;joiAOb9VuypZ0J$RUBbNxlH~&My}W2{rLRnL z_-^!!5*@@mLvLnIN0QiIhGHHqzPd<3m6&`Vvw8X{6CQBzCaG00F|!`5<-vmAC>~F}0=9+5g-X4W2>mQBUE2eh0%g|SqINm6Te;DOFibuJZ*{m1m-=$li zA>OF0B&aPG^YmL#sfV^T*RCPN%5N9BL>0$sDyvtimKQ1W9gBJ=5(@^odQd1zJ)8Lo(zG zeg;Iwc}daKZlFmS1a-tPNNEfJ99rixy+0qS+Sm5iq zL+jh*2DCx)TBOktKeP!XXqS-sX*+N5l;5o1VpaD@M%Pak^Vqbsa_Eo0WNcXh8i zafO?AZFRj;yl(n{r6|&IBA_<(2I?rB(2@jt?Fv>m#>YoLznm1vhc1`weTd-;OKNlU z7eAu`QWzX1>w@I0VgfW#HL`x)yyghsLOaU(#V{i%@fmXs*QfgI)M>KgCz&&%`=PNZ zPu+yGi`h*t8-5KMsj5_yxl+d&O}k-3yJGaH4TJX)ynmlzXsKl%oOgmmFTRO-s`ckV z&u!9meAquxYhwk+gHo^`Q|*lIBH2K=|B*NDyfTf|*+wzNwSNZ2hkhakih?%7j(lPT zD;YT{1@b6F_gc~lu)m$%A9Eb*aK&Q@qrFOd-)-p{v7hkz2lg2jw=-pNt0yOAU(svi zLYL#99x*+EkqXq&U$tR)E{^73j>i*upyP+bN9CfUhi~MgD<%5{I+<#AWsg?a)U-af z&|(T&_pI1K{XL`TB94{Ou)PPi5Y+MbOb^}#nvWufpZWaDcRLGjsu}h_miC|C;Ors| z=3G3ILzSiI!nCg+;$03@KDrVVI`VxANUQz+09hW z{~WkYa@aKYcKD$MeY0x*7Sec0vr5BAj`1Ov&~s(J`O2>w{g%{Jq-lIT_L=68?J+E* zGGTu~fpOk97y&7_Diw3aL;G8#ku@_Hyb)LWa$+&s zEF~rPhKO&PraSlge{A(pz0+TTl9mN_uDi-)@vS9E8zK$1amRo!FM&6Ys)yQdvVSt? zd&vc0p2sNLeK7sJ7^QO9Xkp(Tm$9A!ml{~8K2#1711%(JGl8Eh9QYUDKEx@cv!JHg)>??HhpzbPA3DM&~U< ze~Rf!mHiBTPgT>F;L?v|Ymp&(l9!ZA&Mt9(uv}|zk8-{XfKyu7vYP#;ao1qBoecXG zs7P|7#x6hY;x|`wfR2^)K5ub~0ncUzK+Ybe)UnPC7iajN`lE-k73KK}UD zKzHTYGesC!j*8N598|aVJHKu;Qd&wK$pOh<2p%XS*W6`g#nH`{4mC<`Tm8tWUzn}AWi3+;%dy%2o{JaR5Qy)!>H z%gz0!Cx`4fqYzD`j6j=|L6X8+kHP1A*E0lNx2(ItObT73J3_eKE@=MB4=jMRRrw62 zG<8C+vWR^_5OLT~3Brb~kl1OQ5_pGlWb@Ulbtbkbg~d5y_X_mvTrZdJ`R2u?sF<7U zZv~d(&CJ-A72TvW_u`}1Z=|JAbP7kMUj`&-f$L>F7R;6ggDkC*jsf|P&oalP8U8fK zT_2wdY0JFNakO#`swMjx zM!cT4Z}M9M_60r_9>16xcaX^`A9gqPZ`l_3nb%}8T`Chs482ZkvJhPcGX?jMR}=ah zTZDVQSSASC6SiqO@{GT!Qk?JszB*o9FY#TP6Dko7-f4$6V16IQQ`bDNN^kJC2IR;t zY?SB&z67>8I0W=}iwTS;u3x6J_59+L8+<7^p24|fLiU+*HlGuF3@?Ppk+A-3MnmFl z)qZ;$wA_$w?+0srI|;Kh_%r5`bfl_d$kA>k$+avzku2rs<@<_TvP^;(tTuzj zhE_CzlafJ^=I2x-PY=Nl5R<=t%`qL1pvH4;}21B9;( zkl_bYZ2+YII)|5v`(DLhC^8SK&@Rg;W2>Er#Wa&~W~5#GeHRr{N`OC4&x8mdeH^(Z zSo~{uE-6NJ{V*qLT*hB@@O-Qm!r>wH*J1pN8Ht>Ri`CHLtL;2>NxDqFb41bk*1z+J zhV>B-vfA2MMCt)_#) z3G~quaUUm>*(ov1gX?+|@8-u$!zgCPz9kxLJH$2OO{(l${;)=ie$@*MH+Dtp83U5!%o~k zPQ8KRJ141&WM*HM=`hd+PDS93YX&}Sllg@j-BHpM?!v8!WeV^^4DX@GQ`sea*>H?=b|NHgB}D2V9jt) zJ=prm-}$6M+ZsPel4vwOBmuhqij3Ujz<~(=Z+%`0#*Vm+M8&7Up%ajiBU{{m!_%D9 z1zJjlE#0`HNju{ds8|+m7h{Hj5#iNXfrHNd}8lmEE zQSW{7z*8sq+W$*S6LniEU?Z!#B?GdWkjUeg4$&N$;$N7gqx*-E<^6-zhv(0nSsJz2 UWxWXg`G1#+f~I_}taaG`2PLnS&Hw-a diff --git a/code-samples/community/serving/helloworld-elixir/assets/static/robots.txt b/code-samples/community/serving/helloworld-elixir/assets/static/robots.txt deleted file mode 100644 index 3c9c7c01f3..0000000000 --- a/code-samples/community/serving/helloworld-elixir/assets/static/robots.txt +++ /dev/null @@ -1,5 +0,0 @@ -# See http://www.robotstxt.org/robotstxt.html for documentation on how to use the robots.txt file -# -# To ban all spiders from the entire site uncomment the next two lines: -# User-agent: * -# Disallow: / diff --git a/code-samples/community/serving/helloworld-elixir/ci.sh b/code-samples/community/serving/helloworld-elixir/ci.sh new file mode 100755 index 0000000000..c13959f3b2 --- /dev/null +++ b/code-samples/community/serving/helloworld-elixir/ci.sh @@ -0,0 +1,42 @@ +#!/usr/bin/env bash +# Source from this sample directory; the caller owns cluster setup and ingress forwarding. +IMAGE=dev.local/helloworld-elixir:ci +SAMPLE_URL=http://127.0.0.1:18080 + +main() ( + set -euo pipefail + build + deploy + test +) + +build() ( + set -euo pipefail + docker build -t "$IMAGE" . +) + +deploy() ( + set -euo pipefail + kind load docker-image "$IMAGE" + kubectl patch --local -f service.yaml --type=json -p "$( + jq -cn --arg image "$IMAGE" '[ + {op: "replace", path: "/spec/template/spec/containers/0/image", value: $image}, + {op: "add", path: "/spec/template/spec/containers/0/imagePullPolicy", value: "IfNotPresent"} + ]' + )" -o yaml | kubectl --namespace default apply -f - + kubectl --namespace default wait -f service.yaml --for=condition=Ready --timeout=300s +) + +test() ( + set -euo pipefail + local service_url service_host + service_url=$(kubectl --namespace default get -f service.yaml -o jsonpath='{.status.url}') + service_host=${service_url#*://} + [[ "$service_host" =~ ^[a-zA-Z0-9.-]+$ ]] + local body + body=$(curl -fsS --max-time 30 \ + -H "Host: $service_host" "$SAMPLE_URL/") + # The HTML contains the literal label $TARGET, not a shell variable. + # shellcheck disable=SC2016 + [[ $body == *'

$TARGET = elixir Sample v1

'* ]] +) diff --git a/code-samples/community/serving/helloworld-elixir/config/config.exs b/code-samples/community/serving/helloworld-elixir/config/config.exs index ab1e7af70a..68b933032d 100644 --- a/code-samples/community/serving/helloworld-elixir/config/config.exs +++ b/code-samples/community/serving/helloworld-elixir/config/config.exs @@ -1,23 +1,14 @@ -# This file is responsible for configuring your application -# and its dependencies with the aid of the Mix.Config module. -# -# This configuration file is loaded before any dependency and -# is restricted to this project. -use Mix.Config +import Config -# Configures the endpoint config :hello, HelloWeb.Endpoint, url: [host: "localhost"], - secret_key_base: "P0fv0U47j6mdL40sn3f3BIuvEyIdbxUq+yKc8Mcbwig5105brf2Dzio5ANjTVRUo", - render_errors: [view: HelloWeb.ErrorView, accepts: ~w(html json)] - #pubsub: [name: Hello.PubSub, - # adapter: Phoenix.PubSub.PG2] + adapter: Bandit.PhoenixAdapter, + render_errors: [formats: [html: HelloWeb.ErrorView], layout: false], + pubsub_server: Hello.PubSub, + # Demonstration signing key only; change this for production. + secret_key_base: "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef" -# Configures Elixir's Logger -config :logger, :console, - format: "$time $metadata[$level] $message\n", - metadata: [:user_id] +config :phoenix, :json_library, Jason +config :logger, :default_formatter, format: "$time $metadata[$level] $message\n" -# Import environment specific config. This must remain at the bottom -# of this file so it overrides the configuration defined above. -import_config "#{Mix.env}.exs" +import_config "#{config_env()}.exs" diff --git a/code-samples/community/serving/helloworld-elixir/config/dev.exs b/code-samples/community/serving/helloworld-elixir/config/dev.exs index 4e166ca92a..8316b75850 100644 --- a/code-samples/community/serving/helloworld-elixir/config/dev.exs +++ b/code-samples/community/serving/helloworld-elixir/config/dev.exs @@ -1,49 +1,8 @@ -use Mix.Config +import Config -# For development, we disable any cache and enable -# debugging and code reloading. -# -# The watchers configuration can be used to run external -# watchers to your application. For example, we use it -# with brunch.io to recompile .js and .css sources. config :hello, HelloWeb.Endpoint, - http: [port: 4000], + http: [ip: {0, 0, 0, 0}, port: 4000], debug_errors: true, - code_reloader: true, - check_origin: false, - watchers: [node: ["node_modules/brunch/bin/brunch", "watch", "--stdin", - cd: Path.expand("../assets", __DIR__)]] + check_origin: false -# ## SSL Support -# -# In order to use HTTPS in development, a self-signed -# certificate can be generated by running the following -# command from your terminal: -# -# openssl req -new -newkey rsa:4096 -days 365 -nodes -x509 -subj "/C=US/ST=Denial/L=Springfield/O=Dis/CN=www.example.com" -keyout priv/server.key -out priv/server.pem -# -# The `http:` config above can be replaced with: -# -# https: [port: 4000, keyfile: "priv/server.key", certfile: "priv/server.pem"], -# -# If desired, both `http:` and `https:` keys can be -# configured to run both http and https servers on -# different ports. - -# Watch static and templates for browser reloading. -config :hello, HelloWeb.Endpoint, - live_reload: [ - patterns: [ - ~r{priv/static/.*(js|css|png|jpeg|jpg|gif|svg)$}, - ~r{priv/gettext/.*(po)$}, - ~r{lib/hello_web/views/.*(ex)$}, - ~r{lib/hello_web/templates/.*(eex)$} - ] - ] - -# Do not include metadata nor timestamps in development logs -config :logger, :console, format: "[$level] $message\n" - -# Set a higher stacktrace during development. Avoid configuring such -# in production as building large stacktraces may be expensive. -config :phoenix, :stacktrace_depth, 20 +config :logger, :default_formatter, format: "[$level] $message\n" diff --git a/code-samples/community/serving/helloworld-elixir/config/prod.exs b/code-samples/community/serving/helloworld-elixir/config/prod.exs index 3ee2c7d5c0..215d07fca9 100644 --- a/code-samples/community/serving/helloworld-elixir/config/prod.exs +++ b/code-samples/community/serving/helloworld-elixir/config/prod.exs @@ -1,67 +1,5 @@ -use Mix.Config +import Config -# For production, we often load configuration from external -# sources, such as your system environment. For this reason, -# you won't find the :http configuration below, but set inside -# HelloWeb.Endpoint.init/2 when load_from_system_env is -# true. Any dynamic configuration should be done there. -# -# Don't forget to configure the url host to something meaningful, -# Phoenix uses this information when generating URLs. -# -# Finally, we also include the path to a cache manifest -# containing the digested version of static files. This -# manifest is generated by the mix phx.digest task -# which you typically run after static files are built. -config :hello, HelloWeb.Endpoint, - load_from_system_env: false, - http: [port: 8080], - check_origin: false, - server: true, - root: ".", - cache_static_manifest: "priv/static/cache_manifest.json" +config :hello, HelloWeb.Endpoint, cache_static_manifest: "priv/static/cache_manifest.json" -# Do not print debug messages in production config :logger, level: :info - -# ## SSL Support -# -# To get SSL working, you will need to add the `https` key -# to the previous section and set your `:url` port to 443: -# -# config :hello, HelloWeb.Endpoint, -# ... -# url: [host: "example.com", port: 443], -# https: [:inet6, -# port: 443, -# keyfile: System.get_env("SOME_APP_SSL_KEY_PATH"), -# certfile: System.get_env("SOME_APP_SSL_CERT_PATH")] -# -# Where those two env variables return an absolute path to -# the key and cert in disk or a relative path inside priv, -# for example "priv/ssl/server.key". -# -# We also recommend setting `force_ssl`, ensuring no data is -# ever sent via http, always redirecting to https: -# -# config :hello, HelloWeb.Endpoint, -# force_ssl: [hsts: true] -# -# Check `Plug.SSL` for all available options in `force_ssl`. - -# ## Using releases -# -# If you are doing OTP releases, you need to instruct Phoenix -# to start the server for all endpoints: -# -# config :phoenix, :serve_endpoints, true -# -# Alternatively, you can configure exactly which server to -# start per endpoint: -# -# config :hello, HelloWeb.Endpoint, server: true -# - -# Finally import the config/prod.secret.exs -# which should be versioned separately. -import_config "prod.secret.exs" diff --git a/code-samples/community/serving/helloworld-elixir/config/prod.secret.exs.sample b/code-samples/community/serving/helloworld-elixir/config/prod.secret.exs.sample deleted file mode 100644 index 1092ddd7d0..0000000000 --- a/code-samples/community/serving/helloworld-elixir/config/prod.secret.exs.sample +++ /dev/null @@ -1,12 +0,0 @@ -use Mix.Config - -# In this file, we keep production configuration that -# you'll likely want to automate and keep away from -# your version control system. -# -# You should document the content of this -# file or create a script for recreating it, since it's -# kept out of version control and might be hard to recover -# or recreate for your teammates (or yourself later on). -config :hello, HelloWeb.Endpoint, - secret_key_base: "SECRET+KEY+BASE" diff --git a/code-samples/community/serving/helloworld-elixir/config/runtime.exs b/code-samples/community/serving/helloworld-elixir/config/runtime.exs new file mode 100644 index 0000000000..6960bdd121 --- /dev/null +++ b/code-samples/community/serving/helloworld-elixir/config/runtime.exs @@ -0,0 +1,13 @@ +import Config + +if config_env() == :prod do + port = String.to_integer(System.get_env("PORT") || "8080") + + unless port in 1..65_535 do + raise "PORT must be between 1 and 65535" + end + + config :hello, HelloWeb.Endpoint, + server: true, + http: [ip: {0, 0, 0, 0}, port: port] +end diff --git a/code-samples/community/serving/helloworld-elixir/config/test.exs b/code-samples/community/serving/helloworld-elixir/config/test.exs deleted file mode 100644 index 5ca3a7e469..0000000000 --- a/code-samples/community/serving/helloworld-elixir/config/test.exs +++ /dev/null @@ -1,10 +0,0 @@ -use Mix.Config - -# We don't run a server during test. If one is required, -# you can enable the server option below. -config :hello, HelloWeb.Endpoint, - http: [port: 4001], - server: false - -# Print only warnings and errors during test -config :logger, level: :warn diff --git a/code-samples/community/serving/helloworld-elixir/lib/hello/application.ex b/code-samples/community/serving/helloworld-elixir/lib/hello/application.ex index 736b48637f..502ca860d8 100644 --- a/code-samples/community/serving/helloworld-elixir/lib/hello/application.ex +++ b/code-samples/community/serving/helloworld-elixir/lib/hello/application.ex @@ -1,30 +1,18 @@ defmodule Hello.Application do use Application - # See https://hexdocs.pm/elixir/Application.html - # for more information on OTP Applications + @impl true def start(_type, _args) do - IO.puts :stderr, "Application starting up" - import Supervisor.Spec - - # Define workers and child supervisors to be supervised children = [ - # Start the endpoint when the application starts - supervisor(HelloWeb.Endpoint, []), - # Start your own worker by calling: Hello.Worker.start_link(arg1, arg2, arg3) - # worker(Hello.Worker, [arg1, arg2, arg3]), + {Phoenix.PubSub, name: Hello.PubSub}, + HelloWeb.Endpoint ] - # See https://hexdocs.pm/elixir/Supervisor.html - # for other strategies and supported options - opts = [strategy: :one_for_one, name: Hello.Supervisor] - Supervisor.start_link(children, opts) + Supervisor.start_link(children, strategy: :one_for_one, name: Hello.Supervisor) end - # Tell Phoenix to update the endpoint configuration - # whenever the application is updated. + @impl true def config_change(changed, _new, removed) do - IO.puts :stderr, "Config changed" HelloWeb.Endpoint.config_change(changed, removed) :ok end diff --git a/code-samples/community/serving/helloworld-elixir/lib/hello_web.ex b/code-samples/community/serving/helloworld-elixir/lib/hello_web.ex index 4573268fbb..8c338c1fee 100644 --- a/code-samples/community/serving/helloworld-elixir/lib/hello_web.ex +++ b/code-samples/community/serving/helloworld-elixir/lib/hello_web.ex @@ -1,45 +1,24 @@ defmodule HelloWeb do - @moduledoc """ - The entrypoint for defining your web interface, such - as controllers, views, channels and so on. - - This can be used in your application as: - - use HelloWeb, :controller - use HelloWeb, :view - - The definitions below will be executed for every view, - controller, etc, so keep them short and clean, focused - on imports, uses and aliases. - - Do NOT define functions inside the quoted expressions - below. Instead, define any helper function in modules - and import those modules here. - """ + @moduledoc "Shared helpers for this small Phoenix application." def controller do quote do - use Phoenix.Controller, namespace: HelloWeb + use Phoenix.Controller, + formats: [:html], + layouts: [html: {HelloWeb.LayoutView, :app}] + import Plug.Conn - import HelloWeb.Router.Helpers - import HelloWeb.Gettext end end def view do quote do - use Phoenix.View, root: "lib/hello_web/templates", - namespace: HelloWeb - - # Import convenience functions from controllers - import Phoenix.Controller, only: [get_flash: 2, view_module: 1] + use Phoenix.View, + root: "lib/hello_web/templates", + namespace: HelloWeb - # Use all HTML functionality (forms, tags, etc) - use Phoenix.HTML - - import HelloWeb.Router.Helpers - import HelloWeb.ErrorHelpers - import HelloWeb.Gettext + import Phoenix.HTML + import Phoenix.VerifiedRoutes, only: [static_path: 2] end end @@ -51,17 +30,5 @@ defmodule HelloWeb do end end - def channel do - quote do - use Phoenix.Channel - import HelloWeb.Gettext - end - end - - @doc """ - When used, dispatch to the appropriate controller/view/etc. - """ - defmacro __using__(which) when is_atom(which) do - apply(__MODULE__, which, []) - end + defmacro __using__(which) when is_atom(which), do: apply(__MODULE__, which, []) end diff --git a/code-samples/community/serving/helloworld-elixir/lib/hello_web/channels/user_socket.ex b/code-samples/community/serving/helloworld-elixir/lib/hello_web/channels/user_socket.ex deleted file mode 100644 index 4e7f208138..0000000000 --- a/code-samples/community/serving/helloworld-elixir/lib/hello_web/channels/user_socket.ex +++ /dev/null @@ -1,38 +0,0 @@ -defmodule HelloWeb.UserSocket do - use Phoenix.Socket - - ## Channels - # channel "room:*", HelloWeb.RoomChannel - - ## Transports - transport :longpoll, Phoenix.Transports.LongPoll - # transport :longpoll, Phoenix.Transports.LongPoll - - # Socket params are passed from the client and can - # be used to verify and authenticate a user. After - # verification, you can put default assigns into - # the socket that will be set for all channels, ie - # - # {:ok, assign(socket, :user_id, verified_user_id)} - # - # To deny connection, return `:error`. - # - # See `Phoenix.Token` documentation for examples in - # performing token verification on connect. - def connect(_params, socket) do - IO.puts :stderr, "UserSocket.connect called" - {:ok, socket} - end - - # Socket id's are topics that allow you to identify all sockets for a given user: - # - # def id(socket), do: "user_socket:#{socket.assigns.user_id}" - # - # Would allow you to broadcast a "disconnect" event and terminate - # all active sockets and channels for a given user: - # - # HelloWeb.Endpoint.broadcast("user_socket:#{user.id}", "disconnect", %{}) - # - # Returning `nil` makes this socket anonymous. - def id(_socket), do: nil -end diff --git a/code-samples/community/serving/helloworld-elixir/lib/hello_web/controllers/page_controller.ex b/code-samples/community/serving/helloworld-elixir/lib/hello_web/controllers/page_controller.ex index 7eda062a91..2f02b7c706 100644 --- a/code-samples/community/serving/helloworld-elixir/lib/hello_web/controllers/page_controller.ex +++ b/code-samples/community/serving/helloworld-elixir/lib/hello_web/controllers/page_controller.ex @@ -1,14 +1,19 @@ defmodule HelloWeb.PageController do use HelloWeb, :controller - def index(conn, params) do + # Never echo runtime secrets, release cookies, or arbitrary credentials. + @visible_environment ~w(TARGET PORT K_SERVICE K_REVISION K_CONFIGURATION) + + def index(conn, _params) do env = System.get_env() - target = Map.get(env, "TARGET") - render conn, "index.html", - title: "Hello Knative", - greeting: "Welcome to Knative and Elixir", - target: target, - env: env + conn + |> put_view(HelloWeb.PageView) + |> render("index.html", + title: "Hello Knative", + greeting: "Welcome to Knative and Elixir", + target: Map.get(env, "TARGET"), + env: Map.take(env, @visible_environment) + ) end end diff --git a/code-samples/community/serving/helloworld-elixir/lib/hello_web/endpoint.ex b/code-samples/community/serving/helloworld-elixir/lib/hello_web/endpoint.ex index a3cc10adb7..bdfdd9a225 100644 --- a/code-samples/community/serving/helloworld-elixir/lib/hello_web/endpoint.ex +++ b/code-samples/community/serving/helloworld-elixir/lib/hello_web/endpoint.ex @@ -1,57 +1,22 @@ defmodule HelloWeb.Endpoint do use Phoenix.Endpoint, otp_app: :hello - socket "/socket", HelloWeb.UserSocket - - # Serve at "/" the static files from "priv/static" directory. - # - # You should set gzip to true if you are running phoenix.digest - # when deploying your static files in production. - plug Plug.Static, - at: "/", from: :hello, gzip: false, - only: ~w(css fonts images js favicon.ico robots.txt) - - # Code reloading can be explicitly enabled under the - # :code_reloader configuration of your endpoint. - if code_reloading? do - socket "/phoenix/live_reload/socket", Phoenix.LiveReloader.Socket - plug Phoenix.LiveReloader - plug Phoenix.CodeReloader - end - - plug Plug.Logger - - plug Plug.Parsers, - parsers: [:urlencoded, :multipart, :json], - pass: ["*/*"], - json_decoder: Poison - - plug Plug.MethodOverride - plug Plug.Head - - # The session will be stored in the cookie and signed, - # this means its contents can be read but not tampered with. - # Set :encryption_salt if you would also like to encrypt it. - plug Plug.Session, + plug(Plug.Static, + at: "/", + from: :hello, + gzip: true, + only: ~w(css favicon.ico robots.txt) + ) + + plug(Plug.RequestId) + plug(Plug.Telemetry, event_prefix: [:phoenix, :endpoint]) + plug(Plug.Head) + + plug(Plug.Session, store: :cookie, key: "_hello_key", signing_salt: "M0cGJtXU" + ) - plug HelloWeb.Router - - @doc """ - Callback invoked for dynamically configuring the endpoint. - - It receives the endpoint configuration and checks if - configuration should be loaded from the system environment. - """ - def init(_key, config) do - IO.puts :stderr, "called HelloWeb.Endpoint.init" - if config[:load_from_system_env] do - port = System.get_env("PORT") || raise "expected the PORT environment variable to be set" - {:ok, Keyword.put(config, :http, [:inet6, port: port])} - else - {:ok, config} - end - end + plug(HelloWeb.Router) end diff --git a/code-samples/community/serving/helloworld-elixir/lib/hello_web/gettext.ex b/code-samples/community/serving/helloworld-elixir/lib/hello_web/gettext.ex deleted file mode 100644 index bf7a9325dc..0000000000 --- a/code-samples/community/serving/helloworld-elixir/lib/hello_web/gettext.ex +++ /dev/null @@ -1,24 +0,0 @@ -defmodule HelloWeb.Gettext do - @moduledoc """ - A module providing Internationalization with a gettext-based API. - - By using [Gettext](https://hexdocs.pm/gettext), - your module gains a set of macros for translations, for example: - - import HelloWeb.Gettext - - # Simple translation - gettext "Here is the string to translate" - - # Plural translation - ngettext "Here is the string to translate", - "Here are the strings to translate", - 3 - - # Domain-based translation - dgettext "errors", "Here is the error message to translate" - - See the [Gettext Docs](https://hexdocs.pm/gettext) for detailed usage. - """ - use Gettext, otp_app: :hello -end diff --git a/code-samples/community/serving/helloworld-elixir/lib/hello_web/router.ex b/code-samples/community/serving/helloworld-elixir/lib/hello_web/router.ex index 2b074823cf..4e47fdfc80 100644 --- a/code-samples/community/serving/helloworld-elixir/lib/hello_web/router.ex +++ b/code-samples/community/serving/helloworld-elixir/lib/hello_web/router.ex @@ -2,22 +2,22 @@ defmodule HelloWeb.Router do use HelloWeb, :router pipeline :browser do - plug :accepts, ["html"] - plug :fetch_session - plug :fetch_flash - plug :protect_from_forgery - plug :put_secure_browser_headers + plug(:accepts, ["html"]) + plug(:fetch_session) + plug(:fetch_flash) + plug(:protect_from_forgery) + plug(:put_secure_browser_headers) end pipeline :api do - plug :accepts, ["json"] + plug(:accepts, ["json"]) end scope "/", HelloWeb do - pipe_through :browser # Use the default browser stack + # Use the default browser stack + pipe_through(:browser) - get "/", PageController, :index - get "/:name", PageController, :show + get("/", PageController, :index) end # Other scopes may use custom stacks. diff --git a/code-samples/community/serving/helloworld-elixir/lib/hello_web/templates/layout/app.html.eex b/code-samples/community/serving/helloworld-elixir/lib/hello_web/templates/layout/app.html.eex index a3592f3839..252c71b7a9 100644 --- a/code-samples/community/serving/helloworld-elixir/lib/hello_web/templates/layout/app.html.eex +++ b/code-samples/community/serving/helloworld-elixir/lib/hello_web/templates/layout/app.html.eex @@ -2,31 +2,13 @@ - - - - <%= @title %> - "> + "> - -
-
- -
- - - - -
- <%= render @view_module, @view_template, assigns %> -
- -
- +
+ <%= @inner_content %> +
diff --git a/code-samples/community/serving/helloworld-elixir/lib/hello_web/views/error_helpers.ex b/code-samples/community/serving/helloworld-elixir/lib/hello_web/views/error_helpers.ex deleted file mode 100644 index fa06eae031..0000000000 --- a/code-samples/community/serving/helloworld-elixir/lib/hello_web/views/error_helpers.ex +++ /dev/null @@ -1,44 +0,0 @@ -defmodule HelloWeb.ErrorHelpers do - @moduledoc """ - Conveniences for translating and building error messages. - """ - - use Phoenix.HTML - - @doc """ - Generates tag for inlined form input errors. - """ - def error_tag(form, field) do - Enum.map(Keyword.get_values(form.errors, field), fn (error) -> - content_tag :span, translate_error(error), class: "help-block" - end) - end - - @doc """ - Translates an error message using gettext. - """ - def translate_error({msg, opts}) do - # When using gettext, we typically pass the strings we want - # to translate as a static argument: - # - # # Translate "is invalid" in the "errors" domain - # dgettext "errors", "is invalid" - # - # # Translate the number of files with plural rules - # dngettext "errors", "1 file", "%{count} files", count - # - # Because the error messages we show in our forms and APIs - # are defined inside Ecto, we need to translate them dynamically. - # This requires us to call the Gettext module passing our gettext - # backend as first argument. - # - # Note we use the "errors" domain, which means translations - # should be written to the errors.po file. The :count option is - # set by Ecto and indicates we should also apply plural rules. - if count = opts[:count] do - Gettext.dngettext(HelloWeb.Gettext, "errors", msg, msg, count, opts) - else - Gettext.dgettext(HelloWeb.Gettext, "errors", msg, opts) - end - end -end diff --git a/code-samples/community/serving/helloworld-elixir/lib/hello_web/views/error_view.ex b/code-samples/community/serving/helloworld-elixir/lib/hello_web/views/error_view.ex index 90f183947f..f9c11647a5 100644 --- a/code-samples/community/serving/helloworld-elixir/lib/hello_web/views/error_view.ex +++ b/code-samples/community/serving/helloworld-elixir/lib/hello_web/views/error_view.ex @@ -1,16 +1,10 @@ defmodule HelloWeb.ErrorView do use HelloWeb, :view - # If you want to customize a particular status code - # for a certain format, you may uncomment below. - # def render("500.html", _assigns) do - # "Internal Server Error" - # end - - # By default, Phoenix returns the status message from - # the template name. For example, "404.html" becomes - # "Not Found". - def template_not_found(template, _assigns) do - Phoenix.Controller.status_message_from_template(template) + def render(template, _assigns) do + case Integer.parse(template) do + {status, _format} -> Plug.Conn.Status.reason_phrase(status) + :error -> "Internal Server Error" + end end end diff --git a/code-samples/community/serving/helloworld-elixir/mix.exs b/code-samples/community/serving/helloworld-elixir/mix.exs index 6127c4c806..802af4915a 100644 --- a/code-samples/community/serving/helloworld-elixir/mix.exs +++ b/code-samples/community/serving/helloworld-elixir/mix.exs @@ -1,44 +1,27 @@ -defmodule Hello.Mixfile do +defmodule Hello.MixProject do use Mix.Project def project do [ app: :hello, - version: "0.0.1", - elixir: "~> 1.4", - elixirc_paths: elixirc_paths(Mix.env), - compilers: [:phoenix, :gettext] ++ Mix.compilers, - start_permanent: Mix.env == :prod, + version: "0.1.0", + elixir: "~> 1.20", + start_permanent: Mix.env() == :prod, deps: deps() ] end - # Configuration for the OTP application. - # - # Type `mix help compile.app` for more information. def application do - [ - mod: {Hello.Application, []}, - extra_applications: [:logger, :runtime_tools] - ] + [mod: {Hello.Application, []}, extra_applications: [:logger, :runtime_tools]] end - # Specifies which paths to compile per environment. - defp elixirc_paths(:test), do: ["lib", "test/support"] - defp elixirc_paths(_), do: ["lib"] - - # Specifies your project dependencies. - # - # Type `mix help deps` for examples and options. defp deps do [ - {:phoenix, "~> 1.3.2"}, - {:phoenix_pubsub, "~> 1.0"}, - {:phoenix_html, "~> 2.10"}, - {:phoenix_live_reload, "~> 1.0", only: :dev}, - {:gettext, "~> 0.11"}, - {:cowboy, "~> 1.0"}, - {:distillery, "~> 1.5"} + {:phoenix, "~> 1.8.16"}, + {:phoenix_html, "~> 4.3"}, + {:phoenix_view, "~> 2.0"}, + {:bandit, "~> 1.13"}, + {:jason, "~> 1.4"} ] end end diff --git a/code-samples/community/serving/helloworld-elixir/mix.lock b/code-samples/community/serving/helloworld-elixir/mix.lock index f87e0b7e07..afaf1c5f05 100644 --- a/code-samples/community/serving/helloworld-elixir/mix.lock +++ b/code-samples/community/serving/helloworld-elixir/mix.lock @@ -1,15 +1,17 @@ %{ - "cowboy": {:hex, :cowboy, "1.1.2", "61ac29ea970389a88eca5a65601460162d370a70018afe6f949a29dca91f3bb0", [:rebar3], [{:cowlib, "~> 1.0.2", [hex: :cowlib, repo: "hexpm", optional: false]}, {:ranch, "~> 1.3.2", [hex: :ranch, repo: "hexpm", optional: false]}], "hexpm"}, - "cowlib": {:hex, :cowlib, "1.0.2", "9d769a1d062c9c3ac753096f868ca121e2730b9a377de23dec0f7e08b1df84ee", [:make], [], "hexpm"}, - "distillery": {:hex, :distillery, "1.5.2", "eec18b2d37b55b0bcb670cf2bcf64228ed38ce8b046bb30a9b636a6f5a4c0080", [:mix], [], "hexpm"}, - "file_system": {:hex, :file_system, "0.2.5", "a3060f063b116daf56c044c273f65202e36f75ec42e678dc10653056d3366054", [:mix], [], "hexpm"}, - "gettext": {:hex, :gettext, "0.15.0", "40a2b8ce33a80ced7727e36768499fc9286881c43ebafccae6bab731e2b2b8ce", [:mix], [], "hexpm"}, - "mime": {:hex, :mime, "1.3.0", "5e8d45a39e95c650900d03f897fbf99ae04f60ab1daa4a34c7a20a5151b7a5fe", [:mix], [], "hexpm"}, - "phoenix": {:hex, :phoenix, "1.3.2", "2a00d751f51670ea6bc3f2ba4e6eb27ecb8a2c71e7978d9cd3e5de5ccf7378bd", [:mix], [{:cowboy, "~> 1.0", [hex: :cowboy, repo: "hexpm", optional: true]}, {:phoenix_pubsub, "~> 1.0", [hex: :phoenix_pubsub, repo: "hexpm", optional: false]}, {:plug, "~> 1.3.3 or ~> 1.4", [hex: :plug, repo: "hexpm", optional: false]}, {:poison, "~> 2.2 or ~> 3.0", [hex: :poison, repo: "hexpm", optional: false]}], "hexpm"}, - "phoenix_html": {:hex, :phoenix_html, "2.11.2", "86ebd768258ba60a27f5578bec83095bdb93485d646fc4111db8844c316602d6", [:mix], [{:plug, "~> 1.5", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm"}, - "phoenix_live_reload": {:hex, :phoenix_live_reload, "1.1.5", "8d4c9b1ef9ca82deee6deb5a038d6d8d7b34b9bb909d99784a49332e0d15b3dc", [:mix], [{:file_system, "~> 0.2.1 or ~> 0.3", [hex: :file_system, repo: "hexpm", optional: false]}, {:phoenix, "~> 1.0 or ~> 1.2 or ~> 1.3", [hex: :phoenix, repo: "hexpm", optional: false]}], "hexpm"}, - "phoenix_pubsub": {:hex, :phoenix_pubsub, "1.0.2", "bfa7fd52788b5eaa09cb51ff9fcad1d9edfeb68251add458523f839392f034c1", [:mix], [], "hexpm"}, - "plug": {:hex, :plug, "1.5.1", "1ff35bdecfb616f1a2b1c935ab5e4c47303f866cb929d2a76f0541e553a58165", [:mix], [{:cowboy, "~> 1.0.1 or ~> 1.1 or ~> 2.3", [hex: :cowboy, repo: "hexpm", optional: true]}, {:mime, "~> 1.0", [hex: :mime, repo: "hexpm", optional: false]}], "hexpm"}, - "poison": {:hex, :poison, "3.1.0", "d9eb636610e096f86f25d9a46f35a9facac35609a7591b3be3326e99a0484665", [:mix], [], "hexpm"}, - "ranch": {:hex, :ranch, "1.3.2", "e4965a144dc9fbe70e5c077c65e73c57165416a901bd02ea899cfd95aa890986", [:rebar3], [], "hexpm"}, + "bandit": {:hex, :bandit, "1.13.1", "1ce25a7beb750ed3fa2e905962f1f9a679cb585f1da0ae45637d06e385ce2eae", [:mix], [{:hpax, "~> 1.1", [hex: :hpax, repo: "hexpm", optional: false]}, {:plug, "~> 1.18", [hex: :plug, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}, {:thousand_island, "~> 1.6", [hex: :thousand_island, repo: "hexpm", optional: false]}, {:websock, "~> 0.5", [hex: :websock, repo: "hexpm", optional: false]}], "hexpm", "39028f0b5a21404661410a6c19275060a9822c9dcca70b7c3e4e4fa50b11da58"}, + "hpax": {:hex, :hpax, "1.1.0", "782931867cc23217c68fb5f68fe1a11f5e7544c7fda82c8a7019a5df5a4a1cdf", [:mix], [], "hexpm", "0b8d0f05832f55571d65ac720f79bf8994138ffbb133209dc4685eae0ad456a8"}, + "jason": {:hex, :jason, "1.4.5", "2e3a008590b0b8d7388c20293e9dcc9cf3e5d642fd2a114e4cbbb52e595d940a", [:mix], [{:decimal, "~> 1.0 or ~> 2.0 or ~> 3.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm", "b0c823996102bcd0239b3c2444eb00409b72f6a140c1950bc8b457d836b30684"}, + "mime": {:hex, :mime, "2.0.7", "b8d739037be7cd402aee1ba0306edfdef982687ee7e9859bee6198c1e7e2f128", [:mix], [], "hexpm", "6171188e399ee16023ffc5b76ce445eb6d9672e2e241d2df6050f3c771e80ccd"}, + "phoenix": {:hex, :phoenix, "1.8.16", "6a4742054740496bfabefc95253f978d8f595f07bb601039b7a18b384a873916", [:mix], [{:bandit, "~> 1.0", [hex: :bandit, repo: "hexpm", optional: true]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:phoenix_pubsub, "~> 2.4", [hex: :phoenix_pubsub, repo: "hexpm", optional: false]}, {:phoenix_template, "~> 1.0", [hex: :phoenix_template, repo: "hexpm", optional: false]}, {:phoenix_view, "~> 2.0", [hex: :phoenix_view, repo: "hexpm", optional: true]}, {:plug, "~> 1.14", [hex: :plug, repo: "hexpm", optional: false]}, {:plug_cowboy, "~> 2.7", [hex: :plug_cowboy, repo: "hexpm", optional: true]}, {:plug_crypto, "~> 2.2", [hex: :plug_crypto, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}, {:websock_adapter, "~> 0.5", [hex: :websock_adapter, repo: "hexpm", optional: false]}], "hexpm", "e7355c35889305bc539eb09dae2393abcbaf0f65f6874a3f0dcb5f0ec53737fe"}, + "phoenix_html": {:hex, :phoenix_html, "4.3.0", "d3577a5df4b6954cd7890c84d955c470b5310bb49647f0a114a6eeecc850f7ad", [:mix], [], "hexpm", "3eaa290a78bab0f075f791a46a981bbe769d94bc776869f4f3063a14f30497ad"}, + "phoenix_pubsub": {:hex, :phoenix_pubsub, "2.4.1", "2ff3e7ba8b432120989870997b9adaefb76e1f63805554ab0b9e948987981f4b", [:mix], [], "hexpm", "352fba7a87bf61395b6e97da66944ad9b1bfe5459e5981039f60d68270bd9700"}, + "phoenix_template": {:hex, :phoenix_template, "1.1.0", "b329582281b1e00e4dc664afd60ef5b489cbb356923caa3b1e00abe8651b9a18", [:mix], [{:phoenix_html, "~> 2.14.2 or ~> 3.0 or ~> 4.0", [hex: :phoenix_html, repo: "hexpm", optional: true]}], "hexpm", "eba70070de79b2c3501ef205a74a69f98ab352f3785aa15da9ed161f9fe0fd5d"}, + "phoenix_view": {:hex, :phoenix_view, "2.0.4", "b45c9d9cf15b3a1af5fb555c674b525391b6a1fe975f040fb4d913397b31abf4", [:mix], [{:phoenix_html, "~> 2.14.2 or ~> 3.0 or ~> 4.0", [hex: :phoenix_html, repo: "hexpm", optional: true]}, {:phoenix_template, "~> 1.0", [hex: :phoenix_template, repo: "hexpm", optional: false]}], "hexpm", "4e992022ce14f31fe57335db27a28154afcc94e9983266835bb3040243eb620b"}, + "plug": {:hex, :plug, "1.20.3", "56c480c633ec2ce10140e236e15233bf576e1d323887d7c96711bd02ab5160db", [:mix], [{:mime, "~> 1.0 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:plug_crypto, "~> 1.1.1 or ~> 1.2 or ~> 2.0", [hex: :plug_crypto, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4.3 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "be266aee1b8536ef6409d58cf39a3121319f0ec47cfa1b24024485aa0e76ad76"}, + "plug_crypto": {:hex, :plug_crypto, "2.2.1", "bd1d0358a2e2f5ffb818217e64c394f9d24ed191d862b7fa1bcce264ace2b8ea", [:mix], [], "hexpm", "4417ccbd8f85e627321cd07d2ab9c4b621f1fd883b9905af241f9b4b780b94b6"}, + "telemetry": {:hex, :telemetry, "1.4.2", "a0cb522801dffb1c49fe6e30561badffc7b6d0e180db1300df759faa22062855", [:rebar3], [], "hexpm", "928f6495066506077862c0d1646609eed891a4326bee3126ba54b60af61febb1"}, + "thousand_island": {:hex, :thousand_island, "1.6.0", "d6ad7c2220c96a270cc8ca1586ec4d8bde648a4a3a53f003ce093f41beec0288", [:mix], [{:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "4d4720470f091fc21a1311777c2ac66916c43621bffc068590c9f9bb3cd057a3"}, + "websock": {:hex, :websock, "0.5.3", "2f69a6ebe810328555b6fe5c831a851f485e303a7c8ce6c5f675abeb20ebdadc", [:mix], [], "hexpm", "6105453d7fac22c712ad66fab1d45abdf049868f253cf719b625151460b8b453"}, + "websock_adapter": {:hex, :websock_adapter, "0.6.0", "73db5ab8aaefd1a876a97ce3e6afc96562625de69ef17a4e04426e034849d0b8", [:mix], [{:bandit, ">= 0.6.0", [hex: :bandit, repo: "hexpm", optional: true]}, {:plug, "~> 1.14", [hex: :plug, repo: "hexpm", optional: false]}, {:plug_cowboy, "~> 2.6", [hex: :plug_cowboy, repo: "hexpm", optional: true]}, {:websock, "~> 0.5", [hex: :websock, repo: "hexpm", optional: false]}], "hexpm", "50021a85bce8f203b086705d9e0c5415e2c7eb05d319111b0428fe71f9934617"}, } diff --git a/code-samples/community/serving/helloworld-elixir/priv/gettext/en/LC_MESSAGES/errors.po b/code-samples/community/serving/helloworld-elixir/priv/gettext/en/LC_MESSAGES/errors.po deleted file mode 100644 index cdec3a113c..0000000000 --- a/code-samples/community/serving/helloworld-elixir/priv/gettext/en/LC_MESSAGES/errors.po +++ /dev/null @@ -1,11 +0,0 @@ -## `msgid`s in this file come from POT (.pot) files. -## -## Do not add, change, or remove `msgid`s manually here as -## they're tied to the ones in the corresponding POT file -## (with the same domain). -## -## Use `mix gettext.extract --merge` or `mix gettext.merge` -## to merge POT files into PO files. -msgid "" -msgstr "" -"Language: en\n" diff --git a/code-samples/community/serving/helloworld-elixir/priv/gettext/errors.pot b/code-samples/community/serving/helloworld-elixir/priv/gettext/errors.pot deleted file mode 100644 index 6988141a62..0000000000 --- a/code-samples/community/serving/helloworld-elixir/priv/gettext/errors.pot +++ /dev/null @@ -1,10 +0,0 @@ -## This file is a PO Template file. -## -## `msgid`s here are often extracted from source code. -## Add new translations manually only if they're dynamic -## translations that can't be statically extracted. -## -## Run `mix gettext.extract` to bring this file up to -## date. Leave `msgstr`s empty as changing them here as no -## effect: edit them in PO (`.po`) files instead. - diff --git a/code-samples/community/serving/helloworld-elixir/priv/static/css/app.css b/code-samples/community/serving/helloworld-elixir/priv/static/css/app.css new file mode 100644 index 0000000000..166768c556 --- /dev/null +++ b/code-samples/community/serving/helloworld-elixir/priv/static/css/app.css @@ -0,0 +1,4 @@ +:root { color-scheme: light dark; font-family: system-ui, sans-serif; } +body { margin: 2rem auto; max-width: 60rem; padding: 0 1rem; } +main { line-height: 1.5; } +li { overflow-wrap: anywhere; } diff --git a/code-samples/community/serving/helloworld-elixir/rel/config.exs b/code-samples/community/serving/helloworld-elixir/rel/config.exs deleted file mode 100644 index 7452b3f5fd..0000000000 --- a/code-samples/community/serving/helloworld-elixir/rel/config.exs +++ /dev/null @@ -1,53 +0,0 @@ -# Import all plugins from `rel/plugins` -# They can then be used by adding `plugin MyPlugin` to -# either an environment, or release definition, where -# `MyPlugin` is the name of the plugin module. -Path.join(["rel", "plugins", "*.exs"]) -|> Path.wildcard() -|> Enum.map(&Code.eval_file(&1)) - -use Mix.Releases.Config, - # This sets the default release built by `mix release` - default_release: :default, - # This sets the default environment used by `mix release` - default_environment: Mix.env() - -# For a full list of config options for both releases -# and environments, visit https://hexdocs.pm/distillery/configuration.html - - -# You may define one or more environments in this file, -# an environment's settings will override those of a release -# when building in that environment, this combination of release -# and environment configuration is called a profile - -environment :dev do - # If you are running Phoenix, you should make sure that - # server: true is set and the code reloader is disabled, - # even in dev mode. - # It is recommended that you build with MIX_ENV=prod and pass - # the --env flag to Distillery explicitly if you want to use - # dev mode. - set dev_mode: true - set include_erts: false - set cookie: :"Bps5@RVvPgL9c~C~D(DCQ5*Iu! Date: Mon, 26 Oct 2026 09:16:03 -0400 Subject: [PATCH 05/44] Update the Haskell Hello World sample --- .../serving/helloworld-haskell/Dockerfile | 43 ++-- .../serving/helloworld-haskell/LICENSE | 201 +++++++++++++++ .../serving/helloworld-haskell/README.md | 242 +++++------------- .../serving/helloworld-haskell/app/Main.hs | 30 +-- .../serving/helloworld-haskell/cabal.project | 5 + .../helloworld-haskell/cabal.project.freeze | 178 +++++++++++++ .../serving/helloworld-haskell/ci.sh | 40 +++ .../helloworld-haskell.cabal | 20 ++ .../serving/helloworld-haskell/package.yaml | 15 -- .../serving/helloworld-haskell/stack.yaml | 5 - 10 files changed, 552 insertions(+), 227 deletions(-) create mode 100644 code-samples/community/serving/helloworld-haskell/LICENSE create mode 100644 code-samples/community/serving/helloworld-haskell/cabal.project create mode 100644 code-samples/community/serving/helloworld-haskell/cabal.project.freeze create mode 100755 code-samples/community/serving/helloworld-haskell/ci.sh create mode 100644 code-samples/community/serving/helloworld-haskell/helloworld-haskell.cabal delete mode 100644 code-samples/community/serving/helloworld-haskell/package.yaml delete mode 100644 code-samples/community/serving/helloworld-haskell/stack.yaml diff --git a/code-samples/community/serving/helloworld-haskell/Dockerfile b/code-samples/community/serving/helloworld-haskell/Dockerfile index 87e87eb043..9e12b976dd 100644 --- a/code-samples/community/serving/helloworld-haskell/Dockerfile +++ b/code-samples/community/serving/helloworld-haskell/Dockerfile @@ -1,22 +1,27 @@ -# Use the official Haskell image to create a build artifact. -# https://hub.docker.com/_/haskell/ -FROM haskell:8.2.2 AS builder +# syntax=docker/dockerfile:1 +# GHCup-recommended / Stackage LTS compiler; pin the multi-architecture image. +FROM haskell:9.10.3-slim-bookworm@sha256:3c9f47467dbd6cb62c6ec6b768270c627bc33565e209bece103b7484075b27e3 AS builder +ENV CABAL_DIR=/root/.cabal +WORKDIR /src +COPY helloworld-haskell.cabal cabal.project cabal.project.freeze LICENSE ./ +RUN --mount=type=cache,id=docs-audit-community-helloworld-haskell-cabal,target=/root/.cabal \ + cabal update && cabal build --only-dependencies all +COPY app ./app +RUN --mount=type=cache,id=docs-audit-community-helloworld-haskell-cabal,target=/root/.cabal \ + cabal check \ + && cabal build all --ghc-options=-Werror \ + && mkdir /out \ + && cp "$(cabal list-bin exe:helloworld-haskell-exe)" /out/helloworld-haskell-exe \ + && strip /out/helloworld-haskell-exe \ + && ldd /out/helloworld-haskell-exe -# Copy local code to the container image. +# Same Debian ABI, without GHC/Cabal or build dependencies in the final image. +FROM debian:bookworm-slim@sha256:7ff6ba31527b9e336162fa061746c366c4b2032f4d2cff2078bea43f78efe3f8 +RUN apt-get update \ + && apt-get install -y --no-install-recommends libgmp10 zlib1g \ + && rm -rf /var/lib/apt/lists/* +ENV LANG=C.UTF-8 WORKDIR /app -COPY . . - -# Build and test our code, then build the “helloworld-haskell-exe” executable. -RUN stack setup -RUN stack build --copy-bins - -# Use a Docker multi-stage build to create a lean production image. -# https://docs.docker.com/develop/develop-images/multistage-build/#use-multi-stage-builds -FROM fpco/haskell-scratch:integer-gmp - -# Copy the "helloworld-haskell-exe" executable from the builder stage to the production image. -WORKDIR /root/ -COPY --from=builder /root/.local/bin/helloworld-haskell-exe . - -# Run the web service on container startup. +COPY --from=builder /out/helloworld-haskell-exe . +USER 65532:65532 CMD ["./helloworld-haskell-exe"] diff --git a/code-samples/community/serving/helloworld-haskell/LICENSE b/code-samples/community/serving/helloworld-haskell/LICENSE new file mode 100644 index 0000000000..261eeb9e9f --- /dev/null +++ b/code-samples/community/serving/helloworld-haskell/LICENSE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/code-samples/community/serving/helloworld-haskell/README.md b/code-samples/community/serving/helloworld-haskell/README.md index fa31df862d..70de07daba 100644 --- a/code-samples/community/serving/helloworld-haskell/README.md +++ b/code-samples/community/serving/helloworld-haskell/README.md @@ -1,179 +1,79 @@ # Hello World - Haskell -A simple web app written in Haskell that you can use for testing. It reads in an -env variable `TARGET` and prints "Hello \${TARGET}!". If TARGET is not -specified, it will use "World" as the TARGET. +A small Knative Serving application. `GET /` returns `Hello ${TARGET}` as UTF-8 +plain text, without punctuation or a trailing newline. Unset `TARGET` defaults +to `World`; an explicitly empty `TARGET` returns `Hello `. `PORT` defaults to +`8080`. The server accepts connections from outside the container. ## Prerequisites -- A Kubernetes cluster with Knative installed and DNS configured. Follow the - [Knative installation instructions](https://knative.dev/docs/install/) if you need to create - one. -- [Docker](https://www.docker.com) installed and running on your local machine, - and a Docker Hub account configured (we'll use it for a container registry). - -## Recreating the sample code - -While you can clone all of the code from this directory, hello world apps are -generally more useful if you build them step-by-step. The following instructions -recreate the source files from this folder. - -1. Create a new file named `stack.yaml` and paste the following code: - - ```yaml - flags: {} - packages: - - . - extra-deps: [] - resolver: lts-10.7 - ``` - -1. Create a new file named `package.yaml` and paste the following code - - ```yaml - name: helloworld-haskell - version: 0.1.0.0 - dependencies: - - base >= 4.7 && < 5 - - scotty - - text - - executables: - helloworld-haskell-exe: - main: Main.hs - source-dirs: app - ghc-options: - - -threaded - - -rtsopts - - -with-rtsopts=-N - ``` - -1. Create a `app` folder, then create a new file named `Main.hs` in that folder - and paste the following code. This code creates a basic web server which - listens on port 8080: - - ```haskell - {-# LANGUAGE OverloadedStrings #-} - - import Data.Maybe - import Data.Monoid ((<>)) - import Data.Text.Lazy (Text) - import Data.Text.Lazy - import System.Environment (lookupEnv) - import Web.Scotty (ActionM, ScottyM, scotty) - import Web.Scotty.Trans - - main :: IO () - main = do - t <- fromMaybe "World" <$> lookupEnv "TARGET" - pStr <- fromMaybe "8080" <$> lookupEnv "PORT" - let p = read pStr :: Int - scotty p (route t) - - route :: String -> ScottyM() - route t = get "/" $ hello t - - hello :: String -> ActionM() - hello t = text $ pack ("Hello " ++ t) - ``` - -1. In your project directory, create a file named `Dockerfile` and copy the code - block below into it. - - ```docker - # Use the official Haskell image to create a build artifact. - # https://hub.docker.com/_/haskell/ - FROM haskell:8.2.2 as builder - - # Copy local code to the container image. - WORKDIR /app - COPY . . - - # Build and test our code, then build the “helloworld-haskell-exe” executable. - RUN stack setup - RUN stack build --copy-bins - - # Use a Docker multi-stage build to create a lean production image. - # https://docs.docker.com/develop/develop-images/multistage-build/#use-multi-stage-builds - FROM fpco/haskell-scratch:integer-gmp - - # Copy the "helloworld-haskell-exe" executable from the builder stage to the production image. - WORKDIR /root/ - COPY --from=builder /root/.local/bin/helloworld-haskell-exe . - - # Run the web service on container startup. - CMD ["./helloworld-haskell-exe"] - ``` - -1. Create a new file, `service.yaml` and copy the following service definition - into the file. Make sure to replace `{username}` with your Docker Hub - username. - - ```yaml - apiVersion: serving.knative.dev/v1 - kind: Service - metadata: - name: helloworld-haskell - namespace: default - spec: - template: - spec: - containers: - - image: docker.io/{username}/helloworld-haskell - env: - - name: TARGET - value: "Haskell Sample v1" - ``` - -## Build and deploy this sample - -Once you have recreated the sample code files (or used the files in the sample -folder) you're ready to build and deploy the sample app. - -1. Use Docker to build the sample code into a container. To build and push with - Docker Hub, enter these commands replacing `{username}` with your Docker Hub - username: - - ```bash - # Build and push the container on your local machine. - docker buildx build --platform linux/arm64,linux/amd64 -t "{username}/helloworld-haskell" --push . - ``` - -1. After the build has completed and the container is pushed to Docker Hub, you - can deploy the app into your cluster. Ensure that the container image value - in `service.yaml` matches the container you built in the previous step. Apply - the configuration using `kubectl`: - - ```bash - kubectl apply --filename service.yaml - ``` - -1. Now that your service is created, Knative will perform the following steps: - - - Create a new immutable revision for this version of the app. - - Network programming to create a route, ingress, service, and load balance - for your app. - - Automatically scale your pods up and down (including to zero active pods). - -1. To find the URL for your service, enter: - - ``` - kubectl get ksvc helloworld-haskell --output=custom-columns=NAME:.metadata.name,URL:.status.url - NAME URL - helloworld-haskell http://helloworld-haskell.default.1.2.3.4.sslip.io - ``` - -1. Now you can make a request to your app and see the result. Replace - the URL below with the URL returned in the previous command. - - ```bash - curl http://helloworld-haskell.default.1.2.3.4.sslip.io - Hello world: Haskell Sample v1 - ``` - -## Removing the sample app deployment - -To remove the sample app from your cluster, delete the service record: +- Docker with BuildKit, and a Kubernetes cluster with + [Knative Serving](https://knative.dev/docs/install/) and ingress configured. +- A container registry account if pushing the image for a remote cluster. + A local kind cluster can instead load a locally built image. +- No local Haskell installation is needed; the Dockerfile includes the toolchain. + +## Sample files + +[`Dockerfile`](Dockerfile) builds [`app/Main.hs`](app/Main.hs) using Cabal and +[`cabal.project.freeze`](cabal.project.freeze). The runtime image runs as non-root. +The sample is licensed under [Apache 2.0](LICENSE). +Invalid, zero, or out-of-range `PORT` values fail at startup. + +## Build and run locally + +Run these commands from the sample directory. Use your cluster node's platform +(`linux/arm64` below; substitute `linux/amd64` for x86-64 nodes). + +```bash +docker build --platform linux/arm64 -t helloworld-haskell:local . +docker run --rm -p 127.0.0.1:8080:8080 --memory 128m --cpus 1 helloworld-haskell:local +# In another terminal: +curl -fsS http://127.0.0.1:8080/ +``` + +The build runs `cabal check` and compiles with warnings as errors. To use another +environment, start the image with `-e TARGET='Haskell Sample v1' -e PORT=8081` +and publish `127.0.0.1:8081:8081`, then request that port with curl. + +## Deploy to Knative + +Set your Docker Hub username in the image tag and in `service.yaml`. Build and +push only when you intend to publish: + +```bash +IMAGE=docker.io/{username}/helloworld-haskell +docker build --platform linux/arm64 -t "$IMAGE" . +docker push "$IMAGE" +``` + +Use the checked-in [`service.yaml`](service.yaml). Its `TARGET` value is +`Haskell Sample v1`; replace only the image username/tag and, if needed, namespace +before applying it. + +For a local kind cluster, use a non-latest local reference (for example, +`dev.local/helloworld-haskell:local`), load it with `kind load docker-image`, +and set `imagePullPolicy: IfNotPresent`. Knative must be configured to skip remote +tag resolution for that local registry prefix. Adapt `metadata.namespace` when +using an isolated namespace. + +Knative injects `PORT`. To select another port, set +`spec.template.spec.containers[0].ports[0].containerPort`, for example `8081`. + +```bash +kubectl apply --filename service.yaml +kubectl wait --for=condition=Ready ksvc/helloworld-haskell --timeout=180s +kubectl get ksvc helloworld-haskell -o jsonpath='{.status.url}' +``` + +Request the returned URL through your cluster ingress: + +```bash +curl http://helloworld-haskell.default.1.2.3.4.sslip.io +# Exact body: Hello Haskell Sample v1 (no trailing newline) +``` + +To remove the deployment: ```bash kubectl delete --filename service.yaml diff --git a/code-samples/community/serving/helloworld-haskell/app/Main.hs b/code-samples/community/serving/helloworld-haskell/app/Main.hs index 0e35f7cbf2..69ad6dd755 100644 --- a/code-samples/community/serving/helloworld-haskell/app/Main.hs +++ b/code-samples/community/serving/helloworld-haskell/app/Main.hs @@ -1,22 +1,18 @@ {-# LANGUAGE OverloadedStrings #-} -import Data.Maybe -import Data.Monoid ((<>)) -import Data.Text.Lazy (Text) -import Data.Text.Lazy -import System.Environment (lookupEnv) -import Web.Scotty (ActionM, ScottyM, scotty) -import Web.Scotty.Trans +module Main (main) where + +import Data.Maybe (fromMaybe) +import Data.Text.Lazy (pack) +import System.Environment (lookupEnv) +import Text.Read (readMaybe) +import Web.Scotty (get, scotty, text) main :: IO () main = do - t <- fromMaybe "World" <$> lookupEnv "TARGET" - pStr <- fromMaybe "8080" <$> lookupEnv "PORT" - let p = read pStr :: Int - scotty p (route t) - -route :: String -> ScottyM() -route t = get "/" $ hello t - -hello :: String -> ActionM() -hello t = text $ pack ("Hello " ++ t) + target <- fromMaybe "World" <$> lookupEnv "TARGET" + portText <- fromMaybe "8080" <$> lookupEnv "PORT" + case readMaybe portText :: Maybe Int of + Just port | port >= 1 && port <= 65535 -> + scotty port $ get "/" $ text $ pack ("Hello " ++ target) + _ -> fail "PORT must be an integer between 1 and 65535" diff --git a/code-samples/community/serving/helloworld-haskell/cabal.project b/code-samples/community/serving/helloworld-haskell/cabal.project new file mode 100644 index 0000000000..f4825a828b --- /dev/null +++ b/code-samples/community/serving/helloworld-haskell/cabal.project @@ -0,0 +1,5 @@ +packages: . +with-compiler: ghc-9.10.3 +index-state: 2026-10-23T00:00:00Z +optimization: False +jobs: 2 diff --git a/code-samples/community/serving/helloworld-haskell/cabal.project.freeze b/code-samples/community/serving/helloworld-haskell/cabal.project.freeze new file mode 100644 index 0000000000..63ca28b060 --- /dev/null +++ b/code-samples/community/serving/helloworld-haskell/cabal.project.freeze @@ -0,0 +1,178 @@ +active-repositories: hackage.haskell.org:merge +constraints: any.HUnit ==1.6.2.0, + any.OneTuple ==0.4.3, + OneTuple +base-ge-4-15 +base-ge-4-16, + any.QuickCheck ==2.18.0.0, + QuickCheck -old-random +templatehaskell, + any.StateVar ==1.2.2, + any.aeson ==2.2.5.1, + aeson +ordered-keymap, + any.ansi-terminal ==1.1.5, + ansi-terminal -example, + any.ansi-terminal-types ==1.1.3, + any.appar ==0.1.8, + any.array ==0.5.8.0, + any.assoc ==1.1.1, + assoc -tagged, + any.async ==2.2.6, + async -bench -debug-auto-label, + any.attoparsec ==0.14.4, + attoparsec -developer, + any.auto-update ==0.2.8, + any.base ==4.20.2.0, + any.base-orphans ==0.9.4, + any.base16 ==1.0, + any.base64 ==2.1, + base64 -simd, + any.base64-bytestring ==1.2.1.0, + any.bifunctors ==5.6.3, + bifunctors +tagged, + any.binary ==0.8.9.3, + any.blaze-builder ==0.4.4.1, + any.bsb-http-chunked ==0.0.0.4, + any.byteorder ==1.0.4, + any.bytestring ==0.12.2.0, + any.call-stack ==0.4.0, + any.case-insensitive ==1.2.1.0, + any.character-ps ==0.1, + any.colour ==2.3.7, + any.comonad ==5.0.10, + comonad +containers +distributive +indexed-traversable, + any.containers ==0.7, + any.contravariant ==1.5.6, + contravariant +statevar, + any.cookie ==0.5.1, + any.crypton ==2.2.4, + crypton -check_alignment +integer-gmp -old_toolchain_inliner +support_aesni +support_deepseq +support_pclmuldq +support_s2n_bignum -support_sse +use_target_attributes, + any.crypton-asn1-encoding ==0.10.0, + any.crypton-asn1-parse ==0.10.0, + any.crypton-asn1-types ==0.4.1, + any.crypton-pem ==0.3.0, + any.crypton-x509 ==1.10.0, + any.data-default ==0.8.0.2, + any.data-default-class ==0.2.0.0, + any.data-fix ==0.3.4, + any.deepseq ==1.5.0.0, + any.directory ==1.3.8.5, + any.distributive ==0.6.3, + distributive +tagged, + any.dlist ==1.0, + dlist -werror, + any.easy-file ==0.2.5, + any.exceptions ==0.10.9, + any.fast-logger ==3.2.8, + any.filepath ==1.5.4.0, + any.ghc-bignum ==1.3, + any.ghc-boot-th ==9.10.3, + any.ghc-internal ==9.1003.0, + any.ghc-prim ==0.12.0, + any.hashable ==1.5.1.0, + hashable -arch-native -random-initial-seed, + any.hsc2hs ==0.68.10, + hsc2hs -in-ghc-tree, + any.http-api-data ==0.6.3, + http-api-data -use-text-show, + any.http-date ==0.0.11, + any.http-semantics ==0.4.1, + any.http-types ==0.12.6, + any.http2 ==5.4.9, + http2 -devel -h2spec, + any.indexed-traversable ==0.1.5, + indexed-traversable +base-ge-4-18, + any.indexed-traversable-instances ==0.1.2.1, + any.integer-conversion ==0.1.1, + any.integer-gmp ==1.1, + any.integer-logarithms ==1.0.5, + integer-logarithms -check-bounds +integer-gmp, + any.iproute ==1.7.15, + any.monad-control ==1.0.3.1, + any.mtl ==2.3.1, + any.network ==3.2.9.0, + network -devel, + any.network-byte-order ==0.1.8, + any.network-control ==0.1.7, + any.network-uri ==2.6.4.2, + any.optparse-applicative ==0.19.0.0, + optparse-applicative +process, + any.os-string ==2.0.7, + any.parsec ==3.1.18.0, + any.pretty ==1.1.3.6, + any.prettyprinter ==1.7.2, + prettyprinter -buildreadme +text, + any.prettyprinter-ansi-terminal ==1.1.4, + prettyprinter-ansi-terminal +text, + any.primitive ==0.9.1.0, + any.process ==1.6.26.1, + any.psqueues ==0.2.8.3, + any.ram ==0.22.1, + any.random ==1.3.1, + any.recv ==0.1.2, + any.regex-base ==0.94.0.3, + any.regex-compat ==0.95.2.2, + any.regex-posix ==0.96.0.2, + regex-posix -_regex-posix-clib, + any.resourcet ==1.3.0, + any.rts ==1.0.2, + any.safe-exceptions ==0.1.7.4, + any.scientific ==0.3.10.0, + scientific -integer-simple, + any.scotty ==0.30, + any.semialign ==1.4, + semialign +semigroupoids, + any.semigroupoids ==6.0.2, + semigroupoids +comonad +containers +contravariant +tagged +unordered-containers, + any.simple-sendfile ==0.2.32, + simple-sendfile +allow-bsd -fallback, + any.splitmix ==0.1.3.2, + splitmix -optimised-mixer, + any.stm ==2.5.3.1, + any.streaming-commons ==0.2.3.1, + streaming-commons -use-bytestring-builder, + any.strict ==0.5.1, + any.tagged ==0.8.11, + tagged +deepseq +template-haskell, + any.tasty ==1.5.5, + tasty +terminal-size +unix, + any.template-haskell ==2.22.0.0, + any.terminal-size ==0.3.4, + any.text ==2.1.3, + any.text-iso8601 ==0.1.1.2, + any.text-short ==0.1.6.1, + text-short -asserts, + any.th-abstraction ==0.7.2.0, + any.th-compat ==0.1.7, + any.these ==1.2.1, + any.time ==1.12.2, + any.time-compat ==1.9.9, + any.time-hourglass ==0.3.0, + any.time-manager ==0.4.0, + any.transformers ==0.6.1.1, + any.transformers-base ==0.4.6.1, + transformers-base +orphaninstances, + any.transformers-compat ==0.7.2, + transformers-compat -five +five-three -four +generic-deriving +mtl -three -two, + any.unix ==2.8.7.0, + any.unix-compat ==0.7.4.1, + any.unix-time ==0.5.0, + any.unliftio ==0.2.25.1, + any.unliftio-core ==0.2.1.0, + any.unordered-containers ==0.2.21, + unordered-containers -debug, + any.utf8-string ==1.0.2, + any.uuid-types ==1.0.6.1, + any.vault ==0.3.2.0, + vault +useghc, + any.vector ==0.13.2.0, + vector +boundschecks -internalchecks -unsafechecks -wall, + any.vector-stream ==0.1.0.1, + any.wai ==3.2.5, + any.wai-extra ==3.1.18, + wai-extra -build-example, + any.wai-logger ==2.5.0, + any.warp ==3.4.16, + warp +allow-sendfilefd +include-warp-version -network-bytestring -warp-debug +x509, + any.witherable ==0.5, + any.word8 ==0.1.3, + any.zlib ==0.7.1.1, + zlib -bundled-c-zlib +non-blocking-ffi -pkg-config +index-state: hackage.haskell.org 2026-10-22T19:35:36Z diff --git a/code-samples/community/serving/helloworld-haskell/ci.sh b/code-samples/community/serving/helloworld-haskell/ci.sh new file mode 100755 index 0000000000..d128a280ec --- /dev/null +++ b/code-samples/community/serving/helloworld-haskell/ci.sh @@ -0,0 +1,40 @@ +#!/usr/bin/env bash +# Source from this sample directory; the caller owns cluster setup and ingress forwarding. +IMAGE=dev.local/helloworld-haskell:ci +SAMPLE_URL=http://127.0.0.1:18080 + +main() ( + set -euo pipefail + build + deploy + test +) + +build() ( + set -euo pipefail + docker build -t "$IMAGE" . +) + +deploy() ( + set -euo pipefail + kind load docker-image "$IMAGE" + kubectl patch --local -f service.yaml --type=json -p "$( + jq -cn --arg image "$IMAGE" '[ + {op: "replace", path: "/spec/template/spec/containers/0/image", value: $image}, + {op: "add", path: "/spec/template/spec/containers/0/imagePullPolicy", value: "IfNotPresent"} + ]' + )" -o yaml | kubectl --namespace default apply -f - + kubectl --namespace default wait -f service.yaml --for=condition=Ready --timeout=300s +) + +test() ( + set -euo pipefail + local service_url service_host + service_url=$(kubectl --namespace default get -f service.yaml -o jsonpath='{.status.url}') + service_host=${service_url#*://} + [[ "$service_host" =~ ^[a-zA-Z0-9.-]+$ ]] + local body + body=$(curl -fsS --max-time 30 \ + -H "Host: $service_host" "$SAMPLE_URL/") + [[ $body == 'Hello Haskell Sample v1' ]] +) diff --git a/code-samples/community/serving/helloworld-haskell/helloworld-haskell.cabal b/code-samples/community/serving/helloworld-haskell/helloworld-haskell.cabal new file mode 100644 index 0000000000..585e3afeb8 --- /dev/null +++ b/code-samples/community/serving/helloworld-haskell/helloworld-haskell.cabal @@ -0,0 +1,20 @@ +cabal-version: 3.0 +name: helloworld-haskell +version: 0.1.0.0 +synopsis: A minimal Knative Serving greeting service +description: A small HTTP service demonstrating Knative environment configuration. +category: Web +maintainer: Knative community +license: Apache-2.0 +license-file: LICENSE +build-type: Simple + +executable helloworld-haskell-exe + main-is: Main.hs + hs-source-dirs: app + default-language: Haskell2010 + ghc-options: -Wall -threaded -rtsopts -with-rtsopts=-N1 + build-depends: + base >= 4.20 && < 4.21, + scotty == 0.30, + text >= 2.1 && < 2.2 diff --git a/code-samples/community/serving/helloworld-haskell/package.yaml b/code-samples/community/serving/helloworld-haskell/package.yaml deleted file mode 100644 index 12178e943e..0000000000 --- a/code-samples/community/serving/helloworld-haskell/package.yaml +++ /dev/null @@ -1,15 +0,0 @@ -name: helloworld-haskell -version: 0.1.0.0 -dependencies: -- base >= 4.7 && < 5 -- scotty -- text - -executables: - helloworld-haskell-exe: - main: Main.hs - source-dirs: app - ghc-options: - - -threaded - - -rtsopts - - -with-rtsopts=-N diff --git a/code-samples/community/serving/helloworld-haskell/stack.yaml b/code-samples/community/serving/helloworld-haskell/stack.yaml deleted file mode 100644 index e63cd13e1e..0000000000 --- a/code-samples/community/serving/helloworld-haskell/stack.yaml +++ /dev/null @@ -1,5 +0,0 @@ -flags: {} -packages: -- . -extra-deps: [] -resolver: lts-10.7 From 60cce3b09c09ba7d358ddd2ef89ed90938550e45 Mon Sep 17 00:00:00 2001 From: Dave Protasowski Date: Mon, 26 Oct 2026 09:16:03 -0400 Subject: [PATCH 06/44] Update the Micronaut Hello World sample --- .../helloworld-java-micronaut/.mvn/jvm.config | 1 - .../.mvn/wrapper/MavenWrapperDownloader.java | 110 ------ .../.mvn/wrapper/maven-wrapper.properties | 1 - .../helloworld-java-micronaut/Dockerfile | 29 +- .../helloworld-java-micronaut/README.md | 323 ++++-------------- .../serving/helloworld-java-micronaut/ci.sh | 40 +++ .../dependency-reduced-pom.xml | 173 ---------- .../micronaut-cli.yml | 1 - .../serving/helloworld-java-micronaut/mvnw | 286 ---------------- .../helloworld-java-micronaut/mvnw.cmd | 161 --------- .../serving/helloworld-java-micronaut/pom.xml | 156 ++------- .../com/example/helloworld/Application.java | 2 +- .../src/main/resources/logback.xml | 1 - .../helloworld/HelloWorldControllerTest.java | 28 -- 14 files changed, 148 insertions(+), 1164 deletions(-) delete mode 100644 code-samples/community/serving/helloworld-java-micronaut/.mvn/jvm.config delete mode 100644 code-samples/community/serving/helloworld-java-micronaut/.mvn/wrapper/MavenWrapperDownloader.java delete mode 100644 code-samples/community/serving/helloworld-java-micronaut/.mvn/wrapper/maven-wrapper.properties create mode 100755 code-samples/community/serving/helloworld-java-micronaut/ci.sh delete mode 100644 code-samples/community/serving/helloworld-java-micronaut/dependency-reduced-pom.xml delete mode 100644 code-samples/community/serving/helloworld-java-micronaut/mvnw delete mode 100644 code-samples/community/serving/helloworld-java-micronaut/mvnw.cmd delete mode 100644 code-samples/community/serving/helloworld-java-micronaut/src/test/java/com/example/helloworld/HelloWorldControllerTest.java diff --git a/code-samples/community/serving/helloworld-java-micronaut/.mvn/jvm.config b/code-samples/community/serving/helloworld-java-micronaut/.mvn/jvm.config deleted file mode 100644 index 2256175bc0..0000000000 --- a/code-samples/community/serving/helloworld-java-micronaut/.mvn/jvm.config +++ /dev/null @@ -1 +0,0 @@ --Dcom.sun.management.jmxremote -noverify -XX:TieredStopAtLevel=1 diff --git a/code-samples/community/serving/helloworld-java-micronaut/.mvn/wrapper/MavenWrapperDownloader.java b/code-samples/community/serving/helloworld-java-micronaut/.mvn/wrapper/MavenWrapperDownloader.java deleted file mode 100644 index d475a89ce1..0000000000 --- a/code-samples/community/serving/helloworld-java-micronaut/.mvn/wrapper/MavenWrapperDownloader.java +++ /dev/null @@ -1,110 +0,0 @@ -/* -Licensed to the Apache Software Foundation (ASF) under one -or more contributor license agreements. See the NOTICE file -distributed with this work for additional information -regarding copyright ownership. The ASF licenses this file -to you under the Apache License, Version 2.0 (the -"License"); you may not use this file except in compliance -with the License. You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, -software distributed under the License is distributed on an -"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY -KIND, either express or implied. See the License for the -specific language governing permissions and limitations -under the License. -*/ - -import java.net.*; -import java.io.*; -import java.nio.channels.*; -import java.util.Properties; - -public class MavenWrapperDownloader { - - /** - * Default URL to download the maven-wrapper.jar from, if no 'downloadUrl' is provided. - */ - private static final String DEFAULT_DOWNLOAD_URL = - "https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.4.0/maven-wrapper-0.4.0.jar"; - - /** - * Path to the maven-wrapper.properties file, which might contain a downloadUrl property to - * use instead of the default one. - */ - private static final String MAVEN_WRAPPER_PROPERTIES_PATH = - ".mvn/wrapper/maven-wrapper.properties"; - - /** - * Path where the maven-wrapper.jar will be saved to. - */ - private static final String MAVEN_WRAPPER_JAR_PATH = - ".mvn/wrapper/maven-wrapper.jar"; - - /** - * Name of the property which should be used to override the default download url for the wrapper. - */ - private static final String PROPERTY_NAME_WRAPPER_URL = "wrapperUrl"; - - public static void main(String args[]) { - System.out.println("- Downloader started"); - File baseDirectory = new File(args[0]); - System.out.println("- Using base directory: " + baseDirectory.getAbsolutePath()); - - // If the maven-wrapper.properties exists, read it and check if it contains a custom - // wrapperUrl parameter. - File mavenWrapperPropertyFile = new File(baseDirectory, MAVEN_WRAPPER_PROPERTIES_PATH); - String url = DEFAULT_DOWNLOAD_URL; - if(mavenWrapperPropertyFile.exists()) { - FileInputStream mavenWrapperPropertyFileInputStream = null; - try { - mavenWrapperPropertyFileInputStream = new FileInputStream(mavenWrapperPropertyFile); - Properties mavenWrapperProperties = new Properties(); - mavenWrapperProperties.load(mavenWrapperPropertyFileInputStream); - url = mavenWrapperProperties.getProperty(PROPERTY_NAME_WRAPPER_URL, url); - } catch (IOException e) { - System.out.println("- ERROR loading '" + MAVEN_WRAPPER_PROPERTIES_PATH + "'"); - } finally { - try { - if(mavenWrapperPropertyFileInputStream != null) { - mavenWrapperPropertyFileInputStream.close(); - } - } catch (IOException e) { - // Ignore ... - } - } - } - System.out.println("- Downloading from: : " + url); - - File outputFile = new File(baseDirectory.getAbsolutePath(), MAVEN_WRAPPER_JAR_PATH); - if(!outputFile.getParentFile().exists()) { - if(!outputFile.getParentFile().mkdirs()) { - System.out.println( - "- ERROR creating output direcrory '" + outputFile.getParentFile().getAbsolutePath() + "'"); - } - } - System.out.println("- Downloading to: " + outputFile.getAbsolutePath()); - try { - downloadFileFromURL(url, outputFile); - System.out.println("Done"); - System.exit(0); - } catch (Throwable e) { - System.out.println("- Error downloading"); - e.printStackTrace(); - System.exit(1); - } - } - - private static void downloadFileFromURL(String urlString, File destination) throws Exception { - URL website = new URL(urlString); - ReadableByteChannel rbc; - rbc = Channels.newChannel(website.openStream()); - FileOutputStream fos = new FileOutputStream(destination); - fos.getChannel().transferFrom(rbc, 0, Long.MAX_VALUE); - fos.close(); - rbc.close(); - } - -} diff --git a/code-samples/community/serving/helloworld-java-micronaut/.mvn/wrapper/maven-wrapper.properties b/code-samples/community/serving/helloworld-java-micronaut/.mvn/wrapper/maven-wrapper.properties deleted file mode 100644 index 0773086c03..0000000000 --- a/code-samples/community/serving/helloworld-java-micronaut/.mvn/wrapper/maven-wrapper.properties +++ /dev/null @@ -1 +0,0 @@ -distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.5.3/apache-maven-3.5.3-bin.zip diff --git a/code-samples/community/serving/helloworld-java-micronaut/Dockerfile b/code-samples/community/serving/helloworld-java-micronaut/Dockerfile index 6ddf7201da..302c4c774b 100644 --- a/code-samples/community/serving/helloworld-java-micronaut/Dockerfile +++ b/code-samples/community/serving/helloworld-java-micronaut/Dockerfile @@ -1,23 +1,18 @@ -# Use the official maven/Java 8 image to create a build artifact. +# Public multi-architecture Maven / Java 25 LTS build image. # https://hub.docker.com/_/maven -FROM maven:3.5-jdk-8-alpine AS builder - -# Copy local code to the container image. +FROM maven:3.10.0-eclipse-temurin-25-noble AS builder WORKDIR /app COPY pom.xml . COPY src ./src -# Build a release artifact. -RUN mvn package -DskipTests - -# Use AdoptOpenJDK for base image. -# It's important to use OpenJDK 8u191 or above that has container support enabled. -# https://hub.docker.com/r/adoptopenjdk/openjdk8 -# https://docs.docker.com/develop/develop-images/multistage-build/#use-multi-stage-builds -FROM adoptopenjdk/openjdk8:jdk8u202-b08-alpine-slim +# Build the shaded executable JAR. +RUN mvn -B -ntp verify -# Copy the jar to the production image from the builder stage. -COPY --from=builder /app/target/helloworld-1.0.0-SNAPSHOT.jar /helloworld.jar - -# Run the web service on container startup. -CMD ["java","-jar","/helloworld.jar"] +# Java 25 LTS runtime; no Maven or compiler in the production image. +# https://hub.docker.com/_/eclipse-temurin +FROM eclipse-temurin:25-jre-noble +WORKDIR /app +COPY --from=builder /app/target/helloworld-1.0.0-SNAPSHOT.jar /app/helloworld.jar +USER 10001:10001 +EXPOSE 8080 +ENTRYPOINT ["java", "-jar", "/app/helloworld.jar"] diff --git a/code-samples/community/serving/helloworld-java-micronaut/README.md b/code-samples/community/serving/helloworld-java-micronaut/README.md index 20ef5e25d1..f1758bbd2e 100644 --- a/code-samples/community/serving/helloworld-java-micronaut/README.md +++ b/code-samples/community/serving/helloworld-java-micronaut/README.md @@ -1,281 +1,92 @@ -# Hello World - Java (Micronaut) +# Hello World — Java (Micronaut) -Learn how to deploy a simple web app that is written in Java and uses Micronaut. +Deploy a plain-text HTTP service using Micronaut Platform **5.2.3** and +**Java 25 LTS**. GET `/` returns `Hello World: ${TARGET}` without a trailing +newline. An unset `TARGET` defaults to `NOT SPECIFIED`; an explicitly empty +`TARGET` stays empty. The listening port is `${PORT:8080}`. -This samples uses Docker to build locally. The app reads in a `TARGET` env -variable and then prints "Hello World: \${TARGET}!". If a value for `TARGET` is -not specified, the "NOT SPECIFIED" default value is used. +## Requirements -Use this sample to walk you through the steps of creating and modifying the -sample app, building and pushing your container image to a registry, and then -deploying your app to your Knative cluster. +- Docker with BuildKit for builds and optional multi-platform publication. +- curl for HTTP checks. +- A Knative Serving cluster and kubectl for deployment. +- A container registry account if publishing to a remote cluster. -## Before you begin +Java, Maven and the Micronaut CLI do not need to be installed on the host. +Clone [knative/docs](https://github.com/knative/docs) and work in +`code-samples/community/serving/helloworld-java-micronaut`. -You must meet the following requirements to complete this sample: +## Configuration -- A version of the Knative Serving component installed and DNS configured. Follow the - [Knative installation instructions](https://knative.dev/docs/install/) if you need - to create a Knative cluster. -- The following software downloaded and install on your loacal machine: - - [Java SE 8 or later JDK](http://www.oracle.com/technetwork/java/javase/downloads/index.html). - - [Micronaut 1.1](https://micronaut.io/). - - [Docker](https://www.docker.com) for building and pushing your container - image. - - [curl](https://curl.haxx.se/) to test the sample app after deployment. -- A [Docker Hub](https://hub.docker.com/) account where you can push your - container image. +[`pom.xml`](pom.xml) uses the Micronaut parent and Java 25 to build an executable +JAR. [`application.yml`](src/main/resources/application.yml) configures the +application name and `${PORT:8080}` listening port. -**Tip**: You can clone the [Knatve/docs repo](https://github.com/knative/docs) -and then modify the source files. Alternatively, learn more by manually creating -the files yourself. +## Build and run locally -## Creating and configuring the sample code +The checked-in [Dockerfile](Dockerfile) builds the executable JAR. +Its production stage contains only the Java runtime and application and runs as a +non-root UID. -To create and configure the source files in the root of your working directory: - -1. Create the `pom.xml` file: - - ```xml - 4.0.0 - com.example.micronaut - helloworld - 1.0.0-SNAPSHOT - - 1.1.0 - 1.8 - UTF-8 - com.example.helloworld.Application - - - - - io.micronaut - micronaut-bom - ${micronaut.version} - pom - import - - - - - - io.micronaut - micronaut-inject - compile - - - io.micronaut - micronaut-validation - compile - - - io.micronaut - micronaut-runtime - compile - - - io.micronaut - micronaut-http-client - compile - - - io.micronaut - micronaut-http-server-netty - compile - - - ch.qos.logback - logback-classic - 1.2.3 - runtime - - - - - - - org.apache.maven.plugins - maven-shade-plugin - 3.1.0 - - - package - - shade - - - - - ${exec.mainClass} - - - - - - - - - - - ``` - -1. Create the `HelloWorldController.java` file in the - `src/main/java/com/example/helloworld` directory. The - `[ROOT]/src/main/java/com/example/helloworld/HelloWorldController.java` file - handles requests to the root URI `/`. - - ```java - package com.example.helloworld; - - import io.micronaut.http.MediaType; - import io.micronaut.http.annotation.Controller; - import io.micronaut.http.annotation.Get; - - @Controller("/") - public class HelloWorldController { - - @Get(value = "/", produces = MediaType.TEXT_PLAIN) - public String index() { - String target = System.getenv("TARGET"); - if (target == null) { - target = "NOT SPECIFIED"; - } - return "Hello World: " + target; - } - } - ``` - -1. The Micronaut application is configured via - `src/main/resources/application.yml`: - - ```yaml - micronaut: - application: - name: helloworld-java-micronaut - server: - port: ${PORT:8080} - ``` - -1. Create the `Dockerfile` file: - - ```docker - # Use the official maven/Java 8 image to create a build artifact. - # https://hub.docker.com/_/maven - FROM maven:3.5-jdk-8-alpine as builder - - # Copy local code to the container image. - WORKDIR /app - COPY pom.xml . - COPY src ./src - - # Build a release artifact. - RUN mvn package -DskipTests - - # Use AdoptOpenJDK for base image. - # It's important to use OpenJDK 8u191 or above that has container support enabled. - # https://hub.docker.com/r/adoptopenjdk/openjdk8 - # https://docs.docker.com/develop/develop-images/multistage-build/#use-multi-stage-builds - FROM adoptopenjdk/openjdk8:jdk8u202-b08-alpine-slim - - # Copy the jar to the production image from the builder stage. - COPY --from=builder /app/target/helloworld-1.0.0-SNAPSHOT.jar /helloworld.jar - - # Run the web service on container startup. - CMD ["java","-jar","/helloworld.jar"] - ``` - -1. Create the `service.yaml` file. You must specify your Docker Hub username in - `{username}`. You can also configure the `TARGET`, for example you can modify - the `Micronaut Sample v1` value. - - ```yaml - apiVersion: serving.knative.dev/v1 - kind: Service - metadata: - name: helloworld-java-micronaut - namespace: default - spec: - template: - spec: - containers: - - image: docker.io/{username}/helloworld-java-micronaut - env: - - name: TARGET - value: "Micronaut Sample v1" - ``` - -## Building and deploying the sample - -To build a container image, push your image to the registry, and then deploy -your sample app to your cluster: - -1. Use Docker to build your container image and then push that image to your - Docker Hub registry. You must replace the `{username}` variables in the - following commands with your Docker Hub username. - - ```bash - # Build and push the container on your local machine. - docker buildx build --platform linux/arm64,linux/amd64 -t "{username}/helloworld-java-micronaut" --push . - ``` - -1. Now that your container image is in the registry, you can deploy it to your - Knative cluster by running the `kubectl apply` command: - - ```bash - kubectl apply --filename service.yaml - ``` - - Result: A service name `helloworld-java-micronaut` is created in your cluster - along with the following resources: - - - A new immutable revision for the version of the app that you just deployed. - - The following networking resources are created for your app: - - route - - ingress - - service - - load balancer - - Auto scaling is enable to allow your pods to scale up to meet traffic, and - also back down to zero when there is no traffic. +```sh +docker build -t helloworld-java-micronaut:local . +``` -## Testing the sample app +Start the actual production image, then request it from another terminal: -To verify that your sample app has been successfully deployed: +```sh +docker run --rm --name helloworld-java-micronaut-local -p 8080:8080 helloworld-java-micronaut:local +curl -fsS http://localhost:8080/ +# Hello World: NOT SPECIFIED +``` -1. Retrieve the URL for your service, by running the following `kubectl get` - command: +Stop that container before starting the custom configuration: - ```bash - kubectl get ksvc helloworld-java-micronaut --output=custom-columns=NAME:.metadata.name,URL:.status.url - ``` +```sh +docker run --rm --name helloworld-java-micronaut-local -p 8081:8081 \ + -e PORT=8081 -e TARGET="Micronaut Sample v1" helloworld-java-micronaut:local +curl -fsS http://localhost:8081/ +# Hello World: Micronaut Sample v1 +``` - Example result: +## Publish and deploy to Knative - ```bash - NAME URL - helloworld-java-micronaut http://helloworld-java-micronaut.default.1.2.3.4.sslip.io - ``` +Replace `{username}` in [`service.yaml`](service.yaml) and the image tag below with your registry +namespace. Publication requires credentials and is separate from local validation. -1. Now you can make a request to your app and see the result. Replace - the URL below with the URL returned in the previous command. +```sh +docker buildx build --platform linux/arm64,linux/amd64 \ + -t "{username}/helloworld-java-micronaut" --push . +kubectl apply --filename service.yaml +kubectl wait --for=condition=Ready ksvc/helloworld-java-micronaut --timeout=180s +kubectl get ksvc helloworld-java-micronaut -o jsonpath='{.status.url}' +``` - ```bash - curl http://helloworld-java-micronaut.default.1.2.3.4.sslip.io - ``` +The checked-in [Knative Service manifest](service.yaml) sets +`TARGET="Micronaut Sample v1"` and leaves the namespace selectable by kubectl. - Example result: +Request the actual URL returned by kubectl: - ```bash - Hello World: Micronaut Sample v1 - ``` +```sh +curl -fsS http://helloworld-java-micronaut.default.1.2.3.4.sslip.io/ +# Hello World: Micronaut Sample v1 +``` -Congratulations on deploying your sample Java app to Knative! +For a different Knative container port, add `ports: [{containerPort: 8081}]` +to the container. Knative injects `PORT` automatically; do not explicitly set +that reserved environment variable or use reserved port 9090. To check the +default greeting, remove the `TARGET` entry, apply again, wait for the new revision +to become Ready and request the same route. It should return +`Hello World: NOT SPECIFIED`. -## Removing the sample app deployment +For an isolated kind cluster, use the same production image loaded into its node, +a non-latest local tag such as `dev.local/helloworld-java-micronaut:validation`, +and `imagePullPolicy: IfNotPresent`. The local registry prefix must already be +excluded from Knative tag resolution. This adaptation does not verify remote +publication, public DNS or TLS. -To remove the sample app from your cluster, run the following `kubectl delete` -command: +## Cleanup -```bash +```sh kubectl delete --filename service.yaml ``` diff --git a/code-samples/community/serving/helloworld-java-micronaut/ci.sh b/code-samples/community/serving/helloworld-java-micronaut/ci.sh new file mode 100755 index 0000000000..31ea60cc45 --- /dev/null +++ b/code-samples/community/serving/helloworld-java-micronaut/ci.sh @@ -0,0 +1,40 @@ +#!/usr/bin/env bash +# Source from this sample directory; the caller owns cluster setup and ingress forwarding. +IMAGE=dev.local/helloworld-java-micronaut:ci +SAMPLE_URL=http://127.0.0.1:18080 + +main() ( + set -euo pipefail + build + deploy + test +) + +build() ( + set -euo pipefail + docker build -t "$IMAGE" . +) + +deploy() ( + set -euo pipefail + kind load docker-image "$IMAGE" + kubectl patch --local -f service.yaml --type=json -p "$( + jq -cn --arg image "$IMAGE" '[ + {op: "replace", path: "/spec/template/spec/containers/0/image", value: $image}, + {op: "add", path: "/spec/template/spec/containers/0/imagePullPolicy", value: "IfNotPresent"} + ]' + )" -o yaml | kubectl --namespace default apply -f - + kubectl --namespace default wait -f service.yaml --for=condition=Ready --timeout=300s +) + +test() ( + set -euo pipefail + local service_url service_host + service_url=$(kubectl --namespace default get -f service.yaml -o jsonpath='{.status.url}') + service_host=${service_url#*://} + [[ "$service_host" =~ ^[a-zA-Z0-9.-]+$ ]] + local body + body=$(curl -fsS --max-time 30 \ + -H "Host: $service_host" "$SAMPLE_URL/") + [[ $body == 'Hello World: Micronaut Sample v1' ]] +) diff --git a/code-samples/community/serving/helloworld-java-micronaut/dependency-reduced-pom.xml b/code-samples/community/serving/helloworld-java-micronaut/dependency-reduced-pom.xml deleted file mode 100644 index 62ceaf3269..0000000000 --- a/code-samples/community/serving/helloworld-java-micronaut/dependency-reduced-pom.xml +++ /dev/null @@ -1,173 +0,0 @@ - - - 4.0.0 - com.example.micronaut - helloworld - 1.0.0-SNAPSHOT - - - - - maven-compiler-plugin - 3.7.0 - - - test-compile - - testCompile - - - - -parameters - - - - io.micronaut - micronaut-inject-java - ${micronaut.version} - - - io.micronaut - micronaut-validation - ${micronaut.version} - - - - - - - ${jdk.version} - ${jdk.version} - UTF-8 - - -parameters - - - - io.micronaut - micronaut-inject-java - ${micronaut.version} - - - io.micronaut - micronaut-validation - ${micronaut.version} - - - - - - - - - maven-shade-plugin - 3.1.0 - - - package - - shade - - - - - ${exec.mainClass} - - - - - - - - - org.codehaus.mojo - exec-maven-plugin - 1.6.0 - - java - - -noverify - -Dmicronaut.env.deduction=false - -XX:TieredStopAtLevel=1 - -Dcom.sun.management.jmxremote - -classpath - - ${exec.mainClass} - - - - - maven-surefire-plugin - 2.22.0 - - - - - - org.junit.jupiter - junit-jupiter-api - 5.3.1 - test - - - apiguardian-api - org.apiguardian - - - opentest4j - org.opentest4j - - - junit-platform-commons - org.junit.platform - - - - - org.junit.jupiter - junit-jupiter-engine - 5.3.1 - test - - - junit-platform-engine - org.junit.platform - - - apiguardian-api - org.apiguardian - - - - - io.micronaut.test - micronaut-test-junit5 - 1.0.2 - test - - - micronaut-test-core - io.micronaut.test - - - - - - - - io.micronaut - micronaut-bom - ${micronaut.version} - pom - import - - - - - 1.1.0 - com.example.helloworld.Application - UTF-8 - 1.8 - - - diff --git a/code-samples/community/serving/helloworld-java-micronaut/micronaut-cli.yml b/code-samples/community/serving/helloworld-java-micronaut/micronaut-cli.yml index 5edd337c41..19c3e21f24 100644 --- a/code-samples/community/serving/helloworld-java-micronaut/micronaut-cli.yml +++ b/code-samples/community/serving/helloworld-java-micronaut/micronaut-cli.yml @@ -1,5 +1,4 @@ profile: service defaultPackage: com.example.helloworld --- -testFramework: junit sourceLanguage: java diff --git a/code-samples/community/serving/helloworld-java-micronaut/mvnw b/code-samples/community/serving/helloworld-java-micronaut/mvnw deleted file mode 100644 index 961a825001..0000000000 --- a/code-samples/community/serving/helloworld-java-micronaut/mvnw +++ /dev/null @@ -1,286 +0,0 @@ -#!/bin/sh -# ---------------------------------------------------------------------------- -# Licensed to the Apache Software Foundation (ASF) under one -# or more contributor license agreements. See the NOTICE file -# distributed with this work for additional information -# regarding copyright ownership. The ASF licenses this file -# to you under the Apache License, Version 2.0 (the -# "License"); you may not use this file except in compliance -# with the License. You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, -# software distributed under the License is distributed on an -# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY -# KIND, either express or implied. See the License for the -# specific language governing permissions and limitations -# under the License. -# ---------------------------------------------------------------------------- - -# ---------------------------------------------------------------------------- -# Maven2 Start Up Batch script -# -# Required ENV vars: -# ------------------ -# JAVA_HOME - location of a JDK home dir -# -# Optional ENV vars -# ----------------- -# M2_HOME - location of maven2's installed home dir -# MAVEN_OPTS - parameters passed to the Java VM when running Maven -# e.g. to debug Maven itself, use -# set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000 -# MAVEN_SKIP_RC - flag to disable loading of mavenrc files -# ---------------------------------------------------------------------------- - -if [ -z "$MAVEN_SKIP_RC" ] ; then - - if [ -f /etc/mavenrc ] ; then - . /etc/mavenrc - fi - - if [ -f "$HOME/.mavenrc" ] ; then - . "$HOME/.mavenrc" - fi - -fi - -# OS specific support. $var _must_ be set to either true or false. -cygwin=false; -darwin=false; -mingw=false -case "`uname`" in - CYGWIN*) cygwin=true ;; - MINGW*) mingw=true;; - Darwin*) darwin=true - # Use /usr/libexec/java_home if available, otherwise fall back to /Library/Java/Home - # See https://developer.apple.com/library/mac/qa/qa1170/_index.html - if [ -z "$JAVA_HOME" ]; then - if [ -x "/usr/libexec/java_home" ]; then - export JAVA_HOME="`/usr/libexec/java_home`" - else - export JAVA_HOME="/Library/Java/Home" - fi - fi - ;; -esac - -if [ -z "$JAVA_HOME" ] ; then - if [ -r /etc/gentoo-release ] ; then - JAVA_HOME=`java-config --jre-home` - fi -fi - -if [ -z "$M2_HOME" ] ; then - ## resolve links - $0 may be a link to maven's home - PRG="$0" - - # need this for relative symlinks - while [ -h "$PRG" ] ; do - ls=`ls -ld "$PRG"` - link=`expr "$ls" : '.*-> \(.*\)$'` - if expr "$link" : '/.*' > /dev/null; then - PRG="$link" - else - PRG="`dirname "$PRG"`/$link" - fi - done - - saveddir=`pwd` - - M2_HOME=`dirname "$PRG"`/.. - - # make it fully qualified - M2_HOME=`cd "$M2_HOME" && pwd` - - cd "$saveddir" - # echo Using m2 at $M2_HOME -fi - -# For Cygwin, ensure paths are in UNIX format before anything is touched -if $cygwin ; then - [ -n "$M2_HOME" ] && - M2_HOME=`cygpath --unix "$M2_HOME"` - [ -n "$JAVA_HOME" ] && - JAVA_HOME=`cygpath --unix "$JAVA_HOME"` - [ -n "$CLASSPATH" ] && - CLASSPATH=`cygpath --path --unix "$CLASSPATH"` -fi - -# For Mingw, ensure paths are in UNIX format before anything is touched -if $mingw ; then - [ -n "$M2_HOME" ] && - M2_HOME="`(cd "$M2_HOME"; pwd)`" - [ -n "$JAVA_HOME" ] && - JAVA_HOME="`(cd "$JAVA_HOME"; pwd)`" - # TODO classpath? -fi - -if [ -z "$JAVA_HOME" ]; then - javaExecutable="`which javac`" - if [ -n "$javaExecutable" ] && ! [ "`expr \"$javaExecutable\" : '\([^ ]*\)'`" = "no" ]; then - # readlink(1) is not available as standard on Solaris 10. - readLink=`which readlink` - if [ ! `expr "$readLink" : '\([^ ]*\)'` = "no" ]; then - if $darwin ; then - javaHome="`dirname \"$javaExecutable\"`" - javaExecutable="`cd \"$javaHome\" && pwd -P`/javac" - else - javaExecutable="`readlink -f \"$javaExecutable\"`" - fi - javaHome="`dirname \"$javaExecutable\"`" - javaHome=`expr "$javaHome" : '\(.*\)/bin'` - JAVA_HOME="$javaHome" - export JAVA_HOME - fi - fi -fi - -if [ -z "$JAVACMD" ] ; then - if [ -n "$JAVA_HOME" ] ; then - if [ -x "$JAVA_HOME/jre/sh/java" ] ; then - # IBM's JDK on AIX uses strange locations for the executables - JAVACMD="$JAVA_HOME/jre/sh/java" - else - JAVACMD="$JAVA_HOME/bin/java" - fi - else - JAVACMD="`which java`" - fi -fi - -if [ ! -x "$JAVACMD" ] ; then - echo "Error: JAVA_HOME is not defined correctly." >&2 - echo " We cannot execute $JAVACMD" >&2 - exit 1 -fi - -if [ -z "$JAVA_HOME" ] ; then - echo "Warning: JAVA_HOME environment variable is not set." -fi - -CLASSWORLDS_LAUNCHER=org.codehaus.plexus.classworlds.launcher.Launcher - -# traverses directory structure from process work directory to filesystem root -# first directory with .mvn subdirectory is considered project base directory -find_maven_basedir() { - - if [ -z "$1" ] - then - echo "Path not specified to find_maven_basedir" - return 1 - fi - - basedir="$1" - wdir="$1" - while [ "$wdir" != '/' ] ; do - if [ -d "$wdir"/.mvn ] ; then - basedir=$wdir - break - fi - # workaround for JBEAP-8937 (on Solaris 10/Sparc) - if [ -d "${wdir}" ]; then - wdir=`cd "$wdir/.."; pwd` - fi - # end of workaround - done - echo "${basedir}" -} - -# concatenates all lines of a file -concat_lines() { - if [ -f "$1" ]; then - echo "$(tr -s '\n' ' ' < "$1")" - fi -} - -BASE_DIR=`find_maven_basedir "$(pwd)"` -if [ -z "$BASE_DIR" ]; then - exit 1; -fi - -########################################################################################## -# Extension to allow automatically downloading the maven-wrapper.jar from Maven-central -# This allows using the maven wrapper in projects that prohibit checking in binary data. -########################################################################################## -if [ -r "$BASE_DIR/.mvn/wrapper/maven-wrapper.jar" ]; then - if [ "$MVNW_VERBOSE" = true ]; then - echo "Found .mvn/wrapper/maven-wrapper.jar" - fi -else - if [ "$MVNW_VERBOSE" = true ]; then - echo "Couldn't find .mvn/wrapper/maven-wrapper.jar, downloading it ..." - fi - jarUrl="https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.4.0/maven-wrapper-0.4.0.jar" - while IFS="=" read key value; do - case "$key" in (wrapperUrl) jarUrl="$value"; break ;; - esac - done < "$BASE_DIR/.mvn/wrapper/maven-wrapper.properties" - if [ "$MVNW_VERBOSE" = true ]; then - echo "Downloading from: $jarUrl" - fi - wrapperJarPath="$BASE_DIR/.mvn/wrapper/maven-wrapper.jar" - - if command -v wget > /dev/null; then - if [ "$MVNW_VERBOSE" = true ]; then - echo "Found wget ... using wget" - fi - wget "$jarUrl" -O "$wrapperJarPath" - elif command -v curl > /dev/null; then - if [ "$MVNW_VERBOSE" = true ]; then - echo "Found curl ... using curl" - fi - curl -o "$wrapperJarPath" "$jarUrl" - else - if [ "$MVNW_VERBOSE" = true ]; then - echo "Falling back to using Java to download" - fi - javaClass="$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.java" - if [ -e "$javaClass" ]; then - if [ ! -e "$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.class" ]; then - if [ "$MVNW_VERBOSE" = true ]; then - echo " - Compiling MavenWrapperDownloader.java ..." - fi - # Compiling the Java class - ("$JAVA_HOME/bin/javac" "$javaClass") - fi - if [ -e "$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.class" ]; then - # Running the downloader - if [ "$MVNW_VERBOSE" = true ]; then - echo " - Running MavenWrapperDownloader.java ..." - fi - ("$JAVA_HOME/bin/java" -cp .mvn/wrapper MavenWrapperDownloader "$MAVEN_PROJECTBASEDIR") - fi - fi - fi -fi -########################################################################################## -# End of extension -########################################################################################## - -export MAVEN_PROJECTBASEDIR=${MAVEN_BASEDIR:-"$BASE_DIR"} -if [ "$MVNW_VERBOSE" = true ]; then - echo $MAVEN_PROJECTBASEDIR -fi -MAVEN_OPTS="$(concat_lines "$MAVEN_PROJECTBASEDIR/.mvn/jvm.config") $MAVEN_OPTS" - -# For Cygwin, switch paths to Windows format before running java -if $cygwin; then - [ -n "$M2_HOME" ] && - M2_HOME=`cygpath --path --windows "$M2_HOME"` - [ -n "$JAVA_HOME" ] && - JAVA_HOME=`cygpath --path --windows "$JAVA_HOME"` - [ -n "$CLASSPATH" ] && - CLASSPATH=`cygpath --path --windows "$CLASSPATH"` - [ -n "$MAVEN_PROJECTBASEDIR" ] && - MAVEN_PROJECTBASEDIR=`cygpath --path --windows "$MAVEN_PROJECTBASEDIR"` -fi - -WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain - -exec "$JAVACMD" \ - $MAVEN_OPTS \ - -classpath "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.jar" \ - "-Dmaven.home=${M2_HOME}" "-Dmaven.multiModuleProjectDirectory=${MAVEN_PROJECTBASEDIR}" \ - ${WRAPPER_LAUNCHER} $MAVEN_CONFIG "$@" diff --git a/code-samples/community/serving/helloworld-java-micronaut/mvnw.cmd b/code-samples/community/serving/helloworld-java-micronaut/mvnw.cmd deleted file mode 100644 index 03d90e960b..0000000000 --- a/code-samples/community/serving/helloworld-java-micronaut/mvnw.cmd +++ /dev/null @@ -1,161 +0,0 @@ -@REM ---------------------------------------------------------------------------- -@REM Licensed to the Apache Software Foundation (ASF) under one -@REM or more contributor license agreements. See the NOTICE file -@REM distributed with this work for additional information -@REM regarding copyright ownership. The ASF licenses this file -@REM to you under the Apache License, Version 2.0 (the -@REM "License"); you may not use this file except in compliance -@REM with the License. You may obtain a copy of the License at -@REM -@REM http://www.apache.org/licenses/LICENSE-2.0 -@REM -@REM Unless required by applicable law or agreed to in writing, -@REM software distributed under the License is distributed on an -@REM "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY -@REM KIND, either express or implied. See the License for the -@REM specific language governing permissions and limitations -@REM under the License. -@REM ---------------------------------------------------------------------------- - -@REM ---------------------------------------------------------------------------- -@REM Maven2 Start Up Batch script -@REM -@REM Required ENV vars: -@REM JAVA_HOME - location of a JDK home dir -@REM -@REM Optional ENV vars -@REM M2_HOME - location of maven2's installed home dir -@REM MAVEN_BATCH_ECHO - set to 'on' to enable the echoing of the batch commands -@REM MAVEN_BATCH_PAUSE - set to 'on' to wait for a key stroke before ending -@REM MAVEN_OPTS - parameters passed to the Java VM when running Maven -@REM e.g. to debug Maven itself, use -@REM set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000 -@REM MAVEN_SKIP_RC - flag to disable loading of mavenrc files -@REM ---------------------------------------------------------------------------- - -@REM Begin all REM lines with '@' in case MAVEN_BATCH_ECHO is 'on' -@echo off -@REM set title of command window -title %0 -@REM enable echoing my setting MAVEN_BATCH_ECHO to 'on' -@if "%MAVEN_BATCH_ECHO%" == "on" echo %MAVEN_BATCH_ECHO% - -@REM set %HOME% to equivalent of $HOME -if "%HOME%" == "" (set "HOME=%HOMEDRIVE%%HOMEPATH%") - -@REM Execute a user defined script before this one -if not "%MAVEN_SKIP_RC%" == "" goto skipRcPre -@REM check for pre script, once with legacy .bat ending and once with .cmd ending -if exist "%HOME%\mavenrc_pre.bat" call "%HOME%\mavenrc_pre.bat" -if exist "%HOME%\mavenrc_pre.cmd" call "%HOME%\mavenrc_pre.cmd" -:skipRcPre - -@setlocal - -set ERROR_CODE=0 - -@REM To isolate internal variables from possible post scripts, we use another setlocal -@setlocal - -@REM ==== START VALIDATION ==== -if not "%JAVA_HOME%" == "" goto OkJHome - -echo. -echo Error: JAVA_HOME not found in your environment. >&2 -echo Please set the JAVA_HOME variable in your environment to match the >&2 -echo location of your Java installation. >&2 -echo. -goto error - -:OkJHome -if exist "%JAVA_HOME%\bin\java.exe" goto init - -echo. -echo Error: JAVA_HOME is set to an invalid directory. >&2 -echo JAVA_HOME = "%JAVA_HOME%" >&2 -echo Please set the JAVA_HOME variable in your environment to match the >&2 -echo location of your Java installation. >&2 -echo. -goto error - -@REM ==== END VALIDATION ==== - -:init - -@REM Find the project base dir, i.e. the directory that contains the folder ".mvn". -@REM Fallback to current working directory if not found. - -set MAVEN_PROJECTBASEDIR=%MAVEN_BASEDIR% -IF NOT "%MAVEN_PROJECTBASEDIR%"=="" goto endDetectBaseDir - -set EXEC_DIR=%CD% -set WDIR=%EXEC_DIR% -:findBaseDir -IF EXIST "%WDIR%"\.mvn goto baseDirFound -cd .. -IF "%WDIR%"=="%CD%" goto baseDirNotFound -set WDIR=%CD% -goto findBaseDir - -:baseDirFound -set MAVEN_PROJECTBASEDIR=%WDIR% -cd "%EXEC_DIR%" -goto endDetectBaseDir - -:baseDirNotFound -set MAVEN_PROJECTBASEDIR=%EXEC_DIR% -cd "%EXEC_DIR%" - -:endDetectBaseDir - -IF NOT EXIST "%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config" goto endReadAdditionalConfig - -@setlocal EnableExtensions EnableDelayedExpansion -for /F "usebackq delims=" %%a in ("%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config") do set JVM_CONFIG_MAVEN_PROPS=!JVM_CONFIG_MAVEN_PROPS! %%a -@endlocal & set JVM_CONFIG_MAVEN_PROPS=%JVM_CONFIG_MAVEN_PROPS% - -:endReadAdditionalConfig - -SET MAVEN_JAVA_EXE="%JAVA_HOME%\bin\java.exe" -set WRAPPER_JAR="%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.jar" -set WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain - -set DOWNLOAD_URL="https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.4.0/maven-wrapper-0.4.0.jar" -FOR /F "tokens=1,2 delims==" %%A IN (%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.properties) DO ( - IF "%%A"=="wrapperUrl" SET DOWNLOAD_URL=%%B -) - -@REM Extension to allow automatically downloading the maven-wrapper.jar from Maven-central -@REM This allows using the maven wrapper in projects that prohibit checking in binary data. -if exist %WRAPPER_JAR% ( - echo Found %WRAPPER_JAR% -) else ( - echo Couldn't find %WRAPPER_JAR%, downloading it ... - echo Downloading from: %DOWNLOAD_URL% - powershell -Command "(New-Object Net.WebClient).DownloadFile('%DOWNLOAD_URL%', '%WRAPPER_JAR%')" - echo Finished downloading %WRAPPER_JAR% -) -@REM End of extension - -%MAVEN_JAVA_EXE% %JVM_CONFIG_MAVEN_PROPS% %MAVEN_OPTS% %MAVEN_DEBUG_OPTS% -classpath %WRAPPER_JAR% "-Dmaven.multiModuleProjectDirectory=%MAVEN_PROJECTBASEDIR%" %WRAPPER_LAUNCHER% %MAVEN_CONFIG% %* -if ERRORLEVEL 1 goto error -goto end - -:error -set ERROR_CODE=1 - -:end -@endlocal & set ERROR_CODE=%ERROR_CODE% - -if not "%MAVEN_SKIP_RC%" == "" goto skipRcPost -@REM check for post script, once with legacy .bat ending and once with .cmd ending -if exist "%HOME%\mavenrc_post.bat" call "%HOME%\mavenrc_post.bat" -if exist "%HOME%\mavenrc_post.cmd" call "%HOME%\mavenrc_post.cmd" -:skipRcPost - -@REM pause the script if MAVEN_BATCH_PAUSE is set to 'on' -if "%MAVEN_BATCH_PAUSE%" == "on" pause - -if "%MAVEN_TERMINATE_CMD%" == "on" exit %ERROR_CODE% - -exit /B %ERROR_CODE% diff --git a/code-samples/community/serving/helloworld-java-micronaut/pom.xml b/code-samples/community/serving/helloworld-java-micronaut/pom.xml index 7e777a28bb..f1e71051b4 100644 --- a/code-samples/community/serving/helloworld-java-micronaut/pom.xml +++ b/code-samples/community/serving/helloworld-java-micronaut/pom.xml @@ -1,172 +1,72 @@ - + + 4.0.0 + + io.micronaut.platform + micronaut-parent + 5.2.3 + + com.example.micronaut helloworld 1.0.0-SNAPSHOT - 1.1.0 - 1.8 - UTF-8 + 25 + 25 com.example.helloworld.Application - - - - io.micronaut - micronaut-bom - ${micronaut.version} - pom - import - - - io.micronaut - micronaut-inject - compile - - - io.micronaut - micronaut-validation - compile + micronaut-runtime io.micronaut - micronaut-runtime - compile + micronaut-http-server-netty + io.micronaut - micronaut-http-client - compile + micronaut-jackson-databind + - io.micronaut - micronaut-http-server-netty - compile + org.yaml + snakeyaml + runtime + ch.qos.logback logback-classic - 1.3.12 runtime - - org.junit.jupiter - junit-jupiter-api - test - - - org.junit.jupiter - junit-jupiter-engine - test - - - io.micronaut.test - micronaut-test-junit5 - test - + + + org.apache.maven.plugins + maven-compiler-plugin + org.apache.maven.plugins maven-shade-plugin - 3.1.0 + default-shade package shade - - - ${exec.mainClass} - - - + false - - org.codehaus.mojo - exec-maven-plugin - 1.6.0 - - java - - - -noverify - -Dmicronaut.env.deduction=false - -XX:TieredStopAtLevel=1 - -Dcom.sun.management.jmxremote - -classpath - - ${exec.mainClass} - - - - - maven-surefire-plugin - 2.22.0 - - - - - org.apache.maven.plugins - maven-compiler-plugin - 3.7.0 - - ${jdk.version} - ${jdk.version} - UTF-8 - - -parameters - - - - io.micronaut - micronaut-inject-java - ${micronaut.version} - - - io.micronaut - micronaut-validation - ${micronaut.version} - - - - - - test-compile - - testCompile - - - - -parameters - - - - io.micronaut - micronaut-inject-java - ${micronaut.version} - - - io.micronaut - micronaut-validation - ${micronaut.version} - - - - - - - - diff --git a/code-samples/community/serving/helloworld-java-micronaut/src/main/java/com/example/helloworld/Application.java b/code-samples/community/serving/helloworld-java-micronaut/src/main/java/com/example/helloworld/Application.java index 090c821056..10824fb32f 100644 --- a/code-samples/community/serving/helloworld-java-micronaut/src/main/java/com/example/helloworld/Application.java +++ b/code-samples/community/serving/helloworld-java-micronaut/src/main/java/com/example/helloworld/Application.java @@ -5,6 +5,6 @@ public class Application { public static void main(String[] args) { - Micronaut.run(Application.class); + Micronaut.run(Application.class, args); } } diff --git a/code-samples/community/serving/helloworld-java-micronaut/src/main/resources/logback.xml b/code-samples/community/serving/helloworld-java-micronaut/src/main/resources/logback.xml index 6010eb5233..2d77bdab53 100644 --- a/code-samples/community/serving/helloworld-java-micronaut/src/main/resources/logback.xml +++ b/code-samples/community/serving/helloworld-java-micronaut/src/main/resources/logback.xml @@ -1,7 +1,6 @@ - true diff --git a/code-samples/community/serving/helloworld-java-micronaut/src/test/java/com/example/helloworld/HelloWorldControllerTest.java b/code-samples/community/serving/helloworld-java-micronaut/src/test/java/com/example/helloworld/HelloWorldControllerTest.java deleted file mode 100644 index 2765916534..0000000000 --- a/code-samples/community/serving/helloworld-java-micronaut/src/test/java/com/example/helloworld/HelloWorldControllerTest.java +++ /dev/null @@ -1,28 +0,0 @@ -package com.example.helloworld; - -import static org.junit.jupiter.api.Assertions.assertEquals; - -import javax.inject.Inject; - -import org.junit.jupiter.api.Test; - -import io.micronaut.http.HttpResponse; -import io.micronaut.http.HttpStatus; -import io.micronaut.http.client.RxHttpClient; -import io.micronaut.http.client.annotation.Client; -import io.micronaut.test.annotation.MicronautTest; - -@MicronautTest -public class HelloWorldControllerTest { - - @Inject - @Client("/") - RxHttpClient client; - - @Test - public void testIndex() throws Exception { - HttpResponse response = client.toBlocking().exchange("/", String.class); - assertEquals(HttpStatus.OK, response.status()); - assertEquals("Hello World: NOT SPECIFIED", response.body()); - } -} From 84e567440e1a17e534f43f329faf92574bb20389 Mon Sep 17 00:00:00 2001 From: Dave Protasowski Date: Mon, 26 Oct 2026 09:16:03 -0400 Subject: [PATCH 07/44] Update the Quarkus Hello World sample --- .../.mvn/wrapper/MavenWrapperDownloader.java | 117 ------- .../.mvn/wrapper/maven-wrapper.properties | 2 - .../helloworld-java-quarkus/Dockerfile | 33 +- .../helloworld-java-quarkus/Dockerfile.native | 15 - .../serving/helloworld-java-quarkus/README.md | 325 +++++------------- .../serving/helloworld-java-quarkus/ci.sh | 40 +++ .../serving/helloworld-java-quarkus/mvnw | 305 ---------------- .../serving/helloworld-java-quarkus/mvnw.cmd | 172 --------- .../serving/helloworld-java-quarkus/pom.xml | 94 ++--- .../helloworld-java-quarkus/service.yaml | 2 +- .../developer/demos/GreetingResource.java | 23 +- .../src/main/resources/application.properties | 5 +- .../developer/demos/GreetingResourceTest.java | 21 -- .../demos/NativeGreetingResourceIT.java | 9 - 14 files changed, 185 insertions(+), 978 deletions(-) delete mode 100644 code-samples/community/serving/helloworld-java-quarkus/.mvn/wrapper/MavenWrapperDownloader.java delete mode 100644 code-samples/community/serving/helloworld-java-quarkus/.mvn/wrapper/maven-wrapper.properties delete mode 100644 code-samples/community/serving/helloworld-java-quarkus/Dockerfile.native create mode 100755 code-samples/community/serving/helloworld-java-quarkus/ci.sh delete mode 100644 code-samples/community/serving/helloworld-java-quarkus/mvnw delete mode 100644 code-samples/community/serving/helloworld-java-quarkus/mvnw.cmd delete mode 100644 code-samples/community/serving/helloworld-java-quarkus/src/test/java/com/redhat/developer/demos/GreetingResourceTest.java delete mode 100644 code-samples/community/serving/helloworld-java-quarkus/src/test/java/com/redhat/developer/demos/NativeGreetingResourceIT.java diff --git a/code-samples/community/serving/helloworld-java-quarkus/.mvn/wrapper/MavenWrapperDownloader.java b/code-samples/community/serving/helloworld-java-quarkus/.mvn/wrapper/MavenWrapperDownloader.java deleted file mode 100644 index b20a55a7a9..0000000000 --- a/code-samples/community/serving/helloworld-java-quarkus/.mvn/wrapper/MavenWrapperDownloader.java +++ /dev/null @@ -1,117 +0,0 @@ -/* - * Copyright 2007-present the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -import java.net.*; -import java.io.*; -import java.nio.channels.*; -import java.util.Properties; - -public class MavenWrapperDownloader { - - private static final String WRAPPER_VERSION = "0.5.3"; - /** - * Default URL to download the maven-wrapper.jar from, if no 'downloadUrl' is provided. - */ - private static final String DEFAULT_DOWNLOAD_URL = "https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/" - + WRAPPER_VERSION + "/maven-wrapper-" + WRAPPER_VERSION + " .jar"; - - /** - * Path to the maven-wrapper.properties file, which might contain a downloadUrl property to - * use instead of the default one. - */ - private static final String MAVEN_WRAPPER_PROPERTIES_PATH = - ".mvn/wrapper/maven-wrapper.properties"; - - /** - * Path where the maven-wrapper.jar will be saved to. - */ - private static final String MAVEN_WRAPPER_JAR_PATH = - ".mvn/wrapper/maven-wrapper.jar"; - - /** - * Name of the property which should be used to override the default download url for the wrapper. - */ - private static final String PROPERTY_NAME_WRAPPER_URL = "wrapperUrl"; - - public static void main(String args[]) { - System.out.println("- Downloader started"); - File baseDirectory = new File(args[0]); - System.out.println("- Using base directory: " + baseDirectory.getAbsolutePath()); - - // If the maven-wrapper.properties exists, read it and check if it contains a custom - // wrapperUrl parameter. - File mavenWrapperPropertyFile = new File(baseDirectory, MAVEN_WRAPPER_PROPERTIES_PATH); - String url = DEFAULT_DOWNLOAD_URL; - if(mavenWrapperPropertyFile.exists()) { - FileInputStream mavenWrapperPropertyFileInputStream = null; - try { - mavenWrapperPropertyFileInputStream = new FileInputStream(mavenWrapperPropertyFile); - Properties mavenWrapperProperties = new Properties(); - mavenWrapperProperties.load(mavenWrapperPropertyFileInputStream); - url = mavenWrapperProperties.getProperty(PROPERTY_NAME_WRAPPER_URL, url); - } catch (IOException e) { - System.out.println("- ERROR loading '" + MAVEN_WRAPPER_PROPERTIES_PATH + "'"); - } finally { - try { - if(mavenWrapperPropertyFileInputStream != null) { - mavenWrapperPropertyFileInputStream.close(); - } - } catch (IOException e) { - // Ignore ... - } - } - } - System.out.println("- Downloading from: " + url); - - File outputFile = new File(baseDirectory.getAbsolutePath(), MAVEN_WRAPPER_JAR_PATH); - if(!outputFile.getParentFile().exists()) { - if(!outputFile.getParentFile().mkdirs()) { - System.out.println( - "- ERROR creating output directory '" + outputFile.getParentFile().getAbsolutePath() + "'"); - } - } - System.out.println("- Downloading to: " + outputFile.getAbsolutePath()); - try { - downloadFileFromURL(url, outputFile); - System.out.println("Done"); - System.exit(0); - } catch (Throwable e) { - System.out.println("- Error downloading"); - e.printStackTrace(); - System.exit(1); - } - } - - private static void downloadFileFromURL(String urlString, File destination) throws Exception { - if (System.getenv("MVNW_USERNAME") != null && System.getenv("MVNW_PASSWORD") != null) { - String username = System.getenv("MVNW_USERNAME"); - char[] password = System.getenv("MVNW_PASSWORD").toCharArray(); - Authenticator.setDefault(new Authenticator() { - @Override - protected PasswordAuthentication getPasswordAuthentication() { - return new PasswordAuthentication(username, password); - } - }); - } - URL website = new URL(urlString); - ReadableByteChannel rbc; - rbc = Channels.newChannel(website.openStream()); - FileOutputStream fos = new FileOutputStream(destination); - fos.getChannel().transferFrom(rbc, 0, Long.MAX_VALUE); - fos.close(); - rbc.close(); - } - -} diff --git a/code-samples/community/serving/helloworld-java-quarkus/.mvn/wrapper/maven-wrapper.properties b/code-samples/community/serving/helloworld-java-quarkus/.mvn/wrapper/maven-wrapper.properties deleted file mode 100644 index dbcb670a54..0000000000 --- a/code-samples/community/serving/helloworld-java-quarkus/.mvn/wrapper/maven-wrapper.properties +++ /dev/null @@ -1,2 +0,0 @@ -distributionUrl=http://repo2.maven.org/maven2/org/apache/maven/apache-maven/3.6.0/apache-maven-3.6.0-bin.zip -wrapperUrl=http://repo2.maven.org/maven2/io/takari/maven-wrapper/0.5.4/maven-wrapper-0.5.4.jar diff --git a/code-samples/community/serving/helloworld-java-quarkus/Dockerfile b/code-samples/community/serving/helloworld-java-quarkus/Dockerfile index 88b9adb5ea..483f9ca4bf 100644 --- a/code-samples/community/serving/helloworld-java-quarkus/Dockerfile +++ b/code-samples/community/serving/helloworld-java-quarkus/Dockerfile @@ -1,18 +1,15 @@ -FROM quay.io/rhdevelopers/quarkus-java-builder as builder -COPY . /project -WORKDIR /project -# uncomment this to set the MAVEN_MIRROR_URL of your choice, to make faster builds -# ARG MAVEN_MIRROR_URL= -# e.g. -#ARG MAVEN_MIRROR_URL=http://192.168.64.1:8081/nexus/content/groups/public - -RUN /usr/local/bin/entrypoint-run.sh mvn -DskipTests clean package - -FROM fabric8/java-jboss-openjdk8-jdk:1.5.4 -USER jboss -ENV JAVA_APP_DIR=/deployments - -COPY --from=builder /project/target/lib/* /deployments/lib/ -COPY --from=builder /project/target/*-runner.jar /deployments/app.jar - -ENTRYPOINT [ "/deployments/run-java.sh" ] +# Build with a supported Java LTS and Maven release. +FROM maven:3.9.16-eclipse-temurin-25 AS build +WORKDIR /workspace +ENV MAVEN_OPTS="-Xmx768m -XX:ActiveProcessorCount=2" +COPY pom.xml ./ +COPY src ./src +RUN mvn -B -ntp clean verify + +# Quarkus fast-jar: copy the entire directory, not only quarkus-run.jar. +FROM eclipse-temurin:25-jre +WORKDIR /app +COPY --from=build --chown=1001:0 /workspace/target/quarkus-app/ ./ +USER 1001 +EXPOSE 8080 +ENTRYPOINT ["/bin/sh", "-c", "export PORT=\"${PORT:-8080}\"; exec java -XX:MaxRAMPercentage=75.0 -XX:ActiveProcessorCount=2 -jar /app/quarkus-run.jar"] diff --git a/code-samples/community/serving/helloworld-java-quarkus/Dockerfile.native b/code-samples/community/serving/helloworld-java-quarkus/Dockerfile.native deleted file mode 100644 index 27bd8a7e70..0000000000 --- a/code-samples/community/serving/helloworld-java-quarkus/Dockerfile.native +++ /dev/null @@ -1,15 +0,0 @@ -FROM quay.io/rhdevelopers/quarkus-java-builder:graal-1.0.0-rc15 as builder -COPY . /project - -# uncomment this to set the MAVEN_MIRROR_URL of your choice, to make faster builds -# ARG MAVEN_MIRROR_URL= -# e.g. -# ARG MAVEN_MIRROR_URL=http://192.168.64.1:8081/nexus/content/groups/public - -RUN /usr/local/bin/entrypoint-run.sh mvn -DskipTests clean package -Pnative - -FROM registry.fedoraproject.org/fedora-minimal - -COPY --from=builder /project/target/helloworld-java-quarkus-1.0-SNAPSHOT-runner /app - -ENTRYPOINT [ "/app" ] diff --git a/code-samples/community/serving/helloworld-java-quarkus/README.md b/code-samples/community/serving/helloworld-java-quarkus/README.md index bc10c06198..a4ba6693dc 100644 --- a/code-samples/community/serving/helloworld-java-quarkus/README.md +++ b/code-samples/community/serving/helloworld-java-quarkus/README.md @@ -1,269 +1,132 @@ -# Hello World - Java (Quarkus) +# Hello World — Java (Quarkus) -A simple [JAX-RS REST API](https://github.com/jax-rs) application that is -written in Java and uses [Quarkus](https://quarkus.io/). +A small [Quarkus REST](https://quarkus.io/guides/rest) application for Knative +Serving, using **Quarkus 3.40.1.1 LTS**, **Eclipse Temurin Java 25 LTS** and +**Maven 3.9.16**. Docker builds the application; no local JDK or Maven +installation is required. -This samples uses Docker to build locally. The app reads in a `TARGET` env -variable and then prints "Hello World: \${TARGET}!". If a value for `TARGET` is -not specified, the "NOT SPECIFIED" default value is used. +GET `/` returns HTTP 200 and `text/plain` with exactly `Hello ${TARGET}!`. +`TARGET` defaults to `World` (also when empty). The server binds to `0.0.0.0`; +`PORT` defaults to 8080 (including an empty value in the container). HEAD `/` +returns 200 and POST `/` returns 405. -## Before you begin +## Prerequisites -You must meet the following requirements to run this sample: +- Docker (Linux arm64 and amd64 are supported by the selected toolchain images). +- For deployment: a Kubernetes cluster with [Knative Serving and an ingress + provider installed](https://knative.dev/docs/install/), and `kubectl`. +- A container registry accessible to your cluster. Registry publication requires + your own account; it is unnecessary for local testing. -- Have a Kubernetes cluster running with the Knative Serving component - installed. For more information, see the - [Knative installation instructions](https://knative.dev/docs/install/). -- An installed version of the following tools: - - [Docker](https://www.docker.com) - - [Java SE 8 or later JDK](https://www.eclipse.org/openj9/) - - [Maven](https://maven.apache.org/download.cgi) -- A [Docker Hub account](https://hub.docker.com/) to which you are able to - upload your sample's container image. - -## Getting the code - -You can either clone a working copy of the sample code from the repository, or -following the steps in the -[Recreating the sample code](#recreating-the-sample-code) to walk through the -steps of updating all the files. - -### Cloning the sample code - -Use this method to clone and then immediate run the sample. To clone the sample -code, run the following commands: +## Get the sample ```bash -git clone https://github.com/knative/docs.git knative/docs -cd knative/docs/code-samples/community/serving/helloworld-java-quarkus +git clone https://github.com/knative/docs.git knative-docs +cd knative-docs/code-samples/community/serving/helloworld-java-quarkus ``` -You are now ready to [run the sample locally](#locally-testing-your-sample). - -### Recreating the sample code - -Use the following steps to obtain an incomplete copy of the sample code for -which you update and create the necessary build and configuration files: - -1. From the console, create a new empty web project using the Maven archetype - commands: - - ```bash - mvn io.quarkus:quarkus-maven-plugin:0.13.3:create \ - -DprojectGroupId=com.redhat.developer.demos \ - -DprojectArtifactId=helloworld-java-quarkus \ - -DclassName="com.redhat.developer.demos.GreetingResource" \ - -Dpath="/" - ``` - -1. Update the `GreetingResource` class in - `src/main/java/com/redhat/developer/demos/GreetingResource.java` to handle - the "/" mapping and also add a `@ConfigProperty` field to provide the TARGET - environment variable: - - ```java - package com.redhat.developer.demos; - - import javax.ws.rs.GET; - import javax.ws.rs.Path; - import javax.ws.rs.Produces; - import javax.ws.rs.core.MediaType; - import org.eclipse.microprofile.config.inject.ConfigProperty; - - @Path("/") - public class GreeterResource { - @ConfigProperty(name = "TARGET", defaultValue="World") - String target; - - @GET - @Produces(MediaType.TEXT_PLAIN) - public String greet() { - return "Hello " + target + "!"; - } - } - ``` - -1. Update `src/main/resources/application.properties` to configuration the - application to default to port 8080, but allow the port to be overridden by - the `PORT` environmental variable: - - ``` - # Configuration file - # key = value - - quarkus.http.port=${PORT:8080} - ``` - -1. Update `src/test/java/com/redhat/developer/demos/GreetingResourceTest.java` - test to reflect the change: - - ```java - package com.redhat.developer.demos; - - import io.quarkus.test.junit.QuarkusTest; - import org.junit.jupiter.api.Test; - - import static io.restassured.RestAssured.given; - import static org.hamcrest.CoreMatchers.is; - - @QuarkusTest - public class GreetingResourceTest { - - @Test - public void testHelloEndpoint() { - given() - .when().get("/") - .then() - .statusCode(200) - .body(is("Hello World!")); - } - } - - ``` - -1. Remove `src/main/resources/META-INF/resources/index.html` file since it's - unncessary for this example. - - ```bash - rm src/main/resources/META-INF/resources/index.html - ``` - -1. Remove `.dockerignore` file since it's unncessary for this example. - - ```bash - rm .dockerignore - ``` - -1. In your project directory, create a file named `Dockerfile` and copy the code - block below into it. +## Source and configuration - ```docker - FROM quay.io/rhdevelopers/quarkus-java-builder:graal-1.0.0-rc15 as builder - COPY . /project - WORKDIR /project - # uncomment this to set the MAVEN_MIRROR_URL of your choice, to make faster builds - # ARG MAVEN_MIRROR_URL= - # e.g. - #ARG MAVEN_MIRROR_URL=http://192.168.64.1:8081/nexus/content/groups/public - - RUN /usr/local/bin/entrypoint-run.sh mvn -DskipTests clean package +Use the checked-in [GreetingResource.java](src/main/java/com/redhat/developer/demos/GreetingResource.java), +which uses Jakarta REST APIs and MicroProfile Config to return the greeting. +Its optional `target` configuration treats both missing and empty values as +`World`. The checked-in [application.properties](src/main/resources/application.properties) +sets the external binding and configurable port. - FROM fabric8/java-jboss-openjdk8-jdk:1.5.4 - USER jboss - ENV JAVA_APP_DIR=/deployments +The [pom.xml](pom.xml) imports `io.quarkus.platform:quarkus-bom:3.40.1.1` and uses +`quarkus-rest`. The build runs Java compiler lint and Quarkus fast-jar packaging. - COPY --from=builder /project/target/lib/* /deployments/lib/ - COPY --from=builder /project/target/*-runner.jar /deployments/app.jar +## Build with Docker - ENTRYPOINT [ "/deployments/run-java.sh" ] - ``` +From the sample directory: - If you want to build Quarkus native image, then copy the following code block - in to file called `Dockerfile.native` - - ```docker - FROM quay.io/rhdevelopers/quarkus-java-builder:graal-1.0.0-rc15 as builder - COPY . /project - # uncomment this to set the MAVEN_MIRROR_URL of your choice, to make faster builds - # ARG MAVEN_MIRROR_URL= - # e.g. - # ARG MAVEN_MIRROR_URL=http://192.168.64.1:8081/nexus/content/groups/public - - RUN /usr/local/bin/entrypoint-run.sh mvn -DskipTests clean package -Pnative - - FROM registry.fedoraproject.org/fedora-minimal - - COPY --from=builder /project/target/helloworld-java-quarkus-runner /app - - ENTRYPOINT [ "/app" ] - ``` - -1. Create a new file, `service.yaml` and copy the following service definition - into the file. Make sure to replace `{username}` with your Docker Hub - username. - - ```yaml - apiVersion: serving.knative.dev/v1 - kind: Service - metadata: - name: helloworld-java-quarkus - spec: - template: - spec: - containers: - - image: docker.io/{username}/helloworld-java-quarkus - env: - - name: TARGET - value: "Quarkus Sample v1" - ``` +```bash +# Compile and package in the build stage. +docker build --target build -t helloworld-java-quarkus-build . -## Locally testing your sample +# Build the actual production image. +docker build -t helloworld-java-quarkus . +``` -1. Run the application locally: +The multistage [Dockerfile](Dockerfile) uses Maven/Temurin 25 for compilation and a +Temurin 25 JRE for runtime. It copies the **entire** `target/quarkus-app/` +fast-jar directory and runs as non-root UID 1001. - ```bash - mvn wrapper:wrapper - ./mvnw compile quarkus:dev - ``` +Run locally in one terminal: - Go to `http://localhost:8080/` to see your `Hello World!` message. +```bash +docker run --rm -p 8080:8080 helloworld-java-quarkus +``` -## Building and deploying the sample +Then, in another terminal: -Once you have recreated the sample code files (or used the files in the sample -folder) you're ready to build and deploy the sample app. +```bash +curl -fsS http://localhost:8080/ +# Hello World! +``` -1. Use Docker to build the sample code into a container. To build and push with - Docker Hub, run these commands replacing `{username}` with your Docker Hub - username: +To exercise custom configuration, stop the first container and run: - ```bash - # Build and push the container on your local machine. - docker buildx build --platform linux/arm64,linux/amd64 -t "{username}/helloworld-java-quarkus" --push . +```bash +docker run --rm -p 9090:9090 -e PORT=9090 -e 'TARGET=Quarkus Sample v1' \ + helloworld-java-quarkus +``` - # (OR) - # Build and push the container on your local machine. - Quarkus native mode - docker buildx build --platform linux/arm64,linux/amd64 -t "{username}/helloworld-java-quarkus" --push . -f Dockerfile.native - ``` +```bash +curl -fsS http://localhost:9090/ +# Hello Quarkus Sample v1! +``` -1. After the build has completed and the container is pushed to docker hub, you - can deploy the app into your cluster. Ensure that the container image value - in `service.yaml` matches the container you built in the previous step. Apply - the configuration using `kubectl`: +### Optional local development - ```bash - kubectl apply --filename service.yaml - ``` +With a supported Java 25 JDK and Maven 3.9.16 installed (or use the Docker +build above without a local toolchain): -1. Now that your service is created, Knative will perform the following steps: +```bash +mvn clean verify +mvn quarkus:dev +``` - - Create a new immutable revision for this version of the app. - - Network programming to create a route, ingress, service, and load balancer - for your app. - - Automatically scale your pods up and down (including to zero active pods). +## Publish and deploy -1. To find the URL for your service, use +Replace `{username}` below and in [service.yaml](service.yaml) with your registry namespace. +For an arm64 cluster use `linux/arm64`; for an amd64 cluster use `linux/amd64`. +To publish both architectures using a configured buildx builder: - ```bash - kubectl get ksvc helloworld-java-quarkus +```bash +docker buildx build --platform linux/arm64,linux/amd64 \ + -t '{username}/helloworld-java-quarkus' --push . +``` - NAME URL - helloworld-java-quarkus http://helloworld-java-quarkus.default.1.2.3.4.sslip.io - ``` +The checked-in [Service manifest](service.yaml) uses the image you publish and sets +`TARGET=Quarkus Sample v1`. It routes to container port 8080. Knative injects +`PORT` from `containerPort`; do not set the reserved `PORT` environment variable +in a Knative Service. To test a different port there, set `containerPort` to an +unreserved value such as 8088 (9090 is reserved by Knative). The explicit +`PORT=9090` Docker command above is for standalone local testing only. -1. Now you can make a request to your app and see the result. Replace - the URL below with the URL returned in the previous command. +```bash +kubectl apply -f service.yaml +kubectl wait --for=condition=Ready ksvc/helloworld-java-quarkus --timeout=180s +kubectl get ksvc helloworld-java-quarkus +``` - ```bash - curl http://helloworld-java-quarkus.default.1.2.3.4.sslip.io +Request the URL returned by Knative: - Namaste Knative World! - ``` +```bash +URL=$(kubectl get ksvc helloworld-java-quarkus -o jsonpath='{.status.url}') +curl -fsS "$URL" +# Hello Quarkus Sample v1! +``` -## Removing the sample app deployment +For an isolated kind cluster, publishing can be avoided by tagging the local +image `dev.local/helloworld-java-quarkus:local`, loading it into the node with +`kind load docker-image`, and adapting the Service image reference with +`imagePullPolicy: IfNotPresent`. Use your own namespace and the ingress setup +of that cluster; do not reuse a remote maintainer's image as a substitute. -To remove the sample app from your cluster, delete the service record: +## Remove the deployment ```bash -kubectl delete --filename service.yaml +kubectl delete -f service.yaml ``` diff --git a/code-samples/community/serving/helloworld-java-quarkus/ci.sh b/code-samples/community/serving/helloworld-java-quarkus/ci.sh new file mode 100755 index 0000000000..961a256813 --- /dev/null +++ b/code-samples/community/serving/helloworld-java-quarkus/ci.sh @@ -0,0 +1,40 @@ +#!/usr/bin/env bash +# Source from this sample directory; the caller owns cluster setup and ingress forwarding. +IMAGE=dev.local/helloworld-java-quarkus:ci +SAMPLE_URL=http://127.0.0.1:18080 + +main() ( + set -euo pipefail + build + deploy + test +) + +build() ( + set -euo pipefail + docker build -t "$IMAGE" . +) + +deploy() ( + set -euo pipefail + kind load docker-image "$IMAGE" + kubectl patch --local -f service.yaml --type=json -p "$( + jq -cn --arg image "$IMAGE" '[ + {op: "replace", path: "/spec/template/spec/containers/0/image", value: $image}, + {op: "add", path: "/spec/template/spec/containers/0/imagePullPolicy", value: "IfNotPresent"} + ]' + )" -o yaml | kubectl --namespace default apply -f - + kubectl --namespace default wait -f service.yaml --for=condition=Ready --timeout=300s +) + +test() ( + set -euo pipefail + local service_url service_host + service_url=$(kubectl --namespace default get -f service.yaml -o jsonpath='{.status.url}') + service_host=${service_url#*://} + [[ "$service_host" =~ ^[a-zA-Z0-9.-]+$ ]] + local body + body=$(curl -fsS --max-time 30 \ + -H "Host: $service_host" "$SAMPLE_URL/") + [[ $body == 'Hello Quarkus Sample v1!' ]] +) diff --git a/code-samples/community/serving/helloworld-java-quarkus/mvnw b/code-samples/community/serving/helloworld-java-quarkus/mvnw deleted file mode 100644 index eefb60d678..0000000000 --- a/code-samples/community/serving/helloworld-java-quarkus/mvnw +++ /dev/null @@ -1,305 +0,0 @@ -#!/bin/sh -# ---------------------------------------------------------------------------- -# Licensed to the Apache Software Foundation (ASF) under one -# or more contributor license agreements. See the NOTICE file -# distributed with this work for additional information -# regarding copyright ownership. The ASF licenses this file -# to you under the Apache License, Version 2.0 (the -# "License"); you may not use this file except in compliance -# with the License. You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, -# software distributed under the License is distributed on an -# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY -# KIND, either express or implied. See the License for the -# specific language governing permissions and limitations -# under the License. -# ---------------------------------------------------------------------------- - -# ---------------------------------------------------------------------------- -# Maven2 Start Up Batch script -# -# Required ENV vars: -# ------------------ -# JAVA_HOME - location of a JDK home dir -# -# Optional ENV vars -# ----------------- -# M2_HOME - location of maven2's installed home dir -# MAVEN_OPTS - parameters passed to the Java VM when running Maven -# e.g. to debug Maven itself, use -# set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000 -# MAVEN_SKIP_RC - flag to disable loading of mavenrc files -# ---------------------------------------------------------------------------- - -if [ -z "$MAVEN_SKIP_RC" ] ; then - - if [ -f /etc/mavenrc ] ; then - . /etc/mavenrc - fi - - if [ -f "$HOME/.mavenrc" ] ; then - . "$HOME/.mavenrc" - fi - -fi - -# OS specific support. $var _must_ be set to either true or false. -cygwin=false; -darwin=false; -mingw=false -case "`uname`" in - CYGWIN*) cygwin=true ;; - MINGW*) mingw=true;; - Darwin*) darwin=true - # Use /usr/libexec/java_home if available, otherwise fall back to /Library/Java/Home - # See https://developer.apple.com/library/mac/qa/qa1170/_index.html - if [ -z "$JAVA_HOME" ]; then - if [ -x "/usr/libexec/java_home" ]; then - export JAVA_HOME="`/usr/libexec/java_home`" - else - export JAVA_HOME="/Library/Java/Home" - fi - fi - ;; -esac - -if [ -z "$JAVA_HOME" ] ; then - if [ -r /etc/gentoo-release ] ; then - JAVA_HOME=`java-config --jre-home` - fi -fi - -if [ -z "$M2_HOME" ] ; then - ## resolve links - $0 may be a link to maven's home - PRG="$0" - - # need this for relative symlinks - while [ -h "$PRG" ] ; do - ls=`ls -ld "$PRG"` - link=`expr "$ls" : '.*-> \(.*\)$'` - if expr "$link" : '/.*' > /dev/null; then - PRG="$link" - else - PRG="`dirname "$PRG"`/$link" - fi - done - - saveddir=`pwd` - - M2_HOME=`dirname "$PRG"`/.. - - # make it fully qualified - M2_HOME=`cd "$M2_HOME" && pwd` - - cd "$saveddir" - # echo Using m2 at $M2_HOME -fi - -# For Cygwin, ensure paths are in UNIX format before anything is touched -if $cygwin ; then - [ -n "$M2_HOME" ] && - M2_HOME=`cygpath --unix "$M2_HOME"` - [ -n "$JAVA_HOME" ] && - JAVA_HOME=`cygpath --unix "$JAVA_HOME"` - [ -n "$CLASSPATH" ] && - CLASSPATH=`cygpath --path --unix "$CLASSPATH"` -fi - -# For Mingw, ensure paths are in UNIX format before anything is touched -if $mingw ; then - [ -n "$M2_HOME" ] && - M2_HOME="`(cd "$M2_HOME"; pwd)`" - [ -n "$JAVA_HOME" ] && - JAVA_HOME="`(cd "$JAVA_HOME"; pwd)`" -fi - -if [ -z "$JAVA_HOME" ]; then - javaExecutable="`which javac`" - if [ -n "$javaExecutable" ] && ! [ "`expr \"$javaExecutable\" : '\([^ ]*\)'`" = "no" ]; then - # readlink(1) is not available as standard on Solaris 10. - readLink=`which readlink` - if [ ! `expr "$readLink" : '\([^ ]*\)'` = "no" ]; then - if $darwin ; then - javaHome="`dirname \"$javaExecutable\"`" - javaExecutable="`cd \"$javaHome\" && pwd -P`/javac" - else - javaExecutable="`readlink -f \"$javaExecutable\"`" - fi - javaHome="`dirname \"$javaExecutable\"`" - javaHome=`expr "$javaHome" : '\(.*\)/bin'` - JAVA_HOME="$javaHome" - export JAVA_HOME - fi - fi -fi - -if [ -z "$JAVACMD" ] ; then - if [ -n "$JAVA_HOME" ] ; then - if [ -x "$JAVA_HOME/jre/sh/java" ] ; then - # IBM's JDK on AIX uses strange locations for the executables - JAVACMD="$JAVA_HOME/jre/sh/java" - else - JAVACMD="$JAVA_HOME/bin/java" - fi - else - JAVACMD="`which java`" - fi -fi - -if [ ! -x "$JAVACMD" ] ; then - echo "Error: JAVA_HOME is not defined correctly." >&2 - echo " We cannot execute $JAVACMD" >&2 - exit 1 -fi - -if [ -z "$JAVA_HOME" ] ; then - echo "Warning: JAVA_HOME environment variable is not set." -fi - -CLASSWORLDS_LAUNCHER=org.codehaus.plexus.classworlds.launcher.Launcher - -# traverses directory structure from process work directory to filesystem root -# first directory with .mvn subdirectory is considered project base directory -find_maven_basedir() { - - if [ -z "$1" ] - then - echo "Path not specified to find_maven_basedir" - return 1 - fi - - basedir="$1" - wdir="$1" - while [ "$wdir" != '/' ] ; do - if [ -d "$wdir"/.mvn ] ; then - basedir=$wdir - break - fi - # workaround for JBEAP-8937 (on Solaris 10/Sparc) - if [ -d "${wdir}" ]; then - wdir=`cd "$wdir/.."; pwd` - fi - # end of workaround - done - echo "${basedir}" -} - -# concatenates all lines of a file -concat_lines() { - if [ -f "$1" ]; then - echo "$(tr -s '\n' ' ' < "$1")" - fi -} - -BASE_DIR=`find_maven_basedir "$(pwd)"` -if [ -z "$BASE_DIR" ]; then - exit 1; -fi - -########################################################################################## -# Extension to allow automatically downloading the maven-wrapper.jar from Maven-central -# This allows using the maven wrapper in projects that prohibit checking in binary data. -########################################################################################## -if [ -r "$BASE_DIR/.mvn/wrapper/maven-wrapper.jar" ]; then - if [ "$MVNW_VERBOSE" = true ]; then - echo "Found .mvn/wrapper/maven-wrapper.jar" - fi -else - if [ "$MVNW_VERBOSE" = true ]; then - echo "Couldn't find .mvn/wrapper/maven-wrapper.jar, downloading it ..." - fi - if [ -n "$MVNW_REPOURL" ]; then - jarUrl="$MVNW_REPOURL/io/takari/maven-wrapper/0.5.3/maven-wrapper-0.5.3.jar" - else - jarUrl="https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.5.3/maven-wrapper-0.5.3.jar" - fi - while IFS="=" read key value; do - case "$key" in (wrapperUrl) jarUrl="$value"; break ;; - esac - done < "$BASE_DIR/.mvn/wrapper/maven-wrapper.properties" - if [ "$MVNW_VERBOSE" = true ]; then - echo "Downloading from: $jarUrl" - fi - wrapperJarPath="$BASE_DIR/.mvn/wrapper/maven-wrapper.jar" - if $cygwin; then - wrapperJarPath=`cygpath --path --windows "$wrapperJarPath"` - fi - - if command -v wget > /dev/null; then - if [ "$MVNW_VERBOSE" = true ]; then - echo "Found wget ... using wget" - fi - if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then - wget "$jarUrl" -O "$wrapperJarPath" - else - wget --http-user=$MVNW_USERNAME --http-password=$MVNW_PASSWORD "$jarUrl" -O "$wrapperJarPath" - fi - elif command -v curl > /dev/null; then - if [ "$MVNW_VERBOSE" = true ]; then - echo "Found curl ... using curl" - fi - if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then - curl -o "$wrapperJarPath" "$jarUrl" -f - else - curl --user $MVNW_USERNAME:$MVNW_PASSWORD -o "$wrapperJarPath" "$jarUrl" -f - fi - - else - if [ "$MVNW_VERBOSE" = true ]; then - echo "Falling back to using Java to download" - fi - javaClass="$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.java" - # For Cygwin, switch paths to Windows format before running javac - if $cygwin; then - javaClass=`cygpath --path --windows "$javaClass"` - fi - if [ -e "$javaClass" ]; then - if [ ! -e "$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.class" ]; then - if [ "$MVNW_VERBOSE" = true ]; then - echo " - Compiling MavenWrapperDownloader.java ..." - fi - # Compiling the Java class - ("$JAVA_HOME/bin/javac" "$javaClass") - fi - if [ -e "$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.class" ]; then - # Running the downloader - if [ "$MVNW_VERBOSE" = true ]; then - echo " - Running MavenWrapperDownloader.java ..." - fi - ("$JAVA_HOME/bin/java" -cp .mvn/wrapper MavenWrapperDownloader "$MAVEN_PROJECTBASEDIR") - fi - fi - fi -fi -########################################################################################## -# End of extension -########################################################################################## - -export MAVEN_PROJECTBASEDIR=${MAVEN_BASEDIR:-"$BASE_DIR"} -if [ "$MVNW_VERBOSE" = true ]; then - echo $MAVEN_PROJECTBASEDIR -fi -MAVEN_OPTS="$(concat_lines "$MAVEN_PROJECTBASEDIR/.mvn/jvm.config") $MAVEN_OPTS" - -# For Cygwin, switch paths to Windows format before running java -if $cygwin; then - [ -n "$M2_HOME" ] && - M2_HOME=`cygpath --path --windows "$M2_HOME"` - [ -n "$JAVA_HOME" ] && - JAVA_HOME=`cygpath --path --windows "$JAVA_HOME"` - [ -n "$CLASSPATH" ] && - CLASSPATH=`cygpath --path --windows "$CLASSPATH"` - [ -n "$MAVEN_PROJECTBASEDIR" ] && - MAVEN_PROJECTBASEDIR=`cygpath --path --windows "$MAVEN_PROJECTBASEDIR"` -fi - -WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain - -exec "$JAVACMD" \ - $MAVEN_OPTS \ - -classpath "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.jar" \ - "-Dmaven.home=${M2_HOME}" "-Dmaven.multiModuleProjectDirectory=${MAVEN_PROJECTBASEDIR}" \ - ${WRAPPER_LAUNCHER} $MAVEN_CONFIG "$@" diff --git a/code-samples/community/serving/helloworld-java-quarkus/mvnw.cmd b/code-samples/community/serving/helloworld-java-quarkus/mvnw.cmd deleted file mode 100644 index 77b451d837..0000000000 --- a/code-samples/community/serving/helloworld-java-quarkus/mvnw.cmd +++ /dev/null @@ -1,172 +0,0 @@ -@REM ---------------------------------------------------------------------------- -@REM Licensed to the Apache Software Foundation (ASF) under one -@REM or more contributor license agreements. See the NOTICE file -@REM distributed with this work for additional information -@REM regarding copyright ownership. The ASF licenses this file -@REM to you under the Apache License, Version 2.0 (the -@REM "License"); you may not use this file except in compliance -@REM with the License. You may obtain a copy of the License at -@REM -@REM http://www.apache.org/licenses/LICENSE-2.0 -@REM -@REM Unless required by applicable law or agreed to in writing, -@REM software distributed under the License is distributed on an -@REM "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY -@REM KIND, either express or implied. See the License for the -@REM specific language governing permissions and limitations -@REM under the License. -@REM ---------------------------------------------------------------------------- - -@REM ---------------------------------------------------------------------------- -@REM Maven2 Start Up Batch script -@REM -@REM Required ENV vars: -@REM JAVA_HOME - location of a JDK home dir -@REM -@REM Optional ENV vars -@REM M2_HOME - location of maven2's installed home dir -@REM MAVEN_BATCH_ECHO - set to 'on' to enable the echoing of the batch commands -@REM MAVEN_BATCH_PAUSE - set to 'on' to wait for a key stroke before ending -@REM MAVEN_OPTS - parameters passed to the Java VM when running Maven -@REM e.g. to debug Maven itself, use -@REM set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000 -@REM MAVEN_SKIP_RC - flag to disable loading of mavenrc files -@REM ---------------------------------------------------------------------------- - -@REM Begin all REM lines with '@' in case MAVEN_BATCH_ECHO is 'on' -@echo off -@REM set title of command window -title %0 -@REM enable echoing my setting MAVEN_BATCH_ECHO to 'on' -@if "%MAVEN_BATCH_ECHO%" == "on" echo %MAVEN_BATCH_ECHO% - -@REM set %HOME% to equivalent of $HOME -if "%HOME%" == "" (set "HOME=%HOMEDRIVE%%HOMEPATH%") - -@REM Execute a user defined script before this one -if not "%MAVEN_SKIP_RC%" == "" goto skipRcPre -@REM check for pre script, once with legacy .bat ending and once with .cmd ending -if exist "%HOME%\mavenrc_pre.bat" call "%HOME%\mavenrc_pre.bat" -if exist "%HOME%\mavenrc_pre.cmd" call "%HOME%\mavenrc_pre.cmd" -:skipRcPre - -@setlocal - -set ERROR_CODE=0 - -@REM To isolate internal variables from possible post scripts, we use another setlocal -@setlocal - -@REM ==== START VALIDATION ==== -if not "%JAVA_HOME%" == "" goto OkJHome - -echo. -echo Error: JAVA_HOME not found in your environment. >&2 -echo Please set the JAVA_HOME variable in your environment to match the >&2 -echo location of your Java installation. >&2 -echo. -goto error - -:OkJHome -if exist "%JAVA_HOME%\bin\java.exe" goto init - -echo. -echo Error: JAVA_HOME is set to an invalid directory. >&2 -echo JAVA_HOME = "%JAVA_HOME%" >&2 -echo Please set the JAVA_HOME variable in your environment to match the >&2 -echo location of your Java installation. >&2 -echo. -goto error - -@REM ==== END VALIDATION ==== - -:init - -@REM Find the project base dir, i.e. the directory that contains the folder ".mvn". -@REM Fallback to current working directory if not found. - -set MAVEN_PROJECTBASEDIR=%MAVEN_BASEDIR% -IF NOT "%MAVEN_PROJECTBASEDIR%"=="" goto endDetectBaseDir - -set EXEC_DIR=%CD% -set WDIR=%EXEC_DIR% -:findBaseDir -IF EXIST "%WDIR%"\.mvn goto baseDirFound -cd .. -IF "%WDIR%"=="%CD%" goto baseDirNotFound -set WDIR=%CD% -goto findBaseDir - -:baseDirFound -set MAVEN_PROJECTBASEDIR=%WDIR% -cd "%EXEC_DIR%" -goto endDetectBaseDir - -:baseDirNotFound -set MAVEN_PROJECTBASEDIR=%EXEC_DIR% -cd "%EXEC_DIR%" - -:endDetectBaseDir - -IF NOT EXIST "%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config" goto endReadAdditionalConfig - -@setlocal EnableExtensions EnableDelayedExpansion -for /F "usebackq delims=" %%a in ("%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config") do set JVM_CONFIG_MAVEN_PROPS=!JVM_CONFIG_MAVEN_PROPS! %%a -@endlocal & set JVM_CONFIG_MAVEN_PROPS=%JVM_CONFIG_MAVEN_PROPS% - -:endReadAdditionalConfig - -SET MAVEN_JAVA_EXE="%JAVA_HOME%\bin\java.exe" -set WRAPPER_JAR="%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.jar" -set WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain - -set DOWNLOAD_URL="https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.5.3/maven-wrapper-0.5.3.jar" - -FOR /F "tokens=1,2 delims==" %%A IN ("%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.properties") DO ( - IF "%%A"=="wrapperUrl" SET DOWNLOAD_URL=%%B -) - -@REM Extension to allow automatically downloading the maven-wrapper.jar from Maven-central -@REM This allows using the maven wrapper in projects that prohibit checking in binary data. -if exist %WRAPPER_JAR% ( - echo Found %WRAPPER_JAR% -) else ( - if not "%MVNW_REPOURL%" == "" ( - SET DOWNLOAD_URL="%MVNW_REPOURL%/io/takari/maven-wrapper/0.5.3/maven-wrapper-0.5.3.jar" - ) - echo Couldn't find %WRAPPER_JAR%, downloading it ... - echo Downloading from: %DOWNLOAD_URL% - - powershell -Command "&{"^ - "$webclient = new-object System.Net.WebClient;"^ - "if (-not ([string]::IsNullOrEmpty('%MVNW_USERNAME%') -and [string]::IsNullOrEmpty('%MVNW_PASSWORD%'))) {"^ - "$webclient.Credentials = new-object System.Net.NetworkCredential('%MVNW_USERNAME%', '%MVNW_PASSWORD%');"^ - "}"^ - "[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12; $webclient.DownloadFile('%DOWNLOAD_URL%', '%WRAPPER_JAR%')"^ - "}" - echo Finished downloading %WRAPPER_JAR% -) -@REM End of extension - -%MAVEN_JAVA_EXE% %JVM_CONFIG_MAVEN_PROPS% %MAVEN_OPTS% %MAVEN_DEBUG_OPTS% -classpath %WRAPPER_JAR% "-Dmaven.multiModuleProjectDirectory=%MAVEN_PROJECTBASEDIR%" %WRAPPER_LAUNCHER% %MAVEN_CONFIG% %* -if ERRORLEVEL 1 goto error -goto end - -:error -set ERROR_CODE=1 - -:end -@endlocal & set ERROR_CODE=%ERROR_CODE% - -if not "%MAVEN_SKIP_RC%" == "" goto skipRcPost -@REM check for post script, once with legacy .bat ending and once with .cmd ending -if exist "%HOME%\mavenrc_post.bat" call "%HOME%\mavenrc_post.bat" -if exist "%HOME%\mavenrc_post.cmd" call "%HOME%\mavenrc_post.cmd" -:skipRcPost - -@REM pause the script if MAVEN_BATCH_PAUSE is set to 'on' -if "%MAVEN_BATCH_PAUSE%" == "on" pause - -if "%MAVEN_TERMINATE_CMD%" == "on" exit %ERROR_CODE% - -exit /B %ERROR_CODE% diff --git a/code-samples/community/serving/helloworld-java-quarkus/pom.xml b/code-samples/community/serving/helloworld-java-quarkus/pom.xml index 1bebc82bfd..e67cd9082e 100644 --- a/code-samples/community/serving/helloworld-java-quarkus/pom.xml +++ b/code-samples/community/serving/helloworld-java-quarkus/pom.xml @@ -1,23 +1,24 @@ - - + + 4.0.0 com.redhat.developer.demos helloworld-java-quarkus 1.0-SNAPSHOT - 2.22.0 - 0.13.3 + 3.40.1.1 + 25 + true UTF-8 - 1.8 - 1.8 + 3.15.0 - io.quarkus + io.quarkus.platform quarkus-bom - ${quarkus.version} + ${quarkus.platform.version} pom import @@ -26,88 +27,35 @@ io.quarkus - quarkus-resteasy - - - io.quarkus - quarkus-junit5 - test - - - io.rest-assured - rest-assured - test + quarkus-rest - io.quarkus + io.quarkus.platform quarkus-maven-plugin - ${quarkus.version} + ${quarkus.platform.version} + true build + generate-code - maven-surefire-plugin - ${surefire-plugin.version} + maven-compiler-plugin + ${compiler-plugin.version} - - org.jboss.logmanager.LogManager - + true + + -Xlint:all + - - - native - - - native - - - - - - io.quarkus - quarkus-maven-plugin - ${quarkus.version} - - - - native-image - - - true - - - - - - maven-failsafe-plugin - ${surefire-plugin.version} - - - - integration-test - verify - - - - ${project.build.directory}/${project.build.finalName}-runner - - - - - - - - - diff --git a/code-samples/community/serving/helloworld-java-quarkus/service.yaml b/code-samples/community/serving/helloworld-java-quarkus/service.yaml index f0d1cf19b1..7843e50e4d 100644 --- a/code-samples/community/serving/helloworld-java-quarkus/service.yaml +++ b/code-samples/community/serving/helloworld-java-quarkus/service.yaml @@ -6,7 +6,7 @@ spec: template: spec: containers: - - image: docker.io/saturnism/helloworld-java-quarkus + - image: docker.io/{username}/helloworld-java-quarkus env: - name: TARGET value: "Quarkus Sample v1" diff --git a/code-samples/community/serving/helloworld-java-quarkus/src/main/java/com/redhat/developer/demos/GreetingResource.java b/code-samples/community/serving/helloworld-java-quarkus/src/main/java/com/redhat/developer/demos/GreetingResource.java index 0a28286f63..9e1532cf00 100644 --- a/code-samples/community/serving/helloworld-java-quarkus/src/main/java/com/redhat/developer/demos/GreetingResource.java +++ b/code-samples/community/serving/helloworld-java-quarkus/src/main/java/com/redhat/developer/demos/GreetingResource.java @@ -1,19 +1,20 @@ package com.redhat.developer.demos; -import javax.ws.rs.GET; -import javax.ws.rs.Path; -import javax.ws.rs.Produces; -import javax.ws.rs.core.MediaType; +import jakarta.ws.rs.GET; +import jakarta.ws.rs.Path; +import jakarta.ws.rs.Produces; +import jakarta.ws.rs.core.MediaType; +import java.util.Optional; import org.eclipse.microprofile.config.inject.ConfigProperty; @Path("/") public class GreetingResource { - @ConfigProperty(name = "target", defaultValue="World") - String target; + @ConfigProperty(name = "target") + Optional target; - @GET - @Produces(MediaType.TEXT_PLAIN) - public String hello() { - return "Hello " + target + "!"; - } + @GET + @Produces(MediaType.TEXT_PLAIN) + public String hello() { + return "Hello " + target.orElse("World") + "!"; + } } diff --git a/code-samples/community/serving/helloworld-java-quarkus/src/main/resources/application.properties b/code-samples/community/serving/helloworld-java-quarkus/src/main/resources/application.properties index cac8f0b2c2..39c7868fdd 100644 --- a/code-samples/community/serving/helloworld-java-quarkus/src/main/resources/application.properties +++ b/code-samples/community/serving/helloworld-java-quarkus/src/main/resources/application.properties @@ -1,4 +1,3 @@ -# Configuration file -# key = value - +# Listen on all interfaces in both development and production containers. +quarkus.http.host=0.0.0.0 quarkus.http.port=${PORT:8080} diff --git a/code-samples/community/serving/helloworld-java-quarkus/src/test/java/com/redhat/developer/demos/GreetingResourceTest.java b/code-samples/community/serving/helloworld-java-quarkus/src/test/java/com/redhat/developer/demos/GreetingResourceTest.java deleted file mode 100644 index ef4208dcf2..0000000000 --- a/code-samples/community/serving/helloworld-java-quarkus/src/test/java/com/redhat/developer/demos/GreetingResourceTest.java +++ /dev/null @@ -1,21 +0,0 @@ -package com.redhat.developer.demos; - -import io.quarkus.test.junit.QuarkusTest; -import org.junit.jupiter.api.Test; - -import static io.restassured.RestAssured.given; -import static org.hamcrest.CoreMatchers.is; - -@QuarkusTest -public class GreetingResourceTest { - - @Test - public void testHelloEndpoint() { - given() - .when().get("/") - .then() - .statusCode(200) - .body(is("Hello World!")); - } - -} diff --git a/code-samples/community/serving/helloworld-java-quarkus/src/test/java/com/redhat/developer/demos/NativeGreetingResourceIT.java b/code-samples/community/serving/helloworld-java-quarkus/src/test/java/com/redhat/developer/demos/NativeGreetingResourceIT.java deleted file mode 100644 index 6fc3fa41fe..0000000000 --- a/code-samples/community/serving/helloworld-java-quarkus/src/test/java/com/redhat/developer/demos/NativeGreetingResourceIT.java +++ /dev/null @@ -1,9 +0,0 @@ -package com.redhat.developer.demos; - -import io.quarkus.test.junit.SubstrateTest; - -@SubstrateTest -public class NativeGreetingResourceIT extends GreetingResourceTest { - - // Execute the same tests but in native mode. -} From 9eda5b5b7ab95254d0c7785efc62379d15945708 Mon Sep 17 00:00:00 2001 From: Dave Protasowski Date: Mon, 26 Oct 2026 09:16:03 -0400 Subject: [PATCH 08/44] Update and simplify the R server sample --- .../serving/helloworld-rserver/Dockerfile | 16 ++- .../serving/helloworld-rserver/HelloWorld.R | 5 +- .../serving/helloworld-rserver/README.md | 125 +++++++----------- .../serving/helloworld-rserver/ci.sh | 40 ++++++ .../serving/helloworld-rserver/server.R | 14 +- 5 files changed, 110 insertions(+), 90 deletions(-) create mode 100755 code-samples/community/serving/helloworld-rserver/ci.sh diff --git a/code-samples/community/serving/helloworld-rserver/Dockerfile b/code-samples/community/serving/helloworld-rserver/Dockerfile index 3b3c450ec1..af2d084715 100644 --- a/code-samples/community/serving/helloworld-rserver/Dockerfile +++ b/code-samples/community/serving/helloworld-rserver/Dockerfile @@ -1,13 +1,19 @@ # The official R base image # https://hub.docker.com/_/r-base -FROM r-base:3.6.0 +FROM r-base:4.6.1 -# Copy local code to the container image. WORKDIR /usr/src/app -COPY . . -# Install R packages -RUN Rscript -e "install.packages('plumber', repos='http://cran.us.r-project.org/')" +# Match the official image's Debian sid runtime libraries when installing headers. +RUN apt-get update \ + && apt-get install -y -t unstable --no-install-recommends libcurl4-openssl-dev libsodium-dev \ + && rm -rf /var/lib/apt/lists/* + +# Install the web framework using R's standard package installer. +RUN Rscript -e 'install.packages("plumber", repos = "https://cloud.r-project.org")' + +# Copy the sample after dependency installation for efficient rebuilds. +COPY HelloWorld.R server.R ./ # Run the web service on container startup. CMD ["Rscript", "server.R"] diff --git a/code-samples/community/serving/helloworld-rserver/HelloWorld.R b/code-samples/community/serving/helloworld-rserver/HelloWorld.R index f09d12f34e..f0c8cd30df 100644 --- a/code-samples/community/serving/helloworld-rserver/HelloWorld.R +++ b/code-samples/community/serving/helloworld-rserver/HelloWorld.R @@ -1,9 +1,8 @@ #' HelloWorld function #' @get / -#' @html +#' @serializer html function() { TARGET <- Sys.getenv("TARGET", "World") - message = paste("Hello ", TARGET, "!", sep = "") - print(message) + paste0("Hello ", TARGET, "!") } diff --git a/code-samples/community/serving/helloworld-rserver/README.md b/code-samples/community/serving/helloworld-rserver/README.md index 9f43b0d536..808bf6d461 100644 --- a/code-samples/community/serving/helloworld-rserver/README.md +++ b/code-samples/community/serving/helloworld-rserver/README.md @@ -2,16 +2,19 @@ A simple web app created with R package, [plumber](https://www.rplumber.io). plumber creates a REST API by adding annotations to your R code. The R script -reads an environment variable `TARGET` and prints `Hello ${TARGET}!`. If the -`TARGET` environment variable is not specified, the script uses `World`. +reads an environment variable `TARGET` and returns `Hello ${TARGET}!` as HTML. +If `TARGET` is not specified, the script uses `World`; an explicitly empty value +produces `Hello !`. `PORT` defaults to `8080` and must be an integer from 1 to +65535; invalid or empty values fail startup. -Follow the steps below to create the sample code and then deploy the app to your -cluster. You can also download a working copy of the sample, by running the -following commands: +The Dockerfile uses R 4.6.1 and installs plumber from CRAN. + +Clone the sample, then follow the steps below to test +and deploy the app to your cluster: ```bash git clone https://github.com/knative/docs.git knative-docs -cd knative-docs/code-samples/community/serving/hello-world/helloworld-r +cd knative-docs/code-samples/community/serving/helloworld-rserver ``` ## Before you begin @@ -22,80 +25,48 @@ cd knative-docs/code-samples/community/serving/hello-world/helloworld-r - [Docker](https://www.docker.com) installed and running on your local machine, and a Docker Hub account configured (we'll use it for a container registry). -## Recreating the sample code +## Understanding the sample files -1. Create a new file named `HelloWorld.R` and paste the following script: +Use the files from the cloned directory rather than recreating copies: - ```R - #' HelloWorld function - #' @get / - #' @html - function() { - TARGET <- Sys.getenv("TARGET", "World") +- [`HelloWorld.R`](HelloWorld.R) defines `GET /` using plumber annotations and + returns the greeting with the HTML serializer. +- [`server.R`](server.R) validates `PORT`, loads the API, and listens on + `0.0.0.0` so Docker and Knative can reach it. +- [`Dockerfile`](Dockerfile) installs plumber with `install.packages("plumber", repos="https://cloud.r-project.org")` + and starts the server. +- [`service.yaml`](service.yaml) is the Knative Service manifest. Replace + `{username}` in its image reference with your Docker Hub username before + deploying; its `TARGET` setting produces `Hello R Server Sample v1!`. - message = paste("Hello ", TARGET, "!", sep = "") - print(message) - } - ``` +## Testing locally - This file defines the endpoint `/`, using plumber annotations. - -1. Create a new file named `server.R` and paste the following code: - - ```R - library(plumber) # https://www.rplumber.io/ - - # Translate the HelloWorld file into a Plumber API - r <- plumb("HelloWorld.R") - # Get the PORT env var - PORT <- strtoi(Sys.getenv("PORT", 8080)) - # Run the API - r$run(port=PORT, host="0.0.0.0") - ``` - -1. Create a new file named `Dockerfile` and paste the following code: - - ```docker - # The official R base image - # https://hub.docker.com/_/r-base - FROM r-base:3.6.0 - - # Copy local code to the container image. - WORKDIR /usr/src/app - COPY . . - - # Install R packages - RUN Rscript -e "install.packages('plumber', repos='http://cran.us.r-project.org/')" - - # Run the web service on container startup. - CMD ["Rscript", "server.R"] - ``` - - -1. Create a new file, `service.yaml` and copy the following service definition - into the file. Make sure to replace `{username}` with your Docker Hub - username. - - ```yaml - apiVersion: serving.knative.dev/v1 - kind: Service - metadata: - name: helloworld-rserver - namespace: default - spec: - template: - spec: - containers: - - image: docker.io/{username}/helloworld-rserver - env: - - name: TARGET - value: "R Server Sample v1" - ``` +Build the image and exercise the actual HTTP listener with Docker: + +```bash +docker build -t helloworld-rserver . +docker run --rm -p 127.0.0.1:8080:8080 helloworld-rserver +# In another terminal: curl -i http://127.0.0.1:8080/ +# Expected: HTTP 200, text/html; charset=UTF-8, body Hello World! +``` + +Stop the container before testing an override: + +```bash +docker run --rm -p 127.0.0.1:9090:9090 -e PORT=9090 -e TARGET='R Server Sample v1' helloworld-rserver +# In another terminal: curl -i http://127.0.0.1:9090/ +# Expected body: Hello R Server Sample v1! +``` + +An unknown path returns HTTP 404; unsupported methods such as `POST /` return +HTTP 405. Stop the local container when finished. For a custom port on Knative, +set `containers[].ports[].containerPort` to an allowed port (for example, `18081`); +Knative injects the matching `PORT`. Port `9090` in the standalone Docker example +is reserved by Knative and cannot be used as a Knative container port. ## Building and deploying the sample -Once you have recreated the sample code files (or used the files in the sample -folder) you're ready to build and deploy the sample app. +From the cloned sample directory, build and deploy the sample app. 1. Use Docker to build the sample code into a container. To build and push with Docker Hub, run these commands replacing `{username}` with your Docker Hub @@ -125,14 +96,14 @@ folder) you're ready to build and deploy the sample app. 1. Run the following command to find the domain URL for your service: ```bash - kubectl get ksvc helloworld-r --output=custom-columns=NAME:.metadata.name,URL:.status.url + kubectl get ksvc helloworld-rserver --output=custom-columns=NAME:.metadata.name,URL:.status.url ``` Example: ```bash - NAME URL - helloworld-r http://helloworld-r.default.1.2.3.4.sslip.io + NAME URL + helloworld-rserver http://helloworld-rserver.default.1.2.3.4.sslip.io ``` 1. Now you can make a request to your app and see the result. Replace @@ -146,7 +117,7 @@ folder) you're ready to build and deploy the sample app. ```bash curl http://helloworld-rserver.default.1.2.3.4.sslip.io - [1] "Hello R Sample v1!" + Hello R Server Sample v1! ``` > Note: Add `-v` option to get more detail if the `curl` command failed. diff --git a/code-samples/community/serving/helloworld-rserver/ci.sh b/code-samples/community/serving/helloworld-rserver/ci.sh new file mode 100755 index 0000000000..f3ecaa557b --- /dev/null +++ b/code-samples/community/serving/helloworld-rserver/ci.sh @@ -0,0 +1,40 @@ +#!/usr/bin/env bash +# Source from this sample directory; the caller owns cluster setup and ingress forwarding. +IMAGE=dev.local/helloworld-rserver:ci +SAMPLE_URL=http://127.0.0.1:18080 + +main() ( + set -euo pipefail + build + deploy + test +) + +build() ( + set -euo pipefail + docker build -t "$IMAGE" . +) + +deploy() ( + set -euo pipefail + kind load docker-image "$IMAGE" + kubectl patch --local -f service.yaml --type=json -p "$( + jq -cn --arg image "$IMAGE" '[ + {op: "replace", path: "/spec/template/spec/containers/0/image", value: $image}, + {op: "add", path: "/spec/template/spec/containers/0/imagePullPolicy", value: "IfNotPresent"} + ]' + )" -o yaml | kubectl --namespace default apply -f - + kubectl --namespace default wait -f service.yaml --for=condition=Ready --timeout=300s +) + +test() ( + set -euo pipefail + local service_url service_host + service_url=$(kubectl --namespace default get -f service.yaml -o jsonpath='{.status.url}') + service_host=${service_url#*://} + [[ "$service_host" =~ ^[a-zA-Z0-9.-]+$ ]] + local body + body=$(curl -fsS --max-time 30 \ + -H "Host: $service_host" "$SAMPLE_URL/") + [[ $body == 'Hello R Server Sample v1!' ]] +) diff --git a/code-samples/community/serving/helloworld-rserver/server.R b/code-samples/community/serving/helloworld-rserver/server.R index 5c05dba0f2..3fc9752c3d 100644 --- a/code-samples/community/serving/helloworld-rserver/server.R +++ b/code-samples/community/serving/helloworld-rserver/server.R @@ -1,8 +1,12 @@ library(plumber) # https://www.rplumber.io/ -# Translate the HelloWorld file into a Plumber API +# Validate PORT before opening a listener; Knative supplies this variable. +port_value <- Sys.getenv("PORT", "8080") +PORT <- suppressWarnings(as.integer(port_value)) +if (!grepl("^[0-9]+$", port_value) || is.na(PORT) || PORT < 1L || PORT > 65535L) { + stop("PORT must be an integer between 1 and 65535") +} + +# Translate the HelloWorld file into a Plumber API and listen on all interfaces. r <- plumb("HelloWorld.R") -# Get the PORT env var -PORT <- strtoi(Sys.getenv("PORT", 8080)) -# Run the API -r$run(port=PORT, host="0.0.0.0") +r$run(port = PORT, host = "0.0.0.0") From fcbc7b2c10a5c49b37356fa74c7888edec0ecefb Mon Sep 17 00:00:00 2001 From: Dave Protasowski Date: Mon, 26 Oct 2026 09:16:03 -0400 Subject: [PATCH 09/44] Update the Rust Hello World sample --- .../serving/helloworld-rust/Cargo.lock | 2 +- .../serving/helloworld-rust/Dockerfile | 2 +- .../serving/helloworld-rust/README.md | 136 +++--------------- .../community/serving/helloworld-rust/ci.sh | 40 ++++++ .../serving/helloworld-rust/src/main.rs | 1 + 5 files changed, 65 insertions(+), 116 deletions(-) create mode 100755 code-samples/community/serving/helloworld-rust/ci.sh diff --git a/code-samples/community/serving/helloworld-rust/Cargo.lock b/code-samples/community/serving/helloworld-rust/Cargo.lock index 3df1d62500..8ff62c65b8 100644 --- a/code-samples/community/serving/helloworld-rust/Cargo.lock +++ b/code-samples/community/serving/helloworld-rust/Cargo.lock @@ -1,6 +1,6 @@ # This file is automatically @generated by Cargo. # It is not intended for manual editing. -version = 4 +version = 3 [[package]] name = "addr2line" diff --git a/code-samples/community/serving/helloworld-rust/Dockerfile b/code-samples/community/serving/helloworld-rust/Dockerfile index 2ad920e5f8..670947955a 100644 --- a/code-samples/community/serving/helloworld-rust/Dockerfile +++ b/code-samples/community/serving/helloworld-rust/Dockerfile @@ -7,7 +7,7 @@ WORKDIR /usr/src/app COPY . . # Install production dependencies and build a release artifact. -RUN cargo install --path . +RUN cargo install --locked --path . # Run the web service on container startup. CMD ["hellorust"] diff --git a/code-samples/community/serving/helloworld-rust/README.md b/code-samples/community/serving/helloworld-rust/README.md index 7a6fce7edb..933b5e716d 100644 --- a/code-samples/community/serving/helloworld-rust/README.md +++ b/code-samples/community/serving/helloworld-rust/README.md @@ -1,9 +1,9 @@ # Hello World - Rust A simple web app written in Rust that you can use for testing. It reads in an -env variable `TARGET` and prints "Hello \${TARGET}!". If - -TARGET is not specified, it will use "World" as the TARGET. +env variable `TARGET` and responds with "Hello ${TARGET}!". If `TARGET` is not +specified, it will use "World" as the target. It listens on `PORT` (default 8080); +an invalid port value falls back to 8080. ## Prerequisites @@ -13,123 +13,31 @@ TARGET is not specified, it will use "World" as the TARGET. - [Docker](https://www.docker.com) installed and running on your local machine, and a Docker Hub account configured (we'll use it for a container registry). -## Steps to recreate the sample code - -While you can clone all of the code from this directory, hello world apps are -generally more useful if you build them step-by-step. The following instructions -recreate the source files from this folder. - -1. Create a new file named `Cargo.toml` and paste the following code: - - ```toml - [package] - name = "hellorust" - version = "0.0.0" - edition = "2021" - publish = false - - [dependencies] - hyper = { version = "0.14.27", features = ["full"]} - tokio = { version = "1.33.0", features = ["macros", "rt-multi-thread"] } - pretty_env_logger = "0.5.0" - ``` - -1. Create a `src` folder, then create a new file named `main.rs` in that folder - and paste the following code. This code creates a basic web server which - listens on port 8080: - - ```rust - use hyper::{ - server::conn::AddrStream, - service::{make_service_fn, service_fn}, - Body, Request, Response, Server, - }; - use std::convert::Infallible; - use std::env; - - #[tokio::main] - async fn main() { - pretty_env_logger::init(); - - let mut port: u16 = 8080; - match env::var("PORT") { - Ok(p) => { - match p.parse::() { - Ok(n) => { - port = n; - } - Err(_e) => {} - }; - } - Err(_e) => {} - }; - let addr = ([0, 0, 0, 0], port).into(); - - let make_svc = make_service_fn(|_socket: &AddrStream| async move { - Ok::<_, Infallible>(service_fn(move |_: Request| async move { - let mut hello = "Hello ".to_string(); - match env::var("TARGET") { - Ok(target) => { - hello.push_str(&target); - } - Err(_e) => hello.push_str("World"), - }; - Ok::<_, Infallible>(Response::new(Body::from(hello))) - })) - }); - - let server = Server::bind(&addr).serve(make_svc); - - println!("Listening on http://{}", addr); - if let Err(e) = server.await { - eprintln!("server error: {}", e); - } - } - ``` - -1. In your project directory, create a file named `Dockerfile` and copy the code - block below into it. +## Get and explore the sample code - ```docker - # Use the official Rust image. - # https://hub.docker.com/_/rust - FROM rust:1.73.0 +Clone this repository and change to the sample directory: - # Copy local code to the container image. - WORKDIR /usr/src/app - COPY . . - - # Install production dependencies and build a release artifact. - RUN cargo install --path . +```bash +git clone https://github.com/knative/docs.git +cd docs/code-samples/community/serving/helloworld-rust +``` - # Run the web service on container startup. - CMD ["hellorust"] - ``` +Use the checked-in files rather than recreating copies from this guide: -1. Create a new file, `service.yaml` and copy the following service definition - into the file. Make sure to replace `{username}` with your Docker Hub - username. - - ```yaml - apiVersion: serving.knative.dev/v1 - kind: Service - metadata: - name: helloworld-rust - namespace: default - spec: - template: - spec: - containers: - - image: docker.io/{username}/helloworld-rust - env: - - name: TARGET - value: "Rust Sample v1" - ``` +- [Cargo.toml](Cargo.toml) declares the Rust package and its dependencies. +- [Cargo.lock](Cargo.lock) selects the dependency versions compatible with the + Rust toolchain in the Dockerfile. Keep it with the source when building. +- [src/main.rs](src/main.rs) implements the Hyper server. It binds to all + interfaces, reads `PORT`, and responds to requests with the configured greeting. +- [Dockerfile](Dockerfile) builds and installs the release binary with + `cargo install --locked --path .`, then runs `hellorust` on startup. +- [service.yaml](service.yaml) defines the Knative Service and sets + `TARGET=Rust Sample v1`. Replace `{username}` in its image reference with your + Docker Hub username before deploying. ## Build and deploy this sample -Once you have recreated the sample code files (or used the files in the sample -folder) you're ready to build and deploy the sample app. +From the sample directory, build and deploy the sample app: 1. Use Docker to build the sample code into a container. To build and push with Docker Hub, enter these commands replacing `{username}` with your Docker Hub @@ -169,7 +77,7 @@ folder) you're ready to build and deploy the sample app. ```bash curl http://helloworld-rust.default.1.2.3.4.sslip.io - Hello World! + Hello Rust Sample v1! ``` ## Removing the sample app deployment diff --git a/code-samples/community/serving/helloworld-rust/ci.sh b/code-samples/community/serving/helloworld-rust/ci.sh new file mode 100755 index 0000000000..057662b5ce --- /dev/null +++ b/code-samples/community/serving/helloworld-rust/ci.sh @@ -0,0 +1,40 @@ +#!/usr/bin/env bash +# Source from this sample directory; the caller owns cluster setup and ingress forwarding. +IMAGE=dev.local/helloworld-rust:ci +SAMPLE_URL=http://127.0.0.1:18080 + +main() ( + set -euo pipefail + build + deploy + test +) + +build() ( + set -euo pipefail + docker build -t "$IMAGE" . +) + +deploy() ( + set -euo pipefail + kind load docker-image "$IMAGE" + kubectl patch --local -f service.yaml --type=json -p "$( + jq -cn --arg image "$IMAGE" '[ + {op: "replace", path: "/spec/template/spec/containers/0/image", value: $image}, + {op: "add", path: "/spec/template/spec/containers/0/imagePullPolicy", value: "IfNotPresent"} + ]' + )" -o yaml | kubectl --namespace default apply -f - + kubectl --namespace default wait -f service.yaml --for=condition=Ready --timeout=300s +) + +test() ( + set -euo pipefail + local service_url service_host + service_url=$(kubectl --namespace default get -f service.yaml -o jsonpath='{.status.url}') + service_host=${service_url#*://} + [[ "$service_host" =~ ^[a-zA-Z0-9.-]+$ ]] + local body + body=$(curl -fsS --max-time 30 \ + -H "Host: $service_host" "$SAMPLE_URL/") + [[ $body == 'Hello Rust Sample v1!' ]] +) diff --git a/code-samples/community/serving/helloworld-rust/src/main.rs b/code-samples/community/serving/helloworld-rust/src/main.rs index 0015c9c969..c009af961f 100644 --- a/code-samples/community/serving/helloworld-rust/src/main.rs +++ b/code-samples/community/serving/helloworld-rust/src/main.rs @@ -33,6 +33,7 @@ async fn main() { } Err(_e) => hello.push_str("World"), }; + hello.push('!'); Ok::<_, Infallible>(Response::new(Body::from(hello))) })) }); From 4dac3db76af06221429c3938b96e046c421db461 Mon Sep 17 00:00:00 2001 From: Dave Protasowski Date: Mon, 26 Oct 2026 09:16:03 -0400 Subject: [PATCH 10/44] Update the Swift Hello World sample --- .../serving/helloworld-swift/.swift-version | 2 +- .../serving/helloworld-swift/Dockerfile | 28 ++- .../serving/helloworld-swift/Package.resolved | 222 ++++++++++++++++++ .../serving/helloworld-swift/Package.swift | 32 +-- .../serving/helloworld-swift/README.md | 182 +++++--------- .../Sources/HelloSwift/Greeting.swift | 29 +++ .../Sources/HelloSwift/main.swift | 36 ++- .../community/serving/helloworld-swift/ci.sh | 40 ++++ 8 files changed, 400 insertions(+), 171 deletions(-) create mode 100644 code-samples/community/serving/helloworld-swift/Package.resolved create mode 100644 code-samples/community/serving/helloworld-swift/Sources/HelloSwift/Greeting.swift create mode 100755 code-samples/community/serving/helloworld-swift/ci.sh diff --git a/code-samples/community/serving/helloworld-swift/.swift-version b/code-samples/community/serving/helloworld-swift/.swift-version index bf77d54968..4c77920fd2 100644 --- a/code-samples/community/serving/helloworld-swift/.swift-version +++ b/code-samples/community/serving/helloworld-swift/.swift-version @@ -1 +1 @@ -4.2 +6.4.1 diff --git a/code-samples/community/serving/helloworld-swift/Dockerfile b/code-samples/community/serving/helloworld-swift/Dockerfile index 932d09b7d6..b5ceb213e8 100644 --- a/code-samples/community/serving/helloworld-swift/Dockerfile +++ b/code-samples/community/serving/helloworld-swift/Dockerfile @@ -1,13 +1,15 @@ -# Use the official Swift image. -# https://hub.docker.com/_/swift -FROM swift:4.2 - -# Copy local code to the container image. -WORKDIR /app -COPY . . - -# Install dependencies and build. -RUN swift build -c release - -# Run the web service on container startup. -CMD [ ".build/release/HelloSwift"] +# Build with the current stable Swift toolchain on Ubuntu 24.04 LTS. +FROM swift:6.4.1-noble AS build +WORKDIR /app +COPY Package.swift Package.resolved ./ +RUN swift package resolve --force-resolved-versions +COPY Sources ./Sources +RUN swift build -c release --jobs 2 --force-resolved-versions + +# Ship only the executable and matching Swift runtime, not the compiler. +FROM swift:6.4.1-noble-slim AS runtime +WORKDIR /app +COPY --from=build /app/.build/release/HelloSwift ./HelloSwift +USER 10001:10001 +EXPOSE 8080 +CMD ["./HelloSwift"] diff --git a/code-samples/community/serving/helloworld-swift/Package.resolved b/code-samples/community/serving/helloworld-swift/Package.resolved new file mode 100644 index 0000000000..b2976469f5 --- /dev/null +++ b/code-samples/community/serving/helloworld-swift/Package.resolved @@ -0,0 +1,222 @@ +{ + "originHash" : "11aefb37745a7946fddd1d59fabc6b92d02524b5bddd1d6e077ad2feac7fb69b", + "pins" : [ + { + "identity" : "async-http-client", + "kind" : "remoteSourceControl", + "location" : "https://github.com/swift-server/async-http-client.git", + "state" : { + "revision" : "4c005f955e83f888d5616e579717a36d2dfc6301", + "version" : "1.36.2" + } + }, + { + "identity" : "hummingbird", + "kind" : "remoteSourceControl", + "location" : "https://github.com/hummingbird-project/hummingbird.git", + "state" : { + "revision" : "e12178302dacb8226fc78157f89078a59c75dd98", + "version" : "2.28.0" + } + }, + { + "identity" : "swift-algorithms", + "kind" : "remoteSourceControl", + "location" : "https://github.com/apple/swift-algorithms.git", + "state" : { + "revision" : "87e50f483c54e6efd60e885f7f5aa946cee68023", + "version" : "1.2.1" + } + }, + { + "identity" : "swift-asn1", + "kind" : "remoteSourceControl", + "location" : "https://github.com/apple/swift-asn1.git", + "state" : { + "revision" : "3b6410f7dee09eb33cdd26260c5fd47fda19b0e2", + "version" : "1.7.3" + } + }, + { + "identity" : "swift-async-algorithms", + "kind" : "remoteSourceControl", + "location" : "https://github.com/apple/swift-async-algorithms.git", + "state" : { + "revision" : "13713a4ffdee8abd929f92568ee9462a46ae26e0", + "version" : "1.1.7" + } + }, + { + "identity" : "swift-atomics", + "kind" : "remoteSourceControl", + "location" : "https://github.com/apple/swift-atomics.git", + "state" : { + "revision" : "0442cb5a3f98ab802acb777929fdb446bda11a34", + "version" : "1.3.1" + } + }, + { + "identity" : "swift-certificates", + "kind" : "remoteSourceControl", + "location" : "https://github.com/apple/swift-certificates.git", + "state" : { + "revision" : "ff86b924ead66f853b8baf91f3c41926a8f36177", + "version" : "1.21.0" + } + }, + { + "identity" : "swift-collections", + "kind" : "remoteSourceControl", + "location" : "https://github.com/apple/swift-collections.git", + "state" : { + "revision" : "844289ac455816e193f1dbbe014f3c4dbe429410", + "version" : "1.7.4" + } + }, + { + "identity" : "swift-configuration", + "kind" : "remoteSourceControl", + "location" : "https://github.com/apple/swift-configuration.git", + "state" : { + "revision" : "4e916072eb34f3c136ae64bd2859d6072cdb1b6e", + "version" : "1.3.0" + } + }, + { + "identity" : "swift-crypto", + "kind" : "remoteSourceControl", + "location" : "https://github.com/apple/swift-crypto.git", + "state" : { + "revision" : "c2cbadd7263439d8015189668f96ff5b5be565ce", + "version" : "5.0.3" + } + }, + { + "identity" : "swift-distributed-tracing", + "kind" : "remoteSourceControl", + "location" : "https://github.com/apple/swift-distributed-tracing.git", + "state" : { + "revision" : "297b5b9c82a5631e3199ce3ec0993a3cfb0fbb84", + "version" : "1.7.2" + } + }, + { + "identity" : "swift-http-structured-headers", + "kind" : "remoteSourceControl", + "location" : "https://github.com/apple/swift-http-structured-headers.git", + "state" : { + "revision" : "933538faa42c432d385f02e07df0ace7c5ecfc47", + "version" : "1.7.0" + } + }, + { + "identity" : "swift-http-types", + "kind" : "remoteSourceControl", + "location" : "https://github.com/apple/swift-http-types.git", + "state" : { + "revision" : "bff4b6903cdc99dda49649dd52f46c11cfd3ed50", + "version" : "1.8.0" + } + }, + { + "identity" : "swift-log", + "kind" : "remoteSourceControl", + "location" : "https://github.com/apple/swift-log.git", + "state" : { + "revision" : "c9c6bb9d275ec0d95c3ad16391f3f045dc623104", + "version" : "1.16.2" + } + }, + { + "identity" : "swift-metrics", + "kind" : "remoteSourceControl", + "location" : "https://github.com/apple/swift-metrics.git", + "state" : { + "revision" : "9467f9cc3c32b4386fad1ecc4618328887ef5758", + "version" : "2.12.0" + } + }, + { + "identity" : "swift-nio", + "kind" : "remoteSourceControl", + "location" : "https://github.com/apple/swift-nio.git", + "state" : { + "revision" : "6db9d9abd92ad7399b8a564cafbb87d3507245fa", + "version" : "2.104.0" + } + }, + { + "identity" : "swift-nio-extras", + "kind" : "remoteSourceControl", + "location" : "https://github.com/apple/swift-nio-extras.git", + "state" : { + "revision" : "41449336c8ecfadac6b4b5be75f9c3c306e61ced", + "version" : "1.35.1" + } + }, + { + "identity" : "swift-nio-http2", + "kind" : "remoteSourceControl", + "location" : "https://github.com/apple/swift-nio-http2.git", + "state" : { + "revision" : "0f3e54e29c944c2e835ad52159da7d9e1c94ac69", + "version" : "1.46.0" + } + }, + { + "identity" : "swift-nio-ssl", + "kind" : "remoteSourceControl", + "location" : "https://github.com/apple/swift-nio-ssl.git", + "state" : { + "revision" : "322f3c2a4a21df31c84ca416bf65ee5e9059e440", + "version" : "2.37.5" + } + }, + { + "identity" : "swift-nio-transport-services", + "kind" : "remoteSourceControl", + "location" : "https://github.com/apple/swift-nio-transport-services.git", + "state" : { + "revision" : "67787bb645a5e67d2edcdfbe48a216cc549222d5", + "version" : "1.28.0" + } + }, + { + "identity" : "swift-numerics", + "kind" : "remoteSourceControl", + "location" : "https://github.com/apple/swift-numerics.git", + "state" : { + "revision" : "0c0290ff6b24942dadb83a929ffaaa1481df04a2", + "version" : "1.1.1" + } + }, + { + "identity" : "swift-service-context", + "kind" : "remoteSourceControl", + "location" : "https://github.com/apple/swift-service-context.git", + "state" : { + "revision" : "853ba3d9ce78851b9cc20108e8bd4f13f7fa546c", + "version" : "1.5.1" + } + }, + { + "identity" : "swift-service-lifecycle", + "kind" : "remoteSourceControl", + "location" : "https://github.com/swift-server/swift-service-lifecycle.git", + "state" : { + "revision" : "a18ee16b8039866fd919bdb4e8e906882d4ecc75", + "version" : "2.13.0" + } + }, + { + "identity" : "swift-system", + "kind" : "remoteSourceControl", + "location" : "https://github.com/apple/swift-system", + "state" : { + "revision" : "869129b7bf4ecc57b97d0193ad29690ca2134750", + "version" : "1.8.1" + } + } + ], + "version" : 3 +} diff --git a/code-samples/community/serving/helloworld-swift/Package.swift b/code-samples/community/serving/helloworld-swift/Package.swift index be32db153e..636efc99e8 100644 --- a/code-samples/community/serving/helloworld-swift/Package.swift +++ b/code-samples/community/serving/helloworld-swift/Package.swift @@ -1,16 +1,16 @@ -// swift-tools-version:4.0 - -import PackageDescription - -let package = Package( - name: "HelloSwift", - dependencies: [ - .package(url: "https://github.com/httpswift/swifter.git", .upToNextMajor(from: "1.4.5")) - ], - targets: [ - .target( - name: "HelloSwift", - dependencies: ["Swifter"], - path: "./Sources") - ] -) +// swift-tools-version:6.4 + +import PackageDescription + +let package = Package( + name: "HelloSwift", + dependencies: [ + .package(url: "https://github.com/hummingbird-project/hummingbird.git", from: "2.28.0") + ], + targets: [ + .executableTarget( + name: "HelloSwift", + dependencies: [.product(name: "Hummingbird", package: "hummingbird")] + ) + ] +) diff --git a/code-samples/community/serving/helloworld-swift/README.md b/code-samples/community/serving/helloworld-swift/README.md index 28174a81a7..7fd3838c26 100644 --- a/code-samples/community/serving/helloworld-swift/README.md +++ b/code-samples/community/serving/helloworld-swift/README.md @@ -1,161 +1,101 @@ # Hello World - Swift -A simple web app written in Swift that you can use for testing. The app reads in -an env variable `TARGET` and prints "Hello \${TARGET}!". If TARGET is not -specified, the app uses "World" as the TARGET. +A small HTTP app built with [Swift 6.4.1](https://www.swift.org/install/) and +[Hummingbird 2.28.0](https://github.com/hummingbird-project/hummingbird/releases/tag/2.28.0). +It returns an HTML page containing "Hello ${TARGET}". `TARGET` defaults to +`World`; an explicitly empty `TARGET` produces an empty name. `PORT` defaults to +`8080` and must be an integer from 1 through 65535. The server binds to +`0.0.0.0`, so Knative can reach it. + +GET and POST `/` return `Content-Type: text/html` and this exact response when +`TARGET=Swift`: + +```html +Hello Swift +``` ## Prerequisites -- You must have a Kubernetes cluster with Knative installed and DNS configured. - If you need to create a cluster, follow the +- A Kubernetes cluster with Knative Serving and DNS configured. See the [Knative installation instructions](https://knative.dev/docs/install/). -- You must have [Docker](https://www.docker.com) installed and running on your - local machine, and a Docker Hub account configured (used for container - registry). - -## Recreating the sample code - -While you can clone all of the code from this directory, it might be more useful -if you build this app step-by-step. The following instructions recreate the -source files from this folder. - -1. Create a the `Package.swift` to declare your package and its dependencies. - This app uses [Swifter](https://github.com/httpswift/swifter), a tiny http - server engine for Swift. - - ```swift - // swift-tools-version:4.0 - - import PackageDescription - - let package = Package( - name: "HelloSwift", - dependencies: [ - .package(url: "https://github.com/httpswift/swifter.git", .upToNextMajor(from: "1.4.5")) - ], - targets: [ - .target( - name: "HelloSwift", - dependencies: ["Swifter"], - path: "./Sources") - ] - ) - ``` +- Docker and a container registry account for publishing your image. -1. Add the web server code to a file named `main.swift` in a - `Sources/HelloSwift/` folder: - - ```swift - import Swifter - import Dispatch - import Foundation - - let server = HttpServer() - server["/"] = { r in - let target = ProcessInfo.processInfo.environment["TARGET"] ?? "World" - return HttpResponse.ok(.html("Hello \(target)")) - } - - let semaphore = DispatchSemaphore(value: 0) - do { - let port = UInt16(ProcessInfo.processInfo.environment["PORT"] ?? "8080") - try server.start(port!, forceIPv4: true) - print("Server has started ( port = \(try server.port()) ). Try to connect now...") - semaphore.wait() - } catch { - print("Server start error: \(error)") - semaphore.signal() - } - ``` +The official `swift:6.4.1-noble` builder and `swift:6.4.1-noble-slim` runtime +images support both `linux/arm64` and `linux/amd64`. They use Ubuntu 24.04 LTS. +The runtime image contains the matching Swift runtime without the compiler; +the application runs as an unprivileged numeric user. Use a platform matching +your cluster nodes, or publish both architectures with buildx. -1. In your project directory, create a file named `Dockerfile` and copy the code - block below into it. +## Using the sample code - ```Dockerfile - # Use the official Swift image. - # https://hub.docker.com/_/swift - FROM swift:4.2 +Clone the repository and work in `code-samples/community/serving/helloworld-swift`. - # Copy local code to the container image. - WORKDIR /app - COPY . . +- [Package.swift](Package.swift) declares the executable and HTTP dependency. [Package.resolved](Package.resolved) locks dependency revisions. +- [Greeting.swift](Sources/HelloSwift/Greeting.swift) reads and validates the + environment and constructs the HTML response. +- [main.swift](Sources/HelloSwift/main.swift) registers GET/POST routes and + starts Hummingbird on `0.0.0.0` with the configured port. +- [Dockerfile](Dockerfile) builds the app and its unprivileged runtime image. +- [service.yaml](service.yaml) deploys the application with `TARGET=Swift`. + Replace its `{username}` image placeholder before deploying. - # Install dependencies and build. - RUN swift build -c release +## Testing locally - # Run the web service on container startup. - CMD [ ".build/release/HelloSwift"] - ``` +Build the final runtime image with the locked Swift toolchain: -1. Create a new file, `service.yaml`, and copy the following service definition - into the file. Replace `{username}` with your Docker Hub username. - - ```yaml - apiVersion: serving.knative.dev/v1 - kind: Service - metadata: - name: helloworld-swift - namespace: default - spec: - template: - spec: - containers: - - image: docker.io/{username}/helloworld-swift - env: - - name: TARGET - value: "Swift" - ``` +```bash +docker build -t helloworld-swift . +docker run --rm -p 127.0.0.1:8080:8080 -e TARGET=Swift helloworld-swift +``` -## Building and deploying the sample +In another terminal: -Once you have recreated the sample code files (or used the files in the sample -folder) you're ready to build and deploy the sample app. +```bash +curl http://127.0.0.1:8080/ +# Hello Swift +``` + +To exercise a custom port, use `-e PORT=9090 -p 127.0.0.1:9090:9090`. Omit +`TARGET` to get `Hello World`; use `-e TARGET=` to test an empty name. Invalid +`PORT` values cause startup to fail instead of silently listening elsewhere. +Missing paths return HTTP 404 using Hummingbird's default error response. + +## Building and deploying the sample -1. Use Docker to build the sample code into a container. To build and push with - Docker Hub, run these commands, replacing `{username}` with your Docker Hub - username: +1. Build and push for your cluster's architecture. For a registry image that + supports both native platforms, use: ```bash - # Build and push the container on your local machine. docker buildx build --platform linux/arm64,linux/amd64 -t "{username}/helloworld-swift" --push . ``` -1. After the build has completed and the container is pushed to Docker Hub, you - can deploy the app into your cluster. Ensure that the container image value - in the `service.yaml` file matches the container you built in the previous - step. Apply the configuration using the `kubectl` command: +1. Ensure `service.yaml` references the image you published, then deploy: ```bash kubectl apply --filename service.yaml + kubectl wait --for=condition=Ready ksvc/helloworld-swift --timeout=180s ``` -1. Now that your service is created, Knative performs the following steps: + Knative creates an immutable Revision, programs the route and ingress, and + automatically scales the application (including to zero). - - Creates a new immutable revision for this version of the app. - - Network programming to create a route, ingress, service, and load balancing - for your app. - - Automatically scales your pods up and down (including to zero active pods). +1. Find the actual URL: -1. To find the URL for your service, use the following command: - - ``` - kubectl get ksvc helloworld-swift --output=custom-columns=NAME:.metadata.name,URL:.status.url - NAME URL - helloworld-swift http://helloworld-swift.default.1.2.3.4.sslip.io + ```bash + kubectl get ksvc helloworld-swift --output=custom-columns=NAME:.metadata.name,URL:.status.url + # NAME URL + # helloworld-swift http://helloworld-swift.default.1.2.3.4.sslip.io ``` -1. Now you can make a request to your app and see the result. Replace - the URL below with the URL returned in the previous command. +1. Request the returned URL (replace the example URL with yours): ```bash curl http://helloworld-swift.default.1.2.3.4.sslip.io - Hello Swift + # Hello Swift ``` ## Removing the sample app deployment -To remove the sample app from your cluster, delete the service record: - ```bash kubectl delete --filename service.yaml ``` diff --git a/code-samples/community/serving/helloworld-swift/Sources/HelloSwift/Greeting.swift b/code-samples/community/serving/helloworld-swift/Sources/HelloSwift/Greeting.swift new file mode 100644 index 0000000000..ea4649356d --- /dev/null +++ b/code-samples/community/serving/helloworld-swift/Sources/HelloSwift/Greeting.swift @@ -0,0 +1,29 @@ +import Hummingbird + +struct Configuration { + let target: String + let port: Int + + enum Error: Swift.Error { + case invalidPort(String) + } + + init(environment: [String: String]) throws { + target = environment["TARGET"] ?? "World" + let value = environment["PORT"] ?? "8080" + guard let port = Int(value), (1...65535).contains(port) else { + throw Error.invalidPort(value) + } + self.port = port + } +} + +func greeting(target: String) -> Response { + Response( + status: .ok, + headers: [.contentType: "text/html"], + body: .init( + byteBuffer: .init(string: #"Hello \#(target)"#) + ) + ) +} diff --git a/code-samples/community/serving/helloworld-swift/Sources/HelloSwift/main.swift b/code-samples/community/serving/helloworld-swift/Sources/HelloSwift/main.swift index da76a1bf5d..fc58f0af25 100644 --- a/code-samples/community/serving/helloworld-swift/Sources/HelloSwift/main.swift +++ b/code-samples/community/serving/helloworld-swift/Sources/HelloSwift/main.swift @@ -1,20 +1,16 @@ -import Swifter -import Dispatch -import Foundation - -let server = HttpServer() -server["/"] = { r in - let target = ProcessInfo.processInfo.environment["TARGET"] ?? "World" - return HttpResponse.ok(.html("Hello \(target)")) -} - -let semaphore = DispatchSemaphore(value: 0) -do { - let port = UInt16(ProcessInfo.processInfo.environment["PORT"] ?? "8080") - try server.start(port!, forceIPv4: true) - print("Server has started ( port = \(try server.port()) ). Try to connect now...") - semaphore.wait() -} catch { - print("Server start error: \(error)") - semaphore.signal() -} +import Foundation +import Hummingbird + +let configuration = try Configuration(environment: ProcessInfo.processInfo.environment) +let router = Router() +router.get("/") { _, _ in + greeting(target: configuration.target) +} +router.post("/") { _, _ in + greeting(target: configuration.target) +} +let app = Application( + router: router, + configuration: .init(address: .hostname("0.0.0.0", port: configuration.port)) +) +try await app.runService() diff --git a/code-samples/community/serving/helloworld-swift/ci.sh b/code-samples/community/serving/helloworld-swift/ci.sh new file mode 100755 index 0000000000..280808e2b9 --- /dev/null +++ b/code-samples/community/serving/helloworld-swift/ci.sh @@ -0,0 +1,40 @@ +#!/usr/bin/env bash +# Source from this sample directory; the caller owns cluster setup and ingress forwarding. +IMAGE=dev.local/helloworld-swift:ci +SAMPLE_URL=http://127.0.0.1:18080 + +main() ( + set -euo pipefail + build + deploy + test +) + +build() ( + set -euo pipefail + docker build -t "$IMAGE" . +) + +deploy() ( + set -euo pipefail + kind load docker-image "$IMAGE" + kubectl patch --local -f service.yaml --type=json -p "$( + jq -cn --arg image "$IMAGE" '[ + {op: "replace", path: "/spec/template/spec/containers/0/image", value: $image}, + {op: "add", path: "/spec/template/spec/containers/0/imagePullPolicy", value: "IfNotPresent"} + ]' + )" -o yaml | kubectl --namespace default apply -f - + kubectl --namespace default wait -f service.yaml --for=condition=Ready --timeout=300s +) + +test() ( + set -euo pipefail + local service_url service_host + service_url=$(kubectl --namespace default get -f service.yaml -o jsonpath='{.status.url}') + service_host=${service_url#*://} + [[ "$service_host" =~ ^[a-zA-Z0-9.-]+$ ]] + local body + body=$(curl -fsS --max-time 30 \ + -H "Host: $service_host" "$SAMPLE_URL/") + [[ $body == 'Hello Swift' ]] +) From 69278d6d253260120cb725ac3a9c114772bbc197 Mon Sep 17 00:00:00 2001 From: Dave Protasowski Date: Mon, 26 Oct 2026 09:16:03 -0400 Subject: [PATCH 11/44] Update the Vert.x Hello World sample --- .../serving/helloworld-vertx/Dockerfile | 17 +- .../serving/helloworld-vertx/README.md | 333 ++++++------------ .../community/serving/helloworld-vertx/ci.sh | 40 +++ .../serving/helloworld-vertx/pom.xml | 38 +- .../com/example/helloworld/HelloWorld.java | 54 ++- 5 files changed, 204 insertions(+), 278 deletions(-) create mode 100755 code-samples/community/serving/helloworld-vertx/ci.sh diff --git a/code-samples/community/serving/helloworld-vertx/Dockerfile b/code-samples/community/serving/helloworld-vertx/Dockerfile index 2080225e73..b4415884b4 100644 --- a/code-samples/community/serving/helloworld-vertx/Dockerfile +++ b/code-samples/community/serving/helloworld-vertx/Dockerfile @@ -1,7 +1,12 @@ -# Use fabric8's s2i Builder image. -# https://hub.docker.com/r/fabric8/s2i-java -FROM fabric8/s2i-java:2.0 +# Build the application without requiring Maven or Java on the host. +FROM maven:3.9.16-eclipse-temurin-25 AS build +WORKDIR /app +COPY pom.xml . +COPY src ./src +RUN mvn --batch-mode clean package -# Copy the JAR file to the deployment directory. -ENV JAVA_APP_DIR=/deployments -COPY target/helloworld-1.0.0-SNAPSHOT.jar /deployments/ +FROM eclipse-temurin:25-jre +WORKDIR /app +COPY --from=build /app/target/helloworld-1.0.0-SNAPSHOT.jar app.jar +EXPOSE 8080 +ENTRYPOINT ["java", "-jar", "app.jar"] diff --git a/code-samples/community/serving/helloworld-vertx/README.md b/code-samples/community/serving/helloworld-vertx/README.md index d0976761a6..f755625177 100644 --- a/code-samples/community/serving/helloworld-vertx/README.md +++ b/code-samples/community/serving/helloworld-vertx/README.md @@ -1,249 +1,124 @@ -# Hello World - Eclipse Vert.x - -Learn how to deploy a simple web app that is written in Java and uses Eclipse -Vert.x. This samples uses Docker to build locally. The app reads in a `TARGET` -env variable and then prints "Hello World: \${TARGET}!". If a value for `TARGET` -is not specified, the "NOT SPECIFIED" default value is used. - -Use this sample to walk you through the steps of creating and modifying the -sample app, building and pushing your container image to a registry, and then -deploying your app to your Knative cluster. - -## Before you begin - -You must meet the following requirements to complete this sample: - -- A version of the Knative Serving component installed and running on your - Kubernetes cluster. Follow the - [Knative installation instructions](https://knative.dev/docs/install/) if you need to - create a Knative cluster. -- The following software downloaded and install on your loacal machine: - - [Java SE 8 or later JDK](http://www.oracle.com/technetwork/java/javase/downloads/index.html). - - [Eclipse Vert.x v3.5.4](https://vertx.io/). - - [Docker](https://www.docker.com) for building and pushing your container - image. - - [curl](https://curl.haxx.se/) to test the sample app after deployment. -- A [Docker Hub](https://hub.docker.com/) account where you can push your - container image. - -**Tip**: You can clone the [Knative/docs repo](https://github.com/knative/docs) -and then modify the source files. Alternatively, learn more by manually creating -the files yourself. - -## Creating and configuring the sample code - -To create and configure the source files in the root of your working directory: - -1. Create the `pom.xml` file: - - ```xml - - 4.0.0 - com.example.vertx - helloworld - 1.0.0-SNAPSHOT - - - - io.vertx - vertx-core - ${version.vertx} - - - io.vertx - vertx-rx-java2 - ${version.vertx} - - - - - - - maven-compiler-plugin - 3.8.0 - - 1.8 - 1.8 - - - - org.apache.maven.plugins - maven-shade-plugin - 3.2.0 - - - package - - shade - - - - - - io.vertx.core.Launcher - com.example.helloworld.HelloWorld - - - - - - - - - - - - - 3.5.4 - - - ``` - -1. Create the `HelloWorld.java` file in the - `src/main/java/com/example/helloworld` directory. The - `[ROOT]/src/main/java/com/example/helloworld/HelloWorld.java` file creates a - basic web server that listens on port `8080`. - - ```java - package com.example.helloworld; - - import io.reactivex.Flowable; - import io.vertx.reactivex.core.AbstractVerticle; - import io.vertx.reactivex.core.http.HttpServer; - import io.vertx.reactivex.core.http.HttpServerRequest; - - public class HelloWorld extends AbstractVerticle { - - public void start() { - - final HttpServer server = vertx.createHttpServer(); - final Flowable requestFlowable = server.requestStream().toFlowable(); - - requestFlowable.subscribe(httpServerRequest -> { - - String target = System.getenv("TARGET"); - if (target == null) { - target = "NOT SPECIFIED"; - } - - httpServerRequest.response().setChunked(true) - .putHeader("content-type", "text/plain") - .setStatusCode(200) // OK - .end("Hello World: " + target); - }); - - server.listen(8080); - } - } - ``` - -1. Create the `Dockerfile` file: - - ```docker - # Use fabric8's s2i Builder image. - # https://hub.docker.com/r/fabric8/s2i-java - FROM fabric8/s2i-java:2.0 - - # Copy the JAR file to the deployment directory. - ENV JAVA_APP_DIR=/deployments - COPY target/helloworld-1.0.0-SNAPSHOT.jar /deployments/ - ``` - -1. Create the `service.yaml` file. You must specify your Docker Hub username in - `{username}`. You can also configure the `TARGET`, for example you can modify - the `Eclipse Vert.x Sample v1` value. - - ```yaml - apiVersion: serving.knative.dev/v1 - kind: Service - metadata: - name: helloworld-vertx - namespace: default - spec: - template: - spec: - containers: - - image: docker.io/{username}/helloworld-vertx - env: - - name: TARGET - value: "Eclipse Vert.x Sample v1" - ``` - -## Building and deploying the sample - -To build a container image, push your image to the registry, and then deploy -your sample app to your cluster: - -1. Use Docker to build your container image and then push that image to your - Docker Hub registry. You must replace the `{username}` variables in the - following commands with your Docker Hub username. - - ```bash - # Build and push the container on your local machine. - docker buildx build --platform linux/arm64,linux/amd64 -t "{username}/helloworld-vertx" --push . - ``` - -1. Now that your container image is in the registry, you can deploy it to your - Knative cluster by running the `kubectl apply` command: +# Hello World — Eclipse Vert.x - ```bash - kubectl apply --filename service.yaml - ``` +Deploy a minimal Java HTTP application to Knative Serving using Eclipse Vert.x +5.2.2 and Eclipse Temurin Java 21 LTS. Docker supplies Maven 3.9.16, the JDK, and +all dependencies; no host Java, Maven, or Vert.x installation is required. - Result: A service name `helloworld-vertx` is created in your cluster along - with the following resources: +## Application behavior - - A new immutable revision for the version of the app that you just deployed. - - The following networking resources are created for your app: - - route - - ingress - - service - - load balancer - - Auto scaling is enable to allow your pods to scale up to meet traffic, and - also back down to zero when there is no traffic. +- `TARGET` defaults to `NOT SPECIFIED` when unset. An explicitly empty value stays + empty. +- `PORT` defaults to `8080`; Knative supplies it automatically. A custom port must + be an integer from 1 to 65535. Invalid or empty values fail at startup. +- Requests on any path return HTTP 200 and `Content-Type: text/plain`. The exact + body is `Hello World: ${TARGET}`, without an exclamation mark or trailing + newline. HTTP/1.1 GET and POST responses are chunked; HEAD has no body. -## Testing the sample app +The [Java source](src/main/java/com/example/helloworld/HelloWorld.java) uses the +Vert.x core asynchronous HTTP API directly. The [POM](pom.xml) builds an +executable shaded JAR. The +[Dockerfile](Dockerfile) builds in a JDK stage, then copies only the +application JAR into a public Temurin JRE image. -To verify that your sample app has been successfully deployed: +## Prerequisites -1. Retrieve the URL for your service, by running the following `kubectl get` - command: +- Docker for building and running the container. +- curl for HTTP checks. +- For cluster deployment: a working Kubernetes cluster with + [Knative Serving installed](https://knative.dev/docs/install/), kubectl, and + permission to push an image to a registry your cluster can access. - ```bash - kubectl get ksvc helloworld-vertx --output=custom-columns=NAME:.metadata.name,URL:.status.url - ``` +Clone the [Knative docs repository](https://github.com/knative/docs), then run all +commands below from `code-samples/community/serving/helloworld-vertx`. - Example result: +## Build and run locally - ```bash - NAME URL - helloworld-vertx http://helloworld-vertx.default.1.2.3.4.sslip.io - ``` +Build the final image: -1. Now you can make a request to your app and see the result. Replace - the URL below with the URL returned in the previous command. +```bash +docker build -t helloworld-vertx:local . +``` + +Start the image with default settings: + +```bash +docker run --rm --name helloworld-vertx -p 8080:8080 helloworld-vertx:local +``` + +In another terminal: + +```bash +curl --fail --include http://localhost:8080/ +# HTTP 200, Content-Type: text/plain +# Body: Hello World: NOT SPECIFIED + +docker stop helloworld-vertx +``` + +Test both a custom port and a custom greeting: + +```bash +docker run --rm --name helloworld-vertx -p 9090:9090 \ + -e PORT=9090 -e 'TARGET=Eclipse Vert.x Sample v1' helloworld-vertx:local +``` + +In another terminal: - ```bash - curl http://helloworld-vertx.default.1.2.3.4.sslip.io - ``` +```bash +curl --fail http://localhost:9090/ +# Exact body: Hello World: Eclipse Vert.x Sample v1 + +docker stop helloworld-vertx +``` + +## Publish and deploy to Knative + +Replace `{username}` in these commands and in [service.yaml](service.yaml) with +your registry username. Build for your cluster's CPU architecture; Docker builds +for the local architecture by default. Use an explicit image tag in both places. + +```bash +docker build -t docker.io/{username}/helloworld-vertx . +docker push docker.io/{username}/helloworld-vertx +``` - Example result: +The checked-in Service uses `TARGET=Eclipse Vert.x Sample v1`. Do not override +`PORT` in its environment unless you also configure the corresponding container +port in the Knative Service. Knative's default is 8080. For a custom Knative +port, use a non-reserved port such as 8081; 9090 and 9091 are reserved by +Knative even though they work for standalone Docker containers. - ```bash - Hello World: Eclipse Vert.x Sample v1 - ``` +```bash +kubectl apply --filename service.yaml +kubectl wait --for=condition=Ready ksvc/helloworld-vertx --timeout=180s +kubectl get ksvc helloworld-vertx \ + --output=custom-columns=NAME:.metadata.name,URL:.status.url +``` -Congratulations on deploying your sample Java app to Knative! +Request the returned URL: + +```bash +curl --fail "$(kubectl get ksvc helloworld-vertx -o jsonpath='{.status.url}')" +# Exact body: Hello World: Eclipse Vert.x Sample v1 +``` -## Removing the sample app deployment +To change the greeting, edit `TARGET` in `service.yaml` and apply it again. Knative +creates an immutable revision and routes traffic to it when ready. Serving also +supports scaling this application down to zero and reactivating it on demand. -To remove the sample app from your cluster, run the following `kubectl delete` -command: +## Clean up ```bash kubectl delete --filename service.yaml ``` + +## Version and support references + +- [Vert.x 5.2.2 source and release](https://github.com/eclipse-vertx/vert.x/tree/5.2.2) + and [published versions](https://repo.maven.apache.org/maven2/io/vertx/vertx-core/maven-metadata.xml). +- [OpenJDK 21 LTS](https://openjdk.org/projects/jdk/21/) and public + [Eclipse Temurin container images](https://github.com/adoptium/containers). +- [Apache Maven release/support history](https://maven.apache.org/docs/history.html). + +These are public open-source toolchains; no commercial JDK subscription is +required. Java 21 is used consistently for compilation, testing, and runtime. diff --git a/code-samples/community/serving/helloworld-vertx/ci.sh b/code-samples/community/serving/helloworld-vertx/ci.sh new file mode 100755 index 0000000000..fa58cb5cdf --- /dev/null +++ b/code-samples/community/serving/helloworld-vertx/ci.sh @@ -0,0 +1,40 @@ +#!/usr/bin/env bash +# Source from this sample directory; the caller owns cluster setup and ingress forwarding. +IMAGE=dev.local/helloworld-vertx:ci +SAMPLE_URL=http://127.0.0.1:18080 + +main() ( + set -euo pipefail + build + deploy + test +) + +build() ( + set -euo pipefail + docker build -t "$IMAGE" . +) + +deploy() ( + set -euo pipefail + kind load docker-image "$IMAGE" + kubectl patch --local -f service.yaml --type=json -p "$( + jq -cn --arg image "$IMAGE" '[ + {op: "replace", path: "/spec/template/spec/containers/0/image", value: $image}, + {op: "add", path: "/spec/template/spec/containers/0/imagePullPolicy", value: "IfNotPresent"} + ]' + )" -o yaml | kubectl --namespace default apply -f - + kubectl --namespace default wait -f service.yaml --for=condition=Ready --timeout=300s +) + +test() ( + set -euo pipefail + local service_url service_host + service_url=$(kubectl --namespace default get -f service.yaml -o jsonpath='{.status.url}') + service_host=${service_url#*://} + [[ "$service_host" =~ ^[a-zA-Z0-9.-]+$ ]] + local body + body=$(curl -fsS --max-time 30 \ + -H "Host: $service_host" "$SAMPLE_URL/") + [[ $body == 'Hello World: Eclipse Vert.x Sample v1' ]] +) diff --git a/code-samples/community/serving/helloworld-vertx/pom.xml b/code-samples/community/serving/helloworld-vertx/pom.xml index 0c07df1998..b280da5d1d 100644 --- a/code-samples/community/serving/helloworld-vertx/pom.xml +++ b/code-samples/community/serving/helloworld-vertx/pom.xml @@ -1,39 +1,36 @@ + xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd"> 4.0.0 com.example.vertx helloworld 1.0.0-SNAPSHOT + + 21 + UTF-8 + 5.2.2 + + io.vertx vertx-core ${version.vertx} - - io.vertx - vertx-rx-java2 - ${version.vertx} - + org.apache.maven.plugins maven-compiler-plugin - 3.8.0 - - 1.8 - 1.8 - + 3.16.0 org.apache.maven.plugins maven-shade-plugin - 3.2.0 + 3.6.2 package @@ -41,24 +38,17 @@ shade + false - - - io.vertx.core.Launcher - com.example.helloworld.HelloWorld - + + + com.example.helloworld.HelloWorld - - - - 4.5.24 - diff --git a/code-samples/community/serving/helloworld-vertx/src/main/java/com/example/helloworld/HelloWorld.java b/code-samples/community/serving/helloworld-vertx/src/main/java/com/example/helloworld/HelloWorld.java index 53d8d9868d..9145eb4748 100644 --- a/code-samples/community/serving/helloworld-vertx/src/main/java/com/example/helloworld/HelloWorld.java +++ b/code-samples/community/serving/helloworld-vertx/src/main/java/com/example/helloworld/HelloWorld.java @@ -1,30 +1,46 @@ package com.example.helloworld; -import io.reactivex.Flowable; -import io.vertx.reactivex.core.AbstractVerticle; -import io.vertx.reactivex.core.http.HttpServer; -import io.vertx.reactivex.core.http.HttpServerRequest; +import io.vertx.core.Future; +import io.vertx.core.Vertx; +import io.vertx.core.http.HttpServer; -public class HelloWorld extends AbstractVerticle { +import java.util.Map; - public void start() { +public final class HelloWorld { + private HelloWorld() { + } - final HttpServer server = vertx.createHttpServer(); - final Flowable requestFlowable = server.requestStream().toFlowable(); + static int port(Map environment) { + int port = Integer.parseInt(environment.getOrDefault("PORT", "8080")); + if (port < 1 || port > 65535) { + throw new IllegalArgumentException("PORT must be between 1 and 65535"); + } + return port; + } - requestFlowable.subscribe(httpServerRequest -> { + static String greeting(Map environment) { + return "Hello World: " + environment.getOrDefault("TARGET", "NOT SPECIFIED"); + } - String target = System.getenv("TARGET"); - if (target == null) { - target = "NOT SPECIFIED"; - } + static Future listen(Vertx vertx, int port, String greeting) { + return vertx.createHttpServer() + .requestHandler(request -> request.response() + .setChunked(true) + .putHeader("content-type", "text/plain") + .end(greeting)) + .listen(port, "0.0.0.0"); + } - httpServerRequest.response().setChunked(true) - .putHeader("content-type", "text/plain") - .setStatusCode(200) // OK - .end("Hello World: " + target); + public static void main(String[] args) { + Map environment = System.getenv(); + int port = port(environment); + String greeting = greeting(environment); + Vertx vertx = Vertx.vertx(); + Runtime.getRuntime().addShutdownHook(new Thread(() -> + vertx.close().toCompletionStage().toCompletableFuture().join())); + listen(vertx, port, greeting).onFailure(error -> { + error.printStackTrace(System.err); + System.exit(1); }); - - server.listen(8080); } } From fb30c69107f90abe01480559159741e7da373a05 Mon Sep 17 00:00:00 2001 From: Dave Protasowski Date: Mon, 26 Oct 2026 09:16:03 -0400 Subject: [PATCH 12/44] Update the Bookstore sample and its source links --- .../ML-bad-word-filter/tests/test_func.py | 50 - .../ML-sentiment-analysis/tests/test_func.py | 50 - .../bookstore-sample-app/solution/README.md | 2 +- .../.funcignore | 0 .../.gitignore | 0 .../README.md | 2 +- .../func.yaml | 0 .../function/__init__.py | 0 .../function/func.py | 6 +- .../pyproject.toml | 9 +- .../db-service/200-create-postgre.yaml | 2 + .../solution/db-service/README.md | 14 +- .../solution/frontend/.eslintrc.json | 3 - .../solution/frontend/Dockerfile | 2 +- .../solution/frontend/README.md | 6 +- .../solution/frontend/app/globals.css | 4 +- .../solution/frontend/eslint.config.mjs | 6 + .../solution/frontend/package-lock.json | 528 +++++---- .../solution/frontend/package.json | 8 +- .../solution/node-server/index.js | 6 +- .../solution/node-server/package.json | 3 - .../.funcignore | 0 .../.gitignore | 0 .../README.md | 2 +- .../func.yaml | 0 .../function/__init__.py | 0 .../function/func.py | 7 +- .../pyproject.toml | 9 +- .../solution/slack-sink/README.md | 4 +- .../bookstore-sample-app/solution/solution.sh | 4 +- .../start/db-service/200-create-postgre.yaml | 2 + .../start/db-service/README.md | 14 +- .../start/frontend/.eslintrc.json | 3 - .../start/frontend/Dockerfile | 2 +- .../start/frontend/README.md | 6 +- .../start/frontend/app/globals.css | 4 +- .../start/frontend/eslint.config.mjs | 6 + .../start/frontend/package-lock.json | 1042 ++++++++++++----- .../start/frontend/package.json | 10 +- .../start/node-server/index.js | 6 +- .../start/node-server/package.json | 3 - .../start/slack-sink/README.md | 12 +- ...-analysis-service-for-bookstore-reviews.md | 4 +- .../page-3/create-bad-word-filter-service.md | 4 +- 44 files changed, 1133 insertions(+), 712 deletions(-) delete mode 100644 code-samples/eventing/bookstore-sample-app/solution/ML-bad-word-filter/tests/test_func.py delete mode 100644 code-samples/eventing/bookstore-sample-app/solution/ML-sentiment-analysis/tests/test_func.py rename code-samples/eventing/bookstore-sample-app/solution/{ML-bad-word-filter => bad-word-filter}/.funcignore (100%) rename code-samples/eventing/bookstore-sample-app/solution/{ML-bad-word-filter => bad-word-filter}/.gitignore (100%) rename code-samples/eventing/bookstore-sample-app/solution/{ML-bad-word-filter => bad-word-filter}/README.md (96%) rename code-samples/eventing/bookstore-sample-app/solution/{ML-bad-word-filter => bad-word-filter}/func.yaml (100%) rename code-samples/eventing/bookstore-sample-app/solution/{ML-bad-word-filter => bad-word-filter}/function/__init__.py (100%) rename code-samples/eventing/bookstore-sample-app/solution/{ML-bad-word-filter => bad-word-filter}/function/func.py (90%) rename code-samples/eventing/bookstore-sample-app/solution/{ML-bad-word-filter => bad-word-filter}/pyproject.toml (66%) delete mode 100644 code-samples/eventing/bookstore-sample-app/solution/frontend/.eslintrc.json create mode 100644 code-samples/eventing/bookstore-sample-app/solution/frontend/eslint.config.mjs rename code-samples/eventing/bookstore-sample-app/solution/{ML-sentiment-analysis => sentiment-analysis}/.funcignore (100%) rename code-samples/eventing/bookstore-sample-app/solution/{ML-sentiment-analysis => sentiment-analysis}/.gitignore (100%) rename code-samples/eventing/bookstore-sample-app/solution/{ML-sentiment-analysis => sentiment-analysis}/README.md (96%) rename code-samples/eventing/bookstore-sample-app/solution/{ML-sentiment-analysis => sentiment-analysis}/func.yaml (100%) rename code-samples/eventing/bookstore-sample-app/solution/{ML-sentiment-analysis => sentiment-analysis}/function/__init__.py (100%) rename code-samples/eventing/bookstore-sample-app/solution/{ML-sentiment-analysis => sentiment-analysis}/function/func.py (88%) rename code-samples/eventing/bookstore-sample-app/solution/{ML-sentiment-analysis => sentiment-analysis}/pyproject.toml (65%) delete mode 100644 code-samples/eventing/bookstore-sample-app/start/frontend/.eslintrc.json create mode 100644 code-samples/eventing/bookstore-sample-app/start/frontend/eslint.config.mjs diff --git a/code-samples/eventing/bookstore-sample-app/solution/ML-bad-word-filter/tests/test_func.py b/code-samples/eventing/bookstore-sample-app/solution/ML-bad-word-filter/tests/test_func.py deleted file mode 100644 index ad126f7230..0000000000 --- a/code-samples/eventing/bookstore-sample-app/solution/ML-bad-word-filter/tests/test_func.py +++ /dev/null @@ -1,50 +0,0 @@ -""" -Unit tests for the CloudEvent function implementation. -Tests both the function health endpoints and its handling of CloudEvents. - -Functions are currently served as ASGI applications using hypercorn, so we -use httpx for testing. To run the tests using poetry: - -poetry run python -m unittest discover -""" -import json -import asyncio -import pytest -from cloudevents.http import CloudEvent -from function import new - - -@pytest.mark.asyncio -async def test_func(): - f = new() # Instantiate Function to Test - - # A test CloudEvent - attributes = { - "id": "test-id", - "type": "com.example.test1", - "source": "https://example.com/event-producer", - } - data = {"message": "test message"} - event = CloudEvent(attributes, data) - - invoked = False # Flag indicating send method was invoked - - # Send - # confirms the Functions responds with a CloudEvent which echoes - # the data sent. - async def send(e): - nonlocal invoked - invoked = True # Flag send was invoked - - # Ensure we got a CloudEvent - assert isinstance(e, CloudEvent), f"Expected CloudEvent, got {type(e)}" - - # Ensure it echoes the data sent - assert e.data == data, f"Expected data {data}, got {e.data}" - - # Invoke the Function - scope = {"event": event} # Add the CloudEvent to the scope - await f.handle(scope, {}, send) - - # Assert send was called - assert invoked, "Function did not call send" diff --git a/code-samples/eventing/bookstore-sample-app/solution/ML-sentiment-analysis/tests/test_func.py b/code-samples/eventing/bookstore-sample-app/solution/ML-sentiment-analysis/tests/test_func.py deleted file mode 100644 index ad126f7230..0000000000 --- a/code-samples/eventing/bookstore-sample-app/solution/ML-sentiment-analysis/tests/test_func.py +++ /dev/null @@ -1,50 +0,0 @@ -""" -Unit tests for the CloudEvent function implementation. -Tests both the function health endpoints and its handling of CloudEvents. - -Functions are currently served as ASGI applications using hypercorn, so we -use httpx for testing. To run the tests using poetry: - -poetry run python -m unittest discover -""" -import json -import asyncio -import pytest -from cloudevents.http import CloudEvent -from function import new - - -@pytest.mark.asyncio -async def test_func(): - f = new() # Instantiate Function to Test - - # A test CloudEvent - attributes = { - "id": "test-id", - "type": "com.example.test1", - "source": "https://example.com/event-producer", - } - data = {"message": "test message"} - event = CloudEvent(attributes, data) - - invoked = False # Flag indicating send method was invoked - - # Send - # confirms the Functions responds with a CloudEvent which echoes - # the data sent. - async def send(e): - nonlocal invoked - invoked = True # Flag send was invoked - - # Ensure we got a CloudEvent - assert isinstance(e, CloudEvent), f"Expected CloudEvent, got {type(e)}" - - # Ensure it echoes the data sent - assert e.data == data, f"Expected data {data}, got {e.data}" - - # Invoke the Function - scope = {"event": event} # Add the CloudEvent to the scope - await f.handle(scope, {}, send) - - # Assert send was called - assert invoked, "Function did not call send" diff --git a/code-samples/eventing/bookstore-sample-app/solution/README.md b/code-samples/eventing/bookstore-sample-app/solution/README.md index aacfc5b527..b88b9d4cc0 100644 --- a/code-samples/eventing/bookstore-sample-app/solution/README.md +++ b/code-samples/eventing/bookstore-sample-app/solution/README.md @@ -13,7 +13,7 @@ Here's an overview of the components in the solution: - `db-service/`: Database service for storing book reviews and comments - `frontend/`: User interface for the bookstore application built with Next.js - `node-server/`: Node.js server for handling backend operations -- `sentiment-analysis-app/`: Knative Function for analyzing the sentiment of book reviews +- `sentiment-analysis/`: Knative Function for analyzing the sentiment of book reviews - `sequence/`: Knative Sequence setup for orchestrating workflows - `slack-sink/`: Integration with Slack for notifications with Apache Camel diff --git a/code-samples/eventing/bookstore-sample-app/solution/ML-bad-word-filter/.funcignore b/code-samples/eventing/bookstore-sample-app/solution/bad-word-filter/.funcignore similarity index 100% rename from code-samples/eventing/bookstore-sample-app/solution/ML-bad-word-filter/.funcignore rename to code-samples/eventing/bookstore-sample-app/solution/bad-word-filter/.funcignore diff --git a/code-samples/eventing/bookstore-sample-app/solution/ML-bad-word-filter/.gitignore b/code-samples/eventing/bookstore-sample-app/solution/bad-word-filter/.gitignore similarity index 100% rename from code-samples/eventing/bookstore-sample-app/solution/ML-bad-word-filter/.gitignore rename to code-samples/eventing/bookstore-sample-app/solution/bad-word-filter/.gitignore diff --git a/code-samples/eventing/bookstore-sample-app/solution/ML-bad-word-filter/README.md b/code-samples/eventing/bookstore-sample-app/solution/bad-word-filter/README.md similarity index 96% rename from code-samples/eventing/bookstore-sample-app/solution/ML-bad-word-filter/README.md rename to code-samples/eventing/bookstore-sample-app/solution/bad-word-filter/README.md index 66ce194600..ca5ec6d966 100644 --- a/code-samples/eventing/bookstore-sample-app/solution/ML-bad-word-filter/README.md +++ b/code-samples/eventing/bookstore-sample-app/solution/bad-word-filter/README.md @@ -28,7 +28,7 @@ For development environments where Python is installed, it's suggested to use the `host` builder as follows: ```bash -# Run function on the host (outsdie of a container) +# Run function on the host (outside of a container) func run --builder=host ``` diff --git a/code-samples/eventing/bookstore-sample-app/solution/ML-bad-word-filter/func.yaml b/code-samples/eventing/bookstore-sample-app/solution/bad-word-filter/func.yaml similarity index 100% rename from code-samples/eventing/bookstore-sample-app/solution/ML-bad-word-filter/func.yaml rename to code-samples/eventing/bookstore-sample-app/solution/bad-word-filter/func.yaml diff --git a/code-samples/eventing/bookstore-sample-app/solution/ML-bad-word-filter/function/__init__.py b/code-samples/eventing/bookstore-sample-app/solution/bad-word-filter/function/__init__.py similarity index 100% rename from code-samples/eventing/bookstore-sample-app/solution/ML-bad-word-filter/function/__init__.py rename to code-samples/eventing/bookstore-sample-app/solution/bad-word-filter/function/__init__.py diff --git a/code-samples/eventing/bookstore-sample-app/solution/ML-bad-word-filter/function/func.py b/code-samples/eventing/bookstore-sample-app/solution/bad-word-filter/function/func.py similarity index 90% rename from code-samples/eventing/bookstore-sample-app/solution/ML-bad-word-filter/function/func.py rename to code-samples/eventing/bookstore-sample-app/solution/bad-word-filter/function/func.py index f0ead6c886..8b5986ab50 100644 --- a/code-samples/eventing/bookstore-sample-app/solution/ML-bad-word-filter/function/func.py +++ b/code-samples/eventing/bookstore-sample-app/solution/bad-word-filter/function/func.py @@ -1,5 +1,5 @@ import logging -from cloudevents.http import CloudEvent +from cloudevents.core.v1.event import CloudEvent from profanity_check import predict def new(): @@ -14,10 +14,10 @@ async def handle(self, scope, receive, send): request_event = scope["event"] # 2. Extract the data payload from the event, analyze and create CloudEvent - response_event = self.inappropriate_language_filter(request_event.data) + response_event = self.inappropriate_language_filter(request_event.get_data()) # 3. Send the response - logging.info(f"Sending response: {response_event.data}") + logging.info(f"Sending response: {response_event.get_data()}") await send(response_event) def create_cloud_event(self, inputText, data): diff --git a/code-samples/eventing/bookstore-sample-app/solution/ML-bad-word-filter/pyproject.toml b/code-samples/eventing/bookstore-sample-app/solution/bad-word-filter/pyproject.toml similarity index 66% rename from code-samples/eventing/bookstore-sample-app/solution/ML-bad-word-filter/pyproject.toml rename to code-samples/eventing/bookstore-sample-app/solution/bad-word-filter/pyproject.toml index 675a89c079..2190c7762c 100644 --- a/code-samples/eventing/bookstore-sample-app/solution/ML-bad-word-filter/pyproject.toml +++ b/code-samples/eventing/bookstore-sample-app/solution/bad-word-filter/pyproject.toml @@ -6,10 +6,7 @@ requires-python = ">=3.9" readme = "README.md" license = "MIT" dependencies = [ - "httpx", - "cloudevents", - "pytest", - "pytest-asyncio", + "cloudevents>=2.2,<3", "alt-profanity-check==1.4.1.post1" ] authors = [ @@ -20,8 +17,4 @@ authors = [ requires = ["hatchling"] build-backend = "hatchling.build" -[tool.pytest.ini_options] -asyncio_mode = "strict" -asyncio_default_fixture_loop_scope = "function" - diff --git a/code-samples/eventing/bookstore-sample-app/solution/db-service/200-create-postgre.yaml b/code-samples/eventing/bookstore-sample-app/solution/db-service/200-create-postgre.yaml index c14933e5d4..3c7574da48 100644 --- a/code-samples/eventing/bookstore-sample-app/solution/db-service/200-create-postgre.yaml +++ b/code-samples/eventing/bookstore-sample-app/solution/db-service/200-create-postgre.yaml @@ -13,6 +13,8 @@ spec: labels: app: postgresql spec: + securityContext: + fsGroup: 26 # The image runs as the non-root postgres user (UID/GID 26). containers: - name: postgresql image: quay.io/enterprisedb/postgresql diff --git a/code-samples/eventing/bookstore-sample-app/solution/db-service/README.md b/code-samples/eventing/bookstore-sample-app/solution/db-service/README.md index ad10e638fe..736e71cfb4 100644 --- a/code-samples/eventing/bookstore-sample-app/solution/db-service/README.md +++ b/code-samples/eventing/bookstore-sample-app/solution/db-service/README.md @@ -30,24 +30,24 @@ The sample rows inserted for the BookReviews table are shown below: ## Implementation ### Step 1: Acquire Necessary Files from the Repository -The essential files for setting up your database are located within the `db` directory of our repository. Please download these files to proceed. +The essential files for setting up your database are located within the `db-service` directory of our repository. Please download these files to proceed. ### Step 2: Deploying the PostgreSQL Database -To deploy the PostgreSQL database and populate it with the provided sample data, you'll apply a series of Kubernetes deployment files. Ensure you're positioned in the `code-sample` directory and not within the `db` subdirectory for this operation. +To deploy the PostgreSQL database and populate it with the provided sample data, you'll apply a series of Kubernetes deployment files. Run this command from the `solution` directory, which contains `db-service/`. Within this directory, you will find 6 YAML files, each serving a distinct purpose in the setup process: - `100-create-configmap.yaml`: Generates a ConfigMap including the SQL file for database initialization. - `100-create-secret.yaml`: Produces a Secret holding the PostgreSQL database password. -- `100-create-volume.yaml`: Creates both a PersistentVolume and a PersistentVolumeClaim for database storage. +- `100-create-volume.yaml`: Creates a PersistentVolumeClaim; the cluster must provide a default StorageClass for dynamic provisioning. - `200-create-postgre.yaml`: Establishes the StatefulSet for the PostgreSQL database. -- `300-expose-service.yaml`: Launches a Service to expose the PostgreSQL database externally. +- `300-expose-service.yaml`: Launches a headless Service for PostgreSQL inside the cluster. - `400-create-job.yaml`: Executes a Job that populates the database with the sample data. -Execute the command below to apply all configuration files located in the `db` directory: +Execute the command below to apply all configuration files located in the `db-service` directory: ```bash -kubectl apply -f db +kubectl apply -f db-service ``` -The filenames prefixed with numbers dictate the application order, ensuring Kubernetes orchestrates the resource setup accordingly. +Applying the files does not wait for database readiness. The initialization Job may retry while PostgreSQL starts. Wait for the Job to complete before checking the data. ### Step 3: Confirming the Deployment Following the application of the deployment files, initialization of the database may require some time. Monitor the deployment's progress by executing: diff --git a/code-samples/eventing/bookstore-sample-app/solution/frontend/.eslintrc.json b/code-samples/eventing/bookstore-sample-app/solution/frontend/.eslintrc.json deleted file mode 100644 index bffb357a71..0000000000 --- a/code-samples/eventing/bookstore-sample-app/solution/frontend/.eslintrc.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "extends": "next/core-web-vitals" -} diff --git a/code-samples/eventing/bookstore-sample-app/solution/frontend/Dockerfile b/code-samples/eventing/bookstore-sample-app/solution/frontend/Dockerfile index 2968316a73..f58756e094 100644 --- a/code-samples/eventing/bookstore-sample-app/solution/frontend/Dockerfile +++ b/code-samples/eventing/bookstore-sample-app/solution/frontend/Dockerfile @@ -10,7 +10,7 @@ WORKDIR /app COPY package*.json ./ # Install dependencies -RUN npm install +RUN npm ci # Copy the rest of your application code to the working directory COPY . . diff --git a/code-samples/eventing/bookstore-sample-app/solution/frontend/README.md b/code-samples/eventing/bookstore-sample-app/solution/frontend/README.md index 02f22bb131..598cc89dd0 100644 --- a/code-samples/eventing/bookstore-sample-app/solution/frontend/README.md +++ b/code-samples/eventing/bookstore-sample-app/solution/frontend/README.md @@ -38,7 +38,7 @@ This repository contains a Next.js application that utilizes next-themes and Tai ## Dockerization Steps 1. Clone this repository to your local machine. -2. Navigate to the root directory of the cloned repository. +2. Navigate to this `frontend` directory (the Docker build context). ### Building the Docker Image @@ -50,8 +50,8 @@ docker build -t frontend . ## Running the Docker Container -Once the image is built, you can run a container using the following command: +Run the container and open http://localhost:3000: ```bash -docker run -d -p 3000:3000 frontend +docker run -d -p 3000:8080 frontend ``` diff --git a/code-samples/eventing/bookstore-sample-app/solution/frontend/app/globals.css b/code-samples/eventing/bookstore-sample-app/solution/frontend/app/globals.css index 8fea898f61..03b4103532 100644 --- a/code-samples/eventing/bookstore-sample-app/solution/frontend/app/globals.css +++ b/code-samples/eventing/bookstore-sample-app/solution/frontend/app/globals.css @@ -1,9 +1,9 @@ +@import url('https://fonts.googleapis.com/css2?family=Poppins:wght@400;500;600;700&display=swap'); + @tailwind base; @tailwind components; @tailwind utilities; -@import url('https://fonts.googleapis.com/css2?family=Poppins:wght@400;500;600;700&display=swap'); - #__next { min-height: 100vh; width: 100%; diff --git a/code-samples/eventing/bookstore-sample-app/solution/frontend/eslint.config.mjs b/code-samples/eventing/bookstore-sample-app/solution/frontend/eslint.config.mjs new file mode 100644 index 0000000000..4df86d0a95 --- /dev/null +++ b/code-samples/eventing/bookstore-sample-app/solution/frontend/eslint.config.mjs @@ -0,0 +1,6 @@ +import nextVitals from 'eslint-config-next/core-web-vitals'; + +export default [ + ...nextVitals, + { ignores: ['.next/**', 'next-env.d.ts'] }, +]; diff --git a/code-samples/eventing/bookstore-sample-app/solution/frontend/package-lock.json b/code-samples/eventing/bookstore-sample-app/solution/frontend/package-lock.json index d44a80097e..c69161e169 100644 --- a/code-samples/eventing/bookstore-sample-app/solution/frontend/package-lock.json +++ b/code-samples/eventing/bookstore-sample-app/solution/frontend/package-lock.json @@ -24,7 +24,7 @@ "@types/react-dom": "^18", "@types/ws": "^8.18.1", "autoprefixer": "^10.0.1", - "eslint": "^8", + "eslint": "^9", "eslint-config-next": "16.0.3", "postcss": "^8", "tailwindcss": "^3.3.0", @@ -673,44 +673,115 @@ } }, "node_modules/@eslint-community/regexpp": { - "version": "4.10.0", - "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.10.0.tgz", - "integrity": "sha512-Cu96Sd2By9mCNTx2iyKOmq10v22jUVQv0lQnlGNy16oE9589yE+QADPbrMGCkA51cKZSg3Pu/aTJVTGfL/qjUA==", + "version": "4.12.2", + "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.12.2.tgz", + "integrity": "sha512-EriSTlt5OC9/7SXkRSCAhfSxxoSUgBm33OH+IkwbdpgoqsSsUg7y3uh+IICI/Qg4BBWr3U2i39RpmycbxMq4ew==", "dev": true, + "license": "MIT", "engines": { "node": "^12.0.0 || ^14.0.0 || >=16.0.0" } }, + "node_modules/@eslint/config-array": { + "version": "0.21.2", + "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.21.2.tgz", + "integrity": "sha512-nJl2KGTlrf9GjLimgIru+V/mzgSK0ABCDQRvxw5BjURL7WfH5uoWmizbH7QB6MmnMBd8cIC9uceWnezL1VZWWw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@eslint/object-schema": "^2.1.7", + "debug": "^4.3.1", + "minimatch": "^3.1.5" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/config-helpers": { + "version": "0.4.2", + "resolved": "https://registry.npmjs.org/@eslint/config-helpers/-/config-helpers-0.4.2.tgz", + "integrity": "sha512-gBrxN88gOIf3R7ja5K9slwNayVcZgK6SOUORm2uBzTeIEfeVaIhOpCtTox3P6R7o2jLFwLFTLnC7kU/RGcYEgw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@eslint/core": "^0.17.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/core": { + "version": "0.17.0", + "resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.17.0.tgz", + "integrity": "sha512-yL/sLrpmtDaFEiUj1osRP4TI2MDz1AddJL+jZ7KSqvBuliN4xqYY54IfdN8qD8Toa6g1iloph1fxQNkjOxrrpQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@types/json-schema": "^7.0.15" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, "node_modules/@eslint/eslintrc": { - "version": "2.1.4", - "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-2.1.4.tgz", - "integrity": "sha512-269Z39MS6wVJtsoUl10L60WdkhJVdPG24Q4eZTH3nnF6lpvSShEK3wQjDX9JRWAUPvPh7COouPpU9IrqaZFvtQ==", + "version": "3.3.7", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-3.3.7.tgz", + "integrity": "sha512-F42g89Qd5oAWtp0k0nnSrjziAKza7w8SVT4mStc18LZMaRb4J1HQAHLCalEtDCxrTuksx7NU9qsmeLwpOfPqWw==", "dev": true, + "license": "MIT", "dependencies": { - "ajv": "^6.12.4", + "ajv": "^6.14.0", "debug": "^4.3.2", - "espree": "^9.6.0", - "globals": "^13.19.0", + "espree": "^10.0.1", + "globals": "^14.0.0", "ignore": "^5.2.0", "import-fresh": "^3.2.1", - "js-yaml": "^4.1.0", - "minimatch": "^3.1.2", + "js-yaml": "^4.3.2", + "minimatch": "^3.1.5", "strip-json-comments": "^3.1.1" }, "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" }, "funding": { "url": "https://opencollective.com/eslint" } }, "node_modules/@eslint/js": { - "version": "8.57.0", - "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.57.0.tgz", - "integrity": "sha512-Ys+3g2TaW7gADOJzPt83SJtCDhMjndcDMFVQ/Tj9iA1BfJzFKD9mAUXT3OenpuPHbI6P/myECxRJrofUsDx/5g==", + "version": "9.39.5", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.39.5.tgz", + "integrity": "sha512-QywQuszQh77pIXCsq998c8hbhSTI/azTty1Z6N53dmAudKHhy573j3yvRLsX2BSp8YpLtoCEG8E9DJe+8zUh4A==", "dev": true, + "license": "MIT", "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://eslint.org/donate" + } + }, + "node_modules/@eslint/object-schema": { + "version": "2.1.7", + "resolved": "https://registry.npmjs.org/@eslint/object-schema/-/object-schema-2.1.7.tgz", + "integrity": "sha512-VtAOaymWVfZcmZbp6E2mympDIHvyjXs/12LqWYjVw6qjrfF+VK+fyG33kChz3nnK+SU5/NeHOqrTEHS8sXO3OA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/plugin-kit": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.4.1.tgz", + "integrity": "sha512-43/qtrDUokr7LJqoF2c3+RInu/t4zfrpYdoSDfYyhg52rwLV6TnOvdG4fXm7IkSB3wErkcmJS9iEhjVtOSEjjA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@eslint/core": "^0.17.0", + "levn": "^0.4.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" } }, "node_modules/@formatjs/ecma402-abstract": { @@ -765,18 +836,42 @@ "react": ">= 16" } }, - "node_modules/@humanwhocodes/config-array": { - "version": "0.11.14", - "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.14.tgz", - "integrity": "sha512-3T8LkOmg45BV5FICb15QQMsyUSWrQ8AygVfC7ZG32zOalnqrilm018ZVCw0eapXux8FtA33q8PSRSstjee3jSg==", + "node_modules/@humanfs/core": { + "version": "0.19.2", + "resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.2.tgz", + "integrity": "sha512-UhXNm+CFMWcbChXywFwkmhqjs3PRCmcSa/hfBgLIb7oQ5HNb1wS0icWsGtSAUNgefHeI+eBrA8I1fxmbHsGdvA==", "dev": true, + "license": "Apache-2.0", "dependencies": { - "@humanwhocodes/object-schema": "^2.0.2", - "debug": "^4.3.1", - "minimatch": "^3.0.5" + "@humanfs/types": "^0.15.0" }, "engines": { - "node": ">=10.10.0" + "node": ">=18.18.0" + } + }, + "node_modules/@humanfs/node": { + "version": "0.16.8", + "resolved": "https://registry.npmjs.org/@humanfs/node/-/node-0.16.8.tgz", + "integrity": "sha512-gE1eQNZ3R++kTzFUpdGlpmy8kDZD/MLyHqDwqjkVQI0JMdI1D51sy1H958PNXYkM2rAac7e5/CnIKZrHtPh3BQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@humanfs/core": "^0.19.2", + "@humanfs/types": "^0.15.0", + "@humanwhocodes/retry": "^0.4.0" + }, + "engines": { + "node": ">=18.18.0" + } + }, + "node_modules/@humanfs/types": { + "version": "0.15.0", + "resolved": "https://registry.npmjs.org/@humanfs/types/-/types-0.15.0.tgz", + "integrity": "sha512-ZZ1w0aoQkwuUuC7Yf+7sdeaNfqQiiLcSRbfI08oAxqLtpXQr9AIVX7Ay7HLDuiLYAaFPu8oBYNq/QIi9URHJ3Q==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=18.18.0" } }, "node_modules/@humanwhocodes/module-importer": { @@ -792,11 +887,19 @@ "url": "https://github.com/sponsors/nzakas" } }, - "node_modules/@humanwhocodes/object-schema": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-2.0.2.tgz", - "integrity": "sha512-6EwiSjwWYP7pTckG6I5eyFANjPhmPjUX9JRLUSfNPC7FX7zK9gyZAfUEaECL6ALTpGX5AjnBq3C9XmVWPitNpw==", - "dev": true + "node_modules/@humanwhocodes/retry": { + "version": "0.4.3", + "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.4.3.tgz", + "integrity": "sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=18.18" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } }, "node_modules/@img/colour": { "version": "1.1.0", @@ -4003,6 +4106,20 @@ "tslib": "^2.8.0" } }, + "node_modules/@types/estree": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.9.tgz", + "integrity": "sha512-GhdPgy1el4/ImP05X05Uw4cw2/M93BCUmnEvWZNStlCzEKME4Fkk+YpoA5OiHNQmoS7Cafb8Xa3Pya8m1Qrzeg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/json-schema": { + "version": "7.0.15", + "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", + "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==", + "dev": true, + "license": "MIT" + }, "node_modules/@types/json5": { "version": "0.0.29", "resolved": "https://registry.npmjs.org/@types/json5/-/json5-0.0.29.tgz", @@ -4324,17 +4441,12 @@ "url": "https://opencollective.com/eslint" } }, - "node_modules/@ungap/structured-clone": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.2.0.tgz", - "integrity": "sha512-zuVdFrMJiuCDQUMCzQaD6KL28MjnqqN8XnAqiEq9PNm/hCPTSGfrXCOfwj1ow4LFb/tNymJPwsNbVePc1xFqrQ==", - "dev": true - }, "node_modules/acorn": { - "version": "8.11.3", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.11.3.tgz", - "integrity": "sha512-Y9rRfJG5jcKOE0CLisYbojUjIrIEE7AGMzA/Sm4BslANhbS+cDMpgBdcPT91oJ7OuJ9hYJBx59RjbhxVnrF8Xg==", + "version": "8.19.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.19.0.tgz", + "integrity": "sha512-oJlA3XiRm7Cyk6qFD2Jn8ak9B7jSy0qy00ADO3+8dpT0LSjFihQYv4C02LFCSYJV3Q37xYwwRy5m+IpIiUzqWw==", "dev": true, + "license": "MIT", "bin": { "acorn": "bin/acorn" }, @@ -4347,15 +4459,17 @@ "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", "dev": true, + "license": "MIT", "peerDependencies": { "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" } }, "node_modules/ajv": { - "version": "6.12.6", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", - "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "version": "6.15.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.15.0.tgz", + "integrity": "sha512-fgFx7Hfoq60ytK2c7DhnF8jIvzYgOMxfugjLOSMHjLIPgenqa7S7oaagATUq99mV6IYvN2tRmC0wnTYX6iPbMw==", "dev": true, + "license": "MIT", "dependencies": { "fast-deep-equal": "^3.1.1", "fast-json-stable-stringify": "^2.0.0", @@ -4415,7 +4529,8 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", - "dev": true + "dev": true, + "license": "Python-2.0" }, "node_modules/aria-query": { "version": "5.3.2", @@ -4833,6 +4948,7 @@ "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", "dev": true, + "license": "MIT", "engines": { "node": ">=6" } @@ -5196,18 +5312,6 @@ "resolved": "https://registry.npmjs.org/dlv/-/dlv-1.1.3.tgz", "integrity": "sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA==" }, - "node_modules/doctrine": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", - "integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==", - "dev": true, - "dependencies": { - "esutils": "^2.0.2" - }, - "engines": { - "node": ">=6.0.0" - } - }, "node_modules/dunder-proto": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", @@ -5451,58 +5555,64 @@ } }, "node_modules/eslint": { - "version": "8.57.0", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.57.0.tgz", - "integrity": "sha512-dZ6+mexnaTIbSBZWgou51U6OmzIhYM2VcNdtiTtI7qPNZm35Akpr0f6vtw3w1Kmn5PYo+tZVfh13WrhpS6oLqQ==", - "dev": true, - "dependencies": { - "@eslint-community/eslint-utils": "^4.2.0", - "@eslint-community/regexpp": "^4.6.1", - "@eslint/eslintrc": "^2.1.4", - "@eslint/js": "8.57.0", - "@humanwhocodes/config-array": "^0.11.14", + "version": "9.39.5", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.39.5.tgz", + "integrity": "sha512-DgZS62aPLXKlnxILS/AYCoRvHaZeXceIzlXPkkGGzJWSow1aEk0lbTlxUSlyjC8jcaKxAdOnTDz+o1JFSBsyjw==", + "deprecated": "This version is no longer supported. Please see https://eslint.org/version-support for other options.", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/eslint-utils": "^4.8.0", + "@eslint-community/regexpp": "^4.12.1", + "@eslint/config-array": "^0.21.2", + "@eslint/config-helpers": "^0.4.2", + "@eslint/core": "^0.17.0", + "@eslint/eslintrc": "^3.3.6", + "@eslint/js": "9.39.5", + "@eslint/plugin-kit": "^0.4.1", + "@humanfs/node": "^0.16.6", "@humanwhocodes/module-importer": "^1.0.1", - "@nodelib/fs.walk": "^1.2.8", - "@ungap/structured-clone": "^1.2.0", - "ajv": "^6.12.4", + "@humanwhocodes/retry": "^0.4.2", + "@types/estree": "^1.0.6", + "ajv": "^6.14.0", "chalk": "^4.0.0", - "cross-spawn": "^7.0.2", + "cross-spawn": "^7.0.6", "debug": "^4.3.2", - "doctrine": "^3.0.0", "escape-string-regexp": "^4.0.0", - "eslint-scope": "^7.2.2", - "eslint-visitor-keys": "^3.4.3", - "espree": "^9.6.1", - "esquery": "^1.4.2", + "eslint-scope": "^8.4.0", + "eslint-visitor-keys": "^4.2.1", + "espree": "^10.4.0", + "esquery": "^1.5.0", "esutils": "^2.0.2", "fast-deep-equal": "^3.1.3", - "file-entry-cache": "^6.0.1", + "file-entry-cache": "^8.0.0", "find-up": "^5.0.0", "glob-parent": "^6.0.2", - "globals": "^13.19.0", - "graphemer": "^1.4.0", "ignore": "^5.2.0", "imurmurhash": "^0.1.4", "is-glob": "^4.0.0", - "is-path-inside": "^3.0.3", - "js-yaml": "^4.1.0", "json-stable-stringify-without-jsonify": "^1.0.1", - "levn": "^0.4.1", "lodash.merge": "^4.6.2", - "minimatch": "^3.1.2", + "minimatch": "^3.1.5", "natural-compare": "^1.4.0", - "optionator": "^0.9.3", - "strip-ansi": "^6.0.1", - "text-table": "^0.2.0" + "optionator": "^0.9.3" }, "bin": { "eslint": "bin/eslint.js" }, "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" }, "funding": { - "url": "https://opencollective.com/eslint" + "url": "https://eslint.org/donate" + }, + "peerDependencies": { + "jiti": "*" + }, + "peerDependenciesMeta": { + "jiti": { + "optional": true + } } }, "node_modules/eslint-config-next": { @@ -5806,16 +5916,17 @@ } }, "node_modules/eslint-scope": { - "version": "7.2.2", - "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.2.2.tgz", - "integrity": "sha512-dOt21O7lTMhDM+X9mB4GX+DZrZtCUJPL/wlcTqxyrx5IvO0IYtILdtrQGQp+8n5S0gwSVmOf9NQrjMOgfQZlIg==", + "version": "8.4.0", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-8.4.0.tgz", + "integrity": "sha512-sNXOfKCn74rt8RICKMvJS7XKV/Xk9kA7DyJr8mJik3S7Cwgy3qlkkmyS2uQB3jiJg6VNdZd/pDBJu0nvG2NlTg==", "dev": true, + "license": "BSD-2-Clause", "dependencies": { "esrecurse": "^4.3.0", "estraverse": "^5.2.0" }, "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" }, "funding": { "url": "https://opencollective.com/eslint" @@ -5833,18 +5944,45 @@ "url": "https://opencollective.com/eslint" } }, + "node_modules/eslint/node_modules/eslint-visitor-keys": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.1.tgz", + "integrity": "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, "node_modules/espree": { - "version": "9.6.1", - "resolved": "https://registry.npmjs.org/espree/-/espree-9.6.1.tgz", - "integrity": "sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ==", + "version": "10.4.0", + "resolved": "https://registry.npmjs.org/espree/-/espree-10.4.0.tgz", + "integrity": "sha512-j6PAQ2uUr79PZhBjP5C5fhl8e39FmRnOjsD5lGnWrFU8i2G776tBK7+nP8KuQUTTyAZUwfQqXAgrVH5MbH9CYQ==", "dev": true, + "license": "BSD-2-Clause", "dependencies": { - "acorn": "^8.9.0", + "acorn": "^8.15.0", "acorn-jsx": "^5.3.2", - "eslint-visitor-keys": "^3.4.1" + "eslint-visitor-keys": "^4.2.1" }, "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/espree/node_modules/eslint-visitor-keys": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.1.tgz", + "integrity": "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" }, "funding": { "url": "https://opencollective.com/eslint" @@ -5880,6 +6018,7 @@ "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", "dev": true, + "license": "BSD-2-Clause", "dependencies": { "estraverse": "^5.2.0" }, @@ -5909,7 +6048,8 @@ "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 + "dev": true, + "license": "MIT" }, "node_modules/fast-glob": { "version": "3.3.2", @@ -5941,7 +6081,8 @@ "version": "2.1.0", "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/fast-levenshtein": { "version": "2.0.6", @@ -5958,15 +6099,16 @@ } }, "node_modules/file-entry-cache": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz", - "integrity": "sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==", + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-8.0.0.tgz", + "integrity": "sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==", "dev": true, + "license": "MIT", "dependencies": { - "flat-cache": "^3.0.4" + "flat-cache": "^4.0.0" }, "engines": { - "node": "^10.12.0 || >=12.0.0" + "node": ">=16.0.0" } }, "node_modules/fill-range": { @@ -6019,23 +6161,23 @@ } }, "node_modules/flat-cache": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.2.0.tgz", - "integrity": "sha512-CYcENa+FtcUKLmhhqyctpclsq7QF38pKjZHsGNiSQF5r4FtoKDWabFDl3hzaEQMvT1LHEysw5twgLvpYYb4vbw==", + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-4.0.1.tgz", + "integrity": "sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==", "dev": true, + "license": "MIT", "dependencies": { "flatted": "^3.2.9", - "keyv": "^4.5.3", - "rimraf": "^3.0.2" + "keyv": "^4.5.4" }, "engines": { - "node": "^10.12.0 || >=12.0.0" + "node": ">=16" } }, "node_modules/flatted": { - "version": "3.4.2", - "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.4.2.tgz", - "integrity": "sha512-PjDse7RzhcPkIJwy5t7KPWQSZ9cAbzQXcafsetQoD7sOJRQlGikNbx7yZp2OotDnJyrDcbyRq3Ttb18iYOqkxA==", + "version": "3.4.4", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.4.4.tgz", + "integrity": "sha512-5+ybhBZANEJxaH3X5evAFatUxLfEHSr7n6kYJ+1Qd0mUqr4eu9gIf6GDbWHf8RJijHrjjO8G+la14SlL2SeS1Q==", "dev": true, "license": "ISC" }, @@ -6116,12 +6258,6 @@ } } }, - "node_modules/fs.realpath": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", - "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", - "dev": true - }, "node_modules/fsevents": { "version": "2.3.3", "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", @@ -6326,15 +6462,13 @@ } }, "node_modules/globals": { - "version": "13.24.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-13.24.0.tgz", - "integrity": "sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ==", + "version": "14.0.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-14.0.0.tgz", + "integrity": "sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==", "dev": true, - "dependencies": { - "type-fest": "^0.20.2" - }, + "license": "MIT", "engines": { - "node": ">=8" + "node": ">=18" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" @@ -6488,19 +6622,21 @@ } }, "node_modules/ignore": { - "version": "5.3.1", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.1.tgz", - "integrity": "sha512-5Fytz/IraMjqpwfd34ke28PTVMjZjJG2MPn5t7OE4eUCUNf8BAa7b5WUS9/Qvr6mwOQS7Mk6vdsMno5he+T8Xw==", + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", + "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==", "dev": true, + "license": "MIT", "engines": { "node": ">= 4" } }, "node_modules/import-fresh": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz", - "integrity": "sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==", + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.1.tgz", + "integrity": "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==", "dev": true, + "license": "MIT", "dependencies": { "parent-module": "^1.0.0", "resolve-from": "^4.0.0" @@ -6520,22 +6656,6 @@ "node": ">=0.8.19" } }, - "node_modules/inflight": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", - "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", - "dev": true, - "dependencies": { - "once": "^1.3.0", - "wrappy": "1" - } - }, - "node_modules/inherits": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", - "dev": true - }, "node_modules/internal-slot": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.1.0.tgz", @@ -6834,15 +6954,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/is-path-inside": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-3.0.3.tgz", - "integrity": "sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==", - "dev": true, - "engines": { - "node": ">=8" - } - }, "node_modules/is-plain-object": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-2.0.4.tgz", @@ -7068,10 +7179,20 @@ "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==" }, "node_modules/js-yaml": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.1.tgz", - "integrity": "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==", + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.3.2.tgz", + "integrity": "sha512-SFNOvSJ+Dgf/9An904Yx+CgSlIPCkIpao4qo51lpee25TIRejdH3rhR4EZMGoNx3/TP3O+wzWuiTFl4sqbltzA==", "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/puzrin" + }, + { + "type": "github", + "url": "https://github.com/sponsors/nodeca" + } + ], "license": "MIT", "dependencies": { "argparse": "^2.0.1" @@ -7136,13 +7257,15 @@ "version": "3.0.1", "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/json-schema-traverse": { "version": "0.4.1", "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/json-stable-stringify-without-jsonify": { "version": "1.0.1", @@ -7183,6 +7306,7 @@ "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==", "dev": true, + "license": "MIT", "dependencies": { "json-buffer": "3.0.1" } @@ -7680,15 +7804,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/once": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", - "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", - "dev": true, - "dependencies": { - "wrappy": "1" - } - }, "node_modules/optionator": { "version": "0.9.3", "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.3.tgz", @@ -7774,6 +7889,7 @@ "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", "dev": true, + "license": "MIT", "dependencies": { "callsites": "^3.0.0" }, @@ -7790,15 +7906,6 @@ "node": ">=8" } }, - "node_modules/path-is-absolute": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", - "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/path-key": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", @@ -8113,6 +8220,7 @@ "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", "dev": true, + "license": "MIT", "engines": { "node": ">=6" } @@ -8349,6 +8457,7 @@ "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", "dev": true, + "license": "MIT", "engines": { "node": ">=4" } @@ -8371,41 +8480,6 @@ "node": ">=0.10.0" } }, - "node_modules/rimraf": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", - "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", - "dev": true, - "dependencies": { - "glob": "^7.1.3" - }, - "bin": { - "rimraf": "bin.js" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/rimraf/node_modules/glob": { - "version": "7.2.3", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", - "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", - "dev": true, - "dependencies": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.1.1", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - }, - "engines": { - "node": "*" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, "node_modules/run-parallel": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", @@ -8992,6 +9066,7 @@ "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", "dev": true, + "license": "MIT", "engines": { "node": ">=8" }, @@ -9140,12 +9215,6 @@ "node": ">=6" } }, - "node_modules/text-table": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", - "integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==", - "dev": true - }, "node_modules/thenify": { "version": "3.3.1", "resolved": "https://registry.npmjs.org/thenify/-/thenify-3.3.1.tgz", @@ -9239,18 +9308,6 @@ "node": ">= 0.8.0" } }, - "node_modules/type-fest": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", - "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", - "dev": true, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/typed-array-buffer": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/typed-array-buffer/-/typed-array-buffer-1.0.3.tgz", @@ -9426,6 +9483,7 @@ "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", "dev": true, + "license": "BSD-2-Clause", "dependencies": { "punycode": "^2.1.0" } @@ -9710,12 +9768,6 @@ "url": "https://github.com/chalk/strip-ansi?sponsor=1" } }, - "node_modules/wrappy": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", - "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", - "dev": true - }, "node_modules/write-file-atomic": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-5.0.1.tgz", diff --git a/code-samples/eventing/bookstore-sample-app/solution/frontend/package.json b/code-samples/eventing/bookstore-sample-app/solution/frontend/package.json index bdce502bf4..ff0f4bff52 100644 --- a/code-samples/eventing/bookstore-sample-app/solution/frontend/package.json +++ b/code-samples/eventing/bookstore-sample-app/solution/frontend/package.json @@ -4,10 +4,10 @@ "private": true, "scripts": { "dev": "next dev", - "build": "next build", + "build": "next build --webpack", "start": "next start", - "lint": "next lint", - "prepare": "next-ws patch" + "lint": "eslint .", + "prepare": "next-ws patch --yes" }, "dependencies": { "@heroicons/react": "^2.1.3", @@ -26,7 +26,7 @@ "@types/react-dom": "^18", "@types/ws": "^8.18.1", "autoprefixer": "^10.0.1", - "eslint": "^8", + "eslint": "^9", "eslint-config-next": "16.0.3", "postcss": "^8", "tailwindcss": "^3.3.0", diff --git a/code-samples/eventing/bookstore-sample-app/solution/node-server/index.js b/code-samples/eventing/bookstore-sample-app/solution/node-server/index.js index 78d2fb42b9..8ce06b9693 100644 --- a/code-samples/eventing/bookstore-sample-app/solution/node-server/index.js +++ b/code-samples/eventing/bookstore-sample-app/solution/node-server/index.js @@ -9,13 +9,13 @@ const port = 8000; // Middleware to parse JSON bodies -app.use(express.json()); +app.use(express.json({type: ['application/json', 'application/cloudevents+json']})); app.use(cors()); expressWs(app); // Apply WebSocket functionality to Express // Configure the PostgreSQL connection pool const pool = new Pool({ - host: 'postgresql.default.svc.cluster.local', + host: process.env.PGHOST || 'postgresql', port: 5432, database: 'mydatabase', user: 'myuser', @@ -84,7 +84,7 @@ app.post('/insert', async (req, res) => { // Set headers and send the CloudEvent res.writeHead(200, serializedEvent.headers); - res.end(JSON.stringify(serializedEvent.body)); + res.end(serializedEvent.body); } catch (error) { console.error('Error processing request:', error); diff --git a/code-samples/eventing/bookstore-sample-app/solution/node-server/package.json b/code-samples/eventing/bookstore-sample-app/solution/node-server/package.json index 19484cb209..5f55061111 100644 --- a/code-samples/eventing/bookstore-sample-app/solution/node-server/package.json +++ b/code-samples/eventing/bookstore-sample-app/solution/node-server/package.json @@ -3,9 +3,6 @@ "version": "1.0.0", "description": "", "main": "index.js", - "scripts": { - "test": "echo \"Error: no test specified\" && exit 1" - }, "author": "", "license": "ISC", "dependencies": { diff --git a/code-samples/eventing/bookstore-sample-app/solution/ML-sentiment-analysis/.funcignore b/code-samples/eventing/bookstore-sample-app/solution/sentiment-analysis/.funcignore similarity index 100% rename from code-samples/eventing/bookstore-sample-app/solution/ML-sentiment-analysis/.funcignore rename to code-samples/eventing/bookstore-sample-app/solution/sentiment-analysis/.funcignore diff --git a/code-samples/eventing/bookstore-sample-app/solution/ML-sentiment-analysis/.gitignore b/code-samples/eventing/bookstore-sample-app/solution/sentiment-analysis/.gitignore similarity index 100% rename from code-samples/eventing/bookstore-sample-app/solution/ML-sentiment-analysis/.gitignore rename to code-samples/eventing/bookstore-sample-app/solution/sentiment-analysis/.gitignore diff --git a/code-samples/eventing/bookstore-sample-app/solution/ML-sentiment-analysis/README.md b/code-samples/eventing/bookstore-sample-app/solution/sentiment-analysis/README.md similarity index 96% rename from code-samples/eventing/bookstore-sample-app/solution/ML-sentiment-analysis/README.md rename to code-samples/eventing/bookstore-sample-app/solution/sentiment-analysis/README.md index 66ce194600..ca5ec6d966 100644 --- a/code-samples/eventing/bookstore-sample-app/solution/ML-sentiment-analysis/README.md +++ b/code-samples/eventing/bookstore-sample-app/solution/sentiment-analysis/README.md @@ -28,7 +28,7 @@ For development environments where Python is installed, it's suggested to use the `host` builder as follows: ```bash -# Run function on the host (outsdie of a container) +# Run function on the host (outside of a container) func run --builder=host ``` diff --git a/code-samples/eventing/bookstore-sample-app/solution/ML-sentiment-analysis/func.yaml b/code-samples/eventing/bookstore-sample-app/solution/sentiment-analysis/func.yaml similarity index 100% rename from code-samples/eventing/bookstore-sample-app/solution/ML-sentiment-analysis/func.yaml rename to code-samples/eventing/bookstore-sample-app/solution/sentiment-analysis/func.yaml diff --git a/code-samples/eventing/bookstore-sample-app/solution/ML-sentiment-analysis/function/__init__.py b/code-samples/eventing/bookstore-sample-app/solution/sentiment-analysis/function/__init__.py similarity index 100% rename from code-samples/eventing/bookstore-sample-app/solution/ML-sentiment-analysis/function/__init__.py rename to code-samples/eventing/bookstore-sample-app/solution/sentiment-analysis/function/__init__.py diff --git a/code-samples/eventing/bookstore-sample-app/solution/ML-sentiment-analysis/function/func.py b/code-samples/eventing/bookstore-sample-app/solution/sentiment-analysis/function/func.py similarity index 88% rename from code-samples/eventing/bookstore-sample-app/solution/ML-sentiment-analysis/function/func.py rename to code-samples/eventing/bookstore-sample-app/solution/sentiment-analysis/function/func.py index 8ed46f20b6..0dad7052e5 100644 --- a/code-samples/eventing/bookstore-sample-app/solution/ML-sentiment-analysis/function/func.py +++ b/code-samples/eventing/bookstore-sample-app/solution/sentiment-analysis/function/func.py @@ -1,5 +1,5 @@ import logging -from cloudevents.http import CloudEvent +from cloudevents.core.v1.event import CloudEvent from textblob import TextBlob import textblob @@ -16,10 +16,10 @@ async def handle(self, scope, receive, send): request_event = scope["event"] # 2. Extract the data payload from the event, analyze and create CloudEvent - response_event = self.analyze_sentiment(request_event.data) + response_event = self.analyze_sentiment(request_event.get_data()) # 3. Send the response - logging.info(f"Sending response: {response_event.data}") + logging.info(f"Sending response: {response_event.get_data()}") await send(response_event) def create_cloud_event(self, inputText, badWordResult, data): @@ -27,7 +27,6 @@ def create_cloud_event(self, inputText, badWordResult, data): "type": "moderated-comment", "source": "sentiment-analysis", "datacontenttype": "application/json", - "sentimentResult": data, "badwordfilter": badWordResult, } diff --git a/code-samples/eventing/bookstore-sample-app/solution/ML-sentiment-analysis/pyproject.toml b/code-samples/eventing/bookstore-sample-app/solution/sentiment-analysis/pyproject.toml similarity index 65% rename from code-samples/eventing/bookstore-sample-app/solution/ML-sentiment-analysis/pyproject.toml rename to code-samples/eventing/bookstore-sample-app/solution/sentiment-analysis/pyproject.toml index 447dd7520e..f4e405edb2 100644 --- a/code-samples/eventing/bookstore-sample-app/solution/ML-sentiment-analysis/pyproject.toml +++ b/code-samples/eventing/bookstore-sample-app/solution/sentiment-analysis/pyproject.toml @@ -6,10 +6,7 @@ requires-python = ">=3.9" readme = "README.md" license = "MIT" dependencies = [ - "httpx", - "cloudevents", - "pytest", - "pytest-asyncio", + "cloudevents>=2.2,<3", "textblob", ] authors = [ @@ -20,8 +17,4 @@ authors = [ requires = ["hatchling"] build-backend = "hatchling.build" -[tool.pytest.ini_options] -asyncio_mode = "strict" -asyncio_default_fixture_loop_scope = "function" - diff --git a/code-samples/eventing/bookstore-sample-app/solution/slack-sink/README.md b/code-samples/eventing/bookstore-sample-app/solution/slack-sink/README.md index b64e12f408..fba073e454 100644 --- a/code-samples/eventing/bookstore-sample-app/solution/slack-sink/README.md +++ b/code-samples/eventing/bookstore-sample-app/solution/slack-sink/README.md @@ -128,7 +128,7 @@ spec: apiVersion: camel.apache.org/v1 # The API version for Kamelet. name: slack-sink # The name of the Kamelet to use as the sink, in this case, a predefined "slack-sink" Kamelet. properties: - channel: “#bookstore-owner” # The Slack channel where notifications will be sent. + channel: "#bookstore-owner" # The Slack channel where notifications will be sent. webhookUrl: "https://hooks.slack.com/services/YOUR/SLACK/WEBHOOK" # The Webhook URL provided by Slack for posting messages to a specific channel. ``` @@ -185,7 +185,7 @@ Using curl command to send a CloudEvent to the Broker: -H "Ce-Type: new-review-comment" \ -H "Ce-Source: bookstore-web-app" \ -H "Content-Type: application/json" \ --d 'Hello from Knative!' +-d '{"reviewText":"Hello from Knative!"}' ``` You can find the URI to your Broker by running the following command: diff --git a/code-samples/eventing/bookstore-sample-app/solution/solution.sh b/code-samples/eventing/bookstore-sample-app/solution/solution.sh index 73b30739ef..a9173e6345 100755 --- a/code-samples/eventing/bookstore-sample-app/solution/solution.sh +++ b/code-samples/eventing/bookstore-sample-app/solution/solution.sh @@ -153,13 +153,13 @@ read -p '🛑 Can you see "Hello World!"? If yes, press ENTER to continue...' # Deploy the ML services echo "" echo "📦 Deploying the ML service: bad-word-filter..." -cd "${SCRIPT_DIR}/ML-bad-word-filter" +cd "${SCRIPT_DIR}/bad-word-filter" func deploy -b=s2i -v echo "✅ ML service bad-word-filter deployed." echo "" echo "📦 Deploying the ML services: sentiment-analysis..." -cd "${SCRIPT_DIR}/ML-sentiment-analysis" +cd "${SCRIPT_DIR}/sentiment-analysis" func deploy -b=s2i -v echo "✅ ML service sentiment-analysis deployed." diff --git a/code-samples/eventing/bookstore-sample-app/start/db-service/200-create-postgre.yaml b/code-samples/eventing/bookstore-sample-app/start/db-service/200-create-postgre.yaml index c14933e5d4..3c7574da48 100644 --- a/code-samples/eventing/bookstore-sample-app/start/db-service/200-create-postgre.yaml +++ b/code-samples/eventing/bookstore-sample-app/start/db-service/200-create-postgre.yaml @@ -13,6 +13,8 @@ spec: labels: app: postgresql spec: + securityContext: + fsGroup: 26 # The image runs as the non-root postgres user (UID/GID 26). containers: - name: postgresql image: quay.io/enterprisedb/postgresql diff --git a/code-samples/eventing/bookstore-sample-app/start/db-service/README.md b/code-samples/eventing/bookstore-sample-app/start/db-service/README.md index 257b31e59b..bee3172227 100644 --- a/code-samples/eventing/bookstore-sample-app/start/db-service/README.md +++ b/code-samples/eventing/bookstore-sample-app/start/db-service/README.md @@ -30,24 +30,24 @@ The sample rows inserted for the BookReviews table are shown below: ## Implementation ### Step 1: Acquire Necessary Files from the Repository -The essential files for setting up your database are located within the `db` directory of our repository. Please download these files to proceed. +The essential files for setting up your database are located within the `db-service` directory of our repository. Please download these files to proceed. ### Step 2: Deploying the PostgreSQL Database -To deploy the PostgreSQL database and populate it with the provided sample data, you'll apply a series of Kubernetes deployment files. Ensure you're positioned in the `code-sample` directory and not within the `db` subdirectory for this operation. +To deploy the PostgreSQL database and populate it with the provided sample data, you'll apply a series of Kubernetes deployment files. Run this command from the `start` directory, which contains `db-service/`. Within this directory, you will find 6 YAML files, each serving a distinct purpose in the setup process: - `100-create-configmap.yaml`: Generates a ConfigMap including the SQL file for database initialization. - `100-create-secret.yaml`: Produces a Secret holding the PostgreSQL database password. -- `100-create-volume.yaml`: Creates both a PersistentVolume and a PersistentVolumeClaim for database storage. +- `100-create-volume.yaml`: Creates a PersistentVolumeClaim; the cluster must provide a default StorageClass for dynamic provisioning. - `200-create-postgre.yaml`: Establishes the StatefulSet for the PostgreSQL database. -- `300-expose-service.yaml`: Launches a Service to expose the PostgreSQL database externally. +- `300-expose-service.yaml`: Launches a headless Service for PostgreSQL inside the cluster. - `400-create-job.yaml`: Executes a Job that populates the database with the sample data. -Execute the command below to apply all configuration files located in the `db` directory: +Execute the command below to apply all configuration files located in the `db-service` directory: ```bash -kubectl apply -f db +kubectl apply -f db-service ``` -The filenames prefixed with numbers dictate the application order, ensuring Kubernetes orchestrates the resource setup accordingly. +Applying the files does not wait for database readiness. The initialization Job may retry while PostgreSQL starts. Wait for the Job to complete before checking the data. ### Step 3: Confirming the Deployment Following the application of the deployment files, initialization of the database may require some time. Monitor the deployment's progress by executing: diff --git a/code-samples/eventing/bookstore-sample-app/start/frontend/.eslintrc.json b/code-samples/eventing/bookstore-sample-app/start/frontend/.eslintrc.json deleted file mode 100644 index bffb357a71..0000000000 --- a/code-samples/eventing/bookstore-sample-app/start/frontend/.eslintrc.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "extends": "next/core-web-vitals" -} diff --git a/code-samples/eventing/bookstore-sample-app/start/frontend/Dockerfile b/code-samples/eventing/bookstore-sample-app/start/frontend/Dockerfile index 2968316a73..f58756e094 100644 --- a/code-samples/eventing/bookstore-sample-app/start/frontend/Dockerfile +++ b/code-samples/eventing/bookstore-sample-app/start/frontend/Dockerfile @@ -10,7 +10,7 @@ WORKDIR /app COPY package*.json ./ # Install dependencies -RUN npm install +RUN npm ci # Copy the rest of your application code to the working directory COPY . . diff --git a/code-samples/eventing/bookstore-sample-app/start/frontend/README.md b/code-samples/eventing/bookstore-sample-app/start/frontend/README.md index 02f22bb131..598cc89dd0 100644 --- a/code-samples/eventing/bookstore-sample-app/start/frontend/README.md +++ b/code-samples/eventing/bookstore-sample-app/start/frontend/README.md @@ -38,7 +38,7 @@ This repository contains a Next.js application that utilizes next-themes and Tai ## Dockerization Steps 1. Clone this repository to your local machine. -2. Navigate to the root directory of the cloned repository. +2. Navigate to this `frontend` directory (the Docker build context). ### Building the Docker Image @@ -50,8 +50,8 @@ docker build -t frontend . ## Running the Docker Container -Once the image is built, you can run a container using the following command: +Run the container and open http://localhost:3000: ```bash -docker run -d -p 3000:3000 frontend +docker run -d -p 3000:8080 frontend ``` diff --git a/code-samples/eventing/bookstore-sample-app/start/frontend/app/globals.css b/code-samples/eventing/bookstore-sample-app/start/frontend/app/globals.css index 8fea898f61..03b4103532 100644 --- a/code-samples/eventing/bookstore-sample-app/start/frontend/app/globals.css +++ b/code-samples/eventing/bookstore-sample-app/start/frontend/app/globals.css @@ -1,9 +1,9 @@ +@import url('https://fonts.googleapis.com/css2?family=Poppins:wght@400;500;600;700&display=swap'); + @tailwind base; @tailwind components; @tailwind utilities; -@import url('https://fonts.googleapis.com/css2?family=Poppins:wght@400;500;600;700&display=swap'); - #__next { min-height: 100vh; width: 100%; diff --git a/code-samples/eventing/bookstore-sample-app/start/frontend/eslint.config.mjs b/code-samples/eventing/bookstore-sample-app/start/frontend/eslint.config.mjs new file mode 100644 index 0000000000..4df86d0a95 --- /dev/null +++ b/code-samples/eventing/bookstore-sample-app/start/frontend/eslint.config.mjs @@ -0,0 +1,6 @@ +import nextVitals from 'eslint-config-next/core-web-vitals'; + +export default [ + ...nextVitals, + { ignores: ['.next/**', 'next-env.d.ts'] }, +]; diff --git a/code-samples/eventing/bookstore-sample-app/start/frontend/package-lock.json b/code-samples/eventing/bookstore-sample-app/start/frontend/package-lock.json index c4785aac6b..32de439b8c 100644 --- a/code-samples/eventing/bookstore-sample-app/start/frontend/package-lock.json +++ b/code-samples/eventing/bookstore-sample-app/start/frontend/package-lock.json @@ -24,10 +24,10 @@ "@types/react-dom": "^18", "@types/ws": "^8.18.1", "autoprefixer": "^10.0.1", - "eslint": "^8", + "eslint": "^9", "eslint-config-next": "16.0.3", "postcss": "^8", - "tailwindcss": "^4.3.3", + "tailwindcss": "^3.3.0", "typescript": "^5" } }, @@ -40,6 +40,18 @@ "node": ">=0.10.0" } }, + "node_modules/@alloc/quick-lru": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/@alloc/quick-lru/-/quick-lru-5.3.0.tgz", + "integrity": "sha512-U4+70Pc5ZS9osnCBCE5Jha/ciHM+Yp+CNMNC/7HvYbNRk1Ldd+f7qO65W5qfhu/TCv+/ozljlXXe9Nj8419DMA==", + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/@babel/code-frame": { "version": "7.27.1", "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.27.1.tgz", @@ -662,44 +674,115 @@ } }, "node_modules/@eslint-community/regexpp": { - "version": "4.10.0", - "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.10.0.tgz", - "integrity": "sha512-Cu96Sd2By9mCNTx2iyKOmq10v22jUVQv0lQnlGNy16oE9589yE+QADPbrMGCkA51cKZSg3Pu/aTJVTGfL/qjUA==", + "version": "4.12.2", + "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.12.2.tgz", + "integrity": "sha512-EriSTlt5OC9/7SXkRSCAhfSxxoSUgBm33OH+IkwbdpgoqsSsUg7y3uh+IICI/Qg4BBWr3U2i39RpmycbxMq4ew==", "dev": true, + "license": "MIT", "engines": { "node": "^12.0.0 || ^14.0.0 || >=16.0.0" } }, + "node_modules/@eslint/config-array": { + "version": "0.21.2", + "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.21.2.tgz", + "integrity": "sha512-nJl2KGTlrf9GjLimgIru+V/mzgSK0ABCDQRvxw5BjURL7WfH5uoWmizbH7QB6MmnMBd8cIC9uceWnezL1VZWWw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@eslint/object-schema": "^2.1.7", + "debug": "^4.3.1", + "minimatch": "^3.1.5" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/config-helpers": { + "version": "0.4.2", + "resolved": "https://registry.npmjs.org/@eslint/config-helpers/-/config-helpers-0.4.2.tgz", + "integrity": "sha512-gBrxN88gOIf3R7ja5K9slwNayVcZgK6SOUORm2uBzTeIEfeVaIhOpCtTox3P6R7o2jLFwLFTLnC7kU/RGcYEgw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@eslint/core": "^0.17.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/core": { + "version": "0.17.0", + "resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.17.0.tgz", + "integrity": "sha512-yL/sLrpmtDaFEiUj1osRP4TI2MDz1AddJL+jZ7KSqvBuliN4xqYY54IfdN8qD8Toa6g1iloph1fxQNkjOxrrpQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@types/json-schema": "^7.0.15" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, "node_modules/@eslint/eslintrc": { - "version": "2.1.4", - "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-2.1.4.tgz", - "integrity": "sha512-269Z39MS6wVJtsoUl10L60WdkhJVdPG24Q4eZTH3nnF6lpvSShEK3wQjDX9JRWAUPvPh7COouPpU9IrqaZFvtQ==", + "version": "3.3.7", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-3.3.7.tgz", + "integrity": "sha512-F42g89Qd5oAWtp0k0nnSrjziAKza7w8SVT4mStc18LZMaRb4J1HQAHLCalEtDCxrTuksx7NU9qsmeLwpOfPqWw==", "dev": true, + "license": "MIT", "dependencies": { - "ajv": "^6.12.4", + "ajv": "^6.14.0", "debug": "^4.3.2", - "espree": "^9.6.0", - "globals": "^13.19.0", + "espree": "^10.0.1", + "globals": "^14.0.0", "ignore": "^5.2.0", "import-fresh": "^3.2.1", - "js-yaml": "^4.1.0", - "minimatch": "^3.1.2", + "js-yaml": "^4.3.2", + "minimatch": "^3.1.5", "strip-json-comments": "^3.1.1" }, "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" }, "funding": { "url": "https://opencollective.com/eslint" } }, "node_modules/@eslint/js": { - "version": "8.57.0", - "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.57.0.tgz", - "integrity": "sha512-Ys+3g2TaW7gADOJzPt83SJtCDhMjndcDMFVQ/Tj9iA1BfJzFKD9mAUXT3OenpuPHbI6P/myECxRJrofUsDx/5g==", + "version": "9.39.5", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.39.5.tgz", + "integrity": "sha512-QywQuszQh77pIXCsq998c8hbhSTI/azTty1Z6N53dmAudKHhy573j3yvRLsX2BSp8YpLtoCEG8E9DJe+8zUh4A==", "dev": true, + "license": "MIT", "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://eslint.org/donate" + } + }, + "node_modules/@eslint/object-schema": { + "version": "2.1.7", + "resolved": "https://registry.npmjs.org/@eslint/object-schema/-/object-schema-2.1.7.tgz", + "integrity": "sha512-VtAOaymWVfZcmZbp6E2mympDIHvyjXs/12LqWYjVw6qjrfF+VK+fyG33kChz3nnK+SU5/NeHOqrTEHS8sXO3OA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/plugin-kit": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.4.1.tgz", + "integrity": "sha512-43/qtrDUokr7LJqoF2c3+RInu/t4zfrpYdoSDfYyhg52rwLV6TnOvdG4fXm7IkSB3wErkcmJS9iEhjVtOSEjjA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@eslint/core": "^0.17.0", + "levn": "^0.4.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" } }, "node_modules/@formatjs/ecma402-abstract": { @@ -754,18 +837,42 @@ "react": ">= 16" } }, - "node_modules/@humanwhocodes/config-array": { - "version": "0.11.14", - "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.14.tgz", - "integrity": "sha512-3T8LkOmg45BV5FICb15QQMsyUSWrQ8AygVfC7ZG32zOalnqrilm018ZVCw0eapXux8FtA33q8PSRSstjee3jSg==", + "node_modules/@humanfs/core": { + "version": "0.19.2", + "resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.2.tgz", + "integrity": "sha512-UhXNm+CFMWcbChXywFwkmhqjs3PRCmcSa/hfBgLIb7oQ5HNb1wS0icWsGtSAUNgefHeI+eBrA8I1fxmbHsGdvA==", "dev": true, + "license": "Apache-2.0", "dependencies": { - "@humanwhocodes/object-schema": "^2.0.2", - "debug": "^4.3.1", - "minimatch": "^3.0.5" + "@humanfs/types": "^0.15.0" + }, + "engines": { + "node": ">=18.18.0" + } + }, + "node_modules/@humanfs/node": { + "version": "0.16.8", + "resolved": "https://registry.npmjs.org/@humanfs/node/-/node-0.16.8.tgz", + "integrity": "sha512-gE1eQNZ3R++kTzFUpdGlpmy8kDZD/MLyHqDwqjkVQI0JMdI1D51sy1H958PNXYkM2rAac7e5/CnIKZrHtPh3BQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@humanfs/core": "^0.19.2", + "@humanfs/types": "^0.15.0", + "@humanwhocodes/retry": "^0.4.0" }, "engines": { - "node": ">=10.10.0" + "node": ">=18.18.0" + } + }, + "node_modules/@humanfs/types": { + "version": "0.15.0", + "resolved": "https://registry.npmjs.org/@humanfs/types/-/types-0.15.0.tgz", + "integrity": "sha512-ZZ1w0aoQkwuUuC7Yf+7sdeaNfqQiiLcSRbfI08oAxqLtpXQr9AIVX7Ay7HLDuiLYAaFPu8oBYNq/QIi9URHJ3Q==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=18.18.0" } }, "node_modules/@humanwhocodes/module-importer": { @@ -781,11 +888,19 @@ "url": "https://github.com/sponsors/nzakas" } }, - "node_modules/@humanwhocodes/object-schema": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-2.0.2.tgz", - "integrity": "sha512-6EwiSjwWYP7pTckG6I5eyFANjPhmPjUX9JRLUSfNPC7FX7zK9gyZAfUEaECL6ALTpGX5AjnBq3C9XmVWPitNpw==", - "dev": true + "node_modules/@humanwhocodes/retry": { + "version": "0.4.3", + "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.4.3.tgz", + "integrity": "sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=18.18" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } }, "node_modules/@img/colour": { "version": "1.1.0", @@ -2841,7 +2956,6 @@ "version": "2.1.5", "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", - "dev": true, "dependencies": { "@nodelib/fs.stat": "2.0.5", "run-parallel": "^1.1.9" @@ -2854,7 +2968,6 @@ "version": "2.0.5", "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", - "dev": true, "engines": { "node": ">= 8" } @@ -2863,7 +2976,6 @@ "version": "1.2.8", "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", - "dev": true, "dependencies": { "@nodelib/fs.scandir": "2.1.5", "fastq": "^1.6.0" @@ -3941,6 +4053,20 @@ "tslib": "^2.8.0" } }, + "node_modules/@types/estree": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.9.tgz", + "integrity": "sha512-GhdPgy1el4/ImP05X05Uw4cw2/M93BCUmnEvWZNStlCzEKME4Fkk+YpoA5OiHNQmoS7Cafb8Xa3Pya8m1Qrzeg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/json-schema": { + "version": "7.0.15", + "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", + "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==", + "dev": true, + "license": "MIT" + }, "node_modules/@types/json5": { "version": "0.0.29", "resolved": "https://registry.npmjs.org/@types/json5/-/json5-0.0.29.tgz", @@ -4262,17 +4388,12 @@ "url": "https://opencollective.com/eslint" } }, - "node_modules/@ungap/structured-clone": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.2.0.tgz", - "integrity": "sha512-zuVdFrMJiuCDQUMCzQaD6KL28MjnqqN8XnAqiEq9PNm/hCPTSGfrXCOfwj1ow4LFb/tNymJPwsNbVePc1xFqrQ==", - "dev": true - }, "node_modules/acorn": { - "version": "8.11.3", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.11.3.tgz", - "integrity": "sha512-Y9rRfJG5jcKOE0CLisYbojUjIrIEE7AGMzA/Sm4BslANhbS+cDMpgBdcPT91oJ7OuJ9hYJBx59RjbhxVnrF8Xg==", + "version": "8.19.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.19.0.tgz", + "integrity": "sha512-oJlA3XiRm7Cyk6qFD2Jn8ak9B7jSy0qy00ADO3+8dpT0LSjFihQYv4C02LFCSYJV3Q37xYwwRy5m+IpIiUzqWw==", "dev": true, + "license": "MIT", "bin": { "acorn": "bin/acorn" }, @@ -4285,15 +4406,17 @@ "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", "dev": true, + "license": "MIT", "peerDependencies": { "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" } }, "node_modules/ajv": { - "version": "6.12.6", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", - "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "version": "6.15.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.15.0.tgz", + "integrity": "sha512-fgFx7Hfoq60ytK2c7DhnF8jIvzYgOMxfugjLOSMHjLIPgenqa7S7oaagATUq99mV6IYvN2tRmC0wnTYX6iPbMw==", "dev": true, + "license": "MIT", "dependencies": { "fast-deep-equal": "^3.1.1", "fast-json-stable-stringify": "^2.0.0", @@ -4305,15 +4428,6 @@ "url": "https://github.com/sponsors/epoberezkin" } }, - "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, - "engines": { - "node": ">=8" - } - }, "node_modules/ansi-styles": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", @@ -4329,11 +4443,37 @@ "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, + "node_modules/any-promise": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/any-promise/-/any-promise-1.3.0.tgz", + "integrity": "sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A==", + "license": "MIT" + }, + "node_modules/anymatch": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", + "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", + "license": "ISC", + "dependencies": { + "normalize-path": "^3.0.0", + "picomatch": "^2.0.4" + }, + "engines": { + "node": ">= 8" + } + }, + "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==", + "license": "MIT" + }, "node_modules/argparse": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", - "dev": true + "dev": true, + "license": "Python-2.0" }, "node_modules/aria-query": { "version": "5.3.2", @@ -4625,6 +4765,18 @@ "node": ">=6.0.0" } }, + "node_modules/binary-extensions": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz", + "integrity": "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==", + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/brace-expansion": { "version": "1.1.21", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.21.tgz", @@ -4741,10 +4893,20 @@ "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", "dev": true, + "license": "MIT", "engines": { "node": ">=6" } }, + "node_modules/camelcase-css": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/camelcase-css/-/camelcase-css-2.0.1.tgz", + "integrity": "sha512-QOSvevhslijgYwRx6Rv7zKdMF8lbRmx+uQGx2+vDc+KI/eBnsy9kit5aj23AgGu3pa4t9AgwbnXWqS+iOY+2aA==", + "license": "MIT", + "engines": { + "node": ">= 6" + } + }, "node_modules/caniuse-lite": { "version": "1.0.30001810", "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001810.tgz", @@ -4781,6 +4943,42 @@ "url": "https://github.com/chalk/chalk?sponsor=1" } }, + "node_modules/chokidar": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz", + "integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==", + "license": "MIT", + "dependencies": { + "anymatch": "~3.1.2", + "braces": "~3.0.2", + "glob-parent": "~5.1.2", + "is-binary-path": "~2.1.0", + "is-glob": "~4.0.1", + "normalize-path": "~3.0.0", + "readdirp": "~3.6.0" + }, + "engines": { + "node": ">= 8.10.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + }, + "optionalDependencies": { + "fsevents": "~2.3.2" + } + }, + "node_modules/chokidar/node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, "node_modules/client-only": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/client-only/-/client-only-0.0.1.tgz", @@ -4851,6 +5049,15 @@ "resolved": "https://registry.npmjs.org/color2k/-/color2k-2.0.3.tgz", "integrity": "sha512-zW190nQTIoXcGCaU08DvVNFTmQhUpnJfVuAKfWqUQkflXKpaDdpaYoM0iluLS9lgJNHyBF58KKA2FBEwkD7wog==" }, + "node_modules/commander": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/commander/-/commander-4.1.1.tgz", + "integrity": "sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==", + "license": "MIT", + "engines": { + "node": ">= 6" + } + }, "node_modules/commondir": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/commondir/-/commondir-1.0.1.tgz", @@ -4888,6 +5095,18 @@ "node": ">= 8" } }, + "node_modules/cssesc": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz", + "integrity": "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==", + "license": "MIT", + "bin": { + "cssesc": "bin/cssesc" + }, + "engines": { + "node": ">=4" + } + }, "node_modules/csstype": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz", @@ -5034,17 +5253,17 @@ "resolved": "https://registry.npmjs.org/detect-node-es/-/detect-node-es-1.1.0.tgz", "integrity": "sha512-ypdmJU/TbBby2Dxibuv7ZLW3Bs1QEmM7nHjEANfohJLvE0XVujisn1qPJcZxg+qDucsr+bP6fLD1rPS3AhJ7EQ==" }, - "node_modules/doctrine": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", - "integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==", - "dev": true, - "dependencies": { - "esutils": "^2.0.2" - }, - "engines": { - "node": ">=6.0.0" - } + "node_modules/didyoumean": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/didyoumean/-/didyoumean-1.2.2.tgz", + "integrity": "sha512-gxtyfqMg7GKyhQmb056K7M3xszy/myH8w+B4RT+QXBQsvAOdc3XymqDDPHx1BgPgsdAA5SIifona89YtRATDzw==", + "license": "Apache-2.0" + }, + "node_modules/dlv": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/dlv/-/dlv-1.1.3.tgz", + "integrity": "sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA==", + "license": "MIT" }, "node_modules/dunder-proto": { "version": "1.0.1", @@ -5284,58 +5503,64 @@ } }, "node_modules/eslint": { - "version": "8.57.0", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.57.0.tgz", - "integrity": "sha512-dZ6+mexnaTIbSBZWgou51U6OmzIhYM2VcNdtiTtI7qPNZm35Akpr0f6vtw3w1Kmn5PYo+tZVfh13WrhpS6oLqQ==", - "dev": true, - "dependencies": { - "@eslint-community/eslint-utils": "^4.2.0", - "@eslint-community/regexpp": "^4.6.1", - "@eslint/eslintrc": "^2.1.4", - "@eslint/js": "8.57.0", - "@humanwhocodes/config-array": "^0.11.14", + "version": "9.39.5", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.39.5.tgz", + "integrity": "sha512-DgZS62aPLXKlnxILS/AYCoRvHaZeXceIzlXPkkGGzJWSow1aEk0lbTlxUSlyjC8jcaKxAdOnTDz+o1JFSBsyjw==", + "deprecated": "This version is no longer supported. Please see https://eslint.org/version-support for other options.", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/eslint-utils": "^4.8.0", + "@eslint-community/regexpp": "^4.12.1", + "@eslint/config-array": "^0.21.2", + "@eslint/config-helpers": "^0.4.2", + "@eslint/core": "^0.17.0", + "@eslint/eslintrc": "^3.3.6", + "@eslint/js": "9.39.5", + "@eslint/plugin-kit": "^0.4.1", + "@humanfs/node": "^0.16.6", "@humanwhocodes/module-importer": "^1.0.1", - "@nodelib/fs.walk": "^1.2.8", - "@ungap/structured-clone": "^1.2.0", - "ajv": "^6.12.4", + "@humanwhocodes/retry": "^0.4.2", + "@types/estree": "^1.0.6", + "ajv": "^6.14.0", "chalk": "^4.0.0", - "cross-spawn": "^7.0.2", + "cross-spawn": "^7.0.6", "debug": "^4.3.2", - "doctrine": "^3.0.0", "escape-string-regexp": "^4.0.0", - "eslint-scope": "^7.2.2", - "eslint-visitor-keys": "^3.4.3", - "espree": "^9.6.1", - "esquery": "^1.4.2", + "eslint-scope": "^8.4.0", + "eslint-visitor-keys": "^4.2.1", + "espree": "^10.4.0", + "esquery": "^1.5.0", "esutils": "^2.0.2", "fast-deep-equal": "^3.1.3", - "file-entry-cache": "^6.0.1", + "file-entry-cache": "^8.0.0", "find-up": "^5.0.0", "glob-parent": "^6.0.2", - "globals": "^13.19.0", - "graphemer": "^1.4.0", "ignore": "^5.2.0", "imurmurhash": "^0.1.4", "is-glob": "^4.0.0", - "is-path-inside": "^3.0.3", - "js-yaml": "^4.1.0", "json-stable-stringify-without-jsonify": "^1.0.1", - "levn": "^0.4.1", "lodash.merge": "^4.6.2", - "minimatch": "^3.1.2", + "minimatch": "^3.1.5", "natural-compare": "^1.4.0", - "optionator": "^0.9.3", - "strip-ansi": "^6.0.1", - "text-table": "^0.2.0" + "optionator": "^0.9.3" }, "bin": { "eslint": "bin/eslint.js" }, "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" }, "funding": { - "url": "https://opencollective.com/eslint" + "url": "https://eslint.org/donate" + }, + "peerDependencies": { + "jiti": "*" + }, + "peerDependenciesMeta": { + "jiti": { + "optional": true + } } }, "node_modules/eslint-config-next": { @@ -5639,16 +5864,17 @@ } }, "node_modules/eslint-scope": { - "version": "7.2.2", - "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.2.2.tgz", - "integrity": "sha512-dOt21O7lTMhDM+X9mB4GX+DZrZtCUJPL/wlcTqxyrx5IvO0IYtILdtrQGQp+8n5S0gwSVmOf9NQrjMOgfQZlIg==", + "version": "8.4.0", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-8.4.0.tgz", + "integrity": "sha512-sNXOfKCn74rt8RICKMvJS7XKV/Xk9kA7DyJr8mJik3S7Cwgy3qlkkmyS2uQB3jiJg6VNdZd/pDBJu0nvG2NlTg==", "dev": true, + "license": "BSD-2-Clause", "dependencies": { "esrecurse": "^4.3.0", "estraverse": "^5.2.0" }, "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" }, "funding": { "url": "https://opencollective.com/eslint" @@ -5666,18 +5892,45 @@ "url": "https://opencollective.com/eslint" } }, + "node_modules/eslint/node_modules/eslint-visitor-keys": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.1.tgz", + "integrity": "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, "node_modules/espree": { - "version": "9.6.1", - "resolved": "https://registry.npmjs.org/espree/-/espree-9.6.1.tgz", - "integrity": "sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ==", + "version": "10.4.0", + "resolved": "https://registry.npmjs.org/espree/-/espree-10.4.0.tgz", + "integrity": "sha512-j6PAQ2uUr79PZhBjP5C5fhl8e39FmRnOjsD5lGnWrFU8i2G776tBK7+nP8KuQUTTyAZUwfQqXAgrVH5MbH9CYQ==", "dev": true, + "license": "BSD-2-Clause", "dependencies": { - "acorn": "^8.9.0", + "acorn": "^8.15.0", "acorn-jsx": "^5.3.2", - "eslint-visitor-keys": "^3.4.1" + "eslint-visitor-keys": "^4.2.1" }, "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/espree/node_modules/eslint-visitor-keys": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.1.tgz", + "integrity": "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" }, "funding": { "url": "https://opencollective.com/eslint" @@ -5713,6 +5966,7 @@ "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", "dev": true, + "license": "BSD-2-Clause", "dependencies": { "estraverse": "^5.2.0" }, @@ -5742,13 +5996,13 @@ "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 + "dev": true, + "license": "MIT" }, "node_modules/fast-glob": { "version": "3.3.2", "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.2.tgz", "integrity": "sha512-oX2ruAFQwf/Orj8m737Y5adxDQO0LAB7/S5MnxCdTNDd4p6BsyIVsv9JQsATbTSq8KHRpLwIHbVlUNatxd+1Ow==", - "dev": true, "dependencies": { "@nodelib/fs.stat": "^2.0.2", "@nodelib/fs.walk": "^1.2.3", @@ -5764,7 +6018,6 @@ "version": "5.1.2", "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", - "dev": true, "dependencies": { "is-glob": "^4.0.1" }, @@ -5776,7 +6029,8 @@ "version": "2.1.0", "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/fast-levenshtein": { "version": "2.0.6", @@ -5788,21 +6042,21 @@ "version": "1.17.1", "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.17.1.tgz", "integrity": "sha512-sRVD3lWVIXWg6By68ZN7vho9a1pQcN/WBFaAAsDDFzlJjvoGx0P8z7V1t72grFJfJhu3YPZBuu25f7Kaw2jN1w==", - "dev": true, "dependencies": { "reusify": "^1.0.4" } }, "node_modules/file-entry-cache": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz", - "integrity": "sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==", + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-8.0.0.tgz", + "integrity": "sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==", "dev": true, + "license": "MIT", "dependencies": { - "flat-cache": "^3.0.4" + "flat-cache": "^4.0.0" }, "engines": { - "node": "^10.12.0 || >=12.0.0" + "node": ">=16.0.0" } }, "node_modules/fill-range": { @@ -5855,23 +6109,23 @@ } }, "node_modules/flat-cache": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.2.0.tgz", - "integrity": "sha512-CYcENa+FtcUKLmhhqyctpclsq7QF38pKjZHsGNiSQF5r4FtoKDWabFDl3hzaEQMvT1LHEysw5twgLvpYYb4vbw==", + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-4.0.1.tgz", + "integrity": "sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==", "dev": true, + "license": "MIT", "dependencies": { "flatted": "^3.2.9", - "keyv": "^4.5.3", - "rimraf": "^3.0.2" + "keyv": "^4.5.4" }, "engines": { - "node": "^10.12.0 || >=12.0.0" + "node": ">=16" } }, "node_modules/flatted": { - "version": "3.4.2", - "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.4.2.tgz", - "integrity": "sha512-PjDse7RzhcPkIJwy5t7KPWQSZ9cAbzQXcafsetQoD7sOJRQlGikNbx7yZp2OotDnJyrDcbyRq3Ttb18iYOqkxA==", + "version": "3.4.4", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.4.4.tgz", + "integrity": "sha512-5+ybhBZANEJxaH3X5evAFatUxLfEHSr7n6kYJ+1Qd0mUqr4eu9gIf6GDbWHf8RJijHrjjO8G+la14SlL2SeS1Q==", "dev": true, "license": "ISC" }, @@ -5937,17 +6191,24 @@ } } }, - "node_modules/fs.realpath": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", - "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", - "dev": true + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } }, "node_modules/function-bind": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", - "dev": true, "funding": { "url": "https://github.com/sponsors/ljharb" } @@ -6083,7 +6344,6 @@ "version": "6.0.2", "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", - "dev": true, "dependencies": { "is-glob": "^4.0.3" }, @@ -6092,15 +6352,13 @@ } }, "node_modules/globals": { - "version": "13.24.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-13.24.0.tgz", - "integrity": "sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ==", + "version": "14.0.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-14.0.0.tgz", + "integrity": "sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==", "dev": true, - "dependencies": { - "type-fest": "^0.20.2" - }, + "license": "MIT", "engines": { - "node": ">=8" + "node": ">=18" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" @@ -6229,7 +6487,6 @@ "version": "2.0.2", "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", - "dev": true, "dependencies": { "function-bind": "^1.1.2" }, @@ -6255,19 +6512,21 @@ } }, "node_modules/ignore": { - "version": "5.3.1", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.1.tgz", - "integrity": "sha512-5Fytz/IraMjqpwfd34ke28PTVMjZjJG2MPn5t7OE4eUCUNf8BAa7b5WUS9/Qvr6mwOQS7Mk6vdsMno5he+T8Xw==", + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", + "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==", "dev": true, + "license": "MIT", "engines": { "node": ">= 4" } }, "node_modules/import-fresh": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz", - "integrity": "sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==", + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.1.tgz", + "integrity": "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==", "dev": true, + "license": "MIT", "dependencies": { "parent-module": "^1.0.0", "resolve-from": "^4.0.0" @@ -6287,22 +6546,6 @@ "node": ">=0.8.19" } }, - "node_modules/inflight": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", - "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", - "dev": true, - "dependencies": { - "once": "^1.3.0", - "wrappy": "1" - } - }, - "node_modules/inherits": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", - "dev": true - }, "node_modules/internal-slot": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.1.0.tgz", @@ -6396,6 +6639,18 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/is-binary-path": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", + "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", + "license": "MIT", + "dependencies": { + "binary-extensions": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/is-boolean-object": { "version": "1.2.2", "resolved": "https://registry.npmjs.org/is-boolean-object/-/is-boolean-object-1.2.2.tgz", @@ -6430,7 +6685,6 @@ "version": "2.16.1", "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.16.1.tgz", "integrity": "sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==", - "dev": true, "license": "MIT", "dependencies": { "hasown": "^2.0.2" @@ -6481,7 +6735,6 @@ "version": "2.1.1", "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", - "dev": true, "engines": { "node": ">=0.10.0" } @@ -6526,7 +6779,6 @@ "version": "4.0.3", "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", - "dev": true, "dependencies": { "is-extglob": "^2.1.1" }, @@ -6584,15 +6836,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/is-path-inside": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-3.0.3.tgz", - "integrity": "sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==", - "dev": true, - "engines": { - "node": ">=8" - } - }, "node_modules/is-plain-object": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-2.0.4.tgz", @@ -6790,6 +7033,15 @@ "node": ">= 0.4" } }, + "node_modules/jiti": { + "version": "1.21.7", + "resolved": "https://registry.npmjs.org/jiti/-/jiti-1.21.7.tgz", + "integrity": "sha512-/imKNG4EbWNrVjoNC/1H5/9GFy+tqjGBHCaSsN+P2RnPqjsLmv6UD3Ej+Kj8nBWaRAwyk7kK5ZUc+OEatnTR3A==", + "license": "MIT", + "bin": { + "jiti": "bin/jiti.js" + } + }, "node_modules/js-tokens": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", @@ -6874,13 +7126,15 @@ "version": "3.0.1", "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/json-schema-traverse": { "version": "0.4.1", "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/json-stable-stringify-without-jsonify": { "version": "1.0.1", @@ -6921,6 +7175,7 @@ "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==", "dev": true, + "license": "MIT", "dependencies": { "json-buffer": "3.0.1" } @@ -6976,6 +7231,24 @@ "node": ">= 0.8.0" } }, + "node_modules/lilconfig": { + "version": "3.1.4", + "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-3.1.4.tgz", + "integrity": "sha512-zJZyBmQhArDRefuWTypZxwDpFDIYmrj8ypSBvwGD5P13Y95s6DjzKgu4DVdGbeQa5PDEsAOCAPNk4X+QOR006w==", + "license": "MIT", + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/antonk52" + } + }, + "node_modules/lines-and-columns": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", + "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==", + "license": "MIT" + }, "node_modules/locate-path": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", @@ -7078,7 +7351,6 @@ "version": "1.4.1", "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", - "dev": true, "engines": { "node": ">= 8" } @@ -7121,6 +7393,17 @@ "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" }, + "node_modules/mz": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/mz/-/mz-2.7.0.tgz", + "integrity": "sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q==", + "license": "MIT", + "dependencies": { + "any-promise": "^1.0.0", + "object-assign": "^4.0.1", + "thenify-all": "^1.0.0" + } + }, "node_modules/nanoid": { "version": "3.3.20", "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.20.tgz", @@ -7241,6 +7524,15 @@ "node": ">=18" } }, + "node_modules/normalize-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/normalize-range": { "version": "0.1.2", "resolved": "https://registry.npmjs.org/normalize-range/-/normalize-range-0.1.2.tgz", @@ -7254,11 +7546,19 @@ "version": "4.1.1", "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", - "dev": true, "engines": { "node": ">=0.10.0" } }, + "node_modules/object-hash": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/object-hash/-/object-hash-3.0.0.tgz", + "integrity": "sha512-RSn9F68PjH9HqtltsSnqYC1XXoWe9Bju5+213R98cNGttag9q9yAOTzdbsqvIa7aNm5WffBZFpWYr2aWrklWAw==", + "license": "MIT", + "engines": { + "node": ">= 6" + } + }, "node_modules/object-inspect": { "version": "1.13.4", "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz", @@ -7369,15 +7669,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/once": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", - "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", - "dev": true, - "dependencies": { - "wrappy": "1" - } - }, "node_modules/optionator": { "version": "0.9.3", "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.3.tgz", @@ -7457,6 +7748,7 @@ "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", "dev": true, + "license": "MIT", "dependencies": { "callsites": "^3.0.0" }, @@ -7473,15 +7765,6 @@ "node": ">=8" } }, - "node_modules/path-is-absolute": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", - "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/path-key": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", @@ -7494,8 +7777,7 @@ "node_modules/path-parse": { "version": "1.0.7", "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", - "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", - "dev": true + "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==" }, "node_modules/picocolors": { "version": "1.1.1", @@ -7634,11 +7916,132 @@ "node": "^10 || ^12 || >=14" } }, + "node_modules/postcss-import": { + "version": "15.1.0", + "resolved": "https://registry.npmjs.org/postcss-import/-/postcss-import-15.1.0.tgz", + "integrity": "sha512-hpr+J05B2FVYUAXHeK1YyI267J/dDDhMU6B6civm8hSY1jYJnBXxzKDKDswzJmtLHryrjhnDjqqp/49t8FALew==", + "license": "MIT", + "dependencies": { + "postcss-value-parser": "^4.0.0", + "read-cache": "^1.0.0", + "resolve": "^1.1.7" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "postcss": "^8.0.0" + } + }, + "node_modules/postcss-js": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/postcss-js/-/postcss-js-4.1.0.tgz", + "integrity": "sha512-oIAOTqgIo7q2EOwbhb8UalYePMvYoIeRY2YKntdpFQXNosSu3vLrniGgmH9OKs/qAkfoj5oB3le/7mINW1LCfw==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "camelcase-css": "^2.0.1" + }, + "engines": { + "node": "^12 || ^14 || >= 16" + }, + "peerDependencies": { + "postcss": "^8.4.21" + } + }, + "node_modules/postcss-load-config": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/postcss-load-config/-/postcss-load-config-6.0.1.tgz", + "integrity": "sha512-oPtTM4oerL+UXmx+93ytZVN82RrlY/wPUV8IeDxFrzIjXOLF1pN+EmKPLbubvKHT2HC20xXsCAH2Z+CKV6Oz/g==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "lilconfig": "^3.1.1" + }, + "engines": { + "node": ">= 18" + }, + "peerDependencies": { + "jiti": ">=1.21.0", + "postcss": ">=8.0.9", + "tsx": "^4.8.1", + "yaml": "^2.4.2" + }, + "peerDependenciesMeta": { + "jiti": { + "optional": true + }, + "postcss": { + "optional": true + }, + "tsx": { + "optional": true + }, + "yaml": { + "optional": true + } + } + }, + "node_modules/postcss-nested": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/postcss-nested/-/postcss-nested-6.2.0.tgz", + "integrity": "sha512-HQbt28KulC5AJzG+cZtj9kvKB93CFCdLvog1WFLf1D+xmMvPGlBstkpTEZfK5+AN9hfJocyBFCNiqyS48bpgzQ==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "postcss-selector-parser": "^6.1.1" + }, + "engines": { + "node": ">=12.0" + }, + "peerDependencies": { + "postcss": "^8.2.14" + } + }, + "node_modules/postcss-selector-parser": { + "version": "6.1.5", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.1.5.tgz", + "integrity": "sha512-TEHFzb5KRDKaOgJ4TYE+0jyJtFbjLvkdVOu0fZ2b6MTWzoqZd4Le+61aEppWkvTlYePgOX4ATL1wsAIPMLTw6Q==", + "license": "MIT", + "dependencies": { + "cssesc": "^3.0.0", + "util-deprecate": "^1.0.2" + }, + "engines": { + "node": ">=4" + } + }, "node_modules/postcss-value-parser": { "version": "4.2.0", "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz", - "integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==", - "dev": true + "integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==" }, "node_modules/prelude-ls": { "version": "1.2.1", @@ -7666,6 +8069,7 @@ "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", "dev": true, + "license": "MIT", "engines": { "node": ">=6" } @@ -7674,7 +8078,6 @@ "version": "1.2.3", "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", - "dev": true, "funding": [ { "type": "github", @@ -7803,6 +8206,24 @@ "react": "^16.8.0 || ^17.0.0 || ^18.0.0" } }, + "node_modules/read-cache": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/read-cache/-/read-cache-1.0.2.tgz", + "integrity": "sha512-/peqiBB/n07gQGLsWaHho3WfvUyRscw0gYTsEFMhrIe/nWLkYaf5SbKYjGYqtRV3aPwykJgF2VEMo1ac4bnsGA==", + "license": "MIT" + }, + "node_modules/readdirp": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", + "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", + "license": "MIT", + "dependencies": { + "picomatch": "^2.2.1" + }, + "engines": { + "node": ">=8.10.0" + } + }, "node_modules/recast": { "version": "0.23.11", "resolved": "https://registry.npmjs.org/recast/-/recast-0.23.11.tgz", @@ -7867,7 +8288,6 @@ "version": "1.22.8", "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.8.tgz", "integrity": "sha512-oKWePCxqpd6FlLvGV1VU0x7bkPmmCNolxzjMf4NczoDnQcIWrAF+cPtZn5i6n+RfD2d9i0tzpKnG6Yk168yIyw==", - "dev": true, "dependencies": { "is-core-module": "^2.13.0", "path-parse": "^1.0.7", @@ -7885,6 +8305,7 @@ "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", "dev": true, + "license": "MIT", "engines": { "node": ">=4" } @@ -7902,52 +8323,15 @@ "version": "1.0.4", "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz", "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==", - "dev": true, "engines": { "iojs": ">=1.0.0", "node": ">=0.10.0" } }, - "node_modules/rimraf": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", - "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", - "dev": true, - "dependencies": { - "glob": "^7.1.3" - }, - "bin": { - "rimraf": "bin.js" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/rimraf/node_modules/glob": { - "version": "7.2.3", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", - "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", - "dev": true, - "dependencies": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.1.1", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - }, - "engines": { - "node": "*" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, "node_modules/run-parallel": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", - "dev": true, "funding": [ { "type": "github", @@ -8429,18 +8813,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "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, - "dependencies": { - "ansi-regex": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/strip-bom": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", @@ -8455,6 +8827,7 @@ "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", "dev": true, + "license": "MIT", "engines": { "node": ">=8" }, @@ -8485,6 +8858,28 @@ } } }, + "node_modules/sucrase": { + "version": "3.35.1", + "resolved": "https://registry.npmjs.org/sucrase/-/sucrase-3.35.1.tgz", + "integrity": "sha512-DhuTmvZWux4H1UOnWMB3sk0sbaCVOoQZjv8u1rDoTV0HTdGem9hkAZtl4JZy8P2z4Bg0nT+YMeOFyVr4zcG5Tw==", + "license": "MIT", + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.2", + "commander": "^4.0.0", + "lines-and-columns": "^1.1.6", + "mz": "^2.7.0", + "pirates": "^4.0.1", + "tinyglobby": "^0.2.11", + "ts-interface-checker": "^0.1.9" + }, + "bin": { + "sucrase": "bin/sucrase", + "sucrase-node": "bin/sucrase-node" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + } + }, "node_modules/supports-color": { "version": "7.2.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", @@ -8501,7 +8896,6 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", - "dev": true, "engines": { "node": ">= 0.4" }, @@ -8539,10 +8933,41 @@ } }, "node_modules/tailwindcss": { - "version": "4.3.3", - "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.3.3.tgz", - "integrity": "sha512-gOhV3P7ufE62QDGg1zVaTgCR+EtPv92k2nIhVcVKcLmxT1sUBsQGhnZj175j+MqRt4zLF7ic+sCYjfhxMxj7YQ==", - "license": "MIT" + "version": "3.4.19", + "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.4.19.tgz", + "integrity": "sha512-3ofp+LL8E+pK/JuPLPggVAIaEuhvIz4qNcf3nA1Xn2o/7fb7s/TYpHhwGDv1ZU3PkBluUVaF8PyCHcm48cKLWQ==", + "license": "MIT", + "dependencies": { + "@alloc/quick-lru": "^5.2.0", + "arg": "^5.0.2", + "chokidar": "^3.6.0", + "didyoumean": "^1.2.2", + "dlv": "^1.1.3", + "fast-glob": "^3.3.2", + "glob-parent": "^6.0.2", + "is-glob": "^4.0.3", + "jiti": "^1.21.7", + "lilconfig": "^3.1.3", + "micromatch": "^4.0.8", + "normalize-path": "^3.0.0", + "object-hash": "^3.0.0", + "picocolors": "^1.1.1", + "postcss": "^8.4.47", + "postcss-import": "^15.1.0", + "postcss-js": "^4.0.1", + "postcss-load-config": "^4.0.2 || ^5.0 || ^6.0", + "postcss-nested": "^6.2.0", + "postcss-selector-parser": "^6.1.2", + "resolve": "^1.22.8", + "sucrase": "^3.35.0" + }, + "bin": { + "tailwind": "lib/cli.js", + "tailwindcss": "lib/cli.js" + }, + "engines": { + "node": ">=14.0.0" + } }, "node_modules/tapable": { "version": "2.2.1", @@ -8553,11 +8978,26 @@ "node": ">=6" } }, - "node_modules/text-table": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", - "integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==", - "dev": true + "node_modules/thenify": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/thenify/-/thenify-3.3.1.tgz", + "integrity": "sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw==", + "license": "MIT", + "dependencies": { + "any-promise": "^1.0.0" + } + }, + "node_modules/thenify-all": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/thenify-all/-/thenify-all-1.6.0.tgz", + "integrity": "sha512-RNxQH/qI8/t3thXJDwcstUO4zeqo64+Uy/+sNVRBx4Xn2OX+OZ9oP+iJnNFqplFra2ZUVeKCSa2oVWi3T4uVmA==", + "license": "MIT", + "dependencies": { + "thenify": ">= 3.1.0 < 4" + }, + "engines": { + "node": ">=0.8" + } }, "node_modules/tiny-invariant": { "version": "1.3.3", @@ -8565,6 +9005,51 @@ "integrity": "sha512-+FbBPE1o9QAYvviau/qC5SE3caw21q3xkvWKBtja5vgqOWIHHJ3ioaq1VPfn/Szqctz2bU/oYeKd9/z5BL+PVg==", "license": "MIT" }, + "node_modules/tinyglobby": { + "version": "0.2.17", + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.17.tgz", + "integrity": "sha512-wXR/dYpcqKmfWpEdZjiKJOwCNFndD0DMnrW/cYjVGttEkBfVgcLFHoNrlj47mjOVic9yyNu65alsgF4NQyTa2g==", + "license": "MIT", + "dependencies": { + "fdir": "^6.5.0", + "picomatch": "^4.0.4" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/SuperchupuDev" + } + }, + "node_modules/tinyglobby/node_modules/fdir": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", + "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", + "license": "MIT", + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } + } + }, + "node_modules/tinyglobby/node_modules/picomatch": { + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.7.tgz", + "integrity": "sha512-qcJu88Q2IWqJsDD529JKMdwGm/dvInW4HvQnRwiH9JtihJvzGOscDtHE3x1pBKeUOTysQ8kVmLnJ2kJu7yhcGA==", + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, "node_modules/tmp": { "version": "0.2.7", "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.2.7.tgz", @@ -8598,6 +9083,12 @@ "typescript": ">=4.8.4" } }, + "node_modules/ts-interface-checker": { + "version": "0.1.13", + "resolved": "https://registry.npmjs.org/ts-interface-checker/-/ts-interface-checker-0.1.13.tgz", + "integrity": "sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==", + "license": "Apache-2.0" + }, "node_modules/tsconfig-paths": { "version": "3.15.0", "resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-3.15.0.tgz", @@ -8628,18 +9119,6 @@ "node": ">= 0.8.0" } }, - "node_modules/type-fest": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", - "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", - "dev": true, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/typed-array-buffer": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/typed-array-buffer/-/typed-array-buffer-1.0.3.tgz", @@ -8815,6 +9294,7 @@ "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", "dev": true, + "license": "BSD-2-Clause", "dependencies": { "punycode": "^2.1.0" } @@ -8897,6 +9377,12 @@ } } }, + "node_modules/util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", + "license": "MIT" + }, "node_modules/which": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", @@ -9001,12 +9487,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/wrappy": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", - "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", - "dev": true - }, "node_modules/write-file-atomic": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-5.0.1.tgz", diff --git a/code-samples/eventing/bookstore-sample-app/start/frontend/package.json b/code-samples/eventing/bookstore-sample-app/start/frontend/package.json index 51ca07e636..3d50079474 100644 --- a/code-samples/eventing/bookstore-sample-app/start/frontend/package.json +++ b/code-samples/eventing/bookstore-sample-app/start/frontend/package.json @@ -4,10 +4,10 @@ "private": true, "scripts": { "dev": "next dev", - "build": "next build", + "build": "next build --webpack", "start": "next start", - "lint": "next lint", - "prepare": "next-ws patch" + "lint": "eslint .", + "prepare": "next-ws patch --yes" }, "dependencies": { "@heroicons/react": "^2.1.3", @@ -26,10 +26,10 @@ "@types/react-dom": "^18", "@types/ws": "^8.18.1", "autoprefixer": "^10.0.1", - "eslint": "^8", + "eslint": "^9", "eslint-config-next": "16.0.3", "postcss": "^8", - "tailwindcss": "^4.3.3", + "tailwindcss": "^3.3.0", "typescript": "^5" } } diff --git a/code-samples/eventing/bookstore-sample-app/start/node-server/index.js b/code-samples/eventing/bookstore-sample-app/start/node-server/index.js index 78d2fb42b9..8ce06b9693 100644 --- a/code-samples/eventing/bookstore-sample-app/start/node-server/index.js +++ b/code-samples/eventing/bookstore-sample-app/start/node-server/index.js @@ -9,13 +9,13 @@ const port = 8000; // Middleware to parse JSON bodies -app.use(express.json()); +app.use(express.json({type: ['application/json', 'application/cloudevents+json']})); app.use(cors()); expressWs(app); // Apply WebSocket functionality to Express // Configure the PostgreSQL connection pool const pool = new Pool({ - host: 'postgresql.default.svc.cluster.local', + host: process.env.PGHOST || 'postgresql', port: 5432, database: 'mydatabase', user: 'myuser', @@ -84,7 +84,7 @@ app.post('/insert', async (req, res) => { // Set headers and send the CloudEvent res.writeHead(200, serializedEvent.headers); - res.end(JSON.stringify(serializedEvent.body)); + res.end(serializedEvent.body); } catch (error) { console.error('Error processing request:', error); diff --git a/code-samples/eventing/bookstore-sample-app/start/node-server/package.json b/code-samples/eventing/bookstore-sample-app/start/node-server/package.json index fca53a9c97..3f8606affd 100644 --- a/code-samples/eventing/bookstore-sample-app/start/node-server/package.json +++ b/code-samples/eventing/bookstore-sample-app/start/node-server/package.json @@ -3,9 +3,6 @@ "version": "1.0.0", "description": "", "main": "index.js", - "scripts": { - "test": "echo \"Error: no test specified\" && exit 1" - }, "author": "", "license": "ISC", "dependencies": { diff --git a/code-samples/eventing/bookstore-sample-app/start/slack-sink/README.md b/code-samples/eventing/bookstore-sample-app/start/slack-sink/README.md index 129521aded..b9a4bebc00 100644 --- a/code-samples/eventing/bookstore-sample-app/start/slack-sink/README.md +++ b/code-samples/eventing/bookstore-sample-app/start/slack-sink/README.md @@ -106,7 +106,7 @@ spec: apiVersion: camel.apache.org/v1 # The API version for Kamelet. name: slack-sink # The name of the Kamelet to use as the sink, in this case, a predefined "slack-sink" Kamelet. properties: - channel: “#bookstore-owner” # The Slack channel where notifications will be sent. + channel: "#bookstore-owner" # The Slack channel where notifications will be sent. webhookUrl: "https://hooks.slack.com/services/YOUR/SLACK/WEBHOOK" # The Webhook URL provided by Slack for posting messages to a specific channel. ``` @@ -122,15 +122,15 @@ $ kubectl apply -f slack-sink.yaml You will see this message if the configuration is created successfully: ``` -pipe.camel.apache.org/slack-sink-pipe created +pipe.camel.apache.org/bookstore-notification-service created ``` But this process will take a few seconds to complete. You can check the status of the pipe by running the following command: ```bash -$ kubectl get pipe slack-sink-pipe +$ kubectl get pipe bookstore-notification-service -NAME PHASE REPLICAS -slack-sink-pipe Ready 1 +NAME PHASE REPLICAS +bookstore-notification-service Ready 1 ``` @@ -163,7 +163,7 @@ Using curl command to send a CloudEvents to the Broker: -H "Ce-Type: new-review-comment" \ -H "Ce-Source: bookstore-web-app" \ -H "Content-Type: application/json" \ --d 'Hello from Knative!' +-d '{"reviewText":"Hello from Knative!"}' ``` You can find the URI to your Broker by running the following command: diff --git a/docs/versioned/bookstore/page-2/sentiment-analysis-service-for-bookstore-reviews.md b/docs/versioned/bookstore/page-2/sentiment-analysis-service-for-bookstore-reviews.md index 88a535b894..a4a9bb9f02 100644 --- a/docs/versioned/bookstore/page-2/sentiment-analysis-service-for-bookstore-reviews.md +++ b/docs/versioned/bookstore/page-2/sentiment-analysis-service-for-bookstore-reviews.md @@ -106,7 +106,7 @@ You can find all the supported language templates [here](https://knative.dev/doc ![Image14](images/image14.png) -`sentiment-analysis-app/function/func.py` is the file that contains the code for the function. +[`sentiment-analysis-app/function/func.py`](https://github.com/knative/docs/blob/main/code-samples/eventing/bookstore-sample-app/solution/sentiment-analysis/function/func.py) is the file that contains the code for the function. You can replace the generated code with the sentiment analysis logic. You can use the following code as a starting point: @@ -175,7 +175,7 @@ You can replace the generated code with the sentiment analysis logic. You can us ![Image9](images/image9.png) -The `sentiment-analysis-app/pyproject.toml` file contains the project configuration with the dependencies for the function. Add the `textblob` dependency to the dependencies array : +The [`sentiment-analysis-app/pyproject.toml`](https://github.com/knative/docs/blob/main/code-samples/eventing/bookstore-sample-app/solution/sentiment-analysis/pyproject.toml) file contains the project configuration with the dependencies for the function. Add the `textblob` dependency to the dependencies array : ???+ abstract "_sentiment-analysis-app/pyproject.toml_" ```toml diff --git a/docs/versioned/bookstore/page-3/create-bad-word-filter-service.md b/docs/versioned/bookstore/page-3/create-bad-word-filter-service.md index e86704e6cf..d50f64dde1 100644 --- a/docs/versioned/bookstore/page-3/create-bad-word-filter-service.md +++ b/docs/versioned/bookstore/page-3/create-bad-word-filter-service.md @@ -76,7 +76,7 @@ func create -l python bad-word-filter -t cloudevents ![Image 5](images/image5.png) -`bad-word-filter/function/func.py` is the file that contains the code for the function. You can replace the generated code with the bad word filter logic. You can use the following code as a starting point: +[`bad-word-filter/function/func.py`](https://github.com/knative/docs/blob/main/code-samples/eventing/bookstore-sample-app/solution/bad-word-filter/function/func.py) is the file that contains the code for the function. You can replace the generated code with the bad word filter logic. You can use the following code as a starting point: ???+ abstract "_bad-word-filter/function/func.py_" @@ -128,7 +128,7 @@ func create -l python bad-word-filter -t cloudevents ### **Step 3: Configure the dependencies** ![Image 8](images/image8.png) -The content of `bad-word-filter/pyproject.toml`: +The content of [`bad-word-filter/pyproject.toml`](https://github.com/knative/docs/blob/main/code-samples/eventing/bookstore-sample-app/solution/bad-word-filter/pyproject.toml): ???+ abstract "_bad-word-filter/pyproject.toml_" From 74dc0427dfc7509853b0ffdd3936250d3c9e70ff Mon Sep 17 00:00:00 2001 From: Dave Protasowski Date: Mon, 26 Oct 2026 09:16:03 -0400 Subject: [PATCH 13/44] Refresh the GitHub Source sample instructions --- code-samples/eventing/github-source/README.md | 124 ++++++------------ 1 file changed, 42 insertions(+), 82 deletions(-) diff --git a/code-samples/eventing/github-source/README.md b/code-samples/eventing/github-source/README.md index 0cb333df07..cd7e099908 100644 --- a/code-samples/eventing/github-source/README.md +++ b/code-samples/eventing/github-source/README.md @@ -3,6 +3,15 @@ GitHub Source example shows how to wire GitHub events for consumption by a Knative Service. +## Get the sample files + +Clone the repository and run the commands below from this sample directory: + +```bash +git clone https://github.com/knative/docs.git knative-docs +cd knative-docs/code-samples/eventing/github-source +``` + ## Before you begin 1. Set up [Knative Serving](https://knative.dev/docs/install/serving/install-serving-with-yaml). @@ -29,20 +38,9 @@ kubectl -n knative-sources get pods --selector control-plane=github-controller-m ### Create a Knative Service To verify the GitHub source is working, create a simple Knative -Service that dumps incoming messages to its log. The `service.yaml` file -defines this basic Service. - -```yaml -apiVersion: serving.knative.dev/v1 -kind: Service -metadata: - name: github-message-dumper -spec: - template: - spec: - containers: - - image: gcr.io/knative-releases/knative.dev/eventing/cmd/event_display -``` +Service that dumps incoming messages to its log. +[service.yaml](service.yaml) defines this Service using the Eventing +`event_display` receiver image. Enter the following command to create the service from `service.yaml`: @@ -68,20 +66,10 @@ recommended scopes: ![GitHub UI](personal_access_token.png "GitHub personal access token screenshot") -Update `githubsecret.yaml` with those values. If your generated access -token is `'personal_access_token_value'` and you choose your _secretToken_ -as `'asdfasfdsaf'`, you'd modify `githubsecret.yaml` like so: - -```yaml -apiVersion: v1 -kind: Secret -metadata: - name: githubsecret -type: Opaque -stringData: - accessToken: personal_access_token_value - secretToken: asdfasfdsaf -``` +Update [githubsecret.yaml](githubsecret.yaml) with those values: set +`stringData.accessToken` to your generated access token and +`stringData.secretToken` to your chosen webhook secret. Keep the secret name +and keys unchanged because the source references them. Do not commit real tokens. Hint: you can makeup a random _secretToken_ with: @@ -102,34 +90,14 @@ Source for a specific namespace. Be sure to replace the `ownerAndRepository` value with a valid GitHub public repository owned by your GitHub user. -If using GitHub enterprise you will need to add an additional githubAPIURL -field to the spec specifying your GitHub enterprise API endpoint. - -```yaml -apiVersion: sources.knative.dev/v1alpha1 -kind: GitHubSource -metadata: - name: githubsourcesample -spec: - eventTypes: - - pull_request - ownerAndRepository: / - accessToken: - secretKeyRef: - name: githubsecret - key: accessToken - secretToken: - secretKeyRef: - name: githubsecret - key: secretToken - sink: - ref: - apiVersion: serving.knative.dev/v1 - kind: Service - name: github-message-dumper -``` +Edit [github-source.yaml](github-source.yaml), which subscribes to +`pull_request` events, references the token Secret, and sends events to +`github-message-dumper`. + +If using GitHub Enterprise, add `spec.githubAPIURL` with your enterprise API +endpoint, for example `https://github.mycompany.com/api/v3/`. -Then, apply that yaml using `kubectl`: +Then, apply the file using `kubectl`: ```bash kubectl --namespace default apply --filename github-source.yaml @@ -151,35 +119,27 @@ that the GitHub events were sent into the Knative eventing system by looking at our message dumper function logs. ```bash -kubectl --namespace default get pods -kubectl --namespace default logs github-event-display-XXXX user-container +kubectl --namespace default get pods -l serving.knative.dev/service=github-message-dumper +kubectl --namespace default logs -l serving.knative.dev/service=github-message-dumper -c user-container --tail=50 ``` -You should log lines similar to: - -``` -2018/11/08 18:25:34 Message Dumper received a message: POST / HTTP/1.1 -Host: github-event-display.knative-demo.svc.cluster.local -Accept-Encoding: gzip -Ce-Cloudeventsversion: 0.1 -Ce-Eventid: a8d4cf20-e383-11e8-8069-46e3c8ad2b4d -Ce-Eventtime: 2018-11-08T18:25:32.819548012Z -Ce-Eventtype: dev.knative.source.github.pull_request -Ce-Source: https://github.com/someuser/somerepo/pull/1 -Content-Length: 21060 -Content-Type: application/json -User-Agent: Go-http-client/1.1 -X-B3-Parentspanid: b2e514c3dbe94c03 -X-B3-Sampled: 1 -X-B3-Spanid: c85e346d89c8be4e -X-B3-Traceid: abf6292d458fb8e7 -X-Envoy-Expected-Rq-Timeout-Ms: 60000 -X-Envoy-Internal: true -X-Forwarded-For: 127.0.0.1, 127.0.0.1 -X-Forwarded-Proto: http -X-Request-Id: 8a2201af-5075-9447-b593-ec3a243aff52 - -{"action":"opened","number":1,"pull_request": ...} +You should see a CloudEvent similar to the following (IDs, source, and payload +will depend on your repository and pull request): + +```text +☁️ cloudevents.Event +Context Attributes, + specversion: 1.0 + type: dev.knative.source.github.pull_request + source: https://github.com/someuser/somerepo + id: a8d4cf20-e383-11e8-8069-46e3c8ad2b4d + datacontenttype: application/json +Data, + { + "action": "opened", + "number": 1, + "pull_request": { ... } + } ``` ### Cleanup From ef15d37084be70fc34d5adf589a3a8a7c2888f02 Mon Sep 17 00:00:00 2001 From: Dave Protasowski Date: Mon, 26 Oct 2026 09:16:03 -0400 Subject: [PATCH 14/44] Refresh the GitLab Source sample instructions --- code-samples/eventing/gitlab-source/README.md | 85 ++++++++++--------- 1 file changed, 45 insertions(+), 40 deletions(-) diff --git a/code-samples/eventing/gitlab-source/README.md b/code-samples/eventing/gitlab-source/README.md index 26723648ee..e1da69c56a 100644 --- a/code-samples/eventing/gitlab-source/README.md +++ b/code-samples/eventing/gitlab-source/README.md @@ -11,8 +11,8 @@ You will need: 1. An internet-accessible Kubernetes cluster with [Knative Serving](https://knative.dev/docs/install/serving/install-serving-with-yaml) installed. 1. Ensure Knative Serving is [configured with a domain name](https://knative.dev/docs/serving/using-a-custom-domain). -1. You must ensure that your Knative cluster uses a static IP address refer to your provider's documentation. - that allows GitLab to call into the cluster. +1. Ensure that your Knative cluster has a stable, externally reachable address + that allows GitLab to call into the cluster. Refer to your provider's documentation. 1. Install [Knative Eventing](https://knative.dev/docs/install/eventing/install-eventing-with-yaml). ### Install GitLab Event Source @@ -24,10 +24,11 @@ artifact and replace the `` with version tag: kubectl apply -f https://github.com/knative-extensions/eventing-gitlab/releases/download//gitlab.yaml ``` -Check that the manager is running: +Wait for the controller and webhook to be ready: ```bash -kubectl -n knative-sources get pods --selector control-plane=gitlab-controller-manager +kubectl -n knative-sources rollout status deployment/gitlab-controller-manager +kubectl -n knative-sources rollout status deployment/gitlab-webhook ``` With the controller running you can now move on to a user persona and setup a @@ -50,10 +51,14 @@ We will: ### Create a Knative Service -The `event-display.yaml` file defines the basic service which will -receive events from the GitLab source: +Clone this repository and run the commands below from +`code-samples/eventing/gitlab-source`. -```yaml +Create a Knative Service which receives events from the GitLab source and +prints them to stdout: + +```bash +kubectl -n default apply -f - <<'EOF' apiVersion: serving.knative.dev/v1 kind: Service metadata: @@ -63,12 +68,8 @@ spec: spec: containers: - image: gcr.io/knative-releases/knative.dev/eventing/cmd/event_display -``` - -Create the service: - -```bash -kubectl -n default apply -f event-display.yaml +EOF +kubectl -n default wait --for=condition=Ready kservice/gitlab-event-display --timeout=120s ``` ### Create GitLab Tokens @@ -80,9 +81,10 @@ kubectl -n default apply -f event-display.yaml webhooks. Also decide on a secret token that your source will use to authenticate the incoming webhooks from GitLab. -1. Update the secret values in `secret.yaml` as follows: +1. Replace the placeholder values in the Secret below. + Keep real tokens out of version control: - `accessToken` is the personal access token created in step 1 and + `accessToken` is the project access token created in step 1 and `secretToken` is any token of your choosing. Tip: you can generate a random _secretToken_ by running the command: @@ -91,35 +93,35 @@ kubectl -n default apply -f event-display.yaml head -c 8 /dev/urandom | base64 ``` - `secret.yaml`: +1. Create the secret using `kubectl`. - ```yaml + ```bash + kubectl -n default apply -f - <<'EOF' apiVersion: v1 kind: Secret metadata: name: gitlabsecret type: Opaque stringData: - accessToken: + accessToken: secretToken: - ``` - -1. Create the secret using `kubectl`. - - ```bash - kubectl -n default apply -f secret.yaml + EOF ``` ### Create Event Source for GitLab Events 1. In order to receive GitLab events, you have to create a concrete Event Source for a specific namespace. Replace the `projectUrl` value in the - `gitlabsource.yaml` file with your GitLab project URL, for example + following manifest with your GitLab project URL, for example `https://gitlab.com/knative-examples/functions`. - `gitlabsource.yaml`: + The manifest subscribes to push and issue events, reads both tokens from + `gitlabsecret`, and sends CloudEvents to `gitlab-event-display`. + +1. Apply the edited manifest using `kubectl`: - ```yaml + ```bash + kubectl -n default apply -f - <<'EOF' apiVersion: sources.knative.dev/v1alpha1 kind: GitLabSource metadata: @@ -142,12 +144,8 @@ kubectl -n default apply -f event-display.yaml apiVersion: serving.knative.dev/v1 kind: Service name: gitlab-event-display - ``` - -1. Apply the yaml file using `kubectl`: - - ```bash - kubectl -n default apply -f gitlabsource.yaml + EOF + kubectl -n default wait --for=condition=Ready gitlabsource/gitlabsource-sample --timeout=120s ``` ### Verify @@ -157,14 +155,21 @@ Verify that GitLab webhook was created by looking at the list of webhooks under that points to your Knative cluster. Create a push event and check the logs of the Pod backing the -`gitlab-event-display` knative service. You will see the event: +`gitlab-event-display` Knative service: + +```bash +kubectl -n default logs -l serving.knative.dev/service=gitlab-event-display -c user-container --tail=100 +``` + +With current GitLab source releases, you will see a CloudEvent similar to the +following (the ID, timestamp, source, and payload will vary). Older releases may +emit CloudEvents 0.3 with type `dev.knative.sources.gitlabsource.Push Hook`. ``` ☁️ cloudevents.Event -Validation: valid Context Attributes, - specversion: 0.3 - type: dev.knative.sources.gitlabsource.Push Hook + specversion: 1.0 + type: dev.knative.sources.gitlab.push source: https://gitlab.com// id: f83c080f-c2af-48ff-8d8b-fd5b21c5938e time: 2020-03-12T11:08:41.414572482Z @@ -180,13 +185,13 @@ Data, You can remove the GitLab webhook by deleting the GitLab source: ```bash -kubectl --namespace default delete --filename gitlabsource.yaml +kubectl --namespace default delete gitlabsource gitlabsource-sample ``` Similarly, you can remove the Service and Secret via: ```bash -kubectl --namespace default delete --filename event-display.yaml -kubectl --namespace default delete --filename secret.yaml +kubectl --namespace default delete kservice gitlab-event-display +kubectl --namespace default delete secret gitlabsecret ``` From 24c54ca13f0df75b5cf48de6451553b5524beb96 Mon Sep 17 00:00:00 2001 From: Dave Protasowski Date: Mon, 26 Oct 2026 09:16:04 -0400 Subject: [PATCH 15/44] Consolidate the Go CloudEvents and Eventing samples --- code-samples/eventing/helloworld/README.md | 3 + .../helloworld/helloworld-go/Dockerfile | 31 -- .../helloworld/helloworld-go/README.md | 481 ------------------ .../helloworld/helloworld-go/eventschemas.go | 13 - .../eventing/helloworld/helloworld-go/go.mod | 17 - .../eventing/helloworld/helloworld-go/go.sum | 38 -- .../helloworld/helloworld-go/helloworld.go | 45 -- .../helloworld/helloworld-go/sample-app.yaml | 70 --- .../github.com/cloudevents/sdk-go/v2/LICENSE | 201 -------- .../github.com/google/uuid/LICENSE | 27 - .../hashicorp/golang-lru/simplelru/LICENSE | 362 ------------- .../golang-lru/simplelru/simplelru/lru.go | 161 ------ .../simplelru/simplelru/lru_interface.go | 36 -- .../lightstep/tracecontext.go/LICENSE | 21 - .../github.com/pkg/errors/LICENSE | 23 - .../VENDOR-LICENSE/go.opencensus.io/LICENSE | 202 -------- .../go.uber.org/atomic/LICENSE.txt | 19 - .../go.uber.org/multierr/LICENSE.txt | 19 - .../go.uber.org/zap/LICENSE.txt | 19 - .../cloudevents/cloudevents-go/Dockerfile | 13 +- .../cloudevents/cloudevents-go/README.md | 101 ++-- .../serving/cloudevents/cloudevents-go/ci.sh | 43 ++ .../cloudevents/cloudevents-go/cloudevents.go | 11 +- .../cloudevents-go/eventing/README.md | 156 ++++++ .../eventing/event-display.yaml | 39 ++ .../cloudevents-go/eventing/logger/Dockerfile | 7 + .../cloudevents-go/eventing/logger/capture.go | 34 ++ .../eventing/reply-trigger.yaml | 15 + .../cloudevents-go/eventing/sample-app.yaml | 62 +++ .../serving/cloudevents/cloudevents-go/go.mod | 17 +- .../serving/cloudevents/cloudevents-go/go.sum | 47 +- .../cloudevents/cloudevents-go/service.yaml | 2 +- 32 files changed, 450 insertions(+), 1885 deletions(-) delete mode 100644 code-samples/eventing/helloworld/helloworld-go/Dockerfile delete mode 100644 code-samples/eventing/helloworld/helloworld-go/README.md delete mode 100644 code-samples/eventing/helloworld/helloworld-go/eventschemas.go delete mode 100644 code-samples/eventing/helloworld/helloworld-go/go.mod delete mode 100644 code-samples/eventing/helloworld/helloworld-go/go.sum delete mode 100644 code-samples/eventing/helloworld/helloworld-go/helloworld.go delete mode 100644 code-samples/eventing/helloworld/helloworld-go/sample-app.yaml delete mode 100644 code-samples/eventing/helloworld/helloworld-go/third_party/VENDOR-LICENSE/github.com/cloudevents/sdk-go/v2/LICENSE delete mode 100644 code-samples/eventing/helloworld/helloworld-go/third_party/VENDOR-LICENSE/github.com/google/uuid/LICENSE delete mode 100644 code-samples/eventing/helloworld/helloworld-go/third_party/VENDOR-LICENSE/github.com/hashicorp/golang-lru/simplelru/LICENSE delete mode 100644 code-samples/eventing/helloworld/helloworld-go/third_party/VENDOR-LICENSE/github.com/hashicorp/golang-lru/simplelru/simplelru/lru.go delete mode 100644 code-samples/eventing/helloworld/helloworld-go/third_party/VENDOR-LICENSE/github.com/hashicorp/golang-lru/simplelru/simplelru/lru_interface.go delete mode 100644 code-samples/eventing/helloworld/helloworld-go/third_party/VENDOR-LICENSE/github.com/lightstep/tracecontext.go/LICENSE delete mode 100644 code-samples/eventing/helloworld/helloworld-go/third_party/VENDOR-LICENSE/github.com/pkg/errors/LICENSE delete mode 100644 code-samples/eventing/helloworld/helloworld-go/third_party/VENDOR-LICENSE/go.opencensus.io/LICENSE delete mode 100644 code-samples/eventing/helloworld/helloworld-go/third_party/VENDOR-LICENSE/go.uber.org/atomic/LICENSE.txt delete mode 100644 code-samples/eventing/helloworld/helloworld-go/third_party/VENDOR-LICENSE/go.uber.org/multierr/LICENSE.txt delete mode 100644 code-samples/eventing/helloworld/helloworld-go/third_party/VENDOR-LICENSE/go.uber.org/zap/LICENSE.txt create mode 100755 code-samples/serving/cloudevents/cloudevents-go/ci.sh create mode 100644 code-samples/serving/cloudevents/cloudevents-go/eventing/README.md create mode 100644 code-samples/serving/cloudevents/cloudevents-go/eventing/event-display.yaml create mode 100644 code-samples/serving/cloudevents/cloudevents-go/eventing/logger/Dockerfile create mode 100644 code-samples/serving/cloudevents/cloudevents-go/eventing/logger/capture.go create mode 100644 code-samples/serving/cloudevents/cloudevents-go/eventing/reply-trigger.yaml create mode 100644 code-samples/serving/cloudevents/cloudevents-go/eventing/sample-app.yaml diff --git a/code-samples/eventing/helloworld/README.md b/code-samples/eventing/helloworld/README.md index af3cb350b1..d82665b5c7 100644 --- a/code-samples/eventing/helloworld/README.md +++ b/code-samples/eventing/helloworld/README.md @@ -4,6 +4,9 @@ Following examples include a simple web app written in the language of your choi use to test knative eventing. It shows how to consume a [CloudEvent](https://cloudevents.io/) in Knative eventing, and optionally how to respond back with another CloudEvent in the HTTP response. +- [Go CloudEvents with a Broker and Triggers](../../serving/cloudevents/cloudevents-go/eventing/README.md). +- [Python Hello World](helloworld-python/README.md). + We will deploy the app as a [Kubernetes Deployment](https://kubernetes.io/docs/concepts/workloads/controllers/deployment/) along with a diff --git a/code-samples/eventing/helloworld/helloworld-go/Dockerfile b/code-samples/eventing/helloworld/helloworld-go/Dockerfile deleted file mode 100644 index 74ad184abe..0000000000 --- a/code-samples/eventing/helloworld/helloworld-go/Dockerfile +++ /dev/null @@ -1,31 +0,0 @@ -# Use the official Golang image to create a build artifact. -# This is based on Debian and sets the GOPATH to /go. -# https://hub.docker.com/_/golang -FROM golang:latest AS builder - -ARG TARGETOS -ARG TARGETARCH - -# Copy local code to the container image. -WORKDIR /app - -# Copy local code to the container image. -COPY . ./ - -# Install dependencies and tidy up the go.mod and go.sum files. -RUN go mod tidy - -# Build the binary. -# -mod=readonly ensures immutable go.mod and go.sum in container builds. -RUN CGO_ENABLED=0 GOOS=linux GOOS=${TARGETOS} GOARCH=${TARGETARCH} go build -mod=readonly -v -o helloworld - -# Use a Docker multi-stage build to create a lean production image. -# https://docs.docker.com/develop/develop-images/multistage-build/#use-multi-stage-builds -FROM alpine:3 -RUN apk add --no-cache ca-certificates - -# Copy the binary to the production image from the builder stage. -COPY --from=builder /app/helloworld /helloworld - -# Run the web service on container startup. -CMD ["/helloworld"] diff --git a/code-samples/eventing/helloworld/helloworld-go/README.md b/code-samples/eventing/helloworld/helloworld-go/README.md deleted file mode 100644 index 1ffc437377..0000000000 --- a/code-samples/eventing/helloworld/helloworld-go/README.md +++ /dev/null @@ -1,481 +0,0 @@ -# Hello World - Golang - -A simple web app written in Go that you can use to test knative eventing. It -shows how to consume a [CloudEvent](https://cloudevents.io/) in Knative -eventing, and optionally how to respond back with another CloudEvent in the http -response, using the -[Go SDK for CloudEvents](https://github.com/cloudevents/sdk-go) - -We will deploy the app as a -[Kubernetes Deployment](https://kubernetes.io/docs/concepts/workloads/controllers/deployment/) -along with a -[Kubernetes Service](https://kubernetes.io/docs/concepts/services-networking/service/). -However, you can also deploy the app as a -[Knative Serving Service](https://knative.dev/docs/serving/). - -Do the following steps to create the sample code and then deploy the app to your -cluster. You can also download a working copy of the sample, by running the -following commands: - -```bash -git clone https://github.com/knative/docs.git knative-docs -cd knative-docs/code-samples/eventing/helloworld/helloworld-go -``` - -## Before you begin - -- A Kubernetes cluster with [Knative Eventing](https://knative.dev/docs/install/eventing/install-eventing-with-yaml) installed. -- [Docker](https://www.docker.com) installed and running on your local machine, - and a Docker Hub account configured (we'll use it for a container registry). - -## Recreating the sample code - -1. Create a new file named `helloworld.go` and paste the following code. This - code creates a basic web server which listens on port 8080: - - ```go - package main - - import ( - "context" - "log" - - cloudevents "github.com/cloudevents/sdk-go/v2" - "github.com/google/uuid" - ) - - func receive(ctx context.Context, event cloudevents.Event) (*cloudevents.Event, cloudevents.Result) { - // Here is where your code to process the event will go. - // In this example we will log the event msg - log.Printf("Event received. \n%s\n", event) - data := &HelloWorld{} - if err := event.DataAs(data); err != nil { - log.Printf("Error while extracting cloudevent Data: %s\n", err.Error()) - return nil, cloudevents.NewHTTPResult(400, "failed to convert data: %s", err) - } - log.Printf("Hello World Message from received event %q", data.Msg) - - // Respond with another event (optional) - // This is optional and is intended to show how to respond back with another event after processing. - // The response will go back into the knative eventing system just like any other event - newEvent := cloudevents.NewEvent() - // Setting the ID here is not necessary. When using NewDefaultClient the ID is set - // automatically. We set the ID anyway so it appears in the log. - newEvent.SetID(uuid.New().String()) - newEvent.SetSource("knative/eventing/samples/hello-world") - newEvent.SetType("dev.knative.samples.hifromknative") - if err := newEvent.SetData(cloudevents.ApplicationJSON, HiFromKnative{Msg: "Hi from helloworld-go app!"}); err != nil { - return nil, cloudevents.NewHTTPResult(500, "failed to set response data: %s", err) - } - log.Printf("Responding with event\n%s\n", newEvent) - return &newEvent, nil - } - - func main() { - log.Print("Hello world sample started.") - c, err := cloudevents.NewDefaultClient() - if err != nil { - log.Fatalf("failed to create client, %v", err) - } - log.Fatal(c.StartReceiver(context.Background(), receive)) - } - ``` - -1. Create a new file named `eventschemas.go` and paste the following code. This - defines the data schema of the CloudEvents. - - ```go - package main - - // HelloWorld defines the Data of CloudEvent with type=dev.knative.samples.helloworld - type HelloWorld struct { - // Msg holds the message from the event - Msg string `json:"msg,omitempty"` - } - - // HiFromKnative defines the Data of CloudEvent with type=dev.knative.samples.hifromknative - type HiFromKnative struct { - // Msg holds the message from the event - Msg string `json:"msg,omitempty"` - } - ``` - -1. In your project directory, create a file named `Dockerfile` and copy the following code - block into it. For detailed instructions on dockerizing a Go app, see - [Deploying Go servers with Docker](https://blog.golang.org/docker). - - ```dockerfile - # Use the official Golang image to create a build artifact. - # This is based on Debian and sets the GOPATH to /go. - # https://hub.docker.com/_/golang - FROM golang:1.14 as builder - - ARG TARGETOS - ARG TARGETARCH - - # Copy local code to the container image. - WORKDIR /app - - # Retrieve application dependencies using go modules. - # Allows container builds to reuse downloaded dependencies. - COPY go.* ./ - RUN go mod download - - # Copy local code to the container image. - COPY . ./ - - # Build the binary. - # -mod=readonly ensures immutable go.mod and go.sum in container builds. - RUN CGO_ENABLED=0 GOOS=linux GOOS=${TARGETOS} GOARCH=${TARGETARCH} go build -mod=readonly -v -o helloworld - - # Use a Docker multi-stage build to create a lean production image. - # https://docs.docker.com/develop/develop-images/multistage-build/#use-multi-stage-builds - FROM alpine:3 - RUN apk add --no-cache ca-certificates - - # Copy the binary to the production image from the builder stage. - COPY --from=builder /app/helloworld /helloworld - - # Run the web service on container startup. - CMD ["/helloworld"] - ``` - -1. Create a new file, `sample-app.yaml` and copy the following service - definition into the file. Make sure to replace `{username}` with your Docker - Hub username. - - ```yaml - # Namespace for sample application - apiVersion: v1 - kind: Namespace - metadata: - name: knative-samples - --- - # A default broker - apiVersion: eventing.knative.dev/v1 - kind: Broker - metadata: - name: default - namespace: knative-samples - annotations: - # Note: you can set the eventing.knative.dev/broker.class annotation to change the class of the broker. - # The default broker class is MTChannelBasedBroker, but Knative also supports use of the other class. - eventing.knative.dev/broker.class: MTChannelBasedBroker - spec: {} - --- - # Helloworld-go app deployment - apiVersion: apps/v1 - kind: Deployment - metadata: - name: helloworld-go - namespace: knative-samples - spec: - replicas: 1 - selector: - matchLabels: &labels - app: helloworld-go - template: - metadata: - labels: *labels - spec: - containers: - - name: helloworld-go - image: docker.io/{username}/helloworld-go - - --- - # Service that exposes helloworld-go app. - # This will be the subscriber for the Trigger - kind: Service - apiVersion: v1 - metadata: - name: helloworld-go - namespace: knative-samples - spec: - selector: - app: helloworld-go - ports: - - protocol: TCP - port: 80 - targetPort: 8080 - --- - # Knative Eventing Trigger to trigger the helloworld-go service - apiVersion: eventing.knative.dev/v1 - kind: Trigger - metadata: - name: helloworld-go - namespace: knative-samples - spec: - broker: default - filter: - attributes: - type: dev.knative.samples.helloworld - source: dev.knative.samples/helloworldsource - subscriber: - ref: - apiVersion: v1 - kind: Service - name: helloworld-go - ``` - -1. Use the go tool to create a - [`go.mod`](https://github.com/golang/go/wiki/Modules#gomod) manifest. - - ```bash - go mod init github.com/knative/docs/code-samples/serving/hello-world/helloworld-go - ``` - -## Building and deploying the sample - -Once you have recreated the sample code files (or used the files in the sample -folder) you're ready to build and deploy the sample app. - -1. Use Docker to build the sample code into a container. To build and push with - Docker Hub, run these commands replacing `{username}` with your Docker Hub - username: - - ```bash - # Build and push the container on your local machine. - docker buildx build --platform linux/arm64,linux/amd64 -t "{username}/helloworld-go" --push . - ``` - -1. After the build has completed and the container is pushed to docker hub, you - can deploy the sample application into your cluster. Ensure that the - container image value in `sample-app.yaml` matches the container you built in - the previous step. Apply the configuration by running the following command: - - ```bash - kubectl apply --filename sample-app.yaml - ``` - -1. Verify that the command created the namespace `knative-samples` and a default Broker by running the command: - - ```bash - kubectl get broker --namespace knative-samples - ``` - - **Note:** you can also use injection based on labels with the - Eventing sugar controller. - For how to install the Eventing sugar controller, see - [Install optional Eventing extensions](https://knative.dev/docs/install/eventing/install-eventing-with-yaml/#install-optional-eventing-extensions). - -1. It deployed the helloworld-go app as a K8s Deployment and created a K8s - service names helloworld-go. Verify using the following command. - - ```bash - kubectl --namespace knative-samples get deployments helloworld-go - - kubectl --namespace knative-samples get svc helloworld-go - ``` - -1. It created a Knative Eventing Trigger to route certain events to the - helloworld-go application. Make sure that Ready=true - - ```bash - kubectl --namespace knative-samples get trigger helloworld-go - ``` - -## Send and verify CloudEvents - -Once you have deployed the application and verified that the namespace, sample -application and trigger are ready, let's send a CloudEvent. - -### Send CloudEvent to the Broker - -We can send an http request directly to the [Broker](https://knative.dev/docs/eventing/broker/) -with correct CloudEvent headers set. - -1. Deploy a curl pod and SSH into it - - ```bash - kubectl -n knative-samples run curl --image=radial/busyboxplus:curl -it - ``` - -1. Get the Broker URL - - ```bash - kubectl -n knative-samples get broker default - ``` - -1. Run the following in the SSH terminal. Please replace the URL with the URL of - the default broker. - - ```bash - curl -v "http://broker-ingress.knative-eventing.svc.cluster.local/knative-samples/default" \ - -X POST \ - -H "Ce-Id: 536808d3-88be-4077-9d7a-a3f162705f79" \ - -H "Ce-Specversion: 1.0" \ - -H "Ce-Type: dev.knative.samples.helloworld" \ - -H "Ce-Source: dev.knative.samples/helloworldsource" \ - -H "Content-Type: application/json" \ - -d '{"msg":"Hello World from the curl pod."}' - exit - ``` - -### Verify that event is received by helloworld-go app - -The Helloworld-go app logs the context and the msg of the event you created earlier, and replies -with another event. - -1. Display helloworld-go app logs - - ```bash - kubectl --namespace knative-samples logs -l app=helloworld-go --tail=50 - ``` - - You should see something similar to: - - ```{ .bash .no-copy } - Event received. - Validation: valid - Context Attributes, - specversion: 1.0 - type: dev.knative.samples.helloworld - source: dev.knative.samples/helloworldsource - id: 536808d3-88be-4077-9d7a-a3f162705f79 - time: 2019-10-04T22:35:26.05871736Z - datacontenttype: application/json - Extensions, - knativearrivaltime: 2019-10-04T22:35:26Z - knativehistory: default-kn2-trigger-kn-channel.knative-samples.svc.cluster.local - traceparent: 00-971d4644229653483d38c46e92a959c7-92c66312e4bb39be-00 - Data, - {"msg":"Hello World from the curl pod."} - - Hello World Message "Hello World from the curl pod." - Responded with event - Validation: valid - Context Attributes, - specversion: 1.0 - type: dev.knative.samples.hifromknative - source: knative/eventing/samples/hello-world - id: 37458d77-01f5-411e-a243-a459bbf79682 - datacontenttype: application/json - Data, - {"msg":"Hi from Knative!"} - - ``` - -Play around with the CloudEvent attributes in the curl command and the trigger specification to [understand how triggers work](https://knative.dev/docs/eventing/broker/triggers/). - -## Verify reply from helloworld-go app - -`helloworld-go` app replies back with an event of -`type= dev.knative.samples.hifromknative`, and -`source=knative/eventing/samples/hello-world`. This event enters the eventing -mesh via the Broker and can be delivered to other services using a Trigger - -1. Using the following example, create a YAML file for a Pod that receives any - CloudEvent and logs the event to its output: - - ```yaml - # event-display app deployment - apiVersion: apps/v1 - kind: Deployment - metadata: - name: event-display - namespace: knative-samples - spec: - replicas: 1 - selector: - matchLabels: &labels - app: event-display - template: - metadata: - labels: *labels - spec: - containers: - - name: helloworld-go - # Source code: https://github.com/knative/eventing/tree/main/cmd/event_display - image: gcr.io/knative-releases/knative.dev/eventing/cmd/event_display - --- - # Service that exposes event-display app. - # This will be the subscriber for the Trigger - kind: Service - apiVersion: v1 - metadata: - name: event-display - namespace: knative-samples - spec: - selector: - app: event-display - ports: - - protocol: TCP - port: 80 - targetPort: 8080 - ``` - -1. Apply the YAML file by running the command: - - ```bash - kubectl apply -f .yaml - ``` - - Where `` is the name of the file you created in the previous step. - -1. Using the following example, create a YAML file for a trigger to deliver the event - to the `event-display` Service: - - ```yaml - apiVersion: eventing.knative.dev/v1 - kind: Trigger - metadata: - name: event-display - namespace: knative-samples - spec: - broker: default - filter: - attributes: - type: dev.knative.samples.hifromknative - source: knative/eventing/samples/hello-world - subscriber: - ref: - apiVersion: v1 - kind: Service - name: event-display - ``` - -1. Apply the YAML file by running the command: - - ```bash - kubectl apply -f .yaml - ``` - - Where `` is the name of the file you created in the previous step. - -1. [Send a CloudEvent to the Broker](#send-and-verify-cloudevents) - -1. Check the logs of `event-display` service: - - ```bash - kubectl -n knative-samples logs -l app=event-display --tail=50 - ``` - - You should see something similar to: - - ```{ .bash .no-copy } - cloudevents.Event - Validation: valid - Context Attributes, - specversion: 1.0 - type: dev.knative.samples.hifromknative - source: knative/eventing/samples/hello-world - id: 8a7384b9-8bbe-4634-bf0f-ead07e450b2a - time: 2019-10-04T22:53:39.844943931Z - datacontenttype: application/json - Extensions, - knativearrivaltime: 2019-10-04T22:53:39Z - knativehistory: default-kn2-ingress-kn-channel.knative- samples.svc.cluster.local - traceparent: 00-4b01db030b9ea04bb150b77c8fa86509-2740816590a7604f-00 - Data, - { - "msg": "Hi from helloworld-go app!" - } - ``` - -## Removing the sample app deployment - -To remove the sample app from your cluster, delete the service record: - -```bash -kubectl delete -f sample-app.yaml -``` diff --git a/code-samples/eventing/helloworld/helloworld-go/eventschemas.go b/code-samples/eventing/helloworld/helloworld-go/eventschemas.go deleted file mode 100644 index 531f28b4a8..0000000000 --- a/code-samples/eventing/helloworld/helloworld-go/eventschemas.go +++ /dev/null @@ -1,13 +0,0 @@ -package main - -// HelloWorld defines the Data of CloudEvent with type=dev.knative.samples.helloworld -type HelloWorld struct { - // Msg holds the message from the event - Msg string `json:"msg,omitempty"` -} - -// HiFromKnative defines the Data of CloudEvent with type=dev.knative.samples.hifromknative -type HiFromKnative struct { - // Msg holds the message from the event - Msg string `json:"msg,omitempty"` -} diff --git a/code-samples/eventing/helloworld/helloworld-go/go.mod b/code-samples/eventing/helloworld/helloworld-go/go.mod deleted file mode 100644 index 67694fcb42..0000000000 --- a/code-samples/eventing/helloworld/helloworld-go/go.mod +++ /dev/null @@ -1,17 +0,0 @@ -module knative.dev/docs/code-samples/eventing/helloworld/helloworld-go - -go 1.22 - -require ( - github.com/cloudevents/sdk-go/v2 v2.15.2 - github.com/google/uuid v1.6.0 -) - -require ( - github.com/json-iterator/go v1.1.10 // indirect - github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421 // indirect - github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742 // indirect - go.uber.org/atomic v1.4.0 // indirect - go.uber.org/multierr v1.1.0 // indirect - go.uber.org/zap v1.10.0 // indirect -) diff --git a/code-samples/eventing/helloworld/helloworld-go/go.sum b/code-samples/eventing/helloworld/helloworld-go/go.sum deleted file mode 100644 index df533a5b5a..0000000000 --- a/code-samples/eventing/helloworld/helloworld-go/go.sum +++ /dev/null @@ -1,38 +0,0 @@ -github.com/cloudevents/sdk-go/v2 v2.15.2 h1:54+I5xQEnI73RBhWHxbI1XJcqOFOVJN85vb41+8mHUc= -github.com/cloudevents/sdk-go/v2 v2.15.2/go.mod h1:lL7kSWAE/V8VI4Wh0jbL2v/jvqsm6tjmaQBSvxcv4uE= -github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= -github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/google/go-cmp v0.5.0 h1:/QaMHBdZ26BB3SSst0Iwl10Epc+xhTquomWX0oZEB6w= -github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= -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/json-iterator/go v1.1.10 h1:Kz6Cvnvv2wGdaG/V8yMvfkmNiXq9Ya2KUv4rouJJr68= -github.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= -github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421 h1:ZqeYNhU3OHLH3mGKHDcjJRFFRrJa6eAM5H+CtDdOsPc= -github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= -github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742 h1:Esafd1046DLDQ0W1YjYsBW+p8U2u7vzgW2SQVmlNazg= -github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= -github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= -github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= -github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= -github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= -github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= -github.com/stretchr/testify v1.8.0 h1:pSgiaMZlXftHpm5L7V1+rVB+AZJydKsMxsQBIJw4PKk= -github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= -github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw= -github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc= -go.uber.org/atomic v1.4.0 h1:cxzIVoETapQEqDhQu3QfnvXAV4AlzcvUCxkVUFw3+EU= -go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= -go.uber.org/multierr v1.1.0 h1:HoEmRHQPVSqub6w2z2d2EOVs2fjyFRGyofhKuyDq0QI= -go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0= -go.uber.org/zap v1.10.0 h1:ORx85nbTijNz8ljznvCMR1ZBIPKFn3jQrag10X2AsuM= -go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= -golang.org/x/time v0.0.0-20210723032227-1f47c861a9ac h1:7zkz7BUtwNFFqcowJ+RIgu2MaV/MapERkDIy+mwPyjs= -golang.org/x/time v0.0.0-20210723032227-1f47c861a9ac/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= -golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4= -golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= -gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/code-samples/eventing/helloworld/helloworld-go/helloworld.go b/code-samples/eventing/helloworld/helloworld-go/helloworld.go deleted file mode 100644 index 467b3bcc6b..0000000000 --- a/code-samples/eventing/helloworld/helloworld-go/helloworld.go +++ /dev/null @@ -1,45 +0,0 @@ -package main - -import ( - "context" - "log" - - cloudevents "github.com/cloudevents/sdk-go/v2" - "github.com/google/uuid" -) - -func receive(ctx context.Context, event cloudevents.Event) (*cloudevents.Event, cloudevents.Result) { - // Here is where your code to process the event will go. - // In this example we will log the event msg - log.Printf("Event received. \n%s\n", event) - data := &HelloWorld{} - if err := event.DataAs(data); err != nil { - log.Printf("Error while extracting cloudevent Data: %s\n", err.Error()) - return nil, cloudevents.NewHTTPResult(400, "failed to convert data: %s", err) - } - log.Printf("Hello World Message from received event %q", data.Msg) - - // Respond with another event (optional) - // This is optional and is intended to show how to respond back with another event after processing. - // The response will go back into the knative eventing system just like any other event - newEvent := cloudevents.NewEvent() - // Setting the ID here is not necessary. When using NewDefaultClient the ID is set - // automatically. We set the ID anyway so it appears in the log. - newEvent.SetID(uuid.New().String()) - newEvent.SetSource("knative/eventing/samples/hello-world") - newEvent.SetType("dev.knative.samples.hifromknative") - if err := newEvent.SetData(cloudevents.ApplicationJSON, HiFromKnative{Msg: "Hi from helloworld-go app!"}); err != nil { - return nil, cloudevents.NewHTTPResult(500, "failed to set response data: %s", err) - } - log.Printf("Responding with event\n%s\n", newEvent) - return &newEvent, nil -} - -func main() { - log.Print("Hello world sample started.") - c, err := cloudevents.NewDefaultClient() - if err != nil { - log.Fatalf("failed to create client, %v", err) - } - log.Fatal(c.StartReceiver(context.Background(), receive)) -} diff --git a/code-samples/eventing/helloworld/helloworld-go/sample-app.yaml b/code-samples/eventing/helloworld/helloworld-go/sample-app.yaml deleted file mode 100644 index 542642ebe1..0000000000 --- a/code-samples/eventing/helloworld/helloworld-go/sample-app.yaml +++ /dev/null @@ -1,70 +0,0 @@ -# Namespace for sample application -apiVersion: v1 -kind: Namespace -metadata: - name: knative-samples ---- -# A default broker -apiVersion: eventing.knative.dev/v1 -kind: Broker -metadata: - name: default - namespace: knative-samples - annotations: - # Note: you can set the eventing.knative.dev/broker.class annotation to change the class of the broker. - # The default broker class is MTChannelBasedBroker, but Knative also supports use of the other class. - eventing.knative.dev/broker.class: MTChannelBasedBroker -spec: {} ---- -# Helloworld-go app deployment -apiVersion: apps/v1 -kind: Deployment -metadata: - name: helloworld-go - namespace: knative-samples -spec: - replicas: 1 - selector: - matchLabels: &labels - app: helloworld-go - template: - metadata: - labels: *labels - spec: - containers: - - name: helloworld-go - image: docker.io/{username}/helloworld-go ---- -# Service that exposes helloworld-go app. -# This will be the subscriber for the Trigger -apiVersion: v1 -kind: Service -metadata: - name: helloworld-go - namespace: knative-samples -spec: - selector: - app: helloworld-go - ports: - - protocol: TCP - port: 80 - targetPort: 8080 ---- -# Knative Eventing Trigger to trigger the helloworld-go service -apiVersion: eventing.knative.dev/v1 -kind: Trigger -metadata: - name: helloworld-go - namespace: knative-samples -spec: - broker: default - filter: - attributes: - type: dev.knative.samples.helloworld - source: dev.knative.samples/helloworldsource - subscriber: - ref: - apiVersion: v1 - kind: Service - name: helloworld-go - diff --git a/code-samples/eventing/helloworld/helloworld-go/third_party/VENDOR-LICENSE/github.com/cloudevents/sdk-go/v2/LICENSE b/code-samples/eventing/helloworld/helloworld-go/third_party/VENDOR-LICENSE/github.com/cloudevents/sdk-go/v2/LICENSE deleted file mode 100644 index 261eeb9e9f..0000000000 --- a/code-samples/eventing/helloworld/helloworld-go/third_party/VENDOR-LICENSE/github.com/cloudevents/sdk-go/v2/LICENSE +++ /dev/null @@ -1,201 +0,0 @@ - Apache License - Version 2.0, January 2004 - http://www.apache.org/licenses/ - - TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION - - 1. Definitions. - - "License" shall mean the terms and conditions for use, reproduction, - and distribution as defined by Sections 1 through 9 of this document. - - "Licensor" shall mean the copyright owner or entity authorized by - the copyright owner that is granting the License. - - "Legal Entity" shall mean the union of the acting entity and all - other entities that control, are controlled by, or are under common - control with that entity. For the purposes of this definition, - "control" means (i) the power, direct or indirect, to cause the - direction or management of such entity, whether by contract or - otherwise, or (ii) ownership of fifty percent (50%) or more of the - outstanding shares, or (iii) beneficial ownership of such entity. - - "You" (or "Your") shall mean an individual or Legal Entity - exercising permissions granted by this License. - - "Source" form shall mean the preferred form for making modifications, - including but not limited to software source code, documentation - source, and configuration files. - - "Object" form shall mean any form resulting from mechanical - transformation or translation of a Source form, including but - not limited to compiled object code, generated documentation, - and conversions to other media types. - - "Work" shall mean the work of authorship, whether in Source or - Object form, made available under the License, as indicated by a - copyright notice that is included in or attached to the work - (an example is provided in the Appendix below). - - "Derivative Works" shall mean any work, whether in Source or Object - form, that is based on (or derived from) the Work and for which the - editorial revisions, annotations, elaborations, or other modifications - represent, as a whole, an original work of authorship. For the purposes - of this License, Derivative Works shall not include works that remain - separable from, or merely link (or bind by name) to the interfaces of, - the Work and Derivative Works thereof. - - "Contribution" shall mean any work of authorship, including - the original version of the Work and any modifications or additions - to that Work or Derivative Works thereof, that is intentionally - submitted to Licensor for inclusion in the Work by the copyright owner - or by an individual or Legal Entity authorized to submit on behalf of - the copyright owner. For the purposes of this definition, "submitted" - means any form of electronic, verbal, or written communication sent - to the Licensor or its representatives, including but not limited to - communication on electronic mailing lists, source code control systems, - and issue tracking systems that are managed by, or on behalf of, the - Licensor for the purpose of discussing and improving the Work, but - excluding communication that is conspicuously marked or otherwise - designated in writing by the copyright owner as "Not a Contribution." - - "Contributor" shall mean Licensor and any individual or Legal Entity - on behalf of whom a Contribution has been received by Licensor and - subsequently incorporated within the Work. - - 2. Grant of Copyright License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - copyright license to reproduce, prepare Derivative Works of, - publicly display, publicly perform, sublicense, and distribute the - Work and such Derivative Works in Source or Object form. - - 3. Grant of Patent License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - (except as stated in this section) patent license to make, have made, - use, offer to sell, sell, import, and otherwise transfer the Work, - where such license applies only to those patent claims licensable - by such Contributor that are necessarily infringed by their - Contribution(s) alone or by combination of their Contribution(s) - with the Work to which such Contribution(s) was submitted. If You - institute patent litigation against any entity (including a - cross-claim or counterclaim in a lawsuit) alleging that the Work - or a Contribution incorporated within the Work constitutes direct - or contributory patent infringement, then any patent licenses - granted to You under this License for that Work shall terminate - as of the date such litigation is filed. - - 4. Redistribution. You may reproduce and distribute copies of the - Work or Derivative Works thereof in any medium, with or without - modifications, and in Source or Object form, provided that You - meet the following conditions: - - (a) You must give any other recipients of the Work or - Derivative Works a copy of this License; and - - (b) You must cause any modified files to carry prominent notices - stating that You changed the files; and - - (c) You must retain, in the Source form of any Derivative Works - that You distribute, all copyright, patent, trademark, and - attribution notices from the Source form of the Work, - excluding those notices that do not pertain to any part of - the Derivative Works; and - - (d) If the Work includes a "NOTICE" text file as part of its - distribution, then any Derivative Works that You distribute must - include a readable copy of the attribution notices contained - within such NOTICE file, excluding those notices that do not - pertain to any part of the Derivative Works, in at least one - of the following places: within a NOTICE text file distributed - as part of the Derivative Works; within the Source form or - documentation, if provided along with the Derivative Works; or, - within a display generated by the Derivative Works, if and - wherever such third-party notices normally appear. The contents - of the NOTICE file are for informational purposes only and - do not modify the License. You may add Your own attribution - notices within Derivative Works that You distribute, alongside - or as an addendum to the NOTICE text from the Work, provided - that such additional attribution notices cannot be construed - as modifying the License. - - You may add Your own copyright statement to Your modifications and - may provide additional or different license terms and conditions - for use, reproduction, or distribution of Your modifications, or - for any such Derivative Works as a whole, provided Your use, - reproduction, and distribution of the Work otherwise complies with - the conditions stated in this License. - - 5. Submission of Contributions. Unless You explicitly state otherwise, - any Contribution intentionally submitted for inclusion in the Work - by You to the Licensor shall be under the terms and conditions of - this License, without any additional terms or conditions. - Notwithstanding the above, nothing herein shall supersede or modify - the terms of any separate license agreement you may have executed - with Licensor regarding such Contributions. - - 6. Trademarks. This License does not grant permission to use the trade - names, trademarks, service marks, or product names of the Licensor, - except as required for reasonable and customary use in describing the - origin of the Work and reproducing the content of the NOTICE file. - - 7. Disclaimer of Warranty. Unless required by applicable law or - agreed to in writing, Licensor provides the Work (and each - Contributor provides its Contributions) on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or - implied, including, without limitation, any warranties or conditions - of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A - PARTICULAR PURPOSE. You are solely responsible for determining the - appropriateness of using or redistributing the Work and assume any - risks associated with Your exercise of permissions under this License. - - 8. Limitation of Liability. In no event and under no legal theory, - whether in tort (including negligence), contract, or otherwise, - unless required by applicable law (such as deliberate and grossly - negligent acts) or agreed to in writing, shall any Contributor be - liable to You for damages, including any direct, indirect, special, - incidental, or consequential damages of any character arising as a - result of this License or out of the use or inability to use the - Work (including but not limited to damages for loss of goodwill, - work stoppage, computer failure or malfunction, or any and all - other commercial damages or losses), even if such Contributor - has been advised of the possibility of such damages. - - 9. Accepting Warranty or Additional Liability. While redistributing - the Work or Derivative Works thereof, You may choose to offer, - and charge a fee for, acceptance of support, warranty, indemnity, - or other liability obligations and/or rights consistent with this - License. However, in accepting such obligations, You may act only - on Your own behalf and on Your sole responsibility, not on behalf - of any other Contributor, and only if You agree to indemnify, - defend, and hold each Contributor harmless for any liability - incurred by, or claims asserted against, such Contributor by reason - of your accepting any such warranty or additional liability. - - END OF TERMS AND CONDITIONS - - APPENDIX: How to apply the Apache License to your work. - - To apply the Apache License to your work, attach the following - boilerplate notice, with the fields enclosed by brackets "[]" - replaced with your own identifying information. (Don't include - the brackets!) The text should be enclosed in the appropriate - comment syntax for the file format. We also recommend that a - file or class name and description of purpose be included on the - same "printed page" as the copyright notice for easier - identification within third-party archives. - - Copyright [yyyy] [name of copyright owner] - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. diff --git a/code-samples/eventing/helloworld/helloworld-go/third_party/VENDOR-LICENSE/github.com/google/uuid/LICENSE b/code-samples/eventing/helloworld/helloworld-go/third_party/VENDOR-LICENSE/github.com/google/uuid/LICENSE deleted file mode 100644 index 5dc68268d9..0000000000 --- a/code-samples/eventing/helloworld/helloworld-go/third_party/VENDOR-LICENSE/github.com/google/uuid/LICENSE +++ /dev/null @@ -1,27 +0,0 @@ -Copyright (c) 2009,2014 Google Inc. All rights reserved. - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions are -met: - - * Redistributions of source code must retain the above copyright -notice, this list of conditions and the following disclaimer. - * Redistributions in binary form must reproduce the above -copyright notice, this list of conditions and the following disclaimer -in the documentation and/or other materials provided with the -distribution. - * Neither the name of Google Inc. nor the names of its -contributors may be used to endorse or promote products derived from -this software without specific prior written permission. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR -A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT -OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/code-samples/eventing/helloworld/helloworld-go/third_party/VENDOR-LICENSE/github.com/hashicorp/golang-lru/simplelru/LICENSE b/code-samples/eventing/helloworld/helloworld-go/third_party/VENDOR-LICENSE/github.com/hashicorp/golang-lru/simplelru/LICENSE deleted file mode 100644 index be2cc4dfb6..0000000000 --- a/code-samples/eventing/helloworld/helloworld-go/third_party/VENDOR-LICENSE/github.com/hashicorp/golang-lru/simplelru/LICENSE +++ /dev/null @@ -1,362 +0,0 @@ -Mozilla Public License, version 2.0 - -1. Definitions - -1.1. "Contributor" - - means each individual or legal entity that creates, contributes to the - creation of, or owns Covered Software. - -1.2. "Contributor Version" - - means the combination of the Contributions of others (if any) used by a - Contributor and that particular Contributor's Contribution. - -1.3. "Contribution" - - means Covered Software of a particular Contributor. - -1.4. "Covered Software" - - means Source Code Form to which the initial Contributor has attached the - notice in Exhibit A, the Executable Form of such Source Code Form, and - Modifications of such Source Code Form, in each case including portions - thereof. - -1.5. "Incompatible With Secondary Licenses" - means - - a. that the initial Contributor has attached the notice described in - Exhibit B to the Covered Software; or - - b. that the Covered Software was made available under the terms of - version 1.1 or earlier of the License, but not also under the terms of - a Secondary License. - -1.6. "Executable Form" - - means any form of the work other than Source Code Form. - -1.7. "Larger Work" - - means a work that combines Covered Software with other material, in a - separate file or files, that is not Covered Software. - -1.8. "License" - - means this document. - -1.9. "Licensable" - - means having the right to grant, to the maximum extent possible, whether - at the time of the initial grant or subsequently, any and all of the - rights conveyed by this License. - -1.10. "Modifications" - - means any of the following: - - a. any file in Source Code Form that results from an addition to, - deletion from, or modification of the contents of Covered Software; or - - b. any new file in Source Code Form that contains any Covered Software. - -1.11. "Patent Claims" of a Contributor - - means any patent claim(s), including without limitation, method, - process, and apparatus claims, in any patent Licensable by such - Contributor that would be infringed, but for the grant of the License, - by the making, using, selling, offering for sale, having made, import, - or transfer of either its Contributions or its Contributor Version. - -1.12. "Secondary License" - - means either the GNU General Public License, Version 2.0, the GNU Lesser - General Public License, Version 2.1, the GNU Affero General Public - License, Version 3.0, or any later versions of those licenses. - -1.13. "Source Code Form" - - means the form of the work preferred for making modifications. - -1.14. "You" (or "Your") - - means an individual or a legal entity exercising rights under this - License. For legal entities, "You" includes any entity that controls, is - controlled by, or is under common control with You. For purposes of this - definition, "control" means (a) the power, direct or indirect, to cause - the direction or management of such entity, whether by contract or - otherwise, or (b) ownership of more than fifty percent (50%) of the - outstanding shares or beneficial ownership of such entity. - - -2. License Grants and Conditions - -2.1. Grants - - Each Contributor hereby grants You a world-wide, royalty-free, - non-exclusive license: - - a. under intellectual property rights (other than patent or trademark) - Licensable by such Contributor to use, reproduce, make available, - modify, display, perform, distribute, and otherwise exploit its - Contributions, either on an unmodified basis, with Modifications, or - as part of a Larger Work; and - - b. under Patent Claims of such Contributor to make, use, sell, offer for - sale, have made, import, and otherwise transfer either its - Contributions or its Contributor Version. - -2.2. Effective Date - - The licenses granted in Section 2.1 with respect to any Contribution - become effective for each Contribution on the date the Contributor first - distributes such Contribution. - -2.3. Limitations on Grant Scope - - The licenses granted in this Section 2 are the only rights granted under - this License. No additional rights or licenses will be implied from the - distribution or licensing of Covered Software under this License. - Notwithstanding Section 2.1(b) above, no patent license is granted by a - Contributor: - - a. for any code that a Contributor has removed from Covered Software; or - - b. for infringements caused by: (i) Your and any other third party's - modifications of Covered Software, or (ii) the combination of its - Contributions with other software (except as part of its Contributor - Version); or - - c. under Patent Claims infringed by Covered Software in the absence of - its Contributions. - - This License does not grant any rights in the trademarks, service marks, - or logos of any Contributor (except as may be necessary to comply with - the notice requirements in Section 3.4). - -2.4. Subsequent Licenses - - No Contributor makes additional grants as a result of Your choice to - distribute the Covered Software under a subsequent version of this - License (see Section 10.2) or under the terms of a Secondary License (if - permitted under the terms of Section 3.3). - -2.5. Representation - - Each Contributor represents that the Contributor believes its - Contributions are its original creation(s) or it has sufficient rights to - grant the rights to its Contributions conveyed by this License. - -2.6. Fair Use - - This License is not intended to limit any rights You have under - applicable copyright doctrines of fair use, fair dealing, or other - equivalents. - -2.7. Conditions - - Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted in - Section 2.1. - - -3. Responsibilities - -3.1. Distribution of Source Form - - All distribution of Covered Software in Source Code Form, including any - Modifications that You create or to which You contribute, must be under - the terms of this License. You must inform recipients that the Source - Code Form of the Covered Software is governed by the terms of this - License, and how they can obtain a copy of this License. You may not - attempt to alter or restrict the recipients' rights in the Source Code - Form. - -3.2. Distribution of Executable Form - - If You distribute Covered Software in Executable Form then: - - a. such Covered Software must also be made available in Source Code Form, - as described in Section 3.1, and You must inform recipients of the - Executable Form how they can obtain a copy of such Source Code Form by - reasonable means in a timely manner, at a charge no more than the cost - of distribution to the recipient; and - - b. You may distribute such Executable Form under the terms of this - License, or sublicense it under different terms, provided that the - license for the Executable Form does not attempt to limit or alter the - recipients' rights in the Source Code Form under this License. - -3.3. Distribution of a Larger Work - - You may create and distribute a Larger Work under terms of Your choice, - provided that You also comply with the requirements of this License for - the Covered Software. If the Larger Work is a combination of Covered - Software with a work governed by one or more Secondary Licenses, and the - Covered Software is not Incompatible With Secondary Licenses, this - License permits You to additionally distribute such Covered Software - under the terms of such Secondary License(s), so that the recipient of - the Larger Work may, at their option, further distribute the Covered - Software under the terms of either this License or such Secondary - License(s). - -3.4. Notices - - You may not remove or alter the substance of any license notices - (including copyright notices, patent notices, disclaimers of warranty, or - limitations of liability) contained within the Source Code Form of the - Covered Software, except that You may alter any license notices to the - extent required to remedy known factual inaccuracies. - -3.5. Application of Additional Terms - - You may choose to offer, and to charge a fee for, warranty, support, - indemnity or liability obligations to one or more recipients of Covered - Software. However, You may do so only on Your own behalf, and not on - behalf of any Contributor. You must make it absolutely clear that any - such warranty, support, indemnity, or liability obligation is offered by - You alone, and You hereby agree to indemnify every Contributor for any - liability incurred by such Contributor as a result of warranty, support, - indemnity or liability terms You offer. You may include additional - disclaimers of warranty and limitations of liability specific to any - jurisdiction. - -4. Inability to Comply Due to Statute or Regulation - - If it is impossible for You to comply with any of the terms of this License - with respect to some or all of the Covered Software due to statute, - judicial order, or regulation then You must: (a) comply with the terms of - this License to the maximum extent possible; and (b) describe the - limitations and the code they affect. Such description must be placed in a - text file included with all distributions of the Covered Software under - this License. Except to the extent prohibited by statute or regulation, - such description must be sufficiently detailed for a recipient of ordinary - skill to be able to understand it. - -5. Termination - -5.1. The rights granted under this License will terminate automatically if You - fail to comply with any of its terms. However, if You become compliant, - then the rights granted under this License from a particular Contributor - are reinstated (a) provisionally, unless and until such Contributor - explicitly and finally terminates Your grants, and (b) on an ongoing - basis, if such Contributor fails to notify You of the non-compliance by - some reasonable means prior to 60 days after You have come back into - compliance. Moreover, Your grants from a particular Contributor are - reinstated on an ongoing basis if such Contributor notifies You of the - non-compliance by some reasonable means, this is the first time You have - received notice of non-compliance with this License from such - Contributor, and You become compliant prior to 30 days after Your receipt - of the notice. - -5.2. If You initiate litigation against any entity by asserting a patent - infringement claim (excluding declaratory judgment actions, - counter-claims, and cross-claims) alleging that a Contributor Version - directly or indirectly infringes any patent, then the rights granted to - You by any and all Contributors for the Covered Software under Section - 2.1 of this License shall terminate. - -5.3. In the event of termination under Sections 5.1 or 5.2 above, all end user - license agreements (excluding distributors and resellers) which have been - validly granted by You or Your distributors under this License prior to - termination shall survive termination. - -6. Disclaimer of Warranty - - Covered Software is provided under this License on an "as is" basis, - without warranty of any kind, either expressed, implied, or statutory, - including, without limitation, warranties that the Covered Software is free - of defects, merchantable, fit for a particular purpose or non-infringing. - The entire risk as to the quality and performance of the Covered Software - is with You. Should any Covered Software prove defective in any respect, - You (not any Contributor) assume the cost of any necessary servicing, - repair, or correction. This disclaimer of warranty constitutes an essential - part of this License. No use of any Covered Software is authorized under - this License except under this disclaimer. - -7. Limitation of Liability - - Under no circumstances and under no legal theory, whether tort (including - negligence), contract, or otherwise, shall any Contributor, or anyone who - distributes Covered Software as permitted above, be liable to You for any - direct, indirect, special, incidental, or consequential damages of any - character including, without limitation, damages for lost profits, loss of - goodwill, work stoppage, computer failure or malfunction, or any and all - other commercial damages or losses, even if such party shall have been - informed of the possibility of such damages. This limitation of liability - shall not apply to liability for death or personal injury resulting from - such party's negligence to the extent applicable law prohibits such - limitation. Some jurisdictions do not allow the exclusion or limitation of - incidental or consequential damages, so this exclusion and limitation may - not apply to You. - -8. Litigation - - Any litigation relating to this License may be brought only in the courts - of a jurisdiction where the defendant maintains its principal place of - business and such litigation shall be governed by laws of that - jurisdiction, without reference to its conflict-of-law provisions. Nothing - in this Section shall prevent a party's ability to bring cross-claims or - counter-claims. - -9. Miscellaneous - - This License represents the complete agreement concerning the subject - matter hereof. If any provision of this License is held to be - unenforceable, such provision shall be reformed only to the extent - necessary to make it enforceable. Any law or regulation which provides that - the language of a contract shall be construed against the drafter shall not - be used to construe this License against a Contributor. - - -10. Versions of the License - -10.1. New Versions - - Mozilla Foundation is the license steward. Except as provided in Section - 10.3, no one other than the license steward has the right to modify or - publish new versions of this License. Each version will be given a - distinguishing version number. - -10.2. Effect of New Versions - - You may distribute the Covered Software under the terms of the version - of the License under which You originally received the Covered Software, - or under the terms of any subsequent version published by the license - steward. - -10.3. Modified Versions - - If you create software not governed by this License, and you want to - create a new license for such software, you may create and use a - modified version of this License if you rename the license and remove - any references to the name of the license steward (except to note that - such modified license differs from this License). - -10.4. Distributing Source Code Form that is Incompatible With Secondary - Licenses If You choose to distribute Source Code Form that is - Incompatible With Secondary Licenses under the terms of this version of - the License, the notice described in Exhibit B of this License must be - attached. - -Exhibit A - Source Code Form License Notice - - This Source Code Form is subject to the - terms of the Mozilla Public License, v. - 2.0. If a copy of the MPL was not - distributed with this file, You can - obtain one at - http://mozilla.org/MPL/2.0/. - -If it is not possible or desirable to put the notice in a particular file, -then You may include the notice in a location (such as a LICENSE file in a -relevant directory) where a recipient would be likely to look for such a -notice. - -You may add additional accurate notices of copyright ownership. - -Exhibit B - "Incompatible With Secondary Licenses" Notice - - This Source Code Form is "Incompatible - With Secondary Licenses", as defined by - the Mozilla Public License, v. 2.0. diff --git a/code-samples/eventing/helloworld/helloworld-go/third_party/VENDOR-LICENSE/github.com/hashicorp/golang-lru/simplelru/simplelru/lru.go b/code-samples/eventing/helloworld/helloworld-go/third_party/VENDOR-LICENSE/github.com/hashicorp/golang-lru/simplelru/simplelru/lru.go deleted file mode 100644 index 5673773b22..0000000000 --- a/code-samples/eventing/helloworld/helloworld-go/third_party/VENDOR-LICENSE/github.com/hashicorp/golang-lru/simplelru/simplelru/lru.go +++ /dev/null @@ -1,161 +0,0 @@ -package simplelru - -import ( - "container/list" - "errors" -) - -// EvictCallback is used to get a callback when a cache entry is evicted -type EvictCallback func(key interface{}, value interface{}) - -// LRU implements a non-thread safe fixed size LRU cache -type LRU struct { - size int - evictList *list.List - items map[interface{}]*list.Element - onEvict EvictCallback -} - -// entry is used to hold a value in the evictList -type entry struct { - key interface{} - value interface{} -} - -// NewLRU constructs an LRU of the given size -func NewLRU(size int, onEvict EvictCallback) (*LRU, error) { - if size <= 0 { - return nil, errors.New("Must provide a positive size") - } - c := &LRU{ - size: size, - evictList: list.New(), - items: make(map[interface{}]*list.Element), - onEvict: onEvict, - } - return c, nil -} - -// Purge is used to completely clear the cache. -func (c *LRU) Purge() { - for k, v := range c.items { - if c.onEvict != nil { - c.onEvict(k, v.Value.(*entry).value) - } - delete(c.items, k) - } - c.evictList.Init() -} - -// Add adds a value to the cache. Returns true if an eviction occurred. -func (c *LRU) Add(key, value interface{}) (evicted bool) { - // Check for existing item - if ent, ok := c.items[key]; ok { - c.evictList.MoveToFront(ent) - ent.Value.(*entry).value = value - return false - } - - // Add new item - ent := &entry{key, value} - entry := c.evictList.PushFront(ent) - c.items[key] = entry - - evict := c.evictList.Len() > c.size - // Verify size not exceeded - if evict { - c.removeOldest() - } - return evict -} - -// Get looks up a key's value from the cache. -func (c *LRU) Get(key interface{}) (value interface{}, ok bool) { - if ent, ok := c.items[key]; ok { - c.evictList.MoveToFront(ent) - return ent.Value.(*entry).value, true - } - return -} - -// Contains checks if a key is in the cache, without updating the recent-ness -// or deleting it for being stale. -func (c *LRU) Contains(key interface{}) (ok bool) { - _, ok = c.items[key] - return ok -} - -// Peek returns the key value (or undefined if not found) without updating -// the "recently used"-ness of the key. -func (c *LRU) Peek(key interface{}) (value interface{}, ok bool) { - var ent *list.Element - if ent, ok = c.items[key]; ok { - return ent.Value.(*entry).value, true - } - return nil, ok -} - -// Remove removes the provided key from the cache, returning if the -// key was contained. -func (c *LRU) Remove(key interface{}) (present bool) { - if ent, ok := c.items[key]; ok { - c.removeElement(ent) - return true - } - return false -} - -// RemoveOldest removes the oldest item from the cache. -func (c *LRU) RemoveOldest() (key interface{}, value interface{}, ok bool) { - ent := c.evictList.Back() - if ent != nil { - c.removeElement(ent) - kv := ent.Value.(*entry) - return kv.key, kv.value, true - } - return nil, nil, false -} - -// GetOldest returns the oldest entry -func (c *LRU) GetOldest() (key interface{}, value interface{}, ok bool) { - ent := c.evictList.Back() - if ent != nil { - kv := ent.Value.(*entry) - return kv.key, kv.value, true - } - return nil, nil, false -} - -// Keys returns a slice of the keys in the cache, from oldest to newest. -func (c *LRU) Keys() []interface{} { - keys := make([]interface{}, len(c.items)) - i := 0 - for ent := c.evictList.Back(); ent != nil; ent = ent.Prev() { - keys[i] = ent.Value.(*entry).key - i++ - } - return keys -} - -// Len returns the number of items in the cache. -func (c *LRU) Len() int { - return c.evictList.Len() -} - -// removeOldest removes the oldest item from the cache. -func (c *LRU) removeOldest() { - ent := c.evictList.Back() - if ent != nil { - c.removeElement(ent) - } -} - -// removeElement is used to remove a given list element from the cache -func (c *LRU) removeElement(e *list.Element) { - c.evictList.Remove(e) - kv := e.Value.(*entry) - delete(c.items, kv.key) - if c.onEvict != nil { - c.onEvict(kv.key, kv.value) - } -} diff --git a/code-samples/eventing/helloworld/helloworld-go/third_party/VENDOR-LICENSE/github.com/hashicorp/golang-lru/simplelru/simplelru/lru_interface.go b/code-samples/eventing/helloworld/helloworld-go/third_party/VENDOR-LICENSE/github.com/hashicorp/golang-lru/simplelru/simplelru/lru_interface.go deleted file mode 100644 index 74c7077440..0000000000 --- a/code-samples/eventing/helloworld/helloworld-go/third_party/VENDOR-LICENSE/github.com/hashicorp/golang-lru/simplelru/simplelru/lru_interface.go +++ /dev/null @@ -1,36 +0,0 @@ -package simplelru - -// LRUCache is the interface for simple LRU cache. -type LRUCache interface { - // Adds a value to the cache, returns true if an eviction occurred and - // updates the "recently used"-ness of the key. - Add(key, value interface{}) bool - - // Returns key's value from the cache and - // updates the "recently used"-ness of the key. #value, isFound - Get(key interface{}) (value interface{}, ok bool) - - // Check if a key exsists in cache without updating the recent-ness. - Contains(key interface{}) (ok bool) - - // Returns key's value without updating the "recently used"-ness of the key. - Peek(key interface{}) (value interface{}, ok bool) - - // Removes a key from the cache. - Remove(key interface{}) bool - - // Removes the oldest entry from cache. - RemoveOldest() (interface{}, interface{}, bool) - - // Returns the oldest entry from the cache. #key, value, isFound - GetOldest() (interface{}, interface{}, bool) - - // Returns a slice of the keys in the cache, from oldest to newest. - Keys() []interface{} - - // Returns the number of items in the cache. - Len() int - - // Clear all cache entries - Purge() -} diff --git a/code-samples/eventing/helloworld/helloworld-go/third_party/VENDOR-LICENSE/github.com/lightstep/tracecontext.go/LICENSE b/code-samples/eventing/helloworld/helloworld-go/third_party/VENDOR-LICENSE/github.com/lightstep/tracecontext.go/LICENSE deleted file mode 100644 index 853b46db12..0000000000 --- a/code-samples/eventing/helloworld/helloworld-go/third_party/VENDOR-LICENSE/github.com/lightstep/tracecontext.go/LICENSE +++ /dev/null @@ -1,21 +0,0 @@ -The MIT License (MIT) - -Copyright (c) 2016 - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. diff --git a/code-samples/eventing/helloworld/helloworld-go/third_party/VENDOR-LICENSE/github.com/pkg/errors/LICENSE b/code-samples/eventing/helloworld/helloworld-go/third_party/VENDOR-LICENSE/github.com/pkg/errors/LICENSE deleted file mode 100644 index 835ba3e755..0000000000 --- a/code-samples/eventing/helloworld/helloworld-go/third_party/VENDOR-LICENSE/github.com/pkg/errors/LICENSE +++ /dev/null @@ -1,23 +0,0 @@ -Copyright (c) 2015, Dave Cheney -All rights reserved. - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions are met: - -* Redistributions of source code must retain the above copyright notice, this - list of conditions and the following disclaimer. - -* Redistributions in binary form must reproduce the above copyright notice, - this list of conditions and the following disclaimer in the documentation - and/or other materials provided with the distribution. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" -AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE -IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE -DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE -FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL -DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR -SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER -CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, -OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/code-samples/eventing/helloworld/helloworld-go/third_party/VENDOR-LICENSE/go.opencensus.io/LICENSE b/code-samples/eventing/helloworld/helloworld-go/third_party/VENDOR-LICENSE/go.opencensus.io/LICENSE deleted file mode 100644 index 7a4a3ea242..0000000000 --- a/code-samples/eventing/helloworld/helloworld-go/third_party/VENDOR-LICENSE/go.opencensus.io/LICENSE +++ /dev/null @@ -1,202 +0,0 @@ - - Apache License - Version 2.0, January 2004 - http://www.apache.org/licenses/ - - TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION - - 1. Definitions. - - "License" shall mean the terms and conditions for use, reproduction, - and distribution as defined by Sections 1 through 9 of this document. - - "Licensor" shall mean the copyright owner or entity authorized by - the copyright owner that is granting the License. - - "Legal Entity" shall mean the union of the acting entity and all - other entities that control, are controlled by, or are under common - control with that entity. For the purposes of this definition, - "control" means (i) the power, direct or indirect, to cause the - direction or management of such entity, whether by contract or - otherwise, or (ii) ownership of fifty percent (50%) or more of the - outstanding shares, or (iii) beneficial ownership of such entity. - - "You" (or "Your") shall mean an individual or Legal Entity - exercising permissions granted by this License. - - "Source" form shall mean the preferred form for making modifications, - including but not limited to software source code, documentation - source, and configuration files. - - "Object" form shall mean any form resulting from mechanical - transformation or translation of a Source form, including but - not limited to compiled object code, generated documentation, - and conversions to other media types. - - "Work" shall mean the work of authorship, whether in Source or - Object form, made available under the License, as indicated by a - copyright notice that is included in or attached to the work - (an example is provided in the Appendix below). - - "Derivative Works" shall mean any work, whether in Source or Object - form, that is based on (or derived from) the Work and for which the - editorial revisions, annotations, elaborations, or other modifications - represent, as a whole, an original work of authorship. For the purposes - of this License, Derivative Works shall not include works that remain - separable from, or merely link (or bind by name) to the interfaces of, - the Work and Derivative Works thereof. - - "Contribution" shall mean any work of authorship, including - the original version of the Work and any modifications or additions - to that Work or Derivative Works thereof, that is intentionally - submitted to Licensor for inclusion in the Work by the copyright owner - or by an individual or Legal Entity authorized to submit on behalf of - the copyright owner. For the purposes of this definition, "submitted" - means any form of electronic, verbal, or written communication sent - to the Licensor or its representatives, including but not limited to - communication on electronic mailing lists, source code control systems, - and issue tracking systems that are managed by, or on behalf of, the - Licensor for the purpose of discussing and improving the Work, but - excluding communication that is conspicuously marked or otherwise - designated in writing by the copyright owner as "Not a Contribution." - - "Contributor" shall mean Licensor and any individual or Legal Entity - on behalf of whom a Contribution has been received by Licensor and - subsequently incorporated within the Work. - - 2. Grant of Copyright License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - copyright license to reproduce, prepare Derivative Works of, - publicly display, publicly perform, sublicense, and distribute the - Work and such Derivative Works in Source or Object form. - - 3. Grant of Patent License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - (except as stated in this section) patent license to make, have made, - use, offer to sell, sell, import, and otherwise transfer the Work, - where such license applies only to those patent claims licensable - by such Contributor that are necessarily infringed by their - Contribution(s) alone or by combination of their Contribution(s) - with the Work to which such Contribution(s) was submitted. If You - institute patent litigation against any entity (including a - cross-claim or counterclaim in a lawsuit) alleging that the Work - or a Contribution incorporated within the Work constitutes direct - or contributory patent infringement, then any patent licenses - granted to You under this License for that Work shall terminate - as of the date such litigation is filed. - - 4. Redistribution. You may reproduce and distribute copies of the - Work or Derivative Works thereof in any medium, with or without - modifications, and in Source or Object form, provided that You - meet the following conditions: - - (a) You must give any other recipients of the Work or - Derivative Works a copy of this License; and - - (b) You must cause any modified files to carry prominent notices - stating that You changed the files; and - - (c) You must retain, in the Source form of any Derivative Works - that You distribute, all copyright, patent, trademark, and - attribution notices from the Source form of the Work, - excluding those notices that do not pertain to any part of - the Derivative Works; and - - (d) If the Work includes a "NOTICE" text file as part of its - distribution, then any Derivative Works that You distribute must - include a readable copy of the attribution notices contained - within such NOTICE file, excluding those notices that do not - pertain to any part of the Derivative Works, in at least one - of the following places: within a NOTICE text file distributed - as part of the Derivative Works; within the Source form or - documentation, if provided along with the Derivative Works; or, - within a display generated by the Derivative Works, if and - wherever such third-party notices normally appear. The contents - of the NOTICE file are for informational purposes only and - do not modify the License. You may add Your own attribution - notices within Derivative Works that You distribute, alongside - or as an addendum to the NOTICE text from the Work, provided - that such additional attribution notices cannot be construed - as modifying the License. - - You may add Your own copyright statement to Your modifications and - may provide additional or different license terms and conditions - for use, reproduction, or distribution of Your modifications, or - for any such Derivative Works as a whole, provided Your use, - reproduction, and distribution of the Work otherwise complies with - the conditions stated in this License. - - 5. Submission of Contributions. Unless You explicitly state otherwise, - any Contribution intentionally submitted for inclusion in the Work - by You to the Licensor shall be under the terms and conditions of - this License, without any additional terms or conditions. - Notwithstanding the above, nothing herein shall supersede or modify - the terms of any separate license agreement you may have executed - with Licensor regarding such Contributions. - - 6. Trademarks. This License does not grant permission to use the trade - names, trademarks, service marks, or product names of the Licensor, - except as required for reasonable and customary use in describing the - origin of the Work and reproducing the content of the NOTICE file. - - 7. Disclaimer of Warranty. Unless required by applicable law or - agreed to in writing, Licensor provides the Work (and each - Contributor provides its Contributions) on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or - implied, including, without limitation, any warranties or conditions - of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A - PARTICULAR PURPOSE. You are solely responsible for determining the - appropriateness of using or redistributing the Work and assume any - risks associated with Your exercise of permissions under this License. - - 8. Limitation of Liability. In no event and under no legal theory, - whether in tort (including negligence), contract, or otherwise, - unless required by applicable law (such as deliberate and grossly - negligent acts) or agreed to in writing, shall any Contributor be - liable to You for damages, including any direct, indirect, special, - incidental, or consequential damages of any character arising as a - result of this License or out of the use or inability to use the - Work (including but not limited to damages for loss of goodwill, - work stoppage, computer failure or malfunction, or any and all - other commercial damages or losses), even if such Contributor - has been advised of the possibility of such damages. - - 9. Accepting Warranty or Additional Liability. While redistributing - the Work or Derivative Works thereof, You may choose to offer, - and charge a fee for, acceptance of support, warranty, indemnity, - or other liability obligations and/or rights consistent with this - License. However, in accepting such obligations, You may act only - on Your own behalf and on Your sole responsibility, not on behalf - of any other Contributor, and only if You agree to indemnify, - defend, and hold each Contributor harmless for any liability - incurred by, or claims asserted against, such Contributor by reason - of your accepting any such warranty or additional liability. - - END OF TERMS AND CONDITIONS - - APPENDIX: How to apply the Apache License to your work. - - To apply the Apache License to your work, attach the following - boilerplate notice, with the fields enclosed by brackets "[]" - replaced with your own identifying information. (Don't include - the brackets!) The text should be enclosed in the appropriate - comment syntax for the file format. We also recommend that a - file or class name and description of purpose be included on the - same "printed page" as the copyright notice for easier - identification within third-party archives. - - Copyright [yyyy] [name of copyright owner] - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. \ No newline at end of file diff --git a/code-samples/eventing/helloworld/helloworld-go/third_party/VENDOR-LICENSE/go.uber.org/atomic/LICENSE.txt b/code-samples/eventing/helloworld/helloworld-go/third_party/VENDOR-LICENSE/go.uber.org/atomic/LICENSE.txt deleted file mode 100644 index 8765c9fbc6..0000000000 --- a/code-samples/eventing/helloworld/helloworld-go/third_party/VENDOR-LICENSE/go.uber.org/atomic/LICENSE.txt +++ /dev/null @@ -1,19 +0,0 @@ -Copyright (c) 2016 Uber Technologies, Inc. - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in -all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -THE SOFTWARE. diff --git a/code-samples/eventing/helloworld/helloworld-go/third_party/VENDOR-LICENSE/go.uber.org/multierr/LICENSE.txt b/code-samples/eventing/helloworld/helloworld-go/third_party/VENDOR-LICENSE/go.uber.org/multierr/LICENSE.txt deleted file mode 100644 index 858e02475f..0000000000 --- a/code-samples/eventing/helloworld/helloworld-go/third_party/VENDOR-LICENSE/go.uber.org/multierr/LICENSE.txt +++ /dev/null @@ -1,19 +0,0 @@ -Copyright (c) 2017 Uber Technologies, Inc. - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in -all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -THE SOFTWARE. diff --git a/code-samples/eventing/helloworld/helloworld-go/third_party/VENDOR-LICENSE/go.uber.org/zap/LICENSE.txt b/code-samples/eventing/helloworld/helloworld-go/third_party/VENDOR-LICENSE/go.uber.org/zap/LICENSE.txt deleted file mode 100644 index 6652bed45f..0000000000 --- a/code-samples/eventing/helloworld/helloworld-go/third_party/VENDOR-LICENSE/go.uber.org/zap/LICENSE.txt +++ /dev/null @@ -1,19 +0,0 @@ -Copyright (c) 2016-2017 Uber Technologies, Inc. - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in -all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -THE SOFTWARE. diff --git a/code-samples/serving/cloudevents/cloudevents-go/Dockerfile b/code-samples/serving/cloudevents/cloudevents-go/Dockerfile index 532641b431..92d5abcc70 100644 --- a/code-samples/serving/cloudevents/cloudevents-go/Dockerfile +++ b/code-samples/serving/cloudevents/cloudevents-go/Dockerfile @@ -1,7 +1,7 @@ # Use the official Golang image to create a build artifact. # This is based on Debian and sets the GOPATH to /go. # https://hub.docker.com/_/golang -FROM golang:latest AS builder +FROM golang:1.27 AS builder ARG TARGETOS ARG TARGETARCH @@ -9,20 +9,21 @@ ARG TARGETARCH # Create and change to the app directory. WORKDIR /app +# Download the dependencies recorded in the module files. +COPY go.mod go.sum ./ +RUN go mod download + # Copy local code to the container image. COPY . ./ -# Install dependencies and tidy up the go.mod and go.sum files. -RUN go mod tidy - # Build the binary. # -mod=readonly ensures immutable go.mod and go.sum in container builds. -RUN CGO_ENABLED=0 GOOS=linux GOOS=${TARGETOS} GOARCH=${TARGETARCH} go build -v -o server +RUN CGO_ENABLED=0 GOOS=${TARGETOS} GOARCH=${TARGETARCH} go build -mod=readonly -v -o server # Use the official Alpine image for a lean production container. # https://hub.docker.com/_/alpine # https://docs.docker.com/develop/develop-images/multistage-build/#use-multi-stage-builds -FROM alpine:3 +FROM alpine:3.24 RUN apk add --no-cache ca-certificates # Copy the binary to the production image from the builder stage. diff --git a/code-samples/serving/cloudevents/cloudevents-go/README.md b/code-samples/serving/cloudevents/cloudevents-go/README.md index ddf1d92043..e507a2c1eb 100644 --- a/code-samples/serving/cloudevents/cloudevents-go/README.md +++ b/code-samples/serving/cloudevents/cloudevents-go/README.md @@ -12,18 +12,22 @@ can use for testing. It supports running in two modes: send to a Service or Broker when not initiated by a Broker invocation (e.g. implementing an event source) -The application will use `$K_SINK`-mode whenever the environment variable is -specified. +The application will use `$K_SINK`-mode whenever the environment variable has a +non-empty value. An unset or empty `K_SINK` selects reply mode. -Do the following steps to create the sample code and then deploy the app to your -cluster. You can also download a working copy of the sample, by running the -following commands: +Use the checked-in [application source](cloudevents.go), [Dockerfile](Dockerfile), +and [Service manifest](service.yaml) to build and deploy the app. Download a +working copy of the sample with: ```bash git clone https://github.com/knative/docs.git knative-docs cd knative-docs/code-samples/serving/cloudevents/cloudevents-go ``` +For the Eventing Hello World learning scenario, use the +[Broker/Trigger tutorial](eventing/README.md), which demonstrates filtered input +and routed replies. + ## Before you begin - A Kubernetes cluster with Knative installed and DNS configured. See @@ -31,66 +35,26 @@ cd knative-docs/code-samples/serving/cloudevents/cloudevents-go - [Docker](https://www.docker.com) installed and running on your local machine, and a Docker Hub account configured (we'll use it for a container registry). -## The sample code. - -1. If you look in `cloudevents.go`, you will see two key functions for the different modes of operation: - - ```go - func (recv *Receiver) ReceiveAndSend(ctx context.Context, event cloudevents.Event) cloudevents.Result { - // This is called whenever an event is received if $K_SINK is set, and sends a new event - // to the url in $K_SINK. - } - - func (recv *Receiver) ReceiveAndReply(ctx context.Context, event cloudevents.Event) (*cloudevents.Event, cloudevents.Result) { - // This is called whenever an event is received if $K_SINK is NOT set, and it replies with - // the new event instead. - } - ``` +## Build and deploy -2. Choose how you would like to build the application: +Replace `{username}` below and in [service.yaml](service.yaml) with your registry +namespace. From the sample directory, build and push with Docker: - ### Dockerfile - - * If you look in `Dockerfile`, you will see a method for pulling in the dependencies and building a small Go container based on Alpine. You can build and push this to your registry of choice via: -```bash -# Build and push the container on your local machine. -docker buildx build --platform linux/arm64,linux/amd64 -t "" --push . -``` - - ### ko - - * You can use [`ko`](https://github.com/google/ko) to build and push just the image with: -```bash -ko publish github.com/knative/docs/code-samples/serving/cloudevents/cloudevents-go -``` - However, if you use `ko` for the next step, this is not necessary. - - -3. Choose how you would like to deploy the application: - - ### yaml (with Dockerfile) - * If you look in `service.yaml`, take the `` name you used earlier and insert it into the `image:` field, then run: ```bash +docker buildx build --platform linux/arm64,linux/amd64 \ + -t "docker.io/{username}/cloudevents-go" --push . kubectl apply -f service.yaml +kubectl wait --for=condition=Ready ksvc/cloudevents-go --timeout=180s ``` - ### yaml (with ko) - * If using `ko` to build and push: -```bash -ko apply -f service.yaml -``` +For a local run without publishing: - ### kn (with Dockerfile) - * If using `kn` to deploy: ```bash -kn service create cloudevents-go --image= +docker build -t cloudevents-go:local . +docker run --rm -p 8080:8080 cloudevents-go:local ``` - ### kn (with ko) - * You can compose `kn` and `ko` to build and deploy with a single step using: -```bash -kn service create cloudevents-go --image=$(ko publish github.com/knative/docs/code-samples/serving/cloudevents/cloudevents-go) -``` +Send the example event below to `http://localhost:8080/` instead of the Service URL. ## Testing the sample @@ -121,6 +85,33 @@ You will get back: {"message":"Hello, Dave"} ``` +The response is a CloudEvent in binary HTTP mode: the JSON above is its data, +and the `ce-*` response headers contain its context attributes. Use `curl -i` to +inspect these headers. The application listens on port `8080` by default; set +`PORT` to use a different port when running locally. + +### Sending the response to a sink + +To send the generated event to another service instead of returning it in the +HTTP response, set `K_SINK` to the destination URL. For example: + +Using the `kn` CLI: + +```bash +kn service update cloudevents-go --env K_SINK=http://your-sink.example.com +``` + +Send the same input event again. On successful delivery, the application returns +an empty successful HTTP response, and the sink receives a CloudEvent with type +`dev.knative.docs.sample` and data `{"message":"Hello, Dave"}`. Delivery failures +are returned to the caller as unsuccessful HTTP responses. + +To restore reply mode: + +```bash +kn service update cloudevents-go --env K_SINK- +``` + ## Removing the sample app deployment To remove the sample app from your cluster, delete the service record: diff --git a/code-samples/serving/cloudevents/cloudevents-go/ci.sh b/code-samples/serving/cloudevents/cloudevents-go/ci.sh new file mode 100755 index 0000000000..6b8de2dc67 --- /dev/null +++ b/code-samples/serving/cloudevents/cloudevents-go/ci.sh @@ -0,0 +1,43 @@ +#!/usr/bin/env bash +# Source from this sample directory; the caller owns cluster setup and ingress forwarding. +IMAGE=dev.local/cloudevents-go:ci +SAMPLE_URL=http://127.0.0.1:18080 + +main() ( + set -euo pipefail + build + deploy + test +) + +build() ( + set -euo pipefail + docker build -t "$IMAGE" . +) + +deploy() ( + set -euo pipefail + kind load docker-image "$IMAGE" + kubectl patch --local -f service.yaml --type=json -p "$( + jq -cn --arg image "$IMAGE" '[ + {op: "replace", path: "/spec/template/spec/containers/0/image", value: $image}, + {op: "add", path: "/spec/template/spec/containers/0/imagePullPolicy", value: "IfNotPresent"} + ]' + )" -o yaml | kubectl --namespace default apply -f - + kubectl --namespace default wait -f service.yaml --for=condition=Ready --timeout=300s +) + +test() ( + set -euo pipefail + local service_url service_host + service_url=$(kubectl --namespace default get -f service.yaml -o jsonpath='{.status.url}') + service_host=${service_url#*://} + [[ "$service_host" =~ ^[a-zA-Z0-9.-]+$ ]] + local response + response=$(curl -fsS --max-time 30 \ + -H "Host: $service_host" -H "Content-Type: application/json" \ + -H "Ce-Specversion: 1.0" -H "Ce-Id: smoke-test" \ + -H "Ce-Type: dev.knative.samples.test" -H "Ce-Source: /smoke-test" \ + --data '{"name":"Knative"}' "$SAMPLE_URL/") + jq -e '(.data // .).message == "Hello, Knative"' <<< "$response" +) diff --git a/code-samples/serving/cloudevents/cloudevents-go/cloudevents.go b/code-samples/serving/cloudevents/cloudevents-go/cloudevents.go index dd112ac1f7..21b4419bc7 100644 --- a/code-samples/serving/cloudevents/cloudevents-go/cloudevents.go +++ b/code-samples/serving/cloudevents/cloudevents-go/cloudevents.go @@ -15,18 +15,21 @@ type Receiver struct { // If the K_SINK environment variable is set, then events are sent there, // otherwise we simply reply to the inbound request. Target string `envconfig:"K_SINK"` + + Port int `envconfig:"PORT" default:"8080"` } func main() { - client, err := cloudevents.NewDefaultClient() - if err != nil { + r := Receiver{} + if err := envconfig.Process("", &r); err != nil { log.Fatal(err.Error()) } - r := Receiver{client: client} - if err := envconfig.Process("", &r); err != nil { + client, err := cloudevents.NewClientHTTP(cloudevents.WithPort(r.Port)) + if err != nil { log.Fatal(err.Error()) } + r.client = client // Depending on whether targeting data has been supplied, // we will either reply with our response or send it on to diff --git a/code-samples/serving/cloudevents/cloudevents-go/eventing/README.md b/code-samples/serving/cloudevents/cloudevents-go/eventing/README.md new file mode 100644 index 0000000000..597f031bbc --- /dev/null +++ b/code-samples/serving/cloudevents/cloudevents-go/eventing/README.md @@ -0,0 +1,156 @@ +# Go CloudEvents with a Broker and Triggers + +This example runs the [Go CloudEvents receiver](../README.md) as a Kubernetes +Deployment and Service. Serving is not +required for this variant. A Broker accepts input, a filtered Trigger delivers +it to the receiver, and the receiver's reply becomes a new event in the Broker. +A second Trigger routes that reply to an event logger. This demonstrates +[Broker](https://knative.dev/docs/eventing/brokers/) and +[Trigger](https://knative.dev/docs/eventing/triggers/) routing without confusing +an HTTP acknowledgement from the Broker with actual subscriber delivery. + +## Event format + +| Field | Value | +| --- | --- | +| Input data | `{"name":"Dave"}` | +| Input routing attributes | `type=curl.demo`, `source=curl-command` | +| Reply type | `dev.knative.docs.sample` | +| Reply source | `https://github.com/knative/docs/code-samples/serving/cloudevents/cloudevents-go` | +| Reply data | `{"message":"Hello, Dave"}` | + +Binary and structured CloudEvents v1 are supported. The input Trigger filters +on both input type and source; generated replies have a different type, so they +are not routed back to the receiver in a loop. Each reply receives a fresh ID +and timestamp from the SDK. + +## Prerequisites and source files + +- Kubernetes with [Knative Eventing](https://knative.dev/docs/install/eventing/install-eventing-with-yaml/), + including MTChannelBasedBroker and InMemoryChannel, installed. +- Docker and an image registry reachable by your cluster, or a local kind image + import setup. No third-party API credentials are used. +- `kubectl` configured for your intended test cluster. + +Clone and enter the sample directory: + +```bash +git clone https://github.com/knative/docs.git knative-docs +cd knative-docs/code-samples/serving/cloudevents/cloudevents-go +``` + +Use the checked-in [Broker/receiver/input Trigger manifest](sample-app.yaml), +[event-display Deployment and Service](event-display.yaml), and +[output Trigger](reply-trigger.yaml). The event-display image is a small local +logger built from [its source](logger/capture.go) and +[Dockerfile](logger/Dockerfile). + +## Build and deploy + +Build the receiver with Docker using the [build instructions](../README.md). +Set `image:` in the Deployment in `eventing/sample-app.yaml` to that image. +Also build the logger: + +```bash +docker build -t eventing/logger +docker push +``` + +Replace the placeholder image in `eventing/event-display.yaml` with your logger +image. For kind, importing both locally built images with `kind load docker-image` +and using non-latest tags with `imagePullPolicy: IfNotPresent` avoids a registry +push. Do not change shared cluster image-resolution settings. + +From the sample root: + +```bash +kubectl create namespace knative-samples +kubectl -n knative-samples apply -f eventing/sample-app.yaml +kubectl -n knative-samples apply -f eventing/event-display.yaml +kubectl -n knative-samples apply -f eventing/reply-trigger.yaml +kubectl -n knative-samples rollout status deployment/cloudevents-go-eventing --timeout=180s +kubectl -n knative-samples rollout status deployment/event-display --timeout=180s +kubectl -n knative-samples wait broker/default --for=condition=Ready --timeout=180s +kubectl -n knative-samples wait trigger/input-to-sample trigger/output-to-capture --for=condition=Ready --timeout=180s +``` + +If `knative-samples` already exists, use your own test namespace consistently +rather than deleting someone else's resources. The manifests deliberately omit +namespaces so `kubectl -n` selects the intended namespace. + +## Send binary and structured events + +Get the actual Broker URL: + +```bash +BROKER_URL=$(kubectl -n knative-samples get broker default -o jsonpath='{.status.address.url}') +``` + +The URL is normally cluster-internal. Send from a temporary curl pod; these +commands return HTTP 202 on Broker acceptance (not a synchronous greeting): + +```bash +kubectl -n knative-samples run send-binary --rm -i --restart=Never --image=curlimages/curl:8.17.0 -- \ + --fail-with-body -i "$BROKER_URL" -X POST \ + -H 'content-type: application/json' -H 'ce-specversion: 1.0' \ + -H 'ce-type: curl.demo' -H 'ce-source: curl-command' -H 'ce-id: tutorial-binary' \ + -d '{"name":"Dave"}' +kubectl -n knative-samples run send-structured --rm -i --restart=Never --image=curlimages/curl:8.17.0 -- \ + --fail-with-body -i "$BROKER_URL" -X POST -H 'content-type: application/cloudevents+json' \ + -d '{"specversion":"1.0","id":"tutorial-structured","type":"curl.demo","source":"curl-command","datacontenttype":"application/json","data":{"name":"Dave"}}' +``` + +Check **both** processing and output delivery: + +```bash +kubectl -n knative-samples logs deployment/cloudevents-go-eventing --tail=50 +kubectl -n knative-samples logs deployment/event-display --tail=50 +``` + +The receiver logs `Got an event from: "Dave"` and `Replying with event: "Hello, Dave"`. +The logger records the generated CloudEvent headers, including +`Ce-Type: dev.knative.docs.sample`, the reply source above, a new ID, and JSON data +`{"message":"Hello, Dave"}`. That proves Broker → input Trigger → receiver reply +→ Broker → output Trigger → logger, beyond simply observing HTTP 202. + +Try changing the input source/type to understand why nonmatching events are not +delivered by the input Trigger. Do not change the output filter to match input +attributes: it must select the receiver's generated reply. + +## Explicit sink mode and port configuration + +Normally the receiver returns a CloudEvent in the HTTP response to its caller; +Eventing republishes that reply. A nonempty `K_SINK` instead makes the receiver +explicitly send its output to the selected destination and acknowledge its caller +without a response event. For example, forward output explicitly to this Broker: + +```bash +kubectl -n knative-samples set env deployment/cloudevents-go-eventing K_SINK="$BROKER_URL" +kubectl -n knative-samples rollout status deployment/cloudevents-go-eventing --timeout=180s +``` + +Send another binary or structured input as above and verify the logger again. +The output Trigger still selects `dev.knative.docs.sample`. Reset reply mode: + +```bash +kubectl -n knative-samples set env deployment/cloudevents-go-eventing K_SINK- +kubectl -n knative-samples rollout status deployment/cloudevents-go-eventing --timeout=180s +``` + +Unset or empty `K_SINK` means reply mode. A failing sink returns an unsuccessful +HTTP response, rather than silently acknowledging successful delivery. The default +port is 8080; if setting `PORT` on this Deployment, update the Service `targetPort` +and container port consistently. The [Serving example](../README.md) documents the same port configuration. + +## Cleanup + +Delete only the resources you created: + +```bash +kubectl -n knative-samples delete -f eventing/reply-trigger.yaml +kubectl -n knative-samples delete trigger/input-to-sample broker/default +kubectl -n knative-samples delete deployment/cloudevents-go-eventing service/cloudevents-go-eventing +kubectl -n knative-samples delete -f eventing/event-display.yaml +# Only if you created this otherwise-unused namespace: +kubectl delete namespace knative-samples +``` diff --git a/code-samples/serving/cloudevents/cloudevents-go/eventing/event-display.yaml b/code-samples/serving/cloudevents/cloudevents-go/eventing/event-display.yaml new file mode 100644 index 0000000000..e1c64c822f --- /dev/null +++ b/code-samples/serving/cloudevents/cloudevents-go/eventing/event-display.yaml @@ -0,0 +1,39 @@ +# Source-backed local event logger, built from ../.ci/fixtures. +# It is only an output sink; it does not replace the receiver application. +apiVersion: apps/v1 +kind: Deployment +metadata: + name: event-display +spec: + replicas: 1 + selector: + matchLabels: + app: event-display + template: + metadata: + labels: + app: event-display + spec: + containers: + - name: event-display + image: example.invalid/cloudevents-go-event-display:replace-me + ports: + - containerPort: 8080 + resources: + requests: + cpu: 10m + memory: 16Mi + limits: + cpu: 100m + memory: 64Mi +--- +apiVersion: v1 +kind: Service +metadata: + name: event-display +spec: + selector: + app: event-display + ports: + - port: 80 + targetPort: 8080 diff --git a/code-samples/serving/cloudevents/cloudevents-go/eventing/logger/Dockerfile b/code-samples/serving/cloudevents/cloudevents-go/eventing/logger/Dockerfile new file mode 100644 index 0000000000..d4c6cee515 --- /dev/null +++ b/code-samples/serving/cloudevents/cloudevents-go/eventing/logger/Dockerfile @@ -0,0 +1,7 @@ +FROM golang:1.27 AS builder +WORKDIR /src +COPY capture.go ./ +RUN CGO_ENABLED=0 go build -o /capture capture.go +FROM alpine:3.24 +COPY --from=builder /capture /capture +ENTRYPOINT ["/capture"] diff --git a/code-samples/serving/cloudevents/cloudevents-go/eventing/logger/capture.go b/code-samples/serving/cloudevents/cloudevents-go/eventing/logger/capture.go new file mode 100644 index 0000000000..923637263c --- /dev/null +++ b/code-samples/serving/cloudevents/cloudevents-go/eventing/logger/capture.go @@ -0,0 +1,34 @@ +// This local test sink captures real sample output, never substitutes for the app. +package main + +import ( + "encoding/json" + "io" + "log" + "net/http" +) + +func main() { + http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { + defer r.Body.Close() + body, err := io.ReadAll(io.LimitReader(r.Body, 1<<20)) + if err != nil { + http.Error(w, err.Error(), http.StatusBadRequest) + return + } + event := struct { + Headers http.Header `json:"headers"` + Body json.RawMessage `json:"body"` + }{Headers: r.Header, Body: json.RawMessage(body)} + if err := json.NewEncoder(log.Writer()).Encode(event); err != nil { + http.Error(w, err.Error(), http.StatusBadRequest) + return + } + if r.URL.Path == "/fail" { + w.WriteHeader(http.StatusServiceUnavailable) + } else { + w.WriteHeader(http.StatusNoContent) + } + }) + log.Fatal(http.ListenAndServe(":8080", nil)) +} diff --git a/code-samples/serving/cloudevents/cloudevents-go/eventing/reply-trigger.yaml b/code-samples/serving/cloudevents/cloudevents-go/eventing/reply-trigger.yaml new file mode 100644 index 0000000000..c721d78584 --- /dev/null +++ b/code-samples/serving/cloudevents/cloudevents-go/eventing/reply-trigger.yaml @@ -0,0 +1,15 @@ +apiVersion: eventing.knative.dev/v1 +kind: Trigger +metadata: + name: output-to-capture +spec: + broker: default + filter: + attributes: + type: dev.knative.docs.sample + source: https://github.com/knative/docs/code-samples/serving/cloudevents/cloudevents-go + subscriber: + ref: + apiVersion: v1 + kind: Service + name: event-display diff --git a/code-samples/serving/cloudevents/cloudevents-go/eventing/sample-app.yaml b/code-samples/serving/cloudevents/cloudevents-go/eventing/sample-app.yaml new file mode 100644 index 0000000000..eb8b9273f5 --- /dev/null +++ b/code-samples/serving/cloudevents/cloudevents-go/eventing/sample-app.yaml @@ -0,0 +1,62 @@ +apiVersion: eventing.knative.dev/v1 +kind: Broker +metadata: + name: default + annotations: + eventing.knative.dev/broker.class: MTChannelBasedBroker +spec: {} +--- +# The SAME receiver image as ../service.yaml, deployed without Serving. +apiVersion: apps/v1 +kind: Deployment +metadata: + name: cloudevents-go-eventing +spec: + replicas: 1 + selector: + matchLabels: + app: cloudevents-go-eventing + template: + metadata: + labels: + app: cloudevents-go-eventing + spec: + containers: + - name: receiver + image: docker.io/{username}/cloudevents-go + ports: + - containerPort: 8080 + resources: + requests: + cpu: 25m + memory: 32Mi + limits: + cpu: 200m + memory: 128Mi +--- +apiVersion: v1 +kind: Service +metadata: + name: cloudevents-go-eventing +spec: + selector: + app: cloudevents-go-eventing + ports: + - port: 80 + targetPort: 8080 +--- +apiVersion: eventing.knative.dev/v1 +kind: Trigger +metadata: + name: input-to-sample +spec: + broker: default + filter: + attributes: + type: curl.demo + source: curl-command + subscriber: + ref: + apiVersion: v1 + kind: Service + name: cloudevents-go-eventing diff --git a/code-samples/serving/cloudevents/cloudevents-go/go.mod b/code-samples/serving/cloudevents/cloudevents-go/go.mod index 6e34e4ef90..cac881ec21 100644 --- a/code-samples/serving/cloudevents/cloudevents-go/go.mod +++ b/code-samples/serving/cloudevents/cloudevents-go/go.mod @@ -1,18 +1,17 @@ module knative.dev/docs/code-samples/serving/cloudevents/cloudevents-go -go 1.22 +go 1.27.0 require ( - github.com/cloudevents/sdk-go/v2 v2.15.2 + github.com/cloudevents/sdk-go/v2 v2.16.2 github.com/kelseyhightower/envconfig v1.4.0 ) require ( - github.com/google/uuid v1.1.1 // indirect - github.com/json-iterator/go v1.1.10 // indirect - github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421 // indirect - github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742 // indirect - go.uber.org/atomic v1.4.0 // indirect - go.uber.org/multierr v1.1.0 // indirect - go.uber.org/zap v1.10.0 // indirect + github.com/google/uuid v1.6.0 // indirect + github.com/json-iterator/go v1.1.12 // indirect + github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect + github.com/modern-go/reflect2 v1.0.2 // indirect + go.uber.org/multierr v1.11.0 // indirect + go.uber.org/zap v1.27.0 // indirect ) diff --git a/code-samples/serving/cloudevents/cloudevents-go/go.sum b/code-samples/serving/cloudevents/cloudevents-go/go.sum index 258108f7e4..0a59e1ecc8 100644 --- a/code-samples/serving/cloudevents/cloudevents-go/go.sum +++ b/code-samples/serving/cloudevents/cloudevents-go/go.sum @@ -1,40 +1,37 @@ -github.com/cloudevents/sdk-go/v2 v2.15.2 h1:54+I5xQEnI73RBhWHxbI1XJcqOFOVJN85vb41+8mHUc= -github.com/cloudevents/sdk-go/v2 v2.15.2/go.mod h1:lL7kSWAE/V8VI4Wh0jbL2v/jvqsm6tjmaQBSvxcv4uE= +github.com/cloudevents/sdk-go/v2 v2.16.2 h1:ZYDFrYke4FD+jM8TZTJJO6JhKHzOQl2oqpFK1D+NnQM= +github.com/cloudevents/sdk-go/v2 v2.16.2/go.mod h1:laOcGImm4nVJEU+PHnUrKL56CKmRL65RlQF0kRmW/kg= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/google/go-cmp v0.5.0 h1:/QaMHBdZ26BB3SSst0Iwl10Epc+xhTquomWX0oZEB6w= -github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +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/uuid v1.1.1 h1:Gkbcsh/GbpXz7lPftLA3P6TYMwjCLYm83jiFQZF/3gY= -github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/json-iterator/go v1.1.10 h1:Kz6Cvnvv2wGdaG/V8yMvfkmNiXq9Ya2KUv4rouJJr68= -github.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= +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/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= +github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= github.com/kelseyhightower/envconfig v1.4.0 h1:Im6hONhd3pLkfDFsbRgu68RDNkGF1r3dvMUtDTo2cv8= github.com/kelseyhightower/envconfig v1.4.0/go.mod h1:cccZRl6mQpaq41TPp5QxidR+Sa3axMbJDNb//FQX6Gg= -github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421 h1:ZqeYNhU3OHLH3mGKHDcjJRFFRrJa6eAM5H+CtDdOsPc= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= -github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742 h1:Esafd1046DLDQ0W1YjYsBW+p8U2u7vzgW2SQVmlNazg= -github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= -github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= -github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= +github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= +github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= -github.com/stretchr/testify v1.8.0 h1:pSgiaMZlXftHpm5L7V1+rVB+AZJydKsMxsQBIJw4PKk= -github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= +github.com/stretchr/testify v1.11.0 h1:ib4sjIrwZKxE5u/Japgo/7SJV3PvgjGiRNAvTVGqQl8= +github.com/stretchr/testify v1.11.0/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw= github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc= -go.uber.org/atomic v1.4.0 h1:cxzIVoETapQEqDhQu3QfnvXAV4AlzcvUCxkVUFw3+EU= -go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= -go.uber.org/multierr v1.1.0 h1:HoEmRHQPVSqub6w2z2d2EOVs2fjyFRGyofhKuyDq0QI= -go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0= -go.uber.org/zap v1.10.0 h1:ORx85nbTijNz8ljznvCMR1ZBIPKFn3jQrag10X2AsuM= -go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= -golang.org/x/time v0.0.0-20210723032227-1f47c861a9ac h1:7zkz7BUtwNFFqcowJ+RIgu2MaV/MapERkDIy+mwPyjs= -golang.org/x/time v0.0.0-20210723032227-1f47c861a9ac/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= -golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4= -golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= +go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= +go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= +go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= +go.uber.org/zap v1.27.0 h1:aJMhYGrd5QSmlpLMr2MftRKl7t8J8PTZPA732ud/XR8= +go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E= +golang.org/x/time v0.12.0 h1:ScB/8o8olJvc+CQPWrK3fPZNfh7qgwCrY0zJmoEQLSE= +golang.org/x/time v0.12.0/go.mod h1:CDIdPxbZBQxdj6cxyCIdrNogrJKMJ7pr37NYpMcMDSg= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/code-samples/serving/cloudevents/cloudevents-go/service.yaml b/code-samples/serving/cloudevents/cloudevents-go/service.yaml index 1cf56fcf19..c82ef13211 100644 --- a/code-samples/serving/cloudevents/cloudevents-go/service.yaml +++ b/code-samples/serving/cloudevents/cloudevents-go/service.yaml @@ -7,7 +7,7 @@ spec: template: spec: containers: - - image: ko://github.com/knative/docs/code-samples/serving/cloudevents/cloudevents-go + - image: docker.io/{username}/cloudevents-go # Uncomment this to send events somewhere. # env: # - name: K_SINK From cbc1ac57225e780338b7d665efb83b720048702c Mon Sep 17 00:00:00 2001 From: Dave Protasowski Date: Mon, 26 Oct 2026 09:16:04 -0400 Subject: [PATCH 16/44] Update the Python Eventing sample and test its reply flow --- .../helloworld/helloworld-python/Dockerfile | 14 +- .../helloworld/helloworld-python/README.md | 410 +++--------------- .../helloworld/helloworld-python/ci.sh | 65 +++ .../helloworld-python/helloworld.py | 38 +- .../helloworld-python/requirements.txt | 2 + .../helloworld-python/sample-app.yaml | 65 ++- 6 files changed, 181 insertions(+), 413 deletions(-) create mode 100755 code-samples/eventing/helloworld/helloworld-python/ci.sh diff --git a/code-samples/eventing/helloworld/helloworld-python/Dockerfile b/code-samples/eventing/helloworld/helloworld-python/Dockerfile index 17920ce263..3b46e91a7e 100644 --- a/code-samples/eventing/helloworld/helloworld-python/Dockerfile +++ b/code-samples/eventing/helloworld/helloworld-python/Dockerfile @@ -1,13 +1,11 @@ -FROM python:3.9-alpine - -COPY . /app +FROM python:3.14.9-slim-trixie WORKDIR /app +COPY requirements.txt ./ +RUN pip install --no-cache-dir -r requirements.txt +COPY helloworld.py ./ -RUN pip install -r requirements.txt - +ENV PORT=8080 EXPOSE 8080 -ENTRYPOINT [ "python" ] - -CMD [ "helloworld.py" ] +CMD ["sh", "-c", "exec gunicorn --bind 0.0.0.0:${PORT} --access-logfile - helloworld:app"] diff --git a/code-samples/eventing/helloworld/helloworld-python/README.md b/code-samples/eventing/helloworld/helloworld-python/README.md index dca7863ef7..c28752e94b 100644 --- a/code-samples/eventing/helloworld/helloworld-python/README.md +++ b/code-samples/eventing/helloworld/helloworld-python/README.md @@ -1,374 +1,76 @@ -# Hello World - Python +# Hello World — Python -A simple web app written in Python that you can use to test knative eventing. It shows how to consume a [CloudEvent](https://cloudevents.io/) in Knative eventing, and optionally how to respond back with another CloudEvent in the http response, by adding the Cloud Eventing headers outlined in the Cloud Events standard definition. +This app uses the [CloudEvents Python SDK](https://github.com/cloudevents/sdk-python) +to decode binary or structured CloudEvents and return a new CloudEvent 1.0. +Gunicorn listens on `PORT` (default `8080`). -We will deploy the app as a [Kubernetes Deployment](https://kubernetes.io/docs/concepts/workloads/controllers/deployment/) along with a [Kubernetes Service](https://kubernetes.io/docs/concepts/services-networking/service/). -However, you can also deploy the app as a [Knative Serving Service](https://knative.dev/docs/serving/). +## Build and run -Do the following steps to create the sample code and then deploy the app to your -cluster. You can also download a working copy of the sample, by running the -following commands: +From this directory: -```bash -# Clone the relevant branch version such as "release-0.13" -git clone https://github.com/knative/docs.git knative-docs -cd knative-docs/code-samples/eventing/helloworld/helloworld-python +```sh +docker build -t helloworld-python . +docker run --rm -p 8080:8080 helloworld-python ``` -## Before you begin +Send a CloudEvent in another terminal: -- A Kubernetes cluster with [Knative Eventing](https://knative.dev/docs/install/eventing/install-eventing-with-yaml) installed. -- [Docker](https://www.docker.com) installed and running on your local machine, - and a Docker Hub account configured (we'll use it for a container registry). - -## Recreating the sample code - -1. Create a new file named `helloworld.py` and paste the following code. This - code creates a basic web server which listens on port 8080: - - ```python - from flask import Flask, request, make_response - import uuid - - app = Flask(__name__) - - @app.route('/', methods=['POST']) - def hello_world(): - app.logger.warning(request.data) - # Respond with another event (optional) - response = make_response({ - "msg": "Hi from helloworld-python app!" - }) - response.headers["Ce-Id"] = str(uuid.uuid4()) - response.headers["Ce-specversion"] = "0.3" - response.headers["Ce-Source"] = "knative/eventing/samples/hello-world" - response.headers["Ce-Type"] = "dev.knative.samples.hifromknative" - return response - - if __name__ == '__main__': - app.run(debug=True, host='0.0.0.0', port=8080) - ``` - -1. Add a `requirements.txt` file containing the following contents: - - ```bash - Flask==2.0.3 - ``` - -1. In your project directory, create a file named `Dockerfile` and copy the following code - block into it. For detailed instructions on dockerizing a Go app, see - [Deploying Go servers with Docker](https://blog.golang.org/docker). - - ```docker - FROM python:3.9-alpine - - COPY . /app - - WORKDIR /app - - RUN pip install -r requirements.txt - - EXPOSE 8080 - - ENTRYPOINT [ "python" ] - - CMD [ "helloworld.py" ] - - ``` - -1. Create a new file, `sample-app.yaml` and copy the following service definition into the file. Make sure to replace `{username}` with your Docker Hub username. - - ```yaml - # Namespace for sample application with eventing enabled - apiVersion: v1 - kind: Namespace - metadata: - name: knative-samples - labels: - eventing.knative.dev/injection: enabled - --- - # A default broker - apiVersion: eventing.knative.dev/v1 - kind: Broker - metadata: - name: default - namespace: knative-samples - annotations: - # Note: you can set the eventing.knative.dev/broker.class annotation to change the class of the broker. - # The default broker class is MTChannelBasedBroker, but Knative also supports use of the other class. - eventing.knative.dev/broker.class: MTChannelBasedBroker - spec: {} - --- - # Helloworld-python app deployment - apiVersion: apps/v1 - kind: Deployment - metadata: - name: helloworld-python - namespace: knative-samples - spec: - replicas: 1 - selector: - matchLabels: &labels - app: helloworld-python - template: - metadata: - labels: *labels - spec: - containers: - - name: helloworld-python - image: docker.io/{username}/helloworld-python - imagePullPolicy: IfNotPresent - --- - # Service that exposes helloworld-python app. - # This will be the subscriber for the Trigger - apiVersion: v1 - kind: Service - metadata: - name: helloworld-python - namespace: knative-samples - spec: - selector: - app: helloworld-python - ports: - - protocol: TCP - port: 80 - targetPort: 8080 - --- - # Knative Eventing Trigger to trigger the helloworld-python service - apiVersion: eventing.knative.dev/v1 - kind: Trigger - metadata: - name: helloworld-python - namespace: knative-samples - spec: - broker: default - filter: - attributes: - type: dev.knative.samples.helloworld - source: dev.knative.samples/helloworldsource - subscriber: - ref: - apiVersion: v1 - kind: Service - name: helloworld-python - ``` - -## Building and deploying the sample - -Once you have recreated the sample code files (or used the files in the sample -folder) you're ready to build and deploy the sample app. - -1. Use Docker to build the sample code into a container. To build and push with Docker Hub, run these commands replacing `{username}` with your Docker Hub username: - - ```bash - # Build and push the container on your local machine. - docker buildx build --platform linux/arm64,linux/amd64 -t "{username}/helloworld-python" --push . - ``` - -1. After the build has completed and the container is pushed to Docker Hub, you - can deploy the sample application into your cluster. Ensure that the container image value in `sample-app.yaml` matches the container you built in the previous step. Apply the configuration using `kubectl`: - - ```bash - kubectl apply -f sample-app.yaml - ``` - -1. The previous command creates a namespace `knative-samples` and labels it with `knative-eventing-injection=enabled`, to enable eventing in the namespace. Verify using the following command: - - ```bash - kubectl get ns knative-samples --show-labels - ``` - -1. It deployed the `helloworld-python` app as a K8s Deployment and created a K8s service names helloworld-python. Verify using the following command: - - ```bash - kubectl --namespace knative-samples get deployments helloworld-python - kubectl --namespace knative-samples get svc helloworld-python - ``` - -1. It created a Knative Eventing Trigger to route certain events to the helloworld-python application. Make sure that `Ready=true`: - - ```bash - kubectl -n knative-samples get trigger helloworld-python - ``` - -## Send and verify CloudEvents - -After you have deployed the application, and have verified that the namespace, sample application and trigger are ready, you can send a CloudEvent. - -### Send CloudEvent to the Broker - -You can send an HTTP request directly to the Knative [Broker](https://knative.dev/docs/eventing/broker/) if the correct CloudEvent headers are set. - -1. Deploy a curl pod and SSH into it: - - ```bash - kubectl --namespace knative-samples run curl --image=radial/busyboxplus:curl -it - ``` - -1. Run the following command in the SSH terminal: - - ```bash - curl -v "broker-ingress.knative-eventing.svc.cluster.local/knative-samples/default" \ - -X POST \ - -H "Ce-Id: 536808d3-88be-4077-9d7a-a3f162705f79" \ - -H "Ce-specversion: 0.3" \ - -H "Ce-Type: dev.knative.samples.helloworld" \ - -H "Ce-Source: dev.knative.samples/helloworldsource" \ - -H "Content-Type: application/json" \ - -d '{"msg":"Hello World from the curl pod."}' - exit - ``` - -### Verify that event is received by helloworld-python app - -The Helloworld-python app logs the context and the msg of the event you created earlier, and replies with another event. - - 1. Display helloworld-python app logs - ```bash - kubectl --namespace knative-samples logs -l app=helloworld-python --tail=50 - ``` - You should see something similar to: - ```{ .bash .no-copy } - Event received. Context: Context Attributes, - specversion: 0.3 - type: dev.knative.samples.helloworld - source: dev.knative.samples/helloworldsource - id: 536808d3-88be-4077-9d7a-a3f162705f79 - time: 2019-10-04T22:35:26.05871736Z - datacontenttype: application/json - Extensions, - knativearrivaltime: 2019-10-04T22:35:26Z - knativehistory: default-kn2-trigger-kn-channel.knative-samples.svc.cluster.local - traceparent: 00-971d4644229653483d38c46e92a959c7-92c66312e4bb39be-00 - - Hello World Message "Hello World from the curl pod." - Responded with event Validation: valid - Context Attributes, - specversion: 0.2 - type: dev.knative.samples.hifromknative - source: knative/eventing/samples/hello-world - id: 37458d77-01f5-411e-a243-a459bbf79682 - Data, - {"msg":"Hi from Knative!"} - - ``` - Try the CloudEvent attributes in the curl command and the trigger specification to understand how - [Triggers](https://knative.dev/docs/eventing/broker/triggers/) work. - -## Verify reply from helloworld-python app -The `helloworld-python` app replies with an event type `type= dev.knative.samples.hifromknative`, and source `source=knative/eventing/samples/hello-world`. The event enters the eventing mesh through the broker, and can be delivered to event sinks using a trigger - -1. Deploy a Pod that receives any CloudEvent and logs the event to its output. - - 1. Copy the following YAML into a file: - - ```yaml - # event-display app deploment - apiVersion: apps/v1 - kind: Deployment - metadata: - name: event-display - namespace: knative-samples - spec: - replicas: 1 - selector: - matchLabels: &labels - app: event-display - template: - metadata: - labels: *labels - spec: - containers: - - name: helloworld-python - image: gcr.io/knative-releases/knative.dev/eventing/cmd/event_display - --- - # Service that exposes event-display app. - # This will be the subscriber for the Trigger - kind: Service - apiVersion: v1 - metadata: - name: event-display - namespace: knative-samples - spec: - selector: - app: event-display - ports: - - protocol: TCP - port: 80 - targetPort: 8080 - ``` - - 1. Apply the YAML file by running the command: - - ```bash - kubectl apply -f .yaml - ``` - Where `` is the name of the file you created in the previous step. - -1. Create a trigger to deliver the event to the previously created service. - - 1. Copy the following YAML into a file: +```sh +curl -i http://localhost:8080/ \ + -H 'Ce-Id: hello-python' -H 'Ce-Specversion: 1.0' \ + -H 'Ce-Type: dev.knative.samples.helloworld' \ + -H 'Ce-Source: dev.knative.samples/helloworldsource' \ + -H 'Content-Type: application/json' -d '{"msg":"Hello World!"}' +``` - ```yaml - apiVersion: eventing.knative.dev/v1 - kind: Trigger - metadata: - name: event-display - namespace: knative-samples - spec: - broker: default - filter: - attributes: - type: dev.knative.samples.hifromknative - source: knative/eventing/samples/hello-world - subscriber: - ref: - apiVersion: v1 - kind: Service - name: event-display - ``` +The response contains the JSON message `Hi from helloworld-python app!` and SDK-generated +CloudEvent headers with type `dev.knative.samples.hifromknative` and source +`knative/eventing/samples/hello-world`. - 1. Apply the YAML file by running the command: +## Publish and deploy - ```bash - kubectl apply -f .yaml - ``` - Where `` is the name of the file you created in the previous step. +Use a cluster with Knative Serving, Eventing, InMemoryChannel and the +MTChannelBasedBroker installed. Build and push your image, replacing `{username}`: -1. [Send a CloudEvent to the Broker](#send-and-verify-cloudevents) +```sh +docker buildx build --platform linux/arm64,linux/amd64 \ + -t docker.io/{username}/helloworld-python --push . +``` -1. Check the logs of `event-display` Service: +Set that image in [`sample-app.yaml`](sample-app.yaml), then deploy the native +Broker, app, reply receiver and Triggers: - ```bash - kubectl -n knative-samples logs -l app=event-display --tail=50 - ``` +```sh +kubectl apply -f sample-app.yaml +kubectl -n knative-samples wait --for=condition=Ready --timeout=300s \ + ksvc/helloworld-python ksvc/event-display broker/default \ + trigger/helloworld-python trigger/event-display +``` - Example output: +Publish an event to the Broker from a temporary curl Pod: + +```sh +BROKER_URL=$(kubectl -n knative-samples get broker default -o jsonpath='{.status.address.url}') +kubectl -n knative-samples run curl --image=curlimages/curl --restart=Never --command -- sleep 300 +kubectl -n knative-samples wait --for=condition=Ready pod/curl --timeout=120s +kubectl -n knative-samples exec curl -- curl -i "$BROKER_URL" \ + -H 'Ce-Id: hello-python' -H 'Ce-Specversion: 1.0' \ + -H 'Ce-Type: dev.knative.samples.helloworld' \ + -H 'Ce-Source: dev.knative.samples/helloworldsource' \ + -H 'Content-Type: application/json' -d '{"msg":"Hello World!"}' +kubectl -n knative-samples delete pod curl +``` - ```{ .bash .no-copy } - cloudevents.Event - Validation: valid - Context Attributes, - specversion: 0.3 - type: dev.knative.samples.hifromknative - source: knative/eventing/samples/hello-world - id: 8a7384b9-8bbe-4634-bf0f-ead07e450b2a - time: 2019-10-04T22:53:39.844943931Z - datacontenttype: application/json - Extensions, - knativearrivaltime: 2019-10-04T22:53:39Z - knativehistory: default-kn2-ingress-kn-channel.knative-samples.svc.cluster.local - traceparent: 00-4b01db030b9ea04bb150b77c8fa86509-2740816590a7604f-00 - Data, - { - "msg": "Hi from helloworld- app!" - } - ``` +The Broker returns HTTP 202. Its input Trigger delivers the event to the Python +app; a second Trigger routes the app's reply to the stock `event-display` receiver. +Check for the reply type, source and message: -## Removing the sample app deployment +```sh +kubectl -n knative-samples logs -l serving.knative.dev/service=event-display -c user-container --tail=50 +``` -To remove the sample app from your cluster, delete the service record: +Remove the sample, including its namespace: -```bash +```sh kubectl delete -f sample-app.yaml ``` diff --git a/code-samples/eventing/helloworld/helloworld-python/ci.sh b/code-samples/eventing/helloworld/helloworld-python/ci.sh new file mode 100755 index 0000000000..69ebe340cc --- /dev/null +++ b/code-samples/eventing/helloworld/helloworld-python/ci.sh @@ -0,0 +1,65 @@ +#!/usr/bin/env bash +# Source from this sample directory; main runs only when the caller invokes it. +IMAGE=dev.local/eventing-helloworld-python:ci + +main() ( + set -euo pipefail + build + deploy + test +) + +build() ( + set -euo pipefail + docker build -t "$IMAGE" . +) + +deploy() ( + set -euo pipefail + kubectl apply -f https://github.com/knative/eventing/releases/latest/download/eventing-crds.yaml + kubectl wait --for=condition=Established --timeout=120s \ + crd/brokers.eventing.knative.dev crd/triggers.eventing.knative.dev + kubectl apply -f https://github.com/knative/eventing/releases/latest/download/eventing-core.yaml + kubectl -n knative-eventing wait deployment --all --for=condition=Available --timeout=300s + kubectl apply -f https://github.com/knative/eventing/releases/latest/download/in-memory-channel.yaml + kubectl apply -f https://github.com/knative/eventing/releases/latest/download/mt-channel-broker.yaml + kubectl -n knative-eventing wait deployment --all --for=condition=Available --timeout=300s + + kind load docker-image "$IMAGE" --name "${CLUSTER_NAME:-kind}" + sed "s|docker.io/{username}/helloworld-python|$IMAGE|" sample-app.yaml | kubectl apply -f - + kubectl -n knative-samples wait --for=condition=Ready --timeout=300s \ + ksvc/helloworld-python ksvc/event-display broker/default \ + trigger/helloworld-python trigger/event-display +) + +test() ( + set -euo pipefail + local broker_url status logs + broker_url=$(kubectl -n knative-samples get broker default -o jsonpath='{.status.address.url}') + kubectl -n knative-samples run curl --image=curlimages/curl --restart=Never --command -- sleep 300 + trap 'status=$?; kubectl -n knative-samples delete pod curl --ignore-not-found --wait=false || status=1; exit "$status"' EXIT + kubectl -n knative-samples wait --for=condition=Ready pod/curl --timeout=120s + status=$(kubectl -n knative-samples exec curl -- curl -fsS --max-time 30 \ + -o /dev/null -w '%{http_code}' "$broker_url" \ + -H 'Ce-Id: hello-python' -H 'Ce-Specversion: 1.0' \ + -H 'Ce-Type: dev.knative.samples.helloworld' \ + -H 'Ce-Source: dev.knative.samples/helloworldsource' \ + -H 'Content-Type: application/json' \ + -d '{"msg":"Hello World from the curl pod."}') + [[ "$status" == 202 ]] + + for _attempt in {1..30}; do + logs=$(kubectl -n knative-samples logs -l serving.knative.dev/service=event-display \ + -c user-container --tail=100 2>/dev/null || true) + if grep -Fq 'specversion: 1.0' <<< "$logs" && \ + grep -Fq 'type: dev.knative.samples.hifromknative' <<< "$logs" && \ + grep -Fq 'source: knative/eventing/samples/hello-world' <<< "$logs" && \ + grep -Fq '"msg": "Hi from helloworld-python app!"' <<< "$logs"; then + printf '%s\n' "$logs" + return 0 + fi + sleep 2 + done + echo 'Timed out waiting for the CloudEvent reply at event-display' >&2 + return 1 +) diff --git a/code-samples/eventing/helloworld/helloworld-python/helloworld.py b/code-samples/eventing/helloworld/helloworld-python/helloworld.py index a02024fecb..e6bf659975 100644 --- a/code-samples/eventing/helloworld/helloworld-python/helloworld.py +++ b/code-samples/eventing/helloworld/helloworld-python/helloworld.py @@ -1,22 +1,26 @@ - -from flask import Flask, request, make_response -import uuid +from cloudevents.core.bindings.http import HTTPMessage, from_http_event, to_binary_event +from cloudevents.core.exceptions import BaseCloudEventException +from cloudevents.core.v1.event import CloudEvent +from flask import Flask, request app = Flask(__name__) -@app.route('/', methods=['POST']) -def hello_world(): - app.logger.warning(request.data) - # Respond with another event (optional) - response = make_response({ - "msg": "Hi from helloworld-python app!" - }) - response.headers["Ce-Id"] = str(uuid.uuid4()) - response.headers["Ce-specversion"] = "0.3" - response.headers["Ce-Source"] = "knative/eventing/samples/hello-world" - response.headers["Ce-Type"] = "dev.knative.samples.hifromknative" - return response -if __name__ == '__main__': - app.run(debug=True, host='0.0.0.0', port=8080) +@app.post("/") +def hello_world(): + try: + event = from_http_event(HTTPMessage(dict(request.headers), request.get_data())) + except (BaseCloudEventException, ValueError) as error: + return {"error": str(error)}, 400 + app.logger.warning("Event received: %s", event.get_data()) + reply = CloudEvent( + { + "type": "dev.knative.samples.hifromknative", + "source": "knative/eventing/samples/hello-world", + "datacontenttype": "application/json", + }, + {"msg": "Hi from helloworld-python app!"}, + ) + message = to_binary_event(reply) + return message.body, 200, message.headers diff --git a/code-samples/eventing/helloworld/helloworld-python/requirements.txt b/code-samples/eventing/helloworld/helloworld-python/requirements.txt index af266d21cd..87f913d7bc 100644 --- a/code-samples/eventing/helloworld/helloworld-python/requirements.txt +++ b/code-samples/eventing/helloworld/helloworld-python/requirements.txt @@ -1 +1,3 @@ Flask==3.1.3 +cloudevents==2.2.0 +gunicorn==26.2.0 diff --git a/code-samples/eventing/helloworld/helloworld-python/sample-app.yaml b/code-samples/eventing/helloworld/helloworld-python/sample-app.yaml index a932d8faf0..d785549fd5 100644 --- a/code-samples/eventing/helloworld/helloworld-python/sample-app.yaml +++ b/code-samples/eventing/helloworld/helloworld-python/sample-app.yaml @@ -1,73 +1,70 @@ -# Namespace for sample application with eventing enabled apiVersion: v1 kind: Namespace metadata: name: knative-samples - labels: - eventing.knative.dev/injection: enabled --- -# A default broker apiVersion: eventing.knative.dev/v1 kind: Broker metadata: name: default namespace: knative-samples annotations: - # Note: you can set the eventing.knative.dev/broker.class annotation to change the class of the broker. - # The default broker class is MTChannelBasedBroker, but Knative also supports use of the other class. eventing.knative.dev/broker.class: MTChannelBasedBroker spec: {} --- -# helloworld-python app deployment -apiVersion: apps/v1 -kind: Deployment +apiVersion: serving.knative.dev/v1 +kind: Service metadata: name: helloworld-python namespace: knative-samples spec: - replicas: 1 - selector: - matchLabels: &labels - app: helloworld-python template: - metadata: - labels: *labels spec: containers: - - name: helloworld-python - image: docker.io/{username}/helloworld-python + - image: docker.io/{username}/helloworld-python imagePullPolicy: IfNotPresent --- -# Service that exposes helloworld-python app. -# This will be the subscriber for the Trigger -apiVersion: v1 -kind: Service +apiVersion: eventing.knative.dev/v1 +kind: Trigger metadata: name: helloworld-python namespace: knative-samples spec: - selector: - app: helloworld-python - ports: - - protocol: TCP - port: 80 - targetPort: 8080 + broker: default + filter: + attributes: + type: dev.knative.samples.helloworld + source: dev.knative.samples/helloworldsource + subscriber: + ref: + apiVersion: serving.knative.dev/v1 + kind: Service + name: helloworld-python +--- +apiVersion: serving.knative.dev/v1 +kind: Service +metadata: + name: event-display + namespace: knative-samples +spec: + template: + spec: + containers: + - image: gcr.io/knative-releases/knative.dev/eventing/cmd/event_display --- -# Knative Eventing Trigger to trigger the helloworld-python service apiVersion: eventing.knative.dev/v1 kind: Trigger metadata: - name: helloworld-python + name: event-display namespace: knative-samples spec: broker: default filter: attributes: - type: dev.knative.samples.helloworld - source: dev.knative.samples/helloworldsource + type: dev.knative.samples.hifromknative + source: knative/eventing/samples/hello-world subscriber: ref: - apiVersion: v1 + apiVersion: serving.knative.dev/v1 kind: Service - name: helloworld-python - + name: event-display From 6586c4cec4f728e33994fae218b8128c76a097db Mon Sep 17 00:00:00 2001 From: Dave Protasowski Date: Mon, 26 Oct 2026 09:16:04 -0400 Subject: [PATCH 17/44] Update the Kafka Channel sample --- code-samples/eventing/kafka/channel/README.md | 284 +++++++----------- .../kafka/channel/channel-default.yaml | 4 + .../eventing/kafka/channel/channel.yaml | 11 + .../kafka/channel/default-ch-webhook.yaml | 14 + .../eventing/kafka/channel/event-display.yaml | 9 + .../kafka/channel/event-source-rbac.yaml | 29 ++ .../eventing/kafka/channel/event-source.yaml | 16 + .../eventing/kafka/channel/kafka-channel.yaml | 7 + .../eventing/kafka/channel/subscription.yaml | 15 + 9 files changed, 212 insertions(+), 177 deletions(-) create mode 100644 code-samples/eventing/kafka/channel/channel-default.yaml create mode 100644 code-samples/eventing/kafka/channel/channel.yaml create mode 100644 code-samples/eventing/kafka/channel/default-ch-webhook.yaml create mode 100644 code-samples/eventing/kafka/channel/event-display.yaml create mode 100644 code-samples/eventing/kafka/channel/event-source-rbac.yaml create mode 100644 code-samples/eventing/kafka/channel/event-source.yaml create mode 100644 code-samples/eventing/kafka/channel/kafka-channel.yaml create mode 100644 code-samples/eventing/kafka/channel/subscription.yaml diff --git a/code-samples/eventing/kafka/channel/README.md b/code-samples/eventing/kafka/channel/README.md index aec662f231..416207d4f5 100644 --- a/code-samples/eventing/kafka/channel/README.md +++ b/code-samples/eventing/kafka/channel/README.md @@ -1,249 +1,179 @@ # Apache Kafka Channel Example You can install and configure the Apache Kafka Channel as the -default Channel configuration for Knative Eventing. +default Channel configuration for Knative Eventing, or select Kafka for an +individual Channel. ## Prerequisites - A Kubernetes cluster with - [Knative Eventing](https://knative.dev/docs/install/eventing/install-eventing-with-yaml), as well as the optional Broker and Kafka Channel components. + [Knative Eventing and the Kafka Channel components](https://knative.dev/docs/install/eventing/install-eventing-with-yaml/). +- An Apache Kafka cluster reachable by the Kafka Channel components. Configure + `bootstrap.servers` in the `kafka-channel-config` ConfigMap in the + `knative-eventing` namespace, as described in the + [KafkaChannel installation instructions](https://github.com/knative-extensions/eventing-kafka-broker/blob/main/docs/channel/README.md#installation). +- [Knative Serving](https://knative.dev/docs/install/serving/install-serving-with-yaml/) + for the event display Service. + +Clone this repository and use the checked-in manifests rather than copying them +from the guide: + +```bash +git clone https://github.com/knative/docs.git knative-docs +cd knative-docs/code-samples/eventing/kafka/channel +``` + +The commands below use the `default` namespace for sample resources. A Broker +implementation is not required. Changing the default Channel configuration is an +administrator operation that affects the cluster; skip that change on shared +clusters unless you have permission. ## Creating a Kafka Channel -1. Create a Kafka Channel that contains the following YAML: +Apply [kafka-channel.yaml](kafka-channel.yaml) to create `my-kafka-channel` +with three partitions and replication factor one: - ```yaml - apiVersion: messaging.knative.dev/v1beta1 - kind: KafkaChannel - metadata: - name: my-kafka-channel - spec: - numPartitions: 3 - replicationFactor: 1 - ``` - -1. Apply the YAML file by running the command: - - ```bash - kubectl apply -f .yaml - ``` - Where `` is the name of the file you created in the previous step. +```bash +kubectl -n default apply -f kafka-channel.yaml +``` ## Specifying Kafka as the default Channel implementation -1. To configure Kafka Channel as the [default channel configuration](https://knative.dev/docs/eventing/channels/channel-types-defaults), modify the `default-ch-webhook` ConfigMap so that it contains the following YAML: +To configure Kafka Channel as the +[default channel configuration](https://knative.dev/docs/eventing/channels/channel-types-defaults), +review [default-ch-webhook.yaml](default-ch-webhook.yaml). Merge its +`default-ch-config` with the existing `default-ch-webhook` ConfigMap in +`knative-eventing`; preserve any existing namespace overrides. If you are +intentionally replacing the configuration with this example, apply it with: - ```yaml - apiVersion: v1 - kind: ConfigMap - metadata: - name: default-ch-webhook - namespace: knative-eventing - data: - # Configuration for defaulting channels that do not specify CRD implementations. - default-ch-config: | - clusterDefault: - apiVersion: messaging.knative.dev/v1beta1 - kind: KafkaChannel - spec: - numPartitions: 3 - replicationFactor: 1 - ``` - -1. Apply the YAML file by running the command: - - ```bash - kubectl apply -f .yaml - ``` +```bash +kubectl apply -f default-ch-webhook.yaml +``` - Where `` is the name of the file you created in the previous step. +This selects `KafkaChannel` with three partitions and replication factor one +for generic Channels without an explicit implementation. It is not required +when using the per-Channel template in the next section. ## Creating an Apache Kafka channel -1. After `KafkaChannel` is set as the default Channel type, you can create a Kafka Channel by creating a generic Channel object that contains the following YAML: +Choose **one** of these manifests to create `testchannel-one`: - ```yaml - apiVersion: messaging.knative.dev/v1 - kind: Channel - metadata: - name: testchannel-one - ``` - -1. Apply the YAML file by running the command: - - ```bash - kubectl apply -f .yaml - ``` - - Where `` is the name of the file you created in the previous step. - -1. Verify that the Channel was created properly by checking that your Kafka cluster has a `testchannel-one` Topic. If you are using Strimzi, you can run the command: +- After KafkaChannel is configured as the default, use + [channel-default.yaml](channel-default.yaml). This generic Channel inherits + the administrator's default implementation: ```bash - kubectl -n kafka exec -it my-cluster-kafka-0 -- bin/kafka-topics.sh --bootstrap-server my-cluster-kafka-bootstrap:9092 --list + kubectl -n default apply -f channel-default.yaml ``` - The output looks similar to the following: +- On a shared cluster, use [channel.yaml](channel.yaml) **instead**. Its explicit + `spec.channelTemplate` selects KafkaChannel with three partitions and + replication factor one for this Channel only, without changing defaults: ```bash - ... - __consumer_offsets - knative-messaging-kafka.default.testchannel-one - ... + kubectl -n default apply -f channel.yaml ``` - The Kafka Topic that is created by the Channel contains the name of the namespace, `default` in this example, followed by the name of the Channel. +Wait for the Channel to become ready and check its backing implementation: -**Note:** The topic of a Kafka Channel is an implementation detail and records from it should not be consumed from different applications. +```bash +kubectl -n default wait --for=condition=Ready channels.messaging.knative.dev/testchannel-one --timeout=120s +kubectl -n default get channels.messaging.knative.dev/testchannel-one -o jsonpath='{.spec.channelTemplate}' +``` -## Creating a Service and Trigger that use the Apache Kafka Broker +The template must identify `messaging.knative.dev/v1beta1`, `KafkaChannel`, +with `numPartitions: 3` and `replicationFactor: 1`. -The following example uses a ApiServerSource to publish events to an existing Broker, and a Trigger that routes those events to a Knative Service. - - -1. Create a Knative Service: - - ```yaml - apiVersion: serving.knative.dev/v1 - kind: Service - metadata: - name: broker-kafka-display - spec: - template: - spec: - containers: - - image: gcr.io/knative-releases/knative.dev/eventing/cmd/event_display - ``` +You can also inspect the topics in your Kafka cluster. If you are using Strimzi, +you can run the command: -1. Apply the YAML file by running the command: +```bash +kubectl -n kafka exec -it my-cluster-kafka-0 -- bin/kafka-topics.sh --bootstrap-server my-cluster-kafka-bootstrap:9092 --list +``` - ```bash - kubectl apply -f .yaml - ``` +Topic names vary by KafkaChannel implementation and version; do not depend +on a particular topic naming convention. Check Channel readiness rather than +using a topic name alone to determine whether the Channel is working. - Where `` is the name of the file you created in the previous step. +**Note:** The topic of a Kafka Channel is an implementation detail and records +from it should not be consumed from different applications. -1. Create a ServiceAccount, ClusterRole, and ClusterRoleBinding for the ApiServerSource: +## Sending events through the Kafka Channel - ```yaml - apiVersion: v1 - kind: ServiceAccount - metadata: - name: events-sa - namespace: default +The following example uses an ApiServerSource to publish Kubernetes events to +`testchannel-one`, and a Subscription to deliver those events from the Channel +to a Knative Service. The separate `my-kafka-channel` created earlier demonstrates +the explicit KafkaChannel API and is not used in this event flow. - --- +1. Apply [event-display.yaml](event-display.yaml) to create the Knative Service: - apiVersion: rbac.authorization.k8s.io/v1 - kind: ClusterRole - metadata: - name: event-watcher - rules: - - apiGroups: - - "" - resources: - - events - verbs: - - get - - list - - watch - - --- - - apiVersion: rbac.authorization.k8s.io/v1 - kind: ClusterRoleBinding - metadata: - name: k8s-ra-event-watcher - roleRef: - apiGroup: rbac.authorization.k8s.io - kind: ClusterRole - name: event-watcher - subjects: - - kind: ServiceAccount - name: events-sa - namespace: default + ```bash + kubectl -n default apply -f event-display.yaml ``` -1. Apply the YAML file by running the command: +1. Apply [event-source-rbac.yaml](event-source-rbac.yaml) to create the + ServiceAccount, Role and RoleBinding. These grant access to events only in + the `default` namespace: ```bash - kubectl apply -f .yaml + kubectl -n default apply -f event-source-rbac.yaml ``` - Where `` is the name of the file you created in the previous step. +1. Apply [event-source.yaml](event-source.yaml) to create the ApiServerSource + that sends events to the Channel: -1. Create an ApiServerSource that sends events to the default Broker: - - ```yaml - apiVersion: sources.knative.dev/v1 - kind: ApiServerSource - metadata: - name: testevents-kafka-03 - namespace: default - spec: - serviceAccountName: events-sa - mode: Resource - resources: - - apiVersion: v1 - kind: Event - sink: - ref: - apiVersion: eventing.knative.dev/v1 - kind: Broker - name: default + ```bash + kubectl -n default apply -f event-source.yaml ``` -1. Apply the YAML file by running the command: +1. Apply [subscription.yaml](subscription.yaml) to deliver events from the + Channel to the Service: ```bash - kubectl apply -f .yaml + kubectl -n default apply -f subscription.yaml ``` - Where `` is the name of the file you created in the previous step. - -1. Create a Trigger that filters events from the Broker to the Service: +1. Wait for the Service, Subscription and source to become ready, then create a + Kubernetes event with a recognizable message: - ```yaml - apiVersion: eventing.knative.dev/v1 - kind: Trigger + ```bash + kubectl -n default wait --for=condition=Ready service.serving.knative.dev/kafka-display subscription/testevents-subscription apiserversource/testevents-kafka-03 --timeout=180s + kubectl -n default create -f - <<'EOF' + apiVersion: v1 + kind: Event metadata: - name: testevents-trigger + name: kafka-channel-smoke + involvedObject: + apiVersion: messaging.knative.dev/v1 + kind: Channel + name: testchannel-one namespace: default - spec: - broker: default - subscriber: - ref: - apiVersion: serving.knative.dev/v1 - kind: Service - name: broker-kafka-display + reason: KafkaChannelSmoke + message: Kafka Channel smoke test + type: Normal + EOF ``` -1. Apply the YAML file by running the command: +1. Verify delivery through the Kafka Channel by observing the Service logs: ```bash - kubectl apply -f .yaml + kubectl -n default logs --selector='serving.knative.dev/service=kafka-display' -c user-container --tail=100 ``` - Where `` is the name of the file you created in the previous step. - -1. Verifying the Kafka Channel is working, by observing events in the log of the Service, by running the command: - - ```bash - kubectl logs --selector='serving.knative.dev/service=broker-kafka-display' -c user-container - ``` + The logs should contain a CloudEvent of type + `dev.knative.apiserver.resource.add` whose data includes the message + `Kafka Channel smoke test`. Delivery is asynchronous; repeat the log command + if necessary. Other Kubernetes events may also appear. ## Channel configuration KafkaChannel provides various configuration options, such as: - Authentication - - Encryption - -- Kafka Topic default values (number of partitions, replication factor, and +- Kafka Topic default values (number of partitions, replication factor and retention time) - - The brokers string for your Kafka connection For detailed information on configuration options, see the diff --git a/code-samples/eventing/kafka/channel/channel-default.yaml b/code-samples/eventing/kafka/channel/channel-default.yaml new file mode 100644 index 0000000000..3f76eb871c --- /dev/null +++ b/code-samples/eventing/kafka/channel/channel-default.yaml @@ -0,0 +1,4 @@ +apiVersion: messaging.knative.dev/v1 +kind: Channel +metadata: + name: testchannel-one diff --git a/code-samples/eventing/kafka/channel/channel.yaml b/code-samples/eventing/kafka/channel/channel.yaml new file mode 100644 index 0000000000..b9161937c3 --- /dev/null +++ b/code-samples/eventing/kafka/channel/channel.yaml @@ -0,0 +1,11 @@ +apiVersion: messaging.knative.dev/v1 +kind: Channel +metadata: + name: testchannel-one +spec: + channelTemplate: + apiVersion: messaging.knative.dev/v1beta1 + kind: KafkaChannel + spec: + numPartitions: 3 + replicationFactor: 1 diff --git a/code-samples/eventing/kafka/channel/default-ch-webhook.yaml b/code-samples/eventing/kafka/channel/default-ch-webhook.yaml new file mode 100644 index 0000000000..aba04d78be --- /dev/null +++ b/code-samples/eventing/kafka/channel/default-ch-webhook.yaml @@ -0,0 +1,14 @@ +apiVersion: v1 +kind: ConfigMap +metadata: + name: default-ch-webhook + namespace: knative-eventing +data: + # Configuration for defaulting channels without an explicit implementation. + default-ch-config: | + clusterDefault: + apiVersion: messaging.knative.dev/v1beta1 + kind: KafkaChannel + spec: + numPartitions: 3 + replicationFactor: 1 diff --git a/code-samples/eventing/kafka/channel/event-display.yaml b/code-samples/eventing/kafka/channel/event-display.yaml new file mode 100644 index 0000000000..367ae81858 --- /dev/null +++ b/code-samples/eventing/kafka/channel/event-display.yaml @@ -0,0 +1,9 @@ +apiVersion: serving.knative.dev/v1 +kind: Service +metadata: + name: kafka-display +spec: + template: + spec: + containers: + - image: gcr.io/knative-releases/knative.dev/eventing/cmd/event_display diff --git a/code-samples/eventing/kafka/channel/event-source-rbac.yaml b/code-samples/eventing/kafka/channel/event-source-rbac.yaml new file mode 100644 index 0000000000..7f1f03dae2 --- /dev/null +++ b/code-samples/eventing/kafka/channel/event-source-rbac.yaml @@ -0,0 +1,29 @@ +apiVersion: v1 +kind: ServiceAccount +metadata: + name: events-sa + namespace: default +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: Role +metadata: + name: event-watcher + namespace: default +rules: +- apiGroups: [""] + resources: [events] + verbs: [get, list, watch] +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: RoleBinding +metadata: + name: k8s-ra-event-watcher + namespace: default +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: Role + name: event-watcher +subjects: +- kind: ServiceAccount + name: events-sa + namespace: default diff --git a/code-samples/eventing/kafka/channel/event-source.yaml b/code-samples/eventing/kafka/channel/event-source.yaml new file mode 100644 index 0000000000..c4b68e06ca --- /dev/null +++ b/code-samples/eventing/kafka/channel/event-source.yaml @@ -0,0 +1,16 @@ +apiVersion: sources.knative.dev/v1 +kind: ApiServerSource +metadata: + name: testevents-kafka-03 + namespace: default +spec: + serviceAccountName: events-sa + mode: Resource + resources: + - apiVersion: v1 + kind: Event + sink: + ref: + apiVersion: messaging.knative.dev/v1 + kind: Channel + name: testchannel-one diff --git a/code-samples/eventing/kafka/channel/kafka-channel.yaml b/code-samples/eventing/kafka/channel/kafka-channel.yaml new file mode 100644 index 0000000000..8b5e02f9e2 --- /dev/null +++ b/code-samples/eventing/kafka/channel/kafka-channel.yaml @@ -0,0 +1,7 @@ +apiVersion: messaging.knative.dev/v1beta1 +kind: KafkaChannel +metadata: + name: my-kafka-channel +spec: + numPartitions: 3 + replicationFactor: 1 diff --git a/code-samples/eventing/kafka/channel/subscription.yaml b/code-samples/eventing/kafka/channel/subscription.yaml new file mode 100644 index 0000000000..79d041c0b1 --- /dev/null +++ b/code-samples/eventing/kafka/channel/subscription.yaml @@ -0,0 +1,15 @@ +apiVersion: messaging.knative.dev/v1 +kind: Subscription +metadata: + name: testevents-subscription + namespace: default +spec: + channel: + apiVersion: messaging.knative.dev/v1 + kind: Channel + name: testchannel-one + subscriber: + ref: + apiVersion: serving.knative.dev/v1 + kind: Service + name: kafka-display From 128b510f269d5b5efa5eef4149473cf62f5e9f10 Mon Sep 17 00:00:00 2001 From: Dave Protasowski Date: Mon, 26 Oct 2026 09:16:04 -0400 Subject: [PATCH 18/44] Consolidate the Parallel routing sample using the CloudEvents SDK --- code-samples/eventing/parallel/README.md | 203 ++++++++++++++-- .../event-display.yaml | 0 .../eventing/parallel/functions/Dockerfile | 12 + .../eventing/parallel/functions/README.md | 57 +++++ .../eventing/parallel/functions/go.mod | 13 ++ .../eventing/parallel/functions/go.sum | 35 +++ .../eventing/parallel/functions/main.go | 112 +++++++++ code-samples/eventing/parallel/helpers.yaml | 10 + .../parallel/multiple-branches/README.md | 216 ------------------ .../parallel/multiple-branches/filters.yaml | 27 --- .../multiple-branches/transformers.yaml | 28 --- .../parallel/mutual-exclusivity/README.md | 200 ---------------- .../mutual-exclusivity/event-display.yaml | 9 - .../mutual-exclusivity/ping-source.yaml | 13 -- .../parallel/mutual-exclusivity/switcher.yaml | 14 -- .../mutual-exclusivity/transformers.yaml | 28 --- .../parallel.yaml => parallel-filters.yaml} | 22 +- .../parallel.yaml => parallel-switch.yaml} | 12 +- .../{multiple-branches => }/ping-source.yaml | 0 19 files changed, 448 insertions(+), 563 deletions(-) rename code-samples/eventing/parallel/{multiple-branches => }/event-display.yaml (100%) create mode 100644 code-samples/eventing/parallel/functions/Dockerfile create mode 100644 code-samples/eventing/parallel/functions/README.md create mode 100644 code-samples/eventing/parallel/functions/go.mod create mode 100644 code-samples/eventing/parallel/functions/go.sum create mode 100644 code-samples/eventing/parallel/functions/main.go create mode 100644 code-samples/eventing/parallel/helpers.yaml delete mode 100644 code-samples/eventing/parallel/multiple-branches/README.md delete mode 100644 code-samples/eventing/parallel/multiple-branches/filters.yaml delete mode 100644 code-samples/eventing/parallel/multiple-branches/transformers.yaml delete mode 100644 code-samples/eventing/parallel/mutual-exclusivity/README.md delete mode 100644 code-samples/eventing/parallel/mutual-exclusivity/event-display.yaml delete mode 100644 code-samples/eventing/parallel/mutual-exclusivity/ping-source.yaml delete mode 100644 code-samples/eventing/parallel/mutual-exclusivity/switcher.yaml delete mode 100644 code-samples/eventing/parallel/mutual-exclusivity/transformers.yaml rename code-samples/eventing/parallel/{mutual-exclusivity/parallel.yaml => parallel-filters.yaml} (51%) rename code-samples/eventing/parallel/{multiple-branches/parallel.yaml => parallel-switch.yaml} (74%) rename code-samples/eventing/parallel/{multiple-branches => }/ping-source.yaml (100%) diff --git a/code-samples/eventing/parallel/README.md b/code-samples/eventing/parallel/README.md index ec2536df58..e32f686906 100644 --- a/code-samples/eventing/parallel/README.md +++ b/code-samples/eventing/parallel/README.md @@ -1,29 +1,196 @@ -# Parallel Example +# Parallel event routing: filters and switch cases -The following examples will help you understand how to use Parallel to describe -various flows. +This tutorial sends a minute-scheduled CloudEvent through two Parallel branches, +transforms its data according to rounded timestamp parity, and displays the reply. +One small [Go helper](functions/README.md), one display Service and one PingSource +support **two alternative routing configurations**. Build the helper once; choose +one configuration, not two separate applications. + +The data starts as `{"message":"Even or odd?"}`. A selected even branch returns +`{"message":"we are even!"}`; a selected odd branch returns +`{"message":"this is odd!"}`. Both configurations send transformer replies to the +same global `event-display` sink. ## Prerequisites -All examples require: +- Kubernetes with Knative Serving and Knative Eventing, including Parallel, + PingSource and the InMemoryChannel implementation explicitly used here. +- Docker to build the helper image. +- `kubectl`, and `kind` only for the local-kind image workflow below. + +Run the following commands from `code-samples/eventing/parallel/`. + +## Build one helper image + +Build the [Go helper image](functions/README.md): + +```bash +IMAGE=dev.local/parallel-helpers:go-v1 +docker build -t "$IMAGE" ./functions +``` + +### Local kind cluster + +The supplied [helper Service](helpers.yaml) uses the local-development reference +`dev.local/parallel-helpers:go-v1` with `IfNotPresent`. Load that one image: + +```bash +kind load docker-image "$IMAGE" --name +``` + +Knative must skip remote tag resolution for the local prefix (`dev.local/` is +excluded by default). See +[skipping tag resolution](https://knative.dev/docs/serving/deploying-from-private-registry/#skipping-tag-resolution) +if your installation overrides this. Host Docker image presence alone is not +node image availability. + +### Registry-backed cluster + +The local reference is a convenience, not a required registry. For a normal +cluster, build/tag the same shared context for a registry accessible to its nodes: + +```bash +IMAGE=registry.example.com/your-project/parallel-helpers:go-v1 +docker build -t "$IMAGE" ./functions +docker push "$IMAGE" +``` + +Replace the single helper `image:` in [helpers.yaml](helpers.yaml) with this exact +reference before applying it; configure pull credentials if needed. Keep the +external [event-display image](event-display.yaml) unchanged. This path needs no +kind loading or local tag-resolution exception. Choose one image workflow, not +both, unless you need both targets. + +## Create the common Services + +Use the same namespace for every resource. All destinations use Service references +and relative handler URIs, so neither configuration hardcodes a namespace: + +```bash +NAMESPACE=parallel-demo +kubectl create namespace "$NAMESPACE" +kubectl -n "$NAMESPACE" apply -f ./helpers.yaml -f ./event-display.yaml +kubectl -n "$NAMESPACE" wait ksvc/parallel-helpers ksvc/event-display \ + --for=condition=Ready --timeout=180s +``` + +[helpers.yaml](helpers.yaml) runs the one helper image. Every instance exposes all +six explicit paths; Parallel selects which operations execute. The shared +[event-display.yaml](event-display.yaml) logs the final events. + +## Choose a routing configuration + +Both files define the **same** `odd-even-parallel` resource. Apply exactly one. +They differ in filter paths, not their source, transformers or global reply: + +| Configuration | First branch | Second branch | Manifest | +| --- | --- | --- | --- | +| Independent predicates | `/filter/even` → `/transform/even` | `/filter/odd` → `/transform/odd` | [parallel-filters.yaml](parallel-filters.yaml) | +| Explicit switch cases | `/switch/0` → `/transform/even` | `/switch/1` → `/transform/odd` | [parallel-switch.yaml](parallel-switch.yaml) | + +### Filter routing + +Each branch independently tests the rounded epoch-minute remainder. The even +predicate accepts remainder 0; the odd predicate accepts a nonzero remainder. +For this example the predicates are complementary, but Parallel itself does not +promise mutual exclusion between independent filters. + +```bash +kubectl -n "$NAMESPACE" apply -f ./parallel-filters.yaml +``` + +### Switch routing + +Alternatively, the two explicit case endpoints accept only remainder 0 or 1. +They select at most one subscriber for each event (subject to normal Eventing +redelivery, not an exactly-once delivery guarantee): + +```bash +kubectl -n "$NAMESPACE" apply -f ./parallel-switch.yaml +``` + +For modern timestamps, both configurations produce the same even/odd messages. +Minute parity is calculated as +`Math.round(timestamp_ms / 60000) % 2`: millisecond precision, ties toward positive +infinity and signed remainder. At 29.999 seconds rounding uses the current +minute; at 30 seconds it uses the next minute. Before the Unix epoch, a negative +odd rounded minute has remainder -1: the odd **filter** accepts it, but neither +switch case matches. See the [rounding contract](functions/README.md#minute-rounding). + +In either configuration a match returns the unchanged CloudEvent to its +subscriber. A no-match returns empty HTTP 200, with no event attributes/body and +therefore no subscriber or global reply for that branch. Transformers replace +only the data while preserving context/extensions. Binary and structured JSON +CloudEvents 1.0 are supported, with helper replies retaining input encoding. +Missing/invalid parity time returns 400. The helper contract also documents +request limits, errors, health checks and `PORT` (default 8080). + +## Start the shared PingSource + +After choosing one configuration, wait for it and apply the shared source: + +```bash +kubectl -n "$NAMESPACE" wait parallel/odd-even-parallel \ + --for=condition=Ready --timeout=180s +kubectl -n "$NAMESPACE" apply -f ./ping-source.yaml +kubectl -n "$NAMESPACE" wait pingsource/ping-source \ + --for=condition=Ready --timeout=180s +``` + +[ping-source.yaml](ping-source.yaml) emits `{"message":"Even or odd?"}` every +minute, targeting `odd-even-parallel`, not a helper or display endpoint directly. + +## Inspect behavior + +Wait at least two minute ticks to observe both parities: + +```bash +kubectl -n "$NAMESPACE" logs -l serving.knative.dev/service=event-display \ + --tail=100 -c user-container +``` + +The data alternates between `{"message":"we are even!"}` and +`{"message":"this is odd!"}`. The original PingSource ID, source, time and +`dev.knative.sources.ping` type remain attached to each reply. For example, +`2020-03-03T21:24:00Z` selects even and `2020-03-03T21:24:30Z` rounds to odd. + +The helper logs show each received event's ID, path and `matched` decision: + +```bash +kubectl -n "$NAMESPACE" logs -l serving.knative.dev/service=parallel-helpers \ + --tail=100 -c user-container +``` + +An even event reaches both `/filter/*` or both `/switch/*` paths, depending on the +configuration. Only the selected path has `matched:true`, and only its matching +`/transform/*` path receives that ID. A switch event with remainder -1 reaches both +case paths with `matched:false` and produces no transformer or display event. -- A Kubernetes cluster with - - Knative Eventing - - Knative Serving +## Switch configurations -All examples are using the -[default channel template](https://knative.dev/docs/eventing/channels/create-default-channel). +Keep the helper image and common Services. To avoid changing routing while the +source is actively emitting, stop the source and remove the old Parallel first: -## Examples +```bash +kubectl -n "$NAMESPACE" delete -f ./ping-source.yaml +kubectl -n "$NAMESPACE" delete parallel/odd-even-parallel --wait=true +# Choose the other routing file (use parallel-filters.yaml to switch back). +kubectl -n "$NAMESPACE" apply -f ./parallel-switch.yaml +kubectl -n "$NAMESPACE" wait parallel/odd-even-parallel \ + --for=condition=Ready --timeout=180s +kubectl -n "$NAMESPACE" apply -f ./ping-source.yaml +``` -Each of the following examples use -[`PingSource`](https://knative.dev/docs/eventing/sources/ping-source/) as the source of events. +In-flight deliveries may finish under the previous routing configuration; this is +not a transactional update or durable/exactly-once processing guarantee. Use fresh +namespaces if you need isolated observations of the two configurations. -We also use simple -[functions](https://github.com/lionelvillard/knative-functions) to perform -trivial filtering, transformation and routing of the incoming events. +## Cleanup -The examples are: +These names remove whichever routing variant you selected: -- [Parallel with multiple branches and global reply](multiple-branches/README.md) -- [Parallel with mutually exclusive cases](mutual-exclusivity/README.md) +```bash +kubectl -n "$NAMESPACE" delete pingsource/ping-source parallel/odd-even-parallel +kubectl -n "$NAMESPACE" delete -f ./helpers.yaml -f ./event-display.yaml +kubectl delete namespace "$NAMESPACE" +``` diff --git a/code-samples/eventing/parallel/multiple-branches/event-display.yaml b/code-samples/eventing/parallel/event-display.yaml similarity index 100% rename from code-samples/eventing/parallel/multiple-branches/event-display.yaml rename to code-samples/eventing/parallel/event-display.yaml diff --git a/code-samples/eventing/parallel/functions/Dockerfile b/code-samples/eventing/parallel/functions/Dockerfile new file mode 100644 index 0000000000..4c65805793 --- /dev/null +++ b/code-samples/eventing/parallel/functions/Dockerfile @@ -0,0 +1,12 @@ +FROM golang:1.27 AS build +WORKDIR /src +COPY go.mod go.sum ./ +RUN go mod download +COPY main.go ./ +RUN go vet ./... && CGO_ENABLED=0 go build -mod=readonly -trimpath -ldflags="-s -w" -o /parallel . + +FROM scratch +COPY --from=build /parallel /parallel +USER 65532:65532 +EXPOSE 8080 +ENTRYPOINT ["/parallel"] diff --git a/code-samples/eventing/parallel/functions/README.md b/code-samples/eventing/parallel/functions/README.md new file mode 100644 index 0000000000..cd97e8b52c --- /dev/null +++ b/code-samples/eventing/parallel/functions/README.md @@ -0,0 +1,57 @@ +# Go helpers for Parallel routing + +A Go HTTP application uses the [CloudEvents Go SDK](https://github.com/cloudevents/sdk-go) +for the filter, switch and transform operations in the [Parallel tutorial](../README.md). + +| POST path | Operation | +| --- | --- | +| `/filter/even` | Return the input event when the rounded minute remainder is 0. | +| `/filter/odd` | Return the input event when the remainder is not 0. | +| `/switch/0` | Return the input event only when the remainder equals case 0. | +| `/switch/1` | Return the input event only when the remainder equals case 1. | +| `/transform/even` | Replace data with `{"message":"we are even!"}`. | +| `/transform/odd` | Replace data with `{"message":"this is odd!"}`. | + +Every instance exposes all six paths; the Parallel destinations select their +paths. A filter/switch no-match returns HTTP **200 with no event headers or body**, +so that branch has no reply. Accepted filters/switches preserve the event context and data. +Transformers preserve event context and extensions while replacing the data and +setting its content type to `application/json`. + +Both **binary** and **structured JSON** CloudEvents 1.0 are supported, and replies +retain the input encoding. Binary responses copy CloudEvent attributes, not +request transport/authorization headers. Structured responses preserve attributes +(including extensions), replacing `data` and removing `data_base64` when +transforming. Event bodies are limited to 1 MiB. Missing required context, +unsupported specversion, malformed structured JSON or invalid/missing parity time +return HTTP 400. Transformers do not need a time attribute. Unknown paths return +404; event paths require POST (otherwise 405). + +## Minute rounding + +Event timestamps are rounded to the nearest minute, with ties rounded toward +positive infinity. At 29.999 seconds the current minute is used; at 30 seconds +the next minute is used. Before the Unix epoch, an odd rounded minute has +remainder -1: `/filter/odd` matches it but `/switch/1` does not. Invalid timestamps +return HTTP 400. + +## Build + +Run from `code-samples/eventing/parallel/` to build the helper image: + +```bash +IMAGE=dev.local/parallel-helpers:go-v1 +docker build -t "$IMAGE" ./functions +``` + +Go dependencies are recorded in `go.mod` and `go.sum`. + +`PORT` defaults to **8080** and can be changed, for example: + +```bash +docker run --rm -e PORT=9090 -p 127.0.0.1:9090:9090 dev.local/parallel-helpers:go-v1 +``` + +`GET /healthz` returns 200. Event operations log the event ID, path and match +decision. Follow the tutorial's [filter routing](../README.md#filter-routing) or +[switch routing](../README.md#switch-routing) instructions to deploy. diff --git a/code-samples/eventing/parallel/functions/go.mod b/code-samples/eventing/parallel/functions/go.mod new file mode 100644 index 0000000000..797cdde4ff --- /dev/null +++ b/code-samples/eventing/parallel/functions/go.mod @@ -0,0 +1,13 @@ +module knative.dev/docs/parallel-functions + +go 1.27.0 + +require github.com/cloudevents/sdk-go/v2 v2.16.2 + +require ( + github.com/json-iterator/go v1.1.12 // indirect + github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect + github.com/modern-go/reflect2 v1.0.2 // indirect + go.uber.org/multierr v1.11.0 // indirect + go.uber.org/zap v1.27.0 // indirect +) diff --git a/code-samples/eventing/parallel/functions/go.sum b/code-samples/eventing/parallel/functions/go.sum new file mode 100644 index 0000000000..9a620caf0d --- /dev/null +++ b/code-samples/eventing/parallel/functions/go.sum @@ -0,0 +1,35 @@ +github.com/cloudevents/sdk-go/v2 v2.16.2 h1:ZYDFrYke4FD+jM8TZTJJO6JhKHzOQl2oqpFK1D+NnQM= +github.com/cloudevents/sdk-go/v2 v2.16.2/go.mod h1:laOcGImm4nVJEU+PHnUrKL56CKmRL65RlQF0kRmW/kg= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +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/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= +github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= +github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= +github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= +github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= +github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +github.com/stretchr/testify v1.11.0 h1:ib4sjIrwZKxE5u/Japgo/7SJV3PvgjGiRNAvTVGqQl8= +github.com/stretchr/testify v1.11.0/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= +github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw= +github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc= +go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= +go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= +go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= +go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= +go.uber.org/zap v1.27.0 h1:aJMhYGrd5QSmlpLMr2MftRKl7t8J8PTZPA732ud/XR8= +go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E= +golang.org/x/time v0.12.0 h1:ScB/8o8olJvc+CQPWrK3fPZNfh7qgwCrY0zJmoEQLSE= +golang.org/x/time v0.12.0/go.mod h1:CDIdPxbZBQxdj6cxyCIdrNogrJKMJ7pr37NYpMcMDSg= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/code-samples/eventing/parallel/functions/main.go b/code-samples/eventing/parallel/functions/main.go new file mode 100644 index 0000000000..aab4a4bd34 --- /dev/null +++ b/code-samples/eventing/parallel/functions/main.go @@ -0,0 +1,112 @@ +// Copyright 2026 The Knative Authors +// SPDX-License-Identifier: Apache-2.0 + +// The Parallel sample helpers use explicit operations, not evaluated expressions. +package main + +import ( + "encoding/json" + "log" + "net/http" + "os" + "strings" + "time" + + "github.com/cloudevents/sdk-go/v2/binding" + cehttp "github.com/cloudevents/sdk-go/v2/protocol/http" +) + +const maxEventBytes = 1 << 20 + +// Match Math.round(Date.parse(time)/60000)%2: millisecond precision, +// ties toward +infinity, and signed remainder (including before 1970). +func roundedRemainder(timestamp time.Time) int64 { + n := timestamp.UnixMilli() + 30000 + minutes := n / 60000 + if n < 0 && n%60000 != 0 { + minutes-- // Go division truncates toward zero; Math.floor does not. + } + return minutes % 2 +} + +func newHandler(logger *log.Logger) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + if r.URL.Path == "/healthz" { + w.WriteHeader(http.StatusOK) + return + } + operation := r.URL.Path + switch operation { + case "/filter/even", "/filter/odd", "/switch/0", "/switch/1", "/transform/even", "/transform/odd": + default: + http.NotFound(w, r) + return + } + if r.Method != http.MethodPost { + w.Header().Set("Allow", http.MethodPost) + http.Error(w, "use POST", http.StatusMethodNotAllowed) + return + } + + r.Body = http.MaxBytesReader(w, r.Body, maxEventBytes) + message := cehttp.NewMessageFromHttpRequest(r) + defer message.Finish(nil) + event, err := binding.ToEvent(r.Context(), message) + if err != nil || event.SpecVersion() != "1.0" || event.Validate() != nil { + http.Error(w, "invalid CloudEvents 1.0 event", http.StatusBadRequest) + return + } + + matched := true + if strings.HasPrefix(operation, "/transform/") { + text := "we are even!" + if operation == "/transform/odd" { + text = "this is odd!" + } + if err := event.SetData("application/json", map[string]string{"message": text}); err != nil { + http.Error(w, "cannot encode transformed event", http.StatusInternalServerError) + return + } + } else { + if event.Context.AsV1().Time == nil { + http.Error(w, "missing CloudEvent time", http.StatusBadRequest) + return + } + parity := roundedRemainder(event.Time()) + switch operation { + case "/filter/even", "/switch/0": + matched = parity == 0 + case "/filter/odd": + matched = parity != 0 + case "/switch/1": + matched = parity == 1 + } + } + record, _ := json.Marshal(map[string]any{"path": operation, "id": event.ID(), "matched": matched}) + logger.Print(string(record)) + if !matched { + // An empty successful response stops this branch, without a reply event. + w.WriteHeader(http.StatusOK) + return + } + + ctx := binding.WithForceBinary(r.Context()) + if message.ReadEncoding() == binding.EncodingStructured { + ctx = binding.WithForceStructured(r.Context()) + } + if err := cehttp.WriteResponseWriter(ctx, binding.ToMessage(event), http.StatusOK, w); err != nil { + logger.Printf("cannot write reply: %v", err) + } + }) +} + +func main() { + port := os.Getenv("PORT") + if port == "" { + port = "8080" + } + logger := log.New(os.Stdout, "", 0) + server := &http.Server{Addr: ":" + port, Handler: newHandler(logger), ReadHeaderTimeout: 5 * time.Second} + logger.Printf("listening on %s", server.Addr) + logger.Fatal(server.ListenAndServe()) +} diff --git a/code-samples/eventing/parallel/helpers.yaml b/code-samples/eventing/parallel/helpers.yaml new file mode 100644 index 0000000000..aac3f0b35b --- /dev/null +++ b/code-samples/eventing/parallel/helpers.yaml @@ -0,0 +1,10 @@ +apiVersion: serving.knative.dev/v1 +kind: Service +metadata: + name: parallel-helpers +spec: + template: + spec: + containers: + - image: dev.local/parallel-helpers:go-v1 + imagePullPolicy: IfNotPresent diff --git a/code-samples/eventing/parallel/multiple-branches/README.md b/code-samples/eventing/parallel/multiple-branches/README.md deleted file mode 100644 index 9d3fc6c0ce..0000000000 --- a/code-samples/eventing/parallel/multiple-branches/README.md +++ /dev/null @@ -1,216 +0,0 @@ -# Parallel with multiple cases - -We are going to create a Parallel with two branches: - -- the first branch accepts events with a time that is is even -- the second branch accepts events with a time that is is odd - -The events produced by each branch are then sent to the `event-display` service. - -## Prerequisites - -Please refer to the sample overview for the [prerequisites](../README.md). - -### Create the Knative Services - -Let's first create the filter and transformer services that we will use in our -Parallel. - -```yaml -apiVersion: serving.knative.dev/v1 -kind: Service -metadata: - name: even-filter -spec: - template: - spec: - containers: - - image: villardl/filter-nodejs:0.1 - env: - - name: FILTER - value: | - Math.round(Date.parse(event.time) / 60000) % 2 === 0 ---- -apiVersion: serving.knative.dev/v1 -kind: Service -metadata: - name: odd-filter -spec: - template: - spec: - containers: - - image: villardl/filter-nodejs:0.1 - env: - - name: FILTER - value: | - Math.round(Date.parse(event.time) / 60000) % 2 !== 0 ---- -apiVersion: serving.knative.dev/v1 -kind: Service -metadata: - name: even-transformer -spec: - template: - spec: - containers: - - image: villardl/transformer-nodejs - env: - - name: TRANSFORMER - value: | - ({"message": "we are even!"}) - ---- -apiVersion: serving.knative.dev/v1 -kind: Service -metadata: - name: odd-transformer -spec: - template: - spec: - containers: - - image: villardl/transformer-nodejs:0.1 - env: - - name: TRANSFORMER - value: | - ({"message": "this is odd!"}) -``` - -```bash -kubectl create -f ./filters.yaml -f ./transformers.yaml -``` - -### Create the Service displaying the events created by Sequence - -```yaml -apiVersion: serving.knative.dev/v1 -kind: Service -metadata: - name: event-display -spec: - template: - spec: - containers: - - image: gcr.io/knative-releases/knative.dev/eventing/cmd/event_display -``` - -Change `default` in the following command to create the `Sequence` in the namespace where you want -your resources to be created: - -```bash -kubectl -n default create -f ./event-display.yaml -``` - -### Create the Parallel - -The `parallel.yaml` file contains the specifications for creating the Parallel. - -```yaml -apiVersion: flows.knative.dev/v1 -kind: Parallel -metadata: - name: odd-even-parallel -spec: - channelTemplate: - apiVersion: messaging.knative.dev/v1 - kind: InMemoryChannel - branches: - - filter: - ref: - apiVersion: serving.knative.dev/v1 - kind: Service - name: even-filter - subscriber: - ref: - apiVersion: serving.knative.dev/v1 - kind: Service - name: even-transformer - - filter: - ref: - apiVersion: serving.knative.dev/v1 - kind: Service - name: odd-filter - subscriber: - ref: - apiVersion: serving.knative.dev/v1 - kind: Service - name: odd-transformer - reply: - ref: - apiVersion: serving.knative.dev/v1 - kind: Service - name: event-display -``` - -```bash -kubectl create -f ./parallel.yaml -``` - -### Create the PingSource targeting the Parallel - -This will create a PingSource which will send a CloudEvent with -`{"message": "Even or odd?"}` as the data payload every minute. - -```yaml -apiVersion: sources.knative.dev/v1 -kind: PingSource -metadata: - name: ping-source -spec: - schedule: "*/1 * * * *" - contentType: "application/json" - data: '{"message": "Even or odd?"}' - sink: - ref: - apiVersion: flows.knative.dev/v1 - kind: Parallel - name: odd-even-parallel -``` - -```bash -kubectl create -f ./ping-source.yaml -``` - -### Inspecting the results - -You can now see the final output by inspecting the logs of the event-display -pods. Note that since we set the `PingSource` to emit every minute, it might -take some time for the events to show up in the logs. - -Let's look at the `event-display` log: - -```bash -kubectl logs -l serving.knative.dev/service=event-display --tail=30 -c user-container - -☁️ cloudevents.Event -Validation: valid -Context Attributes, - specversion: 1.0 - type: dev.knative.sources.ping - source: /apis/v1/namespaces/default/pingsources/ping-source - id: 015a4cf4-8a43-44a9-8702-3d4171d27ba5 - time: 2020-03-03T21:24:00.0007254Z - datacontenttype: application/json; charset=utf-8 -Extensions, - knativehistory: odd-even-parallel-kn-parallel-kn-channel.default.svc.cluster.local; odd-even-parallel-kn-parallel-0-kn-channel.default.svc.cluster.local - traceparent: 00-41a139bf073f3cfcba7bb7ce7f1488fc-68a891ace985221a-00 -Data, - { - "message": "we are even!" - } -☁️ cloudevents.Event -Validation: valid -Context Attributes, - specversion: 1.0 - type: dev.knative.sources.ping - source: /apis/v1/namespaces/default/pingsources/ping-source - id: 52e6b097-f914-4b5a-8539-165650e85bcd - time: 2020-03-03T21:23:00.0004662Z - datacontenttype: application/json; charset=utf-8 -Extensions, - knativehistory: odd-even-parallel-kn-parallel-kn-channel.default.svc.cluster.local; odd-even-parallel-kn-parallel-1-kn-channel.default.svc.cluster.local - traceparent: 00-58d371410d7daf2033be226860b4ee5d-05d686ee90c3226f-00 -Data, - { - "message": "this is odd!" - } -``` diff --git a/code-samples/eventing/parallel/multiple-branches/filters.yaml b/code-samples/eventing/parallel/multiple-branches/filters.yaml deleted file mode 100644 index aafa670095..0000000000 --- a/code-samples/eventing/parallel/multiple-branches/filters.yaml +++ /dev/null @@ -1,27 +0,0 @@ -apiVersion: serving.knative.dev/v1 -kind: Service -metadata: - name: even-filter -spec: - template: - spec: - containers: - - image: villardl/filter-nodejs:0.1 - env: - - name: FILTER - value: | - Math.round(Date.parse(event.time) / 60000) % 2 === 0 ---- -apiVersion: serving.knative.dev/v1 -kind: Service -metadata: - name: odd-filter -spec: - template: - spec: - containers: - - image: villardl/filter-nodejs:0.1 - env: - - name: FILTER - value: | - Math.round(Date.parse(event.time) / 60000) % 2 !== 0 \ No newline at end of file diff --git a/code-samples/eventing/parallel/multiple-branches/transformers.yaml b/code-samples/eventing/parallel/multiple-branches/transformers.yaml deleted file mode 100644 index 9f266fe077..0000000000 --- a/code-samples/eventing/parallel/multiple-branches/transformers.yaml +++ /dev/null @@ -1,28 +0,0 @@ -apiVersion: serving.knative.dev/v1 -kind: Service -metadata: - name: even-transformer -spec: - template: - spec: - containers: - - image: villardl/transformer-nodejs:0.1 - env: - - name: TRANSFORMER - value: | - ({"message": "we are even!"}) - ---- -apiVersion: serving.knative.dev/v1 -kind: Service -metadata: - name: odd-transformer -spec: - template: - spec: - containers: - - image: villardl/transformer-nodejs:0.1 - env: - - name: TRANSFORMER - value: | - ({"message": "this is odd!"}) diff --git a/code-samples/eventing/parallel/mutual-exclusivity/README.md b/code-samples/eventing/parallel/mutual-exclusivity/README.md deleted file mode 100644 index 4d85a94b41..0000000000 --- a/code-samples/eventing/parallel/mutual-exclusivity/README.md +++ /dev/null @@ -1,200 +0,0 @@ -# Parallel with mutually exclusive cases - -In this example, we are going to see how we can create a Parallel with mutually -exclusive branches. - -This example is the same as the -[multiple branches example](../multiple-branches/README.md) except that we are -now going to rely on the Knative -[switch](https://github.com/lionelvillard/knative-functions#switch) function to -provide a soft mutual exclusivity guarantee . - -NOTE: this example must be deployed in the default namespace. - -## Prerequisites - -Please refer to the sample overview for the [prerequisites](../README.md). - -### Create the Knative Services - -Let's first create the switcher and transformer services that we will use in our -Parallel. - -```yaml -apiVersion: serving.knative.dev/v1 -kind: Service -metadata: - name: me-even-odd-switcher -spec: - template: - spec: - containers: - - image: villardl/switcher-nodejs:0.1 - env: - - name: EXPRESSION - value: Math.round(Date.parse(event.time) / 60000) % 2 - - name: CASES - value: '[0, 1]' ---- -apiVersion: serving.knative.dev/v1 -kind: Service -metadata: - name: even-transformer -spec: - template: - spec: - containers: - - image: villardl/transformer-nodejs:0.1 - env: - - name: TRANSFORMER - value: | - ({"message": "we are even!"}) - ---- -apiVersion: serving.knative.dev/v1 -kind: Service -metadata: - name: odd-transformer -spec: - template: - spec: - containers: - - image: villardl/transformer-nodejs:0.1 - env: - - name: TRANSFORMER - value: | - ({"message": "this is odd!"}) -. -``` - -```bash -kubectl create -f ./switcher.yaml -f ./transformers.yaml -``` - -### Create the Service displaying the events created by Parallel - -```yaml -apiVersion: serving.knative.dev/v1 -kind: Service -metadata: - name: event-display -spec: - template: - spec: - containers: - - image: gcr.io/knative-releases/knative.dev/eventing/cmd/event_display -``` - -```bash -kubectl -n default create -f ./event-display.yaml -``` - -### Create the Parallel object - -The `parallel.yaml` file contains the specifications for creating the Parallel -object. - -```yaml -apiVersion: flows.knative.dev/v1 -kind: Parallel -metadata: - name: me-odd-even-parallel -spec: - channelTemplate: - apiVersion: messaging.knative.dev/v1 - kind: InMemoryChannel - branches: - - filter: - uri: "http://me-even-odd-switcher.default.svc.cluster.local/0" - subscriber: - ref: - apiVersion: serving.knative.dev/v1 - kind: Service - name: me-even-transformer - - filter: - uri: "http://me-even-odd-switcher.default.svc.cluster.local/1" - subscriber: - ref: - apiVersion: serving.knative.dev/v1 - kind: Service - name: me-odd-transformer - reply: - ref: - apiVersion: serving.knative.dev/v1 - kind: Service - name: me-event-display -``` - -```bash -kubectl create -f ./parallel.yaml -``` - -### Create the PingSource targeting the Parallel object - -This will create a PingSource which will send a CloudEvent with -`{"message": "Even or odd?"}` as the data payload every minute. - -```yaml -apiVersion: sources.knative.dev/v1 -kind: PingSource -metadata: - name: me-ping-source -spec: - schedule: "*/1 * * * *" - contentType: "application/json" - data: '{"message": "Even or odd?"}' - sink: - ref: - apiVersion: flows.knative.dev/v1 - kind: Parallel - name: me-odd-even-parallel -``` - -```bash -kubectl create -f ./ping-source.yaml -``` - -### Inspecting the results - -You can now see the final output by inspecting the logs of the -`me-event-display` pods. Note that since we set the `PingSource` to emit every -minute, it might take some time for the events to show up in the logs. - -Let's look at the `me-event-display` log: - -```bash -kubectl logs -l serving.knative.dev/service=me-event-display --tail=50 -c user-container - -☁️ cloudevents.Event -Validation: valid -Context Attributes, - specversion: 1.0 - type: dev.knative.sources.ping - source: /apis/v1/namespaces/default/pingsources/me-ping-source - id: 0b0db15c-9b36-4388-8eaa-8c23a9dc2707 - time: 2020-03-03T21:30:00.0007292Z - datacontenttype: application/json; charset=utf-8 -Extensions, - knativehistory: me-odd-even-parallel-kn-parallel-kn-channel.default.svc.cluster.local; me-odd-even-parallel-kn-parallel-0-kn-channel.default.svc.cluster.local - traceparent: 00-e8b17109cd21d4fa66a86633b5a614c7-ba96d220fe13211c-00 -Data, - { - "message": "we are even!" - } -☁️ cloudevents.Event -Validation: valid -Context Attributes, - specversion: 1.0 - type: dev.knative.sources.ping - source: /apis/v1/namespaces/default/pingsources/me-ping-source - id: 321170d1-dea7-4b18-9290-28adb1de089b - time: 2020-03-03T21:31:00.0007847Z - datacontenttype: application/json; charset=utf-8 -Extensions, - knativehistory: me-odd-even-parallel-kn-parallel-kn-channel.default.svc.cluster.local; me-odd-even-parallel-kn-parallel-1-kn-channel.default.svc.cluster.local - traceparent: 00-78d8b044d23c85789f0a13fd3679ac24-1d69ddde56d620c7-00 -Data, - { - "message": "this is odd!" - } -``` diff --git a/code-samples/eventing/parallel/mutual-exclusivity/event-display.yaml b/code-samples/eventing/parallel/mutual-exclusivity/event-display.yaml deleted file mode 100644 index c148f30ec4..0000000000 --- a/code-samples/eventing/parallel/mutual-exclusivity/event-display.yaml +++ /dev/null @@ -1,9 +0,0 @@ -apiVersion: serving.knative.dev/v1 -kind: Service -metadata: - name: me-event-display -spec: - template: - spec: - containers: - - image: gcr.io/knative-releases/knative.dev/eventing/cmd/event_display diff --git a/code-samples/eventing/parallel/mutual-exclusivity/ping-source.yaml b/code-samples/eventing/parallel/mutual-exclusivity/ping-source.yaml deleted file mode 100644 index 0f6efc7ee9..0000000000 --- a/code-samples/eventing/parallel/mutual-exclusivity/ping-source.yaml +++ /dev/null @@ -1,13 +0,0 @@ -apiVersion: sources.knative.dev/v1 -kind: PingSource -metadata: - name: me-ping-source -spec: - schedule: "*/1 * * * *" - contentType: "application/json" - data: '{"message": "Even or odd?"}' - sink: - ref: - apiVersion: flows.knative.dev/v1 - kind: Parallel - name: me-odd-even-parallel diff --git a/code-samples/eventing/parallel/mutual-exclusivity/switcher.yaml b/code-samples/eventing/parallel/mutual-exclusivity/switcher.yaml deleted file mode 100644 index a8998dc257..0000000000 --- a/code-samples/eventing/parallel/mutual-exclusivity/switcher.yaml +++ /dev/null @@ -1,14 +0,0 @@ -apiVersion: serving.knative.dev/v1 -kind: Service -metadata: - name: me-even-odd-switcher -spec: - template: - spec: - containers: - - image: villardl/switcher-nodejs:0.1 - env: - - name: EXPRESSION - value: Math.round(Date.parse(event.time) / 60000) % 2 - - name: CASES - value: '[0, 1]' diff --git a/code-samples/eventing/parallel/mutual-exclusivity/transformers.yaml b/code-samples/eventing/parallel/mutual-exclusivity/transformers.yaml deleted file mode 100644 index 4402cbe9ea..0000000000 --- a/code-samples/eventing/parallel/mutual-exclusivity/transformers.yaml +++ /dev/null @@ -1,28 +0,0 @@ -apiVersion: serving.knative.dev/v1 -kind: Service -metadata: - name: me-even-transformer -spec: - template: - spec: - containers: - - image: villardl/transformer-nodejs:0.1 - env: - - name: TRANSFORMER - value: | - ({"message": "we are even!"}) - ---- -apiVersion: serving.knative.dev/v1 -kind: Service -metadata: - name: me-odd-transformer -spec: - template: - spec: - containers: - - image: villardl/transformer-nodejs:0.1 - env: - - name: TRANSFORMER - value: | - ({"message": "this is odd!"}) diff --git a/code-samples/eventing/parallel/mutual-exclusivity/parallel.yaml b/code-samples/eventing/parallel/parallel-filters.yaml similarity index 51% rename from code-samples/eventing/parallel/mutual-exclusivity/parallel.yaml rename to code-samples/eventing/parallel/parallel-filters.yaml index b5868c61df..b5f30731c4 100644 --- a/code-samples/eventing/parallel/mutual-exclusivity/parallel.yaml +++ b/code-samples/eventing/parallel/parallel-filters.yaml @@ -1,28 +1,38 @@ apiVersion: flows.knative.dev/v1 kind: Parallel metadata: - name: me-odd-even-parallel + name: odd-even-parallel spec: channelTemplate: apiVersion: messaging.knative.dev/v1 kind: InMemoryChannel branches: - filter: - uri: "http://me-even-odd-switcher.default.svc.cluster.local/0" + ref: + apiVersion: serving.knative.dev/v1 + kind: Service + name: parallel-helpers + uri: /filter/even subscriber: ref: apiVersion: serving.knative.dev/v1 kind: Service - name: me-even-transformer + name: parallel-helpers + uri: /transform/even - filter: - uri: "http://me-even-odd-switcher.default.svc.cluster.local/1" + ref: + apiVersion: serving.knative.dev/v1 + kind: Service + name: parallel-helpers + uri: /filter/odd subscriber: ref: apiVersion: serving.knative.dev/v1 kind: Service - name: me-odd-transformer + name: parallel-helpers + uri: /transform/odd reply: ref: apiVersion: serving.knative.dev/v1 kind: Service - name: me-event-display + name: event-display diff --git a/code-samples/eventing/parallel/multiple-branches/parallel.yaml b/code-samples/eventing/parallel/parallel-switch.yaml similarity index 74% rename from code-samples/eventing/parallel/multiple-branches/parallel.yaml rename to code-samples/eventing/parallel/parallel-switch.yaml index ef289e81c5..04a0de403d 100644 --- a/code-samples/eventing/parallel/multiple-branches/parallel.yaml +++ b/code-samples/eventing/parallel/parallel-switch.yaml @@ -11,22 +11,26 @@ spec: ref: apiVersion: serving.knative.dev/v1 kind: Service - name: even-filter + name: parallel-helpers + uri: /switch/0 subscriber: ref: apiVersion: serving.knative.dev/v1 kind: Service - name: even-transformer + name: parallel-helpers + uri: /transform/even - filter: ref: apiVersion: serving.knative.dev/v1 kind: Service - name: odd-filter + name: parallel-helpers + uri: /switch/1 subscriber: ref: apiVersion: serving.knative.dev/v1 kind: Service - name: odd-transformer + name: parallel-helpers + uri: /transform/odd reply: ref: apiVersion: serving.knative.dev/v1 diff --git a/code-samples/eventing/parallel/multiple-branches/ping-source.yaml b/code-samples/eventing/parallel/ping-source.yaml similarity index 100% rename from code-samples/eventing/parallel/multiple-branches/ping-source.yaml rename to code-samples/eventing/parallel/ping-source.yaml From 2de8a2b45be49fa545093eff8497ecf2d22eca20 Mon Sep 17 00:00:00 2001 From: Dave Protasowski Date: Mon, 26 Oct 2026 09:16:04 -0400 Subject: [PATCH 19/44] Update the JavaScript event source sample --- .../writing-event-source-easy-way/Dockerfile | 2 +- .../writing-event-source-easy-way/README.md | 239 +++++++----------- .../writing-event-source-easy-way/index.js | 17 +- .../package.json | 1 - 4 files changed, 96 insertions(+), 163 deletions(-) diff --git a/code-samples/eventing/writing-event-source-easy-way/Dockerfile b/code-samples/eventing/writing-event-source-easy-way/Dockerfile index cd9db5c73a..7944c3195e 100644 --- a/code-samples/eventing/writing-event-source-easy-way/Dockerfile +++ b/code-samples/eventing/writing-event-source-easy-way/Dockerfile @@ -15,4 +15,4 @@ RUN npm install COPY index.js ./ EXPOSE 8080 -CMD [ "npm", "start" ] +CMD [ "node", "index.js" ] diff --git a/code-samples/eventing/writing-event-source-easy-way/README.md b/code-samples/eventing/writing-event-source-easy-way/README.md index 57bde1f5df..518617ecc1 100644 --- a/code-samples/eventing/writing-event-source-easy-way/README.md +++ b/code-samples/eventing/writing-event-source-easy-way/README.md @@ -1,104 +1,56 @@ # Writing an event source using JavaScript -This tutorial provides instructions to build an event source in JavaScript and implement it with a ContainerSource or SinkBinding. +This tutorial builds an event source in JavaScript and implements it with a +ContainerSource or SinkBinding. Both inject environment variables into an +application, including the URL of a sink that receives events. -ContainerSource and SinkBinding both work by injecting environment variables to an application. Injected environment variables at minimum contain the URL of a sink that receives events. +## Prerequisites -## Bootstrapping +For the cluster procedures, install Knative Eventing and Knative Serving and +configure `kubectl` to use that cluster. Use a container registry the cluster can +pull from. For SinkBinding in inclusion mode, label your namespace with +`bindings.knative.dev/include=true` before creating the Deployment. -Create the project and add the dependencies: +## Get the sample + +Use the checked-in files rather than recreating the application from this guide: ```bash -npm init -y -npm install cloudevents got --save +git clone https://github.com/knative/docs.git +cd docs/code-samples/eventing/writing-event-source-easy-way ``` -## Using ContainerSource - -A ContainerSource creates a container for your event source image and manages this container. - -The sink URL where events are posted will be made available to the application through the `K_SINK` environment variable by the ContainerSource. - -### Example - -The following example event source emits an event to the sink every second: - -```javascript -// File - index.js -const { CloudEvent, Emitter, emitterFor, httpTransport } = require('cloudevents'); - -const K_SINK = process.env['K_SINK']; -K_SINK || logExit('Error: K_SINK Environment variable is not defined'); -console.log(`Sink URL is ${K_SINK}`); - -const source = 'urn:event:from:heartbeat/example'; -const type = 'heartbeat.example'; +- [index.js](index.js) is the complete event source. It uses the SDK version in + [package.json](package.json) to emit a new CloudEvent every second, with type + `heartbeat.example`, source `urn:event:from:heartbeat/example`, a unique ID, + and data such as `{"hello":"World # 1"}`. +- [Dockerfile](Dockerfile) builds the source image and starts Node directly so it + can handle termination signals. -let eventIndex = 0; -setInterval(() => { - console.log(`Emitting event # ${++eventIndex}`); +`K_SINK` is required; an unset or empty value exits with status 1. This minimal +source does not implement `K_CE_OVERRIDES` and does not retry failed deliveries. - // Create a new CloudEvent each second - const event = new CloudEvent({ source, type, data: {'hello': `World # ${eventIndex}`} }); - - // Emits the 'cloudevent' Node.js event application-wide - event.emit(); -}, 1000); - -// Create a function that can post an event -const emit = emitterFor(httpTransport(K_SINK)); - -// Send the CloudEvent any time a Node.js 'cloudevent' event is emitted -Emitter.on('cloudevent', emit); - -registerGracefulExit(); - -function registerGracefulExit() { - process.on('exit', logExit); - //catches ctrl+c event - process.on('SIGINT', logExit); - process.on('SIGTERM', logExit); - // catches 'kill pid' (for example: nodemon restart) - process.on('SIGUSR1', logExit); - process.on('SIGUSR2', logExit); -} - -function logExit(message = 'Exiting...') { - // Handle graceful exit - process.stdout.write(`${message}\n`); - process.exit(); -} -``` +## Using ContainerSource -```dockerfile -# File - Dockerfile - -FROM node:16 -WORKDIR /usr/src/app -COPY package*.json ./ -RUN npm install -COPY . . -EXPOSE 8080 -CMD [ "node", "index.js" ] -``` +A ContainerSource creates and manages a container for your event source image. +It makes the sink URL available through `K_SINK`. ### Procedure -Before publishing the ContainerSource, you must build the application -image, and push it to a container registry that your cluster can access. - -1. Build and push the image: +1. Build the application image and push it to a registry your cluster can access: ```bash export REGISTRY=docker.io/myregistry - - # Build and push the container on your local machine. docker buildx build --platform linux/arm64,linux/amd64 -t "$REGISTRY/node-heartbeat-source:v1" --push . ``` -1. Create the event display service which logs any CloudEvents posted to it: + Use the image you published in the ContainerSource and Deployment manifests + below. - ```yaml +1. Create the event-display Service, which logs any CloudEvents posted to it: + + ```bash + kubectl apply -f - <<'EOF' apiVersion: serving.knative.dev/v1 kind: Service metadata: @@ -108,11 +60,14 @@ image, and push it to a container registry that your cluster can access. spec: containers: - image: gcr.io/knative-releases/knative.dev/eventing/cmd/event_display + EOF + kubectl wait --for=condition=Ready service.serving.knative.dev/event-display --timeout=180s ``` -1. Create the ContainerSource object: +1. Create the ContainerSource: - ```yaml + ```bash + kubectl apply -f - <<'EOF' apiVersion: sources.knative.dev/v1 kind: ContainerSource metadata: @@ -128,15 +83,19 @@ image, and push it to a container registry that your cluster can access. apiVersion: serving.knative.dev/v1 kind: Service name: event-display + EOF + kubectl wait --for=condition=Ready containersource/heartbeat-source --timeout=180s ``` -1. Check the logs of the `event-display` Service. You can observe that a new message is pushed every second: +1. Check the logs of the `event-display` Service: ```bash - $ kubectl logs -l serving.knative.dev/service=event-display -c user-container + kubectl logs -l serving.knative.dev/service=event-display -c user-container --tail=100 ``` - ```bash + A new event arrives every second, for example: + + ```text ☁️ cloudevents.Event Validation: valid Context Attributes, @@ -147,47 +106,39 @@ image, and push it to a container registry that your cluster can access. datacontenttype: application/json Data, { - "hello": "World 1" + "hello": "World # 1" } ``` -1. Optional: If you are interested in seeing what is injected into the event source as a `K_SINK`, you can check the logs: +1. Optional: inspect the generated Deployment's source logs to see `K_SINK`: ```bash - $ kubectl logs heartbeat-source-7575c888c7-85w5t + kubectl get deployments + # Replace with the Deployment owned by heartbeat-source. + kubectl logs deployment/ ``` - ```bash + ```text Sink URL is http://event-display.default.svc.cluster.local - Emitting event #1 - Emitting event #2 - Event posted successfully - Event posted successfully + Emitting event # 1 + Emitting event # 2 ``` ## Using SinkBinding -SinkBinding does not create any containers. It injects the sink information to an already existing Kubernetes resources. This is a flexible approach as you can use any Kubernetes PodSpecable object as an event source, such as Deployment, Job, or Knative services. +SinkBinding does not create containers. It injects sink information into an +existing Kubernetes PodSpecable resource, such as a Deployment, Job, or Knative +Service. ### Procedure -1. Create the `event-display` service: +1. If the event-display Service does not already exist, run its creation and + readiness commands from the [ContainerSource procedure](#using-containersource). - ```yaml - apiVersion: serving.knative.dev/v1 - kind: Service - metadata: - name: event-display - spec: - template: - spec: - containers: - - image: gcr.io/knative-releases/knative.dev/eventing/cmd/event_display - ``` - -1. Create a Kubernetes Deployment that runs the event source: +1. Create the two-replica Deployment, using your published source image: - ```yaml + ```bash + kubectl apply -f - <<'EOF' apiVersion: apps/v1 kind: Deployment metadata: @@ -205,28 +156,28 @@ SinkBinding does not create any containers. It injects the sink information to a app: node-heartbeats spec: containers: - - name: node-heartbeats - image: docker.io/myregistry/node-heartbeat-source:v1 - ports: - - containerPort: 8080 + - name: node-heartbeats + image: docker.io/myregistry/node-heartbeat-source:v1 + ports: + - containerPort: 8080 + EOF ``` -1. Because the SinkBinding has not yet been created, you will see an error message, because the `K_SINK` environment variable is not yet injected: +1. Before creating the binding, the Pods exit because `K_SINK` is not injected: ```bash - $ kubectl logs node-heartbeats-deployment-9ffbb644b-llkzk + kubectl logs -l app=node-heartbeats --tail=20 ``` - ```bash - Sink URL is undefined - Emitting event #1 - Error during event post - TypeError [ERR_INVALID_ARG_TYPE]: The "url" argument must be of type string. Received type undefined + ```text + Error: K_SINK Environment variable is not defined ``` -1. Create the SinkBinding object: +1. Create the SinkBinding, which selects the Deployment by its + `app: node-heartbeats` label: - ```yaml + ```bash + kubectl apply -f - <<'EOF' apiVersion: sources.knative.dev/v1 kind: SinkBinding metadata: @@ -243,40 +194,26 @@ SinkBinding does not create any containers. It injects the sink information to a apiVersion: serving.knative.dev/v1 kind: Service name: event-display + EOF + kubectl wait --for=condition=Ready sinkbinding/bind-node-heartbeat --timeout=180s + kubectl rollout status deployment/node-heartbeats-deployment --timeout=180s ``` - Observe that the Pods are recreated and this time the `K_SINK` environment variable is injected. - - Since the `replicas` is set to 2, there are 2 pods that are posting events to the sink: + The Pods are recreated with `K_SINK` injected. Both replicas post events to + the sink; the display logs contain two independent heartbeat sequences with + distinct event IDs: ```bash - $ kubectl logs event-display-dpplv-deployment-67c9949cf9-bvjvk -c user-container + kubectl logs -l serving.knative.dev/service=event-display -c user-container --tail=100 ``` - ```bash - ☁️ cloudevents.Event - Validation: valid - Context Attributes, - specversion: 1.0 - type: heartbeat.example - source: urn:event:from:heartbeat/example - id: 47e69d34-def7-449b-8382-3652495f9163 - datacontenttype: application/json - Data, - { - "hello": "World 1" - } +### Cleanup - ☁️ cloudevents.Event - Validation: valid - Context Attributes, - specversion: 1.0 - type: heartbeat.example - source: urn:event:from:heartbeat/example - id: 47e69d34-def7-449b-8382-3652495f9163 - datacontenttype: application/json - Data, - { - "hello": "World 1" - } - ``` +Delete only the resources you created for this tutorial: + +```bash +kubectl delete containersource heartbeat-source --ignore-not-found +kubectl delete sinkbinding bind-node-heartbeat --ignore-not-found +kubectl delete deployment node-heartbeats-deployment --ignore-not-found +kubectl delete kservice event-display --ignore-not-found +``` diff --git a/code-samples/eventing/writing-event-source-easy-way/index.js b/code-samples/eventing/writing-event-source-easy-way/index.js index 54ae453492..72ad7aba52 100644 --- a/code-samples/eventing/writing-event-source-easy-way/index.js +++ b/code-samples/eventing/writing-event-source-easy-way/index.js @@ -1,7 +1,7 @@ const { CloudEvent, Emitter, emitterFor, httpTransport } = require('cloudevents'); const K_SINK = process.env['K_SINK']; -K_SINK || logExit('Error: K_SINK Environment variable is not defined'); +K_SINK || logExit('Error: K_SINK Environment variable is not defined', 1); console.log(`Sink URL is ${K_SINK}`); const source = 'urn:event:from:heartbeat/example'; @@ -27,17 +27,14 @@ Emitter.on('cloudevent', emit); registerGracefulExit(); function registerGracefulExit() { - process.on('exit', logExit); - //catches ctrl+c event - process.on('SIGINT', logExit); - process.on('SIGTERM', logExit); - // catches 'kill pid' (for example: nodemon restart) - process.on('SIGUSR1', logExit); - process.on('SIGUSR2', logExit); + // Catch termination signals without passing their names to logExit. + for (const signal of ['SIGINT', 'SIGTERM', 'SIGUSR1', 'SIGUSR2']) { + process.on(signal, () => logExit()); + } } -function logExit(message = 'Exiting...') { +function logExit(message = 'Exiting...', code = 0) { // Handle graceful exit process.stdout.write(`${message}\n`); - process.exit(); + process.exit(code); } diff --git a/code-samples/eventing/writing-event-source-easy-way/package.json b/code-samples/eventing/writing-event-source-easy-way/package.json index 4ccc4152bf..ab5cb11861 100644 --- a/code-samples/eventing/writing-event-source-easy-way/package.json +++ b/code-samples/eventing/writing-event-source-easy-way/package.json @@ -4,7 +4,6 @@ "description": "", "main": "index.js", "scripts": { - "test": "echo \"Error: no test specified\" && exit 1", "start": "node index.js" }, "author": "", From 56ec0bf0566965b50282c387b74d80c5982c1658 Mon Sep 17 00:00:00 2001 From: Dave Protasowski Date: Mon, 26 Oct 2026 09:16:04 -0400 Subject: [PATCH 20/44] Update the .NET CloudEvents sample --- .../CloudEventActionResult.cs | 25 +--- .../CloudEventsSample.csproj | 8 +- .../cloudevents-dotnet/CloudEventsSample.sln | 34 +++++ .../Controllers/CloudEventsController.cs | 131 ++++++++++-------- .../cloudevents/cloudevents-dotnet/Dockerfile | 20 ++- .../cloudevents-dotnet/Models/SampleOutput.cs | 7 - .../cloudevents/cloudevents-dotnet/README.md | 63 +++++++-- .../cloudevents/cloudevents-dotnet/Startup.cs | 9 +- .../cloudevents/cloudevents-dotnet/ci.sh | 43 ++++++ .../cloudevents-dotnet/packages.lock.json | 30 ++++ 10 files changed, 256 insertions(+), 114 deletions(-) create mode 100644 code-samples/serving/cloudevents/cloudevents-dotnet/CloudEventsSample.sln create mode 100755 code-samples/serving/cloudevents/cloudevents-dotnet/ci.sh create mode 100644 code-samples/serving/cloudevents/cloudevents-dotnet/packages.lock.json diff --git a/code-samples/serving/cloudevents/cloudevents-dotnet/CloudEventActionResult.cs b/code-samples/serving/cloudevents/cloudevents-dotnet/CloudEventActionResult.cs index bfdbce0613..11fa8315b1 100644 --- a/code-samples/serving/cloudevents/cloudevents-dotnet/CloudEventActionResult.cs +++ b/code-samples/serving/cloudevents/cloudevents-dotnet/CloudEventActionResult.cs @@ -11,42 +11,31 @@ You may obtain a copy of the License at limitations under the License. */ -using System.Collections.Generic; -using System.Linq; using System.Net; using System.Threading.Tasks; using CloudNative.CloudEvents; +using CloudNative.CloudEvents.AspNetCore; +using CloudNative.CloudEvents.SystemTextJson; using Microsoft.AspNetCore.Mvc; -using Microsoft.Extensions.Primitives; namespace CloudEventsSample { - /// - /// CloudEvents SDK doesn't provide any helpers to respond back with IActionResult. - /// This helper class wraps CloudEventContent in IActionResult. - /// public class CloudEventActionResult : IActionResult { private readonly HttpStatusCode statusCode; - private readonly CloudEventContent content; + private readonly CloudEvent cloudEvent; - public CloudEventActionResult(HttpStatusCode statusCode, CloudEventContent content) + public CloudEventActionResult(HttpStatusCode statusCode, CloudEvent cloudEvent) { this.statusCode = statusCode; - this.content = content; + this.cloudEvent = cloudEvent; } public async Task ExecuteResultAsync(ActionContext context) { context.HttpContext.Response.StatusCode = (int)this.statusCode; - foreach (var header in this.content.Headers) - { - context.HttpContext.Response.Headers.TryAdd(header.Key, new StringValues(header.Value.ToArray())); - } - - await using var stream = await this.content.ReadAsStreamAsync(); - await stream.CopyToAsync(context.HttpContext.Response.Body); - await context.HttpContext.Response.Body.FlushAsync(); + await this.cloudEvent.CopyToHttpResponseAsync(context.HttpContext.Response, + ContentMode.Structured, new JsonEventFormatter()); } } } diff --git a/code-samples/serving/cloudevents/cloudevents-dotnet/CloudEventsSample.csproj b/code-samples/serving/cloudevents/cloudevents-dotnet/CloudEventsSample.csproj index 8554889ad5..a19cad5e70 100644 --- a/code-samples/serving/cloudevents/cloudevents-dotnet/CloudEventsSample.csproj +++ b/code-samples/serving/cloudevents/cloudevents-dotnet/CloudEventsSample.csproj @@ -1,13 +1,13 @@ - netcoreapp3.1 + net10.0 + true - - + + - diff --git a/code-samples/serving/cloudevents/cloudevents-dotnet/CloudEventsSample.sln b/code-samples/serving/cloudevents/cloudevents-dotnet/CloudEventsSample.sln new file mode 100644 index 0000000000..7c2592538b --- /dev/null +++ b/code-samples/serving/cloudevents/cloudevents-dotnet/CloudEventsSample.sln @@ -0,0 +1,34 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 17 +VisualStudioVersion = 17.0.31903.59 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CloudEventsSample", "CloudEventsSample.csproj", "{9221D791-4A93-4606-8AE4-58AD6FCBF110}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Debug|x64 = Debug|x64 + Debug|x86 = Debug|x86 + Release|Any CPU = Release|Any CPU + Release|x64 = Release|x64 + Release|x86 = Release|x86 + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {9221D791-4A93-4606-8AE4-58AD6FCBF110}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {9221D791-4A93-4606-8AE4-58AD6FCBF110}.Debug|Any CPU.Build.0 = Debug|Any CPU + {9221D791-4A93-4606-8AE4-58AD6FCBF110}.Debug|x64.ActiveCfg = Debug|Any CPU + {9221D791-4A93-4606-8AE4-58AD6FCBF110}.Debug|x64.Build.0 = Debug|Any CPU + {9221D791-4A93-4606-8AE4-58AD6FCBF110}.Debug|x86.ActiveCfg = Debug|Any CPU + {9221D791-4A93-4606-8AE4-58AD6FCBF110}.Debug|x86.Build.0 = Debug|Any CPU + {9221D791-4A93-4606-8AE4-58AD6FCBF110}.Release|Any CPU.ActiveCfg = Release|Any CPU + {9221D791-4A93-4606-8AE4-58AD6FCBF110}.Release|Any CPU.Build.0 = Release|Any CPU + {9221D791-4A93-4606-8AE4-58AD6FCBF110}.Release|x64.ActiveCfg = Release|Any CPU + {9221D791-4A93-4606-8AE4-58AD6FCBF110}.Release|x64.Build.0 = Release|Any CPU + {9221D791-4A93-4606-8AE4-58AD6FCBF110}.Release|x86.ActiveCfg = Release|Any CPU + {9221D791-4A93-4606-8AE4-58AD6FCBF110}.Release|x86.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection +EndGlobal diff --git a/code-samples/serving/cloudevents/cloudevents-dotnet/Controllers/CloudEventsController.cs b/code-samples/serving/cloudevents/cloudevents-dotnet/Controllers/CloudEventsController.cs index 7745ec83c9..d313a00bc4 100644 --- a/code-samples/serving/cloudevents/cloudevents-dotnet/Controllers/CloudEventsController.cs +++ b/code-samples/serving/cloudevents/cloudevents-dotnet/Controllers/CloudEventsController.cs @@ -14,12 +14,15 @@ limitations under the License. using System; using System.Net; using System.Net.Http; -using System.Net.Mime; using System.Text.Json; using System.Threading.Tasks; using CloudEventsSample.Models; -using Microsoft.AspNetCore.Mvc; using CloudNative.CloudEvents; +using CloudNative.CloudEvents.AspNetCore; +using CloudNative.CloudEvents.Http; +using CloudNative.CloudEvents.SystemTextJson; +using Microsoft.AspNetCore.Mvc; +using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Logging; namespace CloudEventsSample.Controllers @@ -32,89 +35,105 @@ public class CloudEventsController : ControllerBase private const string CloudEventResponseUri = "https://github.com/knative/docs/code-samples/serving/cloudevents/cloudevents-dotnet"; - private static readonly Lazy SinkUri = - new Lazy(() => Environment.GetEnvironmentVariable("K_SINK")); - + private readonly string sinkUri; + private readonly IHttpClientFactory clients; private readonly ILogger logger; - public CloudEventsController(ILogger logger) + public CloudEventsController(ILogger logger, + IHttpClientFactory clients, IConfiguration configuration) { this.logger = logger; + this.clients = clients; + this.sinkUri = configuration["K_SINK"]; } - /// - /// Responds to the post request by calling ReceiveAndReply if K_SINK is not set, - /// or by calling ReceiveAndSend if K_SINK is set. - /// [HttpPost] - public async Task Post([FromBody] CloudEvent receivedEvent) + public async Task Post() { + CloudEvent receivedEvent; + SampleInput input; try { - if (string.IsNullOrEmpty(SinkUri.Value)) + receivedEvent = await this.ReadEvent(); + if (receivedEvent.Data is not JsonElement data || data.ValueKind != JsonValueKind.Object) { - return this.ReceiveAndReply(receivedEvent); + return this.BadRequest("Event data must be a JSON object containing a name."); } - else + input = data.Deserialize(); + if (input == null || string.IsNullOrWhiteSpace(input.Name)) { - return await this.ReceiveAndSend(receivedEvent); + return this.BadRequest("Event data must contain a nonempty name."); } } - catch (JsonException) + catch (Exception ex) when (IsInvalidEvent(ex)) { - return this.BadRequest("Failed to read the JSON data."); + return this.BadRequest("Expected a valid CloudEvent with JSON data."); } + + // Log attributes rather than serializing SDK implementation details. + this.logger.LogInformation("Received event {Id} of type {Type}", receivedEvent.Id, receivedEvent.Type); + var response = GetResponseForInput(input); + return string.IsNullOrEmpty(this.sinkUri) + ? this.ReceiveAndReply(response) + : await this.ReceiveAndSend(response); } - /// - /// This is called whenever an event is received if K_SINK environment variable is NOT set. - /// Replies with a new event. - /// - private IActionResult ReceiveAndReply(CloudEvent receivedEvent) + private IActionResult ReceiveAndReply(CloudEvent response) { - this.logger?.LogInformation($"Received event {JsonSerializer.Serialize(receivedEvent)}"); - var content = GetResponseForEvent(receivedEvent); - this.HttpContext.Response.RegisterForDispose(content); - return new CloudEventActionResult(HttpStatusCode.OK, content); + return new CloudEventActionResult(HttpStatusCode.OK, response); } - /// - /// This is called whenever an event is received if K_SINK environment variable is set. - /// Sends a new event to the url in K_SINK. - /// - private async Task ReceiveAndSend(CloudEvent receivedEvent) + private async Task ReceiveAndSend(CloudEvent response) { - this.logger?.LogInformation($"Received event {JsonSerializer.Serialize(receivedEvent)}"); - using var content = GetResponseForEvent(receivedEvent); - using var client = new HttpClient(); - using var result = await client.PostAsync(SinkUri.Value, content); - return this.StatusCode((int)result.StatusCode, await result.Content.ReadAsStringAsync()); + try + { + using var content = response.ToHttpContent(ContentMode.Structured, new JsonEventFormatter()); + using var result = await this.clients.CreateClient("sink").PostAsync(this.sinkUri, content); + return this.StatusCode((int)result.StatusCode, await result.Content.ReadAsStringAsync()); + } + catch (HttpRequestException ex) + { + this.logger.LogWarning(ex, "Failed to send event to K_SINK"); + return this.StatusCode(502, "Failed to send event to K_SINK."); + } + catch (TaskCanceledException) + { + return this.StatusCode(504, "K_SINK request timed out."); + } } - /// - /// Respond back with the JSON serialized request. - /// - [HttpPost, Route("echo")] - public ActionResult Echo([FromBody] CloudEvent receivedEvent) + // Echo the original event using the SDK formatter, not its internal object representation. + [HttpPost("echo")] + public async Task Echo() { - this.logger.LogInformation($"Echo: {JsonSerializer.Serialize(receivedEvent)}"); - return this.Ok(JsonSerializer.Serialize(receivedEvent)); + try + { + return new CloudEventActionResult(HttpStatusCode.OK, await this.ReadEvent()); + } + catch (Exception ex) when (IsInvalidEvent(ex)) + { + return this.BadRequest("Expected a valid CloudEvent with JSON data."); + } } - private static CloudEventContent GetResponseForEvent(CloudEvent receivedEvent) + private async Task ReadEvent() { - var input = JsonSerializer.Deserialize(receivedEvent.Data.ToString()); - var content = new CloudEventContent - ( - new CloudEvent(CloudEventResponseType, new Uri(CloudEventResponseUri)) - { - DataContentType = new ContentType(MediaTypeNames.Application.Json), - Data = new SampleOutput {Message = $"Hello, {input.Name}"}, - }, - ContentMode.Structured, - new JsonEventFormatter() - ); - return content; + var receivedEvent = await this.Request.ToCloudEventAsync(new JsonEventFormatter()); + receivedEvent.Validate(); + return receivedEvent; } + + private static bool IsInvalidEvent(Exception ex) => + ex is JsonException or ArgumentException or InvalidOperationException or FormatException; + + private static CloudEvent GetResponseForInput(SampleInput input) => new CloudEvent + { + Id = Guid.NewGuid().ToString(), + Time = DateTimeOffset.UtcNow, + Type = CloudEventResponseType, + Source = new Uri(CloudEventResponseUri), + DataContentType = "application/json", + Data = new SampleOutput { Message = $"Hello, {input.Name}" } + }; } } diff --git a/code-samples/serving/cloudevents/cloudevents-dotnet/Dockerfile b/code-samples/serving/cloudevents/cloudevents-dotnet/Dockerfile index 4cdc8f2e2c..d7cd16a239 100644 --- a/code-samples/serving/cloudevents/cloudevents-dotnet/Dockerfile +++ b/code-samples/serving/cloudevents/cloudevents-dotnet/Dockerfile @@ -12,19 +12,17 @@ # See the License for the specific language governing permissions and # limitations under the License. -FROM mcr.microsoft.com/dotnet/core/sdk:3.1-alpine AS build-env +FROM mcr.microsoft.com/dotnet/sdk:10.0-alpine AS build-env WORKDIR /app - -# Copy csproj and restore as distinct layers -COPY *.csproj ./ -RUN dotnet restore - -# Copy everything else and build +COPY CloudEventsSample.csproj packages.lock.json ./ +RUN dotnet restore CloudEventsSample.csproj --locked-mode COPY . ./ -RUN dotnet publish -c Release -o out +RUN dotnet publish CloudEventsSample.csproj --no-restore -c Release -o /out -# Build runtime image -FROM mcr.microsoft.com/dotnet/core/aspnet:3.1-alpine +FROM mcr.microsoft.com/dotnet/aspnet:10.0-alpine WORKDIR /app -COPY --from=build-env /app/out . +COPY --from=build-env /out . +ENV ASPNETCORE_HTTP_PORTS=8080 +EXPOSE 8080 +USER $APP_UID ENTRYPOINT ["dotnet", "CloudEventsSample.dll"] diff --git a/code-samples/serving/cloudevents/cloudevents-dotnet/Models/SampleOutput.cs b/code-samples/serving/cloudevents/cloudevents-dotnet/Models/SampleOutput.cs index aa37a968dd..3cf11822d7 100644 --- a/code-samples/serving/cloudevents/cloudevents-dotnet/Models/SampleOutput.cs +++ b/code-samples/serving/cloudevents/cloudevents-dotnet/Models/SampleOutput.cs @@ -17,14 +17,7 @@ namespace CloudEventsSample.Models { public class SampleOutput { - /// - /// Cloud Events SDK for .NET unfortunately still uses Newtonsoft.Json and is not up to date with .Net Core 3. - /// We need to provide Newtonsoft.Json.JsonProperty attribute to ensure that CloudEvent.Data is serialized - /// the way we want it to. - /// JsonPropertyName is to ensure that deserialization through System.Text.Json.Serialization is done correctly. - /// [JsonPropertyName("message")] - [Newtonsoft.Json.JsonProperty("message")] public string Message { get; set; } } } diff --git a/code-samples/serving/cloudevents/cloudevents-dotnet/README.md b/code-samples/serving/cloudevents/cloudevents-dotnet/README.md index df7205a43b..cbeaf9148e 100644 --- a/code-samples/serving/cloudevents/cloudevents-dotnet/README.md +++ b/code-samples/serving/cloudevents/cloudevents-dotnet/README.md @@ -1,4 +1,4 @@ -# Cloud Events - .NET Core +# Cloud Events - .NET A simple web app written in ASP.NET and C# that can receive and send Cloud Events that you can use for testing. It supports running in two modes: @@ -12,12 +12,16 @@ can use for testing. It supports running in two modes: send to a Service or Broker when not initiated by a Broker invocation (e.g. implementing an event source) -The application will use `$K_SINK`-mode whenever the environment variable is -specified. +The application uses `$K_SINK` mode when the environment variable is nonempty. +An absent or empty `K_SINK` selects reply mode. + +This sample targets **.NET 10 LTS** and **CloudNative.CloudEvents 2.9.0**, using +the SDK's System.Text.Json formatter for both binary and structured CloudEvents +v1.0. See the [.NET support policy](https://dotnet.microsoft.com/platform/support/policy/dotnet-core) +and [CloudEvents .NET SDK](https://github.com/cloudevents/sdk-csharp). + +Clone the checked-in sample and use its existing source and deployment files: -Do the following the steps to create the sample code and then deploy the app to your -cluster. You can also download a working copy of the sample, by running the -following commands: ```bash git clone https://github.com/knative/docs.git knative-docs @@ -33,23 +37,23 @@ cd knative-docs/code-samples/serving/cloudevents/cloudevents-dotnet ## The sample code -1. If you look in `controllers\CloudEventsController.cs`, you will see two key functions for the +1. In [`Controllers/CloudEventsController.cs`](Controllers/CloudEventsController.cs), you will see two key functions for the different modes of operation: ```csharp - private async Task ReceiveAndSend(CloudEvent receivedEvent) { + private async Task ReceiveAndSend(CloudEvent response) { // This is called whenever an event is received if $K_SINK is set, and sends a new event // to the url in $K_SINK. } - private IActionResult ReceiveAndReply(CloudEvent receivedEvent) { + private IActionResult ReceiveAndReply(CloudEvent response) { // This is called whenever an event is received if $K_SINK is NOT set, and it replies with // the new event instead. } ``` -1. If you look in `Dockerfile`, you will see a method for pulling in the - dependencies and building an ASP.NET container based on Alpine. You can build +1. The [`Dockerfile`](Dockerfile) pulls in the + dependencies and builds an ASP.NET container based on Alpine. You can build and push this to your registry of choice via: ```bash @@ -57,13 +61,48 @@ cd knative-docs/code-samples/serving/cloudevents/cloudevents-dotnet docker buildx build --platform linux/arm64,linux/amd64 -t "" --push . ``` -1. If you look in `service.yaml`, take the `` name and insert it +1. In the checked-in [`service.yaml`](service.yaml), take the `` name and insert it into the `image:` field. ```bash kubectl apply -f service.yaml ``` +The official .NET 10 Alpine images support both arm64 and amd64. The runtime +image runs as the built-in non-root application user. + +## Testing locally with Docker + +Build and run without pushing an image or using a cluster: + +```bash +docker buildx build --load -t cloudevents-dotnet . +docker run --rm -p 8080:8080 cloudevents-dotnet +``` + +Send the request below to `http://localhost:8080`. The response is a structured +CloudEvent whose `data.message` is `Hello, Dave`. Structured JSON requests using +`Content-Type: application/cloudevents+json` are also supported. + +The image listens on port **8080** by default. Set `ASPNETCORE_URLS` to change +the listening address (for example `-e ASPNETCORE_URLS=http://+:8091` with +`-p 8091:8091`); this app does not read `PORT`. The service manifest explicitly +sets `ASPNETCORE_URLS=http://*:8080`. + +To test forwarding, additionally set `-e K_SINK=http://:` +to a receiver reachable from the container. The receiver gets a structured +CloudEvent; the app returns the receiver's status and body rather than the event, +including non-success statuses. Connection failures return HTTP 502; a sink +request exceeding the 10-second timeout returns HTTP 504. + +Requests to `/` must be valid CloudEvents with JSON object data and a nonempty +string `name`. Malformed JSON, missing required event attributes, or missing or +invalid `name` return HTTP 400. `POST /echo` returns the original event as a +structured CloudEvent, retaining its ID, type, source, and data. + +Dependency versions are recorded in the application's +[`packages.lock.json`](packages.lock.json); production builds restore in locked mode. + ## Testing the sample Get the URL for your Service with: diff --git a/code-samples/serving/cloudevents/cloudevents-dotnet/Startup.cs b/code-samples/serving/cloudevents/cloudevents-dotnet/Startup.cs index a6a84c12f1..eac611e3b0 100644 --- a/code-samples/serving/cloudevents/cloudevents-dotnet/Startup.cs +++ b/code-samples/serving/cloudevents/cloudevents-dotnet/Startup.cs @@ -11,7 +11,7 @@ You may obtain a copy of the License at limitations under the License. */ -using CloudNative.CloudEvents; +using System; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Hosting; using Microsoft.Extensions.Configuration; @@ -32,10 +32,8 @@ public Startup(IConfiguration configuration) // This method gets called by the runtime. Use this method to add services to the container. public void ConfigureServices(IServiceCollection services) { - services.AddControllers(opts => - { - opts.InputFormatters.Insert(0, new CloudEventJsonInputFormatter()); - }); + services.AddControllers(); + services.AddHttpClient("sink", client => client.Timeout = TimeSpan.FromSeconds(10)); } // This method gets called by the runtime. Use this method to configure the HTTP request pipeline. @@ -46,7 +44,6 @@ public void Configure(IApplicationBuilder app, IWebHostEnvironment env) app.UseDeveloperExceptionPage(); } - app.UseHttpsRedirection(); app.UseRouting(); app.UseAuthorization(); app.UseEndpoints(endpoints => { endpoints.MapControllers(); }); diff --git a/code-samples/serving/cloudevents/cloudevents-dotnet/ci.sh b/code-samples/serving/cloudevents/cloudevents-dotnet/ci.sh new file mode 100755 index 0000000000..4d2fb94253 --- /dev/null +++ b/code-samples/serving/cloudevents/cloudevents-dotnet/ci.sh @@ -0,0 +1,43 @@ +#!/usr/bin/env bash +# Source from this sample directory; the caller owns cluster setup and ingress forwarding. +IMAGE=dev.local/cloudevents-dotnet:ci +SAMPLE_URL=http://127.0.0.1:18080 + +main() ( + set -euo pipefail + build + deploy + test +) + +build() ( + set -euo pipefail + docker build -t "$IMAGE" . +) + +deploy() ( + set -euo pipefail + kind load docker-image "$IMAGE" + kubectl patch --local -f service.yaml --type=json -p "$( + jq -cn --arg image "$IMAGE" '[ + {op: "replace", path: "/spec/template/spec/containers/0/image", value: $image}, + {op: "add", path: "/spec/template/spec/containers/0/imagePullPolicy", value: "IfNotPresent"} + ]' + )" -o yaml | kubectl --namespace default apply -f - + kubectl --namespace default wait -f service.yaml --for=condition=Ready --timeout=300s +) + +test() ( + set -euo pipefail + local service_url service_host + service_url=$(kubectl --namespace default get -f service.yaml -o jsonpath='{.status.url}') + service_host=${service_url#*://} + [[ "$service_host" =~ ^[a-zA-Z0-9.-]+$ ]] + local response + response=$(curl -fsS --max-time 30 \ + -H "Host: $service_host" -H "Content-Type: application/json" \ + -H "Ce-Specversion: 1.0" -H "Ce-Id: smoke-test" \ + -H "Ce-Type: dev.knative.samples.test" -H "Ce-Source: /smoke-test" \ + --data '{"name":"Knative"}' "$SAMPLE_URL/") + jq -e '(.data // .).message == "Hello, Knative"' <<< "$response" +) diff --git a/code-samples/serving/cloudevents/cloudevents-dotnet/packages.lock.json b/code-samples/serving/cloudevents/cloudevents-dotnet/packages.lock.json new file mode 100644 index 0000000000..6820627570 --- /dev/null +++ b/code-samples/serving/cloudevents/cloudevents-dotnet/packages.lock.json @@ -0,0 +1,30 @@ +{ + "version": 1, + "dependencies": { + "net10.0": { + "CloudNative.CloudEvents.AspNetCore": { + "type": "Direct", + "requested": "[2.9.0, )", + "resolved": "2.9.0", + "contentHash": "EhXZtGGTgpqHbY40H1N9MWreIiejTBPqSDDOeHz54PI8IffDNbWeUP/27o5uSH692zCWenWVQd5mu1qD9e5gfQ==", + "dependencies": { + "CloudNative.CloudEvents": "2.9.0" + } + }, + "CloudNative.CloudEvents.SystemTextJson": { + "type": "Direct", + "requested": "[2.9.0, )", + "resolved": "2.9.0", + "contentHash": "gkf4+RRR8W4VmB7hZr0Wie5VYz+oRKJnuj5CLtyKANa+Yq+eynMqVBnmnjWf8hvgCVDAzyvM2iq6THZageO8jA==", + "dependencies": { + "CloudNative.CloudEvents": "2.9.0" + } + }, + "CloudNative.CloudEvents": { + "type": "Transitive", + "resolved": "2.9.0", + "contentHash": "Qh8/MS3YBI78Q5+AVXN/hNQZkhFW5OyDHdn4Wi05ZzlylKOsTypBiQQ1u0U8iOZ24HMe8Hc0VH2CIwnY4YTGEA==" + } + } + } +} From 54cccfb896d1d7cd1c05daa226ee1b99177ba8d9 Mon Sep 17 00:00:00 2001 From: Dave Protasowski Date: Mon, 26 Oct 2026 09:16:04 -0400 Subject: [PATCH 21/44] Update the Node.js CloudEvents sample --- .../cloudevents/cloudevents-nodejs/Dockerfile | 30 +- .../cloudevents/cloudevents-nodejs/README.md | 53 +- .../cloudevents/cloudevents-nodejs/ci.sh | 43 + .../cloudevents-nodejs/eslint.config.js | 21 + .../cloudevents/cloudevents-nodejs/index.js | 22 +- .../cloudevents-nodejs/package-lock.json | 8824 +++-------------- .../cloudevents-nodejs/package.json | 18 +- 7 files changed, 1796 insertions(+), 7215 deletions(-) create mode 100755 code-samples/serving/cloudevents/cloudevents-nodejs/ci.sh create mode 100644 code-samples/serving/cloudevents/cloudevents-nodejs/eslint.config.js diff --git a/code-samples/serving/cloudevents/cloudevents-nodejs/Dockerfile b/code-samples/serving/cloudevents/cloudevents-nodejs/Dockerfile index 42d6bb7f8e..71c40b847b 100644 --- a/code-samples/serving/cloudevents/cloudevents-nodejs/Dockerfile +++ b/code-samples/serving/cloudevents/cloudevents-nodejs/Dockerfile @@ -12,27 +12,19 @@ # See the License for the specific language governing permissions and # limitations under the License. -FROM registry.access.redhat.com/ubi8/nodejs-12 +FROM node:24.22.1-alpine -# Copy application dependency manifests to the container image. -# A wildcard is used to ensure both package.json AND package-lock.json are copied. -# Copying this separately prevents re-running npm install on every code change. -COPY package*.json ./ - -# Use ci is faster and more reliable following package-lock.json -RUN npm ci --only=production - -# Doc port listening port -ENV PORT 8080 +WORKDIR /app -EXPOSE $PORT - -ARG ENV=production +# Install only the locked runtime dependencies; keep development tools out of the image. +COPY package*.json ./ +RUN npm ci --omit=dev --cache=/tmp/npm-cache && rm -rf /tmp/npm-cache -ENV NODE_ENV $ENV +ENV PORT=8080 NODE_ENV=production +EXPOSE 8080 -# Run the web service on container startup. -CMD npm run $NODE_ENV +COPY --chown=node:node . ./ +USER node -# Copy local code to the container image. -COPY . ./ +# Run Node directly so the application receives container lifecycle signals. +CMD ["node", "index.js"] diff --git a/code-samples/serving/cloudevents/cloudevents-nodejs/README.md b/code-samples/serving/cloudevents/cloudevents-nodejs/README.md index c52ed76b39..12ac836e88 100644 --- a/code-samples/serving/cloudevents/cloudevents-nodejs/README.md +++ b/code-samples/serving/cloudevents/cloudevents-nodejs/README.md @@ -12,12 +12,14 @@ can use for testing. It supports running in two modes: send to a Service or Broker when not initiated by a Broker invocation (e.g. implementing an event source) -The application will use `$K_SINK`-mode whenever the environment variable is -specified. +The application uses `$K_SINK`-mode when the environment variable is nonempty; +when it is absent or empty, the application replies with the event. `PORT` defaults to `8080` and can be overridden when running locally. +On Knative, set the container's `ports[].containerPort` to change the listening +port (for example, `8081`); Knative injects `PORT`, so do not set it explicitly. +Events without a `name` in their data produce `{"message":"Hello, nameless"}`; +the event's `data` is optional. -Do the following steps to create the sample code and then deploy the app to your -cluster. You can also download a working copy of the sample, by running the -following commands: +Clone the sample's checked-in source, then build and deploy it to your cluster: ```bash git clone https://github.com/knative/docs.git knative-docs @@ -26,6 +28,7 @@ cd knative-docs/code-samples/serving/cloudevents/cloudevents-nodejs ## Before you begin +- Node.js 24 LTS for local development (the Dockerfile uses Node.js 24.22.1). - A Kubernetes cluster with Knative installed and DNS configured. See [Install Knative Serving](https://knative.dev/docs/install/serving/install-serving-with-yaml). - [Docker](https://www.docker.com) installed and running on your local machine, @@ -33,7 +36,7 @@ cd knative-docs/code-samples/serving/cloudevents/cloudevents-nodejs ## The Sample Code -In the `index.js` file, you will see two key functions for the different modes +In [index.js](index.js), you will see two key functions for the different modes of operation: ```js @@ -48,9 +51,19 @@ of operation: } ``` +## Development checks + +Install the locked dependencies with `npm ci`, then run `npm run lint`. +Development-only tools are excluded from the production image. + +This sample uses CloudEvents SDK 10 and Express 5. The lockfile overrides the SDK's +transitive `uuid` dependency to the compatible CommonJS 11.x line to avoid +[GHSA-w5hq-g745-h8pq](https://github.com/advisories/GHSA-w5hq-g745-h8pq). +The production container runs Node directly as the non-root `node` user. + ## Build and Deploy the Application -In the `Dockerfile`, you can see how the dependencies are installed using npm. +The checked-in [Dockerfile](Dockerfile) installs the dependencies using npm. You can build and push this to your registry of choice via: ```bash @@ -60,7 +73,7 @@ You can build and push this to your registry of choice via: ### yaml - To deploy the Knative service, edit the `service.yaml` file and replace `` with the image you have just created. + To deploy the Knative service, edit [service.yaml](service.yaml) and replace `` with the image you have just created. ```bash kubectl apply -f service.yaml @@ -103,6 +116,30 @@ You will get back: {"message":"Hello, Dave"} ``` +The HTTP 200 response is a binary-mode CloudEvent: the body above contains the +event data, and the `ce-specversion`, `ce-id`, `ce-source`, and `ce-type` response +headers contain its attributes. The response type is `dev.knative.docs.sample`. +The app also accepts structured-mode requests with +`Content-Type: application/cloudevents+json`. Invalid events receive HTTP 415. + +### Send to a sink + +To forward the generated event instead of returning it, uncomment the `K_SINK` +environment variable in `service.yaml`, set its value to your destination URL, +and apply the manifest again. With `kn`, use: + +```bash +kn service update cloudevents-nodejs --env K_SINK=http://your-sink.default.svc.cluster.local +``` + +Send the same request above. In this mode the app returns HTTP 202 with an empty +body, and sends the greeting as a binary-mode CloudEvent to `K_SINK`. Check the +sink's received events and the app logs to confirm delivery. The acknowledgment +is sent before delivery completes; delivery errors are logged, not retried. +HTTP non-2xx responses, network errors, and the 10-second outbound request timeout +are reported as delivery failures. A 202 acknowledgment does not guarantee that +the sink accepted the event. + ## Removing the sample app deployment To remove the sample app from your cluster, delete the service. diff --git a/code-samples/serving/cloudevents/cloudevents-nodejs/ci.sh b/code-samples/serving/cloudevents/cloudevents-nodejs/ci.sh new file mode 100755 index 0000000000..b9dc8aa96c --- /dev/null +++ b/code-samples/serving/cloudevents/cloudevents-nodejs/ci.sh @@ -0,0 +1,43 @@ +#!/usr/bin/env bash +# Source from this sample directory; the caller owns cluster setup and ingress forwarding. +IMAGE=dev.local/cloudevents-nodejs:ci +SAMPLE_URL=http://127.0.0.1:18080 + +main() ( + set -euo pipefail + build + deploy + test +) + +build() ( + set -euo pipefail + docker build -t "$IMAGE" . +) + +deploy() ( + set -euo pipefail + kind load docker-image "$IMAGE" + kubectl patch --local -f service.yaml --type=json -p "$( + jq -cn --arg image "$IMAGE" '[ + {op: "replace", path: "/spec/template/spec/containers/0/image", value: $image}, + {op: "add", path: "/spec/template/spec/containers/0/imagePullPolicy", value: "IfNotPresent"} + ]' + )" -o yaml | kubectl --namespace default apply -f - + kubectl --namespace default wait -f service.yaml --for=condition=Ready --timeout=300s +) + +test() ( + set -euo pipefail + local service_url service_host + service_url=$(kubectl --namespace default get -f service.yaml -o jsonpath='{.status.url}') + service_host=${service_url#*://} + [[ "$service_host" =~ ^[a-zA-Z0-9.-]+$ ]] + local response + response=$(curl -fsS --max-time 30 \ + -H "Host: $service_host" -H "Content-Type: application/json" \ + -H "Ce-Specversion: 1.0" -H "Ce-Id: smoke-test" \ + -H "Ce-Type: dev.knative.samples.test" -H "Ce-Source: /smoke-test" \ + --data '{"name":"Knative"}' "$SAMPLE_URL/") + jq -e '(.data // .).message == "Hello, Knative"' <<< "$response" +) diff --git a/code-samples/serving/cloudevents/cloudevents-nodejs/eslint.config.js b/code-samples/serving/cloudevents/cloudevents-nodejs/eslint.config.js new file mode 100644 index 0000000000..cb94365a98 --- /dev/null +++ b/code-samples/serving/cloudevents/cloudevents-nodejs/eslint.config.js @@ -0,0 +1,21 @@ +module.exports = [{ + files: ['*.js'], + languageOptions: { + ecmaVersion: 'latest', + sourceType: 'commonjs', + globals: Object.fromEntries([ + 'require', 'module', 'process', 'console', '__dirname', 'setTimeout', + 'fetch', 'AbortSignal' + ].map(name => [name, 'readonly'])) + }, + rules: { + 'no-undef': 'error', + 'no-unused-vars': 'error', + 'no-unreachable': 'error', + eqeqeq: 'error', + 'valid-typeof': 'error', + semi: ['error', 'never'], + quotes: ['error', 'single'], + indent: ['error', 2] + } +}] diff --git a/code-samples/serving/cloudevents/cloudevents-nodejs/index.js b/code-samples/serving/cloudevents/cloudevents-nodejs/index.js index 864e3ab141..b642981a45 100644 --- a/code-samples/serving/cloudevents/cloudevents-nodejs/index.js +++ b/code-samples/serving/cloudevents/cloudevents-nodejs/index.js @@ -15,11 +15,25 @@ limitations under the License. */ const express = require('express') -const { CloudEvent, HTTP, emitterFor, httpTransport } = require('cloudevents') +const { CloudEvent, HTTP, emitterFor } = require('cloudevents') const PORT = process.env.PORT || 8080 const target = process.env.K_SINK const app = express() -const emit = target ? emitterFor(httpTransport(target)) : null +// The SDK's HTTP transport does not expose response status codes. Use Node's +// built-in fetch transport so rejected deliveries are logged as failures. +const emit = target + ? emitterFor(async message => { + const response = await fetch(target, { + method: 'POST', + headers: message.headers, + body: message.body, + signal: AbortSignal.timeout(10000) + }) + const data = await response.text() + if (!response.ok) throw new Error(`K_SINK returned HTTP ${response.status}`) + return { status: response.status, headers: Object.fromEntries(response.headers), data } + }) + : null const main = () => { app.listen(PORT, function () { @@ -31,7 +45,7 @@ const main = () => { // handle shared the logic for producing the Response event from the Request. const handle = (data) => { - return { message: `Hello, ${data.name ? data.name : 'nameless'}` } + return { message: `Hello, ${data && data.name ? data.name : 'nameless'}` } } // receiveAndSend responds with ack, and send a new event forward @@ -50,7 +64,7 @@ const receiveAndSend = (cloudEvent, res) => { console.log(`Sent event: ${JSON.stringify(ce, null, 2)}`) console.log(`K_SINK responded: ${JSON.stringify({ status: responseSink.status, headers: responseSink.headers, data: responseSink.data }, null, 2)}`) }) - .catch(console.error) + .catch(err => console.error('Failed to send event to K_SINK:', err)) } // receiveAndReply responds with new event diff --git a/code-samples/serving/cloudevents/cloudevents-nodejs/package-lock.json b/code-samples/serving/cloudevents/cloudevents-nodejs/package-lock.json index a5d89d021b..d56c7a9b73 100644 --- a/code-samples/serving/cloudevents/cloudevents-nodejs/package-lock.json +++ b/code-samples/serving/cloudevents/cloudevents-nodejs/package-lock.json @@ -1,7 +1,7 @@ { "name": "knative-serving-cloudevents", "version": "1.0.0", - "lockfileVersion": 2, + "lockfileVersion": 3, "requires": true, "packages": { "": { @@ -9,19 +9,47 @@ "version": "1.0.0", "license": "Apache-2.0", "dependencies": { - "cloudevents": "^6.0.1", - "express": "^4.22.3", - "nodemon": "^2.0.20" + "cloudevents": "^10.0.0", + "express": "^5.3.0" }, "devDependencies": { - "standard": "^17.1.2" + "eslint": "^10.13.0", + "nodemon": "^3.1.14" + }, + "engines": { + "node": ">=24 <25" + } + }, + "node_modules/@cacheable/memory": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@cacheable/memory/-/memory-2.2.0.tgz", + "integrity": "sha512-CTLKqLItRCEixEAewD3/j9DB3/o96gpTPD4eJ1v+DGOlxZRZncRQkGYqqnAGCscYd6RNeXfGeiuCphsPtqyIfQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@cacheable/utils": "^2.5.0", + "@keyv/bigmap": "^1.3.1", + "hookified": "^1.15.1", + "keyv": "^5.6.0" + } + }, + "node_modules/@cacheable/utils": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/@cacheable/utils/-/utils-2.5.0.tgz", + "integrity": "sha512-buipgOVDkkPXNR5+xBpDw7Zk2n1EvU7qBJCNUcL7rhQ//kfpOXPAvQ511Os0vpLYJ1pZnvudNytkQt2hst3wqA==", + "dev": true, + "license": "MIT", + "dependencies": { + "hashery": "^1.5.1", + "keyv": "^5.6.0" } }, "node_modules/@eslint-community/eslint-utils": { - "version": "4.7.0", - "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.7.0.tgz", - "integrity": "sha512-dyybb3AcajC7uha6CvhdVRJqaKyn7w2YKqKyAN37NKYgZT36w+iRb0Dymmc5qEJ549c/S31cMMSFd75bteCpCw==", + "version": "4.10.1", + "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.10.1.tgz", + "integrity": "sha512-cuadcxVFE8sDK6iWJbs8Sn0av2Nrh2QSGQhVlBW9AaAHqHwjWsZHT8LJ4hFGPh7ASBV2deFdM7H/DPjulmh8rg==", "dev": true, + "license": "MIT", "dependencies": { "eslint-visitor-keys": "^3.4.3" }, @@ -35,113 +63,138 @@ "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" } }, + "node_modules/@eslint-community/eslint-utils/node_modules/eslint-visitor-keys": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", + "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, "node_modules/@eslint-community/regexpp": { - "version": "4.12.1", - "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.12.1.tgz", - "integrity": "sha512-CCZCDJuduB9OUkFkY2IgppNZMi2lBQgD2qzwXkEia16cge2pijY/aXi96CJMquDMn3nJdlPV1A5KrJEXwfLNzQ==", + "version": "4.12.2", + "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.12.2.tgz", + "integrity": "sha512-EriSTlt5OC9/7SXkRSCAhfSxxoSUgBm33OH+IkwbdpgoqsSsUg7y3uh+IICI/Qg4BBWr3U2i39RpmycbxMq4ew==", "dev": true, + "license": "MIT", "engines": { "node": "^12.0.0 || ^14.0.0 || >=16.0.0" } }, - "node_modules/@eslint/eslintrc": { - "version": "2.1.4", - "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-2.1.4.tgz", - "integrity": "sha512-269Z39MS6wVJtsoUl10L60WdkhJVdPG24Q4eZTH3nnF6lpvSShEK3wQjDX9JRWAUPvPh7COouPpU9IrqaZFvtQ==", + "node_modules/@eslint/config-array": { + "version": "0.23.5", + "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.23.5.tgz", + "integrity": "sha512-Y3kKLvC1dvTOT+oGlqNQ1XLqK6D1HU2YXPc52NmAlJZbMMWDzGYXMiPRJ8TYD39muD/OTjlZmNJ4ib7dvSrMBA==", "dev": true, + "license": "Apache-2.0", "dependencies": { - "ajv": "^6.12.4", - "debug": "^4.3.2", - "espree": "^9.6.0", - "globals": "^13.19.0", - "ignore": "^5.2.0", - "import-fresh": "^3.2.1", - "js-yaml": "^4.1.0", - "minimatch": "^3.1.2", - "strip-json-comments": "^3.1.1" + "@eslint/object-schema": "^3.0.5", + "debug": "^4.3.1", + "minimatch": "^10.2.4" }, "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" + "node": "^20.19.0 || ^22.13.0 || >=24" } }, - "node_modules/@eslint/eslintrc/node_modules/debug": { - "version": "4.4.1", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.1.tgz", - "integrity": "sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==", + "node_modules/@eslint/config-helpers": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/@eslint/config-helpers/-/config-helpers-0.7.0.tgz", + "integrity": "sha512-DObd/KKUsU+FaFv4PLxSRenpXfQWmPXXP3pPZ6/K1PCrMu2vQpMDMuQe/BqYeoLcz8ro0bVDF1RxOJgfVEdhUw==", "dev": true, + "license": "Apache-2.0", "dependencies": { - "ms": "^2.1.3" + "@eslint/core": "^1.2.1" }, "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } + "node": "^20.19.0 || ^22.13.0 || >=24" } }, - "node_modules/@eslint/eslintrc/node_modules/ms": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", - "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", - "dev": true + "node_modules/@eslint/core": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@eslint/core/-/core-1.2.1.tgz", + "integrity": "sha512-MwcE1P+AZ4C6DWlpin/OmOA54mmIZ/+xZuJiQd4SyB29oAJjN30UW9wkKNptW2ctp4cEsvhlLY/CsQ1uoHDloQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@types/json-schema": "^7.0.15" + }, + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=24" + } }, - "node_modules/@eslint/js": { - "version": "8.57.1", - "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.57.1.tgz", - "integrity": "sha512-d9zaMRSTIKDLhctzH12MtXvJKSSUhaHcjV+2Z+GK+EEY7XKpP5yR4x+N3TAcHTcu963nIr+TMcCb4DBCYX1z6Q==", + "node_modules/@eslint/object-schema": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/@eslint/object-schema/-/object-schema-3.0.5.tgz", + "integrity": "sha512-vqTaUEgxzm+YDSdElad6PiRoX4t8VGDjCtt05zn4nU810UIx/uNEV7/lZJ6KwFThKZOzOxzXy48da+No7HZaMw==", "dev": true, + "license": "Apache-2.0", "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + "node": "^20.19.0 || ^22.13.0 || >=24" } }, - "node_modules/@humanwhocodes/config-array": { - "version": "0.13.0", - "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.13.0.tgz", - "integrity": "sha512-DZLEEqFWQFiyK6h5YIeynKx7JlvCYWL0cImfSRXZ9l4Sg2efkFGTuFf6vzXjK1cq6IYkU+Eg/JizXw+TD2vRNw==", - "deprecated": "Use @eslint/config-array instead", + "node_modules/@eslint/plugin-kit": { + "version": "0.7.3", + "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.7.3.tgz", + "integrity": "sha512-IkO+/KEUvwbVpiURZg+P7zF74z5Jxe0UgJxVni+RtoHQ6IZieXaO02kmadomap/q+l6bc/jdPGGqTjhuZnuz1Q==", "dev": true, + "license": "Apache-2.0", "dependencies": { - "@humanwhocodes/object-schema": "^2.0.3", - "debug": "^4.3.1", - "minimatch": "^3.0.5" + "@eslint/core": "^1.2.1", + "levn": "^0.4.1" }, "engines": { - "node": ">=10.10.0" + "node": "^20.19.0 || ^22.13.0 || >=24" } }, - "node_modules/@humanwhocodes/config-array/node_modules/debug": { - "version": "4.4.1", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.1.tgz", - "integrity": "sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==", + "node_modules/@humanfs/core": { + "version": "0.19.2", + "resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.2.tgz", + "integrity": "sha512-UhXNm+CFMWcbChXywFwkmhqjs3PRCmcSa/hfBgLIb7oQ5HNb1wS0icWsGtSAUNgefHeI+eBrA8I1fxmbHsGdvA==", "dev": true, + "license": "Apache-2.0", "dependencies": { - "ms": "^2.1.3" + "@humanfs/types": "^0.15.0" }, "engines": { - "node": ">=6.0" + "node": ">=18.18.0" + } + }, + "node_modules/@humanfs/node": { + "version": "0.16.8", + "resolved": "https://registry.npmjs.org/@humanfs/node/-/node-0.16.8.tgz", + "integrity": "sha512-gE1eQNZ3R++kTzFUpdGlpmy8kDZD/MLyHqDwqjkVQI0JMdI1D51sy1H958PNXYkM2rAac7e5/CnIKZrHtPh3BQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@humanfs/core": "^0.19.2", + "@humanfs/types": "^0.15.0", + "@humanwhocodes/retry": "^0.4.0" }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } + "engines": { + "node": ">=18.18.0" } }, - "node_modules/@humanwhocodes/config-array/node_modules/ms": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", - "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", - "dev": true + "node_modules/@humanfs/types": { + "version": "0.15.0", + "resolved": "https://registry.npmjs.org/@humanfs/types/-/types-0.15.0.tgz", + "integrity": "sha512-ZZ1w0aoQkwuUuC7Yf+7sdeaNfqQiiLcSRbfI08oAxqLtpXQr9AIVX7Ay7HLDuiLYAaFPu8oBYNq/QIi9URHJ3Q==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=18.18.0" + } }, "node_modules/@humanwhocodes/module-importer": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==", "dev": true, + "license": "Apache-2.0", "engines": { "node": ">=12.22" }, @@ -150,88 +203,84 @@ "url": "https://github.com/sponsors/nzakas" } }, - "node_modules/@humanwhocodes/object-schema": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-2.0.3.tgz", - "integrity": "sha512-93zYdMES/c1D69yZiKDBj0V24vqNzB/koF26KPaagAfd3P/4gUlh3Dys5ogAK+Exi9QyzlD8x/08Zt7wIKcDcA==", - "deprecated": "Use @eslint/object-schema instead", - "dev": true - }, - "node_modules/@nodelib/fs.scandir": { - "version": "2.1.5", - "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", - "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", - "dev": true, - "dependencies": { - "@nodelib/fs.stat": "2.0.5", - "run-parallel": "^1.1.9" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/@nodelib/fs.stat": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", - "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", + "node_modules/@humanwhocodes/retry": { + "version": "0.4.3", + "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.4.3.tgz", + "integrity": "sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ==", "dev": true, + "license": "Apache-2.0", "engines": { - "node": ">= 8" + "node": ">=18.18" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" } }, - "node_modules/@nodelib/fs.walk": { - "version": "1.2.8", - "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", - "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", + "node_modules/@keyv/bigmap": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/@keyv/bigmap/-/bigmap-1.3.1.tgz", + "integrity": "sha512-WbzE9sdmQtKy8vrNPa9BRnwZh5UF4s1KTmSK0KUVLo3eff5BlQNNWDnFOouNpKfPKDnms9xynJjsMYjMaT/aFQ==", "dev": true, + "license": "MIT", "dependencies": { - "@nodelib/fs.scandir": "2.1.5", - "fastq": "^1.6.0" + "hashery": "^1.4.0", + "hookified": "^1.15.0" }, "engines": { - "node": ">= 8" + "node": ">= 18" + }, + "peerDependencies": { + "keyv": "^5.6.0" } }, - "node_modules/@rtsao/scc": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@rtsao/scc/-/scc-1.1.0.tgz", - "integrity": "sha512-zt6OdqaDoOnJ1ZYsCYGt9YmWzDXl4vQdKTyJev62gFhRGKdx7mcT54V9KIjg+d2wi9EXsPvAPKe7i7WjfVWB8g==", - "dev": true + "node_modules/@keyv/serialize": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@keyv/serialize/-/serialize-1.1.1.tgz", + "integrity": "sha512-dXn3FZhPv0US+7dtJsIi2R+c7qWYiReoEh5zUntWCf4oSpMNib8FDhSoed6m3QyZdx5hK7iLFkYk3rNxwt8vTA==", + "dev": true, + "license": "MIT" }, - "node_modules/@types/json5": { - "version": "0.0.29", - "resolved": "https://registry.npmjs.org/@types/json5/-/json5-0.0.29.tgz", - "integrity": "sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==", - "dev": true + "node_modules/@types/esrecurse": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/@types/esrecurse/-/esrecurse-4.3.1.tgz", + "integrity": "sha512-xJBAbDifo5hpffDBuHl0Y8ywswbiAp/Wi7Y/GtAgSlZyIABppyurxVueOPE8LUQOxdlgi6Zqce7uoEpqNTeiUw==", + "dev": true, + "license": "MIT" }, - "node_modules/@ungap/structured-clone": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.3.0.tgz", - "integrity": "sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g==", - "dev": true + "node_modules/@types/estree": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.9.tgz", + "integrity": "sha512-GhdPgy1el4/ImP05X05Uw4cw2/M93BCUmnEvWZNStlCzEKME4Fkk+YpoA5OiHNQmoS7Cafb8Xa3Pya8m1Qrzeg==", + "dev": true, + "license": "MIT" }, - "node_modules/abbrev": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz", - "integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==" + "node_modules/@types/json-schema": { + "version": "7.0.15", + "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", + "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==", + "dev": true, + "license": "MIT" }, "node_modules/accepts": { - "version": "1.3.8", - "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", - "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/accepts/-/accepts-2.0.0.tgz", + "integrity": "sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng==", + "license": "MIT", "dependencies": { - "mime-types": "~2.1.34", - "negotiator": "0.6.3" + "mime-types": "^3.0.0", + "negotiator": "^1.0.0" }, "engines": { "node": ">= 0.6" } }, "node_modules/acorn": { - "version": "8.15.0", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz", - "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", + "version": "8.19.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.19.0.tgz", + "integrity": "sha512-oJlA3XiRm7Cyk6qFD2Jn8ak9B7jSy0qy00ADO3+8dpT0LSjFihQYv4C02LFCSYJV3Q37xYwwRy5m+IpIiUzqWw==", "dev": true, + "license": "MIT", "bin": { "acorn": "bin/acorn" }, @@ -244,20 +293,21 @@ "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", "dev": true, + "license": "MIT", "peerDependencies": { "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" } }, "node_modules/ajv": { - "version": "6.14.0", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.14.0.tgz", - "integrity": "sha512-IWrosm/yrn43eiKqkfkHis7QioDleaXQHdDVPKg0FSwwd/DuvyX79TZnFOnYpB7dcsFAMmtFztZuXPDvSePkFw==", - "dev": true, + "version": "8.20.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.20.0.tgz", + "integrity": "sha512-Thbli+OlOj+iMPYFBVBfJ3OmCAnaSyNn4M1vz9T6Gka5Jt9ba/HIR56joy65tY6kx/FCF5VXNB819Y7/GUrBGA==", + "license": "MIT", "dependencies": { - "fast-deep-equal": "^3.1.1", - "fast-json-stable-stringify": "^2.0.0", - "json-schema-traverse": "^0.4.1", - "uri-js": "^4.2.2" + "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", @@ -268,6 +318,7 @@ "version": "2.1.1", "resolved": "https://registry.npmjs.org/ajv-formats/-/ajv-formats-2.1.1.tgz", "integrity": "sha512-Wx0Kx52hxE7C18hkMEggYlEifqWZtYaRgouJor+WMdPnQyEK13vgEWyVNup7SoeeoLMsr4kf5h6dOW11I15MUA==", + "license": "MIT", "dependencies": { "ajv": "^8.0.0" }, @@ -280,54 +331,12 @@ } } }, - "node_modules/ajv-formats/node_modules/ajv": { - "version": "8.18.0", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.18.0.tgz", - "integrity": "sha512-PlXPeEWMXMZ7sPYOHqmDyCJzcfNrUr3fGNKtezX14ykXOEIvyK81d+qydx89KY5O71FKMPaQ2vBfBFI5NHR63A==", - "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/ajv-formats/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==" - }, - "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, - "engines": { - "node": ">=8" - } - }, - "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, - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, "node_modules/anymatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.2.tgz", - "integrity": "sha512-P43ePfOAIupkguHUycrc4qJ9kz8ZiuOUijaETwX7THt0Y/GNK7v0aa8rY816xWjZ7rJdA5XdMcpVFTKMq+RvWg==", + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", + "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", + "dev": true, + "license": "ISC", "dependencies": { "normalize-path": "^3.0.0", "picomatch": "^2.0.4" @@ -336,20 +345,13 @@ "node": ">= 8" } }, - "node_modules/argparse": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", - "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", - "dev": true - }, - "node_modules/array-buffer-byte-length": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/array-buffer-byte-length/-/array-buffer-byte-length-1.0.2.tgz", - "integrity": "sha512-LHE+8BuR7RYGDKvnrmcuSq3tDcKv9OFEXQt/HpbZhY7V6h0zlUXutnAD82GiFx9rdieCMjkvtcsPqBwgUl1Iiw==", - "dev": true, + "node_modules/available-typed-arrays": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.7.tgz", + "integrity": "sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ==", + "license": "MIT", "dependencies": { - "call-bound": "^1.0.3", - "is-array-buffer": "^3.0.5" + "possible-typed-array-names": "^1.0.0" }, "engines": { "node": ">= 0.4" @@ -358,102 +360,121 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/array-flatten": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", - "integrity": "sha1-ml9pkFGx5wczKPKgCJaLZOopVdI=" + "node_modules/balanced-match": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz", + "integrity": "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "18 || 20 || >=22" + } }, - "node_modules/array-includes": { - "version": "3.1.9", - "resolved": "https://registry.npmjs.org/array-includes/-/array-includes-3.1.9.tgz", - "integrity": "sha512-FmeCCAenzH0KH381SPT5FZmiA/TmpndpcaShhfgEN9eCVjnFBqq3l1xrI42y8+PPLI6hypzou4GXw00WHmPBLQ==", + "node_modules/bignumber.js": { + "version": "9.3.1", + "resolved": "https://registry.npmjs.org/bignumber.js/-/bignumber.js-9.3.1.tgz", + "integrity": "sha512-Ko0uX15oIUS7wJ3Rb30Fs6SkVbLmPBAKdlm7q9+ak9bbIeFf0MwuBsQV6z7+X768/cHsfg+WlysDWJcmthjsjQ==", + "license": "MIT", + "engines": { + "node": "*" + } + }, + "node_modules/binary-extensions": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz", + "integrity": "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==", "dev": true, - "dependencies": { - "call-bind": "^1.0.8", - "call-bound": "^1.0.4", - "define-properties": "^1.2.1", - "es-abstract": "^1.24.0", - "es-object-atoms": "^1.1.1", - "get-intrinsic": "^1.3.0", - "is-string": "^1.1.1", - "math-intrinsics": "^1.1.0" - }, + "license": "MIT", "engines": { - "node": ">= 0.4" + "node": ">=8" }, "funding": { - "url": "https://github.com/sponsors/ljharb" + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/array.prototype.findlast": { - "version": "1.2.5", - "resolved": "https://registry.npmjs.org/array.prototype.findlast/-/array.prototype.findlast-1.2.5.tgz", - "integrity": "sha512-CVvd6FHg1Z3POpBLxO6E6zr+rSKEQ9L6rZHAaY7lLfhKsWYUBBOuMs0e9o24oopj6H+geRCX0YJ+TJLBK2eHyQ==", - "dev": true, + "node_modules/body-parser": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-2.4.0.tgz", + "integrity": "sha512-Ifk1KcLrxaGt1occQagQNFNNOqRt1LFuehEnHHzB8Z6cRgIxRU1TBaiIsUiCdA+gy5PL7Y1IX31DNKkRmxtXjQ==", + "license": "MIT", "dependencies": { - "call-bind": "^1.0.7", - "define-properties": "^1.2.1", - "es-abstract": "^1.23.2", - "es-errors": "^1.3.0", - "es-object-atoms": "^1.0.0", - "es-shim-unscopables": "^1.0.2" + "bytes": "^3.1.2", + "content-type": "^2.1.0", + "debug": "^4.4.3", + "http-errors": "^2.0.1", + "iconv-lite": "^0.7.3", + "on-finished": "^2.4.1", + "qs": "^6.16.1", + "raw-body": "^3.0.2", + "type-is": "^2.1.0" }, "engines": { - "node": ">= 0.4" + "node": ">=18" }, "funding": { - "url": "https://github.com/sponsors/ljharb" + "type": "opencollective", + "url": "https://opencollective.com/express" } }, - "node_modules/array.prototype.findlastindex": { - "version": "1.2.6", - "resolved": "https://registry.npmjs.org/array.prototype.findlastindex/-/array.prototype.findlastindex-1.2.6.tgz", - "integrity": "sha512-F/TKATkzseUExPlfvmwQKGITM3DGTK+vkAsCZoDc5daVygbJBnjEUCbgkAvVFsgfXfX4YIqZ/27G3k3tdXrTxQ==", + "node_modules/brace-expansion": { + "version": "5.0.12", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.12.tgz", + "integrity": "sha512-YovQ3rzhaLMIrDjNDMkNS01tea93qhEhG5xy8f6+R0l+dw3Ki+5sCoIoI942iuLZTHWogWktgwVDhU09iNEimQ==", "dev": true, + "license": "MIT", "dependencies": { - "call-bind": "^1.0.8", - "call-bound": "^1.0.4", - "define-properties": "^1.2.1", - "es-abstract": "^1.23.9", - "es-errors": "^1.3.0", - "es-object-atoms": "^1.1.1", - "es-shim-unscopables": "^1.1.0" + "balanced-match": "^4.0.2" }, "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "node": "20 || >=22" } }, - "node_modules/array.prototype.flat": { - "version": "1.3.3", - "resolved": "https://registry.npmjs.org/array.prototype.flat/-/array.prototype.flat-1.3.3.tgz", - "integrity": "sha512-rwG/ja1neyLqCuGZ5YYrznA62D4mZXg0i1cIskIUKSiqF3Cje9/wXAls9B9s1Wa2fomMsIv8czB8jZcPmxCXFg==", + "node_modules/braces": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", + "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", "dev": true, + "license": "MIT", "dependencies": { - "call-bind": "^1.0.8", - "define-properties": "^1.2.1", - "es-abstract": "^1.23.5", - "es-shim-unscopables": "^1.0.2" + "fill-range": "^7.1.1" }, "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "node": ">=8" } }, - "node_modules/array.prototype.flatmap": { - "version": "1.3.3", - "resolved": "https://registry.npmjs.org/array.prototype.flatmap/-/array.prototype.flatmap-1.3.3.tgz", - "integrity": "sha512-Y7Wt51eKJSyi80hFrJCePGGNo5ktJCslFuboqJsbf57CCPcm5zztluPlc4/aD8sWsKvlwatezpV4U1efk8kpjg==", + "node_modules/bytes": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", + "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/cacheable": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/cacheable/-/cacheable-2.5.0.tgz", + "integrity": "sha512-60cyAOytib/OzBw1JNSoSV/boK1AtHryDIjvVBk7XbN4ugfkM3+Sry7fEjNgPMGgOjuaZPAp8ruZ0Cxafwyq9g==", "dev": true, + "license": "MIT", + "dependencies": { + "@cacheable/memory": "^2.2.0", + "@cacheable/utils": "^2.5.0", + "hookified": "^1.15.0", + "keyv": "^5.6.0", + "qified": "^0.10.1" + } + }, + "node_modules/call-bind": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.9.tgz", + "integrity": "sha512-a/hy+pNsFUTR+Iz8TCJvXudKVLAnz/DyeSUo10I5yvFDQJBFU2s9uqQpoSrJlroHUKoKqzg+epxyP9lqFdzfBQ==", + "license": "MIT", "dependencies": { - "call-bind": "^1.0.8", - "define-properties": "^1.2.1", - "es-abstract": "^1.23.5", - "es-shim-unscopables": "^1.0.2" + "call-bind-apply-helpers": "^1.0.2", + "es-define-property": "^1.0.1", + "get-intrinsic": "^1.3.0", + "set-function-length": "^1.2.2" }, "engines": { "node": ">= 0.4" @@ -462,35 +483,27 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/array.prototype.tosorted": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/array.prototype.tosorted/-/array.prototype.tosorted-1.1.4.tgz", - "integrity": "sha512-p6Fx8B7b7ZhL/gmUsAy0D15WhvDccw3mnGNbZpi3pmeJdxtWsj2jEaI4Y6oo3XiHfzuSgPwKc04MYt6KgvC/wA==", - "dev": true, + "node_modules/call-bind-apply-helpers": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", + "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", + "license": "MIT", "dependencies": { - "call-bind": "^1.0.7", - "define-properties": "^1.2.1", - "es-abstract": "^1.23.3", "es-errors": "^1.3.0", - "es-shim-unscopables": "^1.0.2" + "function-bind": "^1.1.2" }, "engines": { "node": ">= 0.4" } }, - "node_modules/arraybuffer.prototype.slice": { + "node_modules/call-bound": { "version": "1.0.4", - "resolved": "https://registry.npmjs.org/arraybuffer.prototype.slice/-/arraybuffer.prototype.slice-1.0.4.tgz", - "integrity": "sha512-BNoCY6SXXPQ7gF2opIP4GBE+Xw7U+pHMYKuzjgCN3GwiaIR09UUeKfheyIry77QtrCBlC0KK0q5/TER/tYh3PQ==", - "dev": true, + "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz", + "integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==", + "license": "MIT", "dependencies": { - "array-buffer-byte-length": "^1.0.1", - "call-bind": "^1.0.8", - "define-properties": "^1.2.1", - "es-abstract": "^1.23.5", - "es-errors": "^1.3.0", - "get-intrinsic": "^1.2.6", - "is-array-buffer": "^3.0.4" + "call-bind-apply-helpers": "^1.0.2", + "get-intrinsic": "^1.3.0" }, "engines": { "node": ">= 0.4" @@ -499,151 +512,153 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/async-function": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/async-function/-/async-function-1.0.0.tgz", - "integrity": "sha512-hsU18Ae8CDTR6Kgu9DYf0EbCr/a5iGL0rytQDobUcdpYOKokk8LEjVphnXkDkgpi0wYVsqrXuP0bZxJaTqdgoA==", + "node_modules/chokidar": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz", + "integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==", "dev": true, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/available-typed-arrays": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.7.tgz", - "integrity": "sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ==", + "license": "MIT", "dependencies": { - "possible-typed-array-names": "^1.0.0" + "anymatch": "~3.1.2", + "braces": "~3.0.2", + "glob-parent": "~5.1.2", + "is-binary-path": "~2.1.0", + "is-glob": "~4.0.1", + "normalize-path": "~3.0.0", + "readdirp": "~3.6.0" }, "engines": { - "node": ">= 0.4" + "node": ">= 8.10.0" }, "funding": { - "url": "https://github.com/sponsors/ljharb" + "url": "https://paulmillr.com/funding/" + }, + "optionalDependencies": { + "fsevents": "~2.3.2" } }, - "node_modules/balanced-match": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", - "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=" - }, - "node_modules/binary-extensions": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz", - "integrity": "sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==", + "node_modules/chokidar/node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.1" + }, "engines": { - "node": ">=8" + "node": ">= 6" } }, - "node_modules/body-parser": { - "version": "1.20.6", - "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.6.tgz", - "integrity": "sha512-p5tAzS57i5MV9fZFDj9LeIiTZEufbSe2eDozP+ElheSUq1m74CRq1jI4mYNDdVs9vQztXFLuk/Gd6BWTdwRJ5g==", + "node_modules/cloudevents": { + "version": "10.0.0", + "resolved": "https://registry.npmjs.org/cloudevents/-/cloudevents-10.0.0.tgz", + "integrity": "sha512-uyzC+PpMMRawbouHO+3mlisr3QfEDObmo2pN4oTTF6dZncZgpIzdasZx0tRBFI1dMsqCLZZXMtz8cUuvYqHdbw==", + "license": "Apache-2.0", "dependencies": { - "bytes": "~3.1.2", - "content-type": "~1.0.5", - "debug": "2.6.9", - "depd": "2.0.0", - "destroy": "~1.2.0", - "http-errors": "~2.0.1", - "iconv-lite": "~0.4.24", - "on-finished": "~2.4.1", - "qs": "~6.15.1", - "raw-body": "~2.5.3", - "type-is": "~1.6.18", - "unpipe": "~1.0.0" + "ajv": "^8.11.0", + "ajv-formats": "^2.1.1", + "json-bigint": "^1.0.0", + "process": "^0.11.10", + "util": "^0.12.4", + "uuid": "^8.3.2" }, "engines": { - "node": ">= 0.8", - "npm": "1.2.8000 || >= 1.4.16" + "node": ">=20 <=24" } }, - "node_modules/body-parser/node_modules/http-errors": { + "node_modules/content-disposition": { "version": "2.0.1", - "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.1.tgz", - "integrity": "sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ==", - "dependencies": { - "depd": "~2.0.0", - "inherits": "~2.0.4", - "setprototypeof": "~1.2.0", - "statuses": "~2.0.2", - "toidentifier": "~1.0.1" - }, + "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-2.0.1.tgz", + "integrity": "sha512-e+H0ZXHSWYrENhQzw1LPuP4oF5MzVKmDU6d3hxlvaPEYLLg62MxtQNPRx4SYSuYJSBUgnQIG4HIN2tEtNv7Dog==", + "license": "MIT", "engines": { - "node": ">= 0.8" + "node": ">=18" }, "funding": { "type": "opencollective", "url": "https://opencollective.com/express" } }, - "node_modules/body-parser/node_modules/statuses": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.2.tgz", - "integrity": "sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==", + "node_modules/content-type": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/content-type/-/content-type-2.1.0.tgz", + "integrity": "sha512-mj7UPXE0jaqaOsukNZRUEfEi2AcL7C/vwmwcHV0O97eO1E1pxBZuyjlZrx5seTaNBg1U6+o35wpa35Qfcc+7ag==", + "license": "MIT", "engines": { - "node": ">= 0.8" + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" } }, - "node_modules/brace-expansion": { - "version": "1.1.21", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.21.tgz", - "integrity": "sha512-9zeA+KLZNNzglF2TPKRQEDyx6Yby7daAkuy8MiPzpXPsYDWi/DRM8jmwUDxokQjYqBpv5DgPiwD4h4ZZSy1Ujw==", + "node_modules/cookie": { + "version": "0.7.2", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.2.tgz", + "integrity": "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==", "license": "MIT", - "dependencies": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" + "engines": { + "node": ">= 0.6" } }, - "node_modules/braces": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", - "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", - "dependencies": { - "fill-range": "^7.1.1" - }, + "node_modules/cookie-signature": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.2.2.tgz", + "integrity": "sha512-D76uU73ulSXrD1UXF4KE2TMxVVwhsnCgfAyTg9k8P6KGZjlXKrOLe4dJQKI3Bxi5wjesZoFXJWElNWBjPZMbhg==", + "license": "MIT", "engines": { - "node": ">=8" + "node": ">=6.6.0" } }, - "node_modules/builtins": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/builtins/-/builtins-5.1.0.tgz", - "integrity": "sha512-SW9lzGTLvWTP1AY8xeAMZimqDrIaSdLQUcVr9DMef51niJ022Ri87SwRRKYm4A6iHfkPaiVUu/Duw2Wc4J7kKg==", + "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": { - "semver": "^7.0.0" - } - }, - "node_modules/builtins/node_modules/semver": { - "version": "7.7.2", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.2.tgz", - "integrity": "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==", - "dev": true, - "bin": { - "semver": "bin/semver.js" + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" }, "engines": { - "node": ">=10" + "node": ">= 8" } }, - "node_modules/bytes": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", - "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", + "node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, "engines": { - "node": ">= 0.8" + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } } }, - "node_modules/call-bind": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.8.tgz", - "integrity": "sha512-oKlSFMcMwpUg2ednkhQ454wfWiU/ul3CkJe/PEHcTKuiX6RpbehUiFMXu13HalGZxfUwCQzZG747YXBn1im9ww==", + "node_modules/deep-is": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", + "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/define-data-property": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz", + "integrity": "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==", + "license": "MIT", "dependencies": { - "call-bind-apply-helpers": "^1.0.0", "es-define-property": "^1.0.0", - "get-intrinsic": "^1.2.4", - "set-function-length": "^1.2.2" + "es-errors": "^1.3.0", + "gopd": "^1.0.1" }, "engines": { "node": ">= 0.4" @@ -652,279 +667,447 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/call-bind-apply-helpers": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", - "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", + "node_modules/depd": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", + "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/dunder-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", + "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", + "license": "MIT", "dependencies": { + "call-bind-apply-helpers": "^1.0.1", "es-errors": "^1.3.0", - "function-bind": "^1.1.2" + "gopd": "^1.2.0" }, "engines": { "node": ">= 0.4" } }, - "node_modules/call-bound": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz", - "integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==", - "dependencies": { - "call-bind-apply-helpers": "^1.0.2", - "get-intrinsic": "^1.3.0" - }, + "node_modules/ee-first": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", + "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==", + "license": "MIT" + }, + "node_modules/encodeurl": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz", + "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/es-define-property": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", + "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", + "license": "MIT", "engines": { "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/callsites": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", - "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", - "dev": true, + "node_modules/es-errors": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "license": "MIT", "engines": { - "node": ">=6" + "node": ">= 0.4" } }, - "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, + "node_modules/es-object-atoms": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.2.tgz", + "integrity": "sha512-HWcBoN6NileqtSydK2FqHbS/LoDd2pqrnQHLyJzBj4kOp/ky2MWMN694xOfkK8/SnUsW2DH7EfyVlydKCsm1Zw==", + "license": "MIT", "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" + "es-errors": "^1.3.0" }, "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" + "node": ">= 0.4" } }, - "node_modules/chalk/node_modules/has-flag": { + "node_modules/escape-html": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", + "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==", + "license": "MIT" + }, + "node_modules/escape-string-regexp": { "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", "dev": true, + "license": "MIT", "engines": { - "node": ">=8" + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/chalk/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==", + "node_modules/eslint": { + "version": "10.13.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-10.13.0.tgz", + "integrity": "sha512-UohDjD4zSzEwhf58M3O7vc/ycc8O1I7dNlhtD0WxE8Px8EHNJKtUwzBqrzeGhisRuv2ag4sAYswszCJRgKXYUg==", "dev": true, + "license": "MIT", + "workspaces": [ + "packages/*" + ], "dependencies": { - "has-flag": "^4.0.0" + "@eslint-community/eslint-utils": "^4.8.0", + "@eslint-community/regexpp": "^4.12.2", + "@eslint/config-array": "^0.23.5", + "@eslint/config-helpers": "^0.7.0", + "@eslint/core": "^1.2.1", + "@eslint/plugin-kit": "^0.7.3", + "@humanfs/node": "^0.16.6", + "@humanwhocodes/module-importer": "^1.0.1", + "@humanwhocodes/retry": "^0.4.2", + "@types/estree": "^1.0.6", + "ajv": "^6.14.0", + "cross-spawn": "^7.0.6", + "debug": "^4.3.2", + "escape-string-regexp": "^4.0.0", + "eslint-scope": "^9.1.2", + "eslint-visitor-keys": "^5.0.1", + "espree": "^11.2.0", + "esquery": "^1.7.0", + "esutils": "^2.0.2", + "fast-deep-equal": "^3.1.3", + "file-entry-cache": "11.1.5 || >11.1.6 <12", + "find-up": "^5.0.0", + "glob-parent": "^6.0.2", + "ignore": "^5.2.0", + "imurmurhash": "^0.1.4", + "is-glob": "^4.0.0", + "json-stable-stringify-without-jsonify": "^1.0.1", + "minimatch": "^10.2.5", + "natural-compare": "^1.4.0", + "optionator": "^0.9.3" + }, + "bin": { + "eslint": "bin/eslint.js" }, "engines": { - "node": ">=8" + "node": "^20.19.0 || ^22.13.0 || >=24" + }, + "funding": { + "url": "https://eslint.org/donate" + }, + "peerDependencies": { + "jiti": "*" + }, + "peerDependenciesMeta": { + "jiti": { + "optional": true + } } }, - "node_modules/chokidar": { - "version": "3.5.3", - "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.3.tgz", - "integrity": "sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw==", - "funding": [ - { - "type": "individual", - "url": "https://paulmillr.com/funding/" - } - ], + "node_modules/eslint-scope": { + "version": "9.1.2", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-9.1.2.tgz", + "integrity": "sha512-xS90H51cKw0jltxmvmHy2Iai1LIqrfbw57b79w/J7MfvDfkIkFZ+kj6zC3BjtUwh150HsSSdxXZcsuv72miDFQ==", + "dev": true, + "license": "BSD-2-Clause", "dependencies": { - "anymatch": "~3.1.2", - "braces": "~3.0.2", - "glob-parent": "~5.1.2", - "is-binary-path": "~2.1.0", - "is-glob": "~4.0.1", - "normalize-path": "~3.0.0", - "readdirp": "~3.6.0" + "@types/esrecurse": "^4.3.1", + "@types/estree": "^1.0.8", + "esrecurse": "^4.3.0", + "estraverse": "^5.2.0" }, "engines": { - "node": ">= 8.10.0" + "node": "^20.19.0 || ^22.13.0 || >=24" }, - "optionalDependencies": { - "fsevents": "~2.3.2" + "funding": { + "url": "https://opencollective.com/eslint" } }, - "node_modules/cloudevents": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/cloudevents/-/cloudevents-6.0.1.tgz", - "integrity": "sha512-6Y2EOQKsEEciSeIPL8ENob2LcFSt/r0iftTefl3IanSVr2LyvXfbEdD5+pvufVDFdf6OVijvtl7UFx/0voMOsw==", - "dependencies": { - "ajv": "^8.6.3", - "ajv-formats": "^2.1.1", - "util": "^0.12.4", - "uuid": "^8.3.2" + "node_modules/eslint-visitor-keys": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-5.0.1.tgz", + "integrity": "sha512-tD40eHxA35h0PEIZNeIjkHoDR4YjjJp34biM0mDvplBe//mB+IHCqHDGV7pxF+7MklTvighcCPPZC7ynWyjdTA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=24" + }, + "funding": { + "url": "https://opencollective.com/eslint" } }, - "node_modules/cloudevents/node_modules/ajv": { - "version": "8.18.0", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.18.0.tgz", - "integrity": "sha512-PlXPeEWMXMZ7sPYOHqmDyCJzcfNrUr3fGNKtezX14ykXOEIvyK81d+qydx89KY5O71FKMPaQ2vBfBFI5NHR63A==", + "node_modules/eslint/node_modules/ajv": { + "version": "6.15.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.15.0.tgz", + "integrity": "sha512-fgFx7Hfoq60ytK2c7DhnF8jIvzYgOMxfugjLOSMHjLIPgenqa7S7oaagATUq99mV6IYvN2tRmC0wnTYX6iPbMw==", + "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" + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" }, "funding": { "type": "github", "url": "https://github.com/sponsors/epoberezkin" } }, - "node_modules/cloudevents/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==" + "node_modules/eslint/node_modules/json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true, + "license": "MIT" }, - "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==", + "node_modules/espree": { + "version": "11.2.0", + "resolved": "https://registry.npmjs.org/espree/-/espree-11.2.0.tgz", + "integrity": "sha512-7p3DrVEIopW1B1avAGLuCSh1jubc01H2JHc8B4qqGblmg5gI9yumBgACjWo4JlIc04ufug4xJ3SQI8HkS/Rgzw==", "dev": true, + "license": "BSD-2-Clause", "dependencies": { - "color-name": "~1.1.4" + "acorn": "^8.16.0", + "acorn-jsx": "^5.3.2", + "eslint-visitor-keys": "^5.0.1" }, "engines": { - "node": ">=7.0.0" + "node": "^20.19.0 || ^22.13.0 || >=24" + }, + "funding": { + "url": "https://opencollective.com/eslint" } }, - "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 - }, - "node_modules/concat-map": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", - "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=" + "node_modules/esquery": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.7.0.tgz", + "integrity": "sha512-Ap6G0WQwcU/LHsvLwON1fAQX9Zp0A2Y6Y/cJBl9r/JbW90Zyg4/zbG6zzKa2OTALELarYHmKu0GhpM5EO+7T0g==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "estraverse": "^5.1.0" + }, + "engines": { + "node": ">=0.10" + } }, - "node_modules/content-disposition": { - "version": "0.5.4", - "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz", - "integrity": "sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==", + "node_modules/esrecurse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", + "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", + "dev": true, + "license": "BSD-2-Clause", "dependencies": { - "safe-buffer": "5.2.1" + "estraverse": "^5.2.0" }, "engines": { - "node": ">= 0.6" + "node": ">=4.0" } }, - "node_modules/content-type": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz", - "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==", + "node_modules/estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "dev": true, + "license": "BSD-2-Clause", "engines": { - "node": ">= 0.6" + "node": ">=4.0" } }, - "node_modules/cookie": { - "version": "0.7.1", - "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.1.tgz", - "integrity": "sha512-6DnInpx7SJ2AK3+CTUE/ZM0vWTUboZCegxhC2xiIydHR9jNuTAASBrfEpHhiGOZw/nX51bHt6YQl8jsGo4y/0w==", + "node_modules/esutils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", + "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/etag": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", + "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==", + "license": "MIT", "engines": { "node": ">= 0.6" } }, - "node_modules/cookie-signature": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", - "integrity": "sha1-4wOogrNCzD7oylE6eZmXNNqzriw=" + "node_modules/express": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/express/-/express-5.3.0.tgz", + "integrity": "sha512-qCqy1xSoaugxOD4IVHb1ZWdkvCF2W3aE2okKouWj8VwJ+ztskSiXoBnCSJqfKT+cVPJhuJqe9YLbY425+Zh00g==", + "license": "MIT", + "dependencies": { + "accepts": "^2.0.0", + "body-parser": "^2.3.0", + "content-disposition": "^2.0.1", + "content-type": "^2.0.0", + "cookie": "^0.7.1", + "cookie-signature": "^1.2.1", + "debug": "^4.4.0", + "depd": "^2.0.0", + "encodeurl": "^2.0.0", + "escape-html": "^1.0.3", + "etag": "^1.8.1", + "finalhandler": "^2.1.0", + "fresh": "^2.0.0", + "http-errors": "^2.0.0", + "merge-descriptors": "^2.0.0", + "mime-types": "^3.0.0", + "on-finished": "^2.4.1", + "once": "^1.4.0", + "parseurl": "^1.3.3", + "proxy-addr": "^2.0.8", + "qs": "^6.16.0", + "range-parser": "^1.2.1", + "router": "^2.2.0", + "send": "^1.1.0", + "serve-static": "^2.2.0", + "statuses": "^2.0.1", + "type-is": "^2.1.0", + "vary": "^1.1.2" + }, + "engines": { + "node": ">= 18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } }, - "node_modules/cross-spawn": { - "version": "7.0.6", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", - "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", + "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==", + "license": "MIT" + }, + "node_modules/fast-json-stable-stringify": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", + "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", + "dev": true, + "license": "MIT" + }, + "node_modules/fast-levenshtein": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", + "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", "dev": true, + "license": "MIT" + }, + "node_modules/fast-uri": { + "version": "3.1.9", + "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.9.tgz", + "integrity": "sha512-hS/0Wt3Ya043oKXs0sIxrMpKQITXhMLIrVAKKXi4gozGC9v7YRWVnXIZzjNPTO02M9BdxLCWqK1yRXgNziVyJQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], + "license": "BSD-3-Clause" + }, + "node_modules/file-entry-cache": { + "version": "11.1.5", + "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-11.1.5.tgz", + "integrity": "sha512-+PFTHITI08JIGhnNpGNI8T8inUpgZfk3GNEqfT9R2zZV2iFXg3CvqzSl/uEhs7TSGujYRELEANyDvS8Fj7+S7Q==", + "dev": true, + "license": "MIT", "dependencies": { - "path-key": "^3.1.0", - "shebang-command": "^2.0.0", - "which": "^2.0.1" - }, - "engines": { - "node": ">= 8" + "flat-cache": "^6.1.23" } }, - "node_modules/data-view-buffer": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/data-view-buffer/-/data-view-buffer-1.0.2.tgz", - "integrity": "sha512-EmKO5V3OLXh1rtK2wgXRansaK1/mtVdTUEiEI0W8RkvgT05kfxaH29PliLnpLP73yYO6142Q72QNa8Wx/A5CqQ==", + "node_modules/fill-range": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", + "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", "dev": true, + "license": "MIT", "dependencies": { - "call-bound": "^1.0.3", - "es-errors": "^1.3.0", - "is-data-view": "^1.0.2" + "to-regex-range": "^5.0.1" }, "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "node": ">=8" } }, - "node_modules/data-view-byte-length": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/data-view-byte-length/-/data-view-byte-length-1.0.2.tgz", - "integrity": "sha512-tuhGbE6CfTM9+5ANGf+oQb72Ky/0+s3xKUpHvShfiz2RxMFgFPjsXuRLBVMtvMs15awe45SRb83D6wH4ew6wlQ==", - "dev": true, + "node_modules/finalhandler": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-2.1.1.tgz", + "integrity": "sha512-S8KoZgRZN+a5rNwqTxlZZePjT/4cnm0ROV70LedRHZ0p8u9fRID0hJUZQpkKLzro8LfmC8sx23bY6tVNxv8pQA==", + "license": "MIT", "dependencies": { - "call-bound": "^1.0.3", - "es-errors": "^1.3.0", - "is-data-view": "^1.0.2" + "debug": "^4.4.0", + "encodeurl": "^2.0.0", + "escape-html": "^1.0.3", + "on-finished": "^2.4.1", + "parseurl": "^1.3.3", + "statuses": "^2.0.1" }, "engines": { - "node": ">= 0.4" + "node": ">= 18.0.0" }, "funding": { - "url": "https://github.com/sponsors/inspect-js" + "type": "opencollective", + "url": "https://opencollective.com/express" } }, - "node_modules/data-view-byte-offset": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/data-view-byte-offset/-/data-view-byte-offset-1.0.1.tgz", - "integrity": "sha512-BS8PfmtDGnrgYdOonGZQdLZslWIeCGFP9tpan0hi1Co2Zr2NKADsvGYA8XxuG/4UWgJ6Cjtv+YJnB6MM69QGlQ==", + "node_modules/find-up": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", + "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", "dev": true, + "license": "MIT", "dependencies": { - "call-bound": "^1.0.2", - "es-errors": "^1.3.0", - "is-data-view": "^1.0.1" + "locate-path": "^6.0.0", + "path-exists": "^4.0.0" }, "engines": { - "node": ">= 0.4" + "node": ">=10" }, "funding": { - "url": "https://github.com/sponsors/ljharb" + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "node_modules/flat-cache": { + "version": "6.1.23", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-6.1.23.tgz", + "integrity": "sha512-f++BY9pTk+983xK1FLzlLpmM0i0z+jHmx3QESGkURMXujQZz1k5wzwX6hjnQ8goaD0B+sYnDK1yZ6MTyZfUaqA==", + "dev": true, + "license": "MIT", "dependencies": { - "ms": "2.0.0" + "cacheable": "^2.5.0", + "flatted": "^3.4.2", + "hookified": "^1.15.0" } }, - "node_modules/deep-is": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", - "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", - "dev": true + "node_modules/flatted": { + "version": "3.4.4", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.4.4.tgz", + "integrity": "sha512-5+ybhBZANEJxaH3X5evAFatUxLfEHSr7n6kYJ+1Qd0mUqr4eu9gIf6GDbWHf8RJijHrjjO8G+la14SlL2SeS1Q==", + "dev": true, + "license": "ISC" }, - "node_modules/define-data-property": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz", - "integrity": "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==", + "node_modules/for-each": { + "version": "0.3.5", + "resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.5.tgz", + "integrity": "sha512-dKx12eRCVIzqCxFGplyFKJMPvLEWgmNtUrpTiJIR5u97zEhRG8ySrtboPHZXx7daLxQVrl643cTzbab2tkQjxg==", "license": "MIT", "dependencies": { - "es-define-property": "^1.0.0", - "es-errors": "^1.3.0", - "gopd": "^1.0.1" + "is-callable": "^1.2.7" }, "engines": { "node": ">= 0.4" @@ -933,149 +1116,73 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/define-properties": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.2.1.tgz", - "integrity": "sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg==", - "dev": true, - "dependencies": { - "define-data-property": "^1.0.1", - "has-property-descriptors": "^1.0.0", - "object-keys": "^1.1.1" - }, + "node_modules/forwarded": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", + "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==", + "license": "MIT", "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "node": ">= 0.6" } }, - "node_modules/depd": { + "node_modules/fresh": { "version": "2.0.0", - "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", - "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", + "resolved": "https://registry.npmjs.org/fresh/-/fresh-2.0.0.tgz", + "integrity": "sha512-Rx/WycZ60HOaqLKAi6cHRKKI7zxWbJ31MhntmtwMoaTeF7XFH9hhBp8vITaMidfljRQ6eYWCKkaTK+ykVJHP2A==", "license": "MIT", "engines": { "node": ">= 0.8" } }, - "node_modules/destroy": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz", - "integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==", + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], "engines": { - "node": ">= 0.8", - "npm": "1.2.8000 || >= 1.4.16" + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" } }, - "node_modules/doctrine": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", - "integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==", - "dev": true, - "dependencies": { - "esutils": "^2.0.2" - }, + "node_modules/function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/generator-function": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/generator-function/-/generator-function-2.0.1.tgz", + "integrity": "sha512-SFdFmIJi+ybC0vjlHN0ZGVGHc3lgE0DxPAT0djjVg+kjOnSqclqmj0KQ7ykTOLP6YxoqOvuAODGdcHJn+43q3g==", + "license": "MIT", "engines": { - "node": ">=6.0.0" + "node": ">= 0.4" } }, - "node_modules/dunder-proto": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", - "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", - "dependencies": { - "call-bind-apply-helpers": "^1.0.1", - "es-errors": "^1.3.0", - "gopd": "^1.2.0" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/ee-first": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", - "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==" - }, - "node_modules/encodeurl": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", - "integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/error-ex": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", - "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==", - "dev": true, - "dependencies": { - "is-arrayish": "^0.2.1" - } - }, - "node_modules/es-abstract": { - "version": "1.24.0", - "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.24.0.tgz", - "integrity": "sha512-WSzPgsdLtTcQwm4CROfS5ju2Wa1QQcVeT37jFjYzdFz1r9ahadC8B8/a4qxJxM+09F18iumCdRmlr96ZYkQvEg==", - "dev": true, + "node_modules/get-intrinsic": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", + "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", + "license": "MIT", "dependencies": { - "array-buffer-byte-length": "^1.0.2", - "arraybuffer.prototype.slice": "^1.0.4", - "available-typed-arrays": "^1.0.7", - "call-bind": "^1.0.8", - "call-bound": "^1.0.4", - "data-view-buffer": "^1.0.2", - "data-view-byte-length": "^1.0.2", - "data-view-byte-offset": "^1.0.1", + "call-bind-apply-helpers": "^1.0.2", "es-define-property": "^1.0.1", "es-errors": "^1.3.0", "es-object-atoms": "^1.1.1", - "es-set-tostringtag": "^2.1.0", - "es-to-primitive": "^1.3.0", - "function.prototype.name": "^1.1.8", - "get-intrinsic": "^1.3.0", + "function-bind": "^1.1.2", "get-proto": "^1.0.1", - "get-symbol-description": "^1.1.0", - "globalthis": "^1.0.4", "gopd": "^1.2.0", - "has-property-descriptors": "^1.0.2", - "has-proto": "^1.2.0", "has-symbols": "^1.1.0", "hasown": "^2.0.2", - "internal-slot": "^1.1.0", - "is-array-buffer": "^3.0.5", - "is-callable": "^1.2.7", - "is-data-view": "^1.0.2", - "is-negative-zero": "^2.0.3", - "is-regex": "^1.2.1", - "is-set": "^2.0.3", - "is-shared-array-buffer": "^1.0.4", - "is-string": "^1.1.1", - "is-typed-array": "^1.1.15", - "is-weakref": "^1.1.1", - "math-intrinsics": "^1.1.0", - "object-inspect": "^1.13.4", - "object-keys": "^1.1.1", - "object.assign": "^4.1.7", - "own-keys": "^1.0.1", - "regexp.prototype.flags": "^1.5.4", - "safe-array-concat": "^1.1.3", - "safe-push-apply": "^1.0.0", - "safe-regex-test": "^1.1.0", - "set-proto": "^1.0.0", - "stop-iteration-iterator": "^1.1.0", - "string.prototype.trim": "^1.2.10", - "string.prototype.trimend": "^1.0.9", - "string.prototype.trimstart": "^1.0.8", - "typed-array-buffer": "^1.0.3", - "typed-array-byte-length": "^1.0.3", - "typed-array-byte-offset": "^1.0.4", - "typed-array-length": "^1.0.7", - "unbox-primitive": "^1.1.0", - "which-typed-array": "^1.1.19" + "math-intrinsics": "^1.1.0" }, "engines": { "node": ">= 0.4" @@ -1084,6914 +1191,1273 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/es-define-property": { + "node_modules/get-proto": { "version": "1.0.1", - "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", - "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/es-errors": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", - "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", + "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", "license": "MIT", - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/es-iterator-helpers": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/es-iterator-helpers/-/es-iterator-helpers-1.2.1.tgz", - "integrity": "sha512-uDn+FE1yrDzyC0pCo961B2IHbdM8y/ACZsKD4dG6WqrjV53BADjwa7D+1aom2rsNVfLyDgU/eigvlJGJ08OQ4w==", - "dev": true, - "dependencies": { - "call-bind": "^1.0.8", - "call-bound": "^1.0.3", - "define-properties": "^1.2.1", - "es-abstract": "^1.23.6", - "es-errors": "^1.3.0", - "es-set-tostringtag": "^2.0.3", - "function-bind": "^1.1.2", - "get-intrinsic": "^1.2.6", - "globalthis": "^1.0.4", - "gopd": "^1.2.0", - "has-property-descriptors": "^1.0.2", - "has-proto": "^1.2.0", - "has-symbols": "^1.1.0", - "internal-slot": "^1.1.0", - "iterator.prototype": "^1.1.4", - "safe-array-concat": "^1.1.3" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/es-object-atoms": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz", - "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==", - "dependencies": { - "es-errors": "^1.3.0" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/es-set-tostringtag": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz", - "integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==", - "dev": true, - "dependencies": { - "es-errors": "^1.3.0", - "get-intrinsic": "^1.2.6", - "has-tostringtag": "^1.0.2", - "hasown": "^2.0.2" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/es-shim-unscopables": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/es-shim-unscopables/-/es-shim-unscopables-1.1.0.tgz", - "integrity": "sha512-d9T8ucsEhh8Bi1woXCf+TIKDIROLG5WCkxg8geBCbvk22kzwC5G2OnXVMO6FUsvQlgUUXQ2itephWDLqDzbeCw==", - "dev": true, - "dependencies": { - "hasown": "^2.0.2" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/es-to-primitive": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.3.0.tgz", - "integrity": "sha512-w+5mJ3GuFL+NjVtJlvydShqE1eN3h3PbI7/5LAsYJP/2qtuMXjfL2LpHSRqo4b4eSF5K/DH1JXKUAHSB2UW50g==", - "dev": true, "dependencies": { - "is-callable": "^1.2.7", - "is-date-object": "^1.0.5", - "is-symbol": "^1.0.4" + "dunder-proto": "^1.0.1", + "es-object-atoms": "^1.0.0" }, "engines": { "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/escape-html": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", - "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==" - }, - "node_modules/escape-string-regexp": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", - "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", - "dev": true, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/eslint": { - "version": "8.57.1", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.57.1.tgz", - "integrity": "sha512-ypowyDxpVSYpkXr9WPv2PAZCtNip1Mv5KTW0SCurXv/9iOpcrH9PaqUElksqEB6pChqHGDRCFTyrZlGhnLNGiA==", - "deprecated": "This version is no longer supported. Please see https://eslint.org/version-support for other options.", - "dev": true, - "dependencies": { - "@eslint-community/eslint-utils": "^4.2.0", - "@eslint-community/regexpp": "^4.6.1", - "@eslint/eslintrc": "^2.1.4", - "@eslint/js": "8.57.1", - "@humanwhocodes/config-array": "^0.13.0", - "@humanwhocodes/module-importer": "^1.0.1", - "@nodelib/fs.walk": "^1.2.8", - "@ungap/structured-clone": "^1.2.0", - "ajv": "^6.12.4", - "chalk": "^4.0.0", - "cross-spawn": "^7.0.2", - "debug": "^4.3.2", - "doctrine": "^3.0.0", - "escape-string-regexp": "^4.0.0", - "eslint-scope": "^7.2.2", - "eslint-visitor-keys": "^3.4.3", - "espree": "^9.6.1", - "esquery": "^1.4.2", - "esutils": "^2.0.2", - "fast-deep-equal": "^3.1.3", - "file-entry-cache": "^6.0.1", - "find-up": "^5.0.0", - "glob-parent": "^6.0.2", - "globals": "^13.19.0", - "graphemer": "^1.4.0", - "ignore": "^5.2.0", - "imurmurhash": "^0.1.4", - "is-glob": "^4.0.0", - "is-path-inside": "^3.0.3", - "js-yaml": "^4.1.0", - "json-stable-stringify-without-jsonify": "^1.0.1", - "levn": "^0.4.1", - "lodash.merge": "^4.6.2", - "minimatch": "^3.1.2", - "natural-compare": "^1.4.0", - "optionator": "^0.9.3", - "strip-ansi": "^6.0.1", - "text-table": "^0.2.0" - }, - "bin": { - "eslint": "bin/eslint.js" - }, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" - } - }, - "node_modules/eslint-config-standard": { - "version": "17.1.0", - "resolved": "https://registry.npmjs.org/eslint-config-standard/-/eslint-config-standard-17.1.0.tgz", - "integrity": "sha512-IwHwmaBNtDK4zDHQukFDW5u/aTb8+meQWZvNFWkiGmbWjD6bqyuSSBxxXKkCftCUzc1zwCH2m/baCNDLGmuO5Q==", - "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" - } - ], - "engines": { - "node": ">=12.0.0" - }, - "peerDependencies": { - "eslint": "^8.0.1", - "eslint-plugin-import": "^2.25.2", - "eslint-plugin-n": "^15.0.0 || ^16.0.0 ", - "eslint-plugin-promise": "^6.0.0" - } - }, - "node_modules/eslint-config-standard-jsx": { - "version": "11.0.0", - "resolved": "https://registry.npmjs.org/eslint-config-standard-jsx/-/eslint-config-standard-jsx-11.0.0.tgz", - "integrity": "sha512-+1EV/R0JxEK1L0NGolAr8Iktm3Rgotx3BKwgaX+eAuSX8D952LULKtjgZD3F+e6SvibONnhLwoTi9DPxN5LvvQ==", - "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" - } - ], - "peerDependencies": { - "eslint": "^8.8.0", - "eslint-plugin-react": "^7.28.0" - } - }, - "node_modules/eslint-import-resolver-node": { - "version": "0.3.9", - "resolved": "https://registry.npmjs.org/eslint-import-resolver-node/-/eslint-import-resolver-node-0.3.9.tgz", - "integrity": "sha512-WFj2isz22JahUv+B788TlO3N6zL3nNJGU8CcZbPZvVEkBPaJdCV4vy5wyghty5ROFbCRnm132v8BScu5/1BQ8g==", - "dev": true, - "dependencies": { - "debug": "^3.2.7", - "is-core-module": "^2.13.0", - "resolve": "^1.22.4" - } - }, - "node_modules/eslint-import-resolver-node/node_modules/debug": { - "version": "3.2.7", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", - "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", - "dev": true, - "dependencies": { - "ms": "^2.1.1" - } - }, - "node_modules/eslint-import-resolver-node/node_modules/ms": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", - "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", - "dev": true - }, - "node_modules/eslint-module-utils": { - "version": "2.12.1", - "resolved": "https://registry.npmjs.org/eslint-module-utils/-/eslint-module-utils-2.12.1.tgz", - "integrity": "sha512-L8jSWTze7K2mTg0vos/RuLRS5soomksDPoJLXIslC7c8Wmut3bx7CPpJijDcBZtxQ5lrbUdM+s0OlNbz0DCDNw==", - "dev": true, - "dependencies": { - "debug": "^3.2.7" - }, - "engines": { - "node": ">=4" - }, - "peerDependenciesMeta": { - "eslint": { - "optional": true - } - } - }, - "node_modules/eslint-module-utils/node_modules/debug": { - "version": "3.2.7", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", - "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", - "dev": true, - "dependencies": { - "ms": "^2.1.1" - } - }, - "node_modules/eslint-module-utils/node_modules/ms": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", - "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", - "dev": true - }, - "node_modules/eslint-plugin-es": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/eslint-plugin-es/-/eslint-plugin-es-4.1.0.tgz", - "integrity": "sha512-GILhQTnjYE2WorX5Jyi5i4dz5ALWxBIdQECVQavL6s7cI76IZTDWleTHkxz/QT3kvcs2QlGHvKLYsSlPOlPXnQ==", - "dev": true, - "dependencies": { - "eslint-utils": "^2.0.0", - "regexpp": "^3.0.0" - }, - "engines": { - "node": ">=8.10.0" - }, - "funding": { - "url": "https://github.com/sponsors/mysticatea" - }, - "peerDependencies": { - "eslint": ">=4.19.1" - } - }, - "node_modules/eslint-plugin-es/node_modules/eslint-utils": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-2.1.0.tgz", - "integrity": "sha512-w94dQYoauyvlDc43XnGB8lU3Zt713vNChgt4EWwhXAP2XkBvndfxF0AgIqKOOasjPIPzj9JqgwkwbCYD0/V3Zg==", - "dev": true, - "dependencies": { - "eslint-visitor-keys": "^1.1.0" - }, - "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/mysticatea" - } - }, - "node_modules/eslint-plugin-es/node_modules/eslint-visitor-keys": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-1.3.0.tgz", - "integrity": "sha512-6J72N8UNa462wa/KFODt/PJ3IU60SDpC3QXC1Hjc1BXXpfL2C9R5+AU7jhe0F6GREqVMh4Juu+NY7xn+6dipUQ==", - "dev": true, - "engines": { - "node": ">=4" - } - }, - "node_modules/eslint-plugin-import": { - "version": "2.32.0", - "resolved": "https://registry.npmjs.org/eslint-plugin-import/-/eslint-plugin-import-2.32.0.tgz", - "integrity": "sha512-whOE1HFo/qJDyX4SnXzP4N6zOWn79WhnCUY/iDR0mPfQZO8wcYE4JClzI2oZrhBnnMUCBCHZhO6VQyoBU95mZA==", - "dev": true, - "dependencies": { - "@rtsao/scc": "^1.1.0", - "array-includes": "^3.1.9", - "array.prototype.findlastindex": "^1.2.6", - "array.prototype.flat": "^1.3.3", - "array.prototype.flatmap": "^1.3.3", - "debug": "^3.2.7", - "doctrine": "^2.1.0", - "eslint-import-resolver-node": "^0.3.9", - "eslint-module-utils": "^2.12.1", - "hasown": "^2.0.2", - "is-core-module": "^2.16.1", - "is-glob": "^4.0.3", - "minimatch": "^3.1.2", - "object.fromentries": "^2.0.8", - "object.groupby": "^1.0.3", - "object.values": "^1.2.1", - "semver": "^6.3.1", - "string.prototype.trimend": "^1.0.9", - "tsconfig-paths": "^3.15.0" - }, - "engines": { - "node": ">=4" - }, - "peerDependencies": { - "eslint": "^2 || ^3 || ^4 || ^5 || ^6 || ^7.2.0 || ^8 || ^9" - } - }, - "node_modules/eslint-plugin-import/node_modules/debug": { - "version": "3.2.7", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", - "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", - "dev": true, - "dependencies": { - "ms": "^2.1.1" } }, - "node_modules/eslint-plugin-import/node_modules/doctrine": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-2.1.0.tgz", - "integrity": "sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==", + "node_modules/glob-parent": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", + "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", "dev": true, - "dependencies": { - "esutils": "^2.0.2" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/eslint-plugin-import/node_modules/ms": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", - "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", - "dev": true - }, - "node_modules/eslint-plugin-import/node_modules/semver": { - "version": "6.3.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", - "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", - "dev": true, - "bin": { - "semver": "bin/semver.js" - } - }, - "node_modules/eslint-plugin-n": { - "version": "15.7.0", - "resolved": "https://registry.npmjs.org/eslint-plugin-n/-/eslint-plugin-n-15.7.0.tgz", - "integrity": "sha512-jDex9s7D/Qial8AGVIHq4W7NswpUD5DPDL2RH8Lzd9EloWUuvUkHfv4FRLMipH5q2UtyurorBkPeNi1wVWNh3Q==", - "dev": true, - "dependencies": { - "builtins": "^5.0.1", - "eslint-plugin-es": "^4.1.0", - "eslint-utils": "^3.0.0", - "ignore": "^5.1.1", - "is-core-module": "^2.11.0", - "minimatch": "^3.1.2", - "resolve": "^1.22.1", - "semver": "^7.3.8" - }, - "engines": { - "node": ">=12.22.0" - }, - "funding": { - "url": "https://github.com/sponsors/mysticatea" - }, - "peerDependencies": { - "eslint": ">=7.0.0" - } - }, - "node_modules/eslint-plugin-n/node_modules/semver": { - "version": "7.7.2", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.2.tgz", - "integrity": "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==", - "dev": true, - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/eslint-plugin-promise": { - "version": "6.6.0", - "resolved": "https://registry.npmjs.org/eslint-plugin-promise/-/eslint-plugin-promise-6.6.0.tgz", - "integrity": "sha512-57Zzfw8G6+Gq7axm2Pdo3gW/Rx3h9Yywgn61uE/3elTCOePEHVrn2i5CdfBwA1BLK0Q0WqctICIUSqXZW/VprQ==", - "dev": true, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" - }, - "peerDependencies": { - "eslint": "^7.0.0 || ^8.0.0 || ^9.0.0" - } - }, - "node_modules/eslint-plugin-react": { - "version": "7.37.5", - "resolved": "https://registry.npmjs.org/eslint-plugin-react/-/eslint-plugin-react-7.37.5.tgz", - "integrity": "sha512-Qteup0SqU15kdocexFNAJMvCJEfa2xUKNV4CC1xsVMrIIqEy3SQ/rqyxCWNzfrd3/ldy6HMlD2e0JDVpDg2qIA==", - "dev": true, - "dependencies": { - "array-includes": "^3.1.8", - "array.prototype.findlast": "^1.2.5", - "array.prototype.flatmap": "^1.3.3", - "array.prototype.tosorted": "^1.1.4", - "doctrine": "^2.1.0", - "es-iterator-helpers": "^1.2.1", - "estraverse": "^5.3.0", - "hasown": "^2.0.2", - "jsx-ast-utils": "^2.4.1 || ^3.0.0", - "minimatch": "^3.1.2", - "object.entries": "^1.1.9", - "object.fromentries": "^2.0.8", - "object.values": "^1.2.1", - "prop-types": "^15.8.1", - "resolve": "^2.0.0-next.5", - "semver": "^6.3.1", - "string.prototype.matchall": "^4.0.12", - "string.prototype.repeat": "^1.0.0" - }, - "engines": { - "node": ">=4" - }, - "peerDependencies": { - "eslint": "^3 || ^4 || ^5 || ^6 || ^7 || ^8 || ^9.7" - } - }, - "node_modules/eslint-plugin-react/node_modules/doctrine": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-2.1.0.tgz", - "integrity": "sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==", - "dev": true, - "dependencies": { - "esutils": "^2.0.2" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/eslint-plugin-react/node_modules/resolve": { - "version": "2.0.0-next.5", - "resolved": "https://registry.npmjs.org/resolve/-/resolve-2.0.0-next.5.tgz", - "integrity": "sha512-U7WjGVG9sH8tvjW5SmGbQuui75FiyjAX72HX15DwBBwF9dNiQZRQAg9nnPhYy+TUnE0+VcrttuvNI8oSxZcocA==", - "dev": true, - "dependencies": { - "is-core-module": "^2.13.0", - "path-parse": "^1.0.7", - "supports-preserve-symlinks-flag": "^1.0.0" - }, - "bin": { - "resolve": "bin/resolve" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/eslint-plugin-react/node_modules/semver": { - "version": "6.3.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", - "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", - "dev": true, - "bin": { - "semver": "bin/semver.js" - } - }, - "node_modules/eslint-scope": { - "version": "7.2.2", - "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.2.2.tgz", - "integrity": "sha512-dOt21O7lTMhDM+X9mB4GX+DZrZtCUJPL/wlcTqxyrx5IvO0IYtILdtrQGQp+8n5S0gwSVmOf9NQrjMOgfQZlIg==", - "dev": true, - "dependencies": { - "esrecurse": "^4.3.0", - "estraverse": "^5.2.0" - }, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" - } - }, - "node_modules/eslint-utils": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-3.0.0.tgz", - "integrity": "sha512-uuQC43IGctw68pJA1RgbQS8/NP7rch6Cwd4j3ZBtgo4/8Flj4eGE7ZYSZRN3iq5pVUv6GPdW5Z1RFleo84uLDA==", - "dev": true, - "dependencies": { - "eslint-visitor-keys": "^2.0.0" - }, - "engines": { - "node": "^10.0.0 || ^12.0.0 || >= 14.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/mysticatea" - }, - "peerDependencies": { - "eslint": ">=5" - } - }, - "node_modules/eslint-utils/node_modules/eslint-visitor-keys": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-2.1.0.tgz", - "integrity": "sha512-0rSmRBzXgDzIsD6mGdJgevzgezI534Cer5L/vyMX0kHzT/jiB43jRhd9YUlMGYLQy2zprNmoT8qasCGtY+QaKw==", - "dev": true, - "engines": { - "node": ">=10" - } - }, - "node_modules/eslint-visitor-keys": { - "version": "3.4.3", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", - "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", - "dev": true, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" - } - }, - "node_modules/eslint/node_modules/debug": { - "version": "4.4.1", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.1.tgz", - "integrity": "sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==", - "dev": true, - "dependencies": { - "ms": "^2.1.3" - }, - "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } - } - }, - "node_modules/eslint/node_modules/glob-parent": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", - "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", - "dev": true, - "dependencies": { - "is-glob": "^4.0.3" - }, - "engines": { - "node": ">=10.13.0" - } - }, - "node_modules/eslint/node_modules/ms": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", - "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", - "dev": true - }, - "node_modules/espree": { - "version": "9.6.1", - "resolved": "https://registry.npmjs.org/espree/-/espree-9.6.1.tgz", - "integrity": "sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ==", - "dev": true, - "dependencies": { - "acorn": "^8.9.0", - "acorn-jsx": "^5.3.2", - "eslint-visitor-keys": "^3.4.1" - }, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" - } - }, - "node_modules/esquery": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.6.0.tgz", - "integrity": "sha512-ca9pw9fomFcKPvFLXhBKUK90ZvGibiGOvRJNbjljY7s7uq/5YO4BOzcYtJqExdx99rF6aAcnRxHmcUHcz6sQsg==", - "dev": true, - "dependencies": { - "estraverse": "^5.1.0" - }, - "engines": { - "node": ">=0.10" - } - }, - "node_modules/esrecurse": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", - "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", - "dev": true, - "dependencies": { - "estraverse": "^5.2.0" - }, - "engines": { - "node": ">=4.0" - } - }, - "node_modules/estraverse": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", - "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", - "dev": true, - "engines": { - "node": ">=4.0" - } - }, - "node_modules/esutils": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", - "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/etag": { - "version": "1.8.1", - "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", - "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==", - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/express": { - "version": "4.22.3", - "resolved": "https://registry.npmjs.org/express/-/express-4.22.3.tgz", - "integrity": "sha512-Bdcs4+3qlpVlx2NRn6fgX2Ue2/gGRaPeawebgclM0ERSCqDpA+owF1fdPwjJUTAJWMTuAaxjDf+hzb0/4eKvvw==", - "license": "MIT", - "dependencies": { - "accepts": "~1.3.8", - "array-flatten": "1.1.1", - "body-parser": "~1.20.5", - "content-disposition": "~0.5.4", - "content-type": "~1.0.4", - "cookie": "~0.7.1", - "cookie-signature": "~1.0.6", - "debug": "2.6.9", - "depd": "2.0.0", - "encodeurl": "~2.0.0", - "escape-html": "~1.0.3", - "etag": "~1.8.1", - "finalhandler": "~1.3.1", - "fresh": "~0.5.2", - "http-errors": "~2.0.0", - "merge-descriptors": "1.0.3", - "methods": "~1.1.2", - "on-finished": "~2.4.1", - "parseurl": "~1.3.3", - "path-to-regexp": "~0.1.13", - "proxy-addr": "~2.0.7", - "qs": "~6.16.0", - "range-parser": "~1.2.1", - "safe-buffer": "5.2.1", - "send": "~0.19.0", - "serve-static": "~1.16.2", - "setprototypeof": "1.2.0", - "statuses": "~2.0.1", - "type-is": "~1.6.18", - "utils-merge": "1.0.1", - "vary": "~1.1.2" - }, - "engines": { - "node": ">= 0.10.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/express" - } - }, - "node_modules/express/node_modules/encodeurl": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz", - "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==", - "license": "MIT", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/express/node_modules/qs": { - "version": "6.16.1", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.16.1.tgz", - "integrity": "sha512-5Px6J7rfHBAGUhNQ3es4YzcOdBBw0SDcVtThJml4RdnKw4tKs2epA0kPZ+K8X+WiHktT93RHVcQ/3ym5aTCgtg==", - "license": "BSD-3-Clause", - "dependencies": { - "es-define-property": "^1.0.1", - "side-channel": "^1.1.1" - }, - "engines": { - "node": ">=0.6" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "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==" - }, - "node_modules/fast-json-stable-stringify": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", - "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", - "dev": true - }, - "node_modules/fast-levenshtein": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", - "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", - "dev": true - }, - "node_modules/fast-uri": { - "version": "3.1.7", - "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.7.tgz", - "integrity": "sha512-dOvZVzjdZdz7phd9v6jCbwxrBW3fK6n8Rc0CtdmM4bumzMnxywBYhuph6J819RRw/ku+rLbelwfMunktuzVVHg==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/fastify" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/fastify" - } - ], - "license": "BSD-3-Clause" - }, - "node_modules/fastq": { - "version": "1.19.1", - "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.19.1.tgz", - "integrity": "sha512-GwLTyxkCXjXbxqIhTsMI2Nui8huMPtnxg7krajPJAjnEG/iiOS7i+zCtWGZR9G0NBKbXKh6X9m9UIsYX/N6vvQ==", - "dev": true, - "dependencies": { - "reusify": "^1.0.4" - } - }, - "node_modules/file-entry-cache": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz", - "integrity": "sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==", - "dev": true, - "dependencies": { - "flat-cache": "^3.0.4" - }, - "engines": { - "node": "^10.12.0 || >=12.0.0" - } - }, - "node_modules/fill-range": { - "version": "7.1.1", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", - "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", - "dependencies": { - "to-regex-range": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/finalhandler": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.3.1.tgz", - "integrity": "sha512-6BN9trH7bp3qvnrRyzsBz+g3lZxTNZTbVO2EV1CS0WIcDbawYVdYvGflME/9QP0h0pYlCDBCTjYa9nZzMDpyxQ==", - "dependencies": { - "debug": "2.6.9", - "encodeurl": "~2.0.0", - "escape-html": "~1.0.3", - "on-finished": "2.4.1", - "parseurl": "~1.3.3", - "statuses": "2.0.1", - "unpipe": "~1.0.0" - }, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/finalhandler/node_modules/encodeurl": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz", - "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/find-up": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", - "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", - "dev": true, - "dependencies": { - "locate-path": "^6.0.0", - "path-exists": "^4.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/flat-cache": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.2.0.tgz", - "integrity": "sha512-CYcENa+FtcUKLmhhqyctpclsq7QF38pKjZHsGNiSQF5r4FtoKDWabFDl3hzaEQMvT1LHEysw5twgLvpYYb4vbw==", - "dev": true, - "dependencies": { - "flatted": "^3.2.9", - "keyv": "^4.5.3", - "rimraf": "^3.0.2" - }, - "engines": { - "node": "^10.12.0 || >=12.0.0" - } - }, - "node_modules/flatted": { - "version": "3.4.2", - "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.4.2.tgz", - "integrity": "sha512-PjDse7RzhcPkIJwy5t7KPWQSZ9cAbzQXcafsetQoD7sOJRQlGikNbx7yZp2OotDnJyrDcbyRq3Ttb18iYOqkxA==", - "dev": true - }, - "node_modules/for-each": { - "version": "0.3.5", - "resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.5.tgz", - "integrity": "sha512-dKx12eRCVIzqCxFGplyFKJMPvLEWgmNtUrpTiJIR5u97zEhRG8ySrtboPHZXx7daLxQVrl643cTzbab2tkQjxg==", - "dependencies": { - "is-callable": "^1.2.7" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/forwarded": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", - "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/fresh": { - "version": "0.5.2", - "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", - "integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==", - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/fs.realpath": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", - "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", - "dev": true - }, - "node_modules/fsevents": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", - "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", - "hasInstallScript": true, - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": "^8.16.0 || ^10.6.0 || >=11.0.0" - } - }, - "node_modules/function-bind": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", - "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", - "license": "MIT", - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/function.prototype.name": { - "version": "1.1.8", - "resolved": "https://registry.npmjs.org/function.prototype.name/-/function.prototype.name-1.1.8.tgz", - "integrity": "sha512-e5iwyodOHhbMr/yNrc7fDYG4qlbIvI5gajyzPnb5TCwyhjApznQh1BMFou9b30SevY43gCJKXycoCBjMbsuW0Q==", - "dev": true, - "dependencies": { - "call-bind": "^1.0.8", - "call-bound": "^1.0.3", - "define-properties": "^1.2.1", - "functions-have-names": "^1.2.3", - "hasown": "^2.0.2", - "is-callable": "^1.2.7" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/functions-have-names": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/functions-have-names/-/functions-have-names-1.2.3.tgz", - "integrity": "sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==", - "dev": true, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/get-intrinsic": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", - "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", - "dependencies": { - "call-bind-apply-helpers": "^1.0.2", - "es-define-property": "^1.0.1", - "es-errors": "^1.3.0", - "es-object-atoms": "^1.1.1", - "function-bind": "^1.1.2", - "get-proto": "^1.0.1", - "gopd": "^1.2.0", - "has-symbols": "^1.1.0", - "hasown": "^2.0.2", - "math-intrinsics": "^1.1.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/get-proto": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", - "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", - "dependencies": { - "dunder-proto": "^1.0.1", - "es-object-atoms": "^1.0.0" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/get-stdin": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/get-stdin/-/get-stdin-8.0.0.tgz", - "integrity": "sha512-sY22aA6xchAzprjyqmSEQv4UbAAzRN0L2dQB0NlN5acTTK9Don6nhoc3eAbUnpZiCANAMfd/+40kVdKfFygohg==", - "dev": true, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/get-symbol-description": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/get-symbol-description/-/get-symbol-description-1.1.0.tgz", - "integrity": "sha512-w9UMqWwJxHNOvoNzSJ2oPF5wvYcvP7jUvYzhp67yEhTi17ZDBBC1z9pTdGuzjD+EFIqLSYRweZjqfiPzQ06Ebg==", - "dev": true, - "dependencies": { - "call-bound": "^1.0.3", - "es-errors": "^1.3.0", - "get-intrinsic": "^1.2.6" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/glob": { - "version": "7.2.3", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", - "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", - "deprecated": "Glob versions prior to v9 are no longer supported", - "dev": true, - "dependencies": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.1.1", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - }, - "engines": { - "node": "*" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/glob-parent": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", - "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", - "dependencies": { - "is-glob": "^4.0.1" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/globals": { - "version": "13.24.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-13.24.0.tgz", - "integrity": "sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ==", - "dev": true, - "dependencies": { - "type-fest": "^0.20.2" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/globalthis": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/globalthis/-/globalthis-1.0.4.tgz", - "integrity": "sha512-DpLKbNU4WylpxJykQujfCcwYWiV/Jhm50Goo0wrVILAv5jOr9d+H+UR3PhSCD2rCCEIg0uc+G+muBTwD54JhDQ==", - "dev": true, - "dependencies": { - "define-properties": "^1.2.1", - "gopd": "^1.0.1" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/gopd": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", - "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/graceful-fs": { - "version": "4.2.11", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", - "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", - "dev": true - }, - "node_modules/graphemer": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/graphemer/-/graphemer-1.4.0.tgz", - "integrity": "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==", - "dev": true - }, - "node_modules/has-bigints": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/has-bigints/-/has-bigints-1.1.0.tgz", - "integrity": "sha512-R3pbpkcIqv2Pm3dUwgjclDRVmWpTJW2DcMzcIhEXEx1oh/CEMObMm3KLmRJOdvhM7o4uQBnwr8pzRK2sJWIqfg==", - "dev": true, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/has-flag": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", - "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", - "engines": { - "node": ">=4" - } - }, - "node_modules/has-property-descriptors": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz", - "integrity": "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==", - "license": "MIT", - "dependencies": { - "es-define-property": "^1.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/has-proto": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.2.0.tgz", - "integrity": "sha512-KIL7eQPfHQRC8+XluaIw7BHUwwqL19bQn4hzNgdr+1wXoU0KKj6rufu47lhY7KbJR2C6T6+PfyN0Ea7wkSS+qQ==", - "dev": true, - "dependencies": { - "dunder-proto": "^1.0.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/has-symbols": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", - "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/has-tostringtag": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz", - "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", - "dependencies": { - "has-symbols": "^1.0.3" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/hasown": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", - "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", - "license": "MIT", - "dependencies": { - "function-bind": "^1.1.2" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/http-errors": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz", - "integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==", - "license": "MIT", - "dependencies": { - "depd": "2.0.0", - "inherits": "2.0.4", - "setprototypeof": "1.2.0", - "statuses": "2.0.1", - "toidentifier": "1.0.1" - }, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/iconv-lite": { - "version": "0.4.24", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", - "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", - "dependencies": { - "safer-buffer": ">= 2.1.2 < 3" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/ignore": { - "version": "5.3.2", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", - "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==", - "dev": true, - "engines": { - "node": ">= 4" - } - }, - "node_modules/ignore-by-default": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/ignore-by-default/-/ignore-by-default-1.0.1.tgz", - "integrity": "sha1-SMptcvbGo68Aqa1K5odr44ieKwk=" - }, - "node_modules/import-fresh": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.1.tgz", - "integrity": "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==", - "dev": true, - "dependencies": { - "parent-module": "^1.0.0", - "resolve-from": "^4.0.0" - }, - "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/imurmurhash": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", - "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", - "dev": true, - "engines": { - "node": ">=0.8.19" - } - }, - "node_modules/inflight": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", - "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", - "deprecated": "This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful.", - "dev": true, - "dependencies": { - "once": "^1.3.0", - "wrappy": "1" - } - }, - "node_modules/inherits": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" - }, - "node_modules/internal-slot": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.1.0.tgz", - "integrity": "sha512-4gd7VpWNQNB4UKKCFFVcp1AVv+FMOgs9NKzjHKusc8jTMhd5eL1NqQqOpE0KzMds804/yHlglp3uxgluOqAPLw==", - "dev": true, - "dependencies": { - "es-errors": "^1.3.0", - "hasown": "^2.0.2", - "side-channel": "^1.1.0" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/ipaddr.js": { - "version": "1.9.1", - "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", - "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==", - "engines": { - "node": ">= 0.10" - } - }, - "node_modules/is-arguments": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/is-arguments/-/is-arguments-1.1.1.tgz", - "integrity": "sha512-8Q7EARjzEnKpt/PCD7e1cgUS0a6X8u5tdSiMqXhojOdoV9TsMsiO+9VLC5vAmO8N7/GmXn7yjR8qnA6bVAEzfA==", - "dependencies": { - "call-bind": "^1.0.2", - "has-tostringtag": "^1.0.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-array-buffer": { - "version": "3.0.5", - "resolved": "https://registry.npmjs.org/is-array-buffer/-/is-array-buffer-3.0.5.tgz", - "integrity": "sha512-DDfANUiiG2wC1qawP66qlTugJeL5HyzMpfr8lLK+jMQirGzNod0B12cFB/9q838Ru27sBwfw78/rdoU7RERz6A==", - "dev": true, - "dependencies": { - "call-bind": "^1.0.8", - "call-bound": "^1.0.3", - "get-intrinsic": "^1.2.6" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-arrayish": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", - "integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==", - "dev": true - }, - "node_modules/is-async-function": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/is-async-function/-/is-async-function-2.1.1.tgz", - "integrity": "sha512-9dgM/cZBnNvjzaMYHVoxxfPj2QXt22Ev7SuuPrs+xav0ukGB0S6d4ydZdEiM48kLx5kDV+QBPrpVnFyefL8kkQ==", - "dev": true, - "dependencies": { - "async-function": "^1.0.0", - "call-bound": "^1.0.3", - "get-proto": "^1.0.1", - "has-tostringtag": "^1.0.2", - "safe-regex-test": "^1.1.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-bigint": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/is-bigint/-/is-bigint-1.1.0.tgz", - "integrity": "sha512-n4ZT37wG78iz03xPRKJrHTdZbe3IicyucEtdRsV5yglwc3GyUfbAfpSeD0FJ41NbUNSt5wbhqfp1fS+BgnvDFQ==", - "dev": true, - "dependencies": { - "has-bigints": "^1.0.2" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-binary-path": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", - "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", - "dependencies": { - "binary-extensions": "^2.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/is-boolean-object": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/is-boolean-object/-/is-boolean-object-1.2.2.tgz", - "integrity": "sha512-wa56o2/ElJMYqjCjGkXri7it5FbebW5usLw/nPmCMs5DeZ7eziSYZhSmPRn0txqeW4LnAmQQU7FgqLpsEFKM4A==", - "dev": true, - "dependencies": { - "call-bound": "^1.0.3", - "has-tostringtag": "^1.0.2" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-callable": { - "version": "1.2.7", - "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.7.tgz", - "integrity": "sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==", - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-core-module": { - "version": "2.16.1", - "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.16.1.tgz", - "integrity": "sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==", - "dev": true, - "dependencies": { - "hasown": "^2.0.2" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-data-view": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/is-data-view/-/is-data-view-1.0.2.tgz", - "integrity": "sha512-RKtWF8pGmS87i2D6gqQu/l7EYRlVdfzemCJN/P3UOs//x1QE7mfhvzHIApBTRf7axvT6DMGwSwBXYCT0nfB9xw==", - "dev": true, - "dependencies": { - "call-bound": "^1.0.2", - "get-intrinsic": "^1.2.6", - "is-typed-array": "^1.1.13" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-date-object": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.1.0.tgz", - "integrity": "sha512-PwwhEakHVKTdRNVOw+/Gyh0+MzlCl4R6qKvkhuvLtPMggI1WAHt9sOwZxQLSGpUaDnrdyDsomoRgNnCfKNSXXg==", - "dev": true, - "dependencies": { - "call-bound": "^1.0.2", - "has-tostringtag": "^1.0.2" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-extglob": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", - "integrity": "sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-finalizationregistry": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/is-finalizationregistry/-/is-finalizationregistry-1.1.1.tgz", - "integrity": "sha512-1pC6N8qWJbWoPtEjgcL2xyhQOP491EQjeUo3qTKcmV8YSDDJrOepfG8pcC7h/QgnQHYSv0mJ3Z/ZWxmatVrysg==", - "dev": true, - "dependencies": { - "call-bound": "^1.0.3" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-generator-function": { - "version": "1.0.10", - "resolved": "https://registry.npmjs.org/is-generator-function/-/is-generator-function-1.0.10.tgz", - "integrity": "sha512-jsEjy9l3yiXEQ+PsXdmBwEPcOxaXWLspKdplFUVI9vq1iZgIekeC0L167qeu86czQaxed3q/Uzuw0swL0irL8A==", - "dependencies": { - "has-tostringtag": "^1.0.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-glob": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", - "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", - "dependencies": { - "is-extglob": "^2.1.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-map": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/is-map/-/is-map-2.0.3.tgz", - "integrity": "sha512-1Qed0/Hr2m+YqxnM09CjA2d/i6YZNfF6R2oRAOj36eUdS6qIV/huPJNSEpKbupewFs+ZsJlxsjjPbc0/afW6Lw==", - "dev": true, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-negative-zero": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/is-negative-zero/-/is-negative-zero-2.0.3.tgz", - "integrity": "sha512-5KoIu2Ngpyek75jXodFvnafB6DJgr3u8uuK0LEZJjrU19DrMD3EVERaR8sjz8CCGgpZvxPl9SuE1GMVPFHx1mw==", - "dev": true, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-number": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", - "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", - "engines": { - "node": ">=0.12.0" - } - }, - "node_modules/is-number-object": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/is-number-object/-/is-number-object-1.1.1.tgz", - "integrity": "sha512-lZhclumE1G6VYD8VHe35wFaIif+CTy5SJIi5+3y4psDgWu4wPDoBhF8NxUOinEc7pHgiTsT6MaBb92rKhhD+Xw==", - "dev": true, - "dependencies": { - "call-bound": "^1.0.3", - "has-tostringtag": "^1.0.2" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-path-inside": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-3.0.3.tgz", - "integrity": "sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/is-regex": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.2.1.tgz", - "integrity": "sha512-MjYsKHO5O7mCsmRGxWcLWheFqN9DJ/2TmngvjKXihe6efViPqc274+Fx/4fYj/r03+ESvBdTXK0V6tA3rgez1g==", - "dev": true, - "dependencies": { - "call-bound": "^1.0.2", - "gopd": "^1.2.0", - "has-tostringtag": "^1.0.2", - "hasown": "^2.0.2" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-set": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/is-set/-/is-set-2.0.3.tgz", - "integrity": "sha512-iPAjerrse27/ygGLxw+EBR9agv9Y6uLeYVJMu+QNCoouJ1/1ri0mGrcWpfCqFZuzzx3WjtwxG098X+n4OuRkPg==", - "dev": true, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-shared-array-buffer": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/is-shared-array-buffer/-/is-shared-array-buffer-1.0.4.tgz", - "integrity": "sha512-ISWac8drv4ZGfwKl5slpHG9OwPNty4jOWPRIhBpxOoD+hqITiwuipOQ2bNthAzwA3B4fIjO4Nln74N0S9byq8A==", - "dev": true, - "dependencies": { - "call-bound": "^1.0.3" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-string": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/is-string/-/is-string-1.1.1.tgz", - "integrity": "sha512-BtEeSsoaQjlSPBemMQIrY1MY0uM6vnS1g5fmufYOtnxLGUZM2178PKbhsk7Ffv58IX+ZtcvoGwccYsh0PglkAA==", - "dev": true, - "dependencies": { - "call-bound": "^1.0.3", - "has-tostringtag": "^1.0.2" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-symbol": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.1.1.tgz", - "integrity": "sha512-9gGx6GTtCQM73BgmHQXfDmLtfjjTUDSyoxTCbp5WtoixAhfgsDirWIcVQ/IHpvI5Vgd5i/J5F7B9cN/WlVbC/w==", - "dev": true, - "dependencies": { - "call-bound": "^1.0.2", - "has-symbols": "^1.1.0", - "safe-regex-test": "^1.1.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-typed-array": { - "version": "1.1.15", - "resolved": "https://registry.npmjs.org/is-typed-array/-/is-typed-array-1.1.15.tgz", - "integrity": "sha512-p3EcsicXjit7SaskXHs1hA91QxgTw46Fv6EFKKGS5DRFLD8yKnohjF3hxoju94b/OcMZoQukzpPpBE9uLVKzgQ==", - "dependencies": { - "which-typed-array": "^1.1.16" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-weakmap": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/is-weakmap/-/is-weakmap-2.0.2.tgz", - "integrity": "sha512-K5pXYOm9wqY1RgjpL3YTkF39tni1XajUIkawTLUo9EZEVUFga5gSQJF8nNS7ZwJQ02y+1YCNYcMh+HIf1ZqE+w==", - "dev": true, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-weakref": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/is-weakref/-/is-weakref-1.1.1.tgz", - "integrity": "sha512-6i9mGWSlqzNMEqpCp93KwRS1uUOodk2OJ6b+sq7ZPDSy2WuI5NFIxp/254TytR8ftefexkWn5xNiHUNpPOfSew==", - "dev": true, - "dependencies": { - "call-bound": "^1.0.3" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-weakset": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/is-weakset/-/is-weakset-2.0.4.tgz", - "integrity": "sha512-mfcwb6IzQyOKTs84CQMrOwW4gQcaTOAWJ0zzJCl2WSPDrWk/OzDaImWFH3djXhb24g4eudZfLRozAvPGw4d9hQ==", - "dev": true, - "dependencies": { - "call-bound": "^1.0.3", - "get-intrinsic": "^1.2.6" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/isarray": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.5.tgz", - "integrity": "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==", - "dev": true - }, - "node_modules/isexe": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", - "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", - "dev": true - }, - "node_modules/iterator.prototype": { - "version": "1.1.5", - "resolved": "https://registry.npmjs.org/iterator.prototype/-/iterator.prototype-1.1.5.tgz", - "integrity": "sha512-H0dkQoCa3b2VEeKQBOxFph+JAbcrQdE7KC0UkqwpLmv2EC4P41QXP+rqo9wYodACiG5/WM5s9oDApTU8utwj9g==", - "dev": true, - "dependencies": { - "define-data-property": "^1.1.4", - "es-object-atoms": "^1.0.0", - "get-intrinsic": "^1.2.6", - "get-proto": "^1.0.0", - "has-symbols": "^1.1.0", - "set-function-name": "^2.0.2" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/js-tokens": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", - "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", - "dev": true - }, - "node_modules/js-yaml": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.1.tgz", - "integrity": "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==", - "dev": true, - "dependencies": { - "argparse": "^2.0.1" - }, - "bin": { - "js-yaml": "bin/js-yaml.js" - } - }, - "node_modules/json-buffer": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", - "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==", - "dev": true - }, - "node_modules/json-parse-better-errors": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/json-parse-better-errors/-/json-parse-better-errors-1.0.2.tgz", - "integrity": "sha512-mrqyZKfX5EhL7hvqcV6WG1yYjnjeuYDzDhhcAAUrq8Po85NBQBJP+ZDUT75qZQ98IkUoBqdkExkukOU7Ts2wrw==", - "dev": true - }, - "node_modules/json-schema-traverse": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", - "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", - "dev": true - }, - "node_modules/json-stable-stringify-without-jsonify": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", - "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", - "dev": true - }, - "node_modules/json5": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.2.tgz", - "integrity": "sha512-g1MWMLBiz8FKi1e4w0UyVL3w+iJceWAFBAaBnnGKOpNa5f8TLktkbre1+s6oICydWAm+HRUGTmI+//xv2hvXYA==", - "dev": true, - "dependencies": { - "minimist": "^1.2.0" - }, - "bin": { - "json5": "lib/cli.js" - } - }, - "node_modules/jsx-ast-utils": { - "version": "3.3.5", - "resolved": "https://registry.npmjs.org/jsx-ast-utils/-/jsx-ast-utils-3.3.5.tgz", - "integrity": "sha512-ZZow9HBI5O6EPgSJLUb8n2NKgmVWTwCvHGwFuJlMjvLFqlGG6pjirPhtdsseaLZjSibD8eegzmYpUZwoIlj2cQ==", - "dev": true, - "dependencies": { - "array-includes": "^3.1.6", - "array.prototype.flat": "^1.3.1", - "object.assign": "^4.1.4", - "object.values": "^1.1.6" - }, - "engines": { - "node": ">=4.0" - } - }, - "node_modules/keyv": { - "version": "4.5.4", - "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", - "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==", - "dev": true, - "dependencies": { - "json-buffer": "3.0.1" - } - }, - "node_modules/levn": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", - "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", - "dev": true, - "dependencies": { - "prelude-ls": "^1.2.1", - "type-check": "~0.4.0" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/load-json-file": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-5.3.0.tgz", - "integrity": "sha512-cJGP40Jc/VXUsp8/OrnyKyTZ1y6v/dphm3bioS+RrKXjK2BB6wHUd6JptZEFDGgGahMT+InnZO5i1Ei9mpC8Bw==", - "dev": true, - "dependencies": { - "graceful-fs": "^4.1.15", - "parse-json": "^4.0.0", - "pify": "^4.0.1", - "strip-bom": "^3.0.0", - "type-fest": "^0.3.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/load-json-file/node_modules/type-fest": { - "version": "0.3.1", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.3.1.tgz", - "integrity": "sha512-cUGJnCdr4STbePCgqNFbpVNCepa+kAVohJs1sLhxzdH+gnEoOd8VhbYa7pD3zZYGiURWM2xzEII3fQcRizDkYQ==", - "dev": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/locate-path": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", - "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", - "dev": true, - "dependencies": { - "p-locate": "^5.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/lodash.merge": { - "version": "4.6.2", - "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", - "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", - "dev": true - }, - "node_modules/loose-envify": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", - "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", - "dev": true, - "dependencies": { - "js-tokens": "^3.0.0 || ^4.0.0" - }, - "bin": { - "loose-envify": "cli.js" - } - }, - "node_modules/math-intrinsics": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", - "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/media-typer": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", - "integrity": "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/merge-descriptors": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.3.tgz", - "integrity": "sha512-gaNvAS7TZ897/rVaZ0nMtAyxNyi/pdbjbAwUpFQpN70GqnVfOiXpeUUMKRBmzXaSQ8DdTX4/0ms62r2K+hE6mQ==", - "license": "MIT", - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/methods": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", - "integrity": "sha1-VSmk1nZUE07cxSZmVoNbD4Ua/O4=", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/mime": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", - "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==", - "license": "MIT", - "bin": { - "mime": "cli.js" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/mime-db": { - "version": "1.52.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", - "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/mime-types": { - "version": "2.1.35", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", - "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", - "dependencies": { - "mime-db": "1.52.0" - }, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/minimatch": { - "version": "3.1.5", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", - "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", - "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, - "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": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" - }, - "node_modules/natural-compare": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", - "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", - "dev": true - }, - "node_modules/negotiator": { - "version": "0.6.3", - "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", - "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/nodemon": { - "version": "2.0.20", - "resolved": "https://registry.npmjs.org/nodemon/-/nodemon-2.0.20.tgz", - "integrity": "sha512-Km2mWHKKY5GzRg6i1j5OxOHQtuvVsgskLfigG25yTtbyfRGn/GNvIbRyOf1PSCKJ2aT/58TiuUsuOU5UToVViw==", - "dependencies": { - "chokidar": "^3.5.2", - "debug": "^3.2.7", - "ignore-by-default": "^1.0.1", - "minimatch": "^3.1.2", - "pstree.remy": "^1.1.8", - "semver": "^5.7.1", - "simple-update-notifier": "^1.0.7", - "supports-color": "^5.5.0", - "touch": "^3.1.0", - "undefsafe": "^2.0.5" - }, - "bin": { - "nodemon": "bin/nodemon.js" - }, - "engines": { - "node": ">=8.10.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/nodemon" - } - }, - "node_modules/nodemon/node_modules/debug": { - "version": "3.2.7", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", - "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", - "dependencies": { - "ms": "^2.1.1" - } - }, - "node_modules/nodemon/node_modules/ms": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", - "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" - }, - "node_modules/nopt": { - "version": "1.0.10", - "resolved": "https://registry.npmjs.org/nopt/-/nopt-1.0.10.tgz", - "integrity": "sha1-bd0hvSoxQXuScn3Vhfim83YI6+4=", - "dependencies": { - "abbrev": "1" - }, - "bin": { - "nopt": "bin/nopt.js" - }, - "engines": { - "node": "*" - } - }, - "node_modules/normalize-path": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", - "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/object-assign": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", - "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/object-inspect": { - "version": "1.13.4", - "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz", - "integrity": "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==", - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/object-keys": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", - "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==", - "dev": true, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/object.assign": { - "version": "4.1.7", - "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.7.tgz", - "integrity": "sha512-nK28WOo+QIjBkDduTINE4JkF/UJJKyf2EJxvJKfblDpyg0Q+pkOHNTL0Qwy6NP6FhE/EnzV73BxxqcJaXY9anw==", - "dev": true, - "dependencies": { - "call-bind": "^1.0.8", - "call-bound": "^1.0.3", - "define-properties": "^1.2.1", - "es-object-atoms": "^1.0.0", - "has-symbols": "^1.1.0", - "object-keys": "^1.1.1" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/object.entries": { - "version": "1.1.9", - "resolved": "https://registry.npmjs.org/object.entries/-/object.entries-1.1.9.tgz", - "integrity": "sha512-8u/hfXFRBD1O0hPUjioLhoWFHRmt6tKA4/vZPyckBr18l1KE9uHrFaFaUi8MDRTpi4uak2goyPTSNJLXX2k2Hw==", - "dev": true, - "dependencies": { - "call-bind": "^1.0.8", - "call-bound": "^1.0.4", - "define-properties": "^1.2.1", - "es-object-atoms": "^1.1.1" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/object.fromentries": { - "version": "2.0.8", - "resolved": "https://registry.npmjs.org/object.fromentries/-/object.fromentries-2.0.8.tgz", - "integrity": "sha512-k6E21FzySsSK5a21KRADBd/NGneRegFO5pLHfdQLpRDETUNJueLXs3WCzyQ3tFRDYgbq3KHGXfTbi2bs8WQ6rQ==", - "dev": true, - "dependencies": { - "call-bind": "^1.0.7", - "define-properties": "^1.2.1", - "es-abstract": "^1.23.2", - "es-object-atoms": "^1.0.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/object.groupby": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/object.groupby/-/object.groupby-1.0.3.tgz", - "integrity": "sha512-+Lhy3TQTuzXI5hevh8sBGqbmurHbbIjAi0Z4S63nthVLmLxfbj4T54a4CfZrXIrt9iP4mVAPYMo/v99taj3wjQ==", - "dev": true, - "dependencies": { - "call-bind": "^1.0.7", - "define-properties": "^1.2.1", - "es-abstract": "^1.23.2" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/object.values": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/object.values/-/object.values-1.2.1.tgz", - "integrity": "sha512-gXah6aZrcUxjWg2zR2MwouP2eHlCBzdV4pygudehaKXSGW4v2AsRQUK+lwwXhii6KFZcunEnmSUoYp5CXibxtA==", - "dev": true, - "dependencies": { - "call-bind": "^1.0.8", - "call-bound": "^1.0.3", - "define-properties": "^1.2.1", - "es-object-atoms": "^1.0.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/on-finished": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", - "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", - "dependencies": { - "ee-first": "1.1.1" - }, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/once": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", - "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", - "dev": true, - "dependencies": { - "wrappy": "1" - } - }, - "node_modules/optionator": { - "version": "0.9.4", - "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz", - "integrity": "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==", - "dev": true, - "dependencies": { - "deep-is": "^0.1.3", - "fast-levenshtein": "^2.0.6", - "levn": "^0.4.1", - "prelude-ls": "^1.2.1", - "type-check": "^0.4.0", - "word-wrap": "^1.2.5" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/own-keys": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/own-keys/-/own-keys-1.0.1.tgz", - "integrity": "sha512-qFOyK5PjiWZd+QQIh+1jhdb9LpxTF0qs7Pm8o5QHYZ0M3vKqSqzsZaEB6oWlxZ+q2sJBMI/Ktgd2N5ZwQoRHfg==", - "dev": true, - "dependencies": { - "get-intrinsic": "^1.2.6", - "object-keys": "^1.1.1", - "safe-push-apply": "^1.0.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/p-limit": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", - "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", - "dev": true, - "dependencies": { - "yocto-queue": "^0.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/p-locate": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", - "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", - "dev": true, - "dependencies": { - "p-limit": "^3.0.2" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/p-try": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", - "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", - "dev": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/parent-module": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", - "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", - "dev": true, - "dependencies": { - "callsites": "^3.0.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/parse-json": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-4.0.0.tgz", - "integrity": "sha512-aOIos8bujGN93/8Ox/jPLh7RwVnPEysynVFE+fQZyg6jKELEHwzgKdLRFHUgXJL6kylijVSBC4BvN9OmsB48Rw==", - "dev": true, - "dependencies": { - "error-ex": "^1.3.1", - "json-parse-better-errors": "^1.0.1" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/parseurl": { - "version": "1.3.3", - "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", - "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/path-exists": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", - "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/path-is-absolute": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", - "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "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, - "engines": { - "node": ">=8" - } - }, - "node_modules/path-parse": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", - "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", - "dev": true - }, - "node_modules/path-to-regexp": { - "version": "0.1.13", - "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.13.tgz", - "integrity": "sha512-A/AGNMFN3c8bOlvV9RreMdrv7jsmF9XIfDeCd87+I8RNg6s78BhJxMu69NEMHBSJFxKidViTEdruRwEk/WIKqA==" - }, - "node_modules/picomatch": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.2.tgz", - "integrity": "sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA==", - "engines": { - "node": ">=8.6" - }, - "funding": { - "url": "https://github.com/sponsors/jonschlinkert" - } - }, - "node_modules/pify": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/pify/-/pify-4.0.1.tgz", - "integrity": "sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g==", - "dev": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/pkg-conf": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/pkg-conf/-/pkg-conf-3.1.0.tgz", - "integrity": "sha512-m0OTbR/5VPNPqO1ph6Fqbj7Hv6QU7gR/tQW40ZqrL1rjgCU85W6C1bJn0BItuJqnR98PWzw7Z8hHeChD1WrgdQ==", - "dev": true, - "dependencies": { - "find-up": "^3.0.0", - "load-json-file": "^5.2.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/pkg-conf/node_modules/find-up": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-3.0.0.tgz", - "integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==", - "dev": true, - "dependencies": { - "locate-path": "^3.0.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/pkg-conf/node_modules/locate-path": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-3.0.0.tgz", - "integrity": "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==", - "dev": true, - "dependencies": { - "p-locate": "^3.0.0", - "path-exists": "^3.0.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/pkg-conf/node_modules/p-limit": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", - "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", - "dev": true, - "dependencies": { - "p-try": "^2.0.0" - }, - "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/pkg-conf/node_modules/p-locate": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-3.0.0.tgz", - "integrity": "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==", - "dev": true, - "dependencies": { - "p-limit": "^2.0.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/pkg-conf/node_modules/path-exists": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz", - "integrity": "sha512-bpC7GYwiDYQ4wYLe+FA8lhRjhQCMcQGuSgGGqDkg/QerRWw9CmGRT0iSOVRSZJ29NMLZgIzqaljJ63oaL4NIJQ==", - "dev": true, - "engines": { - "node": ">=4" - } - }, - "node_modules/possible-typed-array-names": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/possible-typed-array-names/-/possible-typed-array-names-1.1.0.tgz", - "integrity": "sha512-/+5VFTchJDoVj3bhoqi6UeymcD00DAwb1nJwamzPvHEszJ4FpF6SNNbUbOS8yI56qHzdV8eK0qEfOSiodkTdxg==", - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/prelude-ls": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", - "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", - "dev": true, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/prop-types": { - "version": "15.8.1", - "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz", - "integrity": "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==", - "dev": true, - "dependencies": { - "loose-envify": "^1.4.0", - "object-assign": "^4.1.1", - "react-is": "^16.13.1" - } - }, - "node_modules/proxy-addr": { - "version": "2.0.8", - "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.8.tgz", - "integrity": "sha512-5nnx0yGyVUcY6t9RnWcARWtwT9F1D8O9rt08htPvnd49W1IgZtmLkhu9WfMzQj1cFxjHIO6connUNVW5k7AVyQ==", - "license": "MIT", - "dependencies": { - "forwarded": "0.2.0", - "ipaddr.js": "1.9.1" - }, - "engines": { - "node": ">= 0.10" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/express" - } - }, - "node_modules/pstree.remy": { - "version": "1.1.8", - "resolved": "https://registry.npmjs.org/pstree.remy/-/pstree.remy-1.1.8.tgz", - "integrity": "sha512-77DZwxQmxKnu3aR542U+X8FypNzbfJ+C5XQDk3uWjWxn6151aIMGthWYRXTqT1E5oJvg+ljaa2OJi+VfvCOQ8w==" - }, - "node_modules/punycode": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", - "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==", - "dev": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/qs": { - "version": "6.15.2", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.15.2.tgz", - "integrity": "sha512-Rzq0KEyX/w/tEybncDgdkZrJgVUsUMk3xjh3t5bv3S1HTAtg+uOYt72+ZfwiQwKdysThkTBdL/rTi6HDmX9Ddw==", - "dependencies": { - "side-channel": "^1.1.0" - }, - "engines": { - "node": ">=0.6" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/queue-microtask": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", - "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", - "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" - } - ] - }, - "node_modules/range-parser": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", - "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==", - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/raw-body": { - "version": "2.5.3", - "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.3.tgz", - "integrity": "sha512-s4VSOf6yN0rvbRZGxs8Om5CWj6seneMwK3oDb4lWDH0UPhWcxwOWw5+qk24bxq87szX1ydrwylIOp2uG1ojUpA==", - "dependencies": { - "bytes": "~3.1.2", - "http-errors": "~2.0.1", - "iconv-lite": "~0.4.24", - "unpipe": "~1.0.0" - }, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/raw-body/node_modules/http-errors": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.1.tgz", - "integrity": "sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ==", - "dependencies": { - "depd": "~2.0.0", - "inherits": "~2.0.4", - "setprototypeof": "~1.2.0", - "statuses": "~2.0.2", - "toidentifier": "~1.0.1" - }, - "engines": { - "node": ">= 0.8" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/express" - } - }, - "node_modules/raw-body/node_modules/statuses": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.2.tgz", - "integrity": "sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/react-is": { - "version": "16.13.1", - "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", - "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==", - "dev": true - }, - "node_modules/readdirp": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", - "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", - "dependencies": { - "picomatch": "^2.2.1" - }, - "engines": { - "node": ">=8.10.0" - } - }, - "node_modules/reflect.getprototypeof": { - "version": "1.0.10", - "resolved": "https://registry.npmjs.org/reflect.getprototypeof/-/reflect.getprototypeof-1.0.10.tgz", - "integrity": "sha512-00o4I+DVrefhv+nX0ulyi3biSHCPDe+yLv5o/p6d/UVlirijB8E16FtfwSAi4g3tcqrQ4lRAqQSoFEZJehYEcw==", - "dev": true, - "dependencies": { - "call-bind": "^1.0.8", - "define-properties": "^1.2.1", - "es-abstract": "^1.23.9", - "es-errors": "^1.3.0", - "es-object-atoms": "^1.0.0", - "get-intrinsic": "^1.2.7", - "get-proto": "^1.0.1", - "which-builtin-type": "^1.2.1" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/regexp.prototype.flags": { - "version": "1.5.4", - "resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.5.4.tgz", - "integrity": "sha512-dYqgNSZbDwkaJ2ceRd9ojCGjBq+mOm9LmtXnAnEGyHhN/5R7iDW2TRw3h+o/jCFxus3P2LfWIIiwowAjANm7IA==", - "dev": true, - "dependencies": { - "call-bind": "^1.0.8", - "define-properties": "^1.2.1", - "es-errors": "^1.3.0", - "get-proto": "^1.0.1", - "gopd": "^1.2.0", - "set-function-name": "^2.0.2" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/regexpp": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/regexpp/-/regexpp-3.2.0.tgz", - "integrity": "sha512-pq2bWo9mVD43nbts2wGv17XLiNLya+GklZ8kaDLV2Z08gDCsGpnKn9BFMepvWuHCbyVvY7J5o5+BVvoQbmlJLg==", - "dev": true, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/mysticatea" - } - }, - "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==", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/resolve": { - "version": "1.22.10", - "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.10.tgz", - "integrity": "sha512-NPRy+/ncIMeDlTAsuqwKIiferiawhefFJtkNSW0qZJEqMEb+qBt/77B/jGeeek+F0uOeN05CDa6HXbbIgtVX4w==", - "dev": true, - "dependencies": { - "is-core-module": "^2.16.0", - "path-parse": "^1.0.7", - "supports-preserve-symlinks-flag": "^1.0.0" - }, - "bin": { - "resolve": "bin/resolve" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/resolve-from": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", - "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", - "dev": true, - "engines": { - "node": ">=4" - } - }, - "node_modules/reusify": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.1.0.tgz", - "integrity": "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==", - "dev": true, - "engines": { - "iojs": ">=1.0.0", - "node": ">=0.10.0" - } - }, - "node_modules/rimraf": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", - "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", - "deprecated": "Rimraf versions prior to v4 are no longer supported", - "dev": true, - "dependencies": { - "glob": "^7.1.3" - }, - "bin": { - "rimraf": "bin.js" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/run-parallel": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", - "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", - "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" - } - ], - "dependencies": { - "queue-microtask": "^1.2.2" - } - }, - "node_modules/safe-array-concat": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/safe-array-concat/-/safe-array-concat-1.1.3.tgz", - "integrity": "sha512-AURm5f0jYEOydBj7VQlVvDrjeFgthDdEF5H1dP+6mNpoXOMo1quQqJ4wvJDyRZ9+pO3kGWoOdmV08cSv2aJV6Q==", - "dev": true, - "dependencies": { - "call-bind": "^1.0.8", - "call-bound": "^1.0.2", - "get-intrinsic": "^1.2.6", - "has-symbols": "^1.1.0", - "isarray": "^2.0.5" - }, - "engines": { - "node": ">=0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "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==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ] - }, - "node_modules/safe-push-apply": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/safe-push-apply/-/safe-push-apply-1.0.0.tgz", - "integrity": "sha512-iKE9w/Z7xCzUMIZqdBsp6pEQvwuEebH4vdpjcDWnyzaI6yl6O9FHvVpmGelvEHNsoY6wGblkxR6Zty/h00WiSA==", - "dev": true, - "dependencies": { - "es-errors": "^1.3.0", - "isarray": "^2.0.5" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/safe-regex-test": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/safe-regex-test/-/safe-regex-test-1.1.0.tgz", - "integrity": "sha512-x/+Cz4YrimQxQccJf5mKEbIa1NzeCRNI5Ecl/ekmlYaampdNLPalVyIcCZNNH3MvmqBugV5TMYZXv0ljslUlaw==", - "dev": true, - "dependencies": { - "call-bound": "^1.0.2", - "es-errors": "^1.3.0", - "is-regex": "^1.2.1" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/safer-buffer": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", - "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" - }, - "node_modules/semver": { - "version": "5.7.2", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.2.tgz", - "integrity": "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==", - "bin": { - "semver": "bin/semver" - } - }, - "node_modules/send": { - "version": "0.19.0", - "resolved": "https://registry.npmjs.org/send/-/send-0.19.0.tgz", - "integrity": "sha512-dW41u5VfLXu8SJh5bwRmyYUbAoSB3c9uQh6L8h/KtsFREPWpbX1lrljJo186Jc4nmci/sGUZ9a0a0J2zgfq2hw==", - "license": "MIT", - "dependencies": { - "debug": "2.6.9", - "depd": "2.0.0", - "destroy": "1.2.0", - "encodeurl": "~1.0.2", - "escape-html": "~1.0.3", - "etag": "~1.8.1", - "fresh": "0.5.2", - "http-errors": "2.0.0", - "mime": "1.6.0", - "ms": "2.1.3", - "on-finished": "2.4.1", - "range-parser": "~1.2.1", - "statuses": "2.0.1" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/send/node_modules/ms": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", - "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", - "license": "MIT" - }, - "node_modules/serve-static": { - "version": "1.16.2", - "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.16.2.tgz", - "integrity": "sha512-VqpjJZKadQB/PEbEwvFdO43Ax5dFBZ2UECszz8bQ7pi7wt//PWe1P6MN7eCnjsatYtBT6EuiClbjSWP2WrIoTw==", - "dependencies": { - "encodeurl": "~2.0.0", - "escape-html": "~1.0.3", - "parseurl": "~1.3.3", - "send": "0.19.0" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/serve-static/node_modules/encodeurl": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz", - "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/set-function-length": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.2.tgz", - "integrity": "sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==", - "license": "MIT", - "dependencies": { - "define-data-property": "^1.1.4", - "es-errors": "^1.3.0", - "function-bind": "^1.1.2", - "get-intrinsic": "^1.2.4", - "gopd": "^1.0.1", - "has-property-descriptors": "^1.0.2" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/set-function-name": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/set-function-name/-/set-function-name-2.0.2.tgz", - "integrity": "sha512-7PGFlmtwsEADb0WYyvCMa1t+yke6daIG4Wirafur5kcf+MhUnPms1UeR0CKQdTZD81yESwMHbtn+TR+dMviakQ==", - "dev": true, - "dependencies": { - "define-data-property": "^1.1.4", - "es-errors": "^1.3.0", - "functions-have-names": "^1.2.3", - "has-property-descriptors": "^1.0.2" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/set-proto": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/set-proto/-/set-proto-1.0.0.tgz", - "integrity": "sha512-RJRdvCo6IAnPdsvP/7m6bsQqNnn1FCBX5ZNtFL98MmFF/4xAIJTIg1YbHW5DC2W5SKZanrC6i4HsJqlajw/dZw==", - "dev": true, - "dependencies": { - "dunder-proto": "^1.0.1", - "es-errors": "^1.3.0", - "es-object-atoms": "^1.0.0" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/setprototypeof": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", - "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==", - "license": "ISC" - }, - "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, - "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, - "engines": { - "node": ">=8" - } - }, - "node_modules/side-channel": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.1.tgz", - "integrity": "sha512-6x6dK6zJdpTzF4sQeNYxwtvBzf6Eg4GtlesS94HOvTudUeyK2WXAaIfmDgsyslYrRBeFIlsi54AYsFGUuhmvrQ==", - "license": "MIT", - "dependencies": { - "es-errors": "^1.3.0", - "object-inspect": "^1.13.4", - "side-channel-list": "^1.0.1", - "side-channel-map": "^1.0.1", - "side-channel-weakmap": "^1.0.2" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/side-channel-list": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.1.tgz", - "integrity": "sha512-mjn/0bi/oUURjc5Xl7IaWi/OJJJumuoJFQJfDDyO46+hBWsfaVM65TBHq2eoZBhzl9EchxOijpkbRC8SVBQU0w==", - "license": "MIT", - "dependencies": { - "es-errors": "^1.3.0", - "object-inspect": "^1.13.4" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/side-channel-map": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/side-channel-map/-/side-channel-map-1.0.1.tgz", - "integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==", - "dependencies": { - "call-bound": "^1.0.2", - "es-errors": "^1.3.0", - "get-intrinsic": "^1.2.5", - "object-inspect": "^1.13.3" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/side-channel-weakmap": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz", - "integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==", - "dependencies": { - "call-bound": "^1.0.2", - "es-errors": "^1.3.0", - "get-intrinsic": "^1.2.5", - "object-inspect": "^1.13.3", - "side-channel-map": "^1.0.1" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/simple-update-notifier": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/simple-update-notifier/-/simple-update-notifier-1.0.7.tgz", - "integrity": "sha512-BBKgR84BJQJm6WjWFMHgLVuo61FBDSj1z/xSFUIozqO6wO7ii0JxCqlIud7Enr/+LhlbNI0whErq96P2qHNWew==", - "dependencies": { - "semver": "~7.0.0" - }, - "engines": { - "node": ">=8.10.0" - } - }, - "node_modules/simple-update-notifier/node_modules/semver": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.0.0.tgz", - "integrity": "sha512-+GB6zVA9LWh6zovYQLALHwv5rb2PHGlJi3lfiqIHxR0uuwCgefcOJc59v9fv1w8GbStwxuuqqAjI9NMAOOgq1A==", - "bin": { - "semver": "bin/semver.js" - } - }, - "node_modules/standard": { - "version": "17.1.2", - "resolved": "https://registry.npmjs.org/standard/-/standard-17.1.2.tgz", - "integrity": "sha512-WLm12WoXveKkvnPnPnaFUUHuOB2cUdAsJ4AiGHL2G0UNMrcRAWY2WriQaV8IQ3oRmYr0AWUbLNr94ekYFAHOrA==", - "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" - } - ], - "dependencies": { - "eslint": "^8.41.0", - "eslint-config-standard": "17.1.0", - "eslint-config-standard-jsx": "^11.0.0", - "eslint-plugin-import": "^2.27.5", - "eslint-plugin-n": "^15.7.0", - "eslint-plugin-promise": "^6.1.1", - "eslint-plugin-react": "^7.36.1", - "standard-engine": "^15.1.0", - "version-guard": "^1.1.1" - }, - "bin": { - "standard": "bin/cmd.cjs" - }, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - } - }, - "node_modules/standard-engine": { - "version": "15.1.0", - "resolved": "https://registry.npmjs.org/standard-engine/-/standard-engine-15.1.0.tgz", - "integrity": "sha512-VHysfoyxFu/ukT+9v49d4BRXIokFRZuH3z1VRxzFArZdjSCFpro6rEIU3ji7e4AoAtuSfKBkiOmsrDqKW5ZSRw==", - "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" - } - ], - "dependencies": { - "get-stdin": "^8.0.0", - "minimist": "^1.2.6", - "pkg-conf": "^3.1.0", - "xdg-basedir": "^4.0.0" - }, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - } - }, - "node_modules/statuses": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", - "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/stop-iteration-iterator": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/stop-iteration-iterator/-/stop-iteration-iterator-1.1.0.tgz", - "integrity": "sha512-eLoXW/DHyl62zxY4SCaIgnRhuMr6ri4juEYARS8E6sCEqzKpOiE521Ucofdx+KnDZl5xmvGYaaKCk5FEOxJCoQ==", - "dev": true, - "dependencies": { - "es-errors": "^1.3.0", - "internal-slot": "^1.1.0" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/string.prototype.matchall": { - "version": "4.0.12", - "resolved": "https://registry.npmjs.org/string.prototype.matchall/-/string.prototype.matchall-4.0.12.tgz", - "integrity": "sha512-6CC9uyBL+/48dYizRf7H7VAYCMCNTBeM78x/VTUe9bFEaxBepPJDa1Ow99LqI/1yF7kuy7Q3cQsYMrcjGUcskA==", - "dev": true, - "dependencies": { - "call-bind": "^1.0.8", - "call-bound": "^1.0.3", - "define-properties": "^1.2.1", - "es-abstract": "^1.23.6", - "es-errors": "^1.3.0", - "es-object-atoms": "^1.0.0", - "get-intrinsic": "^1.2.6", - "gopd": "^1.2.0", - "has-symbols": "^1.1.0", - "internal-slot": "^1.1.0", - "regexp.prototype.flags": "^1.5.3", - "set-function-name": "^2.0.2", - "side-channel": "^1.1.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/string.prototype.repeat": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/string.prototype.repeat/-/string.prototype.repeat-1.0.0.tgz", - "integrity": "sha512-0u/TldDbKD8bFCQ/4f5+mNRrXwZ8hg2w7ZR8wa16e8z9XpePWl3eGEcUD0OXpEH/VJH/2G3gjUtR3ZOiBe2S/w==", - "dev": true, - "dependencies": { - "define-properties": "^1.1.3", - "es-abstract": "^1.17.5" - } - }, - "node_modules/string.prototype.trim": { - "version": "1.2.10", - "resolved": "https://registry.npmjs.org/string.prototype.trim/-/string.prototype.trim-1.2.10.tgz", - "integrity": "sha512-Rs66F0P/1kedk5lyYyH9uBzuiI/kNRmwJAR9quK6VOtIpZ2G+hMZd+HQbbv25MgCA6gEffoMZYxlTod4WcdrKA==", - "dev": true, - "dependencies": { - "call-bind": "^1.0.8", - "call-bound": "^1.0.2", - "define-data-property": "^1.1.4", - "define-properties": "^1.2.1", - "es-abstract": "^1.23.5", - "es-object-atoms": "^1.0.0", - "has-property-descriptors": "^1.0.2" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/string.prototype.trimend": { - "version": "1.0.9", - "resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.9.tgz", - "integrity": "sha512-G7Ok5C6E/j4SGfyLCloXTrngQIQU3PWtXGst3yM7Bea9FRURf1S42ZHlZZtsNque2FN2PoUhfZXYLNWwEr4dLQ==", - "dev": true, - "dependencies": { - "call-bind": "^1.0.8", - "call-bound": "^1.0.2", - "define-properties": "^1.2.1", - "es-object-atoms": "^1.0.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/string.prototype.trimstart": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/string.prototype.trimstart/-/string.prototype.trimstart-1.0.8.tgz", - "integrity": "sha512-UXSH262CSZY1tfu3G3Secr6uGLCFVPMhIqHjlgCUtCCcgihYc/xKs9djMTMUOb2j1mVSeU8EU6NWc/iQKU6Gfg==", - "dev": true, - "dependencies": { - "call-bind": "^1.0.7", - "define-properties": "^1.2.1", - "es-object-atoms": "^1.0.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "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, - "dependencies": { - "ansi-regex": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/strip-bom": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", - "integrity": "sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==", - "dev": true, - "engines": { - "node": ">=4" - } - }, - "node_modules/strip-json-comments": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", - "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", - "dev": true, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/supports-color": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", - "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", - "dependencies": { - "has-flag": "^3.0.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/supports-preserve-symlinks-flag": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", - "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", - "dev": true, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/text-table": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", - "integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==", - "dev": true - }, - "node_modules/to-regex-range": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", - "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", - "dependencies": { - "is-number": "^7.0.0" - }, - "engines": { - "node": ">=8.0" - } - }, - "node_modules/toidentifier": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", - "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==", - "license": "MIT", - "engines": { - "node": ">=0.6" - } - }, - "node_modules/touch": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/touch/-/touch-3.1.0.tgz", - "integrity": "sha512-WBx8Uy5TLtOSRtIq+M03/sKDrXCLHxwDcquSP2c43Le03/9serjQBIztjRz6FkJez9D/hleyAXTBGLwwZUw9lA==", - "dependencies": { - "nopt": "~1.0.10" - }, - "bin": { - "nodetouch": "bin/nodetouch.js" - } - }, - "node_modules/tsconfig-paths": { - "version": "3.15.0", - "resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-3.15.0.tgz", - "integrity": "sha512-2Ac2RgzDe/cn48GvOe3M+o82pEFewD3UPbyoUHHdKasHwJKjds4fLXWf/Ux5kATBKN20oaFGu+jbElp1pos0mg==", - "dev": true, - "dependencies": { - "@types/json5": "^0.0.29", - "json5": "^1.0.2", - "minimist": "^1.2.6", - "strip-bom": "^3.0.0" - } - }, - "node_modules/type-check": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", - "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", - "dev": true, - "dependencies": { - "prelude-ls": "^1.2.1" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/type-fest": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", - "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", - "dev": true, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/type-is": { - "version": "1.6.18", - "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", - "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==", - "dependencies": { - "media-typer": "0.3.0", - "mime-types": "~2.1.24" - }, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/typed-array-buffer": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/typed-array-buffer/-/typed-array-buffer-1.0.3.tgz", - "integrity": "sha512-nAYYwfY3qnzX30IkA6AQZjVbtK6duGontcQm1WSG1MD94YLqK0515GNApXkoxKOWMusVssAHWLh9SeaoefYFGw==", - "dev": true, - "dependencies": { - "call-bound": "^1.0.3", - "es-errors": "^1.3.0", - "is-typed-array": "^1.1.14" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/typed-array-byte-length": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/typed-array-byte-length/-/typed-array-byte-length-1.0.3.tgz", - "integrity": "sha512-BaXgOuIxz8n8pIq3e7Atg/7s+DpiYrxn4vdot3w9KbnBhcRQq6o3xemQdIfynqSeXeDrF32x+WvfzmOjPiY9lg==", - "dev": true, - "dependencies": { - "call-bind": "^1.0.8", - "for-each": "^0.3.3", - "gopd": "^1.2.0", - "has-proto": "^1.2.0", - "is-typed-array": "^1.1.14" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/typed-array-byte-offset": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/typed-array-byte-offset/-/typed-array-byte-offset-1.0.4.tgz", - "integrity": "sha512-bTlAFB/FBYMcuX81gbL4OcpH5PmlFHqlCCpAl8AlEzMz5k53oNDvN8p1PNOWLEmI2x4orp3raOFB51tv9X+MFQ==", - "dev": true, - "dependencies": { - "available-typed-arrays": "^1.0.7", - "call-bind": "^1.0.8", - "for-each": "^0.3.3", - "gopd": "^1.2.0", - "has-proto": "^1.2.0", - "is-typed-array": "^1.1.15", - "reflect.getprototypeof": "^1.0.9" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/typed-array-length": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/typed-array-length/-/typed-array-length-1.0.7.tgz", - "integrity": "sha512-3KS2b+kL7fsuk/eJZ7EQdnEmQoaho/r6KUef7hxvltNA5DR8NAUM+8wJMbJyZ4G9/7i3v5zPBIMN5aybAh2/Jg==", - "dev": true, - "dependencies": { - "call-bind": "^1.0.7", - "for-each": "^0.3.3", - "gopd": "^1.0.1", - "is-typed-array": "^1.1.13", - "possible-typed-array-names": "^1.0.0", - "reflect.getprototypeof": "^1.0.6" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/unbox-primitive": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.1.0.tgz", - "integrity": "sha512-nWJ91DjeOkej/TA8pXQ3myruKpKEYgqvpw9lz4OPHj/NWFNluYrjbz9j01CJ8yKQd2g4jFoOkINCTW2I5LEEyw==", - "dev": true, - "dependencies": { - "call-bound": "^1.0.3", - "has-bigints": "^1.0.2", - "has-symbols": "^1.1.0", - "which-boxed-primitive": "^1.1.1" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/undefsafe": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/undefsafe/-/undefsafe-2.0.5.tgz", - "integrity": "sha512-WxONCrssBM8TSPRqN5EmsjVrsv4A8X12J4ArBiiayv3DyyG3ZlIg6yysuuSYdZsVz3TKcTg2fd//Ujd4CHV1iA==" - }, - "node_modules/unpipe": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", - "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/uri-js": { - "version": "4.2.2", - "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.2.2.tgz", - "integrity": "sha512-KY9Frmirql91X2Qgjry0Wd4Y+YTdrdZheS8TFwvkbLWf/G5KNJDCh6pKL5OZctEW4+0Baa5idK2ZQuELRwPznQ==", - "dev": true, - "dependencies": { - "punycode": "^2.1.0" - } - }, - "node_modules/util": { - "version": "0.12.4", - "resolved": "https://registry.npmjs.org/util/-/util-0.12.4.tgz", - "integrity": "sha512-bxZ9qtSlGUWSOy9Qa9Xgk11kSslpuZwaxCg4sNIDj6FLucDab2JxnHwyNTCpHMtK1MjoQiWQ6DiUMZYbSrO+Sw==", - "dependencies": { - "inherits": "^2.0.3", - "is-arguments": "^1.0.4", - "is-generator-function": "^1.0.7", - "is-typed-array": "^1.1.3", - "safe-buffer": "^5.1.2", - "which-typed-array": "^1.1.2" - } - }, - "node_modules/utils-merge": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", - "integrity": "sha1-n5VxD1CiZ5R7LMwSR0HBAoQn5xM=", - "engines": { - "node": ">= 0.4.0" - } - }, - "node_modules/uuid": { - "version": "8.3.2", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", - "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==", - "bin": { - "uuid": "dist/bin/uuid" - } - }, - "node_modules/vary": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", - "integrity": "sha1-IpnwLG3tMNSllhsLn3RSShj2NPw=", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/version-guard": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/version-guard/-/version-guard-1.1.3.tgz", - "integrity": "sha512-JwPr6erhX53EWH/HCSzfy1tTFrtPXUe927wdM1jqBBeYp1OM+qPHjWbsvv6pIBduqdgxxS+ScfG7S28pzyr2DQ==", - "dev": true, - "engines": { - "node": ">=0.10.48" - } - }, - "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, - "dependencies": { - "isexe": "^2.0.0" - }, - "bin": { - "node-which": "bin/node-which" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/which-boxed-primitive": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/which-boxed-primitive/-/which-boxed-primitive-1.1.1.tgz", - "integrity": "sha512-TbX3mj8n0odCBFVlY8AxkqcHASw3L60jIuF8jFP78az3C2YhmGvqbHBpAjTRH2/xqYunrJ9g1jSyjCjpoWzIAA==", - "dev": true, - "dependencies": { - "is-bigint": "^1.1.0", - "is-boolean-object": "^1.2.1", - "is-number-object": "^1.1.1", - "is-string": "^1.1.1", - "is-symbol": "^1.1.1" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/which-builtin-type": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/which-builtin-type/-/which-builtin-type-1.2.1.tgz", - "integrity": "sha512-6iBczoX+kDQ7a3+YJBnh3T+KZRxM/iYNPXicqk66/Qfm1b93iu+yOImkg0zHbj5LNOcNv1TEADiZ0xa34B4q6Q==", - "dev": true, - "dependencies": { - "call-bound": "^1.0.2", - "function.prototype.name": "^1.1.6", - "has-tostringtag": "^1.0.2", - "is-async-function": "^2.0.0", - "is-date-object": "^1.1.0", - "is-finalizationregistry": "^1.1.0", - "is-generator-function": "^1.0.10", - "is-regex": "^1.2.1", - "is-weakref": "^1.0.2", - "isarray": "^2.0.5", - "which-boxed-primitive": "^1.1.0", - "which-collection": "^1.0.2", - "which-typed-array": "^1.1.16" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/which-collection": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/which-collection/-/which-collection-1.0.2.tgz", - "integrity": "sha512-K4jVyjnBdgvc86Y6BkaLZEN933SwYOuBFkdmBu9ZfkcAbdVbpITnDmjvZ/aQjRXQrv5EPkTnD1s39GiiqbngCw==", - "dev": true, - "dependencies": { - "is-map": "^2.0.3", - "is-set": "^2.0.3", - "is-weakmap": "^2.0.2", - "is-weakset": "^2.0.3" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/which-typed-array": { - "version": "1.1.19", - "resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.19.tgz", - "integrity": "sha512-rEvr90Bck4WZt9HHFC4DJMsjvu7x+r6bImz0/BrbWb7A2djJ8hnZMrWnHo9F8ssv0OMErasDhftrfROTyqSDrw==", - "dependencies": { - "available-typed-arrays": "^1.0.7", - "call-bind": "^1.0.8", - "call-bound": "^1.0.4", - "for-each": "^0.3.5", - "get-proto": "^1.0.1", - "gopd": "^1.2.0", - "has-tostringtag": "^1.0.2" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/word-wrap": { - "version": "1.2.5", - "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz", - "integrity": "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/wrappy": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", - "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", - "dev": true - }, - "node_modules/xdg-basedir": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/xdg-basedir/-/xdg-basedir-4.0.0.tgz", - "integrity": "sha512-PSNhEJDejZYV7h50BohL09Er9VaIefr2LMAf3OEmpCkjOi34eYyQYAXUTjEQtZJTKcF0E2UKTh+osDLsgNim9Q==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/yocto-queue": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", - "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", - "dev": true, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - } - }, - "dependencies": { - "@eslint-community/eslint-utils": { - "version": "4.7.0", - "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.7.0.tgz", - "integrity": "sha512-dyybb3AcajC7uha6CvhdVRJqaKyn7w2YKqKyAN37NKYgZT36w+iRb0Dymmc5qEJ549c/S31cMMSFd75bteCpCw==", - "dev": true, - "requires": { - "eslint-visitor-keys": "^3.4.3" - } - }, - "@eslint-community/regexpp": { - "version": "4.12.1", - "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.12.1.tgz", - "integrity": "sha512-CCZCDJuduB9OUkFkY2IgppNZMi2lBQgD2qzwXkEia16cge2pijY/aXi96CJMquDMn3nJdlPV1A5KrJEXwfLNzQ==", - "dev": true - }, - "@eslint/eslintrc": { - "version": "2.1.4", - "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-2.1.4.tgz", - "integrity": "sha512-269Z39MS6wVJtsoUl10L60WdkhJVdPG24Q4eZTH3nnF6lpvSShEK3wQjDX9JRWAUPvPh7COouPpU9IrqaZFvtQ==", - "dev": true, - "requires": { - "ajv": "^6.12.4", - "debug": "^4.3.2", - "espree": "^9.6.0", - "globals": "^13.19.0", - "ignore": "^5.2.0", - "import-fresh": "^3.2.1", - "js-yaml": "^4.1.0", - "minimatch": "^3.1.2", - "strip-json-comments": "^3.1.1" - }, - "dependencies": { - "debug": { - "version": "4.4.1", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.1.tgz", - "integrity": "sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==", - "dev": true, - "requires": { - "ms": "^2.1.3" - } - }, - "ms": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", - "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", - "dev": true - } - } - }, - "@eslint/js": { - "version": "8.57.1", - "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.57.1.tgz", - "integrity": "sha512-d9zaMRSTIKDLhctzH12MtXvJKSSUhaHcjV+2Z+GK+EEY7XKpP5yR4x+N3TAcHTcu963nIr+TMcCb4DBCYX1z6Q==", - "dev": true - }, - "@humanwhocodes/config-array": { - "version": "0.13.0", - "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.13.0.tgz", - "integrity": "sha512-DZLEEqFWQFiyK6h5YIeynKx7JlvCYWL0cImfSRXZ9l4Sg2efkFGTuFf6vzXjK1cq6IYkU+Eg/JizXw+TD2vRNw==", - "dev": true, - "requires": { - "@humanwhocodes/object-schema": "^2.0.3", - "debug": "^4.3.1", - "minimatch": "^3.0.5" - }, - "dependencies": { - "debug": { - "version": "4.4.1", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.1.tgz", - "integrity": "sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==", - "dev": true, - "requires": { - "ms": "^2.1.3" - } - }, - "ms": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", - "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", - "dev": true - } - } - }, - "@humanwhocodes/module-importer": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", - "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==", - "dev": true - }, - "@humanwhocodes/object-schema": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-2.0.3.tgz", - "integrity": "sha512-93zYdMES/c1D69yZiKDBj0V24vqNzB/koF26KPaagAfd3P/4gUlh3Dys5ogAK+Exi9QyzlD8x/08Zt7wIKcDcA==", - "dev": true - }, - "@nodelib/fs.scandir": { - "version": "2.1.5", - "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", - "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", - "dev": true, - "requires": { - "@nodelib/fs.stat": "2.0.5", - "run-parallel": "^1.1.9" - } - }, - "@nodelib/fs.stat": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", - "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", - "dev": true - }, - "@nodelib/fs.walk": { - "version": "1.2.8", - "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", - "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", - "dev": true, - "requires": { - "@nodelib/fs.scandir": "2.1.5", - "fastq": "^1.6.0" - } - }, - "@rtsao/scc": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@rtsao/scc/-/scc-1.1.0.tgz", - "integrity": "sha512-zt6OdqaDoOnJ1ZYsCYGt9YmWzDXl4vQdKTyJev62gFhRGKdx7mcT54V9KIjg+d2wi9EXsPvAPKe7i7WjfVWB8g==", - "dev": true - }, - "@types/json5": { - "version": "0.0.29", - "resolved": "https://registry.npmjs.org/@types/json5/-/json5-0.0.29.tgz", - "integrity": "sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==", - "dev": true - }, - "@ungap/structured-clone": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.3.0.tgz", - "integrity": "sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g==", - "dev": true - }, - "abbrev": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz", - "integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==" - }, - "accepts": { - "version": "1.3.8", - "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", - "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==", - "requires": { - "mime-types": "~2.1.34", - "negotiator": "0.6.3" - } - }, - "acorn": { - "version": "8.15.0", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz", - "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", - "dev": true - }, - "acorn-jsx": { - "version": "5.3.2", - "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", - "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", - "dev": true, - "requires": {} - }, - "ajv": { - "version": "6.14.0", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.14.0.tgz", - "integrity": "sha512-IWrosm/yrn43eiKqkfkHis7QioDleaXQHdDVPKg0FSwwd/DuvyX79TZnFOnYpB7dcsFAMmtFztZuXPDvSePkFw==", - "dev": true, - "requires": { - "fast-deep-equal": "^3.1.1", - "fast-json-stable-stringify": "^2.0.0", - "json-schema-traverse": "^0.4.1", - "uri-js": "^4.2.2" - } - }, - "ajv-formats": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/ajv-formats/-/ajv-formats-2.1.1.tgz", - "integrity": "sha512-Wx0Kx52hxE7C18hkMEggYlEifqWZtYaRgouJor+WMdPnQyEK13vgEWyVNup7SoeeoLMsr4kf5h6dOW11I15MUA==", - "requires": { - "ajv": "^8.0.0" - }, - "dependencies": { - "ajv": { - "version": "8.18.0", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.18.0.tgz", - "integrity": "sha512-PlXPeEWMXMZ7sPYOHqmDyCJzcfNrUr3fGNKtezX14ykXOEIvyK81d+qydx89KY5O71FKMPaQ2vBfBFI5NHR63A==", - "requires": { - "fast-deep-equal": "^3.1.3", - "fast-uri": "^3.0.1", - "json-schema-traverse": "^1.0.0", - "require-from-string": "^2.0.2" - } - }, - "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==" - } - } - }, - "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 - }, - "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, - "requires": { - "color-convert": "^2.0.1" - } - }, - "anymatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.2.tgz", - "integrity": "sha512-P43ePfOAIupkguHUycrc4qJ9kz8ZiuOUijaETwX7THt0Y/GNK7v0aa8rY816xWjZ7rJdA5XdMcpVFTKMq+RvWg==", - "requires": { - "normalize-path": "^3.0.0", - "picomatch": "^2.0.4" - } - }, - "argparse": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", - "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", - "dev": true - }, - "array-buffer-byte-length": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/array-buffer-byte-length/-/array-buffer-byte-length-1.0.2.tgz", - "integrity": "sha512-LHE+8BuR7RYGDKvnrmcuSq3tDcKv9OFEXQt/HpbZhY7V6h0zlUXutnAD82GiFx9rdieCMjkvtcsPqBwgUl1Iiw==", - "dev": true, - "requires": { - "call-bound": "^1.0.3", - "is-array-buffer": "^3.0.5" - } - }, - "array-flatten": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", - "integrity": "sha1-ml9pkFGx5wczKPKgCJaLZOopVdI=" - }, - "array-includes": { - "version": "3.1.9", - "resolved": "https://registry.npmjs.org/array-includes/-/array-includes-3.1.9.tgz", - "integrity": "sha512-FmeCCAenzH0KH381SPT5FZmiA/TmpndpcaShhfgEN9eCVjnFBqq3l1xrI42y8+PPLI6hypzou4GXw00WHmPBLQ==", - "dev": true, - "requires": { - "call-bind": "^1.0.8", - "call-bound": "^1.0.4", - "define-properties": "^1.2.1", - "es-abstract": "^1.24.0", - "es-object-atoms": "^1.1.1", - "get-intrinsic": "^1.3.0", - "is-string": "^1.1.1", - "math-intrinsics": "^1.1.0" - } - }, - "array.prototype.findlast": { - "version": "1.2.5", - "resolved": "https://registry.npmjs.org/array.prototype.findlast/-/array.prototype.findlast-1.2.5.tgz", - "integrity": "sha512-CVvd6FHg1Z3POpBLxO6E6zr+rSKEQ9L6rZHAaY7lLfhKsWYUBBOuMs0e9o24oopj6H+geRCX0YJ+TJLBK2eHyQ==", - "dev": true, - "requires": { - "call-bind": "^1.0.7", - "define-properties": "^1.2.1", - "es-abstract": "^1.23.2", - "es-errors": "^1.3.0", - "es-object-atoms": "^1.0.0", - "es-shim-unscopables": "^1.0.2" - } - }, - "array.prototype.findlastindex": { - "version": "1.2.6", - "resolved": "https://registry.npmjs.org/array.prototype.findlastindex/-/array.prototype.findlastindex-1.2.6.tgz", - "integrity": "sha512-F/TKATkzseUExPlfvmwQKGITM3DGTK+vkAsCZoDc5daVygbJBnjEUCbgkAvVFsgfXfX4YIqZ/27G3k3tdXrTxQ==", - "dev": true, - "requires": { - "call-bind": "^1.0.8", - "call-bound": "^1.0.4", - "define-properties": "^1.2.1", - "es-abstract": "^1.23.9", - "es-errors": "^1.3.0", - "es-object-atoms": "^1.1.1", - "es-shim-unscopables": "^1.1.0" - } - }, - "array.prototype.flat": { - "version": "1.3.3", - "resolved": "https://registry.npmjs.org/array.prototype.flat/-/array.prototype.flat-1.3.3.tgz", - "integrity": "sha512-rwG/ja1neyLqCuGZ5YYrznA62D4mZXg0i1cIskIUKSiqF3Cje9/wXAls9B9s1Wa2fomMsIv8czB8jZcPmxCXFg==", - "dev": true, - "requires": { - "call-bind": "^1.0.8", - "define-properties": "^1.2.1", - "es-abstract": "^1.23.5", - "es-shim-unscopables": "^1.0.2" - } - }, - "array.prototype.flatmap": { - "version": "1.3.3", - "resolved": "https://registry.npmjs.org/array.prototype.flatmap/-/array.prototype.flatmap-1.3.3.tgz", - "integrity": "sha512-Y7Wt51eKJSyi80hFrJCePGGNo5ktJCslFuboqJsbf57CCPcm5zztluPlc4/aD8sWsKvlwatezpV4U1efk8kpjg==", - "dev": true, - "requires": { - "call-bind": "^1.0.8", - "define-properties": "^1.2.1", - "es-abstract": "^1.23.5", - "es-shim-unscopables": "^1.0.2" - } - }, - "array.prototype.tosorted": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/array.prototype.tosorted/-/array.prototype.tosorted-1.1.4.tgz", - "integrity": "sha512-p6Fx8B7b7ZhL/gmUsAy0D15WhvDccw3mnGNbZpi3pmeJdxtWsj2jEaI4Y6oo3XiHfzuSgPwKc04MYt6KgvC/wA==", - "dev": true, - "requires": { - "call-bind": "^1.0.7", - "define-properties": "^1.2.1", - "es-abstract": "^1.23.3", - "es-errors": "^1.3.0", - "es-shim-unscopables": "^1.0.2" - } - }, - "arraybuffer.prototype.slice": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/arraybuffer.prototype.slice/-/arraybuffer.prototype.slice-1.0.4.tgz", - "integrity": "sha512-BNoCY6SXXPQ7gF2opIP4GBE+Xw7U+pHMYKuzjgCN3GwiaIR09UUeKfheyIry77QtrCBlC0KK0q5/TER/tYh3PQ==", - "dev": true, - "requires": { - "array-buffer-byte-length": "^1.0.1", - "call-bind": "^1.0.8", - "define-properties": "^1.2.1", - "es-abstract": "^1.23.5", - "es-errors": "^1.3.0", - "get-intrinsic": "^1.2.6", - "is-array-buffer": "^3.0.4" - } - }, - "async-function": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/async-function/-/async-function-1.0.0.tgz", - "integrity": "sha512-hsU18Ae8CDTR6Kgu9DYf0EbCr/a5iGL0rytQDobUcdpYOKokk8LEjVphnXkDkgpi0wYVsqrXuP0bZxJaTqdgoA==", - "dev": true - }, - "available-typed-arrays": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.7.tgz", - "integrity": "sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ==", - "requires": { - "possible-typed-array-names": "^1.0.0" - } - }, - "balanced-match": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", - "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=" - }, - "binary-extensions": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz", - "integrity": "sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==" - }, - "body-parser": { - "version": "1.20.6", - "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.6.tgz", - "integrity": "sha512-p5tAzS57i5MV9fZFDj9LeIiTZEufbSe2eDozP+ElheSUq1m74CRq1jI4mYNDdVs9vQztXFLuk/Gd6BWTdwRJ5g==", - "requires": { - "bytes": "~3.1.2", - "content-type": "~1.0.5", - "debug": "2.6.9", - "depd": "2.0.0", - "destroy": "~1.2.0", - "http-errors": "~2.0.1", - "iconv-lite": "~0.4.24", - "on-finished": "~2.4.1", - "qs": "~6.15.1", - "raw-body": "~2.5.3", - "type-is": "~1.6.18", - "unpipe": "~1.0.0" - }, - "dependencies": { - "http-errors": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.1.tgz", - "integrity": "sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ==", - "requires": { - "depd": "~2.0.0", - "inherits": "~2.0.4", - "setprototypeof": "~1.2.0", - "statuses": "~2.0.2", - "toidentifier": "~1.0.1" - } - }, - "statuses": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.2.tgz", - "integrity": "sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==" - } - } - }, - "brace-expansion": { - "version": "1.1.21", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.21.tgz", - "integrity": "sha512-9zeA+KLZNNzglF2TPKRQEDyx6Yby7daAkuy8MiPzpXPsYDWi/DRM8jmwUDxokQjYqBpv5DgPiwD4h4ZZSy1Ujw==", - "requires": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" - } - }, - "braces": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", - "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", - "requires": { - "fill-range": "^7.1.1" - } - }, - "builtins": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/builtins/-/builtins-5.1.0.tgz", - "integrity": "sha512-SW9lzGTLvWTP1AY8xeAMZimqDrIaSdLQUcVr9DMef51niJ022Ri87SwRRKYm4A6iHfkPaiVUu/Duw2Wc4J7kKg==", - "dev": true, - "requires": { - "semver": "^7.0.0" - }, - "dependencies": { - "semver": { - "version": "7.7.2", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.2.tgz", - "integrity": "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==", - "dev": true - } - } - }, - "bytes": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", - "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==" - }, - "call-bind": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.8.tgz", - "integrity": "sha512-oKlSFMcMwpUg2ednkhQ454wfWiU/ul3CkJe/PEHcTKuiX6RpbehUiFMXu13HalGZxfUwCQzZG747YXBn1im9ww==", - "requires": { - "call-bind-apply-helpers": "^1.0.0", - "es-define-property": "^1.0.0", - "get-intrinsic": "^1.2.4", - "set-function-length": "^1.2.2" - } - }, - "call-bind-apply-helpers": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", - "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", - "requires": { - "es-errors": "^1.3.0", - "function-bind": "^1.1.2" - } - }, - "call-bound": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz", - "integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==", - "requires": { - "call-bind-apply-helpers": "^1.0.2", - "get-intrinsic": "^1.3.0" - } - }, - "callsites": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", - "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", - "dev": true - }, - "chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "dependencies": { - "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 - }, - "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, - "requires": { - "has-flag": "^4.0.0" - } - } - } - }, - "chokidar": { - "version": "3.5.3", - "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.3.tgz", - "integrity": "sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw==", - "requires": { - "anymatch": "~3.1.2", - "braces": "~3.0.2", - "fsevents": "~2.3.2", - "glob-parent": "~5.1.2", - "is-binary-path": "~2.1.0", - "is-glob": "~4.0.1", - "normalize-path": "~3.0.0", - "readdirp": "~3.6.0" - } - }, - "cloudevents": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/cloudevents/-/cloudevents-6.0.1.tgz", - "integrity": "sha512-6Y2EOQKsEEciSeIPL8ENob2LcFSt/r0iftTefl3IanSVr2LyvXfbEdD5+pvufVDFdf6OVijvtl7UFx/0voMOsw==", - "requires": { - "ajv": "^8.6.3", - "ajv-formats": "^2.1.1", - "util": "^0.12.4", - "uuid": "^8.3.2" - }, - "dependencies": { - "ajv": { - "version": "8.18.0", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.18.0.tgz", - "integrity": "sha512-PlXPeEWMXMZ7sPYOHqmDyCJzcfNrUr3fGNKtezX14ykXOEIvyK81d+qydx89KY5O71FKMPaQ2vBfBFI5NHR63A==", - "requires": { - "fast-deep-equal": "^3.1.3", - "fast-uri": "^3.0.1", - "json-schema-traverse": "^1.0.0", - "require-from-string": "^2.0.2" - } - }, - "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==" - } - } - }, - "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, - "requires": { - "color-name": "~1.1.4" - } - }, - "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 - }, - "concat-map": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", - "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=" - }, - "content-disposition": { - "version": "0.5.4", - "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz", - "integrity": "sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==", - "requires": { - "safe-buffer": "5.2.1" - } - }, - "content-type": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz", - "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==" - }, - "cookie": { - "version": "0.7.1", - "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.1.tgz", - "integrity": "sha512-6DnInpx7SJ2AK3+CTUE/ZM0vWTUboZCegxhC2xiIydHR9jNuTAASBrfEpHhiGOZw/nX51bHt6YQl8jsGo4y/0w==" - }, - "cookie-signature": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", - "integrity": "sha1-4wOogrNCzD7oylE6eZmXNNqzriw=" - }, - "cross-spawn": { - "version": "7.0.6", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", - "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", - "dev": true, - "requires": { - "path-key": "^3.1.0", - "shebang-command": "^2.0.0", - "which": "^2.0.1" - } - }, - "data-view-buffer": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/data-view-buffer/-/data-view-buffer-1.0.2.tgz", - "integrity": "sha512-EmKO5V3OLXh1rtK2wgXRansaK1/mtVdTUEiEI0W8RkvgT05kfxaH29PliLnpLP73yYO6142Q72QNa8Wx/A5CqQ==", - "dev": true, - "requires": { - "call-bound": "^1.0.3", - "es-errors": "^1.3.0", - "is-data-view": "^1.0.2" - } - }, - "data-view-byte-length": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/data-view-byte-length/-/data-view-byte-length-1.0.2.tgz", - "integrity": "sha512-tuhGbE6CfTM9+5ANGf+oQb72Ky/0+s3xKUpHvShfiz2RxMFgFPjsXuRLBVMtvMs15awe45SRb83D6wH4ew6wlQ==", - "dev": true, - "requires": { - "call-bound": "^1.0.3", - "es-errors": "^1.3.0", - "is-data-view": "^1.0.2" - } - }, - "data-view-byte-offset": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/data-view-byte-offset/-/data-view-byte-offset-1.0.1.tgz", - "integrity": "sha512-BS8PfmtDGnrgYdOonGZQdLZslWIeCGFP9tpan0hi1Co2Zr2NKADsvGYA8XxuG/4UWgJ6Cjtv+YJnB6MM69QGlQ==", - "dev": true, - "requires": { - "call-bound": "^1.0.2", - "es-errors": "^1.3.0", - "is-data-view": "^1.0.1" - } - }, - "debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "requires": { - "ms": "2.0.0" - } - }, - "deep-is": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", - "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", - "dev": true - }, - "define-data-property": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz", - "integrity": "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==", - "requires": { - "es-define-property": "^1.0.0", - "es-errors": "^1.3.0", - "gopd": "^1.0.1" - } - }, - "define-properties": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.2.1.tgz", - "integrity": "sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg==", - "dev": true, - "requires": { - "define-data-property": "^1.0.1", - "has-property-descriptors": "^1.0.0", - "object-keys": "^1.1.1" - } - }, - "depd": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", - "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==" - }, - "destroy": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz", - "integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==" - }, - "doctrine": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", - "integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==", - "dev": true, - "requires": { - "esutils": "^2.0.2" - } - }, - "dunder-proto": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", - "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", - "requires": { - "call-bind-apply-helpers": "^1.0.1", - "es-errors": "^1.3.0", - "gopd": "^1.2.0" - } - }, - "ee-first": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", - "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==" - }, - "encodeurl": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", - "integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==" - }, - "error-ex": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", - "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==", - "dev": true, - "requires": { - "is-arrayish": "^0.2.1" - } - }, - "es-abstract": { - "version": "1.24.0", - "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.24.0.tgz", - "integrity": "sha512-WSzPgsdLtTcQwm4CROfS5ju2Wa1QQcVeT37jFjYzdFz1r9ahadC8B8/a4qxJxM+09F18iumCdRmlr96ZYkQvEg==", - "dev": true, - "requires": { - "array-buffer-byte-length": "^1.0.2", - "arraybuffer.prototype.slice": "^1.0.4", - "available-typed-arrays": "^1.0.7", - "call-bind": "^1.0.8", - "call-bound": "^1.0.4", - "data-view-buffer": "^1.0.2", - "data-view-byte-length": "^1.0.2", - "data-view-byte-offset": "^1.0.1", - "es-define-property": "^1.0.1", - "es-errors": "^1.3.0", - "es-object-atoms": "^1.1.1", - "es-set-tostringtag": "^2.1.0", - "es-to-primitive": "^1.3.0", - "function.prototype.name": "^1.1.8", - "get-intrinsic": "^1.3.0", - "get-proto": "^1.0.1", - "get-symbol-description": "^1.1.0", - "globalthis": "^1.0.4", - "gopd": "^1.2.0", - "has-property-descriptors": "^1.0.2", - "has-proto": "^1.2.0", - "has-symbols": "^1.1.0", - "hasown": "^2.0.2", - "internal-slot": "^1.1.0", - "is-array-buffer": "^3.0.5", - "is-callable": "^1.2.7", - "is-data-view": "^1.0.2", - "is-negative-zero": "^2.0.3", - "is-regex": "^1.2.1", - "is-set": "^2.0.3", - "is-shared-array-buffer": "^1.0.4", - "is-string": "^1.1.1", - "is-typed-array": "^1.1.15", - "is-weakref": "^1.1.1", - "math-intrinsics": "^1.1.0", - "object-inspect": "^1.13.4", - "object-keys": "^1.1.1", - "object.assign": "^4.1.7", - "own-keys": "^1.0.1", - "regexp.prototype.flags": "^1.5.4", - "safe-array-concat": "^1.1.3", - "safe-push-apply": "^1.0.0", - "safe-regex-test": "^1.1.0", - "set-proto": "^1.0.0", - "stop-iteration-iterator": "^1.1.0", - "string.prototype.trim": "^1.2.10", - "string.prototype.trimend": "^1.0.9", - "string.prototype.trimstart": "^1.0.8", - "typed-array-buffer": "^1.0.3", - "typed-array-byte-length": "^1.0.3", - "typed-array-byte-offset": "^1.0.4", - "typed-array-length": "^1.0.7", - "unbox-primitive": "^1.1.0", - "which-typed-array": "^1.1.19" - } - }, - "es-define-property": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", - "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==" - }, - "es-errors": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", - "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==" - }, - "es-iterator-helpers": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/es-iterator-helpers/-/es-iterator-helpers-1.2.1.tgz", - "integrity": "sha512-uDn+FE1yrDzyC0pCo961B2IHbdM8y/ACZsKD4dG6WqrjV53BADjwa7D+1aom2rsNVfLyDgU/eigvlJGJ08OQ4w==", - "dev": true, - "requires": { - "call-bind": "^1.0.8", - "call-bound": "^1.0.3", - "define-properties": "^1.2.1", - "es-abstract": "^1.23.6", - "es-errors": "^1.3.0", - "es-set-tostringtag": "^2.0.3", - "function-bind": "^1.1.2", - "get-intrinsic": "^1.2.6", - "globalthis": "^1.0.4", - "gopd": "^1.2.0", - "has-property-descriptors": "^1.0.2", - "has-proto": "^1.2.0", - "has-symbols": "^1.1.0", - "internal-slot": "^1.1.0", - "iterator.prototype": "^1.1.4", - "safe-array-concat": "^1.1.3" - } - }, - "es-object-atoms": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz", - "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==", - "requires": { - "es-errors": "^1.3.0" - } - }, - "es-set-tostringtag": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz", - "integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==", - "dev": true, - "requires": { - "es-errors": "^1.3.0", - "get-intrinsic": "^1.2.6", - "has-tostringtag": "^1.0.2", - "hasown": "^2.0.2" - } - }, - "es-shim-unscopables": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/es-shim-unscopables/-/es-shim-unscopables-1.1.0.tgz", - "integrity": "sha512-d9T8ucsEhh8Bi1woXCf+TIKDIROLG5WCkxg8geBCbvk22kzwC5G2OnXVMO6FUsvQlgUUXQ2itephWDLqDzbeCw==", - "dev": true, - "requires": { - "hasown": "^2.0.2" - } - }, - "es-to-primitive": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.3.0.tgz", - "integrity": "sha512-w+5mJ3GuFL+NjVtJlvydShqE1eN3h3PbI7/5LAsYJP/2qtuMXjfL2LpHSRqo4b4eSF5K/DH1JXKUAHSB2UW50g==", - "dev": true, - "requires": { - "is-callable": "^1.2.7", - "is-date-object": "^1.0.5", - "is-symbol": "^1.0.4" - } - }, - "escape-html": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", - "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==" - }, - "escape-string-regexp": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", - "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", - "dev": true - }, - "eslint": { - "version": "8.57.1", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.57.1.tgz", - "integrity": "sha512-ypowyDxpVSYpkXr9WPv2PAZCtNip1Mv5KTW0SCurXv/9iOpcrH9PaqUElksqEB6pChqHGDRCFTyrZlGhnLNGiA==", - "dev": true, - "requires": { - "@eslint-community/eslint-utils": "^4.2.0", - "@eslint-community/regexpp": "^4.6.1", - "@eslint/eslintrc": "^2.1.4", - "@eslint/js": "8.57.1", - "@humanwhocodes/config-array": "^0.13.0", - "@humanwhocodes/module-importer": "^1.0.1", - "@nodelib/fs.walk": "^1.2.8", - "@ungap/structured-clone": "^1.2.0", - "ajv": "^6.12.4", - "chalk": "^4.0.0", - "cross-spawn": "^7.0.2", - "debug": "^4.3.2", - "doctrine": "^3.0.0", - "escape-string-regexp": "^4.0.0", - "eslint-scope": "^7.2.2", - "eslint-visitor-keys": "^3.4.3", - "espree": "^9.6.1", - "esquery": "^1.4.2", - "esutils": "^2.0.2", - "fast-deep-equal": "^3.1.3", - "file-entry-cache": "^6.0.1", - "find-up": "^5.0.0", - "glob-parent": "^6.0.2", - "globals": "^13.19.0", - "graphemer": "^1.4.0", - "ignore": "^5.2.0", - "imurmurhash": "^0.1.4", - "is-glob": "^4.0.0", - "is-path-inside": "^3.0.3", - "js-yaml": "^4.1.0", - "json-stable-stringify-without-jsonify": "^1.0.1", - "levn": "^0.4.1", - "lodash.merge": "^4.6.2", - "minimatch": "^3.1.2", - "natural-compare": "^1.4.0", - "optionator": "^0.9.3", - "strip-ansi": "^6.0.1", - "text-table": "^0.2.0" - }, - "dependencies": { - "debug": { - "version": "4.4.1", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.1.tgz", - "integrity": "sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==", - "dev": true, - "requires": { - "ms": "^2.1.3" - } - }, - "glob-parent": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", - "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", - "dev": true, - "requires": { - "is-glob": "^4.0.3" - } - }, - "ms": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", - "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", - "dev": true - } - } - }, - "eslint-config-standard": { - "version": "17.1.0", - "resolved": "https://registry.npmjs.org/eslint-config-standard/-/eslint-config-standard-17.1.0.tgz", - "integrity": "sha512-IwHwmaBNtDK4zDHQukFDW5u/aTb8+meQWZvNFWkiGmbWjD6bqyuSSBxxXKkCftCUzc1zwCH2m/baCNDLGmuO5Q==", - "dev": true, - "requires": {} - }, - "eslint-config-standard-jsx": { - "version": "11.0.0", - "resolved": "https://registry.npmjs.org/eslint-config-standard-jsx/-/eslint-config-standard-jsx-11.0.0.tgz", - "integrity": "sha512-+1EV/R0JxEK1L0NGolAr8Iktm3Rgotx3BKwgaX+eAuSX8D952LULKtjgZD3F+e6SvibONnhLwoTi9DPxN5LvvQ==", - "dev": true, - "requires": {} - }, - "eslint-import-resolver-node": { - "version": "0.3.9", - "resolved": "https://registry.npmjs.org/eslint-import-resolver-node/-/eslint-import-resolver-node-0.3.9.tgz", - "integrity": "sha512-WFj2isz22JahUv+B788TlO3N6zL3nNJGU8CcZbPZvVEkBPaJdCV4vy5wyghty5ROFbCRnm132v8BScu5/1BQ8g==", - "dev": true, - "requires": { - "debug": "^3.2.7", - "is-core-module": "^2.13.0", - "resolve": "^1.22.4" - }, - "dependencies": { - "debug": { - "version": "3.2.7", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", - "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", - "dev": true, - "requires": { - "ms": "^2.1.1" - } - }, - "ms": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", - "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", - "dev": true - } - } - }, - "eslint-module-utils": { - "version": "2.12.1", - "resolved": "https://registry.npmjs.org/eslint-module-utils/-/eslint-module-utils-2.12.1.tgz", - "integrity": "sha512-L8jSWTze7K2mTg0vos/RuLRS5soomksDPoJLXIslC7c8Wmut3bx7CPpJijDcBZtxQ5lrbUdM+s0OlNbz0DCDNw==", - "dev": true, - "requires": { - "debug": "^3.2.7" - }, - "dependencies": { - "debug": { - "version": "3.2.7", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", - "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", - "dev": true, - "requires": { - "ms": "^2.1.1" - } - }, - "ms": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", - "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", - "dev": true - } - } - }, - "eslint-plugin-es": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/eslint-plugin-es/-/eslint-plugin-es-4.1.0.tgz", - "integrity": "sha512-GILhQTnjYE2WorX5Jyi5i4dz5ALWxBIdQECVQavL6s7cI76IZTDWleTHkxz/QT3kvcs2QlGHvKLYsSlPOlPXnQ==", - "dev": true, - "requires": { - "eslint-utils": "^2.0.0", - "regexpp": "^3.0.0" - }, - "dependencies": { - "eslint-utils": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-2.1.0.tgz", - "integrity": "sha512-w94dQYoauyvlDc43XnGB8lU3Zt713vNChgt4EWwhXAP2XkBvndfxF0AgIqKOOasjPIPzj9JqgwkwbCYD0/V3Zg==", - "dev": true, - "requires": { - "eslint-visitor-keys": "^1.1.0" - } - }, - "eslint-visitor-keys": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-1.3.0.tgz", - "integrity": "sha512-6J72N8UNa462wa/KFODt/PJ3IU60SDpC3QXC1Hjc1BXXpfL2C9R5+AU7jhe0F6GREqVMh4Juu+NY7xn+6dipUQ==", - "dev": true - } - } - }, - "eslint-plugin-import": { - "version": "2.32.0", - "resolved": "https://registry.npmjs.org/eslint-plugin-import/-/eslint-plugin-import-2.32.0.tgz", - "integrity": "sha512-whOE1HFo/qJDyX4SnXzP4N6zOWn79WhnCUY/iDR0mPfQZO8wcYE4JClzI2oZrhBnnMUCBCHZhO6VQyoBU95mZA==", - "dev": true, - "requires": { - "@rtsao/scc": "^1.1.0", - "array-includes": "^3.1.9", - "array.prototype.findlastindex": "^1.2.6", - "array.prototype.flat": "^1.3.3", - "array.prototype.flatmap": "^1.3.3", - "debug": "^3.2.7", - "doctrine": "^2.1.0", - "eslint-import-resolver-node": "^0.3.9", - "eslint-module-utils": "^2.12.1", - "hasown": "^2.0.2", - "is-core-module": "^2.16.1", - "is-glob": "^4.0.3", - "minimatch": "^3.1.2", - "object.fromentries": "^2.0.8", - "object.groupby": "^1.0.3", - "object.values": "^1.2.1", - "semver": "^6.3.1", - "string.prototype.trimend": "^1.0.9", - "tsconfig-paths": "^3.15.0" - }, - "dependencies": { - "debug": { - "version": "3.2.7", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", - "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", - "dev": true, - "requires": { - "ms": "^2.1.1" - } - }, - "doctrine": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-2.1.0.tgz", - "integrity": "sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==", - "dev": true, - "requires": { - "esutils": "^2.0.2" - } - }, - "ms": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", - "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", - "dev": true - }, - "semver": { - "version": "6.3.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", - "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", - "dev": true - } - } - }, - "eslint-plugin-n": { - "version": "15.7.0", - "resolved": "https://registry.npmjs.org/eslint-plugin-n/-/eslint-plugin-n-15.7.0.tgz", - "integrity": "sha512-jDex9s7D/Qial8AGVIHq4W7NswpUD5DPDL2RH8Lzd9EloWUuvUkHfv4FRLMipH5q2UtyurorBkPeNi1wVWNh3Q==", - "dev": true, - "requires": { - "builtins": "^5.0.1", - "eslint-plugin-es": "^4.1.0", - "eslint-utils": "^3.0.0", - "ignore": "^5.1.1", - "is-core-module": "^2.11.0", - "minimatch": "^3.1.2", - "resolve": "^1.22.1", - "semver": "^7.3.8" - }, - "dependencies": { - "semver": { - "version": "7.7.2", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.2.tgz", - "integrity": "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==", - "dev": true - } - } - }, - "eslint-plugin-promise": { - "version": "6.6.0", - "resolved": "https://registry.npmjs.org/eslint-plugin-promise/-/eslint-plugin-promise-6.6.0.tgz", - "integrity": "sha512-57Zzfw8G6+Gq7axm2Pdo3gW/Rx3h9Yywgn61uE/3elTCOePEHVrn2i5CdfBwA1BLK0Q0WqctICIUSqXZW/VprQ==", - "dev": true, - "requires": {} - }, - "eslint-plugin-react": { - "version": "7.37.5", - "resolved": "https://registry.npmjs.org/eslint-plugin-react/-/eslint-plugin-react-7.37.5.tgz", - "integrity": "sha512-Qteup0SqU15kdocexFNAJMvCJEfa2xUKNV4CC1xsVMrIIqEy3SQ/rqyxCWNzfrd3/ldy6HMlD2e0JDVpDg2qIA==", - "dev": true, - "requires": { - "array-includes": "^3.1.8", - "array.prototype.findlast": "^1.2.5", - "array.prototype.flatmap": "^1.3.3", - "array.prototype.tosorted": "^1.1.4", - "doctrine": "^2.1.0", - "es-iterator-helpers": "^1.2.1", - "estraverse": "^5.3.0", - "hasown": "^2.0.2", - "jsx-ast-utils": "^2.4.1 || ^3.0.0", - "minimatch": "^3.1.2", - "object.entries": "^1.1.9", - "object.fromentries": "^2.0.8", - "object.values": "^1.2.1", - "prop-types": "^15.8.1", - "resolve": "^2.0.0-next.5", - "semver": "^6.3.1", - "string.prototype.matchall": "^4.0.12", - "string.prototype.repeat": "^1.0.0" - }, - "dependencies": { - "doctrine": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-2.1.0.tgz", - "integrity": "sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==", - "dev": true, - "requires": { - "esutils": "^2.0.2" - } - }, - "resolve": { - "version": "2.0.0-next.5", - "resolved": "https://registry.npmjs.org/resolve/-/resolve-2.0.0-next.5.tgz", - "integrity": "sha512-U7WjGVG9sH8tvjW5SmGbQuui75FiyjAX72HX15DwBBwF9dNiQZRQAg9nnPhYy+TUnE0+VcrttuvNI8oSxZcocA==", - "dev": true, - "requires": { - "is-core-module": "^2.13.0", - "path-parse": "^1.0.7", - "supports-preserve-symlinks-flag": "^1.0.0" - } - }, - "semver": { - "version": "6.3.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", - "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", - "dev": true - } - } - }, - "eslint-scope": { - "version": "7.2.2", - "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.2.2.tgz", - "integrity": "sha512-dOt21O7lTMhDM+X9mB4GX+DZrZtCUJPL/wlcTqxyrx5IvO0IYtILdtrQGQp+8n5S0gwSVmOf9NQrjMOgfQZlIg==", - "dev": true, - "requires": { - "esrecurse": "^4.3.0", - "estraverse": "^5.2.0" - } - }, - "eslint-utils": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-3.0.0.tgz", - "integrity": "sha512-uuQC43IGctw68pJA1RgbQS8/NP7rch6Cwd4j3ZBtgo4/8Flj4eGE7ZYSZRN3iq5pVUv6GPdW5Z1RFleo84uLDA==", - "dev": true, - "requires": { - "eslint-visitor-keys": "^2.0.0" - }, - "dependencies": { - "eslint-visitor-keys": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-2.1.0.tgz", - "integrity": "sha512-0rSmRBzXgDzIsD6mGdJgevzgezI534Cer5L/vyMX0kHzT/jiB43jRhd9YUlMGYLQy2zprNmoT8qasCGtY+QaKw==", - "dev": true - } - } - }, - "eslint-visitor-keys": { - "version": "3.4.3", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", - "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", - "dev": true - }, - "espree": { - "version": "9.6.1", - "resolved": "https://registry.npmjs.org/espree/-/espree-9.6.1.tgz", - "integrity": "sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ==", - "dev": true, - "requires": { - "acorn": "^8.9.0", - "acorn-jsx": "^5.3.2", - "eslint-visitor-keys": "^3.4.1" - } - }, - "esquery": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.6.0.tgz", - "integrity": "sha512-ca9pw9fomFcKPvFLXhBKUK90ZvGibiGOvRJNbjljY7s7uq/5YO4BOzcYtJqExdx99rF6aAcnRxHmcUHcz6sQsg==", - "dev": true, - "requires": { - "estraverse": "^5.1.0" - } - }, - "esrecurse": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", - "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", - "dev": true, - "requires": { - "estraverse": "^5.2.0" - } - }, - "estraverse": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", - "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", - "dev": true - }, - "esutils": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", - "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", - "dev": true - }, - "etag": { - "version": "1.8.1", - "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", - "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==" - }, - "express": { - "version": "4.22.3", - "resolved": "https://registry.npmjs.org/express/-/express-4.22.3.tgz", - "integrity": "sha512-Bdcs4+3qlpVlx2NRn6fgX2Ue2/gGRaPeawebgclM0ERSCqDpA+owF1fdPwjJUTAJWMTuAaxjDf+hzb0/4eKvvw==", - "requires": { - "accepts": "~1.3.8", - "array-flatten": "1.1.1", - "body-parser": "~1.20.5", - "content-disposition": "~0.5.4", - "content-type": "~1.0.4", - "cookie": "~0.7.1", - "cookie-signature": "~1.0.6", - "debug": "2.6.9", - "depd": "2.0.0", - "encodeurl": "~2.0.0", - "escape-html": "~1.0.3", - "etag": "~1.8.1", - "finalhandler": "~1.3.1", - "fresh": "~0.5.2", - "http-errors": "~2.0.0", - "merge-descriptors": "1.0.3", - "methods": "~1.1.2", - "on-finished": "~2.4.1", - "parseurl": "~1.3.3", - "path-to-regexp": "~0.1.13", - "proxy-addr": "~2.0.7", - "qs": "~6.16.0", - "range-parser": "~1.2.1", - "safe-buffer": "5.2.1", - "send": "~0.19.0", - "serve-static": "~1.16.2", - "setprototypeof": "1.2.0", - "statuses": "~2.0.1", - "type-is": "~1.6.18", - "utils-merge": "1.0.1", - "vary": "~1.1.2" - }, - "dependencies": { - "encodeurl": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz", - "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==" - }, - "qs": { - "version": "6.16.1", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.16.1.tgz", - "integrity": "sha512-5Px6J7rfHBAGUhNQ3es4YzcOdBBw0SDcVtThJml4RdnKw4tKs2epA0kPZ+K8X+WiHktT93RHVcQ/3ym5aTCgtg==", - "requires": { - "es-define-property": "^1.0.1", - "side-channel": "^1.1.1" - } - } - } - }, - "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==" - }, - "fast-json-stable-stringify": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", - "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", - "dev": true - }, - "fast-levenshtein": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", - "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", - "dev": true - }, - "fast-uri": { - "version": "3.1.7", - "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.7.tgz", - "integrity": "sha512-dOvZVzjdZdz7phd9v6jCbwxrBW3fK6n8Rc0CtdmM4bumzMnxywBYhuph6J819RRw/ku+rLbelwfMunktuzVVHg==" - }, - "fastq": { - "version": "1.19.1", - "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.19.1.tgz", - "integrity": "sha512-GwLTyxkCXjXbxqIhTsMI2Nui8huMPtnxg7krajPJAjnEG/iiOS7i+zCtWGZR9G0NBKbXKh6X9m9UIsYX/N6vvQ==", - "dev": true, - "requires": { - "reusify": "^1.0.4" - } - }, - "file-entry-cache": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz", - "integrity": "sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==", - "dev": true, - "requires": { - "flat-cache": "^3.0.4" - } - }, - "fill-range": { - "version": "7.1.1", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", - "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", - "requires": { - "to-regex-range": "^5.0.1" - } - }, - "finalhandler": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.3.1.tgz", - "integrity": "sha512-6BN9trH7bp3qvnrRyzsBz+g3lZxTNZTbVO2EV1CS0WIcDbawYVdYvGflME/9QP0h0pYlCDBCTjYa9nZzMDpyxQ==", - "requires": { - "debug": "2.6.9", - "encodeurl": "~2.0.0", - "escape-html": "~1.0.3", - "on-finished": "2.4.1", - "parseurl": "~1.3.3", - "statuses": "2.0.1", - "unpipe": "~1.0.0" - }, - "dependencies": { - "encodeurl": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz", - "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==" - } - } - }, - "find-up": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", - "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", - "dev": true, - "requires": { - "locate-path": "^6.0.0", - "path-exists": "^4.0.0" - } - }, - "flat-cache": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.2.0.tgz", - "integrity": "sha512-CYcENa+FtcUKLmhhqyctpclsq7QF38pKjZHsGNiSQF5r4FtoKDWabFDl3hzaEQMvT1LHEysw5twgLvpYYb4vbw==", - "dev": true, - "requires": { - "flatted": "^3.2.9", - "keyv": "^4.5.3", - "rimraf": "^3.0.2" - } - }, - "flatted": { - "version": "3.4.2", - "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.4.2.tgz", - "integrity": "sha512-PjDse7RzhcPkIJwy5t7KPWQSZ9cAbzQXcafsetQoD7sOJRQlGikNbx7yZp2OotDnJyrDcbyRq3Ttb18iYOqkxA==", - "dev": true - }, - "for-each": { - "version": "0.3.5", - "resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.5.tgz", - "integrity": "sha512-dKx12eRCVIzqCxFGplyFKJMPvLEWgmNtUrpTiJIR5u97zEhRG8ySrtboPHZXx7daLxQVrl643cTzbab2tkQjxg==", - "requires": { - "is-callable": "^1.2.7" - } - }, - "forwarded": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", - "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==" - }, - "fresh": { - "version": "0.5.2", - "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", - "integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==" - }, - "fs.realpath": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", - "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", - "dev": true - }, - "fsevents": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", - "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", - "optional": true - }, - "function-bind": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", - "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==" - }, - "function.prototype.name": { - "version": "1.1.8", - "resolved": "https://registry.npmjs.org/function.prototype.name/-/function.prototype.name-1.1.8.tgz", - "integrity": "sha512-e5iwyodOHhbMr/yNrc7fDYG4qlbIvI5gajyzPnb5TCwyhjApznQh1BMFou9b30SevY43gCJKXycoCBjMbsuW0Q==", - "dev": true, - "requires": { - "call-bind": "^1.0.8", - "call-bound": "^1.0.3", - "define-properties": "^1.2.1", - "functions-have-names": "^1.2.3", - "hasown": "^2.0.2", - "is-callable": "^1.2.7" - } - }, - "functions-have-names": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/functions-have-names/-/functions-have-names-1.2.3.tgz", - "integrity": "sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==", - "dev": true - }, - "get-intrinsic": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", - "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", - "requires": { - "call-bind-apply-helpers": "^1.0.2", - "es-define-property": "^1.0.1", - "es-errors": "^1.3.0", - "es-object-atoms": "^1.1.1", - "function-bind": "^1.1.2", - "get-proto": "^1.0.1", - "gopd": "^1.2.0", - "has-symbols": "^1.1.0", - "hasown": "^2.0.2", - "math-intrinsics": "^1.1.0" - } - }, - "get-proto": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", - "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", - "requires": { - "dunder-proto": "^1.0.1", - "es-object-atoms": "^1.0.0" - } - }, - "get-stdin": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/get-stdin/-/get-stdin-8.0.0.tgz", - "integrity": "sha512-sY22aA6xchAzprjyqmSEQv4UbAAzRN0L2dQB0NlN5acTTK9Don6nhoc3eAbUnpZiCANAMfd/+40kVdKfFygohg==", - "dev": true - }, - "get-symbol-description": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/get-symbol-description/-/get-symbol-description-1.1.0.tgz", - "integrity": "sha512-w9UMqWwJxHNOvoNzSJ2oPF5wvYcvP7jUvYzhp67yEhTi17ZDBBC1z9pTdGuzjD+EFIqLSYRweZjqfiPzQ06Ebg==", - "dev": true, - "requires": { - "call-bound": "^1.0.3", - "es-errors": "^1.3.0", - "get-intrinsic": "^1.2.6" - } - }, - "glob": { - "version": "7.2.3", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", - "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", - "dev": true, - "requires": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.1.1", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - } - }, - "glob-parent": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", - "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", - "requires": { - "is-glob": "^4.0.1" - } - }, - "globals": { - "version": "13.24.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-13.24.0.tgz", - "integrity": "sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ==", - "dev": true, - "requires": { - "type-fest": "^0.20.2" - } - }, - "globalthis": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/globalthis/-/globalthis-1.0.4.tgz", - "integrity": "sha512-DpLKbNU4WylpxJykQujfCcwYWiV/Jhm50Goo0wrVILAv5jOr9d+H+UR3PhSCD2rCCEIg0uc+G+muBTwD54JhDQ==", - "dev": true, - "requires": { - "define-properties": "^1.2.1", - "gopd": "^1.0.1" + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.3" + }, + "engines": { + "node": ">=10.13.0" } }, - "gopd": { + "node_modules/gopd": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", - "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==" - }, - "graceful-fs": { - "version": "4.2.11", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", - "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", - "dev": true - }, - "graphemer": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/graphemer/-/graphemer-1.4.0.tgz", - "integrity": "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==", - "dev": true - }, - "has-bigints": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/has-bigints/-/has-bigints-1.1.0.tgz", - "integrity": "sha512-R3pbpkcIqv2Pm3dUwgjclDRVmWpTJW2DcMzcIhEXEx1oh/CEMObMm3KLmRJOdvhM7o4uQBnwr8pzRK2sJWIqfg==", - "dev": true + "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } }, - "has-flag": { + "node_modules/has-flag": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", - "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=" + "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } }, - "has-property-descriptors": { + "node_modules/has-property-descriptors": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz", "integrity": "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==", - "requires": { + "license": "MIT", + "dependencies": { "es-define-property": "^1.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "has-proto": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.2.0.tgz", - "integrity": "sha512-KIL7eQPfHQRC8+XluaIw7BHUwwqL19bQn4hzNgdr+1wXoU0KKj6rufu47lhY7KbJR2C6T6+PfyN0Ea7wkSS+qQ==", - "dev": true, - "requires": { - "dunder-proto": "^1.0.0" - } - }, - "has-symbols": { + "node_modules/has-symbols": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", - "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==" + "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } }, - "has-tostringtag": { + "node_modules/has-tostringtag": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz", "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", - "requires": { + "license": "MIT", + "dependencies": { "has-symbols": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "hasown": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", - "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", - "requires": { + "node_modules/hashery": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/hashery/-/hashery-1.5.1.tgz", + "integrity": "sha512-iZyKG96/JwPz1N55vj2Ie2vXbhu440zfUfJvSwEqEbeLluk7NnapfGqa7LH0mOsnDxTF85Mx8/dyR6HfqcbmbQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "hookified": "^1.15.0" + }, + "engines": { + "node": ">=20" + } + }, + "node_modules/hasown": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.4.tgz", + "integrity": "sha512-T2UbfbBEF32wiepXIsMlTW9+dDYC6wMh/t/vYA4tuOMKqWz/n3vr1NFSxQiyP+zk2mXsoMA/i/7qV6LKut1t1A==", + "license": "MIT", + "dependencies": { "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" } }, - "http-errors": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz", - "integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==", - "requires": { - "depd": "2.0.0", - "inherits": "2.0.4", - "setprototypeof": "1.2.0", - "statuses": "2.0.1", - "toidentifier": "1.0.1" + "node_modules/hookified": { + "version": "1.15.1", + "resolved": "https://registry.npmjs.org/hookified/-/hookified-1.15.1.tgz", + "integrity": "sha512-MvG/clsADq1GPM2KGo2nyfaWVyn9naPiXrqIe4jYjXNZQt238kWyOGrsyc/DmRAQ+Re6yeo6yX/yoNCG5KAEVg==", + "dev": true, + "license": "MIT" + }, + "node_modules/http-errors": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.1.tgz", + "integrity": "sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ==", + "license": "MIT", + "dependencies": { + "depd": "~2.0.0", + "inherits": "~2.0.4", + "setprototypeof": "~1.2.0", + "statuses": "~2.0.2", + "toidentifier": "~1.0.1" + }, + "engines": { + "node": ">= 0.8" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" } }, - "iconv-lite": { - "version": "0.4.24", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", - "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", - "requires": { - "safer-buffer": ">= 2.1.2 < 3" + "node_modules/iconv-lite": { + "version": "0.7.3", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.7.3.tgz", + "integrity": "sha512-IKXpvIzjnC9XTAUbVBcMfGS0EPaIXtW6v+zr+RRp+hqULEpo0owZax6wyRwPOJbWbzjYspQwusTsfVr0ifh4uQ==", + "license": "MIT", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + }, + "engines": { + "node": ">=0.10.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" } }, - "ignore": { + "node_modules/ignore": { "version": "5.3.2", "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==", - "dev": true + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4" + } }, - "ignore-by-default": { + "node_modules/ignore-by-default": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/ignore-by-default/-/ignore-by-default-1.0.1.tgz", - "integrity": "sha1-SMptcvbGo68Aqa1K5odr44ieKwk=" - }, - "import-fresh": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.1.tgz", - "integrity": "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==", + "integrity": "sha512-Ius2VYcGNk7T90CppJqcIkS5ooHUZyIQK+ClZfMfMNFEF9VSE73Fq+906u/CWu92x4gzZMWOwfFYckPObzdEbA==", "dev": true, - "requires": { - "parent-module": "^1.0.0", - "resolve-from": "^4.0.0" - } + "license": "ISC" }, - "imurmurhash": { + "node_modules/imurmurhash": { "version": "0.1.4", "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", - "dev": true - }, - "inflight": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", - "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", "dev": true, - "requires": { - "once": "^1.3.0", - "wrappy": "1" + "license": "MIT", + "engines": { + "node": ">=0.8.19" } }, - "inherits": { + "node_modules/inherits": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" - }, - "internal-slot": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.1.0.tgz", - "integrity": "sha512-4gd7VpWNQNB4UKKCFFVcp1AVv+FMOgs9NKzjHKusc8jTMhd5eL1NqQqOpE0KzMds804/yHlglp3uxgluOqAPLw==", - "dev": true, - "requires": { - "es-errors": "^1.3.0", - "hasown": "^2.0.2", - "side-channel": "^1.1.0" - } + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "license": "ISC" }, - "ipaddr.js": { + "node_modules/ipaddr.js": { "version": "1.9.1", "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", - "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==" - }, - "is-arguments": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/is-arguments/-/is-arguments-1.1.1.tgz", - "integrity": "sha512-8Q7EARjzEnKpt/PCD7e1cgUS0a6X8u5tdSiMqXhojOdoV9TsMsiO+9VLC5vAmO8N7/GmXn7yjR8qnA6bVAEzfA==", - "requires": { - "call-bind": "^1.0.2", - "has-tostringtag": "^1.0.0" - } - }, - "is-array-buffer": { - "version": "3.0.5", - "resolved": "https://registry.npmjs.org/is-array-buffer/-/is-array-buffer-3.0.5.tgz", - "integrity": "sha512-DDfANUiiG2wC1qawP66qlTugJeL5HyzMpfr8lLK+jMQirGzNod0B12cFB/9q838Ru27sBwfw78/rdoU7RERz6A==", - "dev": true, - "requires": { - "call-bind": "^1.0.8", - "call-bound": "^1.0.3", - "get-intrinsic": "^1.2.6" - } - }, - "is-arrayish": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", - "integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==", - "dev": true - }, - "is-async-function": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/is-async-function/-/is-async-function-2.1.1.tgz", - "integrity": "sha512-9dgM/cZBnNvjzaMYHVoxxfPj2QXt22Ev7SuuPrs+xav0ukGB0S6d4ydZdEiM48kLx5kDV+QBPrpVnFyefL8kkQ==", - "dev": true, - "requires": { - "async-function": "^1.0.0", - "call-bound": "^1.0.3", - "get-proto": "^1.0.1", - "has-tostringtag": "^1.0.2", - "safe-regex-test": "^1.1.0" + "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==", + "license": "MIT", + "engines": { + "node": ">= 0.10" } }, - "is-bigint": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/is-bigint/-/is-bigint-1.1.0.tgz", - "integrity": "sha512-n4ZT37wG78iz03xPRKJrHTdZbe3IicyucEtdRsV5yglwc3GyUfbAfpSeD0FJ41NbUNSt5wbhqfp1fS+BgnvDFQ==", - "dev": true, - "requires": { - "has-bigints": "^1.0.2" + "node_modules/is-arguments": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/is-arguments/-/is-arguments-1.2.0.tgz", + "integrity": "sha512-7bVbi0huj/wrIAOzb8U1aszg9kdi3KN/CyU19CTI7tAoZYEZoL9yCDXpbXN+uPsuWnP02cyug1gleqq+TU+YCA==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "has-tostringtag": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "is-binary-path": { + "node_modules/is-binary-path": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", - "requires": { - "binary-extensions": "^2.0.0" - } - }, - "is-boolean-object": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/is-boolean-object/-/is-boolean-object-1.2.2.tgz", - "integrity": "sha512-wa56o2/ElJMYqjCjGkXri7it5FbebW5usLw/nPmCMs5DeZ7eziSYZhSmPRn0txqeW4LnAmQQU7FgqLpsEFKM4A==", "dev": true, - "requires": { - "call-bound": "^1.0.3", - "has-tostringtag": "^1.0.2" + "license": "MIT", + "dependencies": { + "binary-extensions": "^2.0.0" + }, + "engines": { + "node": ">=8" } }, - "is-callable": { + "node_modules/is-callable": { "version": "1.2.7", "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.7.tgz", - "integrity": "sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==" - }, - "is-core-module": { - "version": "2.16.1", - "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.16.1.tgz", - "integrity": "sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==", - "dev": true, - "requires": { - "hasown": "^2.0.2" - } - }, - "is-data-view": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/is-data-view/-/is-data-view-1.0.2.tgz", - "integrity": "sha512-RKtWF8pGmS87i2D6gqQu/l7EYRlVdfzemCJN/P3UOs//x1QE7mfhvzHIApBTRf7axvT6DMGwSwBXYCT0nfB9xw==", - "dev": true, - "requires": { - "call-bound": "^1.0.2", - "get-intrinsic": "^1.2.6", - "is-typed-array": "^1.1.13" - } - }, - "is-date-object": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.1.0.tgz", - "integrity": "sha512-PwwhEakHVKTdRNVOw+/Gyh0+MzlCl4R6qKvkhuvLtPMggI1WAHt9sOwZxQLSGpUaDnrdyDsomoRgNnCfKNSXXg==", - "dev": true, - "requires": { - "call-bound": "^1.0.2", - "has-tostringtag": "^1.0.2" + "integrity": "sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "is-extglob": { + "node_modules/is-extglob": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", - "integrity": "sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=" - }, - "is-finalizationregistry": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/is-finalizationregistry/-/is-finalizationregistry-1.1.1.tgz", - "integrity": "sha512-1pC6N8qWJbWoPtEjgcL2xyhQOP491EQjeUo3qTKcmV8YSDDJrOepfG8pcC7h/QgnQHYSv0mJ3Z/ZWxmatVrysg==", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", "dev": true, - "requires": { - "call-bound": "^1.0.3" + "license": "MIT", + "engines": { + "node": ">=0.10.0" } }, - "is-generator-function": { - "version": "1.0.10", - "resolved": "https://registry.npmjs.org/is-generator-function/-/is-generator-function-1.0.10.tgz", - "integrity": "sha512-jsEjy9l3yiXEQ+PsXdmBwEPcOxaXWLspKdplFUVI9vq1iZgIekeC0L167qeu86czQaxed3q/Uzuw0swL0irL8A==", - "requires": { - "has-tostringtag": "^1.0.0" + "node_modules/is-generator-function": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/is-generator-function/-/is-generator-function-1.1.2.tgz", + "integrity": "sha512-upqt1SkGkODW9tsGNG5mtXTXtECizwtS2kA161M+gJPc1xdb/Ax629af6YrTwcOeQHbewrPNlE5Dx7kzvXTizA==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.4", + "generator-function": "^2.0.0", + "get-proto": "^1.0.1", + "has-tostringtag": "^1.0.2", + "safe-regex-test": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "is-glob": { + "node_modules/is-glob": { "version": "4.0.3", "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", - "requires": { + "dev": true, + "license": "MIT", + "dependencies": { "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" } }, - "is-map": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/is-map/-/is-map-2.0.3.tgz", - "integrity": "sha512-1Qed0/Hr2m+YqxnM09CjA2d/i6YZNfF6R2oRAOj36eUdS6qIV/huPJNSEpKbupewFs+ZsJlxsjjPbc0/afW6Lw==", - "dev": true - }, - "is-negative-zero": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/is-negative-zero/-/is-negative-zero-2.0.3.tgz", - "integrity": "sha512-5KoIu2Ngpyek75jXodFvnafB6DJgr3u8uuK0LEZJjrU19DrMD3EVERaR8sjz8CCGgpZvxPl9SuE1GMVPFHx1mw==", - "dev": true - }, - "is-number": { + "node_modules/is-number": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", - "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==" - }, - "is-number-object": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/is-number-object/-/is-number-object-1.1.1.tgz", - "integrity": "sha512-lZhclumE1G6VYD8VHe35wFaIif+CTy5SJIi5+3y4psDgWu4wPDoBhF8NxUOinEc7pHgiTsT6MaBb92rKhhD+Xw==", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", "dev": true, - "requires": { - "call-bound": "^1.0.3", - "has-tostringtag": "^1.0.2" + "license": "MIT", + "engines": { + "node": ">=0.12.0" } }, - "is-path-inside": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-3.0.3.tgz", - "integrity": "sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==", - "dev": true + "node_modules/is-promise": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/is-promise/-/is-promise-4.0.0.tgz", + "integrity": "sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ==", + "license": "MIT" }, - "is-regex": { + "node_modules/is-regex": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.2.1.tgz", "integrity": "sha512-MjYsKHO5O7mCsmRGxWcLWheFqN9DJ/2TmngvjKXihe6efViPqc274+Fx/4fYj/r03+ESvBdTXK0V6tA3rgez1g==", - "dev": true, - "requires": { + "license": "MIT", + "dependencies": { "call-bound": "^1.0.2", "gopd": "^1.2.0", "has-tostringtag": "^1.0.2", "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "is-set": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/is-set/-/is-set-2.0.3.tgz", - "integrity": "sha512-iPAjerrse27/ygGLxw+EBR9agv9Y6uLeYVJMu+QNCoouJ1/1ri0mGrcWpfCqFZuzzx3WjtwxG098X+n4OuRkPg==", - "dev": true - }, - "is-shared-array-buffer": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/is-shared-array-buffer/-/is-shared-array-buffer-1.0.4.tgz", - "integrity": "sha512-ISWac8drv4ZGfwKl5slpHG9OwPNty4jOWPRIhBpxOoD+hqITiwuipOQ2bNthAzwA3B4fIjO4Nln74N0S9byq8A==", - "dev": true, - "requires": { - "call-bound": "^1.0.3" - } - }, - "is-string": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/is-string/-/is-string-1.1.1.tgz", - "integrity": "sha512-BtEeSsoaQjlSPBemMQIrY1MY0uM6vnS1g5fmufYOtnxLGUZM2178PKbhsk7Ffv58IX+ZtcvoGwccYsh0PglkAA==", - "dev": true, - "requires": { - "call-bound": "^1.0.3", - "has-tostringtag": "^1.0.2" - } - }, - "is-symbol": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.1.1.tgz", - "integrity": "sha512-9gGx6GTtCQM73BgmHQXfDmLtfjjTUDSyoxTCbp5WtoixAhfgsDirWIcVQ/IHpvI5Vgd5i/J5F7B9cN/WlVbC/w==", - "dev": true, - "requires": { - "call-bound": "^1.0.2", - "has-symbols": "^1.1.0", - "safe-regex-test": "^1.1.0" - } - }, - "is-typed-array": { + "node_modules/is-typed-array": { "version": "1.1.15", "resolved": "https://registry.npmjs.org/is-typed-array/-/is-typed-array-1.1.15.tgz", "integrity": "sha512-p3EcsicXjit7SaskXHs1hA91QxgTw46Fv6EFKKGS5DRFLD8yKnohjF3hxoju94b/OcMZoQukzpPpBE9uLVKzgQ==", - "requires": { + "license": "MIT", + "dependencies": { "which-typed-array": "^1.1.16" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "is-weakmap": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/is-weakmap/-/is-weakmap-2.0.2.tgz", - "integrity": "sha512-K5pXYOm9wqY1RgjpL3YTkF39tni1XajUIkawTLUo9EZEVUFga5gSQJF8nNS7ZwJQ02y+1YCNYcMh+HIf1ZqE+w==", - "dev": true - }, - "is-weakref": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/is-weakref/-/is-weakref-1.1.1.tgz", - "integrity": "sha512-6i9mGWSlqzNMEqpCp93KwRS1uUOodk2OJ6b+sq7ZPDSy2WuI5NFIxp/254TytR8ftefexkWn5xNiHUNpPOfSew==", - "dev": true, - "requires": { - "call-bound": "^1.0.3" - } - }, - "is-weakset": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/is-weakset/-/is-weakset-2.0.4.tgz", - "integrity": "sha512-mfcwb6IzQyOKTs84CQMrOwW4gQcaTOAWJ0zzJCl2WSPDrWk/OzDaImWFH3djXhb24g4eudZfLRozAvPGw4d9hQ==", - "dev": true, - "requires": { - "call-bound": "^1.0.3", - "get-intrinsic": "^1.2.6" - } - }, - "isarray": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.5.tgz", - "integrity": "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==", - "dev": true - }, - "isexe": { + "node_modules/isexe": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", - "dev": true - }, - "iterator.prototype": { - "version": "1.1.5", - "resolved": "https://registry.npmjs.org/iterator.prototype/-/iterator.prototype-1.1.5.tgz", - "integrity": "sha512-H0dkQoCa3b2VEeKQBOxFph+JAbcrQdE7KC0UkqwpLmv2EC4P41QXP+rqo9wYodACiG5/WM5s9oDApTU8utwj9g==", "dev": true, - "requires": { - "define-data-property": "^1.1.4", - "es-object-atoms": "^1.0.0", - "get-intrinsic": "^1.2.6", - "get-proto": "^1.0.0", - "has-symbols": "^1.1.0", - "set-function-name": "^2.0.2" - } + "license": "ISC" }, - "js-tokens": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", - "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", - "dev": true - }, - "js-yaml": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.1.tgz", - "integrity": "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==", - "dev": true, - "requires": { - "argparse": "^2.0.1" + "node_modules/json-bigint": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-bigint/-/json-bigint-1.0.0.tgz", + "integrity": "sha512-SiPv/8VpZuWbvLSMtTDU8hEfrZWg/mH/nV/b4o0CYbSxu1UIQPLdwKOCIyLQX+VIPO5vrLX3i8qtqFyhdPSUSQ==", + "license": "MIT", + "dependencies": { + "bignumber.js": "^9.0.0" } }, - "json-buffer": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", - "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==", - "dev": true - }, - "json-parse-better-errors": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/json-parse-better-errors/-/json-parse-better-errors-1.0.2.tgz", - "integrity": "sha512-mrqyZKfX5EhL7hvqcV6WG1yYjnjeuYDzDhhcAAUrq8Po85NBQBJP+ZDUT75qZQ98IkUoBqdkExkukOU7Ts2wrw==", - "dev": true - }, - "json-schema-traverse": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", - "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", - "dev": true + "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==", + "license": "MIT" }, - "json-stable-stringify-without-jsonify": { + "node_modules/json-stable-stringify-without-jsonify": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", - "dev": true - }, - "json5": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.2.tgz", - "integrity": "sha512-g1MWMLBiz8FKi1e4w0UyVL3w+iJceWAFBAaBnnGKOpNa5f8TLktkbre1+s6oICydWAm+HRUGTmI+//xv2hvXYA==", - "dev": true, - "requires": { - "minimist": "^1.2.0" - } - }, - "jsx-ast-utils": { - "version": "3.3.5", - "resolved": "https://registry.npmjs.org/jsx-ast-utils/-/jsx-ast-utils-3.3.5.tgz", - "integrity": "sha512-ZZow9HBI5O6EPgSJLUb8n2NKgmVWTwCvHGwFuJlMjvLFqlGG6pjirPhtdsseaLZjSibD8eegzmYpUZwoIlj2cQ==", "dev": true, - "requires": { - "array-includes": "^3.1.6", - "array.prototype.flat": "^1.3.1", - "object.assign": "^4.1.4", - "object.values": "^1.1.6" - } + "license": "MIT" }, - "keyv": { - "version": "4.5.4", - "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", - "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==", + "node_modules/keyv": { + "version": "5.6.0", + "resolved": "https://registry.npmjs.org/keyv/-/keyv-5.6.0.tgz", + "integrity": "sha512-CYDD3SOtsHtyXeEORYRx2qBtpDJFjRTGXUtmNEMGyzYOKj1TE3tycdlho7kA1Ufx9OYWZzg52QFBGALTirzDSw==", "dev": true, - "requires": { - "json-buffer": "3.0.1" + "license": "MIT", + "dependencies": { + "@keyv/serialize": "^1.1.1" } }, - "levn": { + "node_modules/levn": { "version": "0.4.1", "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", "dev": true, - "requires": { + "license": "MIT", + "dependencies": { "prelude-ls": "^1.2.1", "type-check": "~0.4.0" - } - }, - "load-json-file": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-5.3.0.tgz", - "integrity": "sha512-cJGP40Jc/VXUsp8/OrnyKyTZ1y6v/dphm3bioS+RrKXjK2BB6wHUd6JptZEFDGgGahMT+InnZO5i1Ei9mpC8Bw==", - "dev": true, - "requires": { - "graceful-fs": "^4.1.15", - "parse-json": "^4.0.0", - "pify": "^4.0.1", - "strip-bom": "^3.0.0", - "type-fest": "^0.3.0" }, - "dependencies": { - "type-fest": { - "version": "0.3.1", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.3.1.tgz", - "integrity": "sha512-cUGJnCdr4STbePCgqNFbpVNCepa+kAVohJs1sLhxzdH+gnEoOd8VhbYa7pD3zZYGiURWM2xzEII3fQcRizDkYQ==", - "dev": true - } + "engines": { + "node": ">= 0.8.0" } }, - "locate-path": { + "node_modules/locate-path": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", "dev": true, - "requires": { + "license": "MIT", + "dependencies": { "p-locate": "^5.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "lodash.merge": { - "version": "4.6.2", - "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", - "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", - "dev": true + "node_modules/math-intrinsics": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", + "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } }, - "loose-envify": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", - "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", - "dev": true, - "requires": { - "js-tokens": "^3.0.0 || ^4.0.0" + "node_modules/media-typer": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-1.1.1.tgz", + "integrity": "sha512-yz3xRaG20c6/BOzvYoDaGtPmGscs7YivItZEEqe6GbwNfHuxu9YNmvnEkMzKldAGY4/80pRcQRZSEnhquk9XuQ==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" } }, - "math-intrinsics": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", - "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==" + "node_modules/merge-descriptors": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-2.0.0.tgz", + "integrity": "sha512-Snk314V5ayFLhp3fkUREub6WtjBfPdCPY1Ln8/8munuLuiYhsABgBVWsozAG+MWMbVEvcdcpbi9R7ww22l9Q3g==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } }, - "media-typer": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", - "integrity": "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==" + "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==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } }, - "merge-descriptors": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.3.tgz", - "integrity": "sha512-gaNvAS7TZ897/rVaZ0nMtAyxNyi/pdbjbAwUpFQpN70GqnVfOiXpeUUMKRBmzXaSQ8DdTX4/0ms62r2K+hE6mQ==" + "node_modules/mime-types": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-3.0.2.tgz", + "integrity": "sha512-Lbgzdk0h4juoQ9fCKXW4by0UJqj+nOOrI9MJ1sSj4nI8aI2eo1qmvQEie4VD1glsS250n15LsWsYtCugiStS5A==", + "license": "MIT", + "dependencies": { + "mime-db": "^1.54.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } }, - "methods": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", - "integrity": "sha1-VSmk1nZUE07cxSZmVoNbD4Ua/O4=" - }, - "mime": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", - "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==" - }, - "mime-db": { - "version": "1.52.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", - "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==" - }, - "mime-types": { - "version": "2.1.35", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", - "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", - "requires": { - "mime-db": "1.52.0" - } - }, - "minimatch": { - "version": "3.1.5", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", - "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", - "requires": { - "brace-expansion": "^1.1.7" - } - }, - "minimist": { - "version": "1.2.8", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", - "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", - "dev": true - }, - "ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" + "node_modules/minimatch": { + "version": "10.2.6", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.6.tgz", + "integrity": "sha512-vpLQEs+VLCr1nU0BXS07maYoFwlDAH0gngQuuttxIwutDFEMHq2blX+8vpgxDdK3J1PwjCJiep77OitTZ4Ll1A==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "brace-expansion": "^5.0.8" + }, + "engines": { + "node": "18 || 20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "license": "MIT" }, - "natural-compare": { + "node_modules/natural-compare": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", - "dev": true - }, - "negotiator": { - "version": "0.6.3", - "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", - "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==" - }, - "nodemon": { - "version": "2.0.20", - "resolved": "https://registry.npmjs.org/nodemon/-/nodemon-2.0.20.tgz", - "integrity": "sha512-Km2mWHKKY5GzRg6i1j5OxOHQtuvVsgskLfigG25yTtbyfRGn/GNvIbRyOf1PSCKJ2aT/58TiuUsuOU5UToVViw==", - "requires": { + "dev": true, + "license": "MIT" + }, + "node_modules/negotiator": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-1.1.0.tgz", + "integrity": "sha512-NMPBRMJgiQHjbd8phG3Vebdx4kZ1H121rbl5IkMqeOsahptB9BKo/d7oJ3zTXqTgagn2bWlNSXkh0QUGM31RYg==", + "license": "MIT", + "dependencies": { + "content-type": "^2.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/nodemon": { + "version": "3.1.14", + "resolved": "https://registry.npmjs.org/nodemon/-/nodemon-3.1.14.tgz", + "integrity": "sha512-jakjZi93UtB3jHMWsXL68FXSAosbLfY0In5gtKq3niLSkrWznrVBzXFNOEMJUfc9+Ke7SHWoAZsiMkNP3vq6Jw==", + "dev": true, + "license": "MIT", + "dependencies": { "chokidar": "^3.5.2", - "debug": "^3.2.7", + "debug": "^4", "ignore-by-default": "^1.0.1", - "minimatch": "^3.1.2", + "minimatch": "^10.2.1", "pstree.remy": "^1.1.8", - "semver": "^5.7.1", - "simple-update-notifier": "^1.0.7", + "semver": "^7.5.3", + "simple-update-notifier": "^2.0.0", "supports-color": "^5.5.0", "touch": "^3.1.0", "undefsafe": "^2.0.5" }, - "dependencies": { - "debug": { - "version": "3.2.7", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", - "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", - "requires": { - "ms": "^2.1.1" - } - }, - "ms": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", - "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" - } - } - }, - "nopt": { - "version": "1.0.10", - "resolved": "https://registry.npmjs.org/nopt/-/nopt-1.0.10.tgz", - "integrity": "sha1-bd0hvSoxQXuScn3Vhfim83YI6+4=", - "requires": { - "abbrev": "1" + "bin": { + "nodemon": "bin/nodemon.js" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/nodemon" } }, - "normalize-path": { + "node_modules/normalize-path": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", - "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==" - }, - "object-assign": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", - "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", - "dev": true - }, - "object-inspect": { - "version": "1.13.4", - "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz", - "integrity": "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==" - }, - "object-keys": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", - "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==", - "dev": true - }, - "object.assign": { - "version": "4.1.7", - "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.7.tgz", - "integrity": "sha512-nK28WOo+QIjBkDduTINE4JkF/UJJKyf2EJxvJKfblDpyg0Q+pkOHNTL0Qwy6NP6FhE/EnzV73BxxqcJaXY9anw==", - "dev": true, - "requires": { - "call-bind": "^1.0.8", - "call-bound": "^1.0.3", - "define-properties": "^1.2.1", - "es-object-atoms": "^1.0.0", - "has-symbols": "^1.1.0", - "object-keys": "^1.1.1" - } - }, - "object.entries": { - "version": "1.1.9", - "resolved": "https://registry.npmjs.org/object.entries/-/object.entries-1.1.9.tgz", - "integrity": "sha512-8u/hfXFRBD1O0hPUjioLhoWFHRmt6tKA4/vZPyckBr18l1KE9uHrFaFaUi8MDRTpi4uak2goyPTSNJLXX2k2Hw==", - "dev": true, - "requires": { - "call-bind": "^1.0.8", - "call-bound": "^1.0.4", - "define-properties": "^1.2.1", - "es-object-atoms": "^1.1.1" - } - }, - "object.fromentries": { - "version": "2.0.8", - "resolved": "https://registry.npmjs.org/object.fromentries/-/object.fromentries-2.0.8.tgz", - "integrity": "sha512-k6E21FzySsSK5a21KRADBd/NGneRegFO5pLHfdQLpRDETUNJueLXs3WCzyQ3tFRDYgbq3KHGXfTbi2bs8WQ6rQ==", - "dev": true, - "requires": { - "call-bind": "^1.0.7", - "define-properties": "^1.2.1", - "es-abstract": "^1.23.2", - "es-object-atoms": "^1.0.0" - } - }, - "object.groupby": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/object.groupby/-/object.groupby-1.0.3.tgz", - "integrity": "sha512-+Lhy3TQTuzXI5hevh8sBGqbmurHbbIjAi0Z4S63nthVLmLxfbj4T54a4CfZrXIrt9iP4mVAPYMo/v99taj3wjQ==", + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", "dev": true, - "requires": { - "call-bind": "^1.0.7", - "define-properties": "^1.2.1", - "es-abstract": "^1.23.2" + "license": "MIT", + "engines": { + "node": ">=0.10.0" } }, - "object.values": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/object.values/-/object.values-1.2.1.tgz", - "integrity": "sha512-gXah6aZrcUxjWg2zR2MwouP2eHlCBzdV4pygudehaKXSGW4v2AsRQUK+lwwXhii6KFZcunEnmSUoYp5CXibxtA==", - "dev": true, - "requires": { - "call-bind": "^1.0.8", - "call-bound": "^1.0.3", - "define-properties": "^1.2.1", - "es-object-atoms": "^1.0.0" + "node_modules/object-inspect": { + "version": "1.13.4", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz", + "integrity": "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "on-finished": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", - "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", - "requires": { + "node_modules/on-finished": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.2.tgz", + "integrity": "sha512-ATWUv387i+nLD49EiDoOft5scf32Vsdz3ktrQJG9bi7jRFmaXTssvyvj1EmeKVVgvesbqUtvQQvaS2TpTukAZQ==", + "license": "MIT", + "dependencies": { "ee-first": "1.1.1" + }, + "engines": { + "node": ">= 0.8" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" } }, - "once": { + "node_modules/once": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", - "dev": true, - "requires": { + "license": "ISC", + "dependencies": { "wrappy": "1" } }, - "optionator": { + "node_modules/optionator": { "version": "0.9.4", "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz", "integrity": "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==", "dev": true, - "requires": { + "license": "MIT", + "dependencies": { "deep-is": "^0.1.3", "fast-levenshtein": "^2.0.6", "levn": "^0.4.1", "prelude-ls": "^1.2.1", "type-check": "^0.4.0", "word-wrap": "^1.2.5" + }, + "engines": { + "node": ">= 0.8.0" } }, - "own-keys": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/own-keys/-/own-keys-1.0.1.tgz", - "integrity": "sha512-qFOyK5PjiWZd+QQIh+1jhdb9LpxTF0qs7Pm8o5QHYZ0M3vKqSqzsZaEB6oWlxZ+q2sJBMI/Ktgd2N5ZwQoRHfg==", - "dev": true, - "requires": { - "get-intrinsic": "^1.2.6", - "object-keys": "^1.1.1", - "safe-push-apply": "^1.0.0" - } - }, - "p-limit": { + "node_modules/p-limit": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", "dev": true, - "requires": { + "license": "MIT", + "dependencies": { "yocto-queue": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "p-locate": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", - "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", - "dev": true, - "requires": { - "p-limit": "^3.0.2" - } - }, - "p-try": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", - "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", - "dev": true - }, - "parent-module": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", - "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", - "dev": true, - "requires": { - "callsites": "^3.0.0" - } - }, - "parse-json": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-4.0.0.tgz", - "integrity": "sha512-aOIos8bujGN93/8Ox/jPLh7RwVnPEysynVFE+fQZyg6jKELEHwzgKdLRFHUgXJL6kylijVSBC4BvN9OmsB48Rw==", + "node_modules/p-locate": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", + "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", "dev": true, - "requires": { - "error-ex": "^1.3.1", - "json-parse-better-errors": "^1.0.1" + "license": "MIT", + "dependencies": { + "p-limit": "^3.0.2" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "parseurl": { + "node_modules/parseurl": { "version": "1.3.3", "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", - "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==" + "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } }, - "path-exists": { + "node_modules/path-exists": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", - "dev": true - }, - "path-is-absolute": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", - "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", - "dev": true + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } }, - "path-key": { + "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 - }, - "path-parse": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", - "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", - "dev": true + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } }, - "path-to-regexp": { - "version": "0.1.13", - "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.13.tgz", - "integrity": "sha512-A/AGNMFN3c8bOlvV9RreMdrv7jsmF9XIfDeCd87+I8RNg6s78BhJxMu69NEMHBSJFxKidViTEdruRwEk/WIKqA==" + "node_modules/path-to-regexp": { + "version": "8.4.2", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-8.4.2.tgz", + "integrity": "sha512-qRcuIdP69NPm4qbACK+aDogI5CBDMi1jKe0ry5rSQJz8JVLsC7jV8XpiJjGRLLol3N+R5ihGYcrPLTno6pAdBA==", + "license": "MIT", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } }, - "picomatch": { + "node_modules/picomatch": { "version": "2.3.2", "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.2.tgz", - "integrity": "sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA==" - }, - "pify": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/pify/-/pify-4.0.1.tgz", - "integrity": "sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g==", - "dev": true - }, - "pkg-conf": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/pkg-conf/-/pkg-conf-3.1.0.tgz", - "integrity": "sha512-m0OTbR/5VPNPqO1ph6Fqbj7Hv6QU7gR/tQW40ZqrL1rjgCU85W6C1bJn0BItuJqnR98PWzw7Z8hHeChD1WrgdQ==", + "integrity": "sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA==", "dev": true, - "requires": { - "find-up": "^3.0.0", - "load-json-file": "^5.2.0" + "license": "MIT", + "engines": { + "node": ">=8.6" }, - "dependencies": { - "find-up": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-3.0.0.tgz", - "integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==", - "dev": true, - "requires": { - "locate-path": "^3.0.0" - } - }, - "locate-path": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-3.0.0.tgz", - "integrity": "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==", - "dev": true, - "requires": { - "p-locate": "^3.0.0", - "path-exists": "^3.0.0" - } - }, - "p-limit": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", - "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", - "dev": true, - "requires": { - "p-try": "^2.0.0" - } - }, - "p-locate": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-3.0.0.tgz", - "integrity": "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==", - "dev": true, - "requires": { - "p-limit": "^2.0.0" - } - }, - "path-exists": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz", - "integrity": "sha512-bpC7GYwiDYQ4wYLe+FA8lhRjhQCMcQGuSgGGqDkg/QerRWw9CmGRT0iSOVRSZJ29NMLZgIzqaljJ63oaL4NIJQ==", - "dev": true - } + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" } }, - "possible-typed-array-names": { + "node_modules/possible-typed-array-names": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/possible-typed-array-names/-/possible-typed-array-names-1.1.0.tgz", - "integrity": "sha512-/+5VFTchJDoVj3bhoqi6UeymcD00DAwb1nJwamzPvHEszJ4FpF6SNNbUbOS8yI56qHzdV8eK0qEfOSiodkTdxg==" + "integrity": "sha512-/+5VFTchJDoVj3bhoqi6UeymcD00DAwb1nJwamzPvHEszJ4FpF6SNNbUbOS8yI56qHzdV8eK0qEfOSiodkTdxg==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } }, - "prelude-ls": { + "node_modules/prelude-ls": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", - "dev": true - }, - "prop-types": { - "version": "15.8.1", - "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz", - "integrity": "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==", "dev": true, - "requires": { - "loose-envify": "^1.4.0", - "object-assign": "^4.1.1", - "react-is": "^16.13.1" + "license": "MIT", + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/process": { + "version": "0.11.10", + "resolved": "https://registry.npmjs.org/process/-/process-0.11.10.tgz", + "integrity": "sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A==", + "license": "MIT", + "engines": { + "node": ">= 0.6.0" } }, - "proxy-addr": { + "node_modules/proxy-addr": { "version": "2.0.8", "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.8.tgz", "integrity": "sha512-5nnx0yGyVUcY6t9RnWcARWtwT9F1D8O9rt08htPvnd49W1IgZtmLkhu9WfMzQj1cFxjHIO6connUNVW5k7AVyQ==", - "requires": { + "license": "MIT", + "dependencies": { "forwarded": "0.2.0", "ipaddr.js": "1.9.1" + }, + "engines": { + "node": ">= 0.10" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" } }, - "pstree.remy": { + "node_modules/pstree.remy": { "version": "1.1.8", "resolved": "https://registry.npmjs.org/pstree.remy/-/pstree.remy-1.1.8.tgz", - "integrity": "sha512-77DZwxQmxKnu3aR542U+X8FypNzbfJ+C5XQDk3uWjWxn6151aIMGthWYRXTqT1E5oJvg+ljaa2OJi+VfvCOQ8w==" + "integrity": "sha512-77DZwxQmxKnu3aR542U+X8FypNzbfJ+C5XQDk3uWjWxn6151aIMGthWYRXTqT1E5oJvg+ljaa2OJi+VfvCOQ8w==", + "dev": true, + "license": "MIT" }, - "punycode": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", - "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==", - "dev": true + "node_modules/punycode": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", + "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } }, - "qs": { - "version": "6.15.2", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.15.2.tgz", - "integrity": "sha512-Rzq0KEyX/w/tEybncDgdkZrJgVUsUMk3xjh3t5bv3S1HTAtg+uOYt72+ZfwiQwKdysThkTBdL/rTi6HDmX9Ddw==", - "requires": { - "side-channel": "^1.1.0" + "node_modules/qified": { + "version": "0.10.1", + "resolved": "https://registry.npmjs.org/qified/-/qified-0.10.1.tgz", + "integrity": "sha512-+Owyggi9IxT1ePKGafcI87ubSmxol6smwJ+RAHDQlx9+9cPwFWDiKFFCPuWhr9ignlGpZ9vDQLw67N4dcTVFEA==", + "dev": true, + "license": "MIT", + "dependencies": { + "hookified": "^2.1.1" + }, + "engines": { + "node": ">=20" } }, - "queue-microtask": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", - "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", - "dev": true + "node_modules/qified/node_modules/hookified": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/hookified/-/hookified-2.2.0.tgz", + "integrity": "sha512-p/LgFzRN5FeoD3DLS6bkUapeye6E4SI6yJs6KetENd18S+FBthqYq2amJUWpt5z0EQwwHemidjY5OqJGEKm5uA==", + "dev": true, + "license": "MIT" }, - "range-parser": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", - "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==" - }, - "raw-body": { - "version": "2.5.3", - "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.3.tgz", - "integrity": "sha512-s4VSOf6yN0rvbRZGxs8Om5CWj6seneMwK3oDb4lWDH0UPhWcxwOWw5+qk24bxq87szX1ydrwylIOp2uG1ojUpA==", - "requires": { + "node_modules/qs": { + "version": "6.16.1", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.16.1.tgz", + "integrity": "sha512-5Px6J7rfHBAGUhNQ3es4YzcOdBBw0SDcVtThJml4RdnKw4tKs2epA0kPZ+K8X+WiHktT93RHVcQ/3ym5aTCgtg==", + "license": "BSD-3-Clause", + "dependencies": { + "es-define-property": "^1.0.1", + "side-channel": "^1.1.1" + }, + "engines": { + "node": ">=0.6" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/range-parser": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.3.0.tgz", + "integrity": "sha512-hek2mFQpPuI4E1BBKrSto+BU3e3x4xuarsbiwr3+lf7p44juvFMV0XFWQAP3xUyqXA4RrXLIoaSUGbSt056ZMw==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/raw-body": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-3.0.2.tgz", + "integrity": "sha512-K5zQjDllxWkf7Z5xJdV0/B0WTNqx6vxG70zJE4N0kBs4LovmEYWJzQGxC9bS9RAKu3bgM40lrd5zoLJ12MQ5BA==", + "license": "MIT", + "dependencies": { "bytes": "~3.1.2", "http-errors": "~2.0.1", - "iconv-lite": "~0.4.24", + "iconv-lite": "~0.7.0", "unpipe": "~1.0.0" }, - "dependencies": { - "http-errors": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.1.tgz", - "integrity": "sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ==", - "requires": { - "depd": "~2.0.0", - "inherits": "~2.0.4", - "setprototypeof": "~1.2.0", - "statuses": "~2.0.2", - "toidentifier": "~1.0.1" - } - }, - "statuses": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.2.tgz", - "integrity": "sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==" - } + "engines": { + "node": ">= 0.10" } }, - "react-is": { - "version": "16.13.1", - "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", - "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==", - "dev": true - }, - "readdirp": { + "node_modules/readdirp": { "version": "3.6.0", "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", - "requires": { - "picomatch": "^2.2.1" - } - }, - "reflect.getprototypeof": { - "version": "1.0.10", - "resolved": "https://registry.npmjs.org/reflect.getprototypeof/-/reflect.getprototypeof-1.0.10.tgz", - "integrity": "sha512-00o4I+DVrefhv+nX0ulyi3biSHCPDe+yLv5o/p6d/UVlirijB8E16FtfwSAi4g3tcqrQ4lRAqQSoFEZJehYEcw==", - "dev": true, - "requires": { - "call-bind": "^1.0.8", - "define-properties": "^1.2.1", - "es-abstract": "^1.23.9", - "es-errors": "^1.3.0", - "es-object-atoms": "^1.0.0", - "get-intrinsic": "^1.2.7", - "get-proto": "^1.0.1", - "which-builtin-type": "^1.2.1" - } - }, - "regexp.prototype.flags": { - "version": "1.5.4", - "resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.5.4.tgz", - "integrity": "sha512-dYqgNSZbDwkaJ2ceRd9ojCGjBq+mOm9LmtXnAnEGyHhN/5R7iDW2TRw3h+o/jCFxus3P2LfWIIiwowAjANm7IA==", "dev": true, - "requires": { - "call-bind": "^1.0.8", - "define-properties": "^1.2.1", - "es-errors": "^1.3.0", - "get-proto": "^1.0.1", - "gopd": "^1.2.0", - "set-function-name": "^2.0.2" + "license": "MIT", + "dependencies": { + "picomatch": "^2.2.1" + }, + "engines": { + "node": ">=8.10.0" } }, - "regexpp": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/regexpp/-/regexpp-3.2.0.tgz", - "integrity": "sha512-pq2bWo9mVD43nbts2wGv17XLiNLya+GklZ8kaDLV2Z08gDCsGpnKn9BFMepvWuHCbyVvY7J5o5+BVvoQbmlJLg==", - "dev": true - }, - "require-from-string": { + "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==" - }, - "resolve": { - "version": "1.22.10", - "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.10.tgz", - "integrity": "sha512-NPRy+/ncIMeDlTAsuqwKIiferiawhefFJtkNSW0qZJEqMEb+qBt/77B/jGeeek+F0uOeN05CDa6HXbbIgtVX4w==", - "dev": true, - "requires": { - "is-core-module": "^2.16.0", - "path-parse": "^1.0.7", - "supports-preserve-symlinks-flag": "^1.0.0" - } - }, - "resolve-from": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", - "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", - "dev": true - }, - "reusify": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.1.0.tgz", - "integrity": "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==", - "dev": true - }, - "rimraf": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", - "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", - "dev": true, - "requires": { - "glob": "^7.1.3" - } - }, - "run-parallel": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", - "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", - "dev": true, - "requires": { - "queue-microtask": "^1.2.2" - } - }, - "safe-array-concat": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/safe-array-concat/-/safe-array-concat-1.1.3.tgz", - "integrity": "sha512-AURm5f0jYEOydBj7VQlVvDrjeFgthDdEF5H1dP+6mNpoXOMo1quQqJ4wvJDyRZ9+pO3kGWoOdmV08cSv2aJV6Q==", - "dev": true, - "requires": { - "call-bind": "^1.0.8", - "call-bound": "^1.0.2", - "get-intrinsic": "^1.2.6", - "has-symbols": "^1.1.0", - "isarray": "^2.0.5" + "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" } }, - "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==" - }, - "safe-push-apply": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/safe-push-apply/-/safe-push-apply-1.0.0.tgz", - "integrity": "sha512-iKE9w/Z7xCzUMIZqdBsp6pEQvwuEebH4vdpjcDWnyzaI6yl6O9FHvVpmGelvEHNsoY6wGblkxR6Zty/h00WiSA==", - "dev": true, - "requires": { - "es-errors": "^1.3.0", - "isarray": "^2.0.5" + "node_modules/router": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/router/-/router-2.2.0.tgz", + "integrity": "sha512-nLTrUKm2UyiL7rlhapu/Zl45FwNgkZGaCpZbIHajDYgwlJCOzLSk+cIPAnsEqV955GjILJnKbdQC1nVPz+gAYQ==", + "license": "MIT", + "dependencies": { + "debug": "^4.4.0", + "depd": "^2.0.0", + "is-promise": "^4.0.0", + "parseurl": "^1.3.3", + "path-to-regexp": "^8.0.0" + }, + "engines": { + "node": ">= 18" } }, - "safe-regex-test": { + "node_modules/safe-regex-test": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/safe-regex-test/-/safe-regex-test-1.1.0.tgz", "integrity": "sha512-x/+Cz4YrimQxQccJf5mKEbIa1NzeCRNI5Ecl/ekmlYaampdNLPalVyIcCZNNH3MvmqBugV5TMYZXv0ljslUlaw==", - "dev": true, - "requires": { + "license": "MIT", + "dependencies": { "call-bound": "^1.0.2", "es-errors": "^1.3.0", "is-regex": "^1.2.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "safer-buffer": { + "node_modules/safer-buffer": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", - "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" - }, - "semver": { - "version": "5.7.2", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.2.tgz", - "integrity": "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==" - }, - "send": { - "version": "0.19.0", - "resolved": "https://registry.npmjs.org/send/-/send-0.19.0.tgz", - "integrity": "sha512-dW41u5VfLXu8SJh5bwRmyYUbAoSB3c9uQh6L8h/KtsFREPWpbX1lrljJo186Jc4nmci/sGUZ9a0a0J2zgfq2hw==", - "requires": { - "debug": "2.6.9", - "depd": "2.0.0", - "destroy": "1.2.0", - "encodeurl": "~1.0.2", - "escape-html": "~1.0.3", - "etag": "~1.8.1", - "fresh": "0.5.2", - "http-errors": "2.0.0", - "mime": "1.6.0", - "ms": "2.1.3", - "on-finished": "2.4.1", - "range-parser": "~1.2.1", - "statuses": "2.0.1" + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", + "license": "MIT" + }, + "node_modules/semver": { + "version": "7.8.5", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.5.tgz", + "integrity": "sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" }, - "dependencies": { - "ms": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", - "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" - } + "engines": { + "node": ">=10" } }, - "serve-static": { - "version": "1.16.2", - "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.16.2.tgz", - "integrity": "sha512-VqpjJZKadQB/PEbEwvFdO43Ax5dFBZ2UECszz8bQ7pi7wt//PWe1P6MN7eCnjsatYtBT6EuiClbjSWP2WrIoTw==", - "requires": { - "encodeurl": "~2.0.0", - "escape-html": "~1.0.3", - "parseurl": "~1.3.3", - "send": "0.19.0" + "node_modules/send": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/send/-/send-1.2.1.tgz", + "integrity": "sha512-1gnZf7DFcoIcajTjTwjwuDjzuz4PPcY2StKPlsGAQ1+YH20IRVrBaXSWmdjowTJ6u8Rc01PoYOGHXfP1mYcZNQ==", + "license": "MIT", + "dependencies": { + "debug": "^4.4.3", + "encodeurl": "^2.0.0", + "escape-html": "^1.0.3", + "etag": "^1.8.1", + "fresh": "^2.0.0", + "http-errors": "^2.0.1", + "mime-types": "^3.0.2", + "ms": "^2.1.3", + "on-finished": "^2.4.1", + "range-parser": "^1.2.1", + "statuses": "^2.0.2" + }, + "engines": { + "node": ">= 18" }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/serve-static": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-2.2.1.tgz", + "integrity": "sha512-xRXBn0pPqQTVQiC8wyQrKs2MOlX24zQ0POGaj0kultvoOCstBQM5yvOhAVSUwOMjQtTvsPWoNCHfPGwaaQJhTw==", + "license": "MIT", "dependencies": { - "encodeurl": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz", - "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==" - } + "encodeurl": "^2.0.0", + "escape-html": "^1.0.3", + "parseurl": "^1.3.3", + "send": "^1.2.0" + }, + "engines": { + "node": ">= 18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" } }, - "set-function-length": { + "node_modules/set-function-length": { "version": "1.2.2", "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.2.tgz", "integrity": "sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==", - "requires": { + "license": "MIT", + "dependencies": { "define-data-property": "^1.1.4", "es-errors": "^1.3.0", "function-bind": "^1.1.2", "get-intrinsic": "^1.2.4", "gopd": "^1.0.1", "has-property-descriptors": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" } }, - "set-function-name": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/set-function-name/-/set-function-name-2.0.2.tgz", - "integrity": "sha512-7PGFlmtwsEADb0WYyvCMa1t+yke6daIG4Wirafur5kcf+MhUnPms1UeR0CKQdTZD81yESwMHbtn+TR+dMviakQ==", - "dev": true, - "requires": { - "define-data-property": "^1.1.4", - "es-errors": "^1.3.0", - "functions-have-names": "^1.2.3", - "has-property-descriptors": "^1.0.2" - } - }, - "set-proto": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/set-proto/-/set-proto-1.0.0.tgz", - "integrity": "sha512-RJRdvCo6IAnPdsvP/7m6bsQqNnn1FCBX5ZNtFL98MmFF/4xAIJTIg1YbHW5DC2W5SKZanrC6i4HsJqlajw/dZw==", - "dev": true, - "requires": { - "dunder-proto": "^1.0.1", - "es-errors": "^1.3.0", - "es-object-atoms": "^1.0.0" - } - }, - "setprototypeof": { + "node_modules/setprototypeof": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", - "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==" + "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==", + "license": "ISC" }, - "shebang-command": { + "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, - "requires": { + "license": "MIT", + "dependencies": { "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" } }, - "shebang-regex": { + "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 + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } }, - "side-channel": { + "node_modules/side-channel": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.1.tgz", "integrity": "sha512-6x6dK6zJdpTzF4sQeNYxwtvBzf6Eg4GtlesS94HOvTudUeyK2WXAaIfmDgsyslYrRBeFIlsi54AYsFGUuhmvrQ==", - "requires": { + "license": "MIT", + "dependencies": { "es-errors": "^1.3.0", "object-inspect": "^1.13.4", "side-channel-list": "^1.0.1", "side-channel-map": "^1.0.1", "side-channel-weakmap": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "side-channel-list": { + "node_modules/side-channel-list": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.1.tgz", "integrity": "sha512-mjn/0bi/oUURjc5Xl7IaWi/OJJJumuoJFQJfDDyO46+hBWsfaVM65TBHq2eoZBhzl9EchxOijpkbRC8SVBQU0w==", - "requires": { + "license": "MIT", + "dependencies": { "es-errors": "^1.3.0", "object-inspect": "^1.13.4" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "side-channel-map": { + "node_modules/side-channel-map": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/side-channel-map/-/side-channel-map-1.0.1.tgz", "integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==", - "requires": { + "license": "MIT", + "dependencies": { "call-bound": "^1.0.2", "es-errors": "^1.3.0", "get-intrinsic": "^1.2.5", "object-inspect": "^1.13.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "side-channel-weakmap": { + "node_modules/side-channel-weakmap": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz", "integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==", - "requires": { + "license": "MIT", + "dependencies": { "call-bound": "^1.0.2", "es-errors": "^1.3.0", "get-intrinsic": "^1.2.5", "object-inspect": "^1.13.3", "side-channel-map": "^1.0.1" - } - }, - "simple-update-notifier": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/simple-update-notifier/-/simple-update-notifier-1.0.7.tgz", - "integrity": "sha512-BBKgR84BJQJm6WjWFMHgLVuo61FBDSj1z/xSFUIozqO6wO7ii0JxCqlIud7Enr/+LhlbNI0whErq96P2qHNWew==", - "requires": { - "semver": "~7.0.0" }, - "dependencies": { - "semver": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.0.0.tgz", - "integrity": "sha512-+GB6zVA9LWh6zovYQLALHwv5rb2PHGlJi3lfiqIHxR0uuwCgefcOJc59v9fv1w8GbStwxuuqqAjI9NMAOOgq1A==" - } - } - }, - "standard": { - "version": "17.1.2", - "resolved": "https://registry.npmjs.org/standard/-/standard-17.1.2.tgz", - "integrity": "sha512-WLm12WoXveKkvnPnPnaFUUHuOB2cUdAsJ4AiGHL2G0UNMrcRAWY2WriQaV8IQ3oRmYr0AWUbLNr94ekYFAHOrA==", - "dev": true, - "requires": { - "eslint": "^8.41.0", - "eslint-config-standard": "17.1.0", - "eslint-config-standard-jsx": "^11.0.0", - "eslint-plugin-import": "^2.27.5", - "eslint-plugin-n": "^15.7.0", - "eslint-plugin-promise": "^6.1.1", - "eslint-plugin-react": "^7.36.1", - "standard-engine": "^15.1.0", - "version-guard": "^1.1.1" - } - }, - "standard-engine": { - "version": "15.1.0", - "resolved": "https://registry.npmjs.org/standard-engine/-/standard-engine-15.1.0.tgz", - "integrity": "sha512-VHysfoyxFu/ukT+9v49d4BRXIokFRZuH3z1VRxzFArZdjSCFpro6rEIU3ji7e4AoAtuSfKBkiOmsrDqKW5ZSRw==", - "dev": true, - "requires": { - "get-stdin": "^8.0.0", - "minimist": "^1.2.6", - "pkg-conf": "^3.1.0", - "xdg-basedir": "^4.0.0" - } - }, - "statuses": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", - "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==" - }, - "stop-iteration-iterator": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/stop-iteration-iterator/-/stop-iteration-iterator-1.1.0.tgz", - "integrity": "sha512-eLoXW/DHyl62zxY4SCaIgnRhuMr6ri4juEYARS8E6sCEqzKpOiE521Ucofdx+KnDZl5xmvGYaaKCk5FEOxJCoQ==", - "dev": true, - "requires": { - "es-errors": "^1.3.0", - "internal-slot": "^1.1.0" - } - }, - "string.prototype.matchall": { - "version": "4.0.12", - "resolved": "https://registry.npmjs.org/string.prototype.matchall/-/string.prototype.matchall-4.0.12.tgz", - "integrity": "sha512-6CC9uyBL+/48dYizRf7H7VAYCMCNTBeM78x/VTUe9bFEaxBepPJDa1Ow99LqI/1yF7kuy7Q3cQsYMrcjGUcskA==", - "dev": true, - "requires": { - "call-bind": "^1.0.8", - "call-bound": "^1.0.3", - "define-properties": "^1.2.1", - "es-abstract": "^1.23.6", - "es-errors": "^1.3.0", - "es-object-atoms": "^1.0.0", - "get-intrinsic": "^1.2.6", - "gopd": "^1.2.0", - "has-symbols": "^1.1.0", - "internal-slot": "^1.1.0", - "regexp.prototype.flags": "^1.5.3", - "set-function-name": "^2.0.2", - "side-channel": "^1.1.0" - } - }, - "string.prototype.repeat": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/string.prototype.repeat/-/string.prototype.repeat-1.0.0.tgz", - "integrity": "sha512-0u/TldDbKD8bFCQ/4f5+mNRrXwZ8hg2w7ZR8wa16e8z9XpePWl3eGEcUD0OXpEH/VJH/2G3gjUtR3ZOiBe2S/w==", - "dev": true, - "requires": { - "define-properties": "^1.1.3", - "es-abstract": "^1.17.5" - } - }, - "string.prototype.trim": { - "version": "1.2.10", - "resolved": "https://registry.npmjs.org/string.prototype.trim/-/string.prototype.trim-1.2.10.tgz", - "integrity": "sha512-Rs66F0P/1kedk5lyYyH9uBzuiI/kNRmwJAR9quK6VOtIpZ2G+hMZd+HQbbv25MgCA6gEffoMZYxlTod4WcdrKA==", - "dev": true, - "requires": { - "call-bind": "^1.0.8", - "call-bound": "^1.0.2", - "define-data-property": "^1.1.4", - "define-properties": "^1.2.1", - "es-abstract": "^1.23.5", - "es-object-atoms": "^1.0.0", - "has-property-descriptors": "^1.0.2" - } - }, - "string.prototype.trimend": { - "version": "1.0.9", - "resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.9.tgz", - "integrity": "sha512-G7Ok5C6E/j4SGfyLCloXTrngQIQU3PWtXGst3yM7Bea9FRURf1S42ZHlZZtsNque2FN2PoUhfZXYLNWwEr4dLQ==", - "dev": true, - "requires": { - "call-bind": "^1.0.8", - "call-bound": "^1.0.2", - "define-properties": "^1.2.1", - "es-object-atoms": "^1.0.0" + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "string.prototype.trimstart": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/string.prototype.trimstart/-/string.prototype.trimstart-1.0.8.tgz", - "integrity": "sha512-UXSH262CSZY1tfu3G3Secr6uGLCFVPMhIqHjlgCUtCCcgihYc/xKs9djMTMUOb2j1mVSeU8EU6NWc/iQKU6Gfg==", + "node_modules/simple-update-notifier": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/simple-update-notifier/-/simple-update-notifier-2.0.0.tgz", + "integrity": "sha512-a2B9Y0KlNXl9u/vsW6sTIu9vGEpfKu2wRV6l1H3XEas/0gUIzGzBoP/IouTcUQbm9JWZLH3COxyn03TYlFax6w==", "dev": true, - "requires": { - "call-bind": "^1.0.7", - "define-properties": "^1.2.1", - "es-object-atoms": "^1.0.0" + "license": "MIT", + "dependencies": { + "semver": "^7.5.3" + }, + "engines": { + "node": ">=10" } }, - "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, - "requires": { - "ansi-regex": "^5.0.1" + "node_modules/statuses": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.2.tgz", + "integrity": "sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==", + "license": "MIT", + "engines": { + "node": ">= 0.8" } }, - "strip-bom": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", - "integrity": "sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==", - "dev": true - }, - "strip-json-comments": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", - "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", - "dev": true - }, - "supports-color": { + "node_modules/supports-color": { "version": "5.5.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", - "requires": { + "dev": true, + "license": "MIT", + "dependencies": { "has-flag": "^3.0.0" + }, + "engines": { + "node": ">=4" } }, - "supports-preserve-symlinks-flag": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", - "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", - "dev": true - }, - "text-table": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", - "integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==", - "dev": true - }, - "to-regex-range": { + "node_modules/to-regex-range": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", - "requires": { + "dev": true, + "license": "MIT", + "dependencies": { "is-number": "^7.0.0" + }, + "engines": { + "node": ">=8.0" } }, - "toidentifier": { + "node_modules/toidentifier": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", - "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==" - }, - "touch": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/touch/-/touch-3.1.0.tgz", - "integrity": "sha512-WBx8Uy5TLtOSRtIq+M03/sKDrXCLHxwDcquSP2c43Le03/9serjQBIztjRz6FkJez9D/hleyAXTBGLwwZUw9lA==", - "requires": { - "nopt": "~1.0.10" + "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==", + "license": "MIT", + "engines": { + "node": ">=0.6" } }, - "tsconfig-paths": { - "version": "3.15.0", - "resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-3.15.0.tgz", - "integrity": "sha512-2Ac2RgzDe/cn48GvOe3M+o82pEFewD3UPbyoUHHdKasHwJKjds4fLXWf/Ux5kATBKN20oaFGu+jbElp1pos0mg==", + "node_modules/touch": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/touch/-/touch-3.1.1.tgz", + "integrity": "sha512-r0eojU4bI8MnHr8c5bNo7lJDdI2qXlWWJk6a9EAFG7vbhTjElYhBVS3/miuE0uOuoLdb8Mc/rVfsmm6eo5o9GA==", "dev": true, - "requires": { - "@types/json5": "^0.0.29", - "json5": "^1.0.2", - "minimist": "^1.2.6", - "strip-bom": "^3.0.0" + "license": "ISC", + "bin": { + "nodetouch": "bin/nodetouch.js" } }, - "type-check": { + "node_modules/type-check": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", "dev": true, - "requires": { + "license": "MIT", + "dependencies": { "prelude-ls": "^1.2.1" + }, + "engines": { + "node": ">= 0.8.0" } }, - "type-fest": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", - "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", - "dev": true - }, - "type-is": { - "version": "1.6.18", - "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", - "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==", - "requires": { - "media-typer": "0.3.0", - "mime-types": "~2.1.24" - } - }, - "typed-array-buffer": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/typed-array-buffer/-/typed-array-buffer-1.0.3.tgz", - "integrity": "sha512-nAYYwfY3qnzX30IkA6AQZjVbtK6duGontcQm1WSG1MD94YLqK0515GNApXkoxKOWMusVssAHWLh9SeaoefYFGw==", - "dev": true, - "requires": { - "call-bound": "^1.0.3", - "es-errors": "^1.3.0", - "is-typed-array": "^1.1.14" - } - }, - "typed-array-byte-length": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/typed-array-byte-length/-/typed-array-byte-length-1.0.3.tgz", - "integrity": "sha512-BaXgOuIxz8n8pIq3e7Atg/7s+DpiYrxn4vdot3w9KbnBhcRQq6o3xemQdIfynqSeXeDrF32x+WvfzmOjPiY9lg==", - "dev": true, - "requires": { - "call-bind": "^1.0.8", - "for-each": "^0.3.3", - "gopd": "^1.2.0", - "has-proto": "^1.2.0", - "is-typed-array": "^1.1.14" - } - }, - "typed-array-byte-offset": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/typed-array-byte-offset/-/typed-array-byte-offset-1.0.4.tgz", - "integrity": "sha512-bTlAFB/FBYMcuX81gbL4OcpH5PmlFHqlCCpAl8AlEzMz5k53oNDvN8p1PNOWLEmI2x4orp3raOFB51tv9X+MFQ==", - "dev": true, - "requires": { - "available-typed-arrays": "^1.0.7", - "call-bind": "^1.0.8", - "for-each": "^0.3.3", - "gopd": "^1.2.0", - "has-proto": "^1.2.0", - "is-typed-array": "^1.1.15", - "reflect.getprototypeof": "^1.0.9" - } - }, - "typed-array-length": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/typed-array-length/-/typed-array-length-1.0.7.tgz", - "integrity": "sha512-3KS2b+kL7fsuk/eJZ7EQdnEmQoaho/r6KUef7hxvltNA5DR8NAUM+8wJMbJyZ4G9/7i3v5zPBIMN5aybAh2/Jg==", - "dev": true, - "requires": { - "call-bind": "^1.0.7", - "for-each": "^0.3.3", - "gopd": "^1.0.1", - "is-typed-array": "^1.1.13", - "possible-typed-array-names": "^1.0.0", - "reflect.getprototypeof": "^1.0.6" - } - }, - "unbox-primitive": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.1.0.tgz", - "integrity": "sha512-nWJ91DjeOkej/TA8pXQ3myruKpKEYgqvpw9lz4OPHj/NWFNluYrjbz9j01CJ8yKQd2g4jFoOkINCTW2I5LEEyw==", - "dev": true, - "requires": { - "call-bound": "^1.0.3", - "has-bigints": "^1.0.2", - "has-symbols": "^1.1.0", - "which-boxed-primitive": "^1.1.1" + "node_modules/type-is": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/type-is/-/type-is-2.1.0.tgz", + "integrity": "sha512-faYHw0anBbc/kWF3zFTEnxSFOAGUX9GFbOBthvDdLsIlEoWOFOtS0zgCiQYwIskL9iGXZL3kAXD8OoZ4GmMATA==", + "license": "MIT", + "dependencies": { + "content-type": "^2.0.0", + "media-typer": "^1.1.0", + "mime-types": "^3.0.0" + }, + "engines": { + "node": ">= 18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" } }, - "undefsafe": { + "node_modules/undefsafe": { "version": "2.0.5", "resolved": "https://registry.npmjs.org/undefsafe/-/undefsafe-2.0.5.tgz", - "integrity": "sha512-WxONCrssBM8TSPRqN5EmsjVrsv4A8X12J4ArBiiayv3DyyG3ZlIg6yysuuSYdZsVz3TKcTg2fd//Ujd4CHV1iA==" + "integrity": "sha512-WxONCrssBM8TSPRqN5EmsjVrsv4A8X12J4ArBiiayv3DyyG3ZlIg6yysuuSYdZsVz3TKcTg2fd//Ujd4CHV1iA==", + "dev": true, + "license": "MIT" }, - "unpipe": { + "node_modules/unpipe": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", - "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==" + "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } }, - "uri-js": { - "version": "4.2.2", - "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.2.2.tgz", - "integrity": "sha512-KY9Frmirql91X2Qgjry0Wd4Y+YTdrdZheS8TFwvkbLWf/G5KNJDCh6pKL5OZctEW4+0Baa5idK2ZQuELRwPznQ==", + "node_modules/uri-js": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", + "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", "dev": true, - "requires": { + "license": "BSD-2-Clause", + "dependencies": { "punycode": "^2.1.0" } }, - "util": { - "version": "0.12.4", - "resolved": "https://registry.npmjs.org/util/-/util-0.12.4.tgz", - "integrity": "sha512-bxZ9qtSlGUWSOy9Qa9Xgk11kSslpuZwaxCg4sNIDj6FLucDab2JxnHwyNTCpHMtK1MjoQiWQ6DiUMZYbSrO+Sw==", - "requires": { + "node_modules/util": { + "version": "0.12.5", + "resolved": "https://registry.npmjs.org/util/-/util-0.12.5.tgz", + "integrity": "sha512-kZf/K6hEIrWHI6XqOFUiiMa+79wE/D8Q+NCNAWclkyg3b4d2k7s0QGepNjiABc+aR3N1PAyHL7p6UcLY6LmrnA==", + "license": "MIT", + "dependencies": { "inherits": "^2.0.3", "is-arguments": "^1.0.4", "is-generator-function": "^1.0.7", "is-typed-array": "^1.1.3", - "safe-buffer": "^5.1.2", "which-typed-array": "^1.1.2" } }, - "utils-merge": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", - "integrity": "sha1-n5VxD1CiZ5R7LMwSR0HBAoQn5xM=" - }, - "uuid": { - "version": "8.3.2", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", - "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==" + "node_modules/uuid": { + "version": "11.1.1", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-11.1.1.tgz", + "integrity": "sha512-vIYxrBCC/N/K+Js3qSN88go7kIfNPssr/hHCesKCQNAjmgvYS2oqr69kIufEG+O4+PfezOH4EbIeHCfFov8ZgQ==", + "funding": [ + "https://github.com/sponsors/broofa", + "https://github.com/sponsors/ctavan" + ], + "license": "MIT", + "bin": { + "uuid": "dist/esm/bin/uuid" + } }, - "vary": { + "node_modules/vary": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", - "integrity": "sha1-IpnwLG3tMNSllhsLn3RSShj2NPw=" - }, - "version-guard": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/version-guard/-/version-guard-1.1.3.tgz", - "integrity": "sha512-JwPr6erhX53EWH/HCSzfy1tTFrtPXUe927wdM1jqBBeYp1OM+qPHjWbsvv6pIBduqdgxxS+ScfG7S28pzyr2DQ==", - "dev": true + "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } }, - "which": { + "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, - "requires": { + "license": "ISC", + "dependencies": { "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" } }, - "which-boxed-primitive": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/which-boxed-primitive/-/which-boxed-primitive-1.1.1.tgz", - "integrity": "sha512-TbX3mj8n0odCBFVlY8AxkqcHASw3L60jIuF8jFP78az3C2YhmGvqbHBpAjTRH2/xqYunrJ9g1jSyjCjpoWzIAA==", - "dev": true, - "requires": { - "is-bigint": "^1.1.0", - "is-boolean-object": "^1.2.1", - "is-number-object": "^1.1.1", - "is-string": "^1.1.1", - "is-symbol": "^1.1.1" - } - }, - "which-builtin-type": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/which-builtin-type/-/which-builtin-type-1.2.1.tgz", - "integrity": "sha512-6iBczoX+kDQ7a3+YJBnh3T+KZRxM/iYNPXicqk66/Qfm1b93iu+yOImkg0zHbj5LNOcNv1TEADiZ0xa34B4q6Q==", - "dev": true, - "requires": { - "call-bound": "^1.0.2", - "function.prototype.name": "^1.1.6", - "has-tostringtag": "^1.0.2", - "is-async-function": "^2.0.0", - "is-date-object": "^1.1.0", - "is-finalizationregistry": "^1.1.0", - "is-generator-function": "^1.0.10", - "is-regex": "^1.2.1", - "is-weakref": "^1.0.2", - "isarray": "^2.0.5", - "which-boxed-primitive": "^1.1.0", - "which-collection": "^1.0.2", - "which-typed-array": "^1.1.16" - } - }, - "which-collection": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/which-collection/-/which-collection-1.0.2.tgz", - "integrity": "sha512-K4jVyjnBdgvc86Y6BkaLZEN933SwYOuBFkdmBu9ZfkcAbdVbpITnDmjvZ/aQjRXQrv5EPkTnD1s39GiiqbngCw==", - "dev": true, - "requires": { - "is-map": "^2.0.3", - "is-set": "^2.0.3", - "is-weakmap": "^2.0.2", - "is-weakset": "^2.0.3" - } - }, - "which-typed-array": { - "version": "1.1.19", - "resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.19.tgz", - "integrity": "sha512-rEvr90Bck4WZt9HHFC4DJMsjvu7x+r6bImz0/BrbWb7A2djJ8hnZMrWnHo9F8ssv0OMErasDhftrfROTyqSDrw==", - "requires": { + "node_modules/which-typed-array": { + "version": "1.1.24", + "resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.24.tgz", + "integrity": "sha512-wk4Mf4pR5mRP7eYuuTBCIQ9d0ud2Fv2jRLQpfgnRjbOxAFHmjKFValgTpitVKzJJS8ajnYQV2Du1SZ8j6b/EUQ==", + "license": "MIT", + "dependencies": { "available-typed-arrays": "^1.0.7", - "call-bind": "^1.0.8", + "call-bind": "^1.0.9", "call-bound": "^1.0.4", "for-each": "^0.3.5", "get-proto": "^1.0.1", "gopd": "^1.2.0", "has-tostringtag": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "word-wrap": { + "node_modules/word-wrap": { "version": "1.2.5", "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz", "integrity": "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==", - "dev": true + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } }, - "wrappy": { + "node_modules/wrappy": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", - "dev": true - }, - "xdg-basedir": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/xdg-basedir/-/xdg-basedir-4.0.0.tgz", - "integrity": "sha512-PSNhEJDejZYV7h50BohL09Er9VaIefr2LMAf3OEmpCkjOi34eYyQYAXUTjEQtZJTKcF0E2UKTh+osDLsgNim9Q==", - "dev": true + "license": "ISC" }, - "yocto-queue": { + "node_modules/yocto-queue": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", - "dev": true + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } } } } diff --git a/code-samples/serving/cloudevents/cloudevents-nodejs/package.json b/code-samples/serving/cloudevents/cloudevents-nodejs/package.json index 3c36e6fbfd..a7eaecaffb 100644 --- a/code-samples/serving/cloudevents/cloudevents-nodejs/package.json +++ b/code-samples/serving/cloudevents/cloudevents-nodejs/package.json @@ -8,16 +8,24 @@ "start": "node index.js", "production": "npm start", "dev": "nodemon index.js", - "lint": "standard --fix" + "lint": "eslint index.js eslint.config.js" }, "author": "", "license": "Apache-2.0", + "engines": { + "node": ">=24 <25" + }, "dependencies": { - "cloudevents": "^6.0.1", - "express": "^4.22.3", - "nodemon": "^2.0.20" + "cloudevents": "^10.0.0", + "express": "^5.3.0" }, "devDependencies": { - "standard": "^17.1.2" + "nodemon": "^3.1.14", + "eslint": "^10.13.0" + }, + "overrides": { + "cloudevents": { + "uuid": "^11.1.1" + } } } From fd24296b209628f1d860b5d7c0607802fdfd2d6b Mon Sep 17 00:00:00 2001 From: Dave Protasowski Date: Mon, 26 Oct 2026 09:16:04 -0400 Subject: [PATCH 22/44] Update the Rust CloudEvents sample --- .../cloudevents/cloudevents-rust/Cargo.lock | 2591 +++++++++-------- .../cloudevents/cloudevents-rust/Cargo.toml | 16 +- .../cloudevents/cloudevents-rust/Dockerfile | 19 +- .../cloudevents/cloudevents-rust/README.md | 59 +- .../cloudevents/cloudevents-rust/ci.sh | 43 + .../cloudevents/cloudevents-rust/src/main.rs | 139 +- 6 files changed, 1534 insertions(+), 1333 deletions(-) create mode 100755 code-samples/serving/cloudevents/cloudevents-rust/ci.sh diff --git a/code-samples/serving/cloudevents/cloudevents-rust/Cargo.lock b/code-samples/serving/cloudevents/cloudevents-rust/Cargo.lock index 259ce44f0b..8b337dd3eb 100644 --- a/code-samples/serving/cloudevents/cloudevents-rust/Cargo.lock +++ b/code-samples/serving/cloudevents/cloudevents-rust/Cargo.lock @@ -4,451 +4,337 @@ version = 4 [[package]] name = "actix-codec" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09e55f0a5c2ca15795035d90c46bd0e73a5123b72f68f12596d6ba5282051380" -dependencies = [ - "bitflags", - "bytes", - "futures-core", - "futures-sink", - "log", - "tokio", - "tokio-util 0.2.0", -] - -[[package]] -name = "actix-codec" -version = "0.3.0" +version = "0.5.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "78d1833b3838dbe990df0f1f87baf640cf6146e898166afe401839d1b001e570" +checksum = "4c13df95297bcf9014dc89162b0cc69431e192e34e3b419612fc124cfcd45dbf" dependencies = [ - "bitflags", + "bitflags 2.13.2", "bytes", "futures-core", "futures-sink", - "log", - "pin-project 0.4.17", + "memchr", + "pin-project-lite", "tokio", - "tokio-util 0.3.1", -] - -[[package]] -name = "actix-connect" -version = "2.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "177837a10863f15ba8d3ae3ec12fac1099099529ed20083a27fdfe247381d0dc" -dependencies = [ - "actix-codec 0.3.0", - "actix-rt", - "actix-service", - "actix-utils 2.0.0", - "derive_more", - "either", - "futures-util", - "http", - "log", - "trust-dns-proto", - "trust-dns-resolver", + "tokio-util", + "tracing", ] [[package]] name = "actix-http" -version = "2.2.2" +version = "3.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2be6b66b62a794a8e6d366ac9415bb7d475ffd1e9f4671f38c1d8a8a5df950b3" +checksum = "b796ba3c974e5133f6ca1a2c4e97c5941ea1a46d202df83344afaa18219f1a62" dependencies = [ - "actix-codec 0.3.0", - "actix-connect", - "actix-rt", + "actix-codec", "actix-service", - "actix-threadpool", - "actix-utils 2.0.0", - "base64 0.13.1", - "bitflags", + "actix-utils", + "base64 0.22.1", + "bitflags 2.13.2", "brotli", "bytes", - "cookie", - "copyless", + "bytestring", "derive_more", - "either", "encoding_rs", "flate2", - "futures-channel", + "foldhash", "futures-core", - "futures-util", - "fxhash", "h2", - "http", + "http 0.2.12", "httparse", - "indexmap", - "itoa 0.4.5", + "httpdate", + "itoa", "language-tags", - "lazy_static", - "log", + "local-channel", "mime", "percent-encoding", - "pin-project 1.0.10", + "pin-project-lite", "rand", - "regex", - "serde", - "serde_json", - "serde_urlencoded 0.7.1", - "sha-1", - "slab", - "time 0.2.22", + "sha1", + "smallvec", + "tokio", + "tokio-util", + "tracing", + "zstd", ] [[package]] name = "actix-macros" -version = "0.1.2" +version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a60f9ba7c4e6df97f3aacb14bb5c0cd7d98a49dcbaed0d7f292912ad9a6a3ed2" +checksum = "367f814ad4afbac74f07df5001214da65f65e185c90ef56c4dd8df23f8695b9b" dependencies = [ + "proc-macro2", "quote", - "syn 1.0.96", + "syn 3.0.9", ] [[package]] name = "actix-router" -version = "0.2.4" +version = "0.5.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9d7a10ca4d94e8c8e7a87c5173aba1b97ba9a6563ca02b0e1cd23531093d3ec8" +checksum = "14f8c75c51892f18d9c46150c5ac7beb81c95f78c8b83a634d49f4ca32551fe7" dependencies = [ "bytestring", - "http", - "log", + "cfg-if", + "http 0.2.12", "regex", + "regex-lite", "serde", + "tracing", ] [[package]] name = "actix-rt" -version = "1.1.1" +version = "2.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "143fcc2912e0d1de2bcf4e2f720d2a60c28652ab4179685a1ee159e0fb3db227" +checksum = "e5f794807f82bbd36430c12cd600c73bbab0f52fdde4f0ed49978df113f4807f" dependencies = [ - "actix-macros", - "actix-threadpool", - "copyless", - "futures-channel", - "futures-util", - "smallvec", + "futures-core", "tokio", ] [[package]] name = "actix-server" -version = "1.0.3" +version = "2.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e6d74b464215a473c973a2d7d03a69cc10f4ce1f4b38a7659c5193dc5c675630" +checksum = "5e296908c4d6f3a191e6b9873dbdfec8023350a89129d2b470f61d17233931ee" dependencies = [ - "actix-codec 0.2.0", "actix-rt", "actix-service", - "actix-utils 1.0.6", - "futures-channel", + "futures-core", "futures-util", - "log", "mio", - "mio-uds", - "num_cpus", - "slab", "socket2", + "tokio", + "tracing", ] [[package]] name = "actix-service" -version = "1.0.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0052435d581b5be835d11f4eb3bce417c8af18d87ddf8ace99f8e67e595882bb" -dependencies = [ - "futures-util", - "pin-project 0.4.17", -] - -[[package]] -name = "actix-testing" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "47239ca38799ab74ee6a8a94d1ce857014b2ac36f242f70f3f75a66f691e791c" -dependencies = [ - "actix-macros", - "actix-rt", - "actix-server", - "actix-service", - "log", - "socket2", -] - -[[package]] -name = "actix-threadpool" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cf4082192601de5f303013709ff84d81ca6a1bc4af7fb24f367a500a23c6e84e" -dependencies = [ - "derive_more", - "futures-channel", - "lazy_static", - "log", - "num_cpus", - "parking_lot", - "threadpool", -] - -[[package]] -name = "actix-tls" -version = "2.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "24789b7d7361cf5503a504ebe1c10806896f61e96eca9a7350e23001aca715fb" -dependencies = [ - "actix-codec 0.3.0", - "actix-service", - "actix-utils 2.0.0", - "futures-util", -] - -[[package]] -name = "actix-utils" -version = "1.0.6" +version = "2.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fcf8f5631bf01adec2267808f00e228b761c60c0584cc9fa0b5364f41d147f4e" +checksum = "9e46f36bf0e5af44bdc4bdb36fbbd421aa98c79a9bce724e1edeb3894e10dc7f" dependencies = [ - "actix-codec 0.2.0", - "actix-rt", - "actix-service", - "bitflags", - "bytes", - "either", - "futures", - "log", - "pin-project 0.4.17", - "slab", + "futures-core", + "pin-project-lite", ] [[package]] name = "actix-utils" -version = "2.0.0" +version = "3.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2e9022dec56632d1d7979e59af14f0597a28a830a9c1c7fec8b2327eb9f16b5a" +checksum = "0128396dd7313f697ad05b21b1a7be7d4cbb81888704f55996e4a27db196bb4d" dependencies = [ - "actix-codec 0.3.0", - "actix-rt", - "actix-service", - "bitflags", - "bytes", - "either", - "futures-channel", - "futures-sink", - "futures-util", - "log", - "pin-project 0.4.17", - "slab", + "local-waker", + "pin-project-lite", ] [[package]] name = "actix-web" -version = "3.1.0" +version = "4.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c1b12fe25e11cd9ed2ef2e428427eb6178a1b363f3f7f0dab8278572f11b2da1" +checksum = "67756b0fc99e07b8702b9a856e3d6ec036387243c2ce6310070d1e8f02680bb0" dependencies = [ - "actix-codec 0.3.0", + "actix-codec", "actix-http", "actix-macros", "actix-router", "actix-rt", "actix-server", "actix-service", - "actix-testing", - "actix-threadpool", - "actix-tls", - "actix-utils 2.0.0", + "actix-utils", "actix-web-codegen", - "awc", "bytes", + "bytestring", + "cfg-if", + "cookie", "derive_more", "encoding_rs", - "futures-channel", + "foldhash", "futures-core", "futures-util", - "fxhash", + "impl-more", + "itoa", + "language-tags", "log", "mime", - "pin-project 0.4.17", + "once_cell", + "pin-project-lite", "regex", + "regex-lite", "serde", "serde_json", - "serde_urlencoded 0.6.1", + "serde_urlencoded", + "smallvec", "socket2", - "time 0.2.22", - "tinyvec", + "time", + "tokio", + "tracing", "url", ] [[package]] name = "actix-web-codegen" -version = "0.3.0" +version = "4.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "750ca8fb60bbdc79491991650ba5d2ae7cd75f3fc00ead51390cfe9efda0d4d8" +checksum = "b96b09c4878563f8ab4a5fd0c59f9f0d6e0e9f60eb9b748526a0b9604fd89c50" dependencies = [ + "actix-router", "proc-macro2", "quote", - "syn 1.0.96", -] - -[[package]] -name = "addr2line" -version = "0.12.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a49806b9dadc843c61e7c97e72490ad7f7220ae249012fbda9ad0609457c0543" -dependencies = [ - "gimli", + "syn 3.0.9", ] [[package]] -name = "adler32" -version = "1.0.4" +name = "adler2" +version = "2.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5d2e7343e7fc9de883d1b0341e0b13970f764c14101234857d2ddafa1cb1cac2" +checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa" [[package]] name = "aho-corasick" -version = "0.7.10" +version = "1.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8716408b8bc624ed7f65d223ddb9ac2d044c0547b6fa4b0d554f3a9540496ada" +checksum = "c982642fa9e8606056828ee9a8505737230110bb1099153c79efe865c59d12ba" dependencies = [ "memchr", ] [[package]] name = "alloc-no-stdlib" -version = "2.0.4" +version = "3.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cc7bb162ec39d46ab1ca8c77bf72e890535becd1751bb45f64c597edb4c8c6b3" +checksum = "c2fb6cfd47bf496ff64095c20eaba0c201404ee38714d4142fcfa1dc334fcc7a" [[package]] name = "alloc-stdlib" -version = "0.2.2" +version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "94fb8275041c72129eb51b7d0322c29b8387a0386127718b096429201a5d6ece" +checksum = "0b5c1865780388bfa186411ab5f247819487fc4864c6e9c3106611fa347586e1" dependencies = [ "alloc-no-stdlib", ] [[package]] -name = "arc-swap" -version = "0.4.8" +name = "android_system_properties" +version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dabe5a181f83789739c194cbe5a897dde195078fac08568d09221fd6137a7ba8" +checksum = "ae221649c9976a6f6c56ae1facf410f3ddb33cc661c4b7b61020a912d4237fbc" +dependencies = [ + "libc", +] [[package]] -name = "async-trait" -version = "0.1.36" +name = "anstream" +version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a265e3abeffdce30b2e26b7a11b222fe37c6067404001b434101457d0385eb92" +checksum = "824a212faf96e9acacdbd09febd34438f8f711fb84e09a8916013cd7815ca28d" dependencies = [ - "proc-macro2", - "quote", - "syn 1.0.96", + "anstyle", + "anstyle-parse", + "anstyle-query", + "anstyle-wincon", + "colorchoice", + "is_terminal_polyfill", + "utf8parse", ] [[package]] -name = "atty" -version = "0.2.14" +name = "anstyle" +version = "1.0.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "940b3a0ca603d1eade50a4846a2afffd5ef57a9feac2c0e2ec2e14f9ead76000" + +[[package]] +name = "anstyle-parse" +version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8" +checksum = "52ce7f38b242319f7cabaa6813055467063ecdc9d355bbb4ce0c68908cd8130e" dependencies = [ - "hermit-abi", - "libc", - "winapi 0.3.8", + "utf8parse", ] [[package]] -name = "autocfg" -version = "1.0.0" +name = "anstyle-query" +version = "1.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f8aac770f1885fd7e387acedd76065302551364496e46b3dd00860b2f8359b9d" +checksum = "40c48f72fd53cd289104fc64099abca73db4166ad86ea0b4341abe65af83dadc" +dependencies = [ + "windows-sys 0.61.2", +] [[package]] -name = "awc" -version = "2.0.0" +name = "anstyle-wincon" +version = "3.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "150e00c06683ab44c5f97d033950e5d87a7a042d06d77f5eecb443cbd23d0575" +checksum = "291e6a250ff86cd4a820112fb8898808a366d8f9f58ce16d1f538353ad55747d" dependencies = [ - "actix-codec 0.3.0", - "actix-http", - "actix-rt", - "actix-service", - "base64 0.12.1", - "bytes", - "derive_more", - "futures-core", - "log", - "mime", - "percent-encoding", - "rand", - "serde", - "serde_json", - "serde_urlencoded 0.6.1", + "anstyle", + "once_cell_polyfill", + "windows-sys 0.61.2", ] [[package]] -name = "backtrace" -version = "0.3.48" +name = "async-trait" +version = "0.1.92" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0df2f85c8a2abbe3b7d7e748052fdd9b76a0458fdeb16ad4223f5eca78c7c130" +checksum = "82f6aeea286b8eb4dd3431a1be1b59d290ace00f5bfd8e2a159bc2a05e2c1667" dependencies = [ - "addr2line", - "cfg-if 0.1.10", - "libc", - "object", - "rustc-demangle", + "proc-macro2", + "quote", + "syn 3.0.9", ] [[package]] -name = "base-x" -version = "0.2.6" +name = "atomic-waker" +version = "1.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1b20b618342cf9891c292c4f5ac2cde7287cc5c87e87e9c769d617793607dec1" +checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0" [[package]] -name = "base64" -version = "0.11.0" +name = "autocfg" +version = "1.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b41b7ea54a0c9d92199de89e20e58d49f02f8e699814ef3fdf266f6f748d15c7" +checksum = "f2032f911046de80f0a198e0901378627c33f59ea0ac00e363d481118bd70a53" [[package]] name = "base64" -version = "0.12.1" +version = "0.22.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "53d1ccbaf7d9ec9537465a97bf19edc1a4e158ecb49fc16178202238c569cc42" +checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" [[package]] name = "base64" -version = "0.13.1" +version = "0.23.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9e1b586273c5702936fe7b7d6896644d8be71e6314cfe09d3167c95f712589e8" +checksum = "ac07cdecf99051d9a5238b80f35af32cdeba5b336e55d957b318b50137e18da5" [[package]] name = "bitflags" -version = "1.2.1" +version = "1.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cf1de2fe8c75bc145a2f577add951f8134889b4795d47466a54a5c846d691693" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + +[[package]] +name = "bitflags" +version = "2.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3ded4057c258ba199e2d26386d3af3780957ecaee6c4ef4041c6b4b8b97c0b06" [[package]] name = "block-buffer" -version = "0.9.0" +version = "0.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4152116fd6e9dadb291ae18fc1ec3575ed6d84c29642d97890f4b4a3417297e4" +checksum = "d2f6c7dbe95a6ed67ad9f18e57daf93a2f034c524b99fd2b76d18fdfeb6660aa" dependencies = [ - "generic-array", + "hybrid-array", ] [[package]] name = "brotli" -version = "3.5.0" +version = "9.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d640d25bc63c50fb1f0b545ffd80207d2e10a4c965530809b40ba3386825c391" +checksum = "f8b851b75c23ca7873623d612fe49bd1989aeb03d08fb9432187eb253d3d4c6b" dependencies = [ "alloc-no-stdlib", "alloc-stdlib", @@ -457,9 +343,9 @@ dependencies = [ [[package]] name = "brotli-decompressor" -version = "2.5.1" +version = "6.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4e2e4afe60d7dd600fdd3de8d0f08c2b7ec039712e3b6137ff98b7004e82de4f" +checksum = "941cd9bd4ddab83cb46fa5a2d428f1c857b24ac78cb876cf7beb710840934bd7" dependencies = [ "alloc-no-stdlib", "alloc-stdlib", @@ -467,81 +353,93 @@ dependencies = [ [[package]] name = "bumpalo" -version = "3.12.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0d261e256854913907f67ed06efbc3338dfe6179796deefc1ff763fc1aee5535" - -[[package]] -name = "byteorder" -version = "1.3.4" +version = "3.20.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "08c48aae112d48ed9f069b33538ea9e3e90aa263cfa3d1c24309612b1f7472de" +checksum = "72f5acc6cb2ba439de613abc23857ec3d78374d8ed5ac84e9d11336e87da8649" [[package]] name = "bytes" -version = "0.5.4" +version = "1.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "130aac562c0dd69c56b3b1cc8ffd2e17be31d0b6c25b61c96b76231aa23e39e1" +checksum = "fc652a48c352aef3ea3aed32080501cf3ef6ed5da78602a020c991775b0aff04" [[package]] name = "bytestring" -version = "0.1.5" +version = "1.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fc7c05fa5172da78a62d9949d662d2ac89d4cc7355d7b49adee5163f1fb3f363" +checksum = "86566c496f2f47d9b8147a4c8b02ffdb69c919fe0c2b2e7195d22cbba0e635c9" dependencies = [ "bytes", ] [[package]] name = "cc" -version = "1.0.53" +version = "1.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "404b1fe4f65288577753b17e3b36a04596ee784493ec249bf81c7f2d2acd751c" +checksum = "877bf67ac8e52449eb2f9e98a344ea163beb70dd1f9eebe3cc5357dcfeaee4df" +dependencies = [ + "find-msvc-tools", + "jobserver", + "libc", + "shlex", +] [[package]] name = "cfg-if" -version = "0.1.10" +version = "1.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822" +checksum = "4e7648175b45a9a48536d676f68d918270699102aa8dab5496df06904c914600" [[package]] -name = "cfg-if" -version = "1.0.0" +name = "cfg_aliases" +version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" +checksum = "f079e83a288787bcd14a6aea84cee5c87a67c5a3e660c30f557a3d24761b3527" [[package]] -name = "chrono" -version = "0.4.11" +name = "chacha20" +version = "0.10.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "80094f509cf8b5ae86a4966a39b3ff66cd7e2a3e594accec3743ff3fabeab5b2" +checksum = "65c35e4b699c7e15ccbe7ee35c005e4fc0a278d22238a2857e6ce2dadeda1b06" dependencies = [ - "num-integer", - "num-traits", - "serde", - "time 0.1.43", + "cfg-if", + "cpufeatures", + "rand_core", ] [[package]] -name = "cloudabi" -version = "0.0.3" +name = "chrono" +version = "0.4.45" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ddfc5b9aa5d4507acaf872de71051dfd0e309860e88966e1051e462a077aac4f" +checksum = "1aa79e62e7697b8e29b513a68abacf485adcd1fe8284a4316c5ae868e6633327" dependencies = [ - "bitflags", + "iana-time-zone", + "js-sys", + "num-traits", + "serde", + "wasm-bindgen", + "windows-link", ] [[package]] name = "cloudevents-sdk" -version = "0.3.1" +version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a7c9f6054fbd2d641a2d275e44f922a3816a6cf51751fb0f72fcf26e27209dfd" +checksum = "47598c8e3f47e1cfb787750e1bef5de56cb288d757b7b753ace8b5f5b6a1c7f8" dependencies = [ - "base64 0.12.1", - "bitflags", + "actix-http", + "actix-web", + "async-trait", + "base64 0.22.1", + "bitflags 2.13.2", + "bytes", "chrono", "delegate-attr", + "futures 0.3.34", "hostname", + "http 0.2.12", + "http 1.5.0", + "reqwest", "serde", "serde_json", "snafu", @@ -551,194 +449,237 @@ dependencies = [ ] [[package]] -name = "cloudevents-sdk-actix-web" -version = "0.3.1" +name = "colorchoice" +version = "1.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4cbeee2dc837554a8e57b20025ba175241bae2cfe27b078bedf27c9da668d35a" -dependencies = [ - "actix-web", - "async-trait", - "bytes", - "cloudevents-sdk", - "futures", - "lazy_static", -] +checksum = "1d07550c9036bf2ae0c684c4297d503f838287c83c53686d05370d0e139ae570" [[package]] -name = "cloudevents-sdk-reqwest" -version = "0.3.1" +name = "const-oid" +version = "0.10.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b3402a811b777f123279f268eb146272f3bab907a265634ef3f4d3ad98c3556b" -dependencies = [ - "async-trait", - "bytes", - "cloudevents-sdk", - "lazy_static", - "reqwest", -] +checksum = "a6ef517f0926dd24a1582492c791b6a4818a4d94e789a334894aa15b0d12f55c" [[package]] -name = "const_fn" -version = "0.4.2" +name = "convert_case" +version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ce90df4c658c62f12d78f7508cf92f9173e5184a539c10bfe54a3107b3ffd0f2" +checksum = "633458d4ef8c78b72454de2d54fd6ab2e60f9e02be22f3c6104cdc8a4e0fceb9" +dependencies = [ + "unicode-segmentation", +] [[package]] name = "cookie" -version = "0.14.2" +version = "0.16.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1373a16a4937bc34efec7b391f9c1500c30b8478a701a4f44c9165cc0475a6e0" +checksum = "e859cd57d0710d9e06c381b550c06e76992472a8c6d527aecd2fc673dcc231fb" dependencies = [ "percent-encoding", - "time 0.2.22", + "time", "version_check", ] [[package]] -name = "copyless" -version = "0.1.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6ff9c56c9fb2a49c05ef0e431485a22400af20d33226dc0764d891d09e724127" - -[[package]] -name = "core-foundation" -version = "0.7.0" +name = "core-foundation-sys" +version = "0.8.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "57d24c7a13c43e870e37c1556b74555437870a04514f7685f5b354e090567171" -dependencies = [ - "core-foundation-sys", - "libc", -] +checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" [[package]] -name = "core-foundation-sys" -version = "0.7.0" +name = "core_detect" +version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b3a71ab494c0b5b860bdc8407ae08978052417070c2ced38573a9157ad75b8ac" +checksum = "7f8f80099a98041a3d1622845c271458a2d73e688351bf3cb999266764b81d48" [[package]] -name = "cpuid-bool" -version = "0.1.2" +name = "cpufeatures" +version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8aebca1129a03dc6dc2b127edd729435bbc4a37e1d5f4d7513165089ceb02634" +checksum = "5ca28b0ae3115b884660db4118d803791fd6756b6e88f39c0f3f7859060d7566" +dependencies = [ + "libc", +] [[package]] name = "crc32fast" -version = "1.2.0" +version = "1.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ba125de2af0df55319f41944744ad91c71113bf74a4646efff39afe1f6842db1" +checksum = "0ba106e844045ddc02c20768ac4b0b7cb45f2cc2ecbd833ac9f5dacb183c2055" dependencies = [ - "cfg-if 0.1.10", + "cfg-if", ] [[package]] -name = "ct-logs" -version = "0.6.0" +name = "crypto-common" +version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4d3686f5fa27dbc1d76c751300376e167c5a43387f44bb451fd1c24776e49113" +checksum = "ce6e4c961d6cd6c9a86db418387425e8bdeaf05b3c8bc1411e6dca4c252f1453" dependencies = [ - "sct", + "hybrid-array", ] [[package]] -name = "delegate-attr" -version = "0.2.9" +name = "defmt" +version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ee7e7ea0dba407429d816e8e38dda1a467cd74737722f2ccc8eae60429a1a3ab" +checksum = "e4a0795ac740cb54d7b3775c9c17345f6aaecf884c01733b39d6017738e0e3e1" dependencies = [ - "proc-macro2", - "quote", - "syn 1.0.96", + "bitflags 1.3.2", + "defmt-macros", ] [[package]] -name = "derive_more" -version = "0.99.7" +name = "defmt-macros" +version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2127768764f1556535c01b5326ef94bd60ff08dcfbdc544d53e69ed155610f5d" +checksum = "840bb056bd99c495c9188198a258ab787c79f306d864db3e6e8451442d6d8366" dependencies = [ + "defmt-parser", "proc-macro2", "quote", - "syn 1.0.96", + "syn 3.0.9", ] [[package]] -name = "digest" -version = "0.9.0" +name = "defmt-parser" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d3dd60d1080a57a05ab032377049e0591415d2b31afd7028356dbf3cc6dcb066" +checksum = "71958f69fb2de856c5dd51ac9377e986ca1aa5919f931bf5395a807277f889a0" dependencies = [ - "generic-array", + "thiserror", ] [[package]] -name = "discard" -version = "1.0.4" +name = "delegate-attr" +version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "212d0f5754cb6769937f4501cc0e67f4f4483c8d2c3e1e922ee9edbe4ab4c7c0" +checksum = "a84c9a9c129b98e707ac9a31e204c10c064dd9f949b7da362310ff1a8b137d79" +dependencies = [ + "proc-macro2", + "quote", + "syn 3.0.9", +] [[package]] -name = "doc-comment" -version = "0.3.3" +name = "deranged" +version = "0.5.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fea41bba32d969b513997752735605054bc0dfa92b4c56bf1189f2e174be7a10" +checksum = "9e9de72ce2ad1f90dc62fa25f0f430ef85eb4b0d8fa0be4f30373bc40a21d28e" [[package]] -name = "dtoa" -version = "0.4.5" +name = "derive_more" +version = "2.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4358a9e11b9a09cf52383b451b49a169e8d797b68aa02301ff586d70d9661ea3" +checksum = "d751e9e49156b02b44f9c1815bcb94b984cdcc4396ecc32521c739452808b134" +dependencies = [ + "derive_more-impl", +] [[package]] -name = "either" -version = "1.5.3" +name = "derive_more-impl" +version = "2.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bb1f6b1ce1c140482ea30ddd3335fc0024ac7ee112895426e0a629a6c20adfe3" +checksum = "799a97264921d8623a957f6c3b9011f3b5492f557bbb7a5a19b7fa6d06ba8dcb" +dependencies = [ + "convert_case", + "proc-macro2", + "quote", + "rustc_version", + "syn 2.0.119", + "unicode-xid", +] [[package]] -name = "encoding_rs" -version = "0.8.23" +name = "digest" +version = "0.11.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e8ac63f94732332f44fe654443c46f6375d1939684c17b0afb6cb56b0456e171" +checksum = "f1dd6dbb5841937940781866fa1281a1ff7bd3bf827091440879f9994983d5c2" dependencies = [ - "cfg-if 0.1.10", + "block-buffer", + "const-oid", + "crypto-common", ] [[package]] -name = "enum-as-inner" -version = "0.3.2" +name = "displaydoc" +version = "0.2.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bc4bfcfacb61d231109d1d55202c1f33263319668b168843e02ad4652725ec9c" +checksum = "c6232dd377dcc64799954cbd3a9bb882e9cdc1308ccd87b1c098f1fb2eaf82a8" dependencies = [ - "heck", "proc-macro2", "quote", - "syn 1.0.96", + "syn 3.0.9", ] [[package]] -name = "env_logger" -version = "0.7.1" +name = "encoding_rs" +version = "0.8.42" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e985e0451871ad22fb8d2b6b076e2028a502a0d3950998c2c5c0a4f9b5d9679" +dependencies = [ + "cfg-if", + "core_detect", + "multiversion_no_op", + "rustversion", + "scopeguard", + "simdutf8", +] + +[[package]] +name = "env_filter" +version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "44533bbbb3bb3c1fa17d9f2e4e38bbbaf8396ba82193c4cb1b6445d711445d36" +checksum = "900d271a03799a1ee8d1ca9b19893b48ca674a9284fefcfb85f05e74ed314217" dependencies = [ - "atty", - "humantime", "log", "regex", - "termcolor", ] +[[package]] +name = "env_logger" +version = "0.11.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "de671bd27a75a797dc9ae289ba1e77276e75e2026408aab65185384e2d5cd3f6" +dependencies = [ + "anstream", + "anstyle", + "env_filter", + "jiff", + "log", +] + +[[package]] +name = "equivalent" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "00d174d5400e5e8fd687ad1049e2f578285fa914201b1af7e8b112a4546bd826" + +[[package]] +name = "errno" +version = "0.3.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb" +dependencies = [ + "libc", + "windows-sys 0.61.2", +] + +[[package]] +name = "find-msvc-tools" +version = "0.1.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1c66915cc5391097de7a2b0b972e8505aa33c0df401fd3a6811dbdb32881ba48" + [[package]] name = "flate2" -version = "1.0.14" +version = "1.1.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2cfff41391129e0a856d6d822600b8d71179d46879e310417eb9c762eb178b42" +checksum = "6e634e2e0ebac1ee034020da1ca582e17ffe4e0f5e985823721e168928136dcb" dependencies = [ - "cfg-if 0.1.10", "crc32fast", - "libc", "miniz_oxide", + "zlib-rs", ] [[package]] @@ -748,36 +689,31 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" [[package]] -name = "form_urlencoded" -version = "1.0.1" +name = "foldhash" +version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5fc25a87fa4fd2094bffb06925852034d90a17f0d1e05197d4956d3555752191" -dependencies = [ - "matches", - "percent-encoding", -] +checksum = "77ce24cb58228fbb8aa041425bb1050850ac19177686ea6e0f41a70416f56fdb" [[package]] -name = "fuchsia-zircon" -version = "0.3.3" +name = "form_urlencoded" +version = "1.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2e9763c69ebaae630ba35f74888db465e49e259ba1bc0eda7d06f4a067615d82" +checksum = "cb4cb245038516f5f85277875cdaa4f7d2c9a0fa0468de06ed190163b1581fcf" dependencies = [ - "bitflags", - "fuchsia-zircon-sys", + "percent-encoding", ] [[package]] -name = "fuchsia-zircon-sys" -version = "0.3.3" +name = "futures" +version = "0.1.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3dcaa9ae7725d12cdb85b3ad99a434db70b468c09ded17e012d86b5c1010f7a7" +checksum = "3a471a38ef8ed83cd6e40aa59c1ffe17db6855c18e3604d9c4ed8c08ebc28678" [[package]] name = "futures" -version = "0.3.5" +version = "0.3.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e05b85ec287aac0dc34db7d4a569323df697f9c55b99b15d6b4ef8cde49f613" +checksum = "9a31d2a3fbaaeb2af2368bbdd904aa8e812d3c04a1ee10d3171f52d556e5d0a3" dependencies = [ "futures-channel", "futures-core", @@ -790,9 +726,9 @@ dependencies = [ [[package]] name = "futures-channel" -version = "0.3.28" +version = "0.3.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "955518d47e09b25bbebc7a18df10b81f0c766eaf4c4f1cccef2fca5f2a4fb5f2" +checksum = "b1f9e3d69d39e4862ffed03ed071a76f9a13ba1d9109d355b0f0aa6b15e393c4" dependencies = [ "futures-core", "futures-sink", @@ -800,15 +736,15 @@ dependencies = [ [[package]] name = "futures-core" -version = "0.3.28" +version = "0.3.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4bca583b7e26f571124fe5b7561d49cb2868d79116cfa0eefce955557c6fee8c" +checksum = "92d699e522242e69e3003b94ecc1f960f3a5e015aa7c5d7486e65ad01dd94f5e" [[package]] name = "futures-executor" -version = "0.3.5" +version = "0.3.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "10d6bb888be1153d3abeb9006b11b02cf5e9b209fda28693c31ae1e4e012e314" +checksum = "031b47cf1a3c6cc8bc2fc76cd437f521619387907d469316e7c0bc278f1f5432" dependencies = [ "futures-core", "futures-task", @@ -817,909 +753,987 @@ dependencies = [ [[package]] name = "futures-io" -version = "0.3.28" +version = "0.3.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4fff74096e71ed47f8e023204cfd0aa1289cd54ae5430a9523be060cdb849964" +checksum = "53c0fa8157de1303bfffdaa1cc2a673bfffb60102f76b0ef4441659124373fed" [[package]] name = "futures-macro" -version = "0.3.28" +version = "0.3.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "89ca545a94061b6365f2c7355b4b32bd20df3ff95f02da9329b34ccc3bd6ee72" +checksum = "9fb9654ba8355388abeb8dcb4fc62f511300867002afc858860463bdd9fe0c44" dependencies = [ "proc-macro2", "quote", - "syn 2.0.18", + "syn 3.0.9", ] [[package]] name = "futures-sink" -version = "0.3.28" +version = "0.3.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f43be4fe21a13b9781a69afa4985b0f6ee0e1afab2c6f454a8cf30e2b2237b6e" +checksum = "1944426bf7d03f1d14f708785e4b33efd750b36d48a157b836b3efc15ede8e1d" [[package]] name = "futures-task" -version = "0.3.28" +version = "0.3.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "76d3d132be6c0e6aa1534069c705a74a5997a356c0dc2f86a47765e5617c5b65" +checksum = "cd417de3d1d015fc3bfd2b1ea46dfc7bab72ef86f1cc7cc9c78e728b34a6d1fd" [[package]] name = "futures-util" -version = "0.3.28" +version = "0.3.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26b01e40b772d54cf6c6d721c1d1abd0647a0106a12ecaa1c186273392a69533" +checksum = "0d50a92467f8ba5dd6e3ee5d4bd04d73ab2e4e1c44474a0674821dfce14b79bc" dependencies = [ + "futures 0.1.31", "futures-channel", "futures-core", "futures-io", "futures-macro", "futures-sink", "futures-task", + "libc", "memchr", - "pin-project-lite 0.2.9", - "pin-utils", + "pin-project-lite", "slab", ] [[package]] -name = "fxhash" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c31b6d751ae2c7f11320402d34e41349dd1016f8d5d45e48c4312bc8625af50c" -dependencies = [ - "byteorder", -] - -[[package]] -name = "generic-array" -version = "0.14.4" +name = "getrandom" +version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "501466ecc8a30d1d3b7fc9229b122b2ce8ed6e9d9223f1138d4babb253e51817" +checksum = "ff2abc00be7fca6ebc474524697ae276ad847ad0a6b3faa4bcb027e9a4614ad0" dependencies = [ - "typenum", - "version_check", + "cfg-if", + "js-sys", + "libc", + "wasi", + "wasm-bindgen", ] [[package]] name = "getrandom" -version = "0.1.14" +version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7abc8dd8451921606d809ba32e95b6111925cd2906060d2dcc29c070220503eb" +checksum = "300e883d756b2e4ec94e02791f39b04b522276138852cfc41d9fb7e904106099" dependencies = [ - "cfg-if 0.1.10", + "cfg-if", + "js-sys", "libc", - "wasi", + "r-efi", + "rand_core", "wasm-bindgen", ] -[[package]] -name = "gimli" -version = "0.21.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bcc8e0c9bce37868955864dbecd2b1ab2bdf967e6f28066d65aaac620444b65c" - [[package]] name = "h2" -version = "0.2.5" +version = "0.3.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "79b7246d7e4b979c03fa093da39cfb3617a96bbeee6310af63991668d7e843ff" +checksum = "0beca50380b1fc32983fc1cb4587bfa4bb9e78fc259aad4a0032d2080309222d" dependencies = [ "bytes", "fnv", "futures-core", "futures-sink", "futures-util", - "http", + "http 0.2.12", "indexmap", - "log", "slab", "tokio", - "tokio-util 0.3.1", + "tokio-util", + "tracing", ] +[[package]] +name = "hashbrown" +version = "0.17.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed5909b6e89a2db4456e54cd5f673791d7eca6732202bbf2a9cc504fe2f9b84a" + [[package]] name = "heck" -version = "0.3.1" +version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "20564e78d53d2bb135c343b3f47714a56af2061f1c928fdb541dc7b9fdd94205" -dependencies = [ - "unicode-segmentation", -] +checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" [[package]] -name = "hermit-abi" -version = "0.1.13" +name = "hostname" +version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "91780f809e750b0a89f5544be56617ff6b1227ee485bcb06ebe10cdf89bd3b71" +checksum = "617aaa3557aef3810a6369d0a99fac8a080891b68bd9f9812a1eeda0c0730cbd" dependencies = [ + "cfg-if", "libc", + "windows-link", ] [[package]] -name = "hostname" -version = "0.3.1" +name = "http" +version = "0.2.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3c731c3e10504cc8ed35cfe2f1db4c9274c3d35fa486e3b31df46f068ef3e867" +checksum = "601cbb57e577e2f5ef5be8e7b83f0f63994f25aa94d673e54a92d5c516d101f1" dependencies = [ - "libc", - "match_cfg", - "winapi 0.3.8", + "bytes", + "fnv", + "itoa", ] [[package]] name = "http" -version = "0.2.1" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "28d569972648b2c512421b5f2a405ad6ac9666547189d0c5477a3f200f3e02f9" +checksum = "918d3568bebf352712bc2ef3d46a8bcf1a75b373be6539de198e9105cbbf9ce0" dependencies = [ "bytes", - "fnv", - "itoa 0.4.5", + "itoa", ] [[package]] name = "http-body" -version = "0.3.1" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca2a8f2913ee65f60facd6a5905613afaa448497a0230cc41ce022d93290bc2c" +dependencies = [ + "bytes", + "http 1.5.0", +] + +[[package]] +name = "http-body-util" +version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "13d5ff830006f7646652e057693569bfe0d51760c0085a071769d142a205111b" +checksum = "23169fe34a5fbcdd3f3862e78fb9b6fccd5f02a6dc6f732547005d45631ce71c" dependencies = [ "bytes", - "http", + "futures-core", + "http 1.5.0", + "http-body", + "pin-project-lite", ] [[package]] name = "httparse" -version = "1.3.4" +version = "1.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cd179ae861f0c2e53da70d892f5f3029f9594be0c41dc5269cd371691b1dc2f9" +checksum = "6dbf3de79e51f3d586ab4cb9d5c3e2c14aa28ed23d180cf89b4df0454a69cc87" [[package]] name = "httpdate" -version = "0.3.2" +version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "494b4d60369511e7dea41cf646832512a94e542f68bb9c49e54518e0f468eb47" +checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9" [[package]] -name = "humantime" -version = "1.3.0" +name = "hybrid-array" +version = "0.4.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "df004cfca50ef23c36850aaaa59ad52cc70d0e90243c3c7737a4dd32dc7a3c4f" +checksum = "27f864f10dfb56725ce5ce5472bc52252c8f93a4ab86327122cebf62c5f59a17" dependencies = [ - "quick-error", + "typenum", ] [[package]] name = "hyper" -version = "0.13.10" +version = "1.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8a6f157065790a3ed2f88679250419b5cdd96e714a0d65f7797fd337186e96bb" +checksum = "2c3e324da4c95177d6291d4c8730197c0d1822f8a9766814a4a44fa5ab797c9c" dependencies = [ + "atomic-waker", "bytes", - "futures-channel", "futures-core", - "futures-util", - "h2", - "http", + "http 1.5.0", "http-body", "httparse", - "httpdate", - "itoa 0.4.5", - "pin-project 1.0.10", - "socket2", + "itoa", + "pin-project-lite", + "smallvec", "tokio", - "tower-service", - "tracing", "want", ] [[package]] name = "hyper-rustls" -version = "0.20.0" +version = "0.27.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ac965ea399ec3a25ac7d13b8affd4b8f39325cca00858ddf5eb29b79e6b14b08" +checksum = "dfa8e654703247911e29c23fbeaa261834bd9bb74efba2f9acddc37bfb127f53" dependencies = [ - "bytes", - "ct-logs", - "futures-util", + "http 1.5.0", "hyper", - "log", + "hyper-util", "rustls", - "rustls-native-certs", "tokio", "tokio-rustls", - "webpki", + "tower-service", + "webpki-roots", ] [[package]] -name = "idna" -version = "0.2.0" +name = "hyper-util" +version = "0.1.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "02e2673c30ee86b5b96a9cb52ad15718aa1f966f5ab9ad54a8b95d5ca33120a9" +checksum = "ddc03d96684f9226b8a787cdb71488417b53ab5ea8fdb1dac946cb9431cc8bff" dependencies = [ - "matches", - "unicode-bidi", - "unicode-normalization", + "base64 0.23.1", + "bytes", + "futures-channel", + "futures-util", + "http 1.5.0", + "http-body", + "httparse", + "hyper", + "ipnet", + "libc", + "percent-encoding", + "pin-project-lite", + "socket2", + "tokio", + "tower-service", + "tracing", ] [[package]] -name = "indexmap" -version = "1.3.2" +name = "iana-time-zone" +version = "0.1.65" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "076f042c5b7b98f31d205f1249267e12a6518c1481e9dae9764af19b707d2292" +checksum = "e31bc9ad994ba00e440a8aa5c9ef0ec67d5cb5e5cb0cc7f8b744a35b389cc470" dependencies = [ - "autocfg", + "android_system_properties", + "core-foundation-sys", + "iana-time-zone-haiku", + "js-sys", + "log", + "wasm-bindgen", + "windows-core", ] [[package]] -name = "iovec" -version = "0.1.4" +name = "iana-time-zone-haiku" +version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b2b3ea6ff95e175473f8ffe6a7eb7c00d054240321b84c57051175fe3c1e075e" +checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f" dependencies = [ - "libc", + "cc", ] [[package]] -name = "ipconfig" -version = "0.2.2" +name = "icu_collections" +version = "2.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f7e2f18aece9709094573a9f24f483c4f65caa4298e2f7ae1b71cc65d853fad7" +checksum = "fe090a42a0d3140fed8c63263ed1fecc02422cb2f4bfe55cd8de2de1b8e105dd" dependencies = [ - "socket2", - "widestring", - "winapi 0.3.8", - "winreg", + "displaydoc", + "potential_utf", + "utf8_iter", + "yoke", + "zerofrom", + "zerovec", ] [[package]] -name = "itoa" -version = "0.4.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b8b7a7c0c47db5545ed3fef7468ee7bb5b74691498139e4b3f6a20685dc6dd8e" - -[[package]] -name = "itoa" -version = "1.0.18" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682" - -[[package]] -name = "js-sys" -version = "0.3.39" +name = "icu_locale_core" +version = "2.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fa5a448de267e7358beaf4a5d849518fe9a0c13fce7afd44b06e68550e5562a7" +checksum = "25d29b57c17c6528d0dc4c2141cfa2d08200465acb537a3909c8b9b8a4a984c0" dependencies = [ - "wasm-bindgen", + "displaydoc", + "litemap", + "tinystr", + "writeable", + "zerovec", ] [[package]] -name = "kernel32-sys" -version = "0.2.2" +name = "icu_normalizer" +version = "2.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7507624b29483431c0ba2d82aece8ca6cdba9382bff4ddd0f7490560c056098d" -dependencies = [ - "winapi 0.2.8", - "winapi-build", -] - -[[package]] -name = "knative-cloudevents-example" -version = "0.1.0" +checksum = "404a3b8c486f205c87fe7f23ab6894d84a4ad76d6e4671ae1981ba7def87c599" dependencies = [ - "actix-rt", - "actix-web", - "cloudevents-sdk", - "cloudevents-sdk-actix-web", - "cloudevents-sdk-reqwest", - "env_logger", - "log", - "reqwest", - "serde_json", - "url", + "icu_collections", + "icu_normalizer_data", + "icu_properties", + "icu_provider", + "smallvec", + "zerovec", ] [[package]] -name = "language-tags" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a91d884b6667cd606bb5a69aa0c99ba811a115fc68915e7056ec08a46e93199a" - -[[package]] -name = "lazy_static" -version = "1.4.0" +name = "icu_normalizer_data" +version = "2.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" +checksum = "20f0088bfb7441f7a37fa0c9e20cb1e9cba1cac6b9ce8cd2a6dc44294f8e8cbc" [[package]] -name = "libc" -version = "0.2.70" +name = "icu_properties" +version = "2.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3baa92041a6fec78c687fa0cc2b3fae8884f743d672cf551bed1d6dac6988d0f" +checksum = "2ea83d991c56158f7cf898bf86436c1dfe7c07ee82fe88951f2695c9f37a379f" +dependencies = [ + "displaydoc", + "icu_collections", + "icu_locale_core", + "icu_properties_data", + "icu_provider", + "zerotrie", + "zerovec", +] [[package]] -name = "linked-hash-map" -version = "0.5.3" +name = "icu_properties_data" +version = "2.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8dd5a6d5999d9907cda8ed67bbd137d3af8085216c2ac62de5be860bd41f304a" +checksum = "b98f29af82d02b163fe50161388350e743bb3107cc8e8c2f7314cb505faddfcf" [[package]] -name = "lock_api" -version = "0.3.4" +name = "icu_provider" +version = "2.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c4da24a77a3d8a6d4862d95f72e6fdb9c09a643ecdb402d754004a557f2bec75" +checksum = "413099315636c588ee8f011190b329b971a09a8fae9d157e01dcae8379468514" dependencies = [ - "scopeguard", + "displaydoc", + "icu_locale_core", + "writeable", + "yoke", + "zerofrom", + "zerotrie", + "zerovec", ] [[package]] -name = "log" -version = "0.4.8" +name = "idna" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "14b6052be84e6b71ab17edffc2eeabf5c2c3ae1fdb464aae35ac50c67a44e1f7" +checksum = "3b0875f23caa03898994f6ddc501886a45c7d3d62d04d2d90788d47be1b1e4de" dependencies = [ - "cfg-if 0.1.10", + "idna_adapter", + "smallvec", + "utf8_iter", ] [[package]] -name = "lru-cache" -version = "0.1.2" +name = "idna_adapter" +version = "1.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "31e24f1ad8321ca0e8a1e0ac13f23cb668e6f5466c2c57319f6a5cf1cc8e3b1c" +checksum = "cb68373c0d6620ef8105e855e7745e18b0d00d3bdb07fb532e434244cdb9a714" dependencies = [ - "linked-hash-map", + "icu_normalizer", + "icu_properties", ] [[package]] -name = "match_cfg" -version = "0.1.0" +name = "impl-more" +version = "0.3.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ffbee8634e0d45d258acb448e7eaab3fce7a0a467395d4d9f228e3c1f01fb2e4" +checksum = "2d3a73c82a0b0747dba739b380c046a140b5ae747234bf701df3460282da7193" [[package]] -name = "matches" -version = "0.1.8" +name = "indexmap" +version = "2.14.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7ffc5c5338469d4d3ea17d269fa8ea3512ad247247c30bd2df69e68309ed0a08" +checksum = "cc4e190f5d26ca7051642629da2c52fc03bde85a03197c99408dcd291734c855" +dependencies = [ + "equivalent", + "hashbrown", +] [[package]] -name = "memchr" -version = "2.3.3" +name = "ipnet" +version = "2.12.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3728d817d99e5ac407411fa471ff9800a778d88a24685968b36824eaf4bee400" +checksum = "791930b43c0d5973160d90a8f3894509f2b273430f5c5c73b668636d0287c5c0" [[package]] -name = "mime" -version = "0.3.16" +name = "is_terminal_polyfill" +version = "1.70.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2a60c7ce501c71e03a9c9c0d35b861413ae925bd979cc7a4e30d060069aaac8d" +checksum = "a6cb138bb79a146c1bd460005623e142ef0181e3d0219cb493e02f7d08a35695" [[package]] -name = "mime_guess" -version = "2.0.3" +name = "itoa" +version = "1.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2684d4c2e97d99848d30b324b00c8fcc7e5c897b7cbb5819b09e7c90e8baf212" -dependencies = [ - "mime", - "unicase", -] +checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682" [[package]] -name = "miniz_oxide" -version = "0.3.6" +name = "jiff" +version = "0.2.38" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aa679ff6578b1cddee93d7e82e263b94a575e0bfced07284eb0c037c1d2416a5" +checksum = "b2b005715dcbeb0089a3c0dab99f2ff1cc3b2525323552703d648585d342a383" dependencies = [ - "adler32", + "defmt", + "jiff-core", + "jiff-static", + "log", + "portable-atomic", + "portable-atomic-util", + "serde_core", ] [[package]] -name = "mio" -version = "0.6.22" +name = "jiff-core" +version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fce347092656428bc8eaf6201042cb551b8d67855af7374542a92a0fbfcac430" +checksum = "5e52fe76043ccecc9005d2305ebaadf7d7fc0cc89ca6baa10a94d6bc68c7128c" dependencies = [ - "cfg-if 0.1.10", - "fuchsia-zircon", - "fuchsia-zircon-sys", - "iovec", - "kernel32-sys", - "libc", + "defmt", "log", - "miow", - "net2", - "slab", - "winapi 0.2.8", ] [[package]] -name = "mio-uds" -version = "0.6.8" +name = "jiff-static" +version = "0.2.38" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "afcb699eb26d4332647cc848492bbc15eafb26f08d0304550d5aa1f612e066f0" +checksum = "2cc9817253cf7c7ee4684451bd327e88d6f3658014e54a29198625590650695c" dependencies = [ - "iovec", - "libc", - "mio", + "jiff-core", + "proc-macro2", + "quote", + "syn 2.0.119", ] [[package]] -name = "miow" -version = "0.2.2" +name = "jobserver" +version = "0.1.35" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ebd808424166322d4a38da87083bfddd3ac4c131334ed55856112eb06d46944d" +checksum = "1c00acbd29eabad4a2392fa0e921c874934dbbf4194312ad20f04a0ed67a3cb3" dependencies = [ - "kernel32-sys", - "net2", - "winapi 0.2.8", - "ws2_32-sys", + "getrandom 0.4.3", + "libc", ] [[package]] -name = "net2" -version = "0.2.37" +name = "js-sys" +version = "0.3.106" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "391630d12b68002ae1e25e8f974306474966550ad82dac6886fb8910c19568ae" +checksum = "7883d941dae510fb2d978fc3fe018c71c9e2892fd38854de3e8b92c2e5ad9cc5" dependencies = [ - "cfg-if 0.1.10", - "libc", - "winapi 0.3.8", + "cfg-if", + "futures-util", + "wasm-bindgen", ] [[package]] -name = "num-integer" -version = "0.1.42" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3f6ea62e9d81a77cd3ee9a2a5b9b609447857f3d358704331e4ef39eb247fcba" +name = "knative-cloudevents-example" +version = "0.1.0" dependencies = [ - "autocfg", - "num-traits", + "actix-web", + "cloudevents-sdk", + "env_logger", + "log", + "reqwest", ] [[package]] -name = "num-traits" -version = "0.2.11" +name = "language-tags" +version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c62be47e61d1842b9170f0fdeec8eba98e60e90e5446449a0545e5152acd7096" -dependencies = [ - "autocfg", -] +checksum = "d4345964bb142484797b161f473a503a434de77149dd8c7427788c6e13379388" [[package]] -name = "num_cpus" -version = "1.13.0" +name = "libc" +version = "0.2.191" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "05499f3756671c15885fee9034446956fff3f243d6077b91e5767df161f766b3" +checksum = "65cd87c9a6d60536580b316ad985726104730a5151c99845f2490428ef91240a" + +[[package]] +name = "litemap" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "47d9d19d1d6efa0109d2f65ff4c85cddd50bd572e5a00127ab10987290bcefae" + +[[package]] +name = "local-channel" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6cbc85e69b8df4b8bb8b89ec634e7189099cea8927a276b7384ce5488e53ec8" dependencies = [ - "hermit-abi", - "libc", + "futures-core", + "futures-sink", + "local-waker", ] [[package]] -name = "object" -version = "0.19.0" +name = "local-waker" +version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9cbca9424c482ee628fa549d9c812e2cd22f1180b9222c9200fdfa6eb31aecb2" +checksum = "4d873d7c67ce09b42110d801813efbc9364414e356be9935700d368351657487" [[package]] -name = "once_cell" -version = "1.4.0" +name = "lock_api" +version = "0.4.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "224399e74b87b5f3557511d98dff8b14089b3dadafcab6bb93eab67d3aace965" +dependencies = [ + "scopeguard", +] + +[[package]] +name = "log" +version = "0.4.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b631f7e854af39a1739f401cf34a8a013dfe09eac4fa4dba91e9768bd28168d" +checksum = "f9f8bd3e56ce4dfc153cf470fffbfa98c7620958b312ca5c3a4b8d5181fd13c6" [[package]] -name = "opaque-debug" -version = "0.3.0" +name = "lru-slab" +version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "624a8340c38c1b80fd549087862da4ba43e08858af025b236e509b6649fc13d5" +checksum = "4050469837a6ff301cd14c1f8f24f88549e6d548f24f64e2148eb0f72cebc51f" [[package]] -name = "openssl-probe" -version = "0.1.2" +name = "memchr" +version = "2.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "77af24da69f9d9341038eba93a073b1fdaaa1b788221b00a69bce9e762cb32de" +checksum = "cf8baf1c55e62ffcace7a9f06f4bd9cd3f0c4beb022d3b367256b91b87513d98" [[package]] -name = "parking_lot" -version = "0.10.2" +name = "mime" +version = "0.3.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" + +[[package]] +name = "miniz_oxide" +version = "0.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d3a704eb390aafdc107b0e392f56a82b668e3a71366993b5340f5833fd62505e" +checksum = "b63fbc4a50860e98e7b2aa7804ded1db5cbc3aff9193adaff57a6931bf7c4b4c" dependencies = [ - "lock_api", - "parking_lot_core", + "adler2", + "simd-adler32", ] [[package]] -name = "parking_lot_core" -version = "0.7.2" +name = "mio" +version = "1.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d58c7c768d4ba344e3e8d72518ac13e259d7c7ade24167003b8488e10b6740a3" +checksum = "1788edb87fdc09c7e26304471e2f5be8cdefb1b6930d6e3985fc02ff53bf86ee" dependencies = [ - "cfg-if 0.1.10", - "cloudabi", "libc", - "redox_syscall", - "smallvec", - "winapi 0.3.8", + "log", + "wasi", + "windows-sys 0.61.2", ] [[package]] -name = "percent-encoding" -version = "2.1.0" +name = "multiversion_no_op" +version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d4fd5641d01c8f18a23da7b6fe29298ff4b55afcccdf78973b24cf3175fee32e" +checksum = "743fb55ba31b18fb1ecef6bdc9aa2743314978ac084044301a7eee33fb99a20d" [[package]] -name = "pin-project" -version = "0.4.17" +name = "num-conv" +version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "edc93aeee735e60ecb40cf740eb319ff23eab1c5748abfdb5c180e4ce49f7791" -dependencies = [ - "pin-project-internal 0.4.17", -] +checksum = "521739c6d2bac4aa25192232afe6841231376b2b26d4d9fae5ecf8ca5772e441" [[package]] -name = "pin-project" -version = "1.0.10" +name = "num-traits" +version = "0.2.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "58ad3879ad3baf4e44784bc6a718a8698867bb991f8ce24d1bcbe2cfb4c3a75e" +checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" dependencies = [ - "pin-project-internal 1.0.10", + "autocfg", ] [[package]] -name = "pin-project-internal" -version = "0.4.17" +name = "once_cell" +version = "1.21.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50" + +[[package]] +name = "once_cell_polyfill" +version = "1.70.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "384b8ab6d37215f3c5301a95a4accb5d64aa607f1fcb26a11b5303878451b4fe" + +[[package]] +name = "parking_lot" +version = "0.12.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e58db2081ba5b4c93bd6be09c40fd36cb9193a8336c384f3b40012e531aa7e40" +checksum = "93857453250e3077bd71ff98b6a65ea6621a19bb0f559a85248955ac12c45a1a" dependencies = [ - "proc-macro2", - "quote", - "syn 1.0.96", + "lock_api", + "parking_lot_core", ] [[package]] -name = "pin-project-internal" -version = "1.0.10" +name = "parking_lot_core" +version = "0.9.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "744b6f092ba29c3650faf274db506afd39944f48420f6c86b17cfe0ee1cb36bb" +checksum = "2621685985a2ebf1c516881c026032ac7deafcda1a2c9b7850dc81e3dfcb64c1" dependencies = [ - "proc-macro2", - "quote", - "syn 1.0.96", + "cfg-if", + "libc", + "redox_syscall", + "smallvec", + "windows-link", ] [[package]] -name = "pin-project-lite" -version = "0.1.5" +name = "percent-encoding" +version = "2.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f7505eeebd78492e0f6108f7171c4948dbb120ee8119d9d77d0afa5469bef67f" +checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220" [[package]] name = "pin-project-lite" -version = "0.2.9" +version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e0a7ae3ac2f1173085d398531c705756c94a4c56843785df85a60c1a0afac116" +checksum = "a89322df9ebe1c1578d689c92318e070967d1042b512afbe49518723f4e6d5cd" [[package]] -name = "pin-utils" -version = "0.1.0" +name = "pkg-config" +version = "0.3.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6b464fbc74e149a392436b17d523f769e057cb6877f6a5c4618bc6f11800548" + +[[package]] +name = "portable-atomic" +version = "1.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" +checksum = "05c8b63e8d9609db387f0324918f81d68fe27748f084ef092fb35954d0539a85" [[package]] -name = "ppv-lite86" +name = "portable-atomic-util" version = "0.2.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "237a5ed80e274dbc66f86bd59c1e25edc039660be53194b5fe0a482e0f2612ea" +checksum = "10ab3eb7f3becc3a1cbc4f2c6f20267996cfc1a6467a873763411b136a122715" +dependencies = [ + "portable-atomic", +] + +[[package]] +name = "potential_utf" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d83eb9bc6d8e5cf568e7a1101d60ee05e81ed50ea106026f3d18deeb046d7661" +dependencies = [ + "zerovec", +] [[package]] -name = "proc-macro-hack" -version = "0.5.15" +name = "powerfmt" +version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0d659fe7c6d27f25e9d80a1a094c223f5246f6a6596453e09d7229bf42750b63" +checksum = "4a6394b9e965e73d0a289ee54f589087e2c676aedf60885baf52c76b771e4958" [[package]] name = "proc-macro2" -version = "1.0.60" +version = "1.0.107" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dec2b086b7a862cf4de201096214fa870344cf922b2b30c167badb3af3195406" +checksum = "985e7ec9bb745e6ce6535b544d84d6cd6f7ad8bd711c398938ae983b91a766d9" dependencies = [ "unicode-ident", ] [[package]] -name = "quick-error" -version = "1.2.3" +name = "quinn" +version = "0.11.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a1d01941d82fa2ab50be1e79e6714289dd7cde78eba4c074bc5a4374f650dfe0" +checksum = "4051e23e9185c255a7e33ef59cdbca87a22d359052eecd22fc6b901fb37d9d11" +dependencies = [ + "bytes", + "cfg_aliases", + "pin-project-lite", + "quinn-proto", + "quinn-udp", + "rustc-hash", + "rustls", + "socket2", + "thiserror", + "tokio", + "tracing", + "web-time", +] [[package]] -name = "quote" -version = "1.0.28" +name = "quinn-proto" +version = "0.11.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1b9ab9c7eadfd8df19006f1cf1a4aed13540ed5cbc047010ece5826e10825488" +checksum = "0e750cca55fe4f0439a15d0bb529da9651e79993e8e72c61a899a36d462befbe" dependencies = [ - "proc-macro2", + "bytes", + "getrandom 0.4.3", + "lru-slab", + "rand", + "rand_pcg", + "ring", + "rustc-hash", + "rustls", + "rustls-pki-types", + "slab", + "thiserror", + "tinyvec", + "tracing", + "web-time", ] [[package]] -name = "rand" -version = "0.7.3" +name = "quinn-udp" +version = "0.5.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6a6b1679d49b24bbfe0c803429aa1874472f50d9b363131f0e89fc356b544d03" +checksum = "af66907df18639dcf4db56ca65490cabc4b27a97dbadd96f2926cca73298f016" dependencies = [ - "getrandom", + "cfg_aliases", "libc", - "rand_chacha", - "rand_core", - "rand_hc", + "once_cell", + "socket2", + "tracing", + "windows-sys 0.61.2", ] [[package]] -name = "rand_chacha" -version = "0.2.2" +name = "quote" +version = "1.0.47" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f4c8ed856279c9737206bf725bf36935d8666ead7aa69b52be55af369d193402" +checksum = "1fbf4db142a473a8d80c26bbf18454ed458bf8d26c8219c331daecfdbd079001" dependencies = [ - "ppv-lite86", - "rand_core", + "proc-macro2", ] [[package]] -name = "rand_core" -version = "0.5.1" +name = "r-efi" +version = "6.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8dcc9c7d52a811697d2151c701e0d08956f92b0e24136cf4cf27b57a6a0d9bf" + +[[package]] +name = "rand" +version = "0.10.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "90bde5296fc891b0cef12a6d03ddccc162ce7b2aff54160af9338f8d40df6d19" +checksum = "65c9fb96cbc91e3478eaae79a69fcd3f1ae4ad052e471fe6732fff548984b4af" dependencies = [ - "getrandom", + "chacha20", + "getrandom 0.4.3", + "rand_core", ] [[package]] -name = "rand_hc" -version = "0.2.0" +name = "rand_core" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "63b8176103e19a2643978565ca18b50549f6101881c443590420e4dc998a3c69" + +[[package]] +name = "rand_pcg" +version = "0.10.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ca3129af7b92a17112d59ad498c6f81eaf463253766b90396d39ea7a39d6613c" +checksum = "caa0f4137e1c0a72f4c651489402276c8e8e1cf081f3b0ba156d2cbeef09e86a" dependencies = [ "rand_core", ] [[package]] name = "redox_syscall" -version = "0.1.56" +version = "0.5.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2439c63f3f6139d1b57529d16bc3b8bb855230c8efcc5d3a896c8bea7c3b1e84" +checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d" +dependencies = [ + "bitflags 2.13.2", +] [[package]] name = "regex" -version = "1.3.7" +version = "1.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f020237b6c8eed93db2e2cb53c00c60a8e1bc73da7d073199a1180401450218d" +dependencies = [ + "aho-corasick", + "memchr", + "regex-automata", + "regex-syntax", +] + +[[package]] +name = "regex-automata" +version = "0.4.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a6020f034922e3194c711b82a627453881bc4682166cabb07134a10c26ba7692" +checksum = "ad8553b9b26413251cbf30e620595c7a41b3887f03da04579c0e6b0d6a06b4b2" dependencies = [ "aho-corasick", "memchr", "regex-syntax", - "thread_local", ] +[[package]] +name = "regex-lite" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cab834c73d247e67f4fae452806d17d3c7501756d98c8808d7c9c7aa7d18f973" + [[package]] name = "regex-syntax" -version = "0.6.17" +version = "0.8.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7fe5bd57d1d7414c6b5ed48563a2c855d995ff777729dcd91c369ec7fea395ae" +checksum = "d6f6ff9a378485b298a5286656da665ba74413d36db0979633275d2e708145d4" [[package]] name = "reqwest" -version = "0.10.4" +version = "0.12.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "02b81e49ddec5109a9dcfc5f2a317ff53377c915e9ae9d4f2fb50914b85614e2" +checksum = "eddd3ca559203180a307f12d114c268abf583f59b03cb906fd0b3ff8646c1147" dependencies = [ - "base64 0.11.0", + "base64 0.22.1", "bytes", - "encoding_rs", "futures-core", - "futures-util", - "http", + "http 1.5.0", "http-body", + "http-body-util", "hyper", "hyper-rustls", + "hyper-util", "js-sys", - "lazy_static", "log", - "mime", - "mime_guess", "percent-encoding", - "pin-project-lite 0.1.5", + "pin-project-lite", + "quinn", "rustls", + "rustls-pki-types", "serde", - "serde_urlencoded 0.6.1", - "time 0.1.43", + "serde_json", + "serde_urlencoded", + "sync_wrapper", "tokio", "tokio-rustls", + "tower", + "tower-http", + "tower-service", "url", "wasm-bindgen", "wasm-bindgen-futures", "web-sys", "webpki-roots", - "winreg", -] - -[[package]] -name = "resolv-conf" -version = "0.6.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "11834e137f3b14e309437a8276714eed3a80d1ef894869e510f2c0c0b98b9f4a" -dependencies = [ - "hostname", - "quick-error", ] [[package]] name = "ring" -version = "0.16.13" +version = "0.17.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "703516ae74571f24b465b4a1431e81e2ad51336cb0ded733a55a1aa3eccac196" +checksum = "a4689e6c2294d81e88dc6261c768b63bc4fcdb852be6d1352498b114f61383b7" dependencies = [ "cc", + "cfg-if", + "getrandom 0.2.17", "libc", - "once_cell", - "spin", "untrusted", - "web-sys", - "winapi 0.3.8", + "windows-sys 0.52.0", ] [[package]] -name = "rustc-demangle" -version = "0.1.16" +name = "rustc-hash" +version = "2.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c691c0e608126e00913e33f0ccf3727d5fc84573623b8d65b2df340b5201783" +checksum = "6b1e7f9a428571be2dc5bc0505c13fb6bf936822b894ec87abf8a08a4e51742d" [[package]] name = "rustc_version" -version = "0.2.3" +version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "138e3e0acb6c9fb258b19b67cb8abd63c00679d2851805ea151465464fe9030a" +checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92" dependencies = [ "semver", ] [[package]] name = "rustls" -version = "0.17.0" +version = "0.23.46" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c0d4a31f5d68413404705d6982529b0e11a9aacd4839d1d6222ee3b8cb4015e1" +checksum = "48e13bd8c0e9365c43cfa5c9e8f9ad49d3c8444926c9aac819e0e4dc503c8fdf" dependencies = [ - "base64 0.11.0", - "log", + "once_cell", "ring", - "sct", - "webpki", + "rustls-pki-types", + "rustls-webpki", + "subtle", + "zeroize", ] [[package]] -name = "rustls-native-certs" -version = "0.3.0" +name = "rustls-pki-types" +version = "1.15.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a75ffeb84a6bd9d014713119542ce415db3a3e4748f0bfce1e1416cd224a23a5" +checksum = "2f4925028c7eb5d1fcdaf196971378ed9d2c1c4efc7dc5d011256f76c99c0a96" dependencies = [ - "openssl-probe", - "rustls", - "schannel", - "security-framework", + "web-time", + "zeroize", ] [[package]] -name = "ryu" -version = "1.0.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ed3d612bc64430efeb3f7ee6ef26d590dce0c43249217bddc62112540c7941e1" - -[[package]] -name = "schannel" -version = "0.1.19" +name = "rustls-webpki" +version = "0.103.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f05ba609c234e60bee0d547fe94a4c7e9da733d1c962cf6e59efa4cd9c8bc75" +checksum = "4443b1b6d39d7c663139942cc4558262562d2c84123bb6282ef177009ca8b281" dependencies = [ - "lazy_static", - "winapi 0.3.8", + "ring", + "rustls-pki-types", + "untrusted", ] [[package]] -name = "scopeguard" -version = "1.1.0" +name = "rustversion" +version = "1.0.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd" +checksum = "cf54715a573b99ac80df0bc206da022bcd442c974952c7b9720069370852e21f" [[package]] -name = "sct" -version = "0.6.0" +name = "ryu" +version = "1.0.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e3042af939fca8c3453b7af0f1c66e533a15a86169e39de2657310ade8f98d3c" -dependencies = [ - "ring", - "untrusted", -] +checksum = "9774ba4a74de5f7b1c1451ed6cd5285a32eddb5cccb8cc655a4e50009e06477f" [[package]] -name = "security-framework" -version = "0.4.4" +name = "scopeguard" +version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "64808902d7d99f78eaddd2b4e2509713babc3dc3c85ad6f4c447680f3c01e535" -dependencies = [ - "bitflags", - "core-foundation", - "core-foundation-sys", - "libc", - "security-framework-sys", -] +checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" [[package]] -name = "security-framework-sys" -version = "0.4.3" +name = "semver" +version = "1.0.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "17bf11d99252f512695eb468de5516e5cf75455521e69dfe343f3b74e4748405" -dependencies = [ - "core-foundation-sys", - "libc", -] +checksum = "8a7852d02fc848982e0c167ef163aaff9cd91dc640ba85e263cb1ce46fae51cd" [[package]] -name = "semver" -version = "0.9.0" +name = "serde" +version = "1.0.229" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1d7eb9ef2c18661902cc47e535f9bc51b78acd254da71d375c2f6720d9a40403" +checksum = "4148590afebada386688f18773da617792bf2ef03ffc1e4cbd2b1d45b023e0ba" dependencies = [ - "semver-parser", + "serde_core", + "serde_derive", ] [[package]] -name = "semver-parser" -version = "0.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "388a1df253eca08550bef6c72392cfe7c30914bf41df5269b68cbd6ff8f570a3" - -[[package]] -name = "serde" -version = "1.0.110" +name = "serde_core" +version = "1.0.229" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "99e7b308464d16b56eba9964e4972a3eee817760ab60d88c3f86e1fecb08204c" +checksum = "67dca2c9c51e58a4791a4b1ed58308b39c64224d349a935ab5039aa360942a48" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.110" +version = "1.0.229" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "818fbf6bfa9a42d3bfcaca148547aa00c7b915bec71d1757aa2d44ca68771984" +checksum = "e7a5d71263a5a7d47b41f6b3f06ba276f10cc18b0931f1799f710578e2309348" dependencies = [ "proc-macro2", "quote", - "syn 1.0.96", + "syn 3.0.9", ] [[package]] name = "serde_json" -version = "1.0.53" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "993948e75b189211a9b31a7528f950c6adc21f9720b6438ff80a7fa2f864cea2" -dependencies = [ - "itoa 0.4.5", - "ryu", - "serde", -] - -[[package]] -name = "serde_urlencoded" -version = "0.6.1" +version = "1.0.154" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9ec5d77e2d4c73717816afac02670d5c4f534ea95ed430442cad02e7a6e32c97" +checksum = "e7e9cc8b1b85264074fbcc02a88680c4096b1e47df8f739dceb03bf482f04bd6" dependencies = [ - "dtoa", - "itoa 0.4.5", + "itoa", + "memchr", "serde", - "url", + "serde_core", + "zmij", ] [[package]] @@ -1729,151 +1743,121 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd" dependencies = [ "form_urlencoded", - "itoa 1.0.18", + "itoa", "ryu", "serde", ] [[package]] -name = "sha-1" -version = "0.9.1" +name = "sha1" +version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "170a36ea86c864a3f16dd2687712dd6646f7019f301e57537c7f4dc9f5916770" +checksum = "aacc4cc499359472b4abe1bf11d0b12e688af9a805fa5e3016f9a386dc2d0214" dependencies = [ - "block-buffer", - "cfg-if 0.1.10", - "cpuid-bool", + "cfg-if", + "cpufeatures", "digest", - "opaque-debug", ] [[package]] -name = "sha1" -version = "0.6.0" +name = "shlex" +version = "2.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2579985fda508104f7587689507983eadd6a6e84dd35d6d115361f530916fa0d" +checksum = "f8fadd59c855ef2080decdef8ff161eb6661b86933c9d82e5ba29dc602a55aba" [[package]] name = "signal-hook-registry" -version = "1.2.0" +version = "1.4.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "94f478ede9f64724c5d173d7bb56099ec3e2d9fc2774aac65d34b8b890405f41" +checksum = "c4db69cba1110affc0e9f7bcd48bbf87b3f4fc7c61fc9155afd4c469eb3d6c1b" dependencies = [ - "arc-swap", + "errno", "libc", ] +[[package]] +name = "simd-adler32" +version = "0.3.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3a219298ac11a56ea9a6d2120044824d6f01aeb034955e7af7bc16858527deea" + +[[package]] +name = "simdutf8" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3a9fe34e3e7a50316060351f37187a3f546bce95496156754b601a5fa71b76e" + [[package]] name = "slab" -version = "0.4.2" +version = "0.4.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c111b5bd5695e56cffe5129854aa230b39c93a305372fdbb2668ca2394eea9f8" +checksum = "0c790de23124f9ab44544d7ac05d60440adc586479ce501c1d6d7da3cd8c9cf5" [[package]] name = "smallvec" -version = "1.8.0" +version = "1.16.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f2dd574626839106c320a323308629dcb1acfc96e32a8cba364ddc61ac23ee83" +checksum = "5b3dc8af474f516a851ff4bd12db780f948b9250ad37211e4eec0bccea54e01b" [[package]] name = "snafu" -version = "0.6.8" +version = "0.8.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c7f5aed652511f5c9123cf2afbe9c244c29db6effa2abb05c866e965c82405ce" +checksum = "6e84b3f4eacbf3a1ce05eac6763b4d629d60cbc94d632e4092c54ade71f1e1a2" dependencies = [ - "doc-comment", "snafu-derive", ] [[package]] name = "snafu-derive" -version = "0.6.8" +version = "0.8.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ebf8f7d5720104a9df0f7076a8682024e958bba0fe9848767bb44f251f3648e9" +checksum = "c1c97747dbf44bb1ca44a561ece23508e99cb592e862f22222dcf42f51d1e451" dependencies = [ + "heck", "proc-macro2", "quote", - "syn 1.0.96", + "syn 2.0.119", ] [[package]] name = "socket2" -version = "0.3.12" +version = "0.6.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "03088793f677dce356f3ccc2edb1b314ad191ab702a5de3faf49304f7e104918" +checksum = "c3d1e2c7f27f8d4cb10542a02c49005dbd6e93095799d6f3be745fae9f8fedd4" dependencies = [ - "cfg-if 0.1.10", "libc", - "redox_syscall", - "winapi 0.3.8", + "windows-sys 0.61.2", ] [[package]] -name = "spin" -version = "0.5.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6e63cff320ae2c57904679ba7cb63280a3dc4613885beafb148ee7bf9aa9042d" - -[[package]] -name = "standback" -version = "0.2.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "47e4b8c631c998468961a9ea159f064c5c8499b95b5e4a34b77849d45949d540" - -[[package]] -name = "stdweb" -version = "0.4.20" +name = "stable_deref_trait" +version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d022496b16281348b52d0e30ae99e01a73d737b2f45d38fed4edf79f9325a1d5" -dependencies = [ - "discard", - "rustc_version", - "stdweb-derive", - "stdweb-internal-macros", - "stdweb-internal-runtime", - "wasm-bindgen", -] +checksum = "6ce2be8dc25455e1f91df71bfa12ad37d7af1092ae736f3a6cd0e37bc7810596" [[package]] -name = "stdweb-derive" -version = "0.5.3" +name = "subtle" +version = "2.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c87a60a40fccc84bef0652345bbbbbe20a605bf5d0ce81719fc476f5c03b50ef" -dependencies = [ - "proc-macro2", - "quote", - "serde", - "serde_derive", - "syn 1.0.96", -] +checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" [[package]] -name = "stdweb-internal-macros" -version = "0.2.9" +name = "syn" +version = "2.0.119" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "58fa5ff6ad0d98d1ffa8cb115892b6e69d67799f6763e162a1c9db421dc22e11" +checksum = "872831b642d1a07999a962a351ed35b955ea2cfc8f3862091e2a240a84f17297" dependencies = [ - "base-x", "proc-macro2", "quote", - "serde", - "serde_derive", - "serde_json", - "sha1", - "syn 1.0.96", + "unicode-ident", ] -[[package]] -name = "stdweb-internal-runtime" -version = "0.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "213701ba3370744dcd1a12960caa4843b3d68b4d1c0a5d575e0d65b2ee9d16c0" - [[package]] name = "syn" -version = "1.0.96" +version = "3.0.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0748dd251e24453cb8717f0354206b91557e4ec8703673a4b30208f2abaf1ebf" +checksum = "d78c8dee4c7bf0e14673097256fed6142ce9d3b85a408189d07482442145823b" dependencies = [ "proc-macro2", "quote", @@ -1881,398 +1865,342 @@ dependencies = [ ] [[package]] -name = "syn" -version = "2.0.18" +name = "sync_wrapper" +version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32d41677bcbe24c20c52e7c70b0d8db04134c5d1066bf98662e2871ad200ea3e" +checksum = "0bf256ce5efdfa370213c1dabab5935a12e49f2c58d15e9eac2870d3b4f27263" dependencies = [ - "proc-macro2", - "quote", - "unicode-ident", + "futures-core", ] [[package]] -name = "termcolor" -version = "1.1.0" +name = "synstructure" +version = "0.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bb6bfa289a4d7c5766392812c0a1f4c1ba45afa1ad47803c11e1f407d846d75f" +checksum = "901704edd0dfe137f1987838ee4f259e4e063c31371bdb423f7ae38ec6f77f02" dependencies = [ - "winapi-util", + "proc-macro2", + "quote", + "syn 3.0.9", ] [[package]] name = "thiserror" -version = "1.0.21" +version = "2.0.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "318234ffa22e0920fe9a40d7b8369b5f649d490980cf7aadcf1eb91594869b42" +checksum = "09e52cb86a36cede5cb101bf8908837b3e4c6e5e59fe7fd85c23fb56200d189e" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "1.0.21" +version = "2.0.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cae2447b6282786c3493999f40a9be2a6ad20cb8bd268b0a0dbf5a065535c0ab" +checksum = "fe5197923287db20a58125f0bc85c062f7f2c892de97b18c356f9efb14b28524" dependencies = [ "proc-macro2", "quote", - "syn 1.0.96", + "syn 3.0.9", ] [[package]] -name = "thread_local" -version = "1.0.1" +name = "time" +version = "0.3.55" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d40c6d1b69745a6ec6fb1ca717914848da4b44ae29d9b3080cbee91d72a69b14" +checksum = "cdb87b95ec50ddfa440816d227a17b2ccbdda963a316a727fda0fc4334f7d134" dependencies = [ - "lazy_static", + "deranged", + "num-conv", + "powerfmt", + "serde_core", + "time-core", + "time-macros", ] [[package]] -name = "threadpool" -version = "1.8.1" +name = "time-core" +version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d050e60b33d41c19108b32cea32164033a9013fe3b46cbd4457559bfbf77afaa" -dependencies = [ - "num_cpus", -] +checksum = "9e1c906769ad99c88eaa54e728060edef082f8e358ff32030cb7c7d315e81109" [[package]] -name = "time" -version = "0.1.43" +name = "time-macros" +version = "0.2.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ca8a50ef2360fbd1eeb0ecd46795a87a19024eb4b53c5dc916ca1fd95fe62438" +checksum = "7e689342a48d2ea927c87ea50cabf8594854bf940e9310208848d680d668ed85" dependencies = [ - "libc", - "winapi 0.3.8", + "num-conv", + "time-core", ] [[package]] -name = "time" -version = "0.2.22" +name = "tinystr" +version = "0.8.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "55b7151c9065e80917fbf285d9a5d1432f60db41d170ccafc749a136b41a93af" +checksum = "abc840c238154d58f9e86b2792261edfde565a17696e1cda1a449de27979801a" dependencies = [ - "const_fn", - "libc", - "standback", - "stdweb", - "time-macros", - "version_check", - "winapi 0.3.8", + "displaydoc", + "zerovec", ] [[package]] -name = "time-macros" -version = "0.1.1" +name = "tinyvec" +version = "1.13.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fd3ca314f692efd6c868f8408f53fe444634a845f96c028b97d35f6a1f79f0ee" + +[[package]] +name = "tokio" +version = "1.54.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "957e9c6e26f12cb6d0dd7fc776bb67a706312e7299aed74c8dd5b17ebb27e2f1" +checksum = "b52fd3255258d26fac7264ab5a76b53c13367136dc011d3d6ad4f8ed070396a3" dependencies = [ - "proc-macro-hack", - "time-macros-impl", + "bytes", + "libc", + "mio", + "parking_lot", + "pin-project-lite", + "signal-hook-registry", + "socket2", + "tokio-macros", + "windows-sys 0.61.2", ] [[package]] -name = "time-macros-impl" -version = "0.1.1" +name = "tokio-macros" +version = "2.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e5c3be1edfad6027c69f5491cf4cb310d1a71ecd6af742788c6ff8bced86b8fa" +checksum = "78773a2a397f451582ce068015985c33193cf6dea8b74d2a639fe457b2f07b0e" dependencies = [ - "proc-macro-hack", "proc-macro2", "quote", - "standback", - "syn 1.0.96", + "syn 3.0.9", ] [[package]] -name = "tinyvec" -version = "1.0.1" +name = "tokio-rustls" +version = "0.26.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b78a366903f506d2ad52ca8dc552102ffdd3e937ba8a227f024dc1d1eae28575" +checksum = "c9cc2678c2cdd569ef8215e2afd7954ada2ae20b4fdd2c5fe6139a3b02d105db" dependencies = [ - "tinyvec_macros", + "rustls", + "tokio", ] [[package]] -name = "tinyvec_macros" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cda74da7e1a664f795bb1f8a87ec406fb89a02522cf6e50620d016add6dbbf5c" - -[[package]] -name = "tokio" -version = "0.2.21" +name = "tokio-util" +version = "0.7.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d099fa27b9702bed751524694adbe393e18b36b204da91eb1cbbbbb4a5ee2d58" +checksum = "e464cf451ba96ebfc6f9b6542f17ee8b8956e33f1e40d9690624e59d7a7f8a4b" dependencies = [ "bytes", - "fnv", "futures-core", - "iovec", - "lazy_static", + "futures-sink", "libc", - "memchr", - "mio", - "mio-uds", - "pin-project-lite 0.1.5", - "signal-hook-registry", - "slab", - "winapi 0.3.8", + "pin-project-lite", + "tokio", ] [[package]] -name = "tokio-rustls" -version = "0.13.1" +name = "tower" +version = "0.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "15cb62a0d2770787abc96e99c1cd98fcf17f94959f3af63ca85bdfb203f051b4" +checksum = "ebe5ef63511595f1344e2d5cfa636d973292adc0eec1f0ad45fae9f0851ab1d4" dependencies = [ "futures-core", - "rustls", + "futures-util", + "pin-project-lite", + "sync_wrapper", "tokio", - "webpki", + "tower-layer", + "tower-service", ] [[package]] -name = "tokio-util" -version = "0.2.0" +name = "tower-http" +version = "0.6.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "571da51182ec208780505a32528fc5512a8fe1443ab960b3f2f3ef093cd16930" +checksum = "4cfcf7e2740e6fc6d4d688b4ef00650406bb94adf4731e43c096c3a19fe40840" dependencies = [ + "bitflags 2.13.2", "bytes", - "futures-core", - "futures-sink", - "log", - "pin-project-lite 0.1.5", - "tokio", + "futures-util", + "http 1.5.0", + "http-body", + "pin-project-lite", + "tower", + "tower-layer", + "tower-service", + "url", ] [[package]] -name = "tokio-util" -version = "0.3.1" +name = "tower-layer" +version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "be8242891f2b6cbef26a2d7e8605133c2c554cd35b3e4948ea892d6d68436499" -dependencies = [ - "bytes", - "futures-core", - "futures-sink", - "log", - "pin-project-lite 0.1.5", - "tokio", -] +checksum = "121c2a6cda46980bb0fcd1647ffaf6cd3fc79a013de288782836f6df9c48780e" [[package]] name = "tower-service" -version = "0.3.0" +version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e987b6bf443f4b5b3b6f38704195592cca41c5bb7aedd3c3693c7081f8289860" +checksum = "8df9b6e13f2d32c91b9bd719c00d1958837bc7dec474d94952798cc8e69eeec3" [[package]] name = "tracing" -version = "0.1.34" +version = "0.1.44" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5d0ecdcb44a79f0fe9844f0c4f33a342cbcbb5117de8001e6ba0dc2351327d09" +checksum = "63e71662fa4b2a2c3a26f570f037eb95bb1f85397f3cd8076caed2f026a6d100" dependencies = [ - "cfg-if 1.0.0", "log", - "pin-project-lite 0.2.9", + "pin-project-lite", + "tracing-attributes", "tracing-core", ] [[package]] -name = "tracing-core" -version = "0.1.26" +name = "tracing-attributes" +version = "0.1.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f54c8ca710e81886d498c2fd3331b56c93aa248d49de2222ad2742247c60072f" +checksum = "7490cfa5ec963746568740651ac6781f701c9c5ea257c58e057f3ba8cf69e8da" dependencies = [ - "lazy_static", -] - -[[package]] -name = "trust-dns-proto" -version = "0.19.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cdd7061ba6f4d4d9721afedffbfd403f20f39a4301fee1b70d6fcd09cca69f28" -dependencies = [ - "async-trait", - "backtrace", - "enum-as-inner", - "futures", - "idna", - "lazy_static", - "log", - "rand", - "smallvec", - "thiserror", - "tokio", - "url", + "proc-macro2", + "quote", + "syn 2.0.119", ] [[package]] -name = "trust-dns-resolver" -version = "0.19.5" +name = "tracing-core" +version = "0.1.36" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0f23cdfdc3d8300b3c50c9e84302d3bd6d860fb9529af84ace6cf9665f181b77" +checksum = "db97caf9d906fbde555dd62fa95ddba9eecfd14cb388e4f491a66d74cd5fb79a" dependencies = [ - "backtrace", - "cfg-if 0.1.10", - "futures", - "ipconfig", - "lazy_static", - "log", - "lru-cache", - "resolv-conf", - "smallvec", - "thiserror", - "tokio", - "trust-dns-proto", + "once_cell", ] [[package]] name = "try-lock" -version = "0.2.2" +version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e604eb7b43c06650e854be16a2a03155743d3752dd1c943f6829e26b7a36e382" +checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" [[package]] name = "typenum" -version = "1.12.0" +version = "1.20.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "373c8a200f9e67a0c95e62a4f52fbf80c23b4381c05a17845531982fa99e6b33" - -[[package]] -name = "unicase" -version = "2.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "50f37be617794602aabbeee0be4f259dc1778fabe05e2d67ee8f79326d5cb4f6" -dependencies = [ - "version_check", -] - -[[package]] -name = "unicode-bidi" -version = "0.3.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "49f2bd0c6468a8230e1db229cff8029217cf623c767ea5d60bfbd42729ea54d5" -dependencies = [ - "matches", -] +checksum = "b6f5e870be6c3b371b77fe0ee0bafb859fa4964b4404c27de1d380043c4dda20" [[package]] name = "unicode-ident" -version = "1.0.1" +version = "1.0.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5bd2fe26506023ed7b5e1e315add59d6f584c621d037f9368fea9cfb988f368c" +checksum = "a2c754d6c33795a1c324727428e5a7dedb5b06195f9890bdbcba760d3e246563" [[package]] -name = "unicode-normalization" -version = "0.1.12" +name = "unicode-segmentation" +version = "1.13.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5479532badd04e128284890390c1e876ef7a993d0570b3597ae43dfa1d59afa4" -dependencies = [ - "smallvec", -] +checksum = "c6f5d3c3b1bf09027a88a6bc961fc00497d651009560b5463668dc81b0fa87a8" [[package]] -name = "unicode-segmentation" -version = "1.6.0" +name = "unicode-xid" +version = "0.2.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e83e153d1053cbb5a118eeff7fd5be06ed99153f00dbcd8ae310c5fb2b22edc0" +checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853" [[package]] name = "untrusted" -version = "0.7.1" +version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a156c684c91ea7d62626509bce3cb4e1d9ed5c4d978f7b4352658f96a4c26b4a" +checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" [[package]] name = "url" -version = "2.1.1" +version = "2.5.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "829d4a8476c35c9bf0bbce5a3b23f4106f79728039b726d292bb93bc106787cb" +checksum = "ff67a8a4397373c3ef660812acab3268222035010ab8680ec4215f38ba3d0eed" dependencies = [ + "form_urlencoded", "idna", - "matches", "percent-encoding", "serde", + "serde_derive", ] +[[package]] +name = "utf8_iter" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" + +[[package]] +name = "utf8parse" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" + [[package]] name = "uuid" -version = "0.8.1" +version = "1.28.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9fde2f6a4bea1d6e007c4ad38c6839fa71cbb63b6dbf5b595aa38dc9b1093c11" +checksum = "7cc1186384beb7dd8eedea376413fd654937285ea6c9cfbb928dc3043ea4b606" dependencies = [ - "rand", + "getrandom 0.4.3", + "js-sys", + "wasm-bindgen", ] [[package]] name = "version_check" -version = "0.9.1" +version = "0.9.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "078775d0255232fb988e6fccf26ddc9d1ac274299aaedcedce21c6f72cc533ce" +checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" [[package]] name = "want" -version = "0.3.0" +version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1ce8a968cb1cd110d136ff8b819a556d6fb6d919363c61534f6860c7eb172ba0" +checksum = "ec4cdd0dd910afe868b7ef477227d8d538b46b3075031afee8a9f2acb0a2ed0b" dependencies = [ - "log", "try-lock", ] [[package]] name = "wasi" -version = "0.9.0+wasi-snapshot-preview1" +version = "0.11.1+wasi-snapshot-preview1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cccddf32554fecc6acb585f82a32a72e28b48f8c4c1883ddfeeeaa96f7d8e519" +checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" [[package]] name = "wasm-bindgen" -version = "0.2.62" +version = "0.2.129" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e3c7d40d09cdbf0f4895ae58cf57d92e1e57a9dd8ed2e8390514b54a47cc5551" +checksum = "9bb54f33acc68fd454578d9820b0bde1a1a3d17aa17bb7b6595806d02886d409" dependencies = [ - "cfg-if 0.1.10", - "serde", - "serde_json", + "cfg-if", + "once_cell", + "rustversion", "wasm-bindgen-macro", -] - -[[package]] -name = "wasm-bindgen-backend" -version = "0.2.62" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c3972e137ebf830900db522d6c8fd74d1900dcfc733462e9a12e942b00b4ac94" -dependencies = [ - "bumpalo", - "lazy_static", - "log", - "proc-macro2", - "quote", - "syn 1.0.96", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-futures" -version = "0.4.12" +version = "0.4.79" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8a369c5e1dfb7569e14d62af4da642a3cbc2f9a3652fe586e26ac22222aa4b04" +checksum = "3cbab34de2d982e9b48e18d216d04c4a6f641066ff19ffb699980f591ee3610e" dependencies = [ - "cfg-if 0.1.10", "js-sys", + "tokio", "wasm-bindgen", - "web-sys", ] [[package]] name = "wasm-bindgen-macro" -version = "0.2.62" +version = "0.2.129" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2cd85aa2c579e8892442954685f0d801f9129de24fa2136b2c6a539c76b65776" +checksum = "2e29d0c35b16e224a7eeb5cd2d25e3e1968fbd65604117b44d3b789d00ee8535" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -2280,116 +2208,321 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.62" +version = "0.2.129" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8eb197bd3a47553334907ffd2f16507b4f4f01bbec3ac921a7719e0decdfe72a" +checksum = "6f501a8bc3719dba86ef8ae4728879c08001bea749eb1333ac5b91e040e2a6b7" dependencies = [ + "bumpalo", "proc-macro2", "quote", - "syn 1.0.96", - "wasm-bindgen-backend", + "syn 3.0.9", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-shared" -version = "0.2.62" +version = "0.2.129" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a91c2916119c17a8e316507afaaa2dd94b47646048014bbdf6bef098c1bb58ad" +checksum = "23f0c9c52aa7cd7d77769a4cfe2a9adb1b331f489a41d912ce14513d5ab995c6" +dependencies = [ + "unicode-ident", +] [[package]] name = "web-sys" -version = "0.3.39" +version = "0.3.106" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8bc359e5dd3b46cb9687a051d50a2fdd228e4ba7cf6fcf861a5365c3d671a642" +checksum = "88261b9deccee56594c11a3460c462c41f58d148598fe70ad77070126a68aba4" dependencies = [ "js-sys", "wasm-bindgen", ] [[package]] -name = "webpki" -version = "0.21.2" +name = "web-time" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f1f50e1972865d6b1adb54167d1c8ed48606004c2c9d0ea5f1eeb34d95e863ef" +checksum = "5a6580f308b1fad9207618087a65c04e7a10bc77e02c8e84e9b00dd4b12fa0bb" dependencies = [ - "ring", - "untrusted", + "js-sys", + "wasm-bindgen", ] [[package]] name = "webpki-roots" -version = "0.18.0" +version = "1.0.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "91cd5736df7f12a964a5067a12c62fa38e1bd8080aff1f80bc29be7c80d19ab4" +checksum = "1c1c8e20c8a38fc7d65abfe74eb7280e4aa667e8eec8bc84996d2874810748d9" dependencies = [ - "webpki", + "rustls-pki-types", ] [[package]] -name = "widestring" -version = "0.4.0" +name = "windows-core" +version = "0.62.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "effc0e4ff8085673ea7b9b2e3c73f6bd4d118810c9009ed8f1e16bd96c331db6" +checksum = "b8e83a14d34d0623b51dce9581199302a221863196a1dde71a7663a4c2be9deb" +dependencies = [ + "windows-implement", + "windows-interface", + "windows-link", + "windows-result", + "windows-strings", +] [[package]] -name = "winapi" -version = "0.2.8" +name = "windows-implement" +version = "0.60.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "167dc9d6949a9b857f3451275e911c3f44255842c1f7a76f33c55103a909087a" +checksum = "053e2e040ab57b9dc951b72c264860db7eb3b0200ba345b4e4c3b14f67855ddf" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", +] [[package]] -name = "winapi" -version = "0.3.8" +name = "windows-interface" +version = "0.59.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8093091eeb260906a183e6ae1abdba2ef5ef2257a21801128899c3fc699229c6" +checksum = "3f316c4a2570ba26bbec722032c4099d8c8bc095efccdc15688708623367e358" dependencies = [ - "winapi-i686-pc-windows-gnu", - "winapi-x86_64-pc-windows-gnu", + "proc-macro2", + "quote", + "syn 2.0.119", ] [[package]] -name = "winapi-build" -version = "0.1.1" +name = "windows-link" +version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2d315eee3b34aca4797b2da6b13ed88266e6d612562a0c46390af8299fc699bc" +checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" [[package]] -name = "winapi-i686-pc-windows-gnu" -version = "0.4.0" +name = "windows-result" +version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" +checksum = "7781fa89eaf60850ac3d2da7af8e5242a5ea78d1a11c49bf2910bb5a73853eb5" +dependencies = [ + "windows-link", +] [[package]] -name = "winapi-util" -version = "0.1.5" +name = "windows-strings" +version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178" +checksum = "7837d08f69c77cf6b07689544538e017c1bfcf57e34b4c0ff58e6c2cd3b37091" dependencies = [ - "winapi 0.3.8", + "windows-link", ] [[package]] -name = "winapi-x86_64-pc-windows-gnu" -version = "0.4.0" +name = "windows-sys" +version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" +checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" +dependencies = [ + "windows-targets", +] [[package]] -name = "winreg" -version = "0.6.2" +name = "windows-sys" +version = "0.61.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b2986deb581c4fe11b621998a5e53361efe6b48a151178d0cd9eeffa4dc6acc9" +checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc" dependencies = [ - "winapi 0.3.8", + "windows-link", ] [[package]] -name = "ws2_32-sys" -version = "0.2.1" +name = "windows-targets" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" +dependencies = [ + "windows_aarch64_gnullvm", + "windows_aarch64_msvc", + "windows_i686_gnu", + "windows_i686_gnullvm", + "windows_i686_msvc", + "windows_x86_64_gnu", + "windows_x86_64_gnullvm", + "windows_x86_64_msvc", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" + +[[package]] +name = "windows_i686_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" + +[[package]] +name = "windows_i686_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" + +[[package]] +name = "windows_i686_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" + +[[package]] +name = "writeable" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3ad82d2a33cdc9674dc7465672f271e096168fcdbe0f799d9e6db8c5892679dc" + +[[package]] +name = "yoke" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "417ef3fcf6c300b7dbd25c85531a746931c02ad536f3cf4786ed1e88223340b3" +dependencies = [ + "stable_deref_trait", + "yoke-derive", + "zerofrom", +] + +[[package]] +name = "yoke-derive" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec8ebde2db3681e8c9980cc27822030e68752690ddfa9473e739aeb4dbde6d71" +dependencies = [ + "proc-macro2", + "quote", + "syn 3.0.9", + "synstructure", +] + +[[package]] +name = "zerofrom" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ec05a11813ea801ff6d75110ad09cd0824ddba17dfe17128ea0d5f68e6c5272" +dependencies = [ + "zerofrom-derive", +] + +[[package]] +name = "zerofrom-derive" +version = "0.1.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d59cefebd0c892fa2dd6de581e937301d8552cb44489cdff035c6187cb63fa5e" +checksum = "f75b4683f6c7f45248d4d64056a24298c6281e0993356d7d1b4a1a962ef10d4a" dependencies = [ - "winapi 0.2.8", - "winapi-build", + "proc-macro2", + "quote", + "syn 3.0.9", + "synstructure", +] + +[[package]] +name = "zeroize" +version = "1.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e13084392c5e4bc371903e2935a5eaeed24905a7511356b883835e18a78f6879" + +[[package]] +name = "zerotrie" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ea269c3bd32f0a32c321907a2ae912ba6f4649bb0fc764a15627e99a7095a3f" +dependencies = [ + "displaydoc", + "yoke", + "zerofrom", +] + +[[package]] +name = "zerovec" +version = "0.11.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f58a2799cfe9a1670c7ea8edbd52ef8edb93cb37a7f83bf31ba9e35d9dae6a40" +dependencies = [ + "yoke", + "zerofrom", + "zerovec-derive", +] + +[[package]] +name = "zerovec-derive" +version = "0.11.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3743ddbf47738d929cd1ae2e199d40d0162979dfe31aef8ae646d2bf00bf97a" +dependencies = [ + "proc-macro2", + "quote", + "syn 3.0.9", +] + +[[package]] +name = "zlib-rs" +version = "0.6.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b268e58e7c693d7c271f93ffc4ba3b380412554231c85bf61ca7af91042a4112" + +[[package]] +name = "zmij" +version = "1.0.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29666d0abbfad1e3dc4dcf6144730dd3a3ab225bbbdac83319345b1b44ccfc1b" + +[[package]] +name = "zstd" +version = "0.14.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db3ae9bf90af6e40197bef767eeefc46ad6397420fe873830bd85e5fbbfb3c70" +dependencies = [ + "zstd-safe", +] + +[[package]] +name = "zstd-safe" +version = "8.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ded520f16b29e1a9b771c722221b2b0a0398c2316d9bb3c42b458a5414d7434b" +dependencies = [ + "zstd-sys", +] + +[[package]] +name = "zstd-sys" +version = "2.1.2+zstd.1.5.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "481ada9c543b2323cbb7bbdbb5249177fbf0188ed959ed2c15e4b22421c84ed3" +dependencies = [ + "cc", + "pkg-config", ] diff --git a/code-samples/serving/cloudevents/cloudevents-rust/Cargo.toml b/code-samples/serving/cloudevents/cloudevents-rust/Cargo.toml index d7f9e876a4..f6f9fd0c99 100644 --- a/code-samples/serving/cloudevents/cloudevents-rust/Cargo.toml +++ b/code-samples/serving/cloudevents/cloudevents-rust/Cargo.toml @@ -21,13 +21,9 @@ edition = "2018" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] -cloudevents-sdk = "0.3.1" -cloudevents-sdk-actix-web = "0.3.1" -cloudevents-sdk-reqwest = "0.3.1" -reqwest = { version = "0.10.4", default-features = false, features = ["rustls-tls"] } -actix-web = "^3" -actix-rt = "1" -serde_json = "^1.0" -url = "^2.1" -log = "0.4.0" -env_logger = "0.7.1" +cloudevents-sdk = { version = "0.9.0", features = ["actix", "reqwest"] } +# Match the Reqwest version used by the CloudEvents SDK's HTTP binding. +reqwest = { version = "0.12.28", default-features = false, features = ["rustls-tls"] } +actix-web = "4.17.0" +log = "0.4.34" +env_logger = "0.11.11" diff --git a/code-samples/serving/cloudevents/cloudevents-rust/Dockerfile b/code-samples/serving/cloudevents/cloudevents-rust/Dockerfile index c6450533bc..e29e92cf5d 100644 --- a/code-samples/serving/cloudevents/cloudevents-rust/Dockerfile +++ b/code-samples/serving/cloudevents/cloudevents-rust/Dockerfile @@ -12,9 +12,20 @@ # See the License for the specific language governing permissions and # limitations under the License. -FROM scratch - -# To build it: cargo build --target x86_64-unknown-linux-musl --release -COPY target/x86_64-unknown-linux-musl/release/knative-cloudevents-example /app +# Build for the selected Docker target platform, rather than copying a host binary. +FROM rust:1.99.1-alpine AS build +RUN apk add --no-cache build-base ca-certificates \ + && rustup component add rustfmt clippy +WORKDIR /src +ENV CARGO_BUILD_JOBS=2 +COPY Cargo.toml Cargo.lock ./ +COPY src ./src +RUN cargo fmt --check \ + && cargo clippy --locked --release --all-targets -- -D warnings \ + && cargo build --locked --release +FROM scratch +COPY --from=build /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/ +COPY --from=build /src/target/release/knative-cloudevents-example /app +USER 65532:65532 CMD ["/app"] diff --git a/code-samples/serving/cloudevents/cloudevents-rust/README.md b/code-samples/serving/cloudevents/cloudevents-rust/README.md index 714733d9a2..004099bcde 100644 --- a/code-samples/serving/cloudevents/cloudevents-rust/README.md +++ b/code-samples/serving/cloudevents/cloudevents-rust/README.md @@ -14,12 +14,13 @@ that can receive CloudEvents. It supports running in two modes: implementing an event source). The input event is modified assigning a new source and type attribute. -The application will use `$K_SINK`-mode whenever the environment variable is -specified. +The application uses `$K_SINK` mode when the environment variable is nonempty. +An unset or empty `K_SINK` selects reply mode. Both binary and structured +CloudEvents v1.0 HTTP requests are supported; reply events use binary encoding. -Do the following steps to create the sample code and then deploy the app to your -cluster. You can also download a working copy of the sample by running the -following commands: +Use the checked-in [application](src/main.rs), [Dockerfile](Dockerfile), and +[Service manifest](service.yaml) rather than recreating them from this guide. +Clone the repository to build and deploy the sample: ```bash git clone https://github.com/knative/docs.git knative-docs @@ -32,27 +33,41 @@ cd knative-docs/code-samples/serving/cloudevents/cloudevents-rust [Install Knative Serving](https://knative.dev/docs/install/serving/install-serving-with-yaml). - [Docker](https://www.docker.com) installed and running on your local machine, and a Docker Hub account configured (we'll use it for a container registry). -- This guide uses Rust `musl` toolchain to build the image in order to create - really small docker images. To install the Rust toolchain: [`rustup`](https://rustup.rs/). - To install musl support: [MUSL support for fully static binaries](https://doc.rust-lang.org/edition-guide/rust-2018/platform-and-target-support/musl-support-for-fully-static-binaries.html). +- No host Rust installation is required. The multi-stage Dockerfile uses stable + Rust **1.99.1**, Actix Web **4.17**, and the official CloudEvents SDK **0.9**. + It builds a static musl binary from source for the selected target platform, + runs formatting and Clippy, then copies the binary and CA + certificates into a non-root `scratch` image. ## Build and deploy the sample -To build the binary, run: +Build and push an image for your cluster's architecture: ```bash -cargo build --target x86_64-unknown-linux-musl --release +# Choose linux/amd64 instead for an x86-64 cluster. +docker buildx build --platform linux/arm64 -t "" --push . ``` -This will build a statically linked binary, in order to create an image from scratch. Now build the docker image: +If your builder supports both platforms, publish a multi-architecture image: ```bash -# Build and push the container on your local machine. docker buildx build --platform linux/arm64,linux/amd64 -t "" --push . ``` + +Each platform is compiled from source; no prebuilt host binary is copied. To +build locally without publishing, run `docker build -t cloudevents-rust .`. +The build uses `Cargo.lock` with `--locked`. To run the language checks directly +with a local Rust 1.99.1 toolchain, use: + +```bash +cargo fmt --check +cargo clippy --locked --release --all-targets -- -D warnings +``` + ### yaml -To deploy the Knative Service, look in the `service.yaml` and replace `` with the deployed image name. Then run: +To deploy the Knative Service, edit [service.yaml](service.yaml) and replace +`docker.io/slinkydeveloper/cloudevents-rust` with your deployed image name. Then run: ```bash kubectl apply -f service.yaml @@ -132,6 +147,24 @@ You'll get as result: {"name":"Dave"} ``` +### Forwarding events with `K_SINK` + +To forward events rather than return them, configure `K_SINK` with the HTTP URL +of a receiving Service or Broker (see the commented environment configuration in +`service.yaml`). The receiver gets the same event ID and data with the source +and type shown above. A successful delivery returns HTTP `202 Accepted` with an +empty body to the caller, not a reply CloudEvent. A failed delivery returns HTTP +`500 Internal Server Error`. Sink requests have a 30-second timeout. An empty +`K_SINK` behaves like an unset variable and returns the reply event instead. + +The server listens on all interfaces on `PORT` (default `8080`; invalid values +fall back to the default). On Knative, configure the container's +`ports[].containerPort` (for example `8081`); Knative injects `PORT` automatically. +Do not set the reserved `PORT` environment variable yourself or use Knative's +reserved port `9090`. Outside Knative, you can set `PORT` directly. `RUST_LOG` can +override the default `info` logging level. Missing or malformed CloudEvents +return HTTP `400 Bad Request`. + ## Removing the sample app deployment To remove the sample app from your cluster, delete the service: diff --git a/code-samples/serving/cloudevents/cloudevents-rust/ci.sh b/code-samples/serving/cloudevents/cloudevents-rust/ci.sh new file mode 100755 index 0000000000..a594182c9c --- /dev/null +++ b/code-samples/serving/cloudevents/cloudevents-rust/ci.sh @@ -0,0 +1,43 @@ +#!/usr/bin/env bash +# Source from this sample directory; the caller owns cluster setup and ingress forwarding. +IMAGE=dev.local/cloudevents-rust:ci +SAMPLE_URL=http://127.0.0.1:18080 + +main() ( + set -euo pipefail + build + deploy + test +) + +build() ( + set -euo pipefail + docker build -t "$IMAGE" . +) + +deploy() ( + set -euo pipefail + kind load docker-image "$IMAGE" + kubectl patch --local -f service.yaml --type=json -p "$( + jq -cn --arg image "$IMAGE" '[ + {op: "replace", path: "/spec/template/spec/containers/0/image", value: $image}, + {op: "add", path: "/spec/template/spec/containers/0/imagePullPolicy", value: "IfNotPresent"} + ]' + )" -o yaml | kubectl --namespace default apply -f - + kubectl --namespace default wait -f service.yaml --for=condition=Ready --timeout=300s +) + +test() ( + set -euo pipefail + local service_url service_host + service_url=$(kubectl --namespace default get -f service.yaml -o jsonpath='{.status.url}') + service_host=${service_url#*://} + [[ "$service_host" =~ ^[a-zA-Z0-9.-]+$ ]] + local response + response=$(curl -fsS --max-time 30 \ + -H "Host: $service_host" -H "Content-Type: application/json" \ + -H "Ce-Specversion: 1.0" -H "Ce-Id: smoke-test" \ + -H "Ce-Type: dev.knative.samples.test" -H "Ce-Source: /smoke-test" \ + --data '{"name":"Knative"}' "$SAMPLE_URL/") + jq -e '(.data // .).name == "Knative"' <<< "$response" +) diff --git a/code-samples/serving/cloudevents/cloudevents-rust/src/main.rs b/code-samples/serving/cloudevents/cloudevents-rust/src/main.rs index 8004839c3f..9c5995fc2d 100644 --- a/code-samples/serving/cloudevents/cloudevents-rust/src/main.rs +++ b/code-samples/serving/cloudevents/cloudevents-rust/src/main.rs @@ -14,106 +14,91 @@ See the License for the specific language governing permissions and limitations under the License. */ -#[macro_use] -extern crate log; - -use actix_web::dev::HttpResponseBuilder; -use actix_web::http::StatusCode; use actix_web::{post, web, App, HttpRequest, HttpResponse, HttpServer}; +use cloudevents::binding::actix::{HttpRequestExt, HttpResponseBuilderExt}; +use cloudevents::binding::reqwest::RequestBuilderExt; use cloudevents::{EventBuilder, EventBuilderV10}; -use cloudevents_sdk_actix_web::{HttpRequestExt, HttpResponseBuilderExt}; -use cloudevents_sdk_reqwest::RequestBuilderExt; +use std::time::Duration; -#[post("/")] -async fn reply_event( - req: HttpRequest, - payload: web::Payload, -) -> Result { - let request_event = req.to_event(payload).await?; - info!("{}", request_event); +const SOURCE: &str = + "https://github.com/knative/docs/code-samples/serving/cloudevents/cloudevents-rust"; +const EVENT_TYPE: &str = "dev.knative.docs.sample"; - // Build response event cloning the original event and setting the new type and source - let response_event = EventBuilderV10::from(request_event) - .source("https://github.com/knative/docs/code-samples/serving/cloudevents/cloudevents-rust") - .ty("dev.knative.docs.sample") - .build() - // If i can't build the event, fail with internal server error - .map_err(actix_web::error::ErrorInternalServerError)?; +struct AppState { + sink: Option, + client: reqwest::Client, +} + +impl AppState { + fn new(sink: Option) -> Result { + Ok(Self { + // An explicitly empty K_SINK is equivalent to an unset K_SINK. + sink: sink.filter(|url| !url.is_empty()), + client: reqwest::Client::builder() + .timeout(Duration::from_secs(30)) + .build()?, + }) + } +} - HttpResponseBuilder::new(StatusCode::OK) - .event(response_event) - .await +fn port(value: Option) -> u16 { + value.and_then(|value| value.parse().ok()).unwrap_or(8080) } #[post("/")] -async fn forward_event( +async fn handle_event( req: HttpRequest, payload: web::Payload, - sink_url: web::Data, + state: web::Data, ) -> Result { - let sink_url: &str = &sink_url; - let request_event = req.to_event(payload).await?; - info!("{}", request_event); + log::info!("{}", request_event); - // Build response event cloning the original event and setting the new type and source - let forward_event = EventBuilderV10::from(request_event) - .source("https://github.com/knative/docs/code-samples/serving/cloudevents/cloudevents-rust") - .ty("dev.knative.docs.sample") + // Preserve the original event's ID, data, and extensions; change only source and type. + let response_event = EventBuilderV10::from(request_event) + .source(SOURCE) + .ty(EVENT_TYPE) .build() - // If i can't build the event, fail with internal server error - .map_err(actix_web::error::ErrorInternalServerError)?; - - let client = reqwest::Client::new(); - let response = client.post(sink_url) - .event(forward_event) - .map_err(actix_web::error::ErrorInternalServerError)? - .send() - .await - // If something went wrong when sending the event, fail with internal server error .map_err(actix_web::error::ErrorInternalServerError)?; - if response.status().is_success() { - Ok(HttpResponseBuilder::new(StatusCode::ACCEPTED).finish()) - } else { - Ok( - HttpResponseBuilder::new(StatusCode::INTERNAL_SERVER_ERROR).body(format!( + if let Some(sink) = &state.sink { + let response = state + .client + .post(sink) + .event(response_event) + .map_err(actix_web::error::ErrorInternalServerError)? + .send() + .await + .map_err(actix_web::error::ErrorInternalServerError)?; + + if response.status().is_success() { + Ok(HttpResponse::Accepted().finish()) + } else { + Ok(HttpResponse::InternalServerError().body(format!( "Received from {} status code {}", - sink_url, + sink, response.status().as_str() - )), - ) + ))) + } + } else { + HttpResponse::Ok().event(response_event) } } -#[actix_rt::main] +#[actix_web::main] async fn main() -> std::io::Result<()> { - // Setup logger - std::env::set_var("RUST_LOG", "info"); - env_logger::init(); - - // Get port from envs - let port: u16 = std::env::var("PORT") - .ok() - .map(|e| e.parse().ok()) - .flatten() - .unwrap_or(8080); - - // Create the HTTP server - HttpServer::new(|| { - let app_base = App::new().wrap(actix_web::middleware::Logger::default()); - - // Get k_sink env - let k_sink = std::env::var("K_SINK").ok(); - - // If k_sink is configured, then we start the app in forward mode - // Otherwise, we set it in reply mode - match k_sink { - Some(sink) => app_base.data(sink).service(forward_event), - None => app_base.service(reply_event), - } + env_logger::Builder::from_env(env_logger::Env::default().default_filter_or("info")).init(); + let port = port(std::env::var("PORT").ok()); + let state = + web::Data::new(AppState::new(std::env::var("K_SINK").ok()).map_err(std::io::Error::other)?); + + HttpServer::new(move || { + App::new() + .app_data(state.clone()) + .wrap(actix_web::middleware::Logger::default()) + .service(handle_event) }) - .bind(("127.0.0.1", port))? + .bind(("0.0.0.0", port))? .workers(1) .run() .await From 7ee539f466741932a8c966461067cb99b4ed394c Mon Sep 17 00:00:00 2001 From: Dave Protasowski Date: Mon, 26 Oct 2026 09:16:04 -0400 Subject: [PATCH 23/44] Update the Spring CloudEvents sample --- .../.mvn/wrapper/MavenWrapperDownloader.java | 117 ------- .../.mvn/wrapper/maven-wrapper.properties | 2 - .../cloudevents/cloudevents-spring/README.md | 117 +++++-- .../cloudevents/cloudevents-spring/mvnw | 310 ------------------ .../cloudevents/cloudevents-spring/mvnw.cmd | 310 ------------------ .../cloudevents/cloudevents-spring/pom.xml | 71 ++-- .../spring/CloudEventSampleApplication.java | 76 ++--- .../cloudevents/spring/HttpHeaderFilter.java | 150 +++++++++ .../src/main/resources/application.properties | 5 + ...udEventSampleApplicationFunctionTests.java | 25 -- ...ventSampleApplicationIntegrationTests.java | 51 --- ...ventSampleApplicationIntegrationTests.java | 105 ------ 12 files changed, 309 insertions(+), 1030 deletions(-) delete mode 100644 code-samples/serving/cloudevents/cloudevents-spring/.mvn/wrapper/MavenWrapperDownloader.java delete mode 100644 code-samples/serving/cloudevents/cloudevents-spring/.mvn/wrapper/maven-wrapper.properties delete mode 100755 code-samples/serving/cloudevents/cloudevents-spring/mvnw delete mode 100644 code-samples/serving/cloudevents/cloudevents-spring/mvnw.cmd create mode 100644 code-samples/serving/cloudevents/cloudevents-spring/src/main/java/org/knative/examples/cloudevents/spring/HttpHeaderFilter.java create mode 100644 code-samples/serving/cloudevents/cloudevents-spring/src/main/resources/application.properties delete mode 100644 code-samples/serving/cloudevents/cloudevents-spring/src/test/java/org/knative/examples/cloudevents/spring/CloudEventSampleApplicationFunctionTests.java delete mode 100644 code-samples/serving/cloudevents/cloudevents-spring/src/test/java/org/knative/examples/cloudevents/spring/CloudEventSampleApplicationIntegrationTests.java delete mode 100644 code-samples/serving/cloudevents/cloudevents-spring/src/test/java/org/knative/examples/cloudevents/spring/KSinkCloudEventSampleApplicationIntegrationTests.java diff --git a/code-samples/serving/cloudevents/cloudevents-spring/.mvn/wrapper/MavenWrapperDownloader.java b/code-samples/serving/cloudevents/cloudevents-spring/.mvn/wrapper/MavenWrapperDownloader.java deleted file mode 100644 index e76d1f3241..0000000000 --- a/code-samples/serving/cloudevents/cloudevents-spring/.mvn/wrapper/MavenWrapperDownloader.java +++ /dev/null @@ -1,117 +0,0 @@ -/* - * Copyright 2007-present the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -import java.net.*; -import java.io.*; -import java.nio.channels.*; -import java.util.Properties; - -public class MavenWrapperDownloader { - - private static final String WRAPPER_VERSION = "0.5.6"; - /** - * Default URL to download the maven-wrapper.jar from, if no 'downloadUrl' is provided. - */ - private static final String DEFAULT_DOWNLOAD_URL = "https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/" - + WRAPPER_VERSION + "/maven-wrapper-" + WRAPPER_VERSION + ".jar"; - - /** - * Path to the maven-wrapper.properties file, which might contain a downloadUrl property to - * use instead of the default one. - */ - private static final String MAVEN_WRAPPER_PROPERTIES_PATH = - ".mvn/wrapper/maven-wrapper.properties"; - - /** - * Path where the maven-wrapper.jar will be saved to. - */ - private static final String MAVEN_WRAPPER_JAR_PATH = - ".mvn/wrapper/maven-wrapper.jar"; - - /** - * Name of the property which should be used to override the default download url for the wrapper. - */ - private static final String PROPERTY_NAME_WRAPPER_URL = "wrapperUrl"; - - public static void main(String args[]) { - System.out.println("- Downloader started"); - File baseDirectory = new File(args[0]); - System.out.println("- Using base directory: " + baseDirectory.getAbsolutePath()); - - // If the maven-wrapper.properties exists, read it and check if it contains a custom - // wrapperUrl parameter. - File mavenWrapperPropertyFile = new File(baseDirectory, MAVEN_WRAPPER_PROPERTIES_PATH); - String url = DEFAULT_DOWNLOAD_URL; - if(mavenWrapperPropertyFile.exists()) { - FileInputStream mavenWrapperPropertyFileInputStream = null; - try { - mavenWrapperPropertyFileInputStream = new FileInputStream(mavenWrapperPropertyFile); - Properties mavenWrapperProperties = new Properties(); - mavenWrapperProperties.load(mavenWrapperPropertyFileInputStream); - url = mavenWrapperProperties.getProperty(PROPERTY_NAME_WRAPPER_URL, url); - } catch (IOException e) { - System.out.println("- ERROR loading '" + MAVEN_WRAPPER_PROPERTIES_PATH + "'"); - } finally { - try { - if(mavenWrapperPropertyFileInputStream != null) { - mavenWrapperPropertyFileInputStream.close(); - } - } catch (IOException e) { - // Ignore ... - } - } - } - System.out.println("- Downloading from: " + url); - - File outputFile = new File(baseDirectory.getAbsolutePath(), MAVEN_WRAPPER_JAR_PATH); - if(!outputFile.getParentFile().exists()) { - if(!outputFile.getParentFile().mkdirs()) { - System.out.println( - "- ERROR creating output directory '" + outputFile.getParentFile().getAbsolutePath() + "'"); - } - } - System.out.println("- Downloading to: " + outputFile.getAbsolutePath()); - try { - downloadFileFromURL(url, outputFile); - System.out.println("Done"); - System.exit(0); - } catch (Throwable e) { - System.out.println("- Error downloading"); - e.printStackTrace(); - System.exit(1); - } - } - - private static void downloadFileFromURL(String urlString, File destination) throws Exception { - if (System.getenv("MVNW_USERNAME") != null && System.getenv("MVNW_PASSWORD") != null) { - String username = System.getenv("MVNW_USERNAME"); - char[] password = System.getenv("MVNW_PASSWORD").toCharArray(); - Authenticator.setDefault(new Authenticator() { - @Override - protected PasswordAuthentication getPasswordAuthentication() { - return new PasswordAuthentication(username, password); - } - }); - } - URL website = new URL(urlString); - ReadableByteChannel rbc; - rbc = Channels.newChannel(website.openStream()); - FileOutputStream fos = new FileOutputStream(destination); - fos.getChannel().transferFrom(rbc, 0, Long.MAX_VALUE); - fos.close(); - rbc.close(); - } - -} diff --git a/code-samples/serving/cloudevents/cloudevents-spring/.mvn/wrapper/maven-wrapper.properties b/code-samples/serving/cloudevents/cloudevents-spring/.mvn/wrapper/maven-wrapper.properties deleted file mode 100644 index 642d572ce9..0000000000 --- a/code-samples/serving/cloudevents/cloudevents-spring/.mvn/wrapper/maven-wrapper.properties +++ /dev/null @@ -1,2 +0,0 @@ -distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.6.3/apache-maven-3.6.3-bin.zip -wrapperUrl=https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar diff --git a/code-samples/serving/cloudevents/cloudevents-spring/README.md b/code-samples/serving/cloudevents/cloudevents-spring/README.md index 041ff4a7d5..05200612e6 100644 --- a/code-samples/serving/cloudevents/cloudevents-spring/README.md +++ b/code-samples/serving/cloudevents/cloudevents-spring/README.md @@ -1,7 +1,9 @@ # Cloud Events - Java and Spring -A simple web app written in Java using Spring Cloud Function that can receive CloudEvents. It -supports running in two modes: +A simple web app using Java 25 LTS, Spring Boot 4.0.9 and Spring Cloud Function +5.0.5 that can receive CloudEvents. The build uses Maven 3.9.16 and Jib 3.5.2, +with public release dependencies from Maven Central (no snapshot or milestone +repositories). It supports running in two modes: 1. The default mode has the app reply to your input events with the output event, which is simplest for demonstrating things working in isolation, but @@ -15,11 +17,32 @@ supports running in two modes: source and type attribute. The application will use `$K_SINK`-mode whenever the environment variable is -specified. - -Do the following steps to create the sample code and then deploy the app to your -cluster. You can also download a working copy of the sample, by running the -following commands: +non-empty. If it is unset or empty, the application replies to the request. +Both modes accept binary and structured JSON CloudEvents. Output events use +binary HTTP encoding, with a new ID, source +`https://github.com/knative/docs/code-samples/serving/cloudevents/cloudevents-spring`, +and type `org.knative.examples.cloudevents.spring.Response`. JSON object data +(including additional and nested fields) is preserved in both modes. + +## HTTP contract + +- Send `POST /` with a JSON object in binary CloudEvents 1.0 or structured + `application/cloudevents+json` format. Header names are case-insensitive; + structured content types may include a charset. +- A binary event must include `ce-specversion: 1.0`, `ce-id`, `ce-source` and + `ce-type`. Structured events require the corresponding attributes and an + object-valued `data` field. Ordinary JSON without any event headers is also + accepted and wrapped in a new response event. +- Empty/malformed JSON, partial or unsupported-version events and non-object + data return `400 Bad Request`. Requests larger than 1 MiB return `413`; + methods other than POST return `405`. +- The default port is 8080. Knative's `PORT` environment variable selects another + port; Spring's `SERVER_PORT` can also explicitly override it. + +Use the checked-in [application](src/main/java/org/knative/examples/cloudevents/spring/CloudEventSampleApplication.java), +[HTTP validation](src/main/java/org/knative/examples/cloudevents/spring/HttpHeaderFilter.java), +[runtime configuration](src/main/resources/application.properties), +[Maven build](pom.xml), and [Knative Service manifest](service.yaml). Clone a working copy: ```bash git clone https://github.com/knative/docs.git knative-docs @@ -30,18 +53,41 @@ cd knative-docs/code-samples/serving/cloudevents/cloudevents-spring - A Kubernetes cluster with Knative installed and DNS configured. See [Install Knative Serving](https://knative.dev/docs/install/serving/install-serving-with-yaml). +- A Java 25 JDK and Maven 3.9.16 installed to compile the application. + Alternatively, run Maven commands in the `maven:3.9.16-eclipse-temurin-25` + Docker image with this sample mounted as the working directory. - [Docker](https://www.docker.com) installed and running on your local machine, and a Docker Hub account configured (we'll use it for a container registry). ## Build and deploy the sample -To build the image, run: +First compile and package the application: + +```bash +mvn clean package +``` + +To build and push the image, run (replace `` with a registry image +you can push to): ```bash mvn compile jib:build -Dimage= ``` -To deploy the Knative Service, edit the `service.yaml` file and replace `` +For a local image without a registry push, use: + +```bash +mvn compile jib:buildTar -Dimage=cloudevents-spring:local +docker load -i target/jib-image.tar +``` + +Jib uses the Eclipse Temurin 25 JRE image and targets Linux amd64 by default. +For a native arm64 image, add `-Dimage.architecture=arm64` to the Jib command. +Local images must also be loaded into the Kubernetes node's image store (loading +into host Docker alone is not enough); configure `imagePullPolicy: IfNotPresent` +when using an already-loaded local image. + +To deploy the Knative Service, edit the checked-in [service.yaml](service.yaml) and replace `` with the deployed image name. Then run: ```bash @@ -50,12 +96,11 @@ kubectl apply -f service.yaml ## Testing the sample -Get the URL for your Service with: +Wait for the Service to become ready, then get its URL: ```bash -$ kubectl get ksvc -NAME URL LATESTCREATED LATESTREADY READY REASON -cloudevents-spring http://cloudevents-java.sslip.io cloudevents-spring-86h28 cloudevents-spring-86h28 True +kubectl wait --for=condition=Ready ksvc/cloudevents-spring --timeout=180s +SERVICE_URL=$(kubectl get ksvc cloudevents-spring -o jsonpath='{.status.url}') ``` Then send a CloudEvent to it with: @@ -69,7 +114,7 @@ $ curl \ -H "ce-type: curl.demo" \ -H "ce-id: 123-abc" \ -d '{"name":"Dave"}' \ - http://cloudevents-java.sslip.io + "$SERVICE_URL" ``` You can also send CloudEvents spawning a temporary curl pod in your cluster @@ -85,10 +130,11 @@ $ kubectl run curl \ -H "ce-type: curl.demo" \ -H "ce-id: 123-abc" \ -d '{"name":"Dave"}' \ - http://cloudevents-java.default.svc + http://cloudevents-spring.default.svc ``` -You'll see on the console: +In default reply mode, you'll see a response like this (the generated event ID +will vary): ```bash > POST / HTTP/1.1 @@ -102,23 +148,48 @@ You'll see on the console: > ce-id: 123-abc > Content-Length: 15 > -< HTTP/1.1 202 Accepted +< HTTP/1.1 200 OK < ce-specversion: 1.0 -< ce-id: 123-abc +< ce-id: d93cabfa-0a9b-4500-9a45-21791d132490 < ce-source: https://github.com/knative/docs/code-samples/serving/cloudevents/cloudevents-spring -< ce-type: curl.demo +< ce-type: org.knative.examples.cloudevents.spring.Response < content-type: application/json -< content-length: 15 < {"name":"Dave"} ``` -## Removing the sample app deployment +## Testing forwarding with K_SINK -To remove the sample app from your cluster, delete the service record: +Set `K_SINK` to a reachable HTTP endpoint that accepts CloudEvents. For example, +if an event receiver Service is named `event-receiver` in the `default` namespace: ```bash -kubectl delete --filename service.yaml +kubectl patch ksvc cloudevents-spring --type=json -p='[ + {"op":"add","path":"/spec/template/spec/containers/0/env","value":[ + {"name":"K_SINK","value":"http://event-receiver.default.svc"} + ]} +]' +kubectl wait --for=condition=Ready ksvc/cloudevents-spring --timeout=180s +``` + +Repeat the same curl request. The application forwards the event payload to the +sink with the new CloudEvent ID, source, and type described above. After a +successful delivery, the caller receives `202 Accepted` with an empty body and +no response CloudEvent headers. Check the receiver to verify the forwarded event; +the empty response alone does not show its contents. A sink HTTP failure must +propagate as a server error to the caller rather than being acknowledged as a +successful delivery. Sink connections have a 5-second connection timeout and a +10-second read timeout. Inbound authorization and other arbitrary HTTP headers +are not copied to the sink; only the newly generated event attributes and JSON +payload are sent. + +To return to reply mode: + +```bash +kubectl patch ksvc cloudevents-spring --type=json -p='[ + {"op":"remove","path":"/spec/template/spec/containers/0/env"} +]' +kubectl wait --for=condition=Ready ksvc/cloudevents-spring --timeout=180s ``` ## Removing the sample app deployment diff --git a/code-samples/serving/cloudevents/cloudevents-spring/mvnw b/code-samples/serving/cloudevents/cloudevents-spring/mvnw deleted file mode 100755 index a16b5431b4..0000000000 --- a/code-samples/serving/cloudevents/cloudevents-spring/mvnw +++ /dev/null @@ -1,310 +0,0 @@ -#!/bin/sh -# ---------------------------------------------------------------------------- -# Licensed to the Apache Software Foundation (ASF) under one -# or more contributor license agreements. See the NOTICE file -# distributed with this work for additional information -# regarding copyright ownership. The ASF licenses this file -# to you under the Apache License, Version 2.0 (the -# "License"); you may not use this file except in compliance -# with the License. You may obtain a copy of the License at -# -# https://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, -# software distributed under the License is distributed on an -# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY -# KIND, either express or implied. See the License for the -# specific language governing permissions and limitations -# under the License. -# ---------------------------------------------------------------------------- - -# ---------------------------------------------------------------------------- -# Maven Start Up Batch script -# -# Required ENV vars: -# ------------------ -# JAVA_HOME - location of a JDK home dir -# -# Optional ENV vars -# ----------------- -# M2_HOME - location of maven2's installed home dir -# MAVEN_OPTS - parameters passed to the Java VM when running Maven -# e.g. to debug Maven itself, use -# set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000 -# MAVEN_SKIP_RC - flag to disable loading of mavenrc files -# ---------------------------------------------------------------------------- - -if [ -z "$MAVEN_SKIP_RC" ] ; then - - if [ -f /etc/mavenrc ] ; then - . /etc/mavenrc - fi - - if [ -f "$HOME/.mavenrc" ] ; then - . "$HOME/.mavenrc" - fi - -fi - -# OS specific support. $var _must_ be set to either true or false. -cygwin=false; -darwin=false; -mingw=false -case "`uname`" in - CYGWIN*) cygwin=true ;; - MINGW*) mingw=true;; - Darwin*) darwin=true - # Use /usr/libexec/java_home if available, otherwise fall back to /Library/Java/Home - # See https://developer.apple.com/library/mac/qa/qa1170/_index.html - if [ -z "$JAVA_HOME" ]; then - if [ -x "/usr/libexec/java_home" ]; then - export JAVA_HOME="`/usr/libexec/java_home`" - else - export JAVA_HOME="/Library/Java/Home" - fi - fi - ;; -esac - -if [ -z "$JAVA_HOME" ] ; then - if [ -r /etc/gentoo-release ] ; then - JAVA_HOME=`java-config --jre-home` - fi -fi - -if [ -z "$M2_HOME" ] ; then - ## resolve links - $0 may be a link to maven's home - PRG="$0" - - # need this for relative symlinks - while [ -h "$PRG" ] ; do - ls=`ls -ld "$PRG"` - link=`expr "$ls" : '.*-> \(.*\)$'` - if expr "$link" : '/.*' > /dev/null; then - PRG="$link" - else - PRG="`dirname "$PRG"`/$link" - fi - done - - saveddir=`pwd` - - M2_HOME=`dirname "$PRG"`/.. - - # make it fully qualified - M2_HOME=`cd "$M2_HOME" && pwd` - - cd "$saveddir" - # echo Using m2 at $M2_HOME -fi - -# For Cygwin, ensure paths are in UNIX format before anything is touched -if $cygwin ; then - [ -n "$M2_HOME" ] && - M2_HOME=`cygpath --unix "$M2_HOME"` - [ -n "$JAVA_HOME" ] && - JAVA_HOME=`cygpath --unix "$JAVA_HOME"` - [ -n "$CLASSPATH" ] && - CLASSPATH=`cygpath --path --unix "$CLASSPATH"` -fi - -# For Mingw, ensure paths are in UNIX format before anything is touched -if $mingw ; then - [ -n "$M2_HOME" ] && - M2_HOME="`(cd "$M2_HOME"; pwd)`" - [ -n "$JAVA_HOME" ] && - JAVA_HOME="`(cd "$JAVA_HOME"; pwd)`" -fi - -if [ -z "$JAVA_HOME" ]; then - javaExecutable="`which javac`" - if [ -n "$javaExecutable" ] && ! [ "`expr \"$javaExecutable\" : '\([^ ]*\)'`" = "no" ]; then - # readlink(1) is not available as standard on Solaris 10. - readLink=`which readlink` - if [ ! `expr "$readLink" : '\([^ ]*\)'` = "no" ]; then - if $darwin ; then - javaHome="`dirname \"$javaExecutable\"`" - javaExecutable="`cd \"$javaHome\" && pwd -P`/javac" - else - javaExecutable="`readlink -f \"$javaExecutable\"`" - fi - javaHome="`dirname \"$javaExecutable\"`" - javaHome=`expr "$javaHome" : '\(.*\)/bin'` - JAVA_HOME="$javaHome" - export JAVA_HOME - fi - fi -fi - -if [ -z "$JAVACMD" ] ; then - if [ -n "$JAVA_HOME" ] ; then - if [ -x "$JAVA_HOME/jre/sh/java" ] ; then - # IBM's JDK on AIX uses strange locations for the executables - JAVACMD="$JAVA_HOME/jre/sh/java" - else - JAVACMD="$JAVA_HOME/bin/java" - fi - else - JAVACMD="`which java`" - fi -fi - -if [ ! -x "$JAVACMD" ] ; then - echo "Error: JAVA_HOME is not defined correctly." >&2 - echo " We cannot execute $JAVACMD" >&2 - exit 1 -fi - -if [ -z "$JAVA_HOME" ] ; then - echo "Warning: JAVA_HOME environment variable is not set." -fi - -CLASSWORLDS_LAUNCHER=org.codehaus.plexus.classworlds.launcher.Launcher - -# traverses directory structure from process work directory to filesystem root -# first directory with .mvn subdirectory is considered project base directory -find_maven_basedir() { - - if [ -z "$1" ] - then - echo "Path not specified to find_maven_basedir" - return 1 - fi - - basedir="$1" - wdir="$1" - while [ "$wdir" != '/' ] ; do - if [ -d "$wdir"/.mvn ] ; then - basedir=$wdir - break - fi - # workaround for JBEAP-8937 (on Solaris 10/Sparc) - if [ -d "${wdir}" ]; then - wdir=`cd "$wdir/.."; pwd` - fi - # end of workaround - done - echo "${basedir}" -} - -# concatenates all lines of a file -concat_lines() { - if [ -f "$1" ]; then - echo "$(tr -s '\n' ' ' < "$1")" - fi -} - -BASE_DIR=`find_maven_basedir "$(pwd)"` -if [ -z "$BASE_DIR" ]; then - exit 1; -fi - -########################################################################################## -# Extension to allow automatically downloading the maven-wrapper.jar from Maven-central -# This allows using the maven wrapper in projects that prohibit checking in binary data. -########################################################################################## -if [ -r "$BASE_DIR/.mvn/wrapper/maven-wrapper.jar" ]; then - if [ "$MVNW_VERBOSE" = true ]; then - echo "Found .mvn/wrapper/maven-wrapper.jar" - fi -else - if [ "$MVNW_VERBOSE" = true ]; then - echo "Couldn't find .mvn/wrapper/maven-wrapper.jar, downloading it ..." - fi - if [ -n "$MVNW_REPOURL" ]; then - jarUrl="$MVNW_REPOURL/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar" - else - jarUrl="https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar" - fi - while IFS="=" read key value; do - case "$key" in (wrapperUrl) jarUrl="$value"; break ;; - esac - done < "$BASE_DIR/.mvn/wrapper/maven-wrapper.properties" - if [ "$MVNW_VERBOSE" = true ]; then - echo "Downloading from: $jarUrl" - fi - wrapperJarPath="$BASE_DIR/.mvn/wrapper/maven-wrapper.jar" - if $cygwin; then - wrapperJarPath=`cygpath --path --windows "$wrapperJarPath"` - fi - - if command -v wget > /dev/null; then - if [ "$MVNW_VERBOSE" = true ]; then - echo "Found wget ... using wget" - fi - if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then - wget "$jarUrl" -O "$wrapperJarPath" - else - wget --http-user=$MVNW_USERNAME --http-password=$MVNW_PASSWORD "$jarUrl" -O "$wrapperJarPath" - fi - elif command -v curl > /dev/null; then - if [ "$MVNW_VERBOSE" = true ]; then - echo "Found curl ... using curl" - fi - if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then - curl -o "$wrapperJarPath" "$jarUrl" -f - else - curl --user $MVNW_USERNAME:$MVNW_PASSWORD -o "$wrapperJarPath" "$jarUrl" -f - fi - - else - if [ "$MVNW_VERBOSE" = true ]; then - echo "Falling back to using Java to download" - fi - javaClass="$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.java" - # For Cygwin, switch paths to Windows format before running javac - if $cygwin; then - javaClass=`cygpath --path --windows "$javaClass"` - fi - if [ -e "$javaClass" ]; then - if [ ! -e "$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.class" ]; then - if [ "$MVNW_VERBOSE" = true ]; then - echo " - Compiling MavenWrapperDownloader.java ..." - fi - # Compiling the Java class - ("$JAVA_HOME/bin/javac" "$javaClass") - fi - if [ -e "$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.class" ]; then - # Running the downloader - if [ "$MVNW_VERBOSE" = true ]; then - echo " - Running MavenWrapperDownloader.java ..." - fi - ("$JAVA_HOME/bin/java" -cp .mvn/wrapper MavenWrapperDownloader "$MAVEN_PROJECTBASEDIR") - fi - fi - fi -fi -########################################################################################## -# End of extension -########################################################################################## - -export MAVEN_PROJECTBASEDIR=${MAVEN_BASEDIR:-"$BASE_DIR"} -if [ "$MVNW_VERBOSE" = true ]; then - echo $MAVEN_PROJECTBASEDIR -fi -MAVEN_OPTS="$(concat_lines "$MAVEN_PROJECTBASEDIR/.mvn/jvm.config") $MAVEN_OPTS" - -# For Cygwin, switch paths to Windows format before running java -if $cygwin; then - [ -n "$M2_HOME" ] && - M2_HOME=`cygpath --path --windows "$M2_HOME"` - [ -n "$JAVA_HOME" ] && - JAVA_HOME=`cygpath --path --windows "$JAVA_HOME"` - [ -n "$CLASSPATH" ] && - CLASSPATH=`cygpath --path --windows "$CLASSPATH"` - [ -n "$MAVEN_PROJECTBASEDIR" ] && - MAVEN_PROJECTBASEDIR=`cygpath --path --windows "$MAVEN_PROJECTBASEDIR"` -fi - -# Provide a "standardized" way to retrieve the CLI args that will -# work with both Windows and non-Windows executions. -MAVEN_CMD_LINE_ARGS="$MAVEN_CONFIG $@" -export MAVEN_CMD_LINE_ARGS - -WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain - -exec "$JAVACMD" \ - $MAVEN_OPTS \ - -classpath "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.jar" \ - "-Dmaven.home=${M2_HOME}" "-Dmaven.multiModuleProjectDirectory=${MAVEN_PROJECTBASEDIR}" \ - ${WRAPPER_LAUNCHER} $MAVEN_CONFIG "$@" diff --git a/code-samples/serving/cloudevents/cloudevents-spring/mvnw.cmd b/code-samples/serving/cloudevents/cloudevents-spring/mvnw.cmd deleted file mode 100644 index a16b5431b4..0000000000 --- a/code-samples/serving/cloudevents/cloudevents-spring/mvnw.cmd +++ /dev/null @@ -1,310 +0,0 @@ -#!/bin/sh -# ---------------------------------------------------------------------------- -# Licensed to the Apache Software Foundation (ASF) under one -# or more contributor license agreements. See the NOTICE file -# distributed with this work for additional information -# regarding copyright ownership. The ASF licenses this file -# to you under the Apache License, Version 2.0 (the -# "License"); you may not use this file except in compliance -# with the License. You may obtain a copy of the License at -# -# https://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, -# software distributed under the License is distributed on an -# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY -# KIND, either express or implied. See the License for the -# specific language governing permissions and limitations -# under the License. -# ---------------------------------------------------------------------------- - -# ---------------------------------------------------------------------------- -# Maven Start Up Batch script -# -# Required ENV vars: -# ------------------ -# JAVA_HOME - location of a JDK home dir -# -# Optional ENV vars -# ----------------- -# M2_HOME - location of maven2's installed home dir -# MAVEN_OPTS - parameters passed to the Java VM when running Maven -# e.g. to debug Maven itself, use -# set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000 -# MAVEN_SKIP_RC - flag to disable loading of mavenrc files -# ---------------------------------------------------------------------------- - -if [ -z "$MAVEN_SKIP_RC" ] ; then - - if [ -f /etc/mavenrc ] ; then - . /etc/mavenrc - fi - - if [ -f "$HOME/.mavenrc" ] ; then - . "$HOME/.mavenrc" - fi - -fi - -# OS specific support. $var _must_ be set to either true or false. -cygwin=false; -darwin=false; -mingw=false -case "`uname`" in - CYGWIN*) cygwin=true ;; - MINGW*) mingw=true;; - Darwin*) darwin=true - # Use /usr/libexec/java_home if available, otherwise fall back to /Library/Java/Home - # See https://developer.apple.com/library/mac/qa/qa1170/_index.html - if [ -z "$JAVA_HOME" ]; then - if [ -x "/usr/libexec/java_home" ]; then - export JAVA_HOME="`/usr/libexec/java_home`" - else - export JAVA_HOME="/Library/Java/Home" - fi - fi - ;; -esac - -if [ -z "$JAVA_HOME" ] ; then - if [ -r /etc/gentoo-release ] ; then - JAVA_HOME=`java-config --jre-home` - fi -fi - -if [ -z "$M2_HOME" ] ; then - ## resolve links - $0 may be a link to maven's home - PRG="$0" - - # need this for relative symlinks - while [ -h "$PRG" ] ; do - ls=`ls -ld "$PRG"` - link=`expr "$ls" : '.*-> \(.*\)$'` - if expr "$link" : '/.*' > /dev/null; then - PRG="$link" - else - PRG="`dirname "$PRG"`/$link" - fi - done - - saveddir=`pwd` - - M2_HOME=`dirname "$PRG"`/.. - - # make it fully qualified - M2_HOME=`cd "$M2_HOME" && pwd` - - cd "$saveddir" - # echo Using m2 at $M2_HOME -fi - -# For Cygwin, ensure paths are in UNIX format before anything is touched -if $cygwin ; then - [ -n "$M2_HOME" ] && - M2_HOME=`cygpath --unix "$M2_HOME"` - [ -n "$JAVA_HOME" ] && - JAVA_HOME=`cygpath --unix "$JAVA_HOME"` - [ -n "$CLASSPATH" ] && - CLASSPATH=`cygpath --path --unix "$CLASSPATH"` -fi - -# For Mingw, ensure paths are in UNIX format before anything is touched -if $mingw ; then - [ -n "$M2_HOME" ] && - M2_HOME="`(cd "$M2_HOME"; pwd)`" - [ -n "$JAVA_HOME" ] && - JAVA_HOME="`(cd "$JAVA_HOME"; pwd)`" -fi - -if [ -z "$JAVA_HOME" ]; then - javaExecutable="`which javac`" - if [ -n "$javaExecutable" ] && ! [ "`expr \"$javaExecutable\" : '\([^ ]*\)'`" = "no" ]; then - # readlink(1) is not available as standard on Solaris 10. - readLink=`which readlink` - if [ ! `expr "$readLink" : '\([^ ]*\)'` = "no" ]; then - if $darwin ; then - javaHome="`dirname \"$javaExecutable\"`" - javaExecutable="`cd \"$javaHome\" && pwd -P`/javac" - else - javaExecutable="`readlink -f \"$javaExecutable\"`" - fi - javaHome="`dirname \"$javaExecutable\"`" - javaHome=`expr "$javaHome" : '\(.*\)/bin'` - JAVA_HOME="$javaHome" - export JAVA_HOME - fi - fi -fi - -if [ -z "$JAVACMD" ] ; then - if [ -n "$JAVA_HOME" ] ; then - if [ -x "$JAVA_HOME/jre/sh/java" ] ; then - # IBM's JDK on AIX uses strange locations for the executables - JAVACMD="$JAVA_HOME/jre/sh/java" - else - JAVACMD="$JAVA_HOME/bin/java" - fi - else - JAVACMD="`which java`" - fi -fi - -if [ ! -x "$JAVACMD" ] ; then - echo "Error: JAVA_HOME is not defined correctly." >&2 - echo " We cannot execute $JAVACMD" >&2 - exit 1 -fi - -if [ -z "$JAVA_HOME" ] ; then - echo "Warning: JAVA_HOME environment variable is not set." -fi - -CLASSWORLDS_LAUNCHER=org.codehaus.plexus.classworlds.launcher.Launcher - -# traverses directory structure from process work directory to filesystem root -# first directory with .mvn subdirectory is considered project base directory -find_maven_basedir() { - - if [ -z "$1" ] - then - echo "Path not specified to find_maven_basedir" - return 1 - fi - - basedir="$1" - wdir="$1" - while [ "$wdir" != '/' ] ; do - if [ -d "$wdir"/.mvn ] ; then - basedir=$wdir - break - fi - # workaround for JBEAP-8937 (on Solaris 10/Sparc) - if [ -d "${wdir}" ]; then - wdir=`cd "$wdir/.."; pwd` - fi - # end of workaround - done - echo "${basedir}" -} - -# concatenates all lines of a file -concat_lines() { - if [ -f "$1" ]; then - echo "$(tr -s '\n' ' ' < "$1")" - fi -} - -BASE_DIR=`find_maven_basedir "$(pwd)"` -if [ -z "$BASE_DIR" ]; then - exit 1; -fi - -########################################################################################## -# Extension to allow automatically downloading the maven-wrapper.jar from Maven-central -# This allows using the maven wrapper in projects that prohibit checking in binary data. -########################################################################################## -if [ -r "$BASE_DIR/.mvn/wrapper/maven-wrapper.jar" ]; then - if [ "$MVNW_VERBOSE" = true ]; then - echo "Found .mvn/wrapper/maven-wrapper.jar" - fi -else - if [ "$MVNW_VERBOSE" = true ]; then - echo "Couldn't find .mvn/wrapper/maven-wrapper.jar, downloading it ..." - fi - if [ -n "$MVNW_REPOURL" ]; then - jarUrl="$MVNW_REPOURL/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar" - else - jarUrl="https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar" - fi - while IFS="=" read key value; do - case "$key" in (wrapperUrl) jarUrl="$value"; break ;; - esac - done < "$BASE_DIR/.mvn/wrapper/maven-wrapper.properties" - if [ "$MVNW_VERBOSE" = true ]; then - echo "Downloading from: $jarUrl" - fi - wrapperJarPath="$BASE_DIR/.mvn/wrapper/maven-wrapper.jar" - if $cygwin; then - wrapperJarPath=`cygpath --path --windows "$wrapperJarPath"` - fi - - if command -v wget > /dev/null; then - if [ "$MVNW_VERBOSE" = true ]; then - echo "Found wget ... using wget" - fi - if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then - wget "$jarUrl" -O "$wrapperJarPath" - else - wget --http-user=$MVNW_USERNAME --http-password=$MVNW_PASSWORD "$jarUrl" -O "$wrapperJarPath" - fi - elif command -v curl > /dev/null; then - if [ "$MVNW_VERBOSE" = true ]; then - echo "Found curl ... using curl" - fi - if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then - curl -o "$wrapperJarPath" "$jarUrl" -f - else - curl --user $MVNW_USERNAME:$MVNW_PASSWORD -o "$wrapperJarPath" "$jarUrl" -f - fi - - else - if [ "$MVNW_VERBOSE" = true ]; then - echo "Falling back to using Java to download" - fi - javaClass="$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.java" - # For Cygwin, switch paths to Windows format before running javac - if $cygwin; then - javaClass=`cygpath --path --windows "$javaClass"` - fi - if [ -e "$javaClass" ]; then - if [ ! -e "$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.class" ]; then - if [ "$MVNW_VERBOSE" = true ]; then - echo " - Compiling MavenWrapperDownloader.java ..." - fi - # Compiling the Java class - ("$JAVA_HOME/bin/javac" "$javaClass") - fi - if [ -e "$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.class" ]; then - # Running the downloader - if [ "$MVNW_VERBOSE" = true ]; then - echo " - Running MavenWrapperDownloader.java ..." - fi - ("$JAVA_HOME/bin/java" -cp .mvn/wrapper MavenWrapperDownloader "$MAVEN_PROJECTBASEDIR") - fi - fi - fi -fi -########################################################################################## -# End of extension -########################################################################################## - -export MAVEN_PROJECTBASEDIR=${MAVEN_BASEDIR:-"$BASE_DIR"} -if [ "$MVNW_VERBOSE" = true ]; then - echo $MAVEN_PROJECTBASEDIR -fi -MAVEN_OPTS="$(concat_lines "$MAVEN_PROJECTBASEDIR/.mvn/jvm.config") $MAVEN_OPTS" - -# For Cygwin, switch paths to Windows format before running java -if $cygwin; then - [ -n "$M2_HOME" ] && - M2_HOME=`cygpath --path --windows "$M2_HOME"` - [ -n "$JAVA_HOME" ] && - JAVA_HOME=`cygpath --path --windows "$JAVA_HOME"` - [ -n "$CLASSPATH" ] && - CLASSPATH=`cygpath --path --windows "$CLASSPATH"` - [ -n "$MAVEN_PROJECTBASEDIR" ] && - MAVEN_PROJECTBASEDIR=`cygpath --path --windows "$MAVEN_PROJECTBASEDIR"` -fi - -# Provide a "standardized" way to retrieve the CLI args that will -# work with both Windows and non-Windows executions. -MAVEN_CMD_LINE_ARGS="$MAVEN_CONFIG $@" -export MAVEN_CMD_LINE_ARGS - -WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain - -exec "$JAVACMD" \ - $MAVEN_OPTS \ - -classpath "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.jar" \ - "-Dmaven.home=${M2_HOME}" "-Dmaven.multiModuleProjectDirectory=${MAVEN_PROJECTBASEDIR}" \ - ${WRAPPER_LAUNCHER} $MAVEN_CONFIG "$@" diff --git a/code-samples/serving/cloudevents/cloudevents-spring/pom.xml b/code-samples/serving/cloudevents/cloudevents-spring/pom.xml index a7b9e444cd..26b90c8982 100644 --- a/code-samples/serving/cloudevents/cloudevents-spring/pom.xml +++ b/code-samples/serving/cloudevents/cloudevents-spring/pom.xml @@ -11,43 +11,58 @@ org.springframework.boot spring-boot-starter-parent - 2.4.0 + 4.0.9 - 1.8 + 25 + 5.0.5 + amd64 org.springframework.boot - spring-boot-starter-web + spring-boot-starter-webmvc org.springframework.cloud spring-cloud-function-web - 3.1.0-M5 - - - org.springframework.boot - spring-boot-starter-test - test + ${spring-cloud-function.version} + + io.spring.javaformat + spring-javaformat-maven-plugin + 0.0.47 + + + + validate + + + + com.google.cloud.tools jib-maven-plugin - 2.0.0 + 3.5.2 + + eclipse-temurin:25-jre + + + ${image.architecture} + linux + + + - - -Dorg.slf4j.simpleLogger.defaultLogLevel=warn - org.knative.examples.cloudevents.spring.CloudEventSampleApplication @@ -55,34 +70,4 @@ - - - spring-milestones - Spring Milestones - https://repo.spring.io/milestone - - - spring-snapshots - Spring Snapshots - https://repo.spring.io/snapshot - - true - - - - - - spring-milestones - Spring Milestones - https://repo.spring.io/milestone - - - spring-snapshots - Spring Snapshots - https://repo.spring.io/snapshot - - true - - - diff --git a/code-samples/serving/cloudevents/cloudevents-spring/src/main/java/org/knative/examples/cloudevents/spring/CloudEventSampleApplication.java b/code-samples/serving/cloudevents/cloudevents-spring/src/main/java/org/knative/examples/cloudevents/spring/CloudEventSampleApplication.java index ea98ce4d02..5515f89369 100644 --- a/code-samples/serving/cloudevents/cloudevents-spring/src/main/java/org/knative/examples/cloudevents/spring/CloudEventSampleApplication.java +++ b/code-samples/serving/cloudevents/cloudevents-spring/src/main/java/org/knative/examples/cloudevents/spring/CloudEventSampleApplication.java @@ -1,6 +1,7 @@ package org.knative.examples.cloudevents.spring; import java.net.URI; +import java.time.Duration; import java.util.Map; import java.util.function.Consumer; import java.util.function.Function; @@ -9,70 +10,57 @@ import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression; -import org.springframework.boot.web.client.RestTemplateBuilder; -import org.springframework.cloud.function.cloudevent.CloudEventAttributesProvider; +import org.springframework.cloud.function.cloudevent.CloudEventMessageBuilder; +import org.springframework.cloud.function.cloudevent.CloudEventsFunctionExtensionConfiguration; import org.springframework.cloud.function.cloudevent.CloudEventMessageUtils; -import org.springframework.cloud.function.web.util.HeaderUtils; +import org.springframework.http.MediaType; +import org.springframework.http.client.SimpleClientHttpRequestFactory; import org.springframework.context.annotation.Bean; -import org.springframework.http.RequestEntity; +import org.springframework.context.annotation.Import; import org.springframework.messaging.Message; import org.springframework.messaging.MessageHeaders; -import org.springframework.web.client.RestTemplate; +import org.springframework.web.client.RestClient; @SpringBootApplication(proxyBeanMethods = false) +@Import(CloudEventsFunctionExtensionConfiguration.class) public class CloudEventSampleApplication { public static void main(String[] args) { SpringApplication.run(CloudEventSampleApplication.class, args); } - @Bean - public CloudEventAttributesProvider cloudEventAttributesProvider() { - return attrs -> attrs.setType("org.knative.examples.cloudevents.spring.Response") - .setSource("https://github.com/knative/docs/code-samples/serving/cloudevents/cloudevents-spring"); + private static Message response(T data) { + return CloudEventMessageBuilder.withData(data) + .setType("org.knative.examples.cloudevents.spring.Response") + .setSource("https://github.com/knative/docs/code-samples/serving/cloudevents/cloudevents-spring") + .setHeader(MessageHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE) + .build(); } @Bean @ConditionalOnExpression("'${K_SINK:}'==''") - public Function echo() { - return message -> message; + public Function, Message>> echo() { + return CloudEventSampleApplication::response; } @Bean @ConditionalOnExpression("'${K_SINK:}'!=''") - public Consumer>> sink(CloudEventAttributesProvider provider, - RestTemplateBuilder builder, @Value("${K_SINK}") String url) { - RestTemplate client = builder.build(); - return message -> client.exchange(RequestEntity.post(URI.create(url)) - .headers(HeaderUtils - .fromMessage(new MessageHeaders(CloudEventMessageUtils.generateAttributes(message, provider)))) - .body(message.getPayload()), byte[].class); - } - -} - -class Foo { - - private String name; - - public Foo() { - } - - public Foo(String value) { - this.name = value; - } - - public String getName() { - return name; - } - - public void setName(String name) { - this.name = name; - } - - @Override - public String toString() { - return "Foo [name=" + this.name + "]"; + public Consumer>> sink(@Value("${K_SINK}") String url) { + SimpleClientHttpRequestFactory factory = new SimpleClientHttpRequestFactory(); + factory.setConnectTimeout(Duration.ofSeconds(5)); + factory.setReadTimeout(Duration.ofSeconds(10)); + RestClient client = RestClient.builder().requestFactory(factory).build(); + return message -> { + Message> event = response(message.getPayload()); + client.post() + .uri(URI.create(url)) + .contentType(MediaType.APPLICATION_JSON) + .headers(headers -> CloudEventMessageUtils.getAttributes(event) + .forEach((name, value) -> headers.set(name, value.toString()))) + .body(event.getPayload()) + .retrieve() + .toBodilessEntity(); + }; } } diff --git a/code-samples/serving/cloudevents/cloudevents-spring/src/main/java/org/knative/examples/cloudevents/spring/HttpHeaderFilter.java b/code-samples/serving/cloudevents/cloudevents-spring/src/main/java/org/knative/examples/cloudevents/spring/HttpHeaderFilter.java new file mode 100644 index 0000000000..9046dfd22c --- /dev/null +++ b/code-samples/serving/cloudevents/cloudevents-spring/src/main/java/org/knative/examples/cloudevents/spring/HttpHeaderFilter.java @@ -0,0 +1,150 @@ +package org.knative.examples.cloudevents.spring; + +import java.io.BufferedReader; +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.io.InputStreamReader; +import java.net.URI; +import java.nio.charset.StandardCharsets; +import java.util.Collections; +import java.util.Enumeration; +import java.util.List; +import java.util.Locale; + +import jakarta.servlet.FilterChain; +import jakarta.servlet.ReadListener; +import jakarta.servlet.ServletException; +import jakarta.servlet.ServletInputStream; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletRequestWrapper; +import jakarta.servlet.http.HttpServletResponse; +import jakarta.servlet.http.HttpServletResponseWrapper; +import tools.jackson.databind.JsonNode; +import tools.jackson.databind.ObjectMapper; + +import org.springframework.beans.factory.annotation.Value; +import org.springframework.http.MediaType; +import org.springframework.stereotype.Component; +import org.springframework.web.filter.OncePerRequestFilter; + +/** Validate JSON and bridge case-insensitive HTTP names to SCF message headers. */ +@Component +class HttpHeaderFilter extends OncePerRequestFilter { + + private final ObjectMapper mapper; + + private final boolean sinkMode; + + HttpHeaderFilter(ObjectMapper mapper, @Value("${K_SINK:}") String sink) { + this.mapper = mapper; + this.sinkMode = !sink.isEmpty(); + } + + @Override + protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) + throws ServletException, IOException { + if (!"POST".equals(request.getMethod())) { + response.sendError(405); + return; + } + byte[] body = request.getInputStream().readNBytes(1024 * 1024 + 1); + if (body.length > 1024 * 1024) { + response.sendError(413); + return; + } + try { + JsonNode json = mapper.readTree(body); + if (json == null || !json.isObject()) { + throw new IllegalArgumentException("Expected a JSON object"); + } + MediaType contentType = request.getContentType() == null ? MediaType.APPLICATION_JSON + : MediaType.parseMediaType(request.getContentType()); + if (contentType.isCompatibleWith(MediaType.valueOf("application/cloudevents+json"))) { + for (String name : List.of("id", "source", "type", "specversion")) { + if (!json.hasNonNull(name) || !json.get(name).isString() || json.get(name).asString().isBlank()) { + throw new IllegalArgumentException("Missing CloudEvent attribute: " + name); + } + } + if (!"1.0".equals(json.get("specversion").asString()) || !json.path("data").isObject()) { + throw new IllegalArgumentException("Expected a v1 JSON-object CloudEvent"); + } + URI.create(json.get("source").asString()); + } + else if (Collections.list(request.getHeaderNames()) + .stream() + .anyMatch(name -> name.toLowerCase(Locale.ROOT).startsWith("ce-"))) { + for (String name : List.of("id", "source", "type", "specversion")) { + String value = request.getHeader("ce-" + name); + if (value == null || value.isBlank()) { + throw new IllegalArgumentException("Missing CloudEvent attribute: " + name); + } + } + if (!"1.0".equals(request.getHeader("ce-specversion"))) { + throw new IllegalArgumentException("Expected CloudEvents 1.0"); + } + URI.create(request.getHeader("ce-source")); + } + } + catch (RuntimeException ex) { + response.sendError(400, "Expected valid JSON or a CloudEvents 1.0 JSON object"); + return; + } + HttpServletRequest normalized = new HttpServletRequestWrapper(request) { + @Override + public Enumeration getHeaderNames() { + return Collections.enumeration(Collections.list(super.getHeaderNames()) + .stream() + .map(name -> name.toLowerCase(Locale.ROOT)) + .toList()); + } + + @Override + public ServletInputStream getInputStream() { + ByteArrayInputStream input = new ByteArrayInputStream(body); + return new ServletInputStream() { + @Override + public int read() { + return input.read(); + } + + @Override + public boolean isFinished() { + return input.available() == 0; + } + + @Override + public boolean isReady() { + return true; + } + + @Override + public void setReadListener(ReadListener listener) { + throw new UnsupportedOperationException("Synchronous request body"); + } + }; + } + + @Override + public BufferedReader getReader() { + return new BufferedReader(new InputStreamReader(getInputStream(), StandardCharsets.UTF_8)); + } + }; + HttpServletResponse output = new HttpServletResponseWrapper(response) { + @Override + public void setHeader(String name, String value) { + if (!sinkMode || !name.toLowerCase(Locale.ROOT).startsWith("ce-")) { + super.setHeader(name, value); + } + } + + @Override + public void addHeader(String name, String value) { + if (!sinkMode || !name.toLowerCase(Locale.ROOT).startsWith("ce-")) { + super.addHeader(name, value); + } + } + }; + chain.doFilter(normalized, output); + } + +} diff --git a/code-samples/serving/cloudevents/cloudevents-spring/src/main/resources/application.properties b/code-samples/serving/cloudevents/cloudevents-spring/src/main/resources/application.properties new file mode 100644 index 0000000000..182b64b427 --- /dev/null +++ b/code-samples/serving/cloudevents/cloudevents-spring/src/main/resources/application.properties @@ -0,0 +1,5 @@ +# Knative injects PORT; SERVER_PORT can also override this Spring property. +server.port=${PORT:8080} + +# Only deliberately generated output attributes belong on HTTP responses. +spring.cloud.function.http.request-only-headers=ce-id,ce-source,ce-type,ce-specversion,ce-subject,ce-time,ce-dataschema,ce-datacontenttype diff --git a/code-samples/serving/cloudevents/cloudevents-spring/src/test/java/org/knative/examples/cloudevents/spring/CloudEventSampleApplicationFunctionTests.java b/code-samples/serving/cloudevents/cloudevents-spring/src/test/java/org/knative/examples/cloudevents/spring/CloudEventSampleApplicationFunctionTests.java deleted file mode 100644 index 0a61b6ed7a..0000000000 --- a/code-samples/serving/cloudevents/cloudevents-spring/src/test/java/org/knative/examples/cloudevents/spring/CloudEventSampleApplicationFunctionTests.java +++ /dev/null @@ -1,25 +0,0 @@ -package org.knative.examples.cloudevents.spring; - -import java.util.function.Function; - -import org.junit.jupiter.api.Test; - -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.test.context.SpringBootTest; -import org.springframework.cloud.function.context.FunctionCatalog; - -import static org.assertj.core.api.Assertions.assertThat; - -@SpringBootTest -public class CloudEventSampleApplicationFunctionTests { - - @Autowired - private FunctionCatalog catalog; - - @Test - public void functionInvocation() { - Function function = catalog.lookup("echo"); - assertThat(function.apply(new Foo("Dave")).getName()).isEqualTo("Dave"); - } - -} diff --git a/code-samples/serving/cloudevents/cloudevents-spring/src/test/java/org/knative/examples/cloudevents/spring/CloudEventSampleApplicationIntegrationTests.java b/code-samples/serving/cloudevents/cloudevents-spring/src/test/java/org/knative/examples/cloudevents/spring/CloudEventSampleApplicationIntegrationTests.java deleted file mode 100644 index 1fa6e5a8bf..0000000000 --- a/code-samples/serving/cloudevents/cloudevents-spring/src/test/java/org/knative/examples/cloudevents/spring/CloudEventSampleApplicationIntegrationTests.java +++ /dev/null @@ -1,51 +0,0 @@ -package org.knative.examples.cloudevents.spring; - -import java.net.URI; -import java.util.Arrays; - -import org.junit.jupiter.api.Test; - -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.test.context.SpringBootTest; -import org.springframework.boot.test.context.SpringBootTest.WebEnvironment; -import org.springframework.boot.test.web.client.TestRestTemplate; -import org.springframework.boot.web.server.LocalServerPort; -import org.springframework.http.HttpHeaders; -import org.springframework.http.RequestEntity; -import org.springframework.http.ResponseEntity; - -import static org.assertj.core.api.Assertions.assertThat; - -@SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT) -public class CloudEventSampleApplicationIntegrationTests { - - @Autowired - private TestRestTemplate rest; - - @LocalServerPort - private int port; - - @Test - void postAndCheckHeaders() throws Exception { - - ResponseEntity response = rest.exchange(RequestEntity.post(URI.create("http://localhost:" + port + "/")) // - .header("ce-id", "12345") // - .header("ce-specversion", "1.0") // - .header("ce-type", "io.spring.event") // - .header("ce-source", "https://spring.io/events") // - .body("{\"name\":\"Dave\"}"), String.class); - - HttpHeaders headers = response.getHeaders(); - - assertThat(headers).containsKey("ce-id"); - assertThat(headers).containsKey("ce-source"); - assertThat(headers).containsKey("ce-type"); - - assertThat(headers.get("ce-id")).isNotEqualTo("12345"); - assertThat(headers).containsEntry("ce-type", Arrays.asList("org.knative.examples.cloudevents.spring.Response")); - assertThat(headers).containsEntry("ce-source", - Arrays.asList("https://github.com/knative/docs/code-samples/serving/cloudevents/cloudevents-spring")); - - } - -} diff --git a/code-samples/serving/cloudevents/cloudevents-spring/src/test/java/org/knative/examples/cloudevents/spring/KSinkCloudEventSampleApplicationIntegrationTests.java b/code-samples/serving/cloudevents/cloudevents-spring/src/test/java/org/knative/examples/cloudevents/spring/KSinkCloudEventSampleApplicationIntegrationTests.java deleted file mode 100644 index a99ea93293..0000000000 --- a/code-samples/serving/cloudevents/cloudevents-spring/src/test/java/org/knative/examples/cloudevents/spring/KSinkCloudEventSampleApplicationIntegrationTests.java +++ /dev/null @@ -1,105 +0,0 @@ -package org.knative.examples.cloudevents.spring; - -import java.net.URI; -import java.util.Map; - -import org.junit.jupiter.api.AfterAll; -import org.junit.jupiter.api.BeforeAll; -import org.junit.jupiter.api.Test; - -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.beans.factory.annotation.Value; -import org.springframework.boot.test.context.SpringBootTest; -import org.springframework.boot.test.context.SpringBootTest.WebEnvironment; -import org.springframework.boot.test.context.TestConfiguration; -import org.springframework.boot.test.web.client.TestRestTemplate; -import org.springframework.http.HttpHeaders; -import org.springframework.http.HttpStatus; -import org.springframework.http.MediaType; -import org.springframework.http.RequestEntity; -import org.springframework.http.ResponseEntity; -import org.springframework.util.SocketUtils; -import org.springframework.web.bind.annotation.PostMapping; -import org.springframework.web.bind.annotation.RequestBody; -import org.springframework.web.bind.annotation.RequestHeader; -import org.springframework.web.bind.annotation.RestController; - -import static org.assertj.core.api.Assertions.assertThat; - -@SpringBootTest(webEnvironment = WebEnvironment.DEFINED_PORT) -public class KSinkCloudEventSampleApplicationIntegrationTests { - - @Autowired - private TestRestTemplate rest; - - @Autowired - private TestController controller; - - @Value("${server.port}") - private int port; - - @BeforeAll - static void setUp() { - System.setProperty("server.port", SocketUtils.findAvailableTcpPort() + ""); - System.setProperty("K_SINK", "http://localhost:${server.port}/sink"); - } - - @AfterAll - static void tearDown() { - System.clearProperty("server.port"); - System.clearProperty("K_SINK"); - } - - @Test - void postAndCheckHeaders() throws Exception { - - ResponseEntity response = rest.exchange(RequestEntity.post(URI.create("http://localhost:" + port + "/")) // - .header("ce-id", "12345") // - .header("ce-specversion", "1.0") // - .header("ce-type", "io.spring.event") // - .header("ce-source", "https://spring.io/events") // - .contentType(MediaType.APPLICATION_JSON) // - .body("{\"name\":\"Dave\"}"), String.class); - - assertThat(response.getStatusCode()).isEqualTo(HttpStatus.ACCEPTED); - assertThat(response.getBody()).isNull(); - HttpHeaders headers = response.getHeaders(); - - assertThat(headers).doesNotContainKey("ce-id"); - assertThat(headers).doesNotContainKey("ce-source"); - - Map sink = controller.getHeaders(); - assertThat(sink).containsKey("ce-id"); - assertThat(sink.get("ce-id")).isNotEqualTo("12345"); - assertThat(sink).containsEntry("ce-type", "org.knative.examples.cloudevents.spring.Response"); - assertThat(sink).containsEntry("ce-source", - "https://github.com/knative/docs/code-samples/serving/cloudevents/cloudevents-spring"); - - } - - @TestConfiguration - @RestController - static class TestController { - - private Map body; - - private Map headers; - - @PostMapping("/sink") - public String ok(@RequestBody Map body, @RequestHeader Map headers) { - this.body = body; - this.headers = headers; - return "OK"; - } - - public Map getHeaders() { - return headers; - } - - public Map getBody() { - return body; - } - - } - -} From 7bfc8aa748aedc6d723b279dbb08e03f0b5c1cf4 Mon Sep 17 00:00:00 2001 From: Dave Protasowski Date: Mon, 26 Oct 2026 09:16:04 -0400 Subject: [PATCH 24/44] Update the Vert.x CloudEvents sample --- .../cloudevents/cloudevents-vertx/README.md | 88 +++++-- .../cloudevents/cloudevents-vertx/pom.xml | 63 +++-- .../vertx/CloudEventSampleVerticle.java | 236 ++++++++---------- 3 files changed, 223 insertions(+), 164 deletions(-) diff --git a/code-samples/serving/cloudevents/cloudevents-vertx/README.md b/code-samples/serving/cloudevents/cloudevents-vertx/README.md index a428975f4e..771001f1a2 100644 --- a/code-samples/serving/cloudevents/cloudevents-vertx/README.md +++ b/code-samples/serving/cloudevents/cloudevents-vertx/README.md @@ -6,43 +6,71 @@ supports running in two modes: 1. The default mode has the app reply to your input events with the output event, which is simplest for demonstrating things working in isolation, but is also the model for working for the Knative Eventing `Broker` concept. The - input event is modified assigning a new source and type attribute. + input event is modified by assigning a new source; its type, ID, and data + are preserved. 2. `K_SINK` mode has the app send events to the destination encoded in `$K_SINK`, which is useful to demonstrate how folks can synthesize events to send to a Service or Broker when not initiated by a Broker invocation (e.g. - implementing an event source). The input event is modified assigning a new - source and type attribute. + implementing an event source). The input event is modified by assigning a + new source; its type, ID, and data are preserved. A successful sink response + (any 2xx status) produces an empty `202` response to the caller. A failed + delivery produces `500` (including a sink timeout after five seconds). -The application will use `$K_SINK`-mode whenever the environment variable is -specified. +The application uses `$K_SINK` mode when the variable contains a nonblank, +absolute HTTP(S) URL. An absent, empty, or whitespace-only `K_SINK` selects reply +mode. Both binary and JSON structured CloudEvents v1 are accepted; output is +sent in binary mode. Invalid CloudEvents receive `400` without forwarding. -Do the following steps to create the sample code and then deploy the app to your -cluster. You can also download a working copy of the sample, by running the -following commands: +The HTTP server listens on `$PORT`, defaulting to `8080`. Invalid ports or sink +URLs fail startup with a nonzero exit status. + +Clone the repository and enter the sample directory: ```bash git clone https://github.com/knative/docs.git knative-docs cd knative-docs/code-samples/serving/cloudevents/cloudevents-vertx ``` +Build with [pom.xml](pom.xml) and deploy with [service.yaml](service.yaml). + ## Before you begin - A Kubernetes cluster with Knative installed and DNS configured. See [Install Knative Serving](https://knative.dev/docs/install/serving/install-serving-with-yaml). -- [Docker](https://www.docker.com) installed and running on your local machine, - and a Docker Hub account configured (we'll use it for a container registry). +- Java 21 LTS and [Maven 3.9](https://maven.apache.org/) for the build. + The image uses the public Eclipse Temurin Java 21 JRE and runs as a non-root user. +- A container registry you can push to and that your cluster can pull from. + Configure its credentials for Jib as needed. Jib builds and pushes the image + without requiring a local Docker daemon. ## Build and deploy the sample -1. To build the image, run: +This sample uses CloudEvents Java SDK 5.1.0, including the JSON event-format +provider, and the SDK's compatible Vert.x 4.5.28 integration. The +[Vert.x 4 maintenance line is supported until April 2027](https://vertx.io/blog/eclipse-vert-x-4-5-28/). + +1. To build and push the image, run: ```bash -mvn compile jib:build -Dimage= +mvn clean verify jib:build -Dimage= ``` + Replace `` with your fully qualified registry image name. The image + defaults to Linux **arm64**. For an amd64 Docker host or cluster, add + `-Djib.architecture=amd64` to the Maven build command. Select the architecture + that matches your cluster. + + For local testing with Docker, build without pushing to a registry: + ```bash + mvn clean verify jib:buildTar -Dimage=cloudevents-vertx:local + docker load -i target/jib-image.tar + docker run --rm -p 8080:8080 cloudevents-vertx:local + ``` + Send the event below to `http://localhost:8080` instead of the service URL. + 2. Choose one of the following methods to deploy the sample: ### yaml - * To deploy the Knative Service, look in the `service.yaml` and replace `` with the deployed image name. Then run: + * To deploy the Knative Service, use [service.yaml](service.yaml) and replace `` with the built image name. Then run: ```bash kubectl apply -f service.yaml ``` @@ -60,13 +88,14 @@ Get the URL for your Service with: ```bash $ kubectl get ksvc NAME URL LATESTCREATED LATESTREADY READY REASON -cloudevents-vertx http://cloudevents-java.sslip.io cloudevents-vertx-86h28 cloudevents-vertx-86h28 True +cloudevents-vertx http://cloudevents-vertx.default.example.com cloudevents-vertx-86h28 cloudevents-vertx-86h28 True ``` -Then send a CloudEvent to it with: +Use the actual URL reported by your cluster, then send a CloudEvent to it: ```bash -$ curl \ +SERVICE_URL=$(kubectl get ksvc cloudevents-vertx -o jsonpath='{.status.url}') +curl \ -X POST -v \ -H "content-type: application/json" \ -H "ce-specversion: 1.0" \ @@ -74,7 +103,7 @@ $ curl \ -H "ce-type: curl.demo" \ -H "ce-id: 123-abc" \ -d '{"name":"Dave"}' \ - http://cloudevents-java.sslip.io + "$SERVICE_URL" ``` You can also send CloudEvents spawning a temporary curl pod in your cluster @@ -90,7 +119,7 @@ $ kubectl run curl \ -H "ce-type: curl.demo" \ -H "ce-id: 123-abc" \ -d '{"name":"Dave"}' \ - http://cloudevents-java.default.svc + http://cloudevents-vertx.default.svc ``` You'll see on the console: @@ -118,6 +147,29 @@ You'll see on the console: {"name":"Dave"} ``` +### Structured JSON input + +The equivalent structured CloudEvent produces the same binary response: + +```bash +curl -i "$SERVICE_URL" \ + -H 'content-type: application/cloudevents+json' \ + -d '{"specversion":"1.0","source":"http://curl-command","type":"curl.demo","id":"123-abc","datacontenttype":"application/json","data":{"name":"Dave"}}' +``` + +### Forwarding to a sink + +To enable sink mode, set `K_SINK` to the destination URL, for example: + +```bash +kn service update cloudevents-vertx --env K_SINK=http://my-sink.default.svc.cluster.local +``` + +Replace the URL with a reachable service or Broker that accepts CloudEvents. +Send the same test event again. The sink receives the transformed event and the +caller receives `202` with an empty body, rather than the event shown above. +For local Docker testing, pass `-e K_SINK=` to `docker run`. + ## Removing the sample app deployment To remove the sample app from your cluster, delete the service: diff --git a/code-samples/serving/cloudevents/cloudevents-vertx/pom.xml b/code-samples/serving/cloudevents/cloudevents-vertx/pom.xml index 0dd3451e18..b306a6447e 100644 --- a/code-samples/serving/cloudevents/cloudevents-vertx/pom.xml +++ b/code-samples/serving/cloudevents/cloudevents-vertx/pom.xml @@ -1,25 +1,44 @@ + xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd"> 4.0.0 - org.knative.examples cloudevents-example-vertx 1.0-SNAPSHOT - 1.8 - org.knative.examples.cloudevents.vertx.CloudEventSampleVerticle - 2.0.0-milestone2 + 21 + UTF-8 + 5.1.0 + + 4.5.28 + arm64 + + + + io.vertx + vertx-stack-depchain + ${vertx.version} + pom + import + + + + io.cloudevents cloudevents-core ${cloudevents.version} + + io.cloudevents + cloudevents-json-jackson + ${cloudevents.version} + io.cloudevents cloudevents-http-vertx @@ -30,33 +49,41 @@ + org.apache.maven.plugins maven-compiler-plugin - 3.8.0 + 3.16.0 - 1.8 - 1.8 + true + + + -Xlint:all,-classfile + -Werror + com.google.cloud.tools jib-maven-plugin - 2.0.0 + 3.5.2 + + eclipse-temurin:21-jre-jammy + + + linux + ${jib.architecture} + + + + org.knative.examples.cloudevents.vertx.CloudEventSampleVerticle + 65532:65532 - -Dorg.slf4j.simpleLogger.defaultLogLevel=warn + -XX:ActiveProcessorCount=2 - org.knative.examples.cloudevents.vertx.CloudEventSampleVerticle - - - io.reactiverse - vertx-maven-plugin - 1.0.22 - - diff --git a/code-samples/serving/cloudevents/cloudevents-vertx/src/main/java/org/knative/examples/cloudevents/vertx/CloudEventSampleVerticle.java b/code-samples/serving/cloudevents/cloudevents-vertx/src/main/java/org/knative/examples/cloudevents/vertx/CloudEventSampleVerticle.java index 3cfe694faa..07a1774765 100644 --- a/code-samples/serving/cloudevents/cloudevents-vertx/src/main/java/org/knative/examples/cloudevents/vertx/CloudEventSampleVerticle.java +++ b/code-samples/serving/cloudevents/cloudevents-vertx/src/main/java/org/knative/examples/cloudevents/vertx/CloudEventSampleVerticle.java @@ -8,157 +8,137 @@ import io.vertx.core.Handler; import io.vertx.core.Promise; import io.vertx.core.Vertx; -import io.vertx.core.buffer.Buffer; -import io.vertx.core.http.HttpServer; import io.vertx.core.http.HttpServerRequest; -import io.vertx.core.http.HttpServerResponse; -import io.vertx.ext.web.client.HttpResponse; import io.vertx.ext.web.client.WebClient; - +import io.vertx.ext.web.client.WebClientOptions; import java.net.URI; -import java.util.Optional; public class CloudEventSampleVerticle extends AbstractVerticle { + static final URI OUTPUT_SOURCE = + URI.create( + "https://github.com/knative/docs/code-samples/serving/cloudevents/cloudevents-vertx"); + private final int port; + private final URI sink; + private WebClient client; + + public CloudEventSampleVerticle() { + this(configuredPort(System.getenv("PORT")), configuredSink(System.getenv("K_SINK"))); + } + + // Tests can use an ephemeral listener and a real local HTTP sink. + CloudEventSampleVerticle(int port, URI sink) { + this.port = port; + this.sink = sink; + } + public static void main(String[] args) { + CloudEventSampleVerticle app = new CloudEventSampleVerticle(); Vertx vertx = Vertx.vertx(); - vertx.deployVerticle(new CloudEventSampleVerticle()); + vertx + .deployVerticle(app) + .onFailure( + cause -> { + cause.printStackTrace(); + vertx.close().onComplete(ignored -> System.exit(1)); + }); } - public void start(Promise startPromise) { - HttpServer server = vertx.createHttpServer(); + static int configuredPort(String value) { + int port = value == null ? 8080 : Integer.parseInt(value); + if (port < 1 || port > 65535) { + throw new IllegalArgumentException("PORT must be between 1 and 65535"); + } + return port; + } - // Get the port - int port = Optional.ofNullable(System.getenv("PORT")).map(Integer::parseInt).orElse(8080); + static URI configuredSink(String value) { + if (value == null || value.isBlank()) { + return null; + } + URI sink = URI.create(value); + if (!("http".equalsIgnoreCase(sink.getScheme()) || "https".equalsIgnoreCase(sink.getScheme())) + || sink.getHost() == null) { + throw new IllegalArgumentException("K_SINK must be an absolute HTTP(S) URL"); + } + return sink; + } - // Get the sink uri, if any - Optional env = Optional.ofNullable(System.getenv("K_SINK")).map(URI::create); - if (env.isPresent()) { - server.requestHandler(generateSinkHandler(WebClient.create(vertx), env.get())); + @Override + public void start(Promise startPromise) { + Handler handler; + if (sink == null) { + handler = generateEchoHandler(); } else { - // If K_SINK is not set, just echo back the events - server.requestHandler(generateEchoHandler()); + client = WebClient.create(vertx, new WebClientOptions().setConnectTimeout(2000)); + handler = generateSinkHandler(client, sink); } - - server - // Listen and complete verticle deploy - .listen(port, serverResult -> { - if (serverResult.succeeded()) { - System.out.println("Server started on port " + serverResult.result().actualPort()); - startPromise.complete(); - } else { - System.out.println("Error starting the server"); - serverResult.cause().printStackTrace(); - startPromise.fail(serverResult.cause()); - } - }); + vertx + .createHttpServer() + .requestHandler(handler) + .listen(port) + .onSuccess(server -> System.out.println("Server started on port " + server.actualPort())) + .mapEmpty() + .onComplete(startPromise); } - /** - * Generates an handler that does the echo of the received event - */ - public static Handler generateEchoHandler() { - return request -> { - // Transform the HttpRequest to Event - VertxMessageFactory - .createReader(request) - .map(MessageReader::toEvent) - .onComplete(asyncResult -> { - if (asyncResult.succeeded()) { - CloudEvent event = asyncResult.result(); - System.out.println("Received event: " + event); - - // Let's modify the event changing the source - CloudEvent outputEvent = CloudEventBuilder - .v1(event) - .withSource(URI.create("https://github.com/knative/docs/code-samples/serving/cloudevents/cloudevents-vertx")) - .build(); - - // Set response status code - HttpServerResponse response = request - .response() - .setStatusCode(202); + @Override + public void stop() { + if (client != null) { + client.close(); + } + } - // Reply with the event in binary mode - VertxMessageFactory - .createWriter(response) - .writeBinary(outputEvent); - } else { - System.out.println("Error while decoding the event: " + asyncResult.cause()); + private static CloudEvent transform(CloudEvent event) { + System.out.println("Received event: " + event); + return CloudEventBuilder.v1(event).withSource(OUTPUT_SOURCE).build(); + } - // Reply with a failure - request - .response() - .setStatusCode(400) - .end(); - } - }); - }; + /** Replies with the transformed event in binary mode. */ + public static Handler generateEchoHandler() { + return request -> + VertxMessageFactory.createReader(request) + .map(MessageReader::toEvent) + .onSuccess( + event -> + VertxMessageFactory.createWriter(request.response().setStatusCode(202)) + .writeBinary(transform(event))) + .onFailure( + cause -> { + System.out.println("Error while decoding the event: " + cause); + request.response().setStatusCode(400).end(); + }); } - /** - * Generates an handler that sink the does the echo of the received event - */ + /** Forwards the transformed event to a sink; the caller receives no response event. */ public static Handler generateSinkHandler(WebClient client, URI sink) { - return serverRequest -> { - // Transform the HttpRequest to Event - VertxMessageFactory - .createReader(serverRequest) - .map(MessageReader::toEvent) - .onComplete(asyncResult -> { - if (asyncResult.failed()) { - System.out.println("Error while decoding the event: " + asyncResult.cause()); - - // Reply with a failure - serverRequest - .response() - .setStatusCode(400) - .end(); - return; - } - - CloudEvent event = asyncResult.result(); - System.out.println("Received event: " + event); - - // Let's modify the event changing the source - CloudEvent outputEvent = CloudEventBuilder - .v1(event) - .withSource(URI.create("https://github.com/knative/docs/code-samples/serving/cloudevents/cloudevents-vertx")) - .build(); - - // Send the request to the sink and check the response - VertxMessageFactory - .createWriter(client.postAbs(sink.toString())) - .writeBinary(outputEvent) - .onComplete(ar -> { - if (ar.failed()) { - System.out.println("Something bad happened while sending event to the sink: " + ar.cause()); - serverRequest - .response() - .setStatusCode(500) - .end(); + return request -> + VertxMessageFactory.createReader(request) + .map(MessageReader::toEvent) + .onComplete( + decoded -> { + if (decoded.failed()) { + System.out.println("Error while decoding the event: " + decoded.cause()); + request.response().setStatusCode(400).end(); return; } - - HttpResponse response = ar.result(); - - - if (response.statusCode() >= 200 && response.statusCode() < 300) { - serverRequest - .response() - .setStatusCode(202) - .end(); - } else if (ar.succeeded()) { - System.out.println("Error received from sink: " + response.statusCode() + " " + response.statusMessage()); - serverRequest - .response() - .setStatusCode(500) - .end(); - } + VertxMessageFactory.createWriter(client.postAbs(sink.toString()).timeout(5000)) + .writeBinary(transform(decoded.result())) + .onComplete( + delivery -> { + if (delivery.succeeded() + && delivery.result().statusCode() >= 200 + && delivery.result().statusCode() < 300) { + request.response().setStatusCode(202).end(); + } else { + System.out.println( + "Sink delivery failed: " + + (delivery.failed() + ? delivery.cause() + : delivery.result().statusCode())); + request.response().setStatusCode(500).end(); + } + }); }); - }); - }; } - - } From 9de94b1bb1e79c277350b59705b73e162b184083 Mon Sep 17 00:00:00 2001 From: Dave Protasowski Date: Mon, 26 Oct 2026 09:16:04 -0400 Subject: [PATCH 25/44] Update the Go Git webhook sample --- code-samples/serving/gitwebhook-go/Dockerfile | 4 +- code-samples/serving/gitwebhook-go/README.md | 72 ++++++++----------- .../serving/gitwebhook-go/gitwebhook.go | 22 ++++-- code-samples/serving/gitwebhook-go/go.mod | 2 +- 4 files changed, 50 insertions(+), 50 deletions(-) diff --git a/code-samples/serving/gitwebhook-go/Dockerfile b/code-samples/serving/gitwebhook-go/Dockerfile index 54f3bd9ff8..5ffc0b1c8d 100644 --- a/code-samples/serving/gitwebhook-go/Dockerfile +++ b/code-samples/serving/gitwebhook-go/Dockerfile @@ -12,7 +12,7 @@ # See the License for the specific language governing permissions and # limitations under the License. -FROM golang:latest AS builder +FROM golang:1.27 AS builder ARG TARGETOS ARG TARGETARCH @@ -30,7 +30,7 @@ RUN go mod tidy # -mod=readonly ensures immutable go.mod and go.sum in container builds. RUN CGO_ENABLED=0 GOOS=${TARGETOS} GOARCH=${TARGETARCH} go build -v -o webhook-sample -FROM golang:alpine +FROM golang:1.27 EXPOSE 8080 COPY --from=builder /app/webhook-sample /webhook-sample diff --git a/code-samples/serving/gitwebhook-go/README.md b/code-samples/serving/gitwebhook-go/README.md index 129217a93e..c3d193880b 100644 --- a/code-samples/serving/gitwebhook-go/README.md +++ b/code-samples/serving/gitwebhook-go/README.md @@ -44,11 +44,8 @@ You must meet the following requirements to run this sample: - A [personal access token](https://help.github.com/articles/creating-a-personal-access-token-for-the-command-line/) that you will use to make API requests to GitHub. - - Ensure that you grant `read/write` permission in the repo for that personal - access token. - - 1. Follow the GitHub instructions to - + For a fine-grained token, select the target repository and grant + **Pull requests: Read and write** permission. - A webhook secret that you will use to validate requests. 1. Base64 encode the access token: @@ -75,33 +72,10 @@ You must meet the following requirements to run this sample: kubectl apply --filename github-secret.yaml ``` -1. Next, update the `service.yaml` file in the project to reference the tagged - image from step 1. - - ```yaml - apiVersion: serving.knative.dev/v1 - kind: Service - metadata: - name: gitwebhook - namespace: default - spec: - template: - spec: - containers: - - # Replace {DOCKER_HUB_USERNAME} with your actual docker hub username - image: docker.io/{DOCKER_HUB_USERNAME}/gitwebhook-go:latest - env: - - name: GITHUB_PERSONAL_TOKEN - valueFrom: - secretKeyRef: - name: githubsecret - key: personalAccessToken - - name: WEBHOOK_SECRET - valueFrom: - secretKeyRef: - name: githubsecret - key: webhookSecret - ``` +1. Next, update the image in the checked-in [Service manifest](service.yaml) + to reference the tag you built. The manifest reads `GITHUB_PERSONAL_TOKEN` and + `WEBHOOK_SECRET` from the keys in [the Secret manifest](github-secret.yaml); + keep those references when changing the image. 1. Use `kubectl` to apply the `service.yaml` file. @@ -118,18 +92,18 @@ You must meet the following requirements to run this sample: 1. Create a webhook in your GitHub repo using the URL for your `gitwebhook` service: - 1. Retrieve the hostname for this service, using the following command: + 1. Retrieve the URL for this service, using the following command: ```bash $ kubectl get ksvc gitwebhook \ - --output=custom-columns=NAME:.metadata.name,DOMAIN:.status.domain + --output=custom-columns=NAME:.metadata.name,URL:.status.url ``` Example response: ```bash - NAME DOMAIN - gitwebhook gitwebhook.default.MYCUSTOMDOMAIN.com + NAME URL + gitwebhook http://gitwebhook.default.MYCUSTOMDOMAIN.com ``` where `MYCUSTOMDOMAIN` is the domain that you set as your @@ -141,9 +115,10 @@ You must meet the following requirements to run this sample: 1. Click **Settings** > **Webhooks** > **Add webhook** to open the Webhooks page. - 1. Enter the **Payload URL** as `http://{DOMAIN}`, where `{DOMAIN}` is the - address from the `kubectl get ksvc gitwebhook` command. For example: - `http://gitwebhook.default.MYCUSTOMDOMAIN.com` + 1. Enter the URL from the `kubectl get ksvc gitwebhook` command as the + **Payload URL**, including its scheme. For example: + `https://gitwebhook.default.MYCUSTOMDOMAIN.com/`. + Use HTTPS with a valid certificate for a publicly accessible webhook. 1. Set the **Content type** to `application/json`. @@ -151,8 +126,11 @@ You must meet the following requirements to run this sample: you set in `webhookSecret` (not the base64 encoded value). For example: `mygithubwebhooksecret` - 1. If you did not [enabled TLS certificates](https://knative.dev/docs/serving/using-a-tls-cert), - click **Disable** under **SSL Validation**. + 1. Select **Let me select individual events**, then select **Pull requests**. + The default **Just the push event** does not invoke this sample's handler. + + 1. When using HTTPS, keep **SSL Validation** enabled. See + [enabling TLS certificates](https://knative.dev/docs/serving/using-a-tls-cert). 1. Click **Add webhook** to create the webhook. @@ -176,6 +154,17 @@ kubectl get revisions --output yaml ## Testing the service +The server listens on `PORT` (default `8080`). `WEBHOOK_SECRET` validates the +incoming GitHub `X-Hub-Signature` HMAC-SHA1 header; if it is unset or empty, +signature verification is disabled. Always configure it for a public endpoint. +`GITHUB_PERSONAL_TOKEN` authenticates outbound GitHub API requests. +`GITHUB_API_URL` optionally sets an absolute HTTP(S) API base URL (for example, +`https://github.example.com/api/v3/` for GitHub Enterprise); when unset, the +standard GitHub API is used. Configure this only for a trusted endpoint, since +API requests include the token. The webhook +endpoint is `/` and accepts only POST requests. Only pull request events trigger +an update; other events, including GitHub's initial ping, are ignored. + Now that you have the service running and the webhook created, send a Pull Request to the same GitHub repo where you added the webhook. If all is working right, you'll see the title of the PR will be modified, with the text @@ -187,4 +176,5 @@ To clean up the sample service: ```bash kubectl delete --filename service.yaml +kubectl delete --filename github-secret.yaml ``` diff --git a/code-samples/serving/gitwebhook-go/gitwebhook.go b/code-samples/serving/gitwebhook-go/gitwebhook.go index bcda2a2f06..d874a27ded 100644 --- a/code-samples/serving/gitwebhook-go/gitwebhook.go +++ b/code-samples/serving/gitwebhook-go/gitwebhook.go @@ -21,6 +21,7 @@ import ( "flag" "fmt" "log" + "net/url" "os" "strings" @@ -31,10 +32,9 @@ import ( ) const ( - // Secret given to github. Used for verifying the incoming objects. + // Personal Access Token created in GitHub that allows API calls. personalAccessTokenKey = "GITHUB_PERSONAL_TOKEN" - // Personal Access Token created in github that allows us to make - // calls into github. + // Secret given to GitHub for verifying incoming webhook requests. webhookSecretKey = "WEBHOOK_SECRET" // this is what we tack onto each PR title if not there already titleSuffix = "looks pretty legit" @@ -94,6 +94,13 @@ func main() { tc := oauth2.NewClient(ctx, ts) client := ghclient.NewClient(tc) + if baseURL := os.Getenv("GITHUB_API_URL"); baseURL != "" { + apiURL, err := url.Parse(strings.TrimRight(baseURL, "/") + "/") + if err != nil || apiURL.Host == "" || (apiURL.Scheme != "http" && apiURL.Scheme != "https") { + log.Fatal("GITHUB_API_URL must be an absolute HTTP(S) API base URL") + } + client.BaseURL = apiURL + } h := &GithubHandler{ client: client, @@ -103,8 +110,11 @@ func main() { hook := github.New(&github.Config{Secret: secretToken}) hook.RegisterEvents(h.HandlePullRequest, github.PullRequestEvent) - err := webhooks.Run(hook, ":8080", "/") - if err != nil { - fmt.Println("Failed to run the webhook") + port := os.Getenv("PORT") + if port == "" { + port = "8080" + } + if err := webhooks.Run(hook, ":"+port, "/"); err != nil { + log.Fatalf("Failed to run the webhook: %v", err) } } diff --git a/code-samples/serving/gitwebhook-go/go.mod b/code-samples/serving/gitwebhook-go/go.mod index 86eb6c430c..d0bf478b6c 100644 --- a/code-samples/serving/gitwebhook-go/go.mod +++ b/code-samples/serving/gitwebhook-go/go.mod @@ -1,6 +1,6 @@ module knative.dev/docs/code-samples/serving/gitwebhook-go -go 1.23.0 +go 1.27.0 require ( github.com/google/go-github v17.0.0+incompatible From 17037fad6bf444b8ab9e762c79cd5af5a425ec57 Mon Sep 17 00:00:00 2001 From: Dave Protasowski Date: Mon, 26 Oct 2026 09:16:04 -0400 Subject: [PATCH 26/44] Update the Go gRPC ping sample --- code-samples/serving/grpc-ping-go/Dockerfile | 2 +- code-samples/serving/grpc-ping-go/README.md | 57 +++++++++++-------- .../serving/grpc-ping-go/client/client.go | 2 +- code-samples/serving/grpc-ping-go/go.mod | 2 +- .../serving/grpc-ping-go/grpc-ping.go | 9 ++- 5 files changed, 43 insertions(+), 29 deletions(-) diff --git a/code-samples/serving/grpc-ping-go/Dockerfile b/code-samples/serving/grpc-ping-go/Dockerfile index 47f31de3fa..7ddc4f0479 100644 --- a/code-samples/serving/grpc-ping-go/Dockerfile +++ b/code-samples/serving/grpc-ping-go/Dockerfile @@ -15,7 +15,7 @@ # Use the official Golang image to create a build artifact. # This is based on Debian and sets the GOPATH to /go. # https://hub.docker.com/_/golang -FROM golang AS builder +FROM golang:1.27 AS builder ARG TARGETOS ARG TARGETARCH diff --git a/code-samples/serving/grpc-ping-go/README.md b/code-samples/serving/grpc-ping-go/README.md index 70ed8d5771..3f6f0f4644 100644 --- a/code-samples/serving/grpc-ping-go/README.md +++ b/code-samples/serving/grpc-ping-go/README.md @@ -3,7 +3,9 @@ A [gRPC](https://grpc.io) server written in Go. This sample can be used to try out gRPC, HTTP/2, and custom port configuration -in a knative service. +in a Knative Service. The server listens on `PORT` (default `8080`) and uses +plaintext HTTP/2 (h2c). Knative sets `PORT` from the configured `containerPort`; +keep the port name `h2c` when choosing a custom port. The container image is built with two binaries: the server and the client. This is done for ease of testing and is not a recommended practice @@ -36,26 +38,9 @@ for production containers. docker buildx build --platform linux/arm64,linux/amd64 -t "{username}/grpc-ping-go" --push . ``` -3. Update the `service.yaml` file in the project to reference the published image from step 1. - - Replace `{username}` in `service.yaml` with your Docker Hub user name: - - - ```yaml - apiVersion: serving.knative.dev/v1 - kind: Service - metadata: - name: grpc-ping - namespace: default - spec: - template: - spec: - containers: - - image: docker.io/{username}/grpc-ping-go - ports: - - name: h2c - containerPort: 8080 - ``` +3. Update the checked-in [service.yaml](service.yaml) to reference the published + image from step 2 by replacing `{username}` with your Docker Hub user name. + Keep its `h2c` port name so Knative forwards plaintext HTTP/2 to the server. 4. Use `kubectl` to deploy the service. @@ -96,7 +81,12 @@ The Dockerfile builds the client binary. To run the client you will use the same container image deployed for the server with an override to the entrypoint command to use the client binary instead of the server binary. -Replace `{username}` with your Docker Hub user name and run the command: +Get the service URL with `kubectl get ksvc grpc-ping -o jsonpath='{.status.url}'`. +Replace the example hostname below with that URL's hostname and `{username}` +with your Docker Hub user name. For an HTTP endpoint, use port `80` and +`-insecure` (plaintext HTTP/2, not disabled certificate verification). For an +HTTPS endpoint, use port `443` and omit `-insecure` to enable TLS with certificate +verification. ```bash docker run --rm {username}/grpc-ping-go \ @@ -106,4 +96,25 @@ docker run --rm {username}/grpc-ping-go \ ``` The arguments after the container tag `{username}/grpc-ping-go` are used -instead of the entrypoint command defined in the Dockerfile `CMD` statement. +instead of the default command defined in the Dockerfile `CMD` statement. + +A successful run logs `Ping got hello - pong`, followed by 20 `Got pong ...` +responses from the bidirectional streaming RPC. + +### Local build + +No cluster or registry push is needed for a local smoke test: + +```bash +docker build -t grpc-ping-go:local . +docker run -d --name grpc-ping-local grpc-ping-go:local +docker run --rm --network container:grpc-ping-local grpc-ping-go:local /client -insecure +docker rm -f grpc-ping-local +``` + +To test a custom port locally, add `-e PORT=8081` to the server's `docker run` +command and `-server_addr=127.0.0.1:8081` to the client command. +For a Knative deployment, set `containerPort: 8081` in `service.yaml` instead; +Knative supplies `PORT` automatically. + +The source files use the `grpcping` build tag, which the Dockerfile supplies. diff --git a/code-samples/serving/grpc-ping-go/client/client.go b/code-samples/serving/grpc-ping-go/client/client.go index b97f913f86..7e5c51d298 100644 --- a/code-samples/serving/grpc-ping-go/client/client.go +++ b/code-samples/serving/grpc-ping-go/client/client.go @@ -20,7 +20,7 @@ import ( var ( serverAddr = flag.String("server_addr", "127.0.0.1:8080", "The server address in the format of host:port") serverHostOverride = flag.String("server_host_override", "", "") - insecure = flag.Bool("insecure", false, "Set to true to skip SSL validation") + insecure = flag.Bool("insecure", false, "Set to true to use plaintext HTTP/2 instead of TLS") skipVerify = flag.Bool("skip_verify", false, "Set to true to skip server hostname verification in SSL validation") ) diff --git a/code-samples/serving/grpc-ping-go/go.mod b/code-samples/serving/grpc-ping-go/go.mod index 4eb3aa3b4e..92fdcd93ee 100644 --- a/code-samples/serving/grpc-ping-go/go.mod +++ b/code-samples/serving/grpc-ping-go/go.mod @@ -1,6 +1,6 @@ module github.com/knative/docs/code-samples/serving/grpc-ping-go -go 1.25.0 +go 1.27.0 require ( github.com/golang/protobuf v1.5.4 diff --git a/code-samples/serving/grpc-ping-go/grpc-ping.go b/code-samples/serving/grpc-ping-go/grpc-ping.go index c9ff6b754c..0370a61708 100644 --- a/code-samples/serving/grpc-ping-go/grpc-ping.go +++ b/code-samples/serving/grpc-ping-go/grpc-ping.go @@ -8,6 +8,7 @@ import ( "io" "log" "net" + "os" "time" ping "github.com/knative/docs/code-samples/serving/grpc-ping-go/proto" @@ -16,8 +17,6 @@ import ( "google.golang.org/grpc/reflection" ) -var port = 8080 - type pingServer struct { } @@ -53,7 +52,11 @@ func (p *pingServer) PingStream(stream ping.PingService_PingStreamServer) error } func main() { - lis, err := net.Listen("tcp", fmt.Sprintf("0.0.0.0:%d", port)) + port := os.Getenv("PORT") + if port == "" { + port = "8080" + } + lis, err := net.Listen("tcp", net.JoinHostPort("0.0.0.0", port)) if err != nil { log.Fatalf("failed to listen: %v", err) } From adf0d1e1080495be41b06f2b281086d3972824ee Mon Sep 17 00:00:00 2001 From: Dave Protasowski Date: Mon, 26 Oct 2026 09:16:04 -0400 Subject: [PATCH 27/44] Update the C# Hello World sample --- .../hello-world/helloworld-csharp/Dockerfile | 4 +- .../hello-world/helloworld-csharp/README.md | 122 ++++-------------- .../hello-world/helloworld-csharp/ci.sh | 40 ++++++ .../helloworld-csharp.csproj | 18 +-- 4 files changed, 75 insertions(+), 109 deletions(-) create mode 100755 code-samples/serving/hello-world/helloworld-csharp/ci.sh diff --git a/code-samples/serving/hello-world/helloworld-csharp/Dockerfile b/code-samples/serving/hello-world/helloworld-csharp/Dockerfile index 1022659683..045324697a 100644 --- a/code-samples/serving/hello-world/helloworld-csharp/Dockerfile +++ b/code-samples/serving/hello-world/helloworld-csharp/Dockerfile @@ -1,5 +1,5 @@ # Use Microsoft's official build .NET image. -FROM mcr.microsoft.com/dotnet/sdk:6.0 AS build-env +FROM mcr.microsoft.com/dotnet/sdk:10.0 AS build-env WORKDIR /app # Copy csproj and restore as distinct layers @@ -11,7 +11,7 @@ COPY . ./ RUN dotnet publish -c Release -o out # Build runtime image -FROM mcr.microsoft.com/dotnet/aspnet:6.0 +FROM mcr.microsoft.com/dotnet/aspnet:10.0 WORKDIR /app COPY --from=build-env /app/out . diff --git a/code-samples/serving/hello-world/helloworld-csharp/README.md b/code-samples/serving/hello-world/helloworld-csharp/README.md index c8e8e3c5b2..e0e9d8bd2b 100644 --- a/code-samples/serving/hello-world/helloworld-csharp/README.md +++ b/code-samples/serving/hello-world/helloworld-csharp/README.md @@ -1,12 +1,11 @@ -# Hello world - .NET Core +# Hello world - .NET -A simple web app written in C# using .NET 6.0 that you can use for testing. +A simple web app written in C# using .NET 10 LTS that you can use for testing. It reads in an env variable `TARGET` and prints "Hello \${TARGET}!". If TARGET is not specified, it will use "World" as the TARGET. -Do the following steps to create the sample code and then deploy the app to your -cluster. You can also download a working copy of the sample, by running the -following commands: +Clone the sample, then build and deploy the app to your +cluster: ```bash git clone https://github.com/knative/docs.git knative-docs @@ -19,102 +18,29 @@ cd knative-docs/code-samples/serving/hello-world/helloworld-csharp [Install Knative Serving](https://knative.dev/docs/install/serving/install-serving-with-yaml). - [Docker](https://www.docker.com) installed and running on your local machine, and a Docker Hub account configured (we'll use it for a container registry). -- You have installed [.NET Core SDK 6.0](https://www.microsoft.com/net/). - -## Recreating the sample code - -1. First, make sure you have - [.NET SDK 6.0](https://www.microsoft.com/net/) installed: - - ```bash - dotnet --version - 6.0.101 - ``` - -1. From the console, create a new empty web project using the dotnet command: - - ```bash - dotnet new web -o helloworld-csharp - ``` - -1. Update the `Program.cs` to read the port and define the serving url: - - ```csharp - var port = Environment.GetEnvironmentVariable("PORT") ?? "8080"; - var url = $"http://0.0.0.0:{port}"; - - app.Run(url); - ``` - -1. Update the `Program.cs` to read and return the `TARGET` environment variable: - - ```csharp - var target = Environment.GetEnvironmentVariable("TARGET") ?? "World"; - - app.MapGet("/", () => $"Hello {target}!"); - ``` - -1. In your project directory, create a file named `Dockerfile` and copy the following code - block into it. For detailed instructions on dockerizing an ASP.NET Core - app, see - [Docker images for ASP.NET Core](https://docs.microsoft.com/en-us/aspnet/core/host-and-deploy/docker/building-net-docker-images). - - ```docker - # Use Microsoft's official build .NET image. - FROM mcr.microsoft.com/dotnet/sdk:6.0 AS build-env - WORKDIR /app - - # Copy csproj and restore as distinct layers - COPY *.csproj ./ - RUN dotnet restore - - # Copy everything else and build - COPY . ./ - RUN dotnet publish -c Release -o out - - # Build runtime image - FROM mcr.microsoft.com/dotnet/aspnet:6.0 - WORKDIR /app - COPY --from=build-env /app/out . - - # Run the web service on container startup. - ENTRYPOINT ["dotnet", "helloworld-csharp.dll"] - ``` - -1. Create a `.dockerignore` file to ensure that any files related to a local - build do not affect the container that you build for deployment. - - ```ignore - Dockerfile - README.md - **/obj/ - **/bin/ - ``` - -1. Create a new file, `service.yaml` and copy the following service definition - into the file. Make sure to replace `{username}` with your Docker Hub - username. - - ```yaml - apiVersion: serving.knative.dev/v1 - kind: Service - metadata: - name: helloworld-csharp - namespace: default - spec: - template: - spec: - containers: - - image: docker.io/{username}/helloworld-csharp - env: - - name: TARGET - value: "C# Sample v1" - ``` +- Optional: [.NET SDK 10.0](https://dotnet.microsoft.com/download/dotnet/10.0) for + local development. The supplied Dockerfile includes the SDK; no local .NET + installation is needed to build or run the supplied sample. + +The SDK and runtime images track the stable `10.0` LTS channel so rebuilds pick up +servicing updates. See Microsoft's [.NET support policy](https://dotnet.microsoft.com/platform/support/policy/dotnet-core). + +## Understanding the sample files + +- [Program.cs](Program.cs) is the complete minimal HTTP app. It binds to all + interfaces on `PORT` (default `8080`), as required for container access. An + absent `TARGET` defaults to `World`; an explicitly empty `TARGET` stays empty. +- [helloworld-csharp.csproj](helloworld-csharp.csproj) targets `net10.0`. +- [Dockerfile](Dockerfile) restores and publishes with the .NET 10 SDK, then copies + the result into the matching ASP.NET Core runtime image. For background, see + [Docker images for ASP.NET Core](https://learn.microsoft.com/aspnet/core/host-and-deploy/docker/building-net-docker-images). +- [service.yaml](service.yaml) is the Knative Service definition, including + `TARGET="C# Sample v1"`. Replace `{username}` in its image reference with your + Docker Hub username before applying it. ## Building and deploying the sample -Once you have recreated the sample code files (or used the files in the sample -folder) you're ready to build and deploy the sample app. +From the cloned sample directory, build and deploy the supplied files: 1. Use Docker to build the sample code into a container. To build and push with Docker Hub, run these commands replacing `{username}` with your Docker Hub diff --git a/code-samples/serving/hello-world/helloworld-csharp/ci.sh b/code-samples/serving/hello-world/helloworld-csharp/ci.sh new file mode 100755 index 0000000000..2df441c959 --- /dev/null +++ b/code-samples/serving/hello-world/helloworld-csharp/ci.sh @@ -0,0 +1,40 @@ +#!/usr/bin/env bash +# Source from this sample directory; the caller owns cluster setup and ingress forwarding. +IMAGE=dev.local/helloworld-csharp:ci +SAMPLE_URL=http://127.0.0.1:18080 + +main() ( + set -euo pipefail + build + deploy + test +) + +build() ( + set -euo pipefail + docker build -t "$IMAGE" . +) + +deploy() ( + set -euo pipefail + kind load docker-image "$IMAGE" + kubectl patch --local -f service.yaml --type=json -p "$( + jq -cn --arg image "$IMAGE" '[ + {op: "replace", path: "/spec/template/spec/containers/0/image", value: $image}, + {op: "add", path: "/spec/template/spec/containers/0/imagePullPolicy", value: "IfNotPresent"} + ]' + )" -o yaml | kubectl --namespace default apply -f - + kubectl --namespace default wait -f service.yaml --for=condition=Ready --timeout=300s +) + +test() ( + set -euo pipefail + local service_url service_host + service_url=$(kubectl --namespace default get -f service.yaml -o jsonpath='{.status.url}') + service_host=${service_url#*://} + [[ "$service_host" =~ ^[a-zA-Z0-9.-]+$ ]] + local body + body=$(curl -fsS --max-time 30 \ + -H "Host: $service_host" "$SAMPLE_URL/") + [[ $body == 'Hello C# Sample v1!' ]] +) diff --git a/code-samples/serving/hello-world/helloworld-csharp/helloworld-csharp.csproj b/code-samples/serving/hello-world/helloworld-csharp/helloworld-csharp.csproj index e2b5ad1816..a3a34b647c 100644 --- a/code-samples/serving/hello-world/helloworld-csharp/helloworld-csharp.csproj +++ b/code-samples/serving/hello-world/helloworld-csharp/helloworld-csharp.csproj @@ -1,9 +1,9 @@ - - - - net6.0 - enable - enable - - - \ No newline at end of file + + + + net10.0 + enable + enable + + + From ec9c81ef6fcf283b3ecdc06dfe08b00420b24c54 Mon Sep 17 00:00:00 2001 From: Dave Protasowski Date: Mon, 26 Oct 2026 09:16:04 -0400 Subject: [PATCH 28/44] Update the Go Hello World sample and its article link --- .../hello-world/helloworld-go/Dockerfile | 6 +- .../hello-world/helloworld-go/README.md | 116 +++--------------- .../serving/hello-world/helloworld-go/ci.sh | 40 ++++++ .../serving/hello-world/helloworld-go/go.mod | 2 +- ...p-a-local-knative-environment-with-kind.md | 2 +- 5 files changed, 62 insertions(+), 104 deletions(-) create mode 100755 code-samples/serving/hello-world/helloworld-go/ci.sh diff --git a/code-samples/serving/hello-world/helloworld-go/Dockerfile b/code-samples/serving/hello-world/helloworld-go/Dockerfile index 6774672450..7d4eb824f6 100644 --- a/code-samples/serving/hello-world/helloworld-go/Dockerfile +++ b/code-samples/serving/hello-world/helloworld-go/Dockerfile @@ -1,6 +1,6 @@ # Use the official Golang image to create a build artifact. -# This is based on Debian and sets the GOPATH to /go. -FROM golang:latest AS builder +# Use the supported Go 1.27 release track. +FROM golang:1.27 AS builder ARG TARGETOS ARG TARGETARCH @@ -21,7 +21,7 @@ RUN CGO_ENABLED=0 GOOS=${TARGETOS} GOARCH=${TARGETARCH} go build -mod=readonly - # Use the official Alpine image for a lean production container. # https://hub.docker.com/_/alpine # https://docs.docker.com/develop/develop-images/multistage-build/#use-multi-stage-builds -FROM alpine:3 +FROM alpine:3.24 RUN apk add --no-cache ca-certificates # Copy the binary to the production image from the builder stage. diff --git a/code-samples/serving/hello-world/helloworld-go/README.md b/code-samples/serving/hello-world/helloworld-go/README.md index 84d82b5828..a8a1f1f78b 100644 --- a/code-samples/serving/hello-world/helloworld-go/README.md +++ b/code-samples/serving/hello-world/helloworld-go/README.md @@ -1,6 +1,6 @@ # Hello World - Go -This guide describes the steps required to to create the `helloworld-go` sample app +This guide describes the steps required to create the `helloworld-go` sample app and deploy it to your cluster. The sample app reads a `TARGET` environment variable, and prints `Hello ${TARGET}!`. If `TARGET` is not specified, `World` is used as the default value. @@ -16,43 +16,11 @@ You will need: ## Building -Create a basic web server which listens on port 8080, by copying the following code into a new file named `helloworld.go`: +The web server in [`helloworld.go`](helloworld.go) listens on all interfaces on +port 8080 by default. Set `PORT` to use a different port. Each request returns the +greeting with a trailing newline; an unset or empty `TARGET` defaults to `World`. -```go -package main - -import ( - "fmt" - "log" - "net/http" - "os" -) - -func handler(w http.ResponseWriter, r *http.Request) { - log.Print("helloworld: received a request") - target := os.Getenv("TARGET") - if target == "" { - target = "World" - } - fmt.Fprintf(w, "Hello %s!\n", target) -} - -func main() { - log.Print("helloworld: starting server...") - - http.HandleFunc("/", handler) - - port := os.Getenv("PORT") - if port == "" { - port = "8080" - } - - log.Printf("helloworld: listening on port %s", port) - log.Fatal(http.ListenAndServe(fmt.Sprintf(":%s", port), nil)) -} -``` - -You can also download a working copy of the sample, by running the following commands: +Clone the repository to use the checked-in source, build recipe and service manifest: ```bash git clone https://github.com/knative/docs.git knative-docs @@ -60,47 +28,11 @@ cd knative-docs/code-samples/serving/hello-world/helloworld-go ``` -Navigate to your project directory and copy the following code into a new file named `Dockerfile`: - -```dockerfile -# Use the official Golang image to create a build artifact. -# This is based on Debian and sets the GOPATH to /go. -FROM golang:latest AS builder - -ARG TARGETOS -ARG TARGETARCH - -# Create and change to the app directory. -WORKDIR /app +The checked-in [`Dockerfile`](Dockerfile) compiles a static Go binary and copies +it into a small Alpine runtime image with CA certificates. The module manifest +[`go.mod`](go.mod) is already included; do not run `go mod init` in the cloned sample. -# Copy local code to the container image. -COPY . ./ - -# Install dependencies and tidy up the go.mod and go.sum files. -RUN go mod tidy - -# Build the binary. -# -mod=readonly ensures immutable go.mod and go.sum in container builds. -RUN CGO_ENABLED=0 GOOS=${TARGETOS} GOARCH=${TARGETARCH} go build -mod=readonly -v -o server - -# Use the official Alpine image for a lean production container. -# https://hub.docker.com/_/alpine -# https://docs.docker.com/develop/develop-images/multistage-build/#use-multi-stage-builds -FROM alpine:3 -RUN apk add --no-cache ca-certificates - -# Copy the binary to the production image from the builder stage. -COPY --from=builder /app/server /server - -# Run the web service on container startup. -CMD ["/server"] -``` - -Use the Go tool to create a [`go.mod`](https://github.com/golang/go/wiki/Modules#gomod) manifest. - -```bash -go mod init github.com/knative/docs/code-samples/serving/hello-world/helloworld-go -``` +## Testing ## Deploying @@ -109,8 +41,8 @@ go mod init github.com/knative/docs/code-samples/serving/hello-world/helloworld- To build the sample code into a container, and push using Docker Hub, enter the following commands and replace `{username}` with your Docker Hub username: ```bash -# Build and push the container on your local machine. -docker buildx build --platform linux/arm64,linux/amd64 -t "{username}/helloworld-go" --push . +# Refresh base images, build and push the container on your local machine. +docker buildx build --pull --platform linux/arm64,linux/amd64 -t "{username}/helloworld-go" --push . ``` ### Deploying to knative @@ -118,25 +50,11 @@ After the build has completed and the container is pushed to docker hub, you can ### yaml -1. Create a new file, `service.yaml` and copy the following service definition into the file. Make sure to replace `{username}` with your Docker Hub username. - - ```yaml - apiVersion: serving.knative.dev/v1 - kind: Service - metadata: - name: helloworld-go - namespace: default - spec: - template: - spec: - containers: - - image: docker.io/{username}/helloworld-go - env: - - name: TARGET - value: "Go Sample v1" - ``` +1. Open the checked-in [`service.yaml`](service.yaml) and replace `{username}` + with your Docker Hub username. It creates a Knative Service in the `default` + namespace with `TARGET="Go Sample v1"`. -1. Check that the container image value in the `service.yaml` file matches the container you built in the previous step. +1. Check that the image in `service.yaml` matches the image you built and pushed. 1. Apply the configuration using `kubectl`: @@ -156,7 +74,7 @@ After your service is created, Knative will perform the following steps: Example: ```bash NAME URL - helloworld-go http://helloworld-go.default.1.2.3.4.xip.io + helloworld-go http://helloworld-go.default.1.2.3.4.sslip.io ``` @@ -180,7 +98,7 @@ After your service is created, Knative will perform the following steps: 16.066s Waiting for load balancer to be ready 16.237s Ready to serve. - Service 'helloworld-go' created to latest revision 'helloworld-go-jjzgd-1' is available at URL: http://helloworld-go.default.1.2.3.4.xip.io + Service 'helloworld-go' created to latest revision 'helloworld-go-jjzgd-1' is available at URL: http://helloworld-go.default.1.2.3.4.sslip.io ``` 1. You can then access your service through the resulting URL. diff --git a/code-samples/serving/hello-world/helloworld-go/ci.sh b/code-samples/serving/hello-world/helloworld-go/ci.sh new file mode 100755 index 0000000000..5a66f0c889 --- /dev/null +++ b/code-samples/serving/hello-world/helloworld-go/ci.sh @@ -0,0 +1,40 @@ +#!/usr/bin/env bash +# Source from this sample directory; the caller owns cluster setup and ingress forwarding. +IMAGE=dev.local/helloworld-go:ci +SAMPLE_URL=http://127.0.0.1:18080 + +main() ( + set -euo pipefail + build + deploy + test +) + +build() ( + set -euo pipefail + docker build -t "$IMAGE" . +) + +deploy() ( + set -euo pipefail + kind load docker-image "$IMAGE" + kubectl patch --local -f service.yaml --type=json -p "$( + jq -cn --arg image "$IMAGE" '[ + {op: "replace", path: "/spec/template/spec/containers/0/image", value: $image}, + {op: "add", path: "/spec/template/spec/containers/0/imagePullPolicy", value: "IfNotPresent"} + ]' + )" -o yaml | kubectl --namespace default apply -f - + kubectl --namespace default wait -f service.yaml --for=condition=Ready --timeout=300s +) + +test() ( + set -euo pipefail + local service_url service_host + service_url=$(kubectl --namespace default get -f service.yaml -o jsonpath='{.status.url}') + service_host=${service_url#*://} + [[ "$service_host" =~ ^[a-zA-Z0-9.-]+$ ]] + local body + body=$(curl -fsS --max-time 30 \ + -H "Host: $service_host" "$SAMPLE_URL/") + [[ $body == 'Hello Go Sample v1!' ]] +) diff --git a/code-samples/serving/hello-world/helloworld-go/go.mod b/code-samples/serving/hello-world/helloworld-go/go.mod index 84f3ac40b6..1025923f91 100644 --- a/code-samples/serving/hello-world/helloworld-go/go.mod +++ b/code-samples/serving/hello-world/helloworld-go/go.mod @@ -1,3 +1,3 @@ module github.com/knative/docs/code-samples/serving/hello-world/helloworld-go -go 1.14 +go 1.27.0 diff --git a/docs/blog/articles/set-up-a-local-knative-environment-with-kind.md b/docs/blog/articles/set-up-a-local-knative-environment-with-kind.md index 6d8c050b92..60fae7b7f9 100644 --- a/docs/blog/articles/set-up-a-local-knative-environment-with-kind.md +++ b/docs/blog/articles/set-up-a-local-knative-environment-with-kind.md @@ -284,7 +284,7 @@ The ports, and how they’re tied to the host, should be the same as you’ve de ## Step 4: Deploying your first app Now that the cluster, Knative, and the networking components are ready, you can deploy an app. -The straightforward [Go app](https://github.com/knative/docs/tree/main/code-samples/eventing/helloworld/helloworld-go) that already exists, is an excellent example app to deploy. +The straightforward [Go app](https://github.com/knative/docs/tree/main/code-samples/serving/hello-world/helloworld-go) that already exists, is an excellent example app to deploy. The first step is to create a yaml file with the hello world service definition: ```bash From 4b6148db3d278bd0dd347c03335d97adadd96956 Mon Sep 17 00:00:00 2001 From: Dave Protasowski Date: Mon, 26 Oct 2026 09:16:04 -0400 Subject: [PATCH 29/44] Update the Spring Hello World sample --- .../.mvn/wrapper/maven-wrapper.properties | 1 - .../helloworld-java-spring/Dockerfile | 16 +- .../helloworld-java-spring/README.md | 134 +++-------- .../hello-world/helloworld-java-spring/ci.sh | 40 ++++ .../hello-world/helloworld-java-spring/mvnw | 225 ------------------ .../helloworld-java-spring/mvnw.cmd | 143 ----------- .../helloworld-java-spring/pom.xml | 11 +- .../src/main/resources/application.properties | 2 +- .../HelloworldApplicationTests.java | 16 -- 9 files changed, 91 insertions(+), 497 deletions(-) delete mode 100644 code-samples/serving/hello-world/helloworld-java-spring/.mvn/wrapper/maven-wrapper.properties create mode 100755 code-samples/serving/hello-world/helloworld-java-spring/ci.sh delete mode 100755 code-samples/serving/hello-world/helloworld-java-spring/mvnw delete mode 100644 code-samples/serving/hello-world/helloworld-java-spring/mvnw.cmd delete mode 100644 code-samples/serving/hello-world/helloworld-java-spring/src/test/java/com/example/helloworld/HelloworldApplicationTests.java diff --git a/code-samples/serving/hello-world/helloworld-java-spring/.mvn/wrapper/maven-wrapper.properties b/code-samples/serving/hello-world/helloworld-java-spring/.mvn/wrapper/maven-wrapper.properties deleted file mode 100644 index b573bb50d5..0000000000 --- a/code-samples/serving/hello-world/helloworld-java-spring/.mvn/wrapper/maven-wrapper.properties +++ /dev/null @@ -1 +0,0 @@ -distributionUrl=https://repo1.maven.org/maven2/org/apache/maven/apache-maven/3.5.3/apache-maven-3.5.3-bin.zip diff --git a/code-samples/serving/hello-world/helloworld-java-spring/Dockerfile b/code-samples/serving/hello-world/helloworld-java-spring/Dockerfile index bdf29d827a..9e56a3a4f6 100644 --- a/code-samples/serving/hello-world/helloworld-java-spring/Dockerfile +++ b/code-samples/serving/hello-world/helloworld-java-spring/Dockerfile @@ -1,21 +1,21 @@ -# Use the official maven/Java 8 image to create a build artifact: https://hub.docker.com/_/maven -FROM maven:3.5-jdk-8-alpine AS builder +# Use the official Maven/Java 25 LTS image to build: https://hub.docker.com/_/maven +FROM maven:3.9.16-eclipse-temurin-25 AS builder # Copy local code to the container image. WORKDIR /app COPY pom.xml . COPY src ./src -# Build a release artifact. -RUN mvn package -DskipTests +# Build the executable jar. +RUN mvn --batch-mode --no-transfer-progress package -# Use the Official OpenJDK image for a lean production stage of our multi-stage build. -# https://hub.docker.com/_/openjdk +# Use the Eclipse Temurin Java 25 LTS runtime for the production stage. +# https://hub.docker.com/_/eclipse-temurin # https://docs.docker.com/develop/develop-images/multistage-build/#use-multi-stage-builds -FROM openjdk:8-jre-alpine +FROM eclipse-temurin:25-jre-noble # Copy the jar to the production image from the builder stage. COPY --from=builder /app/target/helloworld-*.jar /helloworld.jar # Run the web service on container startup. -CMD ["java", "-Djava.security.egd=file:/dev/./urandom", "-jar", "/helloworld.jar"] +CMD ["java", "-jar", "/helloworld.jar"] diff --git a/code-samples/serving/hello-world/helloworld-java-spring/README.md b/code-samples/serving/hello-world/helloworld-java-spring/README.md index d1f837c0fd..50da52b083 100644 --- a/code-samples/serving/hello-world/helloworld-java-spring/README.md +++ b/code-samples/serving/hello-world/helloworld-java-spring/README.md @@ -2,7 +2,7 @@ This guide describes the steps required to create the `helloworld-java-spring` sample app and deploy it to your cluster. -The sample app reads a `TARGET` environment variable, and prints `Hello ${TARGET}!`. If `TARGET` is not specified, `World` is used as the default value. +The sample app reads a `TARGET` environment variable, and prints `Hello ${TARGET}!`. If `TARGET` is not specified, `World` is used as the default value. An explicitly empty `TARGET` produces `Hello !`. You can also download a working copy of the sample, by running the following commands: @@ -20,92 +20,52 @@ cd knative-docs/code-samples/serving/hello-world/helloworld-java-spring ## Building the sample app -1. From the console, create a new, empty web project by using the `curl` and `unzip` - commands: +Use the working copy downloaded above, including its `pom.xml`. This sample uses +Java 25 LTS, Spring Boot 4.1.2 and Maven 3.9.16. Local builds require a Java 25 JDK +and Maven 3.9.16 installed. The included Dockerfile supplies both tools, so Docker +builds do not require a local Java or Maven installation. - ```bash - curl https://start.spring.io/starter.zip \ - -d dependencies=web \ - -d name=helloworld \ - -d artifactId=helloworld \ - -o helloworld.zip - unzip helloworld.zip - ``` +Version and support information: [Spring Boot requirements](https://docs.spring.io/spring-boot/4.1/system-requirements.html), +[Spring support policy](https://spring.io/support-policy), +[Temurin support](https://adoptium.net/support), and +[Maven release history](https://maven.apache.org/docs/history.html). - If you don't have `curl` installed, you can accomplish the same by visiting the - [Spring Initializr](https://start.spring.io/) page. Specify Artifact as - `helloworld` and add the `Web` dependency. Then click **Generate Project**, - download and unzip the sample archive. - -1. Update the `SpringBootApplication` class in - `src/main/java/com/example/helloworld/HelloworldApplication.java` by adding a - `@RestController` to handle the "/" mapping and also add a `@Value` field to - provide the `TARGET` environment variable: - - ```java - package com.example.helloworld; - - import org.springframework.beans.factory.annotation.Value; - import org.springframework.boot.SpringApplication; - import org.springframework.boot.autoconfigure.SpringBootApplication; - import org.springframework.web.bind.annotation.GetMapping; - import org.springframework.web.bind.annotation.RestController; - - @SpringBootApplication - public class HelloworldApplication { - - @Value("${TARGET:World}") - String target; - - @RestController - class HelloworldController { - @GetMapping("/") - String hello() { - return "Hello " + target + "!"; - } - } - - public static void main(String[] args) { - SpringApplication.run(HelloworldApplication.class, args); - } - } - ``` +The source and configuration are already checked in. Continue with **Run the +application locally**, or build the included Dockerfile directly. -1. Run the application locally: +1. The [application](src/main/java/com/example/helloworld/HelloworldApplication.java) + uses a `@RestController` to handle the "/" mapping and a `@Value` field to + read the `TARGET` environment variable. - ```bash - mvn wrapper:wrapper - ./mvnw package && java -jar target/helloworld-0.0.1-SNAPSHOT.jar - ``` +1. The HTTP port is configured in + [application.properties](src/main/resources/application.properties): - Go to `http://localhost:8080/` to see your `Hello World!` message. + ```properties + server.port = ${PORT:8080} + ``` -1. In your project directory, create a file named `Dockerfile` and copy the following code block into it: + Knative supplies `PORT` at runtime. When running locally without `PORT`, the + application listens on port 8080. - ```docker - # Use the official maven/Java 8 image to create a build artifact: https://hub.docker.com/_/maven - FROM maven:3.5-jdk-8-alpine AS builder +1. **Run the application locally**: - # Copy local code to the container image. - WORKDIR /app - COPY pom.xml . - COPY src ./src + ```bash + mvn package && java -jar target/helloworld-0.0.1-SNAPSHOT.jar + ``` - # Build a release artifact. - RUN mvn package -DskipTests + Go to `http://localhost:8080/` to see your `Hello World!` message. - # Use the Official OpenJDK image for a lean production stage of our multi-stage build. - # https://hub.docker.com/_/openjdk - # https://docs.docker.com/develop/develop-images/multistage-build/#use-multi-stage-builds - FROM openjdk:8-jre-alpine + To try a custom greeting and port, stop the application and run: - # Copy the jar to the production image from the builder stage. - COPY --from=builder /app/target/helloworld-*.jar /helloworld.jar + ```bash + TARGET="Spring Boot Sample v1" PORT=8085 java -jar target/helloworld-0.0.1-SNAPSHOT.jar + ``` - # Run the web service on container startup. - CMD ["java", "-Djava.security.egd=file:/dev/./urandom", "-jar", "/helloworld.jar"] + In another terminal, `curl http://localhost:8085/` returns + `Hello Spring Boot Sample v1!`. - ``` +1. The included [Dockerfile](Dockerfile) builds the executable JAR in + a Maven/Java 25 stage, then runs it in a Temurin Java 25 JRE image. For detailed instructions on dockerizing a Spring Boot app, see [Spring Boot with Docker](https://spring.io/guides/gs/spring-boot-docker/). For additional information on multi-stage docker builds for Java see [Creating Smaller Java Image using Docker Multi-stage Build](http://blog.arungupta.me/smaller-java-image-docker-multi-stage-build/). @@ -133,25 +93,9 @@ Choose one of the following methods to deploy the app: ### yaml -1. Create a new file named `service.yaml` and copy the following service definition - into the file: - - ```yaml - apiVersion: serving.knative.dev/v1 - kind: Service - metadata: - name: helloworld-java-spring - namespace: default - spec: - template: - spec: - containers: - - image: docker.io/{username}/helloworld-java-spring - env: - - name: TARGET - value: "Spring Boot Sample v1" - ``` - Where `{username}` is your Docker Hub username. +1. Edit the included [service.yaml](service.yaml), replacing `{username}` in + the container image with your Docker Hub username. The manifest sets + `TARGET` to `Spring Boot Sample v1`. **Note:** Ensure that the container image value in `service.yaml` matches the container you built in the previous step. @@ -185,7 +129,7 @@ Choose one of the following methods to deploy the app: ```bash NAME URL - helloworld-java-spring http://helloworld-java-spring.default.1.2.3.4.xip.io + helloworld-java-spring http://helloworld-java-spring.default.1.2.3.4.sslip.io ``` - For kn, run: @@ -197,7 +141,7 @@ Choose one of the following methods to deploy the app: Example: ```bash - http://helloworld-java-spring.default.1.2.3.4.xip.io + http://helloworld-java-spring.default.1.2.3.4.sslip.io ``` 1. Make a request to your app and observe the result. Replace diff --git a/code-samples/serving/hello-world/helloworld-java-spring/ci.sh b/code-samples/serving/hello-world/helloworld-java-spring/ci.sh new file mode 100755 index 0000000000..29d1dfd92f --- /dev/null +++ b/code-samples/serving/hello-world/helloworld-java-spring/ci.sh @@ -0,0 +1,40 @@ +#!/usr/bin/env bash +# Source from this sample directory; the caller owns cluster setup and ingress forwarding. +IMAGE=dev.local/helloworld-java-spring:ci +SAMPLE_URL=http://127.0.0.1:18080 + +main() ( + set -euo pipefail + build + deploy + test +) + +build() ( + set -euo pipefail + docker build -t "$IMAGE" . +) + +deploy() ( + set -euo pipefail + kind load docker-image "$IMAGE" + kubectl patch --local -f service.yaml --type=json -p "$( + jq -cn --arg image "$IMAGE" '[ + {op: "replace", path: "/spec/template/spec/containers/0/image", value: $image}, + {op: "add", path: "/spec/template/spec/containers/0/imagePullPolicy", value: "IfNotPresent"} + ]' + )" -o yaml | kubectl --namespace default apply -f - + kubectl --namespace default wait -f service.yaml --for=condition=Ready --timeout=300s +) + +test() ( + set -euo pipefail + local service_url service_host + service_url=$(kubectl --namespace default get -f service.yaml -o jsonpath='{.status.url}') + service_host=${service_url#*://} + [[ "$service_host" =~ ^[a-zA-Z0-9.-]+$ ]] + local body + body=$(curl -fsS --max-time 30 \ + -H "Host: $service_host" "$SAMPLE_URL/") + [[ $body == 'Hello Spring Boot Sample v1!' ]] +) diff --git a/code-samples/serving/hello-world/helloworld-java-spring/mvnw b/code-samples/serving/hello-world/helloworld-java-spring/mvnw deleted file mode 100755 index 5bf251c077..0000000000 --- a/code-samples/serving/hello-world/helloworld-java-spring/mvnw +++ /dev/null @@ -1,225 +0,0 @@ -#!/bin/sh -# ---------------------------------------------------------------------------- -# Licensed to the Apache Software Foundation (ASF) under one -# or more contributor license agreements. See the NOTICE file -# distributed with this work for additional information -# regarding copyright ownership. The ASF licenses this file -# to you under the Apache License, Version 2.0 (the -# "License"); you may not use this file except in compliance -# with the License. You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, -# software distributed under the License is distributed on an -# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY -# KIND, either express or implied. See the License for the -# specific language governing permissions and limitations -# under the License. -# ---------------------------------------------------------------------------- - -# ---------------------------------------------------------------------------- -# Maven2 Start Up Batch script -# -# Required ENV vars: -# ------------------ -# JAVA_HOME - location of a JDK home dir -# -# Optional ENV vars -# ----------------- -# M2_HOME - location of maven2's installed home dir -# MAVEN_OPTS - parameters passed to the Java VM when running Maven -# e.g. to debug Maven itself, use -# set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000 -# MAVEN_SKIP_RC - flag to disable loading of mavenrc files -# ---------------------------------------------------------------------------- - -if [ -z "$MAVEN_SKIP_RC" ] ; then - - if [ -f /etc/mavenrc ] ; then - . /etc/mavenrc - fi - - if [ -f "$HOME/.mavenrc" ] ; then - . "$HOME/.mavenrc" - fi - -fi - -# OS specific support. $var _must_ be set to either true or false. -cygwin=false; -darwin=false; -mingw=false -case "`uname`" in - CYGWIN*) cygwin=true ;; - MINGW*) mingw=true;; - Darwin*) darwin=true - # Use /usr/libexec/java_home if available, otherwise fall back to /Library/Java/Home - # See https://developer.apple.com/library/mac/qa/qa1170/_index.html - if [ -z "$JAVA_HOME" ]; then - if [ -x "/usr/libexec/java_home" ]; then - export JAVA_HOME="`/usr/libexec/java_home`" - else - export JAVA_HOME="/Library/Java/Home" - fi - fi - ;; -esac - -if [ -z "$JAVA_HOME" ] ; then - if [ -r /etc/gentoo-release ] ; then - JAVA_HOME=`java-config --jre-home` - fi -fi - -if [ -z "$M2_HOME" ] ; then - ## resolve links - $0 may be a link to maven's home - PRG="$0" - - # need this for relative symlinks - while [ -h "$PRG" ] ; do - ls=`ls -ld "$PRG"` - link=`expr "$ls" : '.*-> \(.*\)$'` - if expr "$link" : '/.*' > /dev/null; then - PRG="$link" - else - PRG="`dirname "$PRG"`/$link" - fi - done - - saveddir=`pwd` - - M2_HOME=`dirname "$PRG"`/.. - - # make it fully qualified - M2_HOME=`cd "$M2_HOME" && pwd` - - cd "$saveddir" - # echo Using m2 at $M2_HOME -fi - -# For Cygwin, ensure paths are in UNIX format before anything is touched -if $cygwin ; then - [ -n "$M2_HOME" ] && - M2_HOME=`cygpath --unix "$M2_HOME"` - [ -n "$JAVA_HOME" ] && - JAVA_HOME=`cygpath --unix "$JAVA_HOME"` - [ -n "$CLASSPATH" ] && - CLASSPATH=`cygpath --path --unix "$CLASSPATH"` -fi - -# For Migwn, ensure paths are in UNIX format before anything is touched -if $mingw ; then - [ -n "$M2_HOME" ] && - M2_HOME="`(cd "$M2_HOME"; pwd)`" - [ -n "$JAVA_HOME" ] && - JAVA_HOME="`(cd "$JAVA_HOME"; pwd)`" - # TODO classpath? -fi - -if [ -z "$JAVA_HOME" ]; then - javaExecutable="`which javac`" - if [ -n "$javaExecutable" ] && ! [ "`expr \"$javaExecutable\" : '\([^ ]*\)'`" = "no" ]; then - # readlink(1) is not available as standard on Solaris 10. - readLink=`which readlink` - if [ ! `expr "$readLink" : '\([^ ]*\)'` = "no" ]; then - if $darwin ; then - javaHome="`dirname \"$javaExecutable\"`" - javaExecutable="`cd \"$javaHome\" && pwd -P`/javac" - else - javaExecutable="`readlink -f \"$javaExecutable\"`" - fi - javaHome="`dirname \"$javaExecutable\"`" - javaHome=`expr "$javaHome" : '\(.*\)/bin'` - JAVA_HOME="$javaHome" - export JAVA_HOME - fi - fi -fi - -if [ -z "$JAVACMD" ] ; then - if [ -n "$JAVA_HOME" ] ; then - if [ -x "$JAVA_HOME/jre/sh/java" ] ; then - # IBM's JDK on AIX uses strange locations for the executables - JAVACMD="$JAVA_HOME/jre/sh/java" - else - JAVACMD="$JAVA_HOME/bin/java" - fi - else - JAVACMD="`which java`" - fi -fi - -if [ ! -x "$JAVACMD" ] ; then - echo "Error: JAVA_HOME is not defined correctly." >&2 - echo " We cannot execute $JAVACMD" >&2 - exit 1 -fi - -if [ -z "$JAVA_HOME" ] ; then - echo "Warning: JAVA_HOME environment variable is not set." -fi - -CLASSWORLDS_LAUNCHER=org.codehaus.plexus.classworlds.launcher.Launcher - -# traverses directory structure from process work directory to filesystem root -# first directory with .mvn subdirectory is considered project base directory -find_maven_basedir() { - - if [ -z "$1" ] - then - echo "Path not specified to find_maven_basedir" - return 1 - fi - - basedir="$1" - wdir="$1" - while [ "$wdir" != '/' ] ; do - if [ -d "$wdir"/.mvn ] ; then - basedir=$wdir - break - fi - # workaround for JBEAP-8937 (on Solaris 10/Sparc) - if [ -d "${wdir}" ]; then - wdir=`cd "$wdir/.."; pwd` - fi - # end of workaround - done - echo "${basedir}" -} - -# concatenates all lines of a file -concat_lines() { - if [ -f "$1" ]; then - echo "$(tr -s '\n' ' ' < "$1")" - fi -} - -BASE_DIR=`find_maven_basedir "$(pwd)"` -if [ -z "$BASE_DIR" ]; then - exit 1; -fi - -export MAVEN_PROJECTBASEDIR=${MAVEN_BASEDIR:-"$BASE_DIR"} -echo $MAVEN_PROJECTBASEDIR -MAVEN_OPTS="$(concat_lines "$MAVEN_PROJECTBASEDIR/.mvn/jvm.config") $MAVEN_OPTS" - -# For Cygwin, switch paths to Windows format before running java -if $cygwin; then - [ -n "$M2_HOME" ] && - M2_HOME=`cygpath --path --windows "$M2_HOME"` - [ -n "$JAVA_HOME" ] && - JAVA_HOME=`cygpath --path --windows "$JAVA_HOME"` - [ -n "$CLASSPATH" ] && - CLASSPATH=`cygpath --path --windows "$CLASSPATH"` - [ -n "$MAVEN_PROJECTBASEDIR" ] && - MAVEN_PROJECTBASEDIR=`cygpath --path --windows "$MAVEN_PROJECTBASEDIR"` -fi - -WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain - -exec "$JAVACMD" \ - $MAVEN_OPTS \ - -classpath "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.jar" \ - "-Dmaven.home=${M2_HOME}" "-Dmaven.multiModuleProjectDirectory=${MAVEN_PROJECTBASEDIR}" \ - ${WRAPPER_LAUNCHER} $MAVEN_CONFIG "$@" diff --git a/code-samples/serving/hello-world/helloworld-java-spring/mvnw.cmd b/code-samples/serving/hello-world/helloworld-java-spring/mvnw.cmd deleted file mode 100644 index 019bd74d76..0000000000 --- a/code-samples/serving/hello-world/helloworld-java-spring/mvnw.cmd +++ /dev/null @@ -1,143 +0,0 @@ -@REM ---------------------------------------------------------------------------- -@REM Licensed to the Apache Software Foundation (ASF) under one -@REM or more contributor license agreements. See the NOTICE file -@REM distributed with this work for additional information -@REM regarding copyright ownership. The ASF licenses this file -@REM to you under the Apache License, Version 2.0 (the -@REM "License"); you may not use this file except in compliance -@REM with the License. You may obtain a copy of the License at -@REM -@REM http://www.apache.org/licenses/LICENSE-2.0 -@REM -@REM Unless required by applicable law or agreed to in writing, -@REM software distributed under the License is distributed on an -@REM "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY -@REM KIND, either express or implied. See the License for the -@REM specific language governing permissions and limitations -@REM under the License. -@REM ---------------------------------------------------------------------------- - -@REM ---------------------------------------------------------------------------- -@REM Maven2 Start Up Batch script -@REM -@REM Required ENV vars: -@REM JAVA_HOME - location of a JDK home dir -@REM -@REM Optional ENV vars -@REM M2_HOME - location of maven2's installed home dir -@REM MAVEN_BATCH_ECHO - set to 'on' to enable the echoing of the batch commands -@REM MAVEN_BATCH_PAUSE - set to 'on' to wait for a key stroke before ending -@REM MAVEN_OPTS - parameters passed to the Java VM when running Maven -@REM e.g. to debug Maven itself, use -@REM set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000 -@REM MAVEN_SKIP_RC - flag to disable loading of mavenrc files -@REM ---------------------------------------------------------------------------- - -@REM Begin all REM lines with '@' in case MAVEN_BATCH_ECHO is 'on' -@echo off -@REM enable echoing my setting MAVEN_BATCH_ECHO to 'on' -@if "%MAVEN_BATCH_ECHO%" == "on" echo %MAVEN_BATCH_ECHO% - -@REM set %HOME% to equivalent of $HOME -if "%HOME%" == "" (set "HOME=%HOMEDRIVE%%HOMEPATH%") - -@REM Execute a user defined script before this one -if not "%MAVEN_SKIP_RC%" == "" goto skipRcPre -@REM check for pre script, once with legacy .bat ending and once with .cmd ending -if exist "%HOME%\mavenrc_pre.bat" call "%HOME%\mavenrc_pre.bat" -if exist "%HOME%\mavenrc_pre.cmd" call "%HOME%\mavenrc_pre.cmd" -:skipRcPre - -@setlocal - -set ERROR_CODE=0 - -@REM To isolate internal variables from possible post scripts, we use another setlocal -@setlocal - -@REM ==== START VALIDATION ==== -if not "%JAVA_HOME%" == "" goto OkJHome - -echo. -echo Error: JAVA_HOME not found in your environment. >&2 -echo Please set the JAVA_HOME variable in your environment to match the >&2 -echo location of your Java installation. >&2 -echo. -goto error - -:OkJHome -if exist "%JAVA_HOME%\bin\java.exe" goto init - -echo. -echo Error: JAVA_HOME is set to an invalid directory. >&2 -echo JAVA_HOME = "%JAVA_HOME%" >&2 -echo Please set the JAVA_HOME variable in your environment to match the >&2 -echo location of your Java installation. >&2 -echo. -goto error - -@REM ==== END VALIDATION ==== - -:init - -@REM Find the project base dir, i.e. the directory that contains the folder ".mvn". -@REM Fallback to current working directory if not found. - -set MAVEN_PROJECTBASEDIR=%MAVEN_BASEDIR% -IF NOT "%MAVEN_PROJECTBASEDIR%"=="" goto endDetectBaseDir - -set EXEC_DIR=%CD% -set WDIR=%EXEC_DIR% -:findBaseDir -IF EXIST "%WDIR%"\.mvn goto baseDirFound -cd .. -IF "%WDIR%"=="%CD%" goto baseDirNotFound -set WDIR=%CD% -goto findBaseDir - -:baseDirFound -set MAVEN_PROJECTBASEDIR=%WDIR% -cd "%EXEC_DIR%" -goto endDetectBaseDir - -:baseDirNotFound -set MAVEN_PROJECTBASEDIR=%EXEC_DIR% -cd "%EXEC_DIR%" - -:endDetectBaseDir - -IF NOT EXIST "%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config" goto endReadAdditionalConfig - -@setlocal EnableExtensions EnableDelayedExpansion -for /F "usebackq delims=" %%a in ("%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config") do set JVM_CONFIG_MAVEN_PROPS=!JVM_CONFIG_MAVEN_PROPS! %%a -@endlocal & set JVM_CONFIG_MAVEN_PROPS=%JVM_CONFIG_MAVEN_PROPS% - -:endReadAdditionalConfig - -SET MAVEN_JAVA_EXE="%JAVA_HOME%\bin\java.exe" - -set WRAPPER_JAR="%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.jar" -set WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain - -%MAVEN_JAVA_EXE% %JVM_CONFIG_MAVEN_PROPS% %MAVEN_OPTS% %MAVEN_DEBUG_OPTS% -classpath %WRAPPER_JAR% "-Dmaven.multiModuleProjectDirectory=%MAVEN_PROJECTBASEDIR%" %WRAPPER_LAUNCHER% %MAVEN_CONFIG% %* -if ERRORLEVEL 1 goto error -goto end - -:error -set ERROR_CODE=1 - -:end -@endlocal & set ERROR_CODE=%ERROR_CODE% - -if not "%MAVEN_SKIP_RC%" == "" goto skipRcPost -@REM check for post script, once with legacy .bat ending and once with .cmd ending -if exist "%HOME%\mavenrc_post.bat" call "%HOME%\mavenrc_post.bat" -if exist "%HOME%\mavenrc_post.cmd" call "%HOME%\mavenrc_post.cmd" -:skipRcPost - -@REM pause the script if MAVEN_BATCH_PAUSE is set to 'on' -if "%MAVEN_BATCH_PAUSE%" == "on" pause - -if "%MAVEN_TERMINATE_CMD%" == "on" exit %ERROR_CODE% - -exit /B %ERROR_CODE% diff --git a/code-samples/serving/hello-world/helloworld-java-spring/pom.xml b/code-samples/serving/hello-world/helloworld-java-spring/pom.xml index 8f6c442087..6e2884ade1 100644 --- a/code-samples/serving/hello-world/helloworld-java-spring/pom.xml +++ b/code-samples/serving/hello-world/helloworld-java-spring/pom.xml @@ -14,27 +14,22 @@ org.springframework.boot spring-boot-starter-parent - 2.1.3.RELEASE + 4.1.2 UTF-8 UTF-8 - 1.8 + 25 org.springframework.boot - spring-boot-starter-web + spring-boot-starter-webmvc - - org.springframework.boot - spring-boot-starter-test - test - diff --git a/code-samples/serving/hello-world/helloworld-java-spring/src/main/resources/application.properties b/code-samples/serving/hello-world/helloworld-java-spring/src/main/resources/application.properties index 2ad7e0e629..51931c956c 100644 --- a/code-samples/serving/hello-world/helloworld-java-spring/src/main/resources/application.properties +++ b/code-samples/serving/hello-world/helloworld-java-spring/src/main/resources/application.properties @@ -12,4 +12,4 @@ # See the License for the specific language governing permissions and # limitations under the License. -server.port = ${PORT} +server.port = ${PORT:8080} diff --git a/code-samples/serving/hello-world/helloworld-java-spring/src/test/java/com/example/helloworld/HelloworldApplicationTests.java b/code-samples/serving/hello-world/helloworld-java-spring/src/test/java/com/example/helloworld/HelloworldApplicationTests.java deleted file mode 100644 index 74760a9532..0000000000 --- a/code-samples/serving/hello-world/helloworld-java-spring/src/test/java/com/example/helloworld/HelloworldApplicationTests.java +++ /dev/null @@ -1,16 +0,0 @@ -package com.example.helloworld; - -import org.junit.Test; -import org.junit.runner.RunWith; -import org.springframework.boot.test.context.SpringBootTest; -import org.springframework.test.context.junit4.SpringRunner; - -@RunWith(SpringRunner.class) -@SpringBootTest -public class HelloworldApplicationTests { - - @Test - public void contextLoads() { - } - -} From 9c153e02430db7e3afddf5bb01ada9a8993447f6 Mon Sep 17 00:00:00 2001 From: Dave Protasowski Date: Mon, 26 Oct 2026 09:16:04 -0400 Subject: [PATCH 30/44] Update the Kotlin Hello World sample --- .../hello-world/helloworld-kotlin/Dockerfile | 29 ++-- .../hello-world/helloworld-kotlin/README.md | 153 +++++------------- .../helloworld-kotlin/build.gradle | 28 ++-- .../hello-world/helloworld-kotlin/ci.sh | 40 +++++ .../src/main/kotlin/com/example/hello/Main.kt | 36 +++-- 5 files changed, 130 insertions(+), 156 deletions(-) create mode 100755 code-samples/serving/hello-world/helloworld-kotlin/ci.sh diff --git a/code-samples/serving/hello-world/helloworld-kotlin/Dockerfile b/code-samples/serving/hello-world/helloworld-kotlin/Dockerfile index 7164d394c2..d88f338bfb 100644 --- a/code-samples/serving/hello-world/helloworld-kotlin/Dockerfile +++ b/code-samples/serving/hello-world/helloworld-kotlin/Dockerfile @@ -1,21 +1,16 @@ -# Use the official gradle image to create a build artifact. -# https://hub.docker.com/_/gradle -FROM gradle:6.7 AS builder +# Build with a maintained Gradle release and Java 25 LTS. +FROM gradle:9.8.2-jdk25 AS builder +WORKDIR /workspace -# Copy local code to the container image. -COPY build.gradle . +COPY build.gradle ./ COPY src ./src +RUN gradle clean build installDist --no-daemon --max-workers=2 -# Build a release artifact. -RUN gradle clean build --no-daemon +# Keep the supported Eclipse Temurin runtime separate from the build tools. +FROM eclipse-temurin:25-jre +WORKDIR /app +COPY --from=builder /workspace/build/install/helloworld-kotlin/ ./ -# Use the Official OpenJDK image for a lean production stage of our multi-stage build. -# https://hub.docker.com/_/openjdk -# https://docs.docker.com/develop/develop-images/multistage-build/#use-multi-stage-builds -FROM openjdk:8-jre-alpine - -# Copy the jar to the production image from the builder stage. -COPY --from=builder /home/gradle/build/libs/gradle.jar /helloworld.jar - -# Run the web service on container startup. -CMD [ "java", "-jar", "-Djava.security.egd=file:/dev/./urandom", "/helloworld.jar" ] +USER 10001:10001 +EXPOSE 8080 +ENTRYPOINT ["/app/bin/helloworld-kotlin"] diff --git a/code-samples/serving/hello-world/helloworld-kotlin/README.md b/code-samples/serving/hello-world/helloworld-kotlin/README.md index 64f9e69c59..17e9d83375 100644 --- a/code-samples/serving/hello-world/helloworld-kotlin/README.md +++ b/code-samples/serving/hello-world/helloworld-kotlin/README.md @@ -2,11 +2,17 @@ A simple web app written in Kotlin using [Ktor](https://ktor.io/) that you can use for testing. It reads in an env variable `TARGET` and prints "Hello -\${TARGET}". If TARGET is not specified, it will use "World" as the TARGET. +\${TARGET}!". If TARGET is not specified, it will use "World" as the TARGET. +An empty TARGET stays empty. The server listens on `0.0.0.0` and uses `PORT` +(default `8080`). GET `/` returns an HTML greeting followed by a newline; +unknown routes return 404 and unsupported methods return 405. -Do the following steps to create the sample code and then deploy the app to your -cluster. You can also download a working copy of the sample, by running the -following commands: +The sample uses Java 21 LTS (Eclipse Temurin), Gradle 9.8.2, Kotlin 2.4.21, +and Ktor 3.6.0. Docker supplies all build tools; no host Java or Gradle install +is needed. + +Clone the repository and use the checked-in sample files to build and deploy the +app: ```bash git clone https://github.com/knative/docs.git knative-docs @@ -20,123 +26,46 @@ cd knative-docs/code-samples/serving/hello-world/helloworld-kotlin - [Docker](https://www.docker.com) installed and running on your local machine, and a Docker Hub account configured (we'll use it for a container registry). -## Recreating the sample code - -1. Create a new directory and cd into it: - - ```bash - mkdir hello - cd hello - ``` - -2. Create a file named `Main.kt` at `src/main/kotlin/com/example/hello` and copy - the following code block into it: - - ```bash - mkdir -p src/main/kotlin/com/example/hello - ``` - - ```kotlin - package com.example.hello - - import io.ktor.application.* - import io.ktor.http.* - import io.ktor.response.* - import io.ktor.routing.* - import io.ktor.server.engine.* - import io.ktor.server.netty.* - - fun main(args: Array) { - val target = System.getenv("TARGET") ?: "World" - val port = System.getenv("PORT") ?: "8080" - embeddedServer(Netty, port.toInt()) { - routing { - get("/") { - call.respondText("Hello $target!\n", ContentType.Text.Html) - } - } - }.start(wait = true) - } - ``` - -3. Switch back to `hello` directory - -4. Create a new file, `build.gradle` and copy the following setting - - ```groovy - plugins { - id "org.jetbrains.kotlin.jvm" version "1.4.10" - } - apply plugin: 'application' +## Sample files - mainClassName = 'com.example.hello.MainKt' +The sample files are: - jar { - manifest { - attributes 'Main-Class': mainClassName - } - from { configurations.compile.collect { it.isDirectory() ? it : zipTree(it) } } - } +- [Main.kt](src/main/kotlin/com/example/hello/Main.kt) starts the externally + accessible Ktor server and defines the greeting route. +- [build.gradle](build.gradle) declares the Java toolchain, Kotlin/Ktor + dependencies, application entrypoint, and lint checks. +- [Dockerfile](Dockerfile) builds the application, then runs the + production distribution on a Java 21 JRE as a non-root user. +- [service.yaml](service.yaml) deploys the Knative Service with + `TARGET="Kotlin Sample v1"`. Before deploying, replace `{username}` in its image + reference with your Docker Hub username. - repositories { - mavenCentral() - } - - dependencies { - compile "io.ktor:ktor-server-netty:1.3.1" - } - ``` - -5. Create a file named `Dockerfile` and copy the following code block into it. - - ```docker - # Use the official gradle image to create a build artifact. - # https://hub.docker.com/_/gradle - FROM gradle:6.7 AS builder - - # Copy local code to the container image. - COPY build.gradle . - COPY src ./src - - # Build a release artifact. - RUN gradle clean build --no-daemon +## Build and deploy this sample - # Use the Official OpenJDK image for a lean production stage of our multi-stage build. - # https://hub.docker.com/_/openjdk - # https://docs.docker.com/develop/develop-images/multistage-build/#use-multi-stage-builds - FROM openjdk:8-jre-alpine +From the cloned sample directory, build and deploy the app using the checked-in +files. The Docker build runs `ktlintCheck` before +assembling the production distribution. - # Copy the jar to the production image from the builder stage. - COPY --from=builder /home/gradle/build/libs/gradle.jar /helloworld.jar +For a local build and smoke test without publishing to a registry: - # Run the web service on container startup. - CMD [ "java", "-jar", "-Djava.security.egd=file:/dev/./urandom", "/helloworld.jar" ] - ``` +```bash +docker build -t helloworld-kotlin:local . +docker run --rm -p 127.0.0.1:8080:8080 helloworld-kotlin:local +# In another terminal: +curl --fail http://127.0.0.1:8080/ +# Hello World! +``` -6. Create a new file, `service.yaml` and copy the following service definition - into the file. Make sure to replace `{username}` with your Docker Hub - username. - - ```yaml - apiVersion: serving.knative.dev/v1 - kind: Service - metadata: - name: helloworld-kotlin - namespace: default - spec: - template: - spec: - containers: - - image: docker.io/{username}/helloworld-kotlin - env: - - name: TARGET - value: "Kotlin Sample v1" - ``` +To use a custom target and port, run instead: -## Build and deploy this sample +```bash +docker run --rm -e TARGET='Kotlin Sample v1' -e PORT=8081 \ + -p 127.0.0.1:8081:8081 helloworld-kotlin:local +``` -Once you have recreated the sample code files (or used the files in the sample -folder) you're ready to build and deploy the sample app. +When deploying a custom port to Knative, set the container's `ports.containerPort` +(e.g. `8081`) in `service.yaml`; Knative injects `PORT` automatically. Do not use +Knative-reserved ports such as 9090 or 9091. 1. Use Docker to build the sample code into a container. To build and push with Docker Hub, run these commands replacing `{username}` with your Docker Hub diff --git a/code-samples/serving/hello-world/helloworld-kotlin/build.gradle b/code-samples/serving/hello-world/helloworld-kotlin/build.gradle index 3de55aebf3..fc475b5b90 100644 --- a/code-samples/serving/hello-world/helloworld-kotlin/build.gradle +++ b/code-samples/serving/hello-world/helloworld-kotlin/build.gradle @@ -1,21 +1,27 @@ plugins { - id "org.jetbrains.kotlin.jvm" version "1.4.10" + id 'org.jetbrains.kotlin.jvm' version '2.4.21' + id 'application' + id 'org.jlleitschuh.gradle.ktlint' version '14.2.0' } -apply plugin: 'application' -mainClassName = 'com.example.hello.MainKt' +repositories { + mavenCentral() +} -jar { - manifest { - attributes 'Main-Class': mainClassName - } - from { configurations.compile.collect { it.isDirectory() ? it : zipTree(it) } } +kotlin { + jvmToolchain(25) } -repositories { - mavenCentral() +application { + applicationName = 'helloworld-kotlin' + mainClass = 'com.example.hello.MainKt' +} + +ktlint { + version = '1.8.0' } dependencies { - compile "io.ktor:ktor-server-netty:1.3.1" + implementation 'io.ktor:ktor-server-netty:3.6.0' + runtimeOnly 'org.slf4j:slf4j-simple:2.0.20' } diff --git a/code-samples/serving/hello-world/helloworld-kotlin/ci.sh b/code-samples/serving/hello-world/helloworld-kotlin/ci.sh new file mode 100755 index 0000000000..93c3bc83cf --- /dev/null +++ b/code-samples/serving/hello-world/helloworld-kotlin/ci.sh @@ -0,0 +1,40 @@ +#!/usr/bin/env bash +# Source from this sample directory; the caller owns cluster setup and ingress forwarding. +IMAGE=dev.local/helloworld-kotlin:ci +SAMPLE_URL=http://127.0.0.1:18080 + +main() ( + set -euo pipefail + build + deploy + test +) + +build() ( + set -euo pipefail + docker build -t "$IMAGE" . +) + +deploy() ( + set -euo pipefail + kind load docker-image "$IMAGE" + kubectl patch --local -f service.yaml --type=json -p "$( + jq -cn --arg image "$IMAGE" '[ + {op: "replace", path: "/spec/template/spec/containers/0/image", value: $image}, + {op: "add", path: "/spec/template/spec/containers/0/imagePullPolicy", value: "IfNotPresent"} + ]' + )" -o yaml | kubectl --namespace default apply -f - + kubectl --namespace default wait -f service.yaml --for=condition=Ready --timeout=300s +) + +test() ( + set -euo pipefail + local service_url service_host + service_url=$(kubectl --namespace default get -f service.yaml -o jsonpath='{.status.url}') + service_host=${service_url#*://} + [[ "$service_host" =~ ^[a-zA-Z0-9.-]+$ ]] + local body + body=$(curl -fsS --max-time 30 \ + -H "Host: $service_host" "$SAMPLE_URL/") + [[ $body == 'Hello Kotlin Sample v1!' ]] +) diff --git a/code-samples/serving/hello-world/helloworld-kotlin/src/main/kotlin/com/example/hello/Main.kt b/code-samples/serving/hello-world/helloworld-kotlin/src/main/kotlin/com/example/hello/Main.kt index 13fd6c2cbc..735258cc77 100644 --- a/code-samples/serving/hello-world/helloworld-kotlin/src/main/kotlin/com/example/hello/Main.kt +++ b/code-samples/serving/hello-world/helloworld-kotlin/src/main/kotlin/com/example/hello/Main.kt @@ -1,20 +1,24 @@ package com.example.hello -import io.ktor.application.* -import io.ktor.http.* -import io.ktor.response.* -import io.ktor.routing.* -import io.ktor.server.engine.* -import io.ktor.server.netty.* +import io.ktor.http.ContentType +import io.ktor.server.application.Application +import io.ktor.server.engine.embeddedServer +import io.ktor.server.netty.Netty +import io.ktor.server.response.respondText +import io.ktor.server.routing.get +import io.ktor.server.routing.routing -fun main(args: Array) { - val target = System.getenv("TARGET") ?: "World" - val port = System.getenv("PORT") ?: "8080" - embeddedServer(Netty, port.toInt()) { - routing { - get("/") { - call.respondText("Hello $target!\n", ContentType.Text.Html) - } - } +fun main() { + val port = System.getenv("PORT")?.toInt() ?: 8080 + embeddedServer(Netty, port = port, host = "0.0.0.0") { + module() }.start(wait = true) -} \ No newline at end of file +} + +fun Application.module(target: String = System.getenv("TARGET") ?: "World") { + routing { + get("/") { + call.respondText("Hello $target!\n", ContentType.Text.Html) + } + } +} From c852e250fea599e01f68a9729dc78161d384dcdf Mon Sep 17 00:00:00 2001 From: Dave Protasowski Date: Mon, 26 Oct 2026 09:16:04 -0400 Subject: [PATCH 31/44] Update the Node.js Hello World sample --- .../hello-world/helloworld-nodejs/Dockerfile | 10 +- .../hello-world/helloworld-nodejs/README.md | 159 +---- .../hello-world/helloworld-nodejs/ci.sh | 40 ++ .../helloworld-nodejs/package-lock.json | 653 +++++++++--------- .../helloworld-nodejs/package.json | 8 +- 5 files changed, 425 insertions(+), 445 deletions(-) create mode 100755 code-samples/serving/hello-world/helloworld-nodejs/ci.sh diff --git a/code-samples/serving/hello-world/helloworld-nodejs/Dockerfile b/code-samples/serving/hello-world/helloworld-nodejs/Dockerfile index 14fc5a7f4b..ef4458f2cd 100644 --- a/code-samples/serving/hello-world/helloworld-nodejs/Dockerfile +++ b/code-samples/serving/hello-world/helloworld-nodejs/Dockerfile @@ -1,20 +1,20 @@ -# Use the official lightweight Node.js 12 image. +# Use the official lightweight Node.js 24 LTS image. # https://hub.docker.com/_/node -FROM node:12-slim +FROM node:24-bookworm-slim # Create and change to the app directory. WORKDIR /usr/src/app # Copy application dependency manifests to the container image. # A wildcard is used to ensure both package.json AND package-lock.json are copied. -# Copying this separately prevents re-running npm install on every code change. +# Copying this separately prevents re-running npm ci on every code change. COPY package*.json ./ # Install production dependencies. -RUN npm install --only=production +RUN npm ci --omit=dev # Copy local code to the container image. COPY . ./ # Run the web service on container startup. -CMD [ "npm", "start" ] +CMD [ "node", "index.js" ] diff --git a/code-samples/serving/hello-world/helloworld-nodejs/README.md b/code-samples/serving/hello-world/helloworld-nodejs/README.md index c01fa77e4b..de5a6a21b9 100644 --- a/code-samples/serving/hello-world/helloworld-nodejs/README.md +++ b/code-samples/serving/hello-world/helloworld-nodejs/README.md @@ -2,11 +2,14 @@ A simple web app written in Node.js that you can use for testing. It reads in an env variable `TARGET` and prints "Hello \${TARGET}!". If TARGET is not -specified, it will use "World" as the TARGET. +specified or is empty, it will use "World" as the TARGET. The app listens on +`0.0.0.0` and uses `PORT`, defaulting to `8080` when unset or empty. -Do the following steps to create the sample code and then deploy the app to your -cluster. You can also download a working copy of the sample, by running the -following commands: +This sample uses [Node.js 24 LTS](https://github.com/nodejs/Release#release-schedule) +and [Express 5](https://expressjs.com/en/5x/api.html). The Docker image tracks +Node 24 patch updates; `package-lock.json` pins application dependencies. + +Clone the working sample, then build and deploy it using the instructions below: ```bash git clone https://github.com/knative/docs.git knative-docs @@ -19,132 +22,26 @@ cd knative-docs/code-samples/serving/hello-world/helloworld-nodejs [Install Knative Serving](https://knative.dev/docs/install/serving/install-serving-with-yaml). - [Docker](https://www.docker.com) installed and running on your local machine, and a Docker Hub account configured (we'll use it for a container registry). -- [Node.js](https://nodejs.org/en/) installed and configured. - -## Recreating the sample code - -1. Create a new directory and initialize `npm`: - - ```bash - npm init - - package name: (helloworld-nodejs) - version: (1.0.0) - description: - entry point: (index.js) - test command: - git repository: - keywords: - author: - license: (ISC) Apache-2.0 - ``` - -1. Install the `express` package: - - ```bash - npm install express - ``` - -1. Create a new file named `index.js` and paste the following code: - - ```js - const express = require('express'); - const app = express(); - - app.get('/', (req, res) => { - console.log('Hello world received a request.'); - - const target = process.env.TARGET || 'World'; - res.send(`Hello ${target}!\n`); - }); - - const port = process.env.PORT || 8080; - app.listen(port, () => { - console.log('Hello world listening on port', port); - }); - ``` - -1. Modify the `package.json` file to add a start command to the scripts section: - - ```json - { - "name": "knative-serving-helloworld", - "version": "1.0.0", - "description": "Simple hello world sample in Node", - "main": "index.js", - "scripts": { - "start": "node index.js" - }, - "author": "", - "license": "Apache-2.0", - "dependencies": { - "express": "^4.22.1" - } - } - ``` - -1. In your project directory, create a file named `Dockerfile` and copy the following code - block into it. For detailed instructions on dockerizing a Node.js app, - see - [Dockerizing a Node.js web app](https://nodejs.org/en/docs/guides/nodejs-docker-webapp/). - - ```Dockerfile - # Use the official lightweight Node.js 12 image. - # https://hub.docker.com/_/node - FROM node:12-slim - - # Create and change to the app directory. - WORKDIR /usr/src/app - - # Copy application dependency manifests to the container image. - # A wildcard is used to ensure both package.json AND package-lock.json are copied. - # Copying this separately prevents re-running npm install on every code change. - COPY package*.json ./ +Building and testing the checked-in sample requires only Docker, not a host +Node.js installation. - # Install production dependencies. - RUN npm install --only=production +## Understanding the sample - # Copy local code to the container image. - COPY . ./ +Use the checked-in files from the clone rather than recreating them: - # Run the web service on container startup. - CMD [ "npm", "start" ] - ``` - -1. Create a `.dockerignore` file to ensure that any files related to a local - build do not affect the container that you build for deployment. - - ```ignore - Dockerfile - README.md - node_modules - npm-debug.log - ``` - -1. Create a new file, `service.yaml` and copy the following service definition - into the file. Make sure to replace `{username}` with your Docker Hub - username. - - ```yaml - apiVersion: serving.knative.dev/v1 - kind: Service - metadata: - name: helloworld-nodejs - namespace: default - spec: - template: - spec: - containers: - - image: docker.io/{username}/helloworld-nodejs - env: - - name: TARGET - value: "Node.js Sample v1" - ``` +- [index.js](index.js) defines the Express `GET /` route, greeting and environment + defaults. It binds to `0.0.0.0` so requests can reach it outside the container. +- [package.json](package.json) declares Express 5 and the start/check commands; + [package-lock.json](package-lock.json) pins the dependency graph. +- [Dockerfile](Dockerfile) uses the Node 24 LTS image, installs production + dependencies with `npm ci --omit=dev`, and starts Node directly. +- [service.yaml](service.yaml) defines the Knative Service and sets + `TARGET="Node.js Sample v1"`. Replace `{username}` in its image reference with + your Docker Hub username before deployment. ## Building and deploying the sample -Once you have recreated the sample code files (or used the files in the sample -folder) you're ready to build and deploy the sample app. +From the cloned sample directory, build and deploy the sample app: 1. Use Docker to build the sample code into a container. To build and push with Docker Hub, run these commands replacing `{username}` with your Docker Hub @@ -187,6 +84,20 @@ folder) you're ready to build and deploy the sample app. Hello Node.js Sample v1! ``` +## Local build + +From the sample directory, build and run without installing Node.js locally: + +```bash +docker build -t helloworld-nodejs:local . +docker run --rm helloworld-nodejs:local npm run check +docker run --rm -p 127.0.0.1:8080:8080 -e TARGET="Node.js Sample v1" helloworld-nodejs:local +``` + +In another terminal, `curl http://127.0.0.1:8080` returns +`Hello Node.js Sample v1!`. Stop the container with Ctrl-C. To select a different +container port, pass `-e PORT=9090` and publish `127.0.0.1:9090:9090` instead. + ## Removing the sample app deployment To remove the sample app from your cluster, delete the service record: diff --git a/code-samples/serving/hello-world/helloworld-nodejs/ci.sh b/code-samples/serving/hello-world/helloworld-nodejs/ci.sh new file mode 100755 index 0000000000..2d12d9bdbc --- /dev/null +++ b/code-samples/serving/hello-world/helloworld-nodejs/ci.sh @@ -0,0 +1,40 @@ +#!/usr/bin/env bash +# Source from this sample directory; the caller owns cluster setup and ingress forwarding. +IMAGE=dev.local/helloworld-nodejs:ci +SAMPLE_URL=http://127.0.0.1:18080 + +main() ( + set -euo pipefail + build + deploy + test +) + +build() ( + set -euo pipefail + docker build -t "$IMAGE" . +) + +deploy() ( + set -euo pipefail + kind load docker-image "$IMAGE" + kubectl patch --local -f service.yaml --type=json -p "$( + jq -cn --arg image "$IMAGE" '[ + {op: "replace", path: "/spec/template/spec/containers/0/image", value: $image}, + {op: "add", path: "/spec/template/spec/containers/0/imagePullPolicy", value: "IfNotPresent"} + ]' + )" -o yaml | kubectl --namespace default apply -f - + kubectl --namespace default wait -f service.yaml --for=condition=Ready --timeout=300s +) + +test() ( + set -euo pipefail + local service_url service_host + service_url=$(kubectl --namespace default get -f service.yaml -o jsonpath='{.status.url}') + service_host=${service_url#*://} + [[ "$service_host" =~ ^[a-zA-Z0-9.-]+$ ]] + local body + body=$(curl -fsS --max-time 30 \ + -H "Host: $service_host" "$SAMPLE_URL/") + [[ $body == 'Hello Node.js Sample v1!' ]] +) diff --git a/code-samples/serving/hello-world/helloworld-nodejs/package-lock.json b/code-samples/serving/hello-world/helloworld-nodejs/package-lock.json index 4c0dd400ab..ebb6160336 100644 --- a/code-samples/serving/hello-world/helloworld-nodejs/package-lock.json +++ b/code-samples/serving/hello-world/helloworld-nodejs/package-lock.json @@ -9,48 +9,47 @@ "version": "1.0.0", "license": "Apache-2.0", "dependencies": { - "express": "^4.22.1" + "express": "^5.3.0" + }, + "engines": { + "node": ">=24" } }, "node_modules/accepts": { - "version": "1.3.8", - "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", - "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/accepts/-/accepts-2.0.0.tgz", + "integrity": "sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng==", + "license": "MIT", "dependencies": { - "mime-types": "~2.1.34", - "negotiator": "0.6.3" + "mime-types": "^3.0.0", + "negotiator": "^1.0.0" }, "engines": { "node": ">= 0.6" } }, - "node_modules/array-flatten": { - "version": "1.1.1", - "resolved": "http://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", - "integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==" - }, "node_modules/body-parser": { - "version": "1.20.3", - "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.3.tgz", - "integrity": "sha512-7rAxByjUMqQ3/bHJy7D6OGXvx/MMc4IqBn/X0fcM1QUcAItpZrBEYhWGem+tzXH90c+G01ypMcYJBO9Y30203g==", + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-2.4.0.tgz", + "integrity": "sha512-Ifk1KcLrxaGt1occQagQNFNNOqRt1LFuehEnHHzB8Z6cRgIxRU1TBaiIsUiCdA+gy5PL7Y1IX31DNKkRmxtXjQ==", "license": "MIT", "dependencies": { - "bytes": "3.1.2", - "content-type": "~1.0.5", - "debug": "2.6.9", - "depd": "2.0.0", - "destroy": "1.2.0", - "http-errors": "2.0.0", - "iconv-lite": "0.4.24", - "on-finished": "2.4.1", - "qs": "6.13.0", - "raw-body": "2.5.2", - "type-is": "~1.6.18", - "unpipe": "1.0.0" + "bytes": "^3.1.2", + "content-type": "^2.1.0", + "debug": "^4.4.3", + "http-errors": "^2.0.1", + "iconv-lite": "^0.7.3", + "on-finished": "^2.4.1", + "qs": "^6.16.1", + "raw-body": "^3.0.2", + "type-is": "^2.1.0" }, "engines": { - "node": ">= 0.8", - "npm": "1.2.8000 || >= 1.4.16" + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" } }, "node_modules/bytes": { @@ -92,44 +91,64 @@ } }, "node_modules/content-disposition": { - "version": "0.5.4", - "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz", - "integrity": "sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==", - "dependencies": { - "safe-buffer": "5.2.1" - }, + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-2.0.1.tgz", + "integrity": "sha512-e+H0ZXHSWYrENhQzw1LPuP4oF5MzVKmDU6d3hxlvaPEYLLg62MxtQNPRx4SYSuYJSBUgnQIG4HIN2tEtNv7Dog==", + "license": "MIT", "engines": { - "node": ">= 0.6" + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" } }, "node_modules/content-type": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz", - "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==", + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/content-type/-/content-type-2.1.0.tgz", + "integrity": "sha512-mj7UPXE0jaqaOsukNZRUEfEi2AcL7C/vwmwcHV0O97eO1E1pxBZuyjlZrx5seTaNBg1U6+o35wpa35Qfcc+7ag==", "license": "MIT", "engines": { - "node": ">= 0.6" + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" } }, "node_modules/cookie": { - "version": "0.7.1", - "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.1.tgz", - "integrity": "sha512-6DnInpx7SJ2AK3+CTUE/ZM0vWTUboZCegxhC2xiIydHR9jNuTAASBrfEpHhiGOZw/nX51bHt6YQl8jsGo4y/0w==", + "version": "0.7.2", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.2.tgz", + "integrity": "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==", + "license": "MIT", "engines": { "node": ">= 0.6" } }, "node_modules/cookie-signature": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", - "integrity": "sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==" + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.2.2.tgz", + "integrity": "sha512-D76uU73ulSXrD1UXF4KE2TMxVVwhsnCgfAyTg9k8P6KGZjlXKrOLe4dJQKI3Bxi5wjesZoFXJWElNWBjPZMbhg==", + "license": "MIT", + "engines": { + "node": ">=6.6.0" + } }, "node_modules/debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "license": "MIT", "dependencies": { - "ms": "2.0.0" + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } } }, "node_modules/depd": { @@ -141,16 +160,6 @@ "node": ">= 0.8" } }, - "node_modules/destroy": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz", - "integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==", - "license": "MIT", - "engines": { - "node": ">= 0.8", - "npm": "1.2.8000 || >= 1.4.16" - } - }, "node_modules/dunder-proto": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", @@ -168,12 +177,14 @@ "node_modules/ee-first": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", - "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==" + "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==", + "license": "MIT" }, "node_modules/encodeurl": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", - "integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz", + "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==", + "license": "MIT", "engines": { "node": ">= 0.8" } @@ -197,9 +208,9 @@ } }, "node_modules/es-object-atoms": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz", - "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==", + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.2.tgz", + "integrity": "sha512-HWcBoN6NileqtSydK2FqHbS/LoDd2pqrnQHLyJzBj4kOp/ky2MWMN694xOfkK8/SnUsW2DH7EfyVlydKCsm1Zw==", "license": "MIT", "dependencies": { "es-errors": "^1.3.0" @@ -211,7 +222,8 @@ "node_modules/escape-html": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", - "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==" + "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==", + "license": "MIT" }, "node_modules/etag": { "version": "1.8.1", @@ -223,115 +235,85 @@ } }, "node_modules/express": { - "version": "4.22.1", - "resolved": "https://registry.npmjs.org/express/-/express-4.22.1.tgz", - "integrity": "sha512-F2X8g9P1X7uCPZMA3MVf9wcTqlyNp7IhH5qPCI0izhaOIYXaW9L535tGA3qmjRzpH+bZczqq7hVKxTR4NWnu+g==", + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/express/-/express-5.3.0.tgz", + "integrity": "sha512-qCqy1xSoaugxOD4IVHb1ZWdkvCF2W3aE2okKouWj8VwJ+ztskSiXoBnCSJqfKT+cVPJhuJqe9YLbY425+Zh00g==", "license": "MIT", "dependencies": { - "accepts": "~1.3.8", - "array-flatten": "1.1.1", - "body-parser": "~1.20.3", - "content-disposition": "~0.5.4", - "content-type": "~1.0.4", - "cookie": "~0.7.1", - "cookie-signature": "~1.0.6", - "debug": "2.6.9", - "depd": "2.0.0", - "encodeurl": "~2.0.0", - "escape-html": "~1.0.3", - "etag": "~1.8.1", - "finalhandler": "~1.3.1", - "fresh": "~0.5.2", - "http-errors": "~2.0.0", - "merge-descriptors": "1.0.3", - "methods": "~1.1.2", - "on-finished": "~2.4.1", - "parseurl": "~1.3.3", - "path-to-regexp": "~0.1.12", - "proxy-addr": "~2.0.7", - "qs": "~6.14.0", - "range-parser": "~1.2.1", - "safe-buffer": "5.2.1", - "send": "~0.19.0", - "serve-static": "~1.16.2", - "setprototypeof": "1.2.0", - "statuses": "~2.0.1", - "type-is": "~1.6.18", - "utils-merge": "1.0.1", - "vary": "~1.1.2" - }, - "engines": { - "node": ">= 0.10.0" + "accepts": "^2.0.0", + "body-parser": "^2.3.0", + "content-disposition": "^2.0.1", + "content-type": "^2.0.0", + "cookie": "^0.7.1", + "cookie-signature": "^1.2.1", + "debug": "^4.4.0", + "depd": "^2.0.0", + "encodeurl": "^2.0.0", + "escape-html": "^1.0.3", + "etag": "^1.8.1", + "finalhandler": "^2.1.0", + "fresh": "^2.0.0", + "http-errors": "^2.0.0", + "merge-descriptors": "^2.0.0", + "mime-types": "^3.0.0", + "on-finished": "^2.4.1", + "once": "^1.4.0", + "parseurl": "^1.3.3", + "proxy-addr": "^2.0.8", + "qs": "^6.16.0", + "range-parser": "^1.2.1", + "router": "^2.2.0", + "send": "^1.1.0", + "serve-static": "^2.2.0", + "statuses": "^2.0.1", + "type-is": "^2.1.0", + "vary": "^1.1.2" + }, + "engines": { + "node": ">= 18" }, "funding": { "type": "opencollective", "url": "https://opencollective.com/express" } }, - "node_modules/express/node_modules/encodeurl": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz", - "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==", + "node_modules/finalhandler": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-2.1.1.tgz", + "integrity": "sha512-S8KoZgRZN+a5rNwqTxlZZePjT/4cnm0ROV70LedRHZ0p8u9fRID0hJUZQpkKLzro8LfmC8sx23bY6tVNxv8pQA==", "license": "MIT", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/express/node_modules/qs": { - "version": "6.14.1", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.14.1.tgz", - "integrity": "sha512-4EK3+xJl8Ts67nLYNwqw/dsFVnCf+qR7RgXSK9jEEm9unao3njwMDdmsdvoKBKHzxd7tCYz5e5M+SnMjdtXGQQ==", - "license": "BSD-3-Clause", "dependencies": { - "side-channel": "^1.1.0" + "debug": "^4.4.0", + "encodeurl": "^2.0.0", + "escape-html": "^1.0.3", + "on-finished": "^2.4.1", + "parseurl": "^1.3.3", + "statuses": "^2.0.1" }, "engines": { - "node": ">=0.6" + "node": ">= 18.0.0" }, "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/finalhandler": { - "version": "1.3.1", - "resolved": "http://registry.npmjs.org/finalhandler/-/finalhandler-1.3.1.tgz", - "integrity": "sha512-6BN9trH7bp3qvnrRyzsBz+g3lZxTNZTbVO2EV1CS0WIcDbawYVdYvGflME/9QP0h0pYlCDBCTjYa9nZzMDpyxQ==", - "dependencies": { - "debug": "2.6.9", - "encodeurl": "~2.0.0", - "escape-html": "~1.0.3", - "on-finished": "2.4.1", - "parseurl": "~1.3.3", - "statuses": "2.0.1", - "unpipe": "~1.0.0" - }, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/finalhandler/node_modules/encodeurl": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz", - "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==", - "engines": { - "node": ">= 0.8" + "type": "opencollective", + "url": "https://opencollective.com/express" } }, "node_modules/forwarded": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==", + "license": "MIT", "engines": { "node": ">= 0.6" } }, "node_modules/fresh": { - "version": "0.5.2", - "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", - "integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/fresh/-/fresh-2.0.0.tgz", + "integrity": "sha512-Rx/WycZ60HOaqLKAi6cHRKKI7zxWbJ31MhntmtwMoaTeF7XFH9hhBp8vITaMidfljRQ6eYWCKkaTK+ykVJHP2A==", "license": "MIT", "engines": { - "node": ">= 0.6" + "node": ">= 0.8" } }, "node_modules/function-bind": { @@ -405,9 +387,9 @@ } }, "node_modules/hasown": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", - "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.4.tgz", + "integrity": "sha512-T2UbfbBEF32wiepXIsMlTW9+dDYC6wMh/t/vYA4tuOMKqWz/n3vr1NFSxQiyP+zk2mXsoMA/i/7qV6LKut1t1A==", "license": "MIT", "dependencies": { "function-bind": "^1.1.2" @@ -417,31 +399,39 @@ } }, "node_modules/http-errors": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz", - "integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.1.tgz", + "integrity": "sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ==", "license": "MIT", "dependencies": { - "depd": "2.0.0", - "inherits": "2.0.4", - "setprototypeof": "1.2.0", - "statuses": "2.0.1", - "toidentifier": "1.0.1" + "depd": "~2.0.0", + "inherits": "~2.0.4", + "setprototypeof": "~1.2.0", + "statuses": "~2.0.2", + "toidentifier": "~1.0.1" }, "engines": { "node": ">= 0.8" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" } }, "node_modules/iconv-lite": { - "version": "0.4.24", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", - "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", + "version": "0.7.3", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.7.3.tgz", + "integrity": "sha512-IKXpvIzjnC9XTAUbVBcMfGS0EPaIXtW6v+zr+RRp+hqULEpo0owZax6wyRwPOJbWbzjYspQwusTsfVr0ifh4uQ==", "license": "MIT", "dependencies": { - "safer-buffer": ">= 2.1.2 < 3" + "safer-buffer": ">= 2.1.2 < 3.0.0" }, "engines": { "node": ">=0.10.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" } }, "node_modules/inherits": { @@ -454,10 +444,17 @@ "version": "1.9.1", "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==", + "license": "MIT", "engines": { "node": ">= 0.10" } }, + "node_modules/is-promise": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/is-promise/-/is-promise-4.0.0.tgz", + "integrity": "sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ==", + "license": "MIT" + }, "node_modules/math-intrinsics": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", @@ -468,73 +465,75 @@ } }, "node_modules/media-typer": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", - "integrity": "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==", + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-1.1.1.tgz", + "integrity": "sha512-yz3xRaG20c6/BOzvYoDaGtPmGscs7YivItZEEqe6GbwNfHuxu9YNmvnEkMzKldAGY4/80pRcQRZSEnhquk9XuQ==", "license": "MIT", "engines": { - "node": ">= 0.6" + "node": ">= 0.8" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" } }, "node_modules/merge-descriptors": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.3.tgz", - "integrity": "sha512-gaNvAS7TZ897/rVaZ0nMtAyxNyi/pdbjbAwUpFQpN70GqnVfOiXpeUUMKRBmzXaSQ8DdTX4/0ms62r2K+hE6mQ==", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-2.0.0.tgz", + "integrity": "sha512-Snk314V5ayFLhp3fkUREub6WtjBfPdCPY1Ln8/8munuLuiYhsABgBVWsozAG+MWMbVEvcdcpbi9R7ww22l9Q3g==", "license": "MIT", - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/methods": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", - "integrity": "sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==", "engines": { - "node": ">= 0.6" - } - }, - "node_modules/mime": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", - "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==", - "license": "MIT", - "bin": { - "mime": "cli.js" + "node": ">=18" }, - "engines": { - "node": ">=4" + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/mime-db": { - "version": "1.52.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", - "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "version": "1.54.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.54.0.tgz", + "integrity": "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==", + "license": "MIT", "engines": { "node": ">= 0.6" } }, "node_modules/mime-types": { - "version": "2.1.35", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", - "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-3.0.2.tgz", + "integrity": "sha512-Lbgzdk0h4juoQ9fCKXW4by0UJqj+nOOrI9MJ1sSj4nI8aI2eo1qmvQEie4VD1glsS250n15LsWsYtCugiStS5A==", + "license": "MIT", "dependencies": { - "mime-db": "1.52.0" + "mime-db": "^1.54.0" }, "engines": { - "node": ">= 0.6" + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" } }, "node_modules/ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "license": "MIT" }, "node_modules/negotiator": { - "version": "0.6.3", - "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", - "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==", + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-1.1.0.tgz", + "integrity": "sha512-NMPBRMJgiQHjbd8phG3Vebdx4kZ1H121rbl5IkMqeOsahptB9BKo/d7oJ3zTXqTgagn2bWlNSXkh0QUGM31RYg==", + "license": "MIT", + "dependencies": { + "content-type": "^2.1.0" + }, "engines": { - "node": ">= 0.6" + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" } }, "node_modules/object-inspect": { @@ -550,48 +549,74 @@ } }, "node_modules/on-finished": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", - "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.2.tgz", + "integrity": "sha512-ATWUv387i+nLD49EiDoOft5scf32Vsdz3ktrQJG9bi7jRFmaXTssvyvj1EmeKVVgvesbqUtvQQvaS2TpTukAZQ==", + "license": "MIT", "dependencies": { "ee-first": "1.1.1" }, "engines": { "node": ">= 0.8" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "license": "ISC", + "dependencies": { + "wrappy": "1" } }, "node_modules/parseurl": { "version": "1.3.3", "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==", + "license": "MIT", "engines": { "node": ">= 0.8" } }, "node_modules/path-to-regexp": { - "version": "0.1.12", - "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.12.tgz", - "integrity": "sha512-RA1GjUVMnvYFxuqovrEqZoxxW5NUZqbwKtYz/Tt7nXerk0LbLblQmrsgdeOxV5SFHf0UDggjS/bSeOZwt1pmEQ==", - "license": "MIT" + "version": "8.4.2", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-8.4.2.tgz", + "integrity": "sha512-qRcuIdP69NPm4qbACK+aDogI5CBDMi1jKe0ry5rSQJz8JVLsC7jV8XpiJjGRLLol3N+R5ihGYcrPLTno6pAdBA==", + "license": "MIT", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } }, "node_modules/proxy-addr": { - "version": "2.0.7", - "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", - "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==", + "version": "2.0.8", + "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.8.tgz", + "integrity": "sha512-5nnx0yGyVUcY6t9RnWcARWtwT9F1D8O9rt08htPvnd49W1IgZtmLkhu9WfMzQj1cFxjHIO6connUNVW5k7AVyQ==", + "license": "MIT", "dependencies": { "forwarded": "0.2.0", "ipaddr.js": "1.9.1" }, "engines": { "node": ">= 0.10" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" } }, "node_modules/qs": { - "version": "6.13.0", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.13.0.tgz", - "integrity": "sha512-+38qI9SOr8tfZ4QmJNplMUxqjbe7LKvvZgWdExBOmd+egZTtjLB67Gu0HRX3u/XOq7UU2Nx6nsjvS16Z9uwfpg==", + "version": "6.16.1", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.16.1.tgz", + "integrity": "sha512-5Px6J7rfHBAGUhNQ3es4YzcOdBBw0SDcVtThJml4RdnKw4tKs2epA0kPZ+K8X+WiHktT93RHVcQ/3ym5aTCgtg==", + "license": "BSD-3-Clause", "dependencies": { - "side-channel": "^1.0.6" + "es-define-property": "^1.0.1", + "side-channel": "^1.1.1" }, "engines": { "node": ">=0.6" @@ -601,47 +626,48 @@ } }, "node_modules/range-parser": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", - "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==", + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.3.0.tgz", + "integrity": "sha512-hek2mFQpPuI4E1BBKrSto+BU3e3x4xuarsbiwr3+lf7p44juvFMV0XFWQAP3xUyqXA4RrXLIoaSUGbSt056ZMw==", "license": "MIT", "engines": { "node": ">= 0.6" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" } }, "node_modules/raw-body": { - "version": "2.5.2", - "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.2.tgz", - "integrity": "sha512-8zGqypfENjCIqGhgXToC8aB2r7YrBX+AQAfIPs/Mlk+BtPTztOvTS01NRW/3Eh60J+a48lt8qsCzirQ6loCVfA==", + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-3.0.2.tgz", + "integrity": "sha512-K5zQjDllxWkf7Z5xJdV0/B0WTNqx6vxG70zJE4N0kBs4LovmEYWJzQGxC9bS9RAKu3bgM40lrd5zoLJ12MQ5BA==", "license": "MIT", "dependencies": { - "bytes": "3.1.2", - "http-errors": "2.0.0", - "iconv-lite": "0.4.24", - "unpipe": "1.0.0" + "bytes": "~3.1.2", + "http-errors": "~2.0.1", + "iconv-lite": "~0.7.0", + "unpipe": "~1.0.0" }, "engines": { - "node": ">= 0.8" + "node": ">= 0.10" } }, - "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==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ] + "node_modules/router": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/router/-/router-2.2.0.tgz", + "integrity": "sha512-nLTrUKm2UyiL7rlhapu/Zl45FwNgkZGaCpZbIHajDYgwlJCOzLSk+cIPAnsEqV955GjILJnKbdQC1nVPz+gAYQ==", + "license": "MIT", + "dependencies": { + "debug": "^4.4.0", + "depd": "^2.0.0", + "is-promise": "^4.0.0", + "parseurl": "^1.3.3", + "path-to-regexp": "^8.0.0" + }, + "engines": { + "node": ">= 18" + } }, "node_modules/safer-buffer": { "version": "2.1.2", @@ -650,55 +676,48 @@ "license": "MIT" }, "node_modules/send": { - "version": "0.19.0", - "resolved": "https://registry.npmjs.org/send/-/send-0.19.0.tgz", - "integrity": "sha512-dW41u5VfLXu8SJh5bwRmyYUbAoSB3c9uQh6L8h/KtsFREPWpbX1lrljJo186Jc4nmci/sGUZ9a0a0J2zgfq2hw==", + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/send/-/send-1.2.1.tgz", + "integrity": "sha512-1gnZf7DFcoIcajTjTwjwuDjzuz4PPcY2StKPlsGAQ1+YH20IRVrBaXSWmdjowTJ6u8Rc01PoYOGHXfP1mYcZNQ==", "license": "MIT", "dependencies": { - "debug": "2.6.9", - "depd": "2.0.0", - "destroy": "1.2.0", - "encodeurl": "~1.0.2", - "escape-html": "~1.0.3", - "etag": "~1.8.1", - "fresh": "0.5.2", - "http-errors": "2.0.0", - "mime": "1.6.0", - "ms": "2.1.3", - "on-finished": "2.4.1", - "range-parser": "~1.2.1", - "statuses": "2.0.1" + "debug": "^4.4.3", + "encodeurl": "^2.0.0", + "escape-html": "^1.0.3", + "etag": "^1.8.1", + "fresh": "^2.0.0", + "http-errors": "^2.0.1", + "mime-types": "^3.0.2", + "ms": "^2.1.3", + "on-finished": "^2.4.1", + "range-parser": "^1.2.1", + "statuses": "^2.0.2" }, "engines": { - "node": ">= 0.8.0" + "node": ">= 18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" } }, - "node_modules/send/node_modules/ms": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", - "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", - "license": "MIT" - }, "node_modules/serve-static": { - "version": "1.16.2", - "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.16.2.tgz", - "integrity": "sha512-VqpjJZKadQB/PEbEwvFdO43Ax5dFBZ2UECszz8bQ7pi7wt//PWe1P6MN7eCnjsatYtBT6EuiClbjSWP2WrIoTw==", + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-2.2.1.tgz", + "integrity": "sha512-xRXBn0pPqQTVQiC8wyQrKs2MOlX24zQ0POGaj0kultvoOCstBQM5yvOhAVSUwOMjQtTvsPWoNCHfPGwaaQJhTw==", + "license": "MIT", "dependencies": { - "encodeurl": "~2.0.0", - "escape-html": "~1.0.3", - "parseurl": "~1.3.3", - "send": "0.19.0" + "encodeurl": "^2.0.0", + "escape-html": "^1.0.3", + "parseurl": "^1.3.3", + "send": "^1.2.0" }, "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/serve-static/node_modules/encodeurl": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz", - "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==", - "engines": { - "node": ">= 0.8" + "node": ">= 18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" } }, "node_modules/setprototypeof": { @@ -708,14 +727,14 @@ "license": "ISC" }, "node_modules/side-channel": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz", - "integrity": "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==", + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.1.tgz", + "integrity": "sha512-6x6dK6zJdpTzF4sQeNYxwtvBzf6Eg4GtlesS94HOvTudUeyK2WXAaIfmDgsyslYrRBeFIlsi54AYsFGUuhmvrQ==", "license": "MIT", "dependencies": { "es-errors": "^1.3.0", - "object-inspect": "^1.13.3", - "side-channel-list": "^1.0.0", + "object-inspect": "^1.13.4", + "side-channel-list": "^1.0.1", "side-channel-map": "^1.0.1", "side-channel-weakmap": "^1.0.2" }, @@ -727,13 +746,13 @@ } }, "node_modules/side-channel-list": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.0.tgz", - "integrity": "sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==", + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.1.tgz", + "integrity": "sha512-mjn/0bi/oUURjc5Xl7IaWi/OJJJumuoJFQJfDDyO46+hBWsfaVM65TBHq2eoZBhzl9EchxOijpkbRC8SVBQU0w==", "license": "MIT", "dependencies": { "es-errors": "^1.3.0", - "object-inspect": "^1.13.3" + "object-inspect": "^1.13.4" }, "engines": { "node": ">= 0.4" @@ -780,9 +799,10 @@ } }, "node_modules/statuses": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", - "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==", + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.2.tgz", + "integrity": "sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==", + "license": "MIT", "engines": { "node": ">= 0.8" } @@ -797,41 +817,46 @@ } }, "node_modules/type-is": { - "version": "1.6.18", - "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", - "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==", + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/type-is/-/type-is-2.1.0.tgz", + "integrity": "sha512-faYHw0anBbc/kWF3zFTEnxSFOAGUX9GFbOBthvDdLsIlEoWOFOtS0zgCiQYwIskL9iGXZL3kAXD8OoZ4GmMATA==", "license": "MIT", "dependencies": { - "media-typer": "0.3.0", - "mime-types": "~2.1.24" + "content-type": "^2.0.0", + "media-typer": "^1.1.0", + "mime-types": "^3.0.0" }, "engines": { - "node": ">= 0.6" + "node": ">= 18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" } }, "node_modules/unpipe": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==", + "license": "MIT", "engines": { "node": ">= 0.8" } }, - "node_modules/utils-merge": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", - "integrity": "sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==", - "engines": { - "node": ">= 0.4.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==", + "license": "MIT", "engines": { "node": ">= 0.8" } + }, + "node_modules/wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", + "license": "ISC" } } } diff --git a/code-samples/serving/hello-world/helloworld-nodejs/package.json b/code-samples/serving/hello-world/helloworld-nodejs/package.json index 745d7764d9..32259716e3 100644 --- a/code-samples/serving/hello-world/helloworld-nodejs/package.json +++ b/code-samples/serving/hello-world/helloworld-nodejs/package.json @@ -4,11 +4,15 @@ "description": "Simple hello world sample in Node", "main": "index.js", "scripts": { - "start": "node index.js" + "start": "node index.js", + "check": "node --check index.js" + }, + "engines": { + "node": ">=24" }, "author": "", "license": "Apache-2.0", "dependencies": { - "express": "^4.22.1" + "express": "^5.3.0" } } From 776e9516d4c9c4ba7e435ba550d762e6deb75a54 Mon Sep 17 00:00:00 2001 From: Dave Protasowski Date: Mon, 26 Oct 2026 09:16:04 -0400 Subject: [PATCH 32/44] Update the PHP Hello World sample --- .../hello-world/helloworld-php/Dockerfile | 16 ++- .../hello-world/helloworld-php/README.md | 134 +++++++----------- .../serving/hello-world/helloworld-php/ci.sh | 40 ++++++ .../hello-world/helloworld-php/index.php | 1 + 4 files changed, 105 insertions(+), 86 deletions(-) create mode 100755 code-samples/serving/hello-world/helloworld-php/ci.sh diff --git a/code-samples/serving/hello-world/helloworld-php/Dockerfile b/code-samples/serving/hello-world/helloworld-php/Dockerfile index 681c9c943c..6d9519bf9d 100644 --- a/code-samples/serving/hello-world/helloworld-php/Dockerfile +++ b/code-samples/serving/hello-world/helloworld-php/Dockerfile @@ -1,16 +1,18 @@ -# Use the official PHP 7.3 image. +# Use the supported PHP 8.5 release with Apache on Debian Trixie. # https://hub.docker.com/_/php -FROM php:7.3-apache +FROM php:8.5-apache-trixie # Copy local code to the container image. COPY index.php /var/www/html/ +# Default to port 8080 for local runs; Knative supplies PORT when deployed. +ENV PORT=8080 + # Use the PORT environment variable in Apache configuration files. -RUN sed -i 's/80/${PORT}/g' /etc/apache2/sites-available/000-default.conf /etc/apache2/ports.conf +RUN sed -i 's/*:80>/*:${PORT}>/' /etc/apache2/sites-available/000-default.conf \ + && sed -i 's/^Listen 80$/Listen ${PORT}/' /etc/apache2/ports.conf -# Configure PHP for development. -# Switch to the production php.ini for production operations. -# RUN mv "$PHP_INI_DIR/php.ini-production" "$PHP_INI_DIR/php.ini" +# Use PHP's production configuration (do not display errors to clients). # https://hub.docker.com/_/php#configuration -RUN mv "$PHP_INI_DIR/php.ini-development" "$PHP_INI_DIR/php.ini" +RUN mv "$PHP_INI_DIR/php.ini-production" "$PHP_INI_DIR/php.ini" diff --git a/code-samples/serving/hello-world/helloworld-php/README.md b/code-samples/serving/hello-world/helloworld-php/README.md index b6122ba5b7..3baa2d5579 100644 --- a/code-samples/serving/hello-world/helloworld-php/README.md +++ b/code-samples/serving/hello-world/helloworld-php/README.md @@ -2,11 +2,19 @@ A simple web app written in PHP that you can use for testing. It reads in an env variable `TARGET` and prints `Hello ${TARGET}!`. If `TARGET` is not specified, -it will use `World` as the `TARGET`. +it will use `World` as the `TARGET`. PHP also treats an empty `TARGET` or the +string `0` as false, so these values use `World` as well. Responses are UTF-8 +plain text: characters in `TARGET` are preserved, not interpreted as HTML. -Do the following steps to create the sample code and then deploy the app to your -cluster. You can also download a working copy of the sample, by running the -following commands: +The sample uses the official `php:8.5-apache-trixie` image (PHP 8.5 with Apache +on Debian Trixie) and PHP's production configuration. PHP 8.5 is actively +supported through December 2027 and receives security fixes through December +2029; see [PHP supported versions](https://www.php.net/supported-versions.php). +The minor-version image tag receives patch updates; rebuild with `--pull` to +pick them up. + +Clone the repository and use the checked-in sample files to build and deploy +the app to your cluster: ```bash git clone https://github.com/knative/docs.git knative-docs @@ -20,87 +28,36 @@ cd knative-docs/code-samples/serving/hello-world/helloworld-php - [Docker](https://www.docker.com) installed and running on your local machine, and a Docker Hub account configured (we'll use it for a container registry). -## Recreating the sample code - -1. Create a new directory and cd into it: - - ```bash - mkdir app - cd app - ``` - -1. Create a file named `index.php` and copy the following code block into it: - - ```php - - ``` - -1. Create a file named `Dockerfile` and copy the following code block into it. See - [official PHP docker image](https://hub.docker.com/_/php/) for more details. - - ```docker - # Use the official PHP 7.3 image. - # https://hub.docker.com/_/php - FROM php:7.3-apache - - # Copy local code to the container image. - COPY index.php /var/www/html/ - - # Use the PORT environment variable in Apache configuration files. - RUN sed -i 's/80/${PORT}/g' /etc/apache2/sites-available/000-default.conf /etc/apache2/ports.conf - - # Configure PHP for development. - # Switch to the production php.ini for production operations. - # RUN mv "$PHP_INI_DIR/php.ini-production" "$PHP_INI_DIR/php.ini" - # https://hub.docker.com/_/php#configuration - RUN mv "$PHP_INI_DIR/php.ini-development" "$PHP_INI_DIR/php.ini" - ``` - -1. Create a `.dockerignore` file to ensure that any files related to a local - build do not affect the container that you build for deployment. - - ```ignore - README.md - ``` - -1. Create a new file, `service.yaml` and copy the following service definition - into the file. Make sure to replace `{username}` with your Docker Hub - username. - - ```yaml - apiVersion: serving.knative.dev/v1 - kind: Service - metadata: - name: helloworld-php - namespace: default - spec: - template: - spec: - containers: - - image: docker.io/{username}/helloworld-php - env: - - name: TARGET - value: "PHP Sample v1" - ``` +## Sample files + +Use the files from the directory selected above; there is no need to +recreate them by copying code from this guide: + +- [index.php](index.php) reads `TARGET` and returns the UTF-8 plain-text greeting. +- [Dockerfile](Dockerfile) packages the app with the + [official PHP Apache image](https://hub.docker.com/_/php/), uses PHP's production + configuration, and configures Apache to listen on `PORT` (default `8080`). +- [service.yaml](service.yaml) defines the Knative Service and sets + `TARGET="PHP Sample v1"`. Replace `{username}` in its image reference with + your Docker Hub username before deployment. ## Building and deploying the sample -Once you have recreated the sample code files (or used the files in the sample folder) you're ready to build and deploy the sample app. +Run the following commands from the sample directory cloned above. -Choose one of the following methods: +First, build and push the image for either deployment method. Replace +`{username}` with your Docker Hub username: -### yaml - 1. Use Docker to build the sample code into a container. To build and push with Docker Hub, run these commands replacing `{username}` with your Docker Hub username: +```bash +# Build and push the container on your local machine. +docker buildx build --pull --platform linux/arm64,linux/amd64 -t "{username}/helloworld-php" --push . +``` + +Choose one of the following deployment methods: - ```bash - # Build and push the container on your local machine. - docker buildx build --platform linux/arm64,linux/amd64 -t "{username}/helloworld-php" --push . - ``` +### yaml - 1. After the build has completed and the container is pushed to docker hub, you can deploy the app into your cluster. Ensure that the container image value in `service.yaml` matches the container you built in the previous step. Apply the configuration using `kubectl`: +After the build has completed and the container is pushed to Docker Hub, you can deploy the app into your cluster. Ensure that the container image value in `service.yaml` matches the container you built in the previous step. Apply the configuration using `kubectl`: ```bash kubectl apply --filename service.yaml @@ -138,7 +95,7 @@ Now that your service is created, Knative will perform the following steps: ### kubectl ``` - kubectl get ksvc helloworld-php --output=custom columns=NAME:.metadata.name,URL:.status.url + kubectl get ksvc helloworld-php --output=custom-columns=NAME:.metadata.name,URL:.status.url NAME URL helloworld-php http://helloworld-php.default.1.2.3.4.xip.io ``` @@ -159,6 +116,25 @@ curl http://helloworld-php.default.1.2.3.4.sslip.io Hello PHP Sample v1! ``` +## Testing locally + +To test without a cluster or registry, build a local image and run it: + +```bash +docker build --pull -t helloworld-php:local . +docker run --rm -p 127.0.0.1:8080:8080 helloworld-php:local +``` + +In another terminal, `curl http://localhost:8080` should return `Hello World!`. +Stop the container with Ctrl-C. To test a custom target and listening port, run: + +```bash +docker run --rm -p 127.0.0.1:9090:9090 -e PORT=9090 -e TARGET="PHP Sample v1" helloworld-php:local +``` + +Then `curl http://localhost:9090` should return `Hello PHP Sample v1!`. +Port 9090 is for this Docker-only check; Knative reserves it for its queue proxy. + ## Removing the sample app deployment To remove the sample app from your cluster, delete the service record: diff --git a/code-samples/serving/hello-world/helloworld-php/ci.sh b/code-samples/serving/hello-world/helloworld-php/ci.sh new file mode 100755 index 0000000000..07e2916c82 --- /dev/null +++ b/code-samples/serving/hello-world/helloworld-php/ci.sh @@ -0,0 +1,40 @@ +#!/usr/bin/env bash +# Source from this sample directory; the caller owns cluster setup and ingress forwarding. +IMAGE=dev.local/helloworld-php:ci +SAMPLE_URL=http://127.0.0.1:18080 + +main() ( + set -euo pipefail + build + deploy + test +) + +build() ( + set -euo pipefail + docker build -t "$IMAGE" . +) + +deploy() ( + set -euo pipefail + kind load docker-image "$IMAGE" + kubectl patch --local -f service.yaml --type=json -p "$( + jq -cn --arg image "$IMAGE" '[ + {op: "replace", path: "/spec/template/spec/containers/0/image", value: $image}, + {op: "add", path: "/spec/template/spec/containers/0/imagePullPolicy", value: "IfNotPresent"} + ]' + )" -o yaml | kubectl --namespace default apply -f - + kubectl --namespace default wait -f service.yaml --for=condition=Ready --timeout=300s +) + +test() ( + set -euo pipefail + local service_url service_host + service_url=$(kubectl --namespace default get -f service.yaml -o jsonpath='{.status.url}') + service_host=${service_url#*://} + [[ "$service_host" =~ ^[a-zA-Z0-9.-]+$ ]] + local body + body=$(curl -fsS --max-time 30 \ + -H "Host: $service_host" "$SAMPLE_URL/") + [[ $body == 'Hello PHP Sample v1!' ]] +) diff --git a/code-samples/serving/hello-world/helloworld-php/index.php b/code-samples/serving/hello-world/helloworld-php/index.php index 7253c94a59..3f40e08c51 100644 --- a/code-samples/serving/hello-world/helloworld-php/index.php +++ b/code-samples/serving/hello-world/helloworld-php/index.php @@ -1,4 +1,5 @@ From 3f8f33c2ef3b0ae2f001cdfe6e8af6943497d51c Mon Sep 17 00:00:00 2001 From: Dave Protasowski Date: Mon, 26 Oct 2026 09:16:04 -0400 Subject: [PATCH 33/44] Update the Python Serving sample --- .../hello-world/helloworld-python/Dockerfile | 20 +-- .../hello-world/helloworld-python/README.md | 128 ++++++------------ .../hello-world/helloworld-python/app.py | 10 +- .../hello-world/helloworld-python/ci.sh | 40 ++++++ .../helloworld-python/requirements.txt | 8 ++ 5 files changed, 103 insertions(+), 103 deletions(-) create mode 100755 code-samples/serving/hello-world/helloworld-python/ci.sh create mode 100644 code-samples/serving/hello-world/helloworld-python/requirements.txt diff --git a/code-samples/serving/hello-world/helloworld-python/Dockerfile b/code-samples/serving/hello-world/helloworld-python/Dockerfile index a84faddcbb..8ebfce31fb 100644 --- a/code-samples/serving/hello-world/helloworld-python/Dockerfile +++ b/code-samples/serving/hello-world/helloworld-python/Dockerfile @@ -1,20 +1,22 @@ # Use the official lightweight Python image. # https://hub.docker.com/_/python -FROM python:3.7-slim +FROM python:3.14.9-slim-trixie # Allow statements and log messages to immediately appear in the Knative logs -ENV PYTHONUNBUFFERED True +ENV PYTHONUNBUFFERED=1 -# Copy local code to the container image. -ENV APP_HOME /app -WORKDIR $APP_HOME -COPY . ./ +# Knative supplies PORT; default to 8080 for local runs. +ENV PORT=8080 +WORKDIR /app -# Install production dependencies. -RUN pip install Flask gunicorn +# Install pinned production dependencies before copying application code. +COPY requirements.txt ./ +RUN python -m pip install --no-cache-dir -r requirements.txt + +COPY app.py ./ # Run the web service on container startup. Here we use the gunicorn # webserver, with one worker process and 8 threads. # For environments with multiple CPU cores, increase the number of workers # to be equal to the cores available. -CMD exec gunicorn --bind :$PORT --workers 1 --threads 8 --timeout 0 app:app +CMD ["sh", "-c", "exec gunicorn --bind \"0.0.0.0:${PORT:-8080}\" --workers 1 --threads 8 --timeout 0 app:app"] diff --git a/code-samples/serving/hello-world/helloworld-python/README.md b/code-samples/serving/hello-world/helloworld-python/README.md index b15cc3057d..3def560c65 100644 --- a/code-samples/serving/hello-world/helloworld-python/README.md +++ b/code-samples/serving/hello-world/helloworld-python/README.md @@ -5,7 +5,11 @@ app and deploy it to your cluster. The sample app reads a `TARGET` environment variable, and prints `Hello ${TARGET}!`. If `TARGET` is not specified, `World` is used as the default -value. +value. The production container uses Gunicorn and listens on `0.0.0.0:$PORT`; +`PORT` defaults to `8080` locally and is supplied by Knative in the cluster. +An explicitly empty `TARGET` produces `Hello !`. + +This sample uses Python 3.14, Flask and Gunicorn. You can also download a working copy of the sample, by running the following commands: @@ -28,82 +32,43 @@ cd knative-docs/code-samples/serving/hello-world/helloworld-python ## Build -1. Create a new directory and cd into it: - - ```bash - mkdir app - cd app - ``` - -2. Create a file named `app.py` and copy the following code block into it: - - ```python - import os +1. Use the cloned sample directory from the commands above. The checked-in + files provide the application and build: - from flask import Flask + - [app.py](app.py) defines the greeting and the development entrypoint. + - [requirements.txt](requirements.txt) pins the dependencies used by the image build. + - [Dockerfile](Dockerfile) uses the official supported Python image and starts + Gunicorn with one worker and eight threads, listening on `0.0.0.0:$PORT`. - app = Flask(__name__) +2. Build and push the container to Docker Hub or your registry of choice. + Replace `{username}` with your Docker Hub username or registry URL: - @app.route('/') - def hello_world(): - target = os.environ.get('TARGET', 'World') - return 'Hello {}!\n'.format(target) + ```bash + # Build and push the container on your local machine. + docker buildx build --platform linux/arm64,linux/amd64 -t "{username}/helloworld-python" --push . + ``` - if __name__ == "__main__": - app.run(debug=True,host='0.0.0.0',port=int(os.environ.get('PORT', 8080))) +## Local testing - ``` +Build a local single-platform image (no registry push needed): -3. In your project directory, create a file named `Dockerfile` and copy the following code - block into it. See - [official Python docker image](https://hub.docker.com/_/python/) for more - details. - - ```dockerfile - # Use the official lightweight Python image. - # https://hub.docker.com/_/python - FROM python:3.7-slim - - # Allow statements and log messages to immediately appear in the Knative logs - ENV PYTHONUNBUFFERED True - - # Copy local code to the container image. - ENV APP_HOME /app - WORKDIR $APP_HOME - COPY . ./ - - # Install production dependencies. - RUN pip install Flask gunicorn - - # Run the web service on container startup. Here we use the gunicorn - # webserver, with one worker process and 8 threads. - # For environments with multiple CPU cores, increase the number of workers - # to be equal to the cores available. - CMD exec gunicorn --bind :$PORT --workers 1 --threads 8 --timeout 0 app:app - ``` - -4. Create a `.dockerignore` file to ensure that any files related to a local - build do not affect the container that you build for deployment. - - ```ignore - Dockerfile - README.md - *.pyc - *.pyo - *.pyd - __pycache__ - ``` +```bash +docker build -t helloworld-python:local . +docker run --rm -p 127.0.0.1:8080:8080 helloworld-python:local +``` - **NOTE:** Use Docker to build the sample code into a container. To build and - push to Docker Hub or container registry of your choice, run these commands replacing `{username}` with your Docker Hub username or the URL of the container registry. +In another terminal, `curl http://localhost:8080/` should print `Hello World!`. +Stop the container with Ctrl+C. To test a custom port and greeting: -5. Use Docker to build the sample code into a container, then push the container - to the Docker registry: +```bash +docker run --rm -e PORT=9090 -e TARGET="Python Sample v1" \ + -p 127.0.0.1:9090:9090 helloworld-python:local +``` - ```bash - # Build and push the container on your local machine. - docker buildx build --platform linux/arm64,linux/amd64 -t "{username}/helloworld-python" --push . - ``` +`curl http://localhost:9090/` should print `Hello Python Sample v1!`. +Stop this container with Ctrl+C before continuing. The container always uses +Gunicorn; running `python app.py` directly is only for local development and does +not enable Flask's debugger by default. ## Deploying the app @@ -113,28 +78,11 @@ cd knative-docs/code-samples/serving/hello-world/helloworld-python ### yaml - 1. Create a new file, `service.yaml` and copy the following service - definition into the file. Make sure to replace `{username}` with your - Docker Hub username or with the URL provided by your container registry - - ```yaml - apiVersion: serving.knative.dev/v1 - kind: Service - metadata: - name: helloworld-python - namespace: default - spec: - template: - spec: - containers: - - image: docker.io/{username}/helloworld-python - env: - - name: TARGET - value: "Python Sample v1" - ``` - - 1. Ensure that the container image value in `service.yaml` matches the container - you built in the previous step. Apply the configuration using `kubectl`: + 1. Open the checked-in [service.yaml](service.yaml). It defines a Knative + Service in the `default` namespace with `TARGET="Python Sample v1"`. + Replace the placeholder image with the image you built and pushed above. + + 2. Apply that configuration using `kubectl`: ```bash kubectl apply --filename service.yaml diff --git a/code-samples/serving/hello-world/helloworld-python/app.py b/code-samples/serving/hello-world/helloworld-python/app.py index de64bd6bfd..d0fd1273b9 100644 --- a/code-samples/serving/hello-world/helloworld-python/app.py +++ b/code-samples/serving/hello-world/helloworld-python/app.py @@ -4,10 +4,12 @@ app = Flask(__name__) -@app.route('/') + +@app.route("/") def hello_world(): - target = os.environ.get('TARGET', 'World') - return 'Hello {}!\n'.format(target) + target = os.environ.get("TARGET", "World") + return f"Hello {target}!\n" + if __name__ == "__main__": - app.run(debug=True,host='0.0.0.0',port=int(os.environ.get('PORT', 8080))) + app.run(host="0.0.0.0", port=int(os.environ.get("PORT", "8080"))) diff --git a/code-samples/serving/hello-world/helloworld-python/ci.sh b/code-samples/serving/hello-world/helloworld-python/ci.sh new file mode 100755 index 0000000000..ed6ce91974 --- /dev/null +++ b/code-samples/serving/hello-world/helloworld-python/ci.sh @@ -0,0 +1,40 @@ +#!/usr/bin/env bash +# Source from this sample directory; the caller owns cluster setup and ingress forwarding. +IMAGE=dev.local/helloworld-python:ci +SAMPLE_URL=http://127.0.0.1:18080 + +main() ( + set -euo pipefail + build + deploy + test +) + +build() ( + set -euo pipefail + docker build -t "$IMAGE" . +) + +deploy() ( + set -euo pipefail + kind load docker-image "$IMAGE" + kubectl patch --local -f service.yaml --type=json -p "$( + jq -cn --arg image "$IMAGE" '[ + {op: "replace", path: "/spec/template/spec/containers/0/image", value: $image}, + {op: "add", path: "/spec/template/spec/containers/0/imagePullPolicy", value: "IfNotPresent"} + ]' + )" -o yaml | kubectl --namespace default apply -f - + kubectl --namespace default wait -f service.yaml --for=condition=Ready --timeout=300s +) + +test() ( + set -euo pipefail + local service_url service_host + service_url=$(kubectl --namespace default get -f service.yaml -o jsonpath='{.status.url}') + service_host=${service_url#*://} + [[ "$service_host" =~ ^[a-zA-Z0-9.-]+$ ]] + local body + body=$(curl -fsS --max-time 30 \ + -H "Host: $service_host" "$SAMPLE_URL/") + [[ $body == 'Hello Python Sample v1!' ]] +) diff --git a/code-samples/serving/hello-world/helloworld-python/requirements.txt b/code-samples/serving/hello-world/helloworld-python/requirements.txt new file mode 100644 index 0000000000..1343b61ca1 --- /dev/null +++ b/code-samples/serving/hello-world/helloworld-python/requirements.txt @@ -0,0 +1,8 @@ +blinker==1.9.0 +click==8.5.0 +Flask==3.1.3 +gunicorn==26.2.0 +itsdangerous==2.2.0 +Jinja2==3.1.6 +MarkupSafe==3.0.4 +Werkzeug==3.1.9 From 7367e15f5e4493ae70537f0f5a433f0299d8495b Mon Sep 17 00:00:00 2001 From: Dave Protasowski Date: Mon, 26 Oct 2026 09:16:05 -0400 Subject: [PATCH 34/44] Update the Ruby Hello World sample --- .../hello-world/helloworld-ruby/Dockerfile | 7 +- .../hello-world/helloworld-ruby/Gemfile | 6 +- .../hello-world/helloworld-ruby/Gemfile.lock | 25 ++-- .../hello-world/helloworld-ruby/README.md | 129 ++++++------------ .../hello-world/helloworld-ruby/app.rb | 1 + .../serving/hello-world/helloworld-ruby/ci.sh | 40 ++++++ 6 files changed, 108 insertions(+), 100 deletions(-) create mode 100755 code-samples/serving/hello-world/helloworld-ruby/ci.sh diff --git a/code-samples/serving/hello-world/helloworld-ruby/Dockerfile b/code-samples/serving/hello-world/helloworld-ruby/Dockerfile index 4239794e83..8e2f9bed76 100644 --- a/code-samples/serving/hello-world/helloworld-ruby/Dockerfile +++ b/code-samples/serving/hello-world/helloworld-ruby/Dockerfile @@ -1,15 +1,16 @@ # Use the official lightweight Ruby image. # https://hub.docker.com/_/ruby -FROM ruby:2.6-slim +FROM ruby:4.0-slim-trixie # Install production dependencies. WORKDIR /usr/src/app COPY Gemfile Gemfile.lock ./ ENV BUNDLE_FROZEN=true -RUN gem install bundler && bundle install +ENV APP_ENV=production +RUN gem install bundler -v 4.0.23 --no-document && bundle install # Copy local code to the container image. COPY . ./ # Run the web service on container startup. -CMD ["ruby", "./app.rb"] +CMD ["bundle", "exec", "ruby", "./app.rb"] diff --git a/code-samples/serving/hello-world/helloworld-ruby/Gemfile b/code-samples/serving/hello-world/helloworld-ruby/Gemfile index f1ba04a8bc..ef6b4c4e43 100644 --- a/code-samples/serving/hello-world/helloworld-ruby/Gemfile +++ b/code-samples/serving/hello-world/helloworld-ruby/Gemfile @@ -1,4 +1,6 @@ source 'https://rubygems.org' -gem 'sinatra' -gem 'rack', '>= 2.0.6' +gem 'sinatra', '~> 4.2.1' +gem 'rack', '~> 3.2.7' +gem 'rackup', '~> 2.3' +gem 'webrick', '~> 1.9' diff --git a/code-samples/serving/hello-world/helloworld-ruby/Gemfile.lock b/code-samples/serving/hello-world/helloworld-ruby/Gemfile.lock index 894ead23a9..979263fa05 100644 --- a/code-samples/serving/hello-world/helloworld-ruby/Gemfile.lock +++ b/code-samples/serving/hello-world/helloworld-ruby/Gemfile.lock @@ -3,32 +3,35 @@ GEM specs: base64 (0.3.0) logger (1.7.0) - mustermann (3.0.4) - ruby2_keywords (~> 0.0.1) - rack (3.1.21) - rack-protection (4.2.0) + mustermann (3.1.1) + rack (3.2.7) + rack-protection (4.2.1) base64 (>= 0.1.0) logger (>= 1.6.0) rack (>= 3.0.0, < 4) rack-session (2.1.2) base64 (>= 0.1.0) rack (>= 3.0.0) - ruby2_keywords (0.0.5) - sinatra (4.2.0) + rackup (2.3.1) + rack (>= 3) + sinatra (4.2.1) logger (>= 1.6.0) mustermann (~> 3.0) rack (>= 3.0.0, < 4) - rack-protection (= 4.2.0) + rack-protection (= 4.2.1) rack-session (>= 2.0.0, < 3) tilt (~> 2.0) - tilt (2.6.1) + tilt (2.9.0) + webrick (1.9.2) PLATFORMS ruby DEPENDENCIES - rack (>= 2.0.6) - sinatra + rack (~> 3.2.7) + rackup (~> 2.3) + sinatra (~> 4.2.1) + webrick (~> 1.9) BUNDLED WITH - 2.1.4 + 4.0.23 diff --git a/code-samples/serving/hello-world/helloworld-ruby/README.md b/code-samples/serving/hello-world/helloworld-ruby/README.md index 9215ae46a1..3fa546269f 100644 --- a/code-samples/serving/hello-world/helloworld-ruby/README.md +++ b/code-samples/serving/hello-world/helloworld-ruby/README.md @@ -3,7 +3,11 @@ This guide describes the steps required to create the `helloworld-ruby` sample app and deploy it to your cluster. The sample app reads a `TARGET` environment variable, and prints `Hello ${TARGET}!`. -If `TARGET` is not specified, `World` is used as the default value. +If `TARGET` is not specified, `World` is used as the default value. An explicitly +empty `TARGET` produces `Hello !`. + +The Dockerfile uses `ruby:4.0-slim-trixie` and installs the gems pinned in +[Gemfile.lock](Gemfile.lock). You can also download a working copy of the sample, by running the following commands: @@ -21,85 +25,56 @@ cd knative-docs/code-samples/serving/hello-world/helloworld-ruby and a Docker Hub account configured. - (optional) The Knative CLI client [kn](https://github.com/knative/client/releases) that simplifies the deployment. Alternative you can also use [`kubectl`](https://kubernetes.io/docs/tasks/tools/install-kubectl/) and apply resource files directly. -## Recreating the sample code - -1. Create a new directory and cd into it: +## Using the sample code - ```bash - mkdir app - cd app - ``` +After cloning and entering the sample directory above, use the checked-in files: -1. Create a file named `app.rb` and copy the following code block into it: +- [app.rb](app.rb) defines the greeting route, binds to `0.0.0.0`, and reads + `PORT` (default `8080`). +- [Dockerfile](Dockerfile) builds the production image from the + [official Ruby image](https://hub.docker.com/_/ruby/) and starts the app with + the locked dependencies. +- [Gemfile](Gemfile) and [Gemfile.lock](Gemfile.lock) define the server and its + resolved dependencies. Docker installs them; no local Ruby installation is required. +- [service.yaml](service.yaml) defines the Knative Service and configures + `TARGET="Ruby Sample v1"`. - ```ruby - require 'sinatra' - - set :bind, '0.0.0.0' - - get '/' do - target = ENV['TARGET'] || 'World' - "Hello #{target}!\n" - end - ``` +## Building and testing locally -1. Create a file named `Dockerfile` and copy the following code block into it. See - [official Ruby docker image](https://hub.docker.com/_/ruby/) for more - details. +Build the image, replacing `{username}` with your Docker Hub username: - ```docker - # Use the official lightweight Ruby image. - # https://hub.docker.com/_/ruby - FROM ruby:2.6-slim - - # Install production dependencies. - WORKDIR /usr/src/app - COPY Gemfile Gemfile.lock ./ - ENV BUNDLE_FROZEN=true - RUN gem install bundler && bundle install - - # Copy local code to the container image. - COPY . ./ +```bash +docker build --pull -t docker.io/{username}/helloworld-ruby . +``` - # Run the web service on container startup. - CMD ["ruby", "./app.rb"] - ``` +Run it locally and request the default greeting from another terminal: -1. Create a file named `Gemfile` and copy the following text block into it. +```bash +docker run --rm -p 127.0.0.1:8080:8080 docker.io/{username}/helloworld-ruby +# In another terminal: +curl http://localhost:8080 +# Hello World! +``` - ```gem - source 'https://rubygems.org' +Stop the container with Ctrl-C. To test a custom greeting and port, run: - gem 'sinatra' - gem 'rack', '>= 2.0.6' - ``` +```bash +docker run --rm -p 127.0.0.1:8081:8081 -e PORT=8081 -e TARGET="Ruby Sample v1" docker.io/{username}/helloworld-ruby +# In another terminal: +curl http://localhost:8081 +# Hello Ruby Sample v1! +``` -1. Run bundle. If you don't have bundler installed, copy the - [Gemfile.lock](Gemfile.lock) to your working directory. +The app listens on `0.0.0.0` and uses `PORT` (default `8080`). The image sets +`APP_ENV=production` so Sinatra accepts requests for the service's external hostname. - ```bash - bundle install - ``` +## Deploying -1. Create a new file, `service.yaml` and copy the following service definition into the file. Make sure to replace `{username}` with your Docker Hub username. - - ```yaml -apiVersion: serving.knative.dev/v1 -kind: Service -metadata: - name: helloworld-ruby - namespace: default -spec: - template: - spec: - containers: - - image: docker.io/{username}/helloworld-ruby - env: - - name: TARGET - value: "Ruby Sample v1" - ``` +Stop the local container and push the image before deploying: -## Deploying +```bash +docker push docker.io/{username}/helloworld-ruby +``` After the build has completed and the container is pushed to Docker Hub, you can deploy the app into your cluster. @@ -107,23 +82,9 @@ Choose one of the following methods to deploy the app: ### yaml - * Create a new file, `service.yaml` and copy the following service definition into the file. Make sure to replace `{username}` with your Docker Hub username. -```yaml - apiVersion: serving.knative.dev/v1 - kind: Service - metadata: - name: helloworld-ruby - namespace: default - spec: - template: - spec: - containers: - - image: docker.io/{username}/helloworld-ruby - env: - - name: TARGET - value: "Ruby Sample v1" -``` -Ensure that the container image value in `service.yaml` matches the container you built in the previous step. +Edit the checked-in [service.yaml](service.yaml), replacing `{username}` in +its image reference with your Docker Hub username. Ensure that the image matches +the one you built and pushed in the previous steps. Apply the configuration using `kubectl`: ```bash kubectl apply --filename service.yaml diff --git a/code-samples/serving/hello-world/helloworld-ruby/app.rb b/code-samples/serving/hello-world/helloworld-ruby/app.rb index f65ed7295a..6eb8c79758 100644 --- a/code-samples/serving/hello-world/helloworld-ruby/app.rb +++ b/code-samples/serving/hello-world/helloworld-ruby/app.rb @@ -1,6 +1,7 @@ require 'sinatra' set :bind, '0.0.0.0' +set :port, ENV.fetch('PORT', '8080') get '/' do target = ENV['TARGET'] || 'World' diff --git a/code-samples/serving/hello-world/helloworld-ruby/ci.sh b/code-samples/serving/hello-world/helloworld-ruby/ci.sh new file mode 100755 index 0000000000..3a36b6f05a --- /dev/null +++ b/code-samples/serving/hello-world/helloworld-ruby/ci.sh @@ -0,0 +1,40 @@ +#!/usr/bin/env bash +# Source from this sample directory; the caller owns cluster setup and ingress forwarding. +IMAGE=dev.local/helloworld-ruby:ci +SAMPLE_URL=http://127.0.0.1:18080 + +main() ( + set -euo pipefail + build + deploy + test +) + +build() ( + set -euo pipefail + docker build -t "$IMAGE" . +) + +deploy() ( + set -euo pipefail + kind load docker-image "$IMAGE" + kubectl patch --local -f service.yaml --type=json -p "$( + jq -cn --arg image "$IMAGE" '[ + {op: "replace", path: "/spec/template/spec/containers/0/image", value: $image}, + {op: "add", path: "/spec/template/spec/containers/0/imagePullPolicy", value: "IfNotPresent"} + ]' + )" -o yaml | kubectl --namespace default apply -f - + kubectl --namespace default wait -f service.yaml --for=condition=Ready --timeout=300s +) + +test() ( + set -euo pipefail + local service_url service_host + service_url=$(kubectl --namespace default get -f service.yaml -o jsonpath='{.status.url}') + service_host=${service_url#*://} + [[ "$service_host" =~ ^[a-zA-Z0-9.-]+$ ]] + local body + body=$(curl -fsS --max-time 30 \ + -H "Host: $service_host" "$SAMPLE_URL/") + [[ $body == 'Hello Ruby Sample v1!' ]] +) From c3d863360711c4638903534d622130b1424b1516 Mon Sep 17 00:00:00 2001 From: Dave Protasowski Date: Mon, 26 Oct 2026 09:16:05 -0400 Subject: [PATCH 35/44] Update the shell Hello World sample --- .../hello-world/helloworld-shell/Dockerfile | 16 ++-- .../hello-world/helloworld-shell/README.md | 90 +++++++------------ .../hello-world/helloworld-shell/ci.sh | 40 +++++++++ .../hello-world/helloworld-shell/script.sh | 6 +- 4 files changed, 80 insertions(+), 72 deletions(-) create mode 100755 code-samples/serving/hello-world/helloworld-shell/ci.sh diff --git a/code-samples/serving/hello-world/helloworld-shell/Dockerfile b/code-samples/serving/hello-world/helloworld-shell/Dockerfile index 2c6fdc768a..5613ce2079 100644 --- a/code-samples/serving/hello-world/helloworld-shell/Dockerfile +++ b/code-samples/serving/hello-world/helloworld-shell/Dockerfile @@ -1,17 +1,11 @@ -# Busybox image that contains the simple 'httpd' -# https://git.busybox.net/busybox/tree/networking/httpd.c -FROM busybox +# BusyBox includes the httpd applet used by this sample. +FROM busybox:1.38 -# Serve from this directory WORKDIR /var/www -# Prepare httpd command for being started via init -# This indirection is required for proper SIGTERM handling -RUN echo "::sysinit:httpd -vv -p 8080 -u daemon -h /var/www" > /etc/inittab - -# Copy over our CGI script and make it executable COPY --chown=daemon:daemon script.sh cgi-bin/index.cgi RUN chmod 755 cgi-bin/index.cgi -# Startup init which in turn starts httpd -CMD init +# BusyBox init handles SIGTERM and reaps CGI processes; httpd daemonizes. +# Configure the listener at startup so Knative's PORT is honored (default 8080). +CMD ["sh", "-c", "printf '::sysinit:httpd -vv -p %s -u daemon -h /var/www\\n' \"${PORT:-8080}\" > /etc/inittab; exec busybox init"] diff --git a/code-samples/serving/hello-world/helloworld-shell/README.md b/code-samples/serving/hello-world/helloworld-shell/README.md index 6dfeb5a012..c36611439c 100644 --- a/code-samples/serving/hello-world/helloworld-shell/README.md +++ b/code-samples/serving/hello-world/helloworld-shell/README.md @@ -4,7 +4,11 @@ This guide describes the steps required to create the `helloworld-shell` sample cluster. The sample app reads a `TARGET` environment variable, and prints `Hello ${TARGET}!`. -If `TARGET` is not specified, `World` is used as the default value. +If `TARGET` is not specified or is empty, `World` is used as the default value. +The server listens on all interfaces on `PORT`, defaulting to `8080` when unset or empty. + +The image uses BusyBox 1.38 and its `httpd` server; no language runtime or +additional application dependencies are needed. You can also download a working copy of the sample, by running the following commands: @@ -24,47 +28,14 @@ cd knative-docs/code-samples/serving/hello-world/helloworld-shell ## Building -1. Create a new file named `script.sh` and paste the following script. This will run BusyBox' `http` returning a friendly welcome message as `plain/text` plus some extra information: - - ```bash - #!/bin/sh - - # Print out CGI header - # See https://tools.ietf.org/html/draft-robinson-www-interface-00 - # for the full CGI specification - echo -e "Content-Type: text/plain\n" - - # Use environment variable TARGET or "World" if not set - echo "Hello ${TARGET:=World}!" - - # In this script you can perform more dynamic actions, too. - # Like printing the date, checking CGI environment variables, ... - ``` - -1. Create a new file named `Dockerfile` and copy the following code block into it. - - ```docker - # Busybox image that contains the simple 'httpd' - # https://git.busybox.net/busybox/tree/networking/httpd.c - FROM busybox - - # Serve from this directory - WORKDIR /var/www - - # Prepare httpd command for being started via init - # This indirection is required for proper SIGTERM handling - RUN echo "::sysinit:httpd -vv -p 8080 -u daemon -h /var/www" > /etc/inittab +Use the files in the cloned sample directory: - # Copy over our CGI script and make it executable - COPY --chown=daemon:daemon script.sh cgi-bin/index.cgi - RUN chmod 755 cgi-bin/index.cgi +- [`script.sh`](script.sh) is the CGI application served by BusyBox's `httpd`. + It returns the greeting as `text/plain`, using `World` when `TARGET` is unset or empty. +- [`Dockerfile`](Dockerfile) copies the CGI script into the BusyBox image and + configures the listener from `PORT` at startup. - # Startup init which in turn starts httpd - CMD init - ``` - -Once you have recreated the sample code files (or used the files in the sample -folder) you're ready to build and deploy the sample app. +Build from this directory so Docker can find the CGI script: 1. Use Docker to build the sample code into a container. To build and push with Docker Hub, run these commands replacing `{username}` with your Docker Hub @@ -72,9 +43,25 @@ folder) you're ready to build and deploy the sample app. ```bash # Build and push the container on your local machine. - docker buildx build --platform linux/arm64,linux/amd64 -t "{username}/helloworld-shell" --push . + docker buildx build --pull --platform linux/arm64,linux/amd64 -t "{username}/helloworld-shell" --push . ``` +## Testing locally + +Build a single-platform image without pushing, then run it on a custom port: + +```bash +docker build --pull -t helloworld-shell:local . +docker run --rm -d --name helloworld-shell-local -p 127.0.0.1:8081:9090 \ + -e PORT=9090 -e TARGET="Shell Sample v1" helloworld-shell:local +curl --fail http://127.0.0.1:8081/ +# Hello Shell Sample v1! +docker stop helloworld-shell-local +``` + +Omit `TARGET` or pass `-e TARGET=` to use the `World` fallback. +Omit `PORT` to listen on 8080. + ## Deploying After the build has completed and the container is pushed to Docker Hub, you can deploy the app into your cluster. @@ -82,23 +69,10 @@ After the build has completed and the container is pushed to Docker Hub, you can Choose one of the following methods to deploy the app: ### yaml -1. Create a new file, `service.yaml` and copy the following service definition into the file. Make sure to replace `{username}` with your Docker Hub username. -```yaml -apiVersion: serving.knative.dev/v1 -kind: Service -metadata: - name: helloworld-shell - namespace: default -spec: - template: - spec: - containers: - - image: docker.io/{username}/helloworld-shell - env: - - name: TARGET - value: "Shell Sample v1" -``` -Ensure that the container image value in `service.yaml` matches the container you built in the previous step. +1. Open the checked-in [`service.yaml`](service.yaml) and replace `{username}` in its + image reference with your Docker Hub username. The manifest configures + `TARGET="Shell Sample v1"`. Ensure that its image reference matches the image + built and pushed in the previous step. 1. Apply the configuration using `kubectl`: ```bash kubectl apply --filename service.yaml diff --git a/code-samples/serving/hello-world/helloworld-shell/ci.sh b/code-samples/serving/hello-world/helloworld-shell/ci.sh new file mode 100755 index 0000000000..3b82d85a63 --- /dev/null +++ b/code-samples/serving/hello-world/helloworld-shell/ci.sh @@ -0,0 +1,40 @@ +#!/usr/bin/env bash +# Source from this sample directory; the caller owns cluster setup and ingress forwarding. +IMAGE=dev.local/helloworld-shell:ci +SAMPLE_URL=http://127.0.0.1:18080 + +main() ( + set -euo pipefail + build + deploy + test +) + +build() ( + set -euo pipefail + docker build -t "$IMAGE" . +) + +deploy() ( + set -euo pipefail + kind load docker-image "$IMAGE" + kubectl patch --local -f service.yaml --type=json -p "$( + jq -cn --arg image "$IMAGE" '[ + {op: "replace", path: "/spec/template/spec/containers/0/image", value: $image}, + {op: "add", path: "/spec/template/spec/containers/0/imagePullPolicy", value: "IfNotPresent"} + ]' + )" -o yaml | kubectl --namespace default apply -f - + kubectl --namespace default wait -f service.yaml --for=condition=Ready --timeout=300s +) + +test() ( + set -euo pipefail + local service_url service_host + service_url=$(kubectl --namespace default get -f service.yaml -o jsonpath='{.status.url}') + service_host=${service_url#*://} + [[ "$service_host" =~ ^[a-zA-Z0-9.-]+$ ]] + local body + body=$(curl -fsS --max-time 30 \ + -H "Host: $service_host" "$SAMPLE_URL/") + [[ $body == 'Hello Shell Sample v1!' ]] +) diff --git a/code-samples/serving/hello-world/helloworld-shell/script.sh b/code-samples/serving/hello-world/helloworld-shell/script.sh index 21b5de805b..2c8a01f40a 100755 --- a/code-samples/serving/hello-world/helloworld-shell/script.sh +++ b/code-samples/serving/hello-world/helloworld-shell/script.sh @@ -3,10 +3,10 @@ # Print out CGI header # See https://tools.ietf.org/html/draft-robinson-www-interface-00 # for the full CGI specification -echo -e "Content-Type: text/plain\n" +printf 'Content-Type: text/plain\r\n\r\n' -# Use environment variable TARGET or "World" if not set -echo "Hello ${TARGET:=World}!" +# Use environment variable TARGET or "World" if unset or empty +printf 'Hello %s!\n' "${TARGET:-World}" # In this script you can perform more dynamic actions, too. # Like printing the date, checking CGI environment variables, ... From c79ebc37bbc251dee0d496d92494ad3939dfa1de Mon Sep 17 00:00:00 2001 From: Dave Protasowski Date: Mon, 26 Oct 2026 09:16:05 -0400 Subject: [PATCH 36/44] Update the Go routing sample to reuse Hello World --- .../serving/knative-routing-go/Dockerfile | 38 ----------------- .../serving/knative-routing-go/README.md | 41 +++++++++++------- .../serving/knative-routing-go/go.mod | 3 -- .../serving/knative-routing-go/sample.go | 42 ------------------- .../serving/knative-routing-go/sample.yaml | 8 ++-- 5 files changed, 31 insertions(+), 101 deletions(-) delete mode 100644 code-samples/serving/knative-routing-go/Dockerfile delete mode 100644 code-samples/serving/knative-routing-go/go.mod delete mode 100644 code-samples/serving/knative-routing-go/sample.go diff --git a/code-samples/serving/knative-routing-go/Dockerfile b/code-samples/serving/knative-routing-go/Dockerfile deleted file mode 100644 index 14ecf5188c..0000000000 --- a/code-samples/serving/knative-routing-go/Dockerfile +++ /dev/null @@ -1,38 +0,0 @@ -# Copyright 2018 The Knative Authors -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# https://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -FROM golang:latest AS builder - -ARG TARGETOS -ARG TARGETARCH - -# Create and change to the app directory. -WORKDIR /app - -# Copy local code to the container image. -COPY . ./ - -# Install dependencies and tidy up the go.mod and go.sum files. -RUN go mod tidy - -# Build the binary. -# -mod=readonly ensures immutable go.mod and go.sum in container builds. -RUN CGO_ENABLED=0 GOOS=${TARGETOS} GOARCH=${TARGETARCH} go build -v -o sample - -FROM gcr.io/distroless/base - -EXPOSE 8080 -COPY --from=builder /app/sample /sample - -ENTRYPOINT ["/sample"] diff --git a/code-samples/serving/knative-routing-go/README.md b/code-samples/serving/knative-routing-go/README.md index bbf236fa53..053f0055ba 100644 --- a/code-samples/serving/knative-routing-go/README.md +++ b/code-samples/serving/knative-routing-go/README.md @@ -7,16 +7,25 @@ configure routing based on other request data such as headers, or even to map Knative and external resources under the same domain name. In this sample, we set up two web services: `Search` service and `Login` -service, which simply read in an env variable `SERVICE_NAME` and prints -`"${SERVICE_NAME} is called"`. We'll then create a VirtualService with host -`example.com`, and define routing rules in the VirtualService so that +service, which reuse the [Go Hello World application](../hello-world/helloworld-go/helloworld.go) +with different `TARGET` environment variables and print `Hello ${TARGET}!`. +If `TARGET` is unset or empty, the greeting is `Hello World!`. The application +listens on port 8080 by default (`PORT` overrides it). We'll then create a +VirtualService with host `example.com`, and define routing rules so that `example.com/search` maps to the Search service, and `example.com/login` maps to the Login service. +The sample uses the [Go Hello World application](../hello-world/helloworld-go/helloworld.go), +[Dockerfile](../hello-world/helloworld-go/Dockerfile), +[Service definitions](sample.yaml), [public routing rules](routing.yaml), and +[internal routing rules](routing-internal.yaml). Use these checked-in files after +cloning the repository; there is no need to recreate them from documentation. + ## Prerequisites 1. A Kubernetes cluster with [Knative Serving](https://knative.dev/docs/install/serving/install-serving-with-yaml) - installed. + installed with the Istio networking layer. This sample uses Istio Gateway and + VirtualService resources; it does not work with Kourier or Contour alone. 2. Install [Docker](https://docs.docker.com/get-started/#prepare-your-docker-environment). 3. Acquire a domain name. @@ -28,7 +37,7 @@ the Login service. 4. Check out the code: ``` -go get -d github.com/knative/docs/code-samples/serving/knative-routing-go +git clone https://github.com/knative/docs.git knative-docs ``` ## Setup @@ -44,10 +53,10 @@ Then, check the value for `data`. The domain name should be in the format of Build the application container and publish it to a container registry: -1. Move into the sample directory: +1. Move into the repository root: ```bash -cd $GOPATH/src/github.com/knative/docs +cd knative-docs ``` 2. Set your preferred container registry: @@ -71,15 +80,19 @@ export REPO="docker.io/" ``` # Build and push the container on your local machine. -docker buildx build --platform linux/arm64,linux/amd64 -t "${REPO}/knative-routing-go" --push . -f code-samples/serving/knative-routing-go/Dockerfile +docker buildx build --platform linux/arm64,linux/amd64 -t "${REPO}/helloworld-go" --push code-samples/serving/hello-world/helloworld-go ``` +Both Services use this single image; `TARGET="Search Service"` and +`TARGET="Login Service"` distinguish their responses. No separate routing backend +source or image is needed. + 4. Replace the image reference path with our published image path in the configuration file `code-samples/serving/knative-routing-go/sample.yaml`: - Manually replace: - `image: github.com/knative/docs/code-samples/serving/knative-routing-go` - with `image: ${REPO}/knative-routing-go` If you manually changed the .yaml + `image: github.com/knative/docs/code-samples/serving/hello-world/helloworld-go` + with `image: ${REPO}/helloworld-go` If you manually changed the .yaml file, you must replace \${REPO} with the correct path on your local machine. @@ -88,7 +101,7 @@ docker buildx build --platform linux/arm64,linux/amd64 -t "${REPO}/knative-routi - Run this command: ``` - perl -pi -e "s@github.com/knative/docs/code-samples/serving@${REPO}@g" code-samples/serving/knative-routing-go/sample.yaml + perl -pi -e "s@github.com/knative/docs/code-samples/serving/hello-world@${REPO}@g" code-samples/serving/knative-routing-go/sample.yaml ``` ## Deploy the Service @@ -152,7 +165,7 @@ search-service http://search-service.default.example.com curl http://${GATEWAY_IP} --header "Host:search-service.default.example.com" ``` -You should see: `Search Service is called !` +You should see: `Hello Search Service!` 4. Similarly, you can also directly access "Login" service with: @@ -160,7 +173,7 @@ You should see: `Search Service is called !` curl http://${GATEWAY_IP} --header "Host:login-service.default.example.com" ``` -You should see: `Login Service is called !` +You should see: `Hello Login Service!` ## Apply Custom Routing Rule @@ -283,7 +296,7 @@ kubectl apply --filename code-samples/serving/knative-routing-go/routing-interna will replace the custom routing rule with one that uses the `knative-local-gateway`, enabling access via `example.com/search` and `example.com/login` again. -With these changes, you can also use [the `auto-tls` feature](https://knative.dev/docs/serving/using-auto-tls) in combination with the global setting +With these changes, you can also use [external domain TLS](https://knative.dev/docs/serving/encryption/external-domain-tls/) in combination with the global setting `"http-protocol": "Redirected"`, which would otherwise try to redirect the `entry-route` VirtualService requests from HTTP to HTTPS, failing the request. diff --git a/code-samples/serving/knative-routing-go/go.mod b/code-samples/serving/knative-routing-go/go.mod deleted file mode 100644 index 61b1cd9cd2..0000000000 --- a/code-samples/serving/knative-routing-go/go.mod +++ /dev/null @@ -1,3 +0,0 @@ -module knative.dev/docs/code-samples/serving/knative-routing-go - -go 1.22 diff --git a/code-samples/serving/knative-routing-go/sample.go b/code-samples/serving/knative-routing-go/sample.go deleted file mode 100644 index 3366d41f4b..0000000000 --- a/code-samples/serving/knative-routing-go/sample.go +++ /dev/null @@ -1,42 +0,0 @@ -/* -Copyright 2018 The Knative Authors - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - https://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -package main - -import ( - "flag" - "fmt" - "log" - "net/http" - "os" -) - -func handler(w http.ResponseWriter, r *http.Request) { - serviceName := os.Getenv("SERVICE_NAME") - if serviceName == "" { - serviceName = "NOT SPECIFIED" - } - log.Printf("%s received a request.", serviceName) - fmt.Fprintf(w, "%s is called !\n", serviceName) -} - -func main() { - flag.Parse() - log.Print("Sample started.") - - http.HandleFunc("/", handler) - http.ListenAndServe(":8080", nil) -} diff --git a/code-samples/serving/knative-routing-go/sample.yaml b/code-samples/serving/knative-routing-go/sample.yaml index 3ce6b0eb97..7440af2024 100644 --- a/code-samples/serving/knative-routing-go/sample.yaml +++ b/code-samples/serving/knative-routing-go/sample.yaml @@ -23,9 +23,9 @@ spec: containers: - # This is the Go import path for the binary to containerize # and substitute here. - image: github.com/knative/docs/code-samples/serving/knative-routing-go + image: github.com/knative/docs/code-samples/serving/hello-world/helloworld-go env: - - name: SERVICE_NAME + - name: TARGET value: Search Service readinessProbe: httpGet: @@ -43,9 +43,9 @@ spec: containers: - # This is the Go import path for the binary to containerize # and substitute here. - image: github.com/knative/docs/code-samples/serving/knative-routing-go + image: github.com/knative/docs/code-samples/serving/hello-world/helloworld-go env: - - name: SERVICE_NAME + - name: TARGET value: Login Service readinessProbe: httpGet: From d5be777fcd54390847b690ea2cad14f65b018407 Mon Sep 17 00:00:00 2001 From: Dave Protasowski Date: Mon, 26 Oct 2026 09:16:05 -0400 Subject: [PATCH 37/44] Update the multi-container sample --- .../serving/multi-container/README.md | 301 ++++-------------- .../serving/multi-container/service.yaml | 4 +- .../servingcontainer/Dockerfile | 4 +- .../multi-container/servingcontainer/go.mod | 2 +- .../servingcontainer/servingcontainer.go | 18 +- .../sidecarcontainer/Dockerfile | 4 +- .../multi-container/sidecarcontainer/go.mod | 2 +- 7 files changed, 90 insertions(+), 245 deletions(-) diff --git a/code-samples/serving/multi-container/README.md b/code-samples/serving/multi-container/README.md index 0545fcc794..efee4471f1 100644 --- a/code-samples/serving/multi-container/README.md +++ b/code-samples/serving/multi-container/README.md @@ -1,265 +1,100 @@ -# Knative multi-container samples +# Knative multi-container sample -A simple web app written in Go that you can use for multi container testing. +This Go web app demonstrates a serving container calling a sidecar over the +shared Pod network. The serving container listens on port 8881 and the sidecar +on port 8882. Requests return `Yay!! multi-container works`. +These ports are fixed in the source; setting the `PORT` environment variable +does not override them. Keep `containerPort: 8881` in the Service manifest. ## Prerequisites -- A Kubernetes cluster with Knative installed and DNS configured. See - [Install Knative Serving](https://knative.dev/docs/install/serving/install-serving-with-yaml). -- [Docker](https://www.docker.com) installed and running on your local machine, - and a Docker Hub account configured (we'll use it for a container registry). -- Make sure multi-container flag is enabled as part of `config-features` configmap. +- A Kubernetes cluster with [Knative Serving installed](https://knative.dev/docs/install/serving/install-serving-with-yaml) + and DNS configured. +- Docker installed and running, and a container registry you can push images to. +- If your Knative version gates multi-container support, enable the + `multi-container` feature in the `config-features` ConfigMap. -The following steps show how you can use the sample code and deploy the app to your -cluster. +## Build the sample -You can download a working copy of the sample, by entering the -following command: +Clone the repository and enter the sample directory: ```bash -git clone -b "{{ branch }}" https://github.com/knative/docs knative-docs +git clone https://github.com/knative/docs.git knative-docs +cd knative-docs/code-samples/serving/multi-container ``` -## Using the sample code +Use the checked-in files rather than recreating them from snippets: -To test multi container functionality, you must create two containers: a serving container, and a sidecar container. +- Serving container: [Go source](servingcontainer/servingcontainer.go), + [Dockerfile](servingcontainer/Dockerfile), and [module](servingcontainer/go.mod). +- Sidecar container: [Go source](sidecarcontainer/sidecarcontainer.go), + [Dockerfile](sidecarcontainer/Dockerfile), and [module](sidecarcontainer/go.mod). +- Knative Service: [service.yaml](service.yaml). -The `multi-container` directory is provided in the sample code, and contains predefined code and dockerfiles for creating the containers. +No local Go installation or `go mod init` is necessary. -You can update the default files and YAML by using the steps outlined in this section. +Build and push both images, replacing `username` with your Docker Hub username: -### Serving Container -1. After you have cloned the sample repository, navigate to the servingcontainer directory: - - `cd knative-docs/code-samples/serving/multi-container/servingcontainer` -1. Create a basic web server which listens on port 8881. -You can do this by copying the following code into the `servingcontainer.go` file: - - ```go - package main - import ( - "fmt" - "io" - "log" - "net/http" - ) - func handler(w http.ResponseWriter, r *http.Request) { - log.Println("serving container received a request.") - res, err := http.Get("http://127.0.0.1:8882") - if err != nil { - log.Fatal(err) - } - resp, err := io.ReadAll(res.Body) - if err != nil { - log.Fatal(err) - } - fmt.Fprintln(w, string(resp)) - } - func main() { - log.Print("serving container started...") - http.HandleFunc("/", handler) - log.Fatal(http.ListenAndServe(":8881", nil)) - } - ``` -1. Copy the following code into the `Dockerfile` file: - - ```docker - # Use the official Golang image to create a build artifact. - # This is based on Debian and sets the GOPATH to /go. - # https://hub.docker.com/_/golang - FROM golang:1.15 as builder - - ARG TARGETOS - ARG TARGETARCH - - # Create and change to the app directory. - WORKDIR /app - # Retrieve application dependencies using go modules. - # Allows container builds to reuse downloaded dependencies. - COPY go.* ./ - RUN go mod download - # Copy local code to the container image. - COPY . ./ - # Build the binary. - # -mod=readonly ensures immutable go.mod and go.sum in container builds. - RUN CGO_ENABLED=0 GOOS=linux GOOS=${TARGETOS} GOARCH=${TARGETARCH} go build -mod=readonly -v -o servingcontainer - # Use the official Alpine image for a lean production container. - # https://hub.docker.com/_/alpine - # https://docs.docker.com/develop/develop-images/multistage-build/#use-multi-stage-builds - FROM alpine:3 - RUN apk add --no-cache ca-certificates - # Copy the binary to the production image from the builder stage. - COPY --from=builder /app/servingcontainer /servingcontainer - # Run the web service on container startup. - CMD ["/servingcontainer"] - ``` - -### Sidecar Container -1. After you have cloned the sample repository, navigate to the sidecarcontainer directory: - ```text - cd - - cd knative-docs/code-samples/serving/multi-container/sidecarcontainer - ``` - -1. Create a basic web server which listens on port 8882. -You can do this by copying the following code into the `sidecarcontainer.go` file: - - ```go - package main - import ( - "fmt" - "log" - "net/http" - ) - func handler(w http.ResponseWriter, r *http.Request) { - log.Println("sidecar container received a request.") - fmt.Fprintln(w, "Yay!! multi-container works") - } - func main() { - log.Print("sidecar container started...") - http.HandleFunc("/", handler) - log.Fatal(http.ListenAndServe(":8882", nil)) - } - ``` - -1. Copy the following code into the `Dockerfile` file: - - ```docker - # Use the official Golang image to create a build artifact. - # This is based on Debian and sets the GOPATH to /go. - # https://hub.docker.com/_/golang - FROM golang:1.15 as builder - - ARG TARGETOS - ARG TARGETARCH - - # Create and change to the app directory. - WORKDIR /app - # Retrieve application dependencies using go modules. - # Allows container builds to reuse downloaded dependencies. - COPY go.* ./ - RUN go mod download - # Copy local code to the container image. - COPY . ./ - # Build the binary. - # -mod=readonly ensures immutable go.mod and go.sum in container builds. - RUN CGO_ENABLED=0 GOOS=linux GOOS=${TARGETOS} GOARCH=${TARGETARCH} go build -mod=readonly -v -o sidecarcontainer - # Use the official Alpine image for a lean production container. - # https://hub.docker.com/_/alpine - # https://docs.docker.com/develop/develop-images/multistage-build/#use-multi-stage-builds - FROM alpine:3 - RUN apk add --no-cache ca-certificates - # Copy the binary to the production image from the builder stage. - COPY --from=builder /app/sidecarcontainer /sidecarcontainer - # Run the web service on container startup. - CMD ["/sidecarcontainer"] - ``` - -### Writing Knative Service YAML - -1. After you have cloned the sample repository, navigate to the `multi-container` directory: - 1. `cd -` - 2. `cd knative-docs/code-samples/serving/multi-container/` - -1. Copy the following YAML service definition into the `service.yaml` file: - - ```yaml - apiVersion: serving.knative.dev/v1 - kind: Service - metadata: - name: multi-container - namespace: default - spec: - template: - spec: - containers: - - image: docker.io/{username}/servingcontainer - ports: - - containerPort: 8881 - - image: docker.io/{username}/sidecarcontainer - ``` - -**NOTE:** Replace `{username}` with your Docker Hub username. - -1. Use Go tool to create a - [`go.mod`](https://github.com/golang/go/wiki/Modules#gomod) manifest: - - servingcontainer - ```bash - cd - - cd knative-docs/code-samples/serving/multi-container/servingcontainer - go mod init github.com/knative/docs/code-samples/serving/multi-container/servingcontainer - ``` - sidecarcontainer - ```bash - cd - - cd knative-docs/code-samples/serving/multi-container/sidecarcontainer - go mod init github.com/knative/docs/code-samples/serving/multi-container/sidecarcontainer - ``` - -## Building and deploying the sample - -After you have modified the sample code files you can build and deploy the sample app. - -1. Use Docker to build the sample code into a container. To build and push with - Docker Hub, run these commands replacing `{username}` with your Docker Hub - username: +```bash +export DOCKER_HUB_USERNAME=username +docker buildx build --platform linux/arm64,linux/amd64 \ + -t "${DOCKER_HUB_USERNAME}/servingcontainer" --push ./servingcontainer +docker buildx build --platform linux/arm64,linux/amd64 \ + -t "${DOCKER_HUB_USERNAME}/sidecarcontainer" --push ./sidecarcontainer +``` - ```bash - # Build and push the container on your local machine. - cd - - cd knative-docs/code-samples/serving/multi-container/servingcontainer - docker buildx build --platform linux/arm64,linux/amd64 -t "{username}/servingcontainer" --push . +## Test locally with Docker - cd - - cd knative-docs/code-samples/serving/multi-container/sidecarcontainer - docker buildx build --platform linux/arm64,linux/amd64 -t "{username}/sidecarcontainer" --push . - ``` +Build native images locally without publishing them: -1. After the build has completed and the container is pushed to Docker Hub, you - can deploy the app into your cluster. Ensure that the container image value - in `service.yaml` matches the container you built in the previous step. Apply - the configuration using `kubectl`: +```bash +docker build -t servingcontainer ./servingcontainer +docker build -t sidecarcontainer ./sidecarcontainer +``` - ```bash - cd - - cd knative-docs/code-samples/serving/multi-container - kubectl apply --filename service.yaml - ``` +Use a shared network namespace to reproduce the Pod's localhost networking: -1. Now that your service is created, Knative will perform the following steps: +```bash +docker run -d --name knative-sample-sidecar -p 127.0.0.1:8881:8881 sidecarcontainer +docker run -d --name knative-sample-serving \ + --network container:knative-sample-sidecar servingcontainer +curl --fail http://127.0.0.1:8881 +# Yay!! multi-container works +``` - - Create a new immutable revision for this version of the app. - - Network programming to create a route, ingress, service, and load balance - for your app. - - Automatically scale your pods up and down (including to zero active pods). +If the sidecar is unavailable, returns a non-200 status, or takes longer than +five seconds to respond, the serving container returns HTTP 502 instead of +terminating. Retry once both containers have started. -1. Run the following command to find the domain URL for your service: +Clean up the local containers: - ```bash - kubectl get ksvc multi-container --output=custom-columns=NAME:.metadata.name,URL:.status.url - ``` +```bash +docker rm -f knative-sample-serving knative-sample-sidecar +``` - Example: +## Deploy to Knative - ```bash - NAME URL - multi-container http://multi-container.default.1.2.3.4.sslip.io - ``` +Replace both `{username}` placeholders in [service.yaml](service.yaml) with your registry +username, matching the images you pushed. The manifest exposes only port 8881; +the containers communicate over `127.0.0.1:8882` inside the Pod. -1. Now you can make a request to your app and see the result. Replace - the following URL with the URL returned in the previous command. +```bash +kubectl apply --filename service.yaml +kubectl wait --for=condition=Ready ksvc/multi-container --timeout=120s +kubectl get ksvc multi-container --output=custom-columns=NAME:.metadata.name,URL:.status.url +``` - ```bash - curl http://multi-container.default.1.2.3.4.sslip.io - Yay!! multi-container works - ``` +Request the URL returned above: - > Note: Add `-v` option to get more detail if the `curl` command failed. +```bash +curl --fail "$(kubectl get ksvc multi-container -o jsonpath='{.status.url}')" +# Yay!! multi-container works +``` -## Removing the sample app deployment +Knative creates an immutable Revision, configures routing, and scales the Pod +containing both containers based on traffic, including scaling to zero. -To remove the sample app from your cluster, delete the service record: +## Clean up the deployment ```bash kubectl delete --filename service.yaml diff --git a/code-samples/serving/multi-container/service.yaml b/code-samples/serving/multi-container/service.yaml index 49f83cb8fb..96c5d44581 100644 --- a/code-samples/serving/multi-container/service.yaml +++ b/code-samples/serving/multi-container/service.yaml @@ -21,7 +21,7 @@ spec: template: spec: containers: - - image: docker.io/savita3020/servingcontainer + - image: docker.io/{username}/servingcontainer ports: - containerPort: 8881 - - image: docker.io/savita3020/sidecarcontainer + - image: docker.io/{username}/sidecarcontainer diff --git a/code-samples/serving/multi-container/servingcontainer/Dockerfile b/code-samples/serving/multi-container/servingcontainer/Dockerfile index bb5139bb93..3f194d0519 100644 --- a/code-samples/serving/multi-container/servingcontainer/Dockerfile +++ b/code-samples/serving/multi-container/servingcontainer/Dockerfile @@ -1,7 +1,7 @@ # Use the official Golang image to create a build artifact. # This is based on Debian and sets the GOPATH to /go. # https://hub.docker.com/_/golang -FROM golang:latest AS builder +FROM golang:1.27 AS builder ARG TARGETOS ARG TARGETARCH @@ -17,7 +17,7 @@ RUN go mod tidy # Build the binary. # -mod=readonly ensures immutable go.mod and go.sum in container builds. -RUN CGO_ENABLED=0 GOOS=linux GOOS=${TARGETOS} GOARCH=${TARGETARCH} go build -mod=readonly -v -o servingcontainer +RUN CGO_ENABLED=0 GOOS=${TARGETOS} GOARCH=${TARGETARCH} go build -mod=readonly -v -o servingcontainer # Use the official Alpine image for a lean production container. # https://hub.docker.com/_/alpine diff --git a/code-samples/serving/multi-container/servingcontainer/go.mod b/code-samples/serving/multi-container/servingcontainer/go.mod index bec44ea553..c3b7814c50 100644 --- a/code-samples/serving/multi-container/servingcontainer/go.mod +++ b/code-samples/serving/multi-container/servingcontainer/go.mod @@ -1,3 +1,3 @@ module github.com/knative/docs/code-samples/serving/multi-container/servingcontainer -go 1.22 +go 1.27.0 diff --git a/code-samples/serving/multi-container/servingcontainer/servingcontainer.go b/code-samples/serving/multi-container/servingcontainer/servingcontainer.go index 45efa5ff7c..556d7f57f2 100644 --- a/code-samples/serving/multi-container/servingcontainer/servingcontainer.go +++ b/code-samples/serving/multi-container/servingcontainer/servingcontainer.go @@ -21,19 +21,29 @@ import ( "io" "log" "net/http" + "time" ) +var sidecarClient = &http.Client{Timeout: 5 * time.Second} + func handler(w http.ResponseWriter, r *http.Request) { log.Println("serving container received a request.") - res, err := http.Get("http://127.0.0.1:8882") + res, err := sidecarClient.Get("http://127.0.0.1:8882") if err != nil { - log.Fatal(err) + http.Error(w, "sidecar unavailable", http.StatusBadGateway) + return + } + defer res.Body.Close() + if res.StatusCode != http.StatusOK { + http.Error(w, "sidecar returned an error", http.StatusBadGateway) + return } resp, err := io.ReadAll(res.Body) if err != nil { - log.Fatal(err) + http.Error(w, "unable to read sidecar response", http.StatusBadGateway) + return } - fmt.Fprintln(w, string(resp)) + fmt.Fprint(w, string(resp)) } func main() { diff --git a/code-samples/serving/multi-container/sidecarcontainer/Dockerfile b/code-samples/serving/multi-container/sidecarcontainer/Dockerfile index 597714478c..20646ecc4a 100644 --- a/code-samples/serving/multi-container/sidecarcontainer/Dockerfile +++ b/code-samples/serving/multi-container/sidecarcontainer/Dockerfile @@ -1,7 +1,7 @@ # Use the official Golang image to create a build artifact. # This is based on Debian and sets the GOPATH to /go. # https://hub.docker.com/_/golang -FROM golang:latest AS builder +FROM golang:1.27 AS builder ARG TARGETOS ARG TARGETARCH @@ -17,7 +17,7 @@ RUN go mod tidy # Build the binary. # -mod=readonly ensures immutable go.mod and go.sum in container builds. -RUN CGO_ENABLED=0 GOOS=linux GOOS=${TARGETOS} GOARCH=${TARGETARCH} go build -mod=readonly -v -o sidecarcontainer +RUN CGO_ENABLED=0 GOOS=${TARGETOS} GOARCH=${TARGETARCH} go build -mod=readonly -v -o sidecarcontainer # Use the official Alpine image for a lean production container. # https://hub.docker.com/_/alpine diff --git a/code-samples/serving/multi-container/sidecarcontainer/go.mod b/code-samples/serving/multi-container/sidecarcontainer/go.mod index c80d051b4e..5493045bb3 100644 --- a/code-samples/serving/multi-container/sidecarcontainer/go.mod +++ b/code-samples/serving/multi-container/sidecarcontainer/go.mod @@ -1,3 +1,3 @@ module github.com/knative/docs/code-samples/serving/multi-container/sidecarcontainer -go 1.22 +go 1.27.0 From 6097f21eff01fa7c5874510344946e4d400b9aee Mon Sep 17 00:00:00 2001 From: Dave Protasowski Date: Mon, 26 Oct 2026 09:16:05 -0400 Subject: [PATCH 38/44] Simplify the Go mounted Secret sample --- code-samples/serving/secrets-go/Dockerfile | 39 +-- code-samples/serving/secrets-go/README.md | 282 +++++-------------- code-samples/serving/secrets-go/ci.sh | 41 +++ code-samples/serving/secrets-go/go.mod | 53 +--- code-samples/serving/secrets-go/go.sum | 127 --------- code-samples/serving/secrets-go/secret.yaml | 11 +- code-samples/serving/secrets-go/secrets.go | 56 ++-- code-samples/serving/secrets-go/service.yaml | 29 +- 8 files changed, 157 insertions(+), 481 deletions(-) create mode 100755 code-samples/serving/secrets-go/ci.sh delete mode 100644 code-samples/serving/secrets-go/go.sum diff --git a/code-samples/serving/secrets-go/Dockerfile b/code-samples/serving/secrets-go/Dockerfile index 513049996b..078be002b0 100644 --- a/code-samples/serving/secrets-go/Dockerfile +++ b/code-samples/serving/secrets-go/Dockerfile @@ -1,32 +1,13 @@ -# Use the official Golang image to create a build artifact. -# This is based on Debian and sets the GOPATH to /go. -# https://hub.docker.com/_/golang -FROM golang:latest AS builder - +# Standard-library-only build using the supported Go 1.27 release track. +FROM golang:1.27 AS builder ARG TARGETOS ARG TARGETARCH - -# Create and change to the app directory. WORKDIR /app - -# Copy local code to the container image. -COPY . ./ - -# Install dependencies and tidy up the go.mod and go.sum files. -RUN go mod tidy - -# Build the output command inside the container. -RUN CGO_ENABLED=0 GOOS=linux GOOS=${TARGETOS} GOARCH=${TARGETARCH} go build -v -o hellosecrets - -# Use a Docker multi-stage build to create a lean production image. -# https://docs.docker.com/develop/develop-images/multistage-build/#use-multi-stage-builds -FROM alpine - -# Enable the use of outbound https -RUN apk add --no-cache ca-certificates - -# Copy the binary to the production image from the builder stage. -COPY --from=builder /app/hellosecrets /hellosecrets - -# Run the web service on container startup. -CMD ["/hellosecrets"] +COPY . . +RUN CGO_ENABLED=0 GOOS=${TARGETOS} GOARCH=${TARGETARCH} go build -trimpath -o /hellosecrets . + +# No shell, SDK, package manager, CA store or outbound service is required. +FROM scratch +COPY --from=builder /hellosecrets /hellosecrets +USER 65532:65532 +ENTRYPOINT ["/hellosecrets"] diff --git a/code-samples/serving/secrets-go/README.md b/code-samples/serving/secrets-go/README.md index 438fd22da7..394ec21f9d 100644 --- a/code-samples/serving/secrets-go/README.md +++ b/code-samples/serving/secrets-go/README.md @@ -1,13 +1,8 @@ # Knative Secrets - Go -A simple web app written in Go that you can use for testing. It demonstrates how -to use a Kubernetes secret as a Volume with Knative. We will create a new Google -Service Account and place it into a Kubernetes secret, then we will mount it -into a container as a Volume. - -Do the following steps to create the sample code and then deploy the app to your -cluster. You can also download a working copy of the sample, by running the -following commands: +A standard-library Go web app that reads a Kubernetes Secret mounted as a file. +It returns a safe success message, **never the secret's contents**. No cloud +account, credentials, SDK or external service is required. ```bash git clone https://github.com/knative/docs.git knative-docs @@ -16,239 +11,100 @@ cd knative-docs/code-samples/serving/secrets-go ## Before you begin -- A Kubernetes cluster with Knative installed. See - [Install Knative Serving](https://knative.dev/docs/install/serving/install-serving-with-yaml). -- [Docker](https://www.docker.com) installed and running on your local machine, - and a Docker Hub account configured (we'll use it for a container registry). -- Create a - [Google Cloud project](https://cloud.google.com/resource-manager/docs/creating-managing-projects) - and install the `gcloud` CLI and run `gcloud auth login`. This sample will use - a mix of `gcloud` and `kubectl` commands. The rest of the sample assumes that - you've set the `$PROJECT_ID` environment variable to your Google Cloud project - id, and also set your project ID as default using - `gcloud config set project $PROJECT_ID`. - -## Recreating the sample code - -1. Create a new file named `secrets.go` and paste the following code. This code - creates a basic web server which listens on port 8080: - - ```go - package main - - import ( - "context" - "fmt" - "log" - "net/http" - "os" - - "cloud.google.com/go/storage" - ) - - func main() { - log.Print("Secrets sample started.") - - // This sets up the standard GCS storage client, which will pull - // credentials from GOOGLE_APPLICATION_CREDENTIALS if specified. - ctx := context.Background() - client, err := storage.NewClient(ctx) - if err != nil { - log.Fatalf("Unable to initialize storage client: %v", err) - } - - http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { - // This GCS bucket has been configured so that any authenticated - // user can access it (Read Only), so any Service Account can - // run this sample. - bkt := client.Bucket("knative-secrets-sample") - - // Access the attributes of this GCS bucket, and write it back to the - // user. On failure, return a 500 and the error message. - attrs, err := bkt.Attrs(ctx) - if err != nil { - http.Error(w, err.Error(), http.StatusInternalServerError) - return - } - fmt.Fprintln(w, - fmt.Sprintf("bucket %s, created at %s, is located in %s with storage class %s\n", - attrs.Name, attrs.Created, attrs.Location, attrs.StorageClass)) - - }) - - port := os.Getenv("PORT") - if port == "" { - port = "8080" - } - - log.Fatal(http.ListenAndServe(fmt.Sprintf(":%s", port), nil)) - } - ``` - -1. In your project directory, create a file named `Dockerfile` and copy the following code - block into it. For detailed instructions on dockerizing a Go app, see - [Deploying Go servers with Docker](https://blog.golang.org/docker). - - ```docker - # Use the official Golang image to create a build artifact. - # This is based on Debian and sets the GOPATH to /go. - # https://hub.docker.com/_/golang - FROM golang as builder - - ARG TARGETOS - ARG TARGETARCH +- A Kubernetes cluster with + [Knative Serving installed](https://knative.dev/docs/install/serving/install-serving-with-yaml). +- Docker and `kubectl`, plus a container registry you can push to and the cluster + can pull from. - # Copy local code to the container image. - WORKDIR /go/src/github.com/knative/docs/hellosecrets - COPY . . +## Sample files - # Build the output command inside the container. - RUN CGO_ENABLED=0 GOOS=linux GOOS=${TARGETOS} GOARCH=${TARGETARCH} go build -v -o hellosecrets +- [secrets.go](secrets.go): HTTP server. It reads `SECRET_FILE` on every request, + defaulting to `/var/secret/message`, and listens on `PORT` (default `8080`). +- [secret.yaml](secret.yaml): an Opaque Secret named `mounted-secret` with a + `message` key containing the non-sensitive example value `demonstration-only`. +- [service.yaml](service.yaml): Knative Service with that Secret mounted read-only + at `/var/secret`. Its key becomes the filename `message`. +- [Dockerfile](Dockerfile) and [go.mod](go.mod): pinned Go build and a non-root, + minimal final image. There are no third-party Go modules. - # Use a Docker multi-stage build to create a lean production image. - # https://docs.docker.com/develop/develop-images/multistage-build/#use-multi-stage-builds - FROM alpine +Only use demonstration values with this sample. Never commit real Secret values. +Kubernetes Secret manifests contain data, not encryption; restrict their access +and configure encryption at rest as appropriate for your cluster. - # Enable the use of outbound https - RUN apk add --no-cache ca-certificates +## Build and deploy - # Copy the binary to the production image from the builder stage. - COPY --from=builder /go/src/github.com/knative/docs/hellosecrets/hellosecrets /hellosecrets +1. In [service.yaml](service.yaml), replace `docker.io/{username}/secrets-go` with + the image reference you will build and push. These manifests use the `default` + namespace; if you choose another, change **both** manifests consistently. - # Service must listen to $PORT environment variable. - # This default value facilitates local development. - ENV PORT 8080 - - # Run the web service on container startup. - CMD ["/hellosecrets"] - ``` - -1. [Create a new Google Service Account](https://cloud.google.com/iam/docs/creating-managing-service-accounts). - This Service Account doesn't need any privileges, the GCS bucket has been - configured so that any authenticated identity may read it. +1. Build and push the sample, substituting your registry username: ```bash - gcloud iam service-accounts create knative-secrets - ``` - -1. Create a new JSON key for this account - - ```bash - gcloud iam service-accounts keys create robot.json \ - --iam-account=knative-secrets@$PROJECT_ID.iam.gserviceaccount.com - ``` - -1. Create a new Kubernetes secret from this JSON key: - - ```bash - kubectl create secret generic google-robot-secret --from-file=./robot.json - ``` - - You can achieve a similar result by editting `secret.yaml`, copying the - contents of `robot.json` as instructed there, and running - `kubectl apply --filename secret.yaml`. - -1. Create a new file, `service.yaml` and copy the following service definition - into the file. Make sure to replace `{username}` with your Docker Hub - username. - - ```yaml - apiVersion: serving.knative.dev/v1 - kind: Service - metadata: - name: secrets-go - namespace: default - spec: - template: - spec: - containers: - # Replace {username} with your DockerHub username - - image: docker.io/{username}/secrets-go - - env: - # This directs the Google Cloud SDK to use the identity and project - # defined by the Service Account (aka robot) in the JSON file at - # this path. - # - `/var/secret` is determined by the `volumeMounts[0].mountPath` - # below. This can be changed if both places are changed. - # - `robot.json` is determined by the "key" that is used to hold the - # secret content in the Kubernetes secret. This can be changed - # if both places are changed. - - name: GOOGLE_APPLICATION_CREDENTIALS - value: /var/secret/robot.json - - # This section specified where in the container we want the - # volume containing our secret to be mounted. - volumeMounts: - - name: robot-secret - mountPath: /var/secret - - # This section attaches the secret "google-robot-secret" to - # the Pod holding the user container. - volumes: - - name: robot-secret - secret: - secretName: google-robot-secret + docker buildx build --platform linux/arm64,linux/amd64 \ + -t docker.io/{username}/secrets-go --push . ``` -## Building and deploying the sample - -Once you have recreated the sample code files (or used the files in the sample -folder) you're ready to build and deploy the sample app. - -1. Use Docker to build the sample code into a container. To build and push with - Docker Hub, run these commands replacing `{username}` with your Docker Hub - username: +1. Create the sample Secret before the Knative Service: ```bash - # Build and push the container on your local machine. - docker buildx build --platform linux/arm64,linux/amd64 -t "{username}/secrets-go" --push . + kubectl apply --filename secret.yaml + kubectl apply --filename service.yaml + kubectl wait --for=condition=Ready ksvc/secrets-go --timeout=180s ``` -1. After the build has completed and the container is pushed to docker hub, you - can deploy the app into your cluster. Ensure that the container image value - in `service.yaml` matches the container you built in the previous step. Apply - the configuration using `kubectl`: +1. Request the actual Knative route: ```bash - kubectl apply --filename service.yaml + URL=$(kubectl get ksvc secrets-go -o jsonpath='{.status.url}') + curl --fail-with-body "$URL" ``` -1. Now that your service is created, Knative will perform the following steps: + Expected response: `Secret file loaded.` followed by a newline. The response + does not reveal the value, length or path of the Secret file. - - Create a new immutable revision for this version of the app. - - Network programming to create a route, ingress, service, and load balance - for your app. - - Automatically scale your pods up and down (including to zero active pods). +## Updates and errors -1. Run the following command to find the domain URL for your service: +The app reopens the file on every request rather than caching it. Kubernetes +projects Secret updates **eventually**, not immediately. This sample mounts the +whole volume (not `subPath`), so updates can reach the running app without a pod +restart. - ```bash - kubectl get ksvc secrets-go --output=custom-columns=NAME:.metadata.name,URL:.status.url - ``` +To demonstrate a behavior change using only sample data, set the file to empty: - Example: - - ```bash - NAME URL - secrets-go http://secrets-go.default.1.2.3.4.sslip.io - ``` +```bash +kubectl patch secret mounted-secret --type=merge -p '{"stringData":{"message":""}}' +# Repeat after the projected volume has updated; expect HTTP 503: +curl -i "$URL" +``` -1. Now you can make a request to your app and see the result. Replace - the following URL with the URL returned in the previous command. +The exact body becomes `Secret file is empty.` followed by a newline. Whitespace- +only files are also treated as empty. Restore the checked-in sample value: - ```bash - curl http://secrets-go.default.1.2.3.4.sslip.io - bucket knative-secrets-sample, created at 2019-02-01 14:44:05.804 +0000 UTC, is located in US with storage class MULTI_REGIONAL - ``` +```bash +kubectl apply --filename secret.yaml +# After projection, expect HTTP 200 and "Secret file loaded." again: +curl -i "$URL" +``` - > Note: Add `-v` option to get more detail if the `curl` command failed. +A missing or unreadable file returns HTTP 503 with `Secret file unavailable.` and +a newline. No underlying filesystem error, path or content is returned or logged. +The server still starts, so Knative's default TCP readiness is not proof that the +file can be read; check the HTTP response too. A missing **Kubernetes Secret** is +different: the required volume cannot mount and the new pod/revision cannot become +Ready until the Secret exists. -## Removing the sample app deployment +To use a different file path, add `SECRET_FILE` to the container's environment in +[service.yaml](service.yaml) and adjust the volume mount or projected filename to +match. For a custom Knative port, set the container's `ports[].containerPort`; +Knative supplies `PORT`. Outside Knative, set `PORT` directly, or leave it unset +for port 8080. -To remove the sample app from your cluster, delete the service record: +## Cleanup ```bash kubectl delete --filename service.yaml -kubectl delete secret google-robot-secret +kubectl delete --filename secret.yaml ``` + +Remove your unused local sample image tags after testing. Do not prune unrelated +Docker images, volumes or shared cluster resources. diff --git a/code-samples/serving/secrets-go/ci.sh b/code-samples/serving/secrets-go/ci.sh new file mode 100755 index 0000000000..c4ed12dd5a --- /dev/null +++ b/code-samples/serving/secrets-go/ci.sh @@ -0,0 +1,41 @@ +#!/usr/bin/env bash +# Source from this sample directory; the caller owns cluster setup and ingress forwarding. +IMAGE=dev.local/secrets-go:ci +SAMPLE_URL=http://127.0.0.1:18080 + +main() ( + set -euo pipefail + build + deploy + test +) + +build() ( + set -euo pipefail + docker build -t "$IMAGE" . +) + +deploy() ( + set -euo pipefail + kind load docker-image "$IMAGE" + kubectl --namespace default apply -f secret.yaml + kubectl patch --local -f service.yaml --type=json -p "$( + jq -cn --arg image "$IMAGE" '[ + {op: "replace", path: "/spec/template/spec/containers/0/image", value: $image}, + {op: "add", path: "/spec/template/spec/containers/0/imagePullPolicy", value: "IfNotPresent"} + ]' + )" -o yaml | kubectl --namespace default apply -f - + kubectl --namespace default wait -f service.yaml --for=condition=Ready --timeout=300s +) + +test() ( + set -euo pipefail + local service_url service_host + service_url=$(kubectl --namespace default get -f service.yaml -o jsonpath='{.status.url}') + service_host=${service_url#*://} + [[ "$service_host" =~ ^[a-zA-Z0-9.-]+$ ]] + local body + body=$(curl -fsS --max-time 30 \ + -H "Host: $service_host" "$SAMPLE_URL/") + [[ $body == 'Secret file loaded.' ]] +) diff --git a/code-samples/serving/secrets-go/go.mod b/code-samples/serving/secrets-go/go.mod index f9537688eb..b11d6bf4b1 100644 --- a/code-samples/serving/secrets-go/go.mod +++ b/code-samples/serving/secrets-go/go.mod @@ -1,54 +1,3 @@ module knative.dev/docs/code-samples/serving/secrets-go -go 1.25.0 - -require cloud.google.com/go/storage v1.56.0 - -require ( - cel.dev/expr v0.25.2 // indirect - cloud.google.com/go v0.121.6 // indirect - cloud.google.com/go/auth v0.18.2 // indirect - cloud.google.com/go/auth/oauth2adapt v0.2.8 // indirect - cloud.google.com/go/compute/metadata v0.9.0 // indirect - cloud.google.com/go/iam v1.5.3 // indirect - cloud.google.com/go/monitoring v1.24.3 // indirect - github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.33.0 // indirect - github.com/GoogleCloudPlatform/opentelemetry-operations-go/exporter/metric v0.53.0 // indirect - github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/resourcemapping v0.53.0 // indirect - github.com/cespare/xxhash/v2 v2.3.0 // indirect - github.com/cncf/xds/go v0.0.0-20260202195803-dba9d589def2 // indirect - github.com/envoyproxy/go-control-plane/envoy v1.37.0 // indirect - github.com/envoyproxy/protoc-gen-validate v1.3.3 // indirect - github.com/felixge/httpsnoop v1.0.4 // indirect - github.com/go-jose/go-jose/v4 v4.1.4 // indirect - github.com/go-logr/logr v1.4.3 // indirect - github.com/go-logr/stdr v1.2.2 // indirect - github.com/google/s2a-go v0.1.9 // indirect - github.com/google/uuid v1.6.0 // indirect - github.com/googleapis/enterprise-certificate-proxy v0.3.11 // indirect - github.com/googleapis/gax-go/v2 v2.17.0 // indirect - github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10 // indirect - github.com/spiffe/go-spiffe/v2 v2.7.0 // indirect - go.opentelemetry.io/auto/sdk v1.2.1 // indirect - go.opentelemetry.io/contrib/detectors/gcp v1.44.0 // indirect - go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.61.0 // indirect - go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.61.0 // indirect - go.opentelemetry.io/otel v1.44.0 // indirect - go.opentelemetry.io/otel/metric v1.44.0 // indirect - go.opentelemetry.io/otel/sdk v1.44.0 // indirect - go.opentelemetry.io/otel/sdk/metric v1.44.0 // indirect - go.opentelemetry.io/otel/trace v1.44.0 // indirect - golang.org/x/crypto v0.52.0 // indirect - golang.org/x/net v0.55.0 // indirect - golang.org/x/oauth2 v0.36.0 // indirect - golang.org/x/sync v0.20.0 // indirect - golang.org/x/sys v0.45.0 // indirect - golang.org/x/text v0.37.0 // indirect - golang.org/x/time v0.14.0 // indirect - google.golang.org/api v0.264.0 // indirect - google.golang.org/genproto v0.0.0-20260128011058-8636f8732409 // indirect - google.golang.org/genproto/googleapis/api v0.0.0-20260526163538-3dc84a4a5aaa // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20260526163538-3dc84a4a5aaa // indirect - google.golang.org/grpc v1.83.1 // indirect - google.golang.org/protobuf v1.36.11 // indirect -) +go 1.27.0 diff --git a/code-samples/serving/secrets-go/go.sum b/code-samples/serving/secrets-go/go.sum deleted file mode 100644 index ad5355a235..0000000000 --- a/code-samples/serving/secrets-go/go.sum +++ /dev/null @@ -1,127 +0,0 @@ -cel.dev/expr v0.25.2 h1:K6j46C81hXtZQfuX60cVWQFBJahKSE2gfRbNuvr5bFs= -cel.dev/expr v0.25.2/go.mod h1:hrXvqGP6G6gyx8UAHSHJ5RGk//1Oj5nXQ2NI02Nrsg4= -cloud.google.com/go v0.121.6 h1:waZiuajrI28iAf40cWgycWNgaXPO06dupuS+sgibK6c= -cloud.google.com/go v0.121.6/go.mod h1:coChdst4Ea5vUpiALcYKXEpR1S9ZgXbhEzzMcMR66vI= -cloud.google.com/go/auth v0.18.2 h1:+Nbt5Ev0xEqxlNjd6c+yYUeosQ5TtEUaNcN/3FozlaM= -cloud.google.com/go/auth v0.18.2/go.mod h1:xD+oY7gcahcu7G2SG2DsBerfFxgPAJz17zz2joOFF3M= -cloud.google.com/go/auth/oauth2adapt v0.2.8 h1:keo8NaayQZ6wimpNSmW5OPc283g65QNIiLpZnkHRbnc= -cloud.google.com/go/auth/oauth2adapt v0.2.8/go.mod h1:XQ9y31RkqZCcwJWNSx2Xvric3RrU88hAYYbjDWYDL+c= -cloud.google.com/go/compute/metadata v0.9.0 h1:pDUj4QMoPejqq20dK0Pg2N4yG9zIkYGdBtwLoEkH9Zs= -cloud.google.com/go/compute/metadata v0.9.0/go.mod h1:E0bWwX5wTnLPedCKqk3pJmVgCBSM6qQI1yTBdEb3C10= -cloud.google.com/go/iam v1.5.3 h1:+vMINPiDF2ognBJ97ABAYYwRgsaqxPbQDlMnbHMjolc= -cloud.google.com/go/iam v1.5.3/go.mod h1:MR3v9oLkZCTlaqljW6Eb2d3HGDGK5/bDv93jhfISFvU= -cloud.google.com/go/logging v1.13.1 h1:O7LvmO0kGLaHY/gq8cV7T0dyp6zJhYAOtZPX4TF3QtY= -cloud.google.com/go/logging v1.13.1/go.mod h1:XAQkfkMBxQRjQek96WLPNze7vsOmay9H5PqfsNYDqvw= -cloud.google.com/go/longrunning v0.8.0 h1:LiKK77J3bx5gDLi4SMViHixjD2ohlkwBi+mKA7EhfW8= -cloud.google.com/go/longrunning v0.8.0/go.mod h1:UmErU2Onzi+fKDg2gR7dusz11Pe26aknR4kHmJJqIfk= -cloud.google.com/go/monitoring v1.24.3 h1:dde+gMNc0UhPZD1Azu6at2e79bfdztVDS5lvhOdsgaE= -cloud.google.com/go/monitoring v1.24.3/go.mod h1:nYP6W0tm3N9H/bOw8am7t62YTzZY+zUeQ+Bi6+2eonI= -cloud.google.com/go/storage v1.56.0 h1:iixmq2Fse2tqxMbWhLWC9HfBj1qdxqAmiK8/eqtsLxI= -cloud.google.com/go/storage v1.56.0/go.mod h1:Tpuj6t4NweCLzlNbw9Z9iwxEkrSem20AetIeH/shgVU= -cloud.google.com/go/trace v1.11.7 h1:kDNDX8JkaAG3R2nq1lIdkb7FCSi1rCmsEtKVsty7p+U= -cloud.google.com/go/trace v1.11.7/go.mod h1:TNn9d5V3fQVf6s4SCveVMIBS2LJUqo73GACmq/Tky0s= -github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.33.0 h1:l7+6kwRMJNwdCvYdDl7Eax+wzEYHSnNY7zrrfbhDdTA= -github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.33.0/go.mod h1:pJTkW8hEUIIi3Pf65lPZOnn4Y81yCllX6IWk2jNXdkM= -github.com/GoogleCloudPlatform/opentelemetry-operations-go/exporter/metric v0.53.0 h1:owcC2UnmsZycprQ5RfRgjydWhuoxg71LUfyiQdijZuM= -github.com/GoogleCloudPlatform/opentelemetry-operations-go/exporter/metric v0.53.0/go.mod h1:ZPpqegjbE99EPKsu3iUWV22A04wzGPcAY/ziSIQEEgs= -github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/cloudmock v0.53.0 h1:4LP6hvB4I5ouTbGgWtixJhgED6xdf67twf9PoY96Tbg= -github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/cloudmock v0.53.0/go.mod h1:jUZ5LYlw40WMd07qxcQJD5M40aUxrfwqQX1g7zxYnrQ= -github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/resourcemapping v0.53.0 h1:Ron4zCA/yk6U7WOBXhTJcDpsUBG9npumK6xw2auFltQ= -github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/resourcemapping v0.53.0/go.mod h1:cSgYe11MCNYunTnRXrKiR/tHc0eoKjICUuWpNZoVCOo= -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/cncf/xds/go v0.0.0-20260202195803-dba9d589def2 h1:aBangftG7EVZoUb69Os8IaYg++6uMOdKK83QtkkvJik= -github.com/cncf/xds/go v0.0.0-20260202195803-dba9d589def2/go.mod h1:qwXFYgsP6T7XnJtbKlf1HP8AjxZZyzxMmc+Lq5GjlU4= -github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= -github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/envoyproxy/go-control-plane v0.14.0 h1:hbG2kr4RuFj222B6+7T83thSPqLjwBIfQawTkC++2HA= -github.com/envoyproxy/go-control-plane v0.14.0/go.mod h1:NcS5X47pLl/hfqxU70yPwL9ZMkUlwlKxtAohpi2wBEU= -github.com/envoyproxy/go-control-plane/envoy v1.37.0 h1:u3riX6BoYRfF4Dr7dwSOroNfdSbEPe9Yyl09/B6wBrQ= -github.com/envoyproxy/go-control-plane/envoy v1.37.0/go.mod h1:DReE9MMrmecPy+YvQOAOHNYMALuowAnbjjEMkkWOi6A= -github.com/envoyproxy/go-control-plane/ratelimit v0.1.0 h1:/G9QYbddjL25KvtKTv3an9lx6VBE2cnb8wp1vEGNYGI= -github.com/envoyproxy/go-control-plane/ratelimit v0.1.0/go.mod h1:Wk+tMFAFbCXaJPzVVHnPgRKdUdwW/KdbRt94AzgRee4= -github.com/envoyproxy/protoc-gen-validate v1.3.3 h1:MVQghNeW+LZcmXe7SY1V36Z+WFMDjpqGAGacLe2T0ds= -github.com/envoyproxy/protoc-gen-validate v1.3.3/go.mod h1:TsndJ/ngyIdQRhMcVVGDDHINPLWB7C82oDArY51KfB0= -github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg= -github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= -github.com/go-jose/go-jose/v4 v4.1.4 h1:moDMcTHmvE6Groj34emNPLs/qtYXRVcd6S7NHbHz3kA= -github.com/go-jose/go-jose/v4 v4.1.4/go.mod h1:x4oUasVrzR7071A4TnHLGSPpNOm2a21K9Kf04k1rs08= -github.com/go-logr/logr v1.2.2/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/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= -github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= -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/martian/v3 v3.3.3 h1:DIhPTQrbPkgs2yJYdXU/eNACCG5DVQjySNRNlflZ9Fc= -github.com/google/martian/v3 v3.3.3/go.mod h1:iEPrYcgCF7jA9OtScMFQyAlZZ4YXTKEtJ1E6RWzmBA0= -github.com/google/s2a-go v0.1.9 h1:LGD7gtMgezd8a/Xak7mEWL0PjoTQFvpRudN895yqKW0= -github.com/google/s2a-go v0.1.9/go.mod h1:YA0Ei2ZQL3acow2O62kdp9UlnvMmU7kA6Eutn0dXayM= -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/enterprise-certificate-proxy v0.3.11 h1:vAe81Msw+8tKUxi2Dqh/NZMz7475yUvmRIkXr4oN2ao= -github.com/googleapis/enterprise-certificate-proxy v0.3.11/go.mod h1:RFV7MUdlb7AgEq2v7FmMCfeSMCllAzWxFgRdusoGks8= -github.com/googleapis/gax-go/v2 v2.17.0 h1:RksgfBpxqff0EZkDWYuz9q/uWsTVz+kf43LsZ1J6SMc= -github.com/googleapis/gax-go/v2 v2.17.0/go.mod h1:mzaqghpQp4JDh3HvADwrat+6M3MOIDp5YKHhb9PAgDY= -github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10 h1:GFCKgmp0tecUJ0sJuv4pzYCqS9+RGSn52M3FUwPs+uo= -github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10/go.mod h1:t/avpk3KcrXxUnYOhZhMXJlSEyie6gQbtLq5NM3loB8= -github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= -github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/spiffe/go-spiffe/v2 v2.7.0 h1:uXe1MflJoHw58wAUvxVlcM7WpKtijWG7I1UidcGh6g4= -github.com/spiffe/go-spiffe/v2 v2.7.0/go.mod h1:47Q0Q9/AqGha8QLHp+kxpH4Wca7X7EnOtlIJy3mxZ3U= -github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= -github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= -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/contrib/detectors/gcp v1.44.0 h1:NmLfL734pJhM0JKaYd2Y28+nY9dPRWYAAbxhRCrKXPw= -go.opentelemetry.io/contrib/detectors/gcp v1.44.0/go.mod h1:tNAsgd8avTGke1+MndXlU5Cru4PQ9Ai/cCNWQv/ZJ/s= -go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.61.0 h1:q4XOmH/0opmeuJtPsbFNivyl7bCt7yRBbeEm2sC/XtQ= -go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.61.0/go.mod h1:snMWehoOh2wsEwnvvwtDyFCxVeDAODenXHtn5vzrKjo= -go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.61.0 h1:F7Jx+6hwnZ41NSFTO5q4LYDtJRXBf2PD0rNBkeB/lus= -go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.61.0/go.mod h1:UHB22Z8QsdRDrnAtX4PntOl36ajSxcdUMt1sF7Y6E7Q= -go.opentelemetry.io/otel v1.44.0 h1:JjwHmHpA4iZ3wBxluu2fbbE7j4kqlE8jXyAyPXH7HqU= -go.opentelemetry.io/otel v1.44.0/go.mod h1:BMgjTHL9WPRlRjL2oZCBTL4whCGtXch2H4BhOPIAyYc= -go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v1.36.0 h1:rixTyDGXFxRy1xzhKrotaHy3/KXdPhlWARrCgK+eqUY= -go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v1.36.0/go.mod h1:dowW6UsM9MKbJq5JTz2AMVp3/5iW5I/TStsk8S+CfHw= -go.opentelemetry.io/otel/metric v1.44.0 h1:1w0gILTcHdr3YI+ixLyjemwrVnsMURbTZFrSYCdDdmc= -go.opentelemetry.io/otel/metric v1.44.0/go.mod h1:8O7hanEPBNgEMmybD3s2VBKcgWOCsA6tzHBPODAiquo= -go.opentelemetry.io/otel/metric/x v0.66.0 h1:YkCrx1zLOChi9ZcZ6euupOcsgzbVlec7D/xoEU1+cTA= -go.opentelemetry.io/otel/metric/x v0.66.0/go.mod h1:d1+BDj9t96do0/1LoU1ayfCv79ZgNE41qbhBvnMOBZk= -go.opentelemetry.io/otel/sdk v1.44.0 h1:nHYwb9lK+fJPU/dnT6s7W7Z8itMWyqrnVfbheVYrZ58= -go.opentelemetry.io/otel/sdk v1.44.0/go.mod h1:Osuydd3Se74nqjAKxid74N5eC+jfEqfTegHRnq58oK0= -go.opentelemetry.io/otel/sdk/metric v1.44.0 h1:3LlKgI+VjbVsjNRFZJZAJ30WjXC5VkNRks6si09iEfI= -go.opentelemetry.io/otel/sdk/metric v1.44.0/go.mod h1:5B5pMARnXxKhltooO4xUuCBorl65a4EpnTalObqOigA= -go.opentelemetry.io/otel/trace v1.44.0 h1:jxF5CsGYCe74MCRx2X4g7WsY/VBKRqqpNvXlX/6gtIk= -go.opentelemetry.io/otel/trace v1.44.0/go.mod h1:oLl1jrMQAVo6v3GAggN+1VH9VIz9iUSvW53sW1Q8PIE= -golang.org/x/crypto v0.52.0 h1:RMs7fP2rXdep0CftQlK8Uf+kibLm7qkCcradZWYz988= -golang.org/x/crypto v0.52.0/go.mod h1:1QgfPxDqh0T2M/elOJtp9RvuR95kVjir0e6/BvEmGbc= -golang.org/x/net v0.55.0 h1:bcvxaJn3e1U6InsFWt1JUq1aSjnRxLzT2rtD2KfkDF8= -golang.org/x/net v0.55.0/go.mod h1:L5U2KuzuOe1lY7Z+aWVIKK6qEeJXnXV9yzGA+WCHJww= -golang.org/x/oauth2 v0.36.0 h1:peZ/1z27fi9hUOFCAZaHyrpWG5lwe0RJEEEeH0ThlIs= -golang.org/x/oauth2 v0.36.0/go.mod h1:YDBUJMTkDnJS+A4BP4eZBjCqtokkg1hODuPjwiGPO7Q= -golang.org/x/sync v0.20.0 h1:e0PTpb7pjO8GAtTs2dQ6jYa5BWYlMuX047Dco/pItO4= -golang.org/x/sync v0.20.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0= -golang.org/x/sys v0.45.0 h1:dO4czNzziLiiXplLQgBCEpCvXQ3dnkn0SdaZSYdQ+FY= -golang.org/x/sys v0.45.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw= -golang.org/x/text v0.37.0 h1:Cqjiwd9eSg8e0QAkyCaQTNHFIIzWtidPahFWR83rTrc= -golang.org/x/text v0.37.0/go.mod h1:a5sjxXGs9hsn/AJVwuElvCAo9v8QYLzvavO5z2PiM38= -golang.org/x/time v0.14.0 h1:MRx4UaLrDotUKUdCIqzPC48t1Y9hANFKIRpNx+Te8PI= -golang.org/x/time v0.14.0/go.mod h1:eL/Oa2bBBK0TkX57Fyni+NgnyQQN4LitPmob2Hjnqw4= -gonum.org/v1/gonum v0.17.0 h1:VbpOemQlsSMrYmn7T2OUvQ4dqxQXU+ouZFQsZOx50z4= -gonum.org/v1/gonum v0.17.0/go.mod h1:El3tOrEuMpv2UdMrbNlKEh9vd86bmQ6vqIcDwxEOc1E= -google.golang.org/api v0.264.0 h1:+Fo3DQXBK8gLdf8rFZ3uLu39JpOnhvzJrLMQSoSYZJM= -google.golang.org/api v0.264.0/go.mod h1:fAU1xtNNisHgOF5JooAs8rRaTkl2rT3uaoNGo9NS3R8= -google.golang.org/genproto v0.0.0-20260128011058-8636f8732409 h1:VQZ/yAbAtjkHgH80teYd2em3xtIkkHd7ZhqfH2N9CsM= -google.golang.org/genproto v0.0.0-20260128011058-8636f8732409/go.mod h1:rxKD3IEILWEu3P44seeNOAwZN4SaoKaQ/2eTg4mM6EM= -google.golang.org/genproto/googleapis/api v0.0.0-20260526163538-3dc84a4a5aaa h1:Kjn0N0tCrDgiAFW+lGO4JZ3ck44CehvJQMAwj9QF0G8= -google.golang.org/genproto/googleapis/api v0.0.0-20260526163538-3dc84a4a5aaa/go.mod h1:q4lMZS6kskjT5HvCPrnnypcDPVJqT/f4nfxmkE7gryY= -google.golang.org/genproto/googleapis/rpc v0.0.0-20260526163538-3dc84a4a5aaa h1:mZHHdPZl0dbGHCflZgAq/Q468DWVFcU2whhB2KAo8fk= -google.golang.org/genproto/googleapis/rpc v0.0.0-20260526163538-3dc84a4a5aaa/go.mod h1:4Hqkh8ycfw05ld/3BWL7rJOSfebL2Q+DVDeRgYgxUU8= -google.golang.org/grpc v1.83.1 h1:HIO0+BEtBP6soyqvqC8sNUjZ7bTs+0hFQuFF+RAy++Y= -google.golang.org/grpc v1.83.1/go.mod h1:kDyl6SKsiHKt0uylY5gtn5cEjkrIOhQOGDgIc4JGwzQ= -google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE= -google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco= -gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= -gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/code-samples/serving/secrets-go/secret.yaml b/code-samples/serving/secrets-go/secret.yaml index e7805e42d7..17e30f480e 100644 --- a/code-samples/serving/secrets-go/secret.yaml +++ b/code-samples/serving/secrets-go/secret.yaml @@ -1,12 +1,9 @@ apiVersion: v1 kind: Secret metadata: - name: google-robot-secret + name: mounted-secret namespace: default - +type: Opaque stringData: - robot.json: | - You can place the body of the Google Service Account key file - here for use with this sample. Be careful that each line of - the JSON key file has been indented enough so that it remains - a part of the "robot.json" key's value. + # Sample tutorial data only. Never commit real secret values. + message: demonstration-only diff --git a/code-samples/serving/secrets-go/secrets.go b/code-samples/serving/secrets-go/secrets.go index 172834539e..b2a2879c7a 100644 --- a/code-samples/serving/secrets-go/secrets.go +++ b/code-samples/serving/secrets-go/secrets.go @@ -1,49 +1,41 @@ package main import ( - "context" + "bytes" "fmt" "log" "net/http" "os" - - "cloud.google.com/go/storage" ) -func main() { - log.Print("Secrets sample started.") - - // This sets up the standard GCS storage client, which will pull - // credentials from GOOGLE_APPLICATION_DEFAULT if specified. - ctx := context.Background() - client, err := storage.NewClient(ctx) - if err != nil { - log.Fatalf("Unable to initialize storage client: %v", err) +func envOrDefault(name, fallback string) string { + if value := os.Getenv(name); value != "" { + return value } + return fallback +} - http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { - // This GCS bucket has been configured so that any authenticated - // user can access it (Read Only), so any Service Account can - // run this sample. - bkt := client.Bucket("knative-secrets-sample") - - // Access the attributes of this GCS bucket, and write it back to the - // user. On failure, return a 500 and the error message. - attrs, err := bkt.Attrs(ctx) +func secretHandler(path string) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + // Reopen the file on every request so projected Secret updates are seen. + // Never include file contents, paths or underlying errors in the response. + value, err := os.ReadFile(path) if err != nil { - http.Error(w, err.Error(), http.StatusInternalServerError) + http.Error(w, "Secret file unavailable.", http.StatusServiceUnavailable) return } - fmt.Fprintln(w, - fmt.Sprintf("bucket %s, created at %s, is located in %s with storage class %s\n", - attrs.Name, attrs.Created, attrs.Location, attrs.StorageClass)) - - }) - - port := os.Getenv("PORT") - if port == "" { - port = "8080" + if len(bytes.TrimSpace(value)) == 0 { + http.Error(w, "Secret file is empty.", http.StatusServiceUnavailable) + return + } + w.Header().Set("Content-Type", "text/plain; charset=utf-8") + fmt.Fprintln(w, "Secret file loaded.") } +} - log.Fatal(http.ListenAndServe(fmt.Sprintf(":%s", port), nil)) +func main() { + path := envOrDefault("SECRET_FILE", "/var/secret/message") + port := envOrDefault("PORT", "8080") + log.Print("Mounted Secret sample started.") + log.Fatal(http.ListenAndServe(":"+port, secretHandler(path))) } diff --git a/code-samples/serving/secrets-go/service.yaml b/code-samples/serving/secrets-go/service.yaml index 7135afb6c2..ba1001126b 100644 --- a/code-samples/serving/secrets-go/service.yaml +++ b/code-samples/serving/secrets-go/service.yaml @@ -7,28 +7,15 @@ spec: template: spec: containers: - - image: github.com/knative/docs/code-samples/serving/secrets-go - env: - # This directs the Google Cloud SDK to use the identity and project - # defined by the Service Account (aka robot) in the JSON file at - # this path. - # - `/var/secret` is determined by the `volumeMounts[0].mountPath` - # below. This can be changed if both places are changed. - # - `robot.json` is determined by the "key" that is used to hold the - # secret content in the Kubernetes secret. This can be changed - # if both places are changed. - - name: GOOGLE_APPLICATION_DEFAULT - value: /var/secret/robot.json - - # This section specified where in the container we want the - # volume containing our secret to be mounted. + # Replace {username} with your container registry username. + - image: docker.io/{username}/secrets-go + # SECRET_FILE defaults to /var/secret/message. If you change the mount + # path or projected filename, set SECRET_FILE to the matching file path. volumeMounts: - - name: robot-secret + - name: demo-secret mountPath: /var/secret - - # This section attaches the secret "google-robot-secret" to - # the Pod holding the user container. + readOnly: true volumes: - - name: robot-secret + - name: demo-secret secret: - secretName: google-robot-secret + secretName: mounted-secret From 4e2a68f6759a4e5966d7b5d0b3dd2e4a323c9977 Mon Sep 17 00:00:00 2001 From: Dave Protasowski Date: Mon, 26 Oct 2026 09:16:05 -0400 Subject: [PATCH 39/44] Fix the tag-header routing sample instructions --- .../tag-header-based-routing/README.md | 49 ++++++++++++++++--- 1 file changed, 41 insertions(+), 8 deletions(-) diff --git a/code-samples/serving/tag-header-based-routing/README.md b/code-samples/serving/tag-header-based-routing/README.md index eb7dfd66f5..3545fe296f 100644 --- a/code-samples/serving/tag-header-based-routing/README.md +++ b/code-samples/serving/tag-header-based-routing/README.md @@ -15,10 +15,10 @@ Currently Istio, Contour and Kourier ingress support this feature. 1. A Knative cluster that has an ingress controller installed with Knative v0.16 and later. -1. Move into the docs directory: +1. Move into the root of your clone of the docs repository: ```bash - cd $GOPATH/src/github.com/knative/docs + cd /path/to/docs ``` ## Enabling tag header based routing @@ -34,7 +34,7 @@ kubectl patch cm config-features -n knative-serving -p '{"data":{"tag-header-bas Follow the instructions in [helloworld-go](../hello-world/helloworld-go/README.md) to build the `helloworld` image and upload it to your container repository. -Replace `{username}` in the [sample.yaml](sample.yaml) with your Docker Hub username. +Replace `{username}` in [sample.yaml](sample.yaml) with your Docker Hub username. ## Setting up the revisions with tag @@ -46,12 +46,26 @@ The Knative Service is configured to route all of the traffic to the second Revi provide the `Knative-Serving-Tag` or they provide an incorrect value for `Knative-Serving-Tag`, the requests will be routed to the second Revision. -Run the following command to set up the Knative Service and Revisions. +The two YAML documents in `sample.yaml` update the same Service. First, apply +only the first document and wait for its Revision to be ready: +```bash +sed '/^---$/q' code-samples/serving/tag-header-based-routing/sample.yaml | kubectl apply -f - +kubectl wait --for=condition=Ready ksvc/tag-header --timeout=180s ``` + +Then update the Service to create the second Revision and tag the first: + +```bash kubectl apply -f code-samples/serving/tag-header-based-routing/sample.yaml +kubectl wait --for=create revision/tag-header-revision-2 --timeout=180s +kubectl wait --for=condition=Ready revision/tag-header-revision-2 --timeout=180s +kubectl wait --for=condition=Ready ksvc/tag-header --timeout=180s ``` +Waiting before applying the complete manifest ensures the first Revision exists +before the second definition replaces the configuration and traffic refers to it. + ## Check the created resources Check the two created Revisions using the following command @@ -82,12 +96,23 @@ You should see the following block which indicates the tag `rev1` is successfull ## Sending request with tag header +Set `INGRESS_IP` to the reachable address of your ingress (including a port if +needed). Obtain the Host header from the Service URL instead of assuming a +particular cluster domain: + +```bash +SERVICE_URL=$(kubectl get ksvc tag-header -o jsonpath='{.status.url}') +SERVICE_HOST=${SERVICE_URL#*://} +``` + +The following examples use an HTTP ingress endpoint. + 1. Run the following command to send a request to the first Revision. ``` - curl ${INGRESS_IP} -H "Host:tag-header.default.example.com" -H "Knative-Serving-Tag:rev1" + curl "http://${INGRESS_IP}" -H "Host:${SERVICE_HOST}" -H "Knative-Serving-Tag:rev1" ``` - where `${INGRESS_IP}` is the IP of your ingress. + where `${INGRESS_IP}` is the address of your ingress. You should get the following response: @@ -98,7 +123,7 @@ You should see the following block which indicates the tag `rev1` is successfull 1. Run the following command to send requests without the `Knative-Serving-Tag` header: ``` - curl ${INGRESS_IP} -H "Host:tag-header.default.example.com" + curl "http://${INGRESS_IP}" -H "Host:${SERVICE_HOST}" ``` You should get the response from the second Revision: @@ -110,7 +135,7 @@ You should see the following block which indicates the tag `rev1` is successfull 1. Run the following command to send requests with an incorrect `Knative-Serving-Tag` header: ``` - curl ${INGRESS_IP} -H "Host:tag-header.default.example.com" -H "Knative-Serving-Tag:wrongHeader" + curl "http://${INGRESS_IP}" -H "Host:${SERVICE_HOST}" -H "Knative-Serving-Tag:wrongHeader" ``` You should get the response from the second Revision: @@ -118,3 +143,11 @@ You should see the following block which indicates the tag `rev1` is successfull ``` Hello Second Revision! ``` + +## Cleanup + +Delete the Service and its Revisions when finished: + +```bash +kubectl delete ksvc tag-header +``` From 2e13c2161c7c4ea77842182c8d17f07ba3288a2e Mon Sep 17 00:00:00 2001 From: Dave Protasowski Date: Mon, 26 Oct 2026 09:16:05 -0400 Subject: [PATCH 40/44] Update the Go WebSocket sample --- code-samples/serving/websockets-go/Dockerfile | 2 +- code-samples/serving/websockets-go/README.md | 122 ++++++------------ code-samples/serving/websockets-go/go.mod | 2 +- 3 files changed, 45 insertions(+), 81 deletions(-) diff --git a/code-samples/serving/websockets-go/Dockerfile b/code-samples/serving/websockets-go/Dockerfile index d5842feee4..9414ac101b 100644 --- a/code-samples/serving/websockets-go/Dockerfile +++ b/code-samples/serving/websockets-go/Dockerfile @@ -1,6 +1,6 @@ # Use the official Golang image to create a build artifact. # This is based on Debian and sets the GOPATH to /go. -FROM golang:latest AS builder +FROM golang:1.27 AS builder ARG TARGETOS ARG TARGETARCH diff --git a/code-samples/serving/websockets-go/README.md b/code-samples/serving/websockets-go/README.md index 8ac6220c9d..982142e495 100644 --- a/code-samples/serving/websockets-go/README.md +++ b/code-samples/serving/websockets-go/README.md @@ -1,6 +1,6 @@ # WebSocket - Go -A simple [WebSocket](https://datatracker.ietf.org/doc/html/rfc6455) server that performs the HTTP upgrade and prints log messages on all standardized WebSocket events, such as `open`, `message`, `close` and `error`. The server is written in Golang and uses the [Gorilla WebSocket](github.com/gorilla/websocket) library. +A simple [WebSocket](https://datatracker.ietf.org/doc/html/rfc6455) server that performs the HTTP upgrade and prints log messages on all standardized WebSocket events, such as `open`, `message`, `close` and `error`. The server is written in Golang and uses the [Gorilla WebSocket](https://github.com/gorilla/websocket) library. ## Before you begin @@ -10,75 +10,32 @@ A simple [WebSocket](https://datatracker.ietf.org/doc/html/rfc6455) server that - [ko](https://github.com/ko-build/ko) or [Docker](https://www.docker.com) installed and running on your local machine, and a Docker Hub account configured (we'll use it for a container registry). -## The sample code. - -1. If you look in `cmd/server/main.go`, you will the `main` function setting a `handleWebSocket` function and starting the web server on the `/ws` context: - - ```go - func main() { - http.HandleFunc("/ws", handleWebSocket) - fmt.Println("Starting server on :8080...") - if err := http.ListenAndServe(":8080", nil); err != nil { - log.Fatalf("Server error: %v", err) - } - } - ``` - -2. The `handleWebSocket` performs the protocol upgrade and assigns various websocket handler functions, such as `OnOpen` or `OnMessage`: - - ```go - func handleWebSocket(w http.ResponseWriter, r *http.Request) { - conn, err := upgrader.Upgrade(w, r, nil) - if err != nil { - log.Printf("Error upgrading to websocket: %v", err) - return - } - handlers.OnOpen(conn) - - go func() { - defer handlers.OnClose(conn) - for { - messageType, message, err := conn.ReadMessage() - if err != nil { - handlers.OnError(conn, err) - break - } - handlers.OnMessage(conn, messageType, message) - } - }() - } - ``` - -3. The WebSocket application logic is located in the `pkg/handlers/handlers.go` file and contains callbacks for each WebSocket event: - - ```go - func OnOpen(conn *websocket.Conn) { - log.Printf("WebSocket connection opened: %v", conn.RemoteAddr()) - } - - func OnMessage(conn *websocket.Conn, messageType int, message []byte) { - log.Printf("Received message from %v: %s", conn.RemoteAddr(), string(message)) - - if err := conn.WriteMessage(messageType, message); err != nil { - log.Printf("Error sending message: %v", err) - } - } - - func OnClose(conn *websocket.Conn) { - log.Printf("WebSocket connection closed: %v", conn.RemoteAddr()) - conn.Close() - } - - func OnError(conn *websocket.Conn, err error) { - log.Printf("WebSocket error from %v: %v", conn.RemoteAddr(), err) - } - ``` +## The sample code + +The server listens on port `8080` at `/ws`; it does not read environment variables. +Knative reserves the `PORT` environment variable, so do not set it explicitly in +the Service manifest. +It echoes text and binary messages and accepts connections from any origin. This +permissive origin policy is for demonstration only; restrict allowed origins before +using this sample in production. + +1. In [`cmd/server/main.go`](cmd/server/main.go), `main` registers + `handleWebSocket` at `/ws` and starts the HTTP server on port `8080`. +2. In the same file, `handleWebSocket` upgrades the HTTP connection, calls + `OnOpen`, and reads messages in a goroutine. Each message is passed to + `OnMessage`; read errors invoke `OnError`, and the deferred `OnClose` closes + the connection. +3. [`pkg/handlers/handlers.go`](pkg/handlers/handlers.go) contains the callbacks. + They log connection events and echo each message with its original text or + binary message type. ## Build the application +Run the build and deployment commands from `code-samples/serving/websockets-go`. + ### Dockerfile - * If you look in `Dockerfile`, you will see a method for pulling in the dependencies and building a small Go container based on Alpine. You can build and push this to your registry of choice via: + * If you look in the [Dockerfile](Dockerfile), you will see a method for building the Go container. You can build and push this to your registry of choice via: ```bash # Build and push the container on your local machine. docker buildx build --platform linux/arm64,linux/amd64 -t "" --push . @@ -88,14 +45,14 @@ docker buildx build --platform linux/arm64,linux/amd64 -t "" --push . * You can use `ko` to build and push just the image with: ```bash -ko publish github.com/knative/docs/code-samples/serving/websockets-go +ko publish ./cmd/server ``` However, if you use `ko` for the next step, this is not necessary. ## Deploy the application ### yaml (with Dockerfile) - * If you look in `service.yaml`, take the `` name you used earlier and insert it into the `image:` field, then run: + * If you look in [`config/service.yaml`](config/service.yaml), take the `` name you used earlier and insert it into the `image:` field, then run: ```bash kubectl apply -f config/service.yaml ``` @@ -111,12 +68,17 @@ ko apply -f config/service.yaml Get the URL for your Service with: ```bash - kubectl get ksvc - NAME URL LATESTCREATED LATESTREADY READY REASON - websocket-server http://websockets-server.default.svc.cluster.local websockets-server-00001 websocket-server-00001 True + kubectl get ksvc websockets-server ``` -Now run a container with the [wscat](https://github.com/websockets/wscat) CLI and point it to the WebSocket application `ws://websocket-server.default.svc.cluster.local/ws`, like: +For example, the Service URL might be +`http://websockets-server.default.svc.cluster.local` (the domain depends on your +cluster configuration). Change `http://` to `ws://` (`https://` to `wss://` for TLS) +and append `/ws` to connect to the URL reported by your cluster. + +Alternatively, run a container inside the cluster with the +[wscat](https://github.com/websockets/wscat) CLI and use the Service's cluster-local +address: ```bash @@ -125,14 +87,16 @@ Now run a container with the [wscat](https://github.com/websockets/wscat) CLI an Afterward you can chat with the WebSocket server like: - ```bash - ```If you don't see a command prompt, try pressing enter. - ```connected (press CTRL+C to quit) - ```> Hello - ```< Hello - ```> + ```text + If you don't see a command prompt, try pressing enter. + Connected (press CTRL+C to quit) + > Hello + < Hello + > ``` -The above is scaling to exactly one pod, since only one client was connected. Since Knative Serving allows you a dynamic scalling, a certain number of concurrent connections lead to a number of pods. +Knative can scale the application based on concurrent connections. The number of +pods depends on autoscaling settings and traffic, not just the number of clients. ->> **NOTE:** Depending on the target annotation you have ([`autoscaling.knative.dev/target`](https://knative.dev/docs/serving/autoscaling/autoscaling-targets/)) you can scale based on num of connections. +> **NOTE:** The [`autoscaling.knative.dev/target`](https://knative.dev/docs/serving/autoscaling/autoscaling-targets/) +> annotation controls the target concurrency used for scaling. diff --git a/code-samples/serving/websockets-go/go.mod b/code-samples/serving/websockets-go/go.mod index a54f7fd80e..48d456e455 100644 --- a/code-samples/serving/websockets-go/go.mod +++ b/code-samples/serving/websockets-go/go.mod @@ -1,5 +1,5 @@ module github.com/knative/docs/code-samples/serving/websockets-go -go 1.22 +go 1.27.0 require github.com/gorilla/websocket v1.5.3 From fee5f0caf2c6ec5c800fbc45d7613741c45af191 Mon Sep 17 00:00:00 2001 From: Dave Protasowski Date: Mon, 26 Oct 2026 09:16:05 -0400 Subject: [PATCH 41/44] Remove obsolete shared sample tooling and duplicate files --- .../serving/helloworld-clojure/index.md | 154 ---------- .../serving/helloworld-dart/index.md | 109 ------- .../serving/helloworld-elixir/.gcloudignore | 24 -- .../serving/helloworld-elixir/index.md | 290 ------------------ .../serving/helloworld-haskell/index.md | 180 ----------- .../helloworld-java-micronaut/.dockerignore | 1 - .../helloworld-java-micronaut/index.md | 281 ----------------- .../helloworld-java-quarkus/.dockerignore | 11 - .../serving/helloworld-java-quarkus/index.md | 268 ---------------- .../serving/helloworld-rserver/index.md | 160 ---------- .../serving/helloworld-rust/index.md | 181 ----------- .../serving/helloworld-swift/index.md | 161 ---------- .../serving/helloworld-vertx/index.md | 249 --------------- .../cloudevents-nodejs/.dockerignore | 4 - .../helloworld-csharp/.dockerignore | 4 - .../helloworld-java-spring/.dockerignore | 1 - .../helloworld-nodejs/.dockerignore | 4 - .../hello-world/helloworld-php/.dockerignore | 3 - .../helloworld-python/.dockerignore | 6 - config/redirects.yml | 6 +- docs/versioned/samples/eventing.md | 5 +- docs/versioned/samples/serving.md | 4 +- test/flag.go | 49 --- test/sampleapp/config.go | 68 ---- test/sampleapp/config.yaml | 44 --- test/sampleapp/helloworld-java-spring.zip | Bin 53343 -> 0 bytes test/sampleconsistency/main.go | 17 - .../sampleconsistency_test.go | 147 --------- 28 files changed, 7 insertions(+), 2424 deletions(-) delete mode 100644 code-samples/community/serving/helloworld-clojure/index.md delete mode 100644 code-samples/community/serving/helloworld-dart/index.md delete mode 100644 code-samples/community/serving/helloworld-elixir/.gcloudignore delete mode 100644 code-samples/community/serving/helloworld-elixir/index.md delete mode 100644 code-samples/community/serving/helloworld-haskell/index.md delete mode 100644 code-samples/community/serving/helloworld-java-micronaut/.dockerignore delete mode 100644 code-samples/community/serving/helloworld-java-micronaut/index.md delete mode 100644 code-samples/community/serving/helloworld-java-quarkus/.dockerignore delete mode 100644 code-samples/community/serving/helloworld-java-quarkus/index.md delete mode 100644 code-samples/community/serving/helloworld-rserver/index.md delete mode 100644 code-samples/community/serving/helloworld-rust/index.md delete mode 100644 code-samples/community/serving/helloworld-swift/index.md delete mode 100644 code-samples/community/serving/helloworld-vertx/index.md delete mode 100644 code-samples/serving/cloudevents/cloudevents-nodejs/.dockerignore delete mode 100644 code-samples/serving/hello-world/helloworld-csharp/.dockerignore delete mode 100644 code-samples/serving/hello-world/helloworld-java-spring/.dockerignore delete mode 100644 code-samples/serving/hello-world/helloworld-nodejs/.dockerignore delete mode 100644 code-samples/serving/hello-world/helloworld-php/.dockerignore delete mode 100644 code-samples/serving/hello-world/helloworld-python/.dockerignore delete mode 100644 test/flag.go delete mode 100644 test/sampleapp/config.go delete mode 100644 test/sampleapp/config.yaml delete mode 100644 test/sampleapp/helloworld-java-spring.zip delete mode 100644 test/sampleconsistency/main.go delete mode 100644 test/sampleconsistency/sampleconsistency_test.go diff --git a/code-samples/community/serving/helloworld-clojure/index.md b/code-samples/community/serving/helloworld-clojure/index.md deleted file mode 100644 index ef0cd6b734..0000000000 --- a/code-samples/community/serving/helloworld-clojure/index.md +++ /dev/null @@ -1,154 +0,0 @@ -# Hello World - Clojure - -A simple web app written in Clojure that you can use for testing. It reads in an -env variable `TARGET` and prints "Hello \${TARGET}!". If TARGET is not -specified, it will use "World" as the TARGET. - -## Prerequisites - -- A Kubernetes cluster with Knative installed and DNS configured. Follow the - [Knative installation instructions](https://knative.dev/docs/install/) if you need to create - one. -- [Docker](https://www.docker.com) installed and running on your local machine, - and a Docker Hub account configured (we'll use it for a container registry). - -## Recreating the sample code - -While you can clone all of the code from this directory, hello world apps are -generally more useful if you build them step-by-step. The following instructions -recreate the source files from this folder. - -1. Create a new file named `src/helloworld/core.clj` and paste the following - code. This code creates a basic web server which listens on port 8080: - - ```clojure - (ns helloworld.core - (:use ring.adapter.jetty) - (:gen-class)) - - (defn handler [request] - {:status 200 - :headers {"Content-Type" "text/html"} - :body (str "Hello " - (if-let [target (System/getenv "TARGET")] - target - "World") - "!\n")}) - - (defn -main [& args] - (run-jetty handler {:port (if-let [port (System/getenv "PORT")] - (Integer/parseInt port) - 8080)})) - ``` - -1. In your project directory, create a file named `project.clj` and copy the - code below into it. This code defines the project's dependencies and - entrypoint. - - ```clojure - (defproject helloworld "1.0.0-SNAPSHOT" - :description "Hello World - Clojure sample" - :dependencies [[org.clojure/clojure "1.9.0"] - [ring/ring-core "1.6.3"] - [ring/ring-jetty-adapter "1.6.3"]] - :main helloworld.core) - ``` - -1. In your project directory, create a file named `Dockerfile` and copy the code - block below into it. For detailed instructions on dockerizing a Clojure app, - see - [the clojure image documentation](https://github.com/docker-library/docs/tree/master/clojure). - - ```docker - # Use the official Clojure image. - # https://hub.docker.com/_/clojure - FROM clojure - - # Create the project and download dependencies. - WORKDIR /usr/src/app - COPY project.clj . - RUN lein deps - - # Copy local code to the container image. - COPY . . - - # Build an uberjar release artifact. - RUN mv "$(lein uberjar | sed -n 's/^Created \(.*standalone\.jar\)/\1/p')" app-standalone.jar - - # Run the web service on container startup. - CMD ["java", "-jar", "app-standalone.jar"] - ``` - -1. Create a new file, `service.yaml` and copy the following service definition - into the file. Make sure to replace `{username}` with your Docker Hub - username. - - ```yaml - apiVersion: serving.knative.dev/v1 - kind: Service - metadata: - name: helloworld-clojure - namespace: default - spec: - template: - spec: - containers: - - image: docker.io/{username}/helloworld-clojure - env: - - name: TARGET - value: "Clojure Sample v1" - ``` - -## Building and deploying the sample - -Once you have recreated the sample code files (or used the files in the sample -folder) you're ready to build and deploy the sample app. - -1. Use Docker to build the sample code into a container. To build and push with - Docker Hub, run these commands replacing `{username}` with your Docker Hub - username: - - ```bash - # Build and push the container on your local machine. - docker buildx build --platform linux/arm64,linux/amd64 -t "{username}/helloworld-clojure" --push . - ``` - -1. After the build has completed and the container is pushed to docker hub, you - can deploy the app into your cluster. Ensure that the container image value - in `service.yaml` matches the container you built in the previous step. Apply - the configuration using `kubectl`: - - ```bash - kubectl apply --filename service.yaml - ``` - -1. Now that your service is created, Knative will perform the following steps: - - - Create a new immutable revision for this version of the app. - - Network programming to create a route, ingress, service, and load balance - for your app. - - Automatically scale your pods up and down (including to zero active pods). - -1. To find the URL for your service, use - - ``` - kubectl get ksvc helloworld-clojure --output=custom-columns=NAME:.metadata.name,URL:.status.url - NAME URL - helloworld-clojure http://helloworld-clojure.default.1.2.3.4.sslip.io - ``` - -1. Now you can make a request to your app and see the result. Replace - the URL below with the URL returned in the previous command. - - ```bash - curl http://helloworld-clojure.default.1.2.3.4.sslip.io - Hello World! - ``` - -## Removing the sample app deployment - -To remove the sample app from your cluster, delete the service record: - -```bash -kubectl delete --filename service.yaml -``` diff --git a/code-samples/community/serving/helloworld-dart/index.md b/code-samples/community/serving/helloworld-dart/index.md deleted file mode 100644 index ec42da4e65..0000000000 --- a/code-samples/community/serving/helloworld-dart/index.md +++ /dev/null @@ -1,109 +0,0 @@ -# Hello World - Dart - -A simple web app written in the [Dart](https://www.dart.dev) programming language -that you can use for testing. It reads in the env variable `TARGET` and prints -`"Hello $TARGET"`. If `TARGET` is not specified, it will use `"World"` as -`TARGET`. - -## Prerequisites - -- A Kubernetes cluster with Knative installed and DNS configured. Follow the - [Knative installation instructions](https://knative.dev/docs/install/) if - you need to create one. -- [Docker](https://www.docker.com) installed and running on your local machine, - and a Docker Hub account configured (we'll use it for a container registry). -- The [Dart SDK](https://dart.dev/get-dart) installed and - configured if you want to run the program locally. - -## Recreating the sample code - -While you can clone all of the code from this directory, we recommend you create -your hello world Dart application by using the `dart` developer tool. This takes -just a few steps: - -1. Create a new Dart app using the `server_shelf` template: - - ```shell - > dart create -t server-shelf helloworld-dart - ``` - -1. If you want to run locally, install dependencies. If you only want to run in - Docker or Knative, you can skip this step. - - ```shell - > dart pub get - ``` - -1. Create a new file, `service.yaml` and copy the following service definition - into the file. Make sure to replace `{username}` with your Docker Hub - username. - - ```yaml - apiVersion: serving.knative.dev/v1 - kind: Service - metadata: - name: helloworld-dart - namespace: default - spec: - template: - spec: - containers: - - image: docker.io/{username}/helloworld-dart - env: - - name: TARGET - value: "Dart Sample v1" - ``` - -## Building and deploying the sample - -Once you have recreated the sample code files (or used the files in the sample -folder) you're ready to build and deploy the sample app. - -1. Use Docker to build the sample code into a container. To build and push with - Docker Hub, run these commands replacing `{username}` with your Docker Hub - username: - - ```bash - # Build and push the container on your local machine. - docker buildx build --platform linux/arm64,linux/amd64 -t "{username}/helloworld-dart" --push . - ``` - -1. After the build has completed and the container is pushed to docker hub, you - can deploy the app into your cluster. Ensure that the container image value - in `service.yaml` matches the container you built in the previous step. Apply - the configuration using `kubectl`: - - ```bash - kubectl apply --filename service.yaml - ``` - -1. Now that your service is created, Knative will perform the following steps: - - - Create a new immutable revision for this version of the app. - - Network programming to create a route, ingress, service, and load balance - for your app. - - Automatically scale your pods up and down (including to zero active pods). - -1. To find the URL for your service, use - - ``` - kubectl get ksvc helloworld-dart --output=custom-columns=NAME:.metadata.name,URL:.status.url - NAME URL - helloworld-dart http://helloworld-dart.default.1.2.3.4.sslip.io - ``` - -1. Now you can make a request to your app and see the result. Replace - the URL below with the URL returned in the previous command. - - ```bash - curl http://helloworld-dart.default.1.2.3.4.sslip.io - Hello Dart Sample v1 - ``` - -## Removing the sample app deployment - -To remove the sample app from your cluster, delete the service record: - -```bash -kubectl delete --filename service.yaml -``` diff --git a/code-samples/community/serving/helloworld-elixir/.gcloudignore b/code-samples/community/serving/helloworld-elixir/.gcloudignore deleted file mode 100644 index 9d29dd2500..0000000000 --- a/code-samples/community/serving/helloworld-elixir/.gcloudignore +++ /dev/null @@ -1,24 +0,0 @@ -# For building with Google Cloud Build using the gcloud command line tool. -# Similar to .gitignore, except we want the /config/*.secrete.exs uploaded for the build. - -# App artifacts -/_build -/db -/deps -/*.ez - -# Generated on crash by the VM -erl_crash.dump - -# Generated on crash by NPM -npm-debug.log -/assets/package-lock.json - -# Static artifacts -/assets/node_modules - -# Since we are building assets from assets/, -# we ignore priv/static. You may want to comment -# this depending on your deployment strategy. -/priv/static/ - diff --git a/code-samples/community/serving/helloworld-elixir/index.md b/code-samples/community/serving/helloworld-elixir/index.md deleted file mode 100644 index 62ec1612ae..0000000000 --- a/code-samples/community/serving/helloworld-elixir/index.md +++ /dev/null @@ -1,290 +0,0 @@ -# Hello World - Elixir - -A simple web application written in [Elixir](https://elixir-lang.org/) using the -[Phoenix Framework](https://phoenixframework.org/). The application prints all -environment variables to the main page. - -## Set up Elixir and Phoenix Locally - -Following the -[Phoenix Installation Guide](https://hexdocs.pm/phoenix/installation.html) is -the best way to get your computer set up for developing, building, running, and -packaging Elixir Web applications. - -## Running Locally - -To start your Phoenix server: - -- Install dependencies with `mix deps.get` -- Install Node.js dependencies with `cd assets && npm install` -- Start Phoenix endpoint with `mix phx.server` - -Now you can visit [`localhost:4000`](http://localhost:4000) from your browser. - -## Recreating the sample code - -1. Generate a new project. - - ```bash - mix phoenix.new helloelixir - ``` - - When asked, if you want to `Fetch and install dependencies? [Yn]` select `y` - -1. Follow the direction in the output to change directories into start your - local server with `mix phoenix.server` - -1. In the new directory, create a new Dockerfile for packaging your application - for deployment - - ```docker - # Start from a base image for elixir - # Phoenix works best on pre 1.7 at the moment. - FROM elixir:1.6.6-alpine - - # Set up Elixir and Phoenix - ARG APP_NAME=hello - ARG PHOENIX_SUBDIR=. - ENV MIX_ENV=prod REPLACE_OS_VARS=true TERM=xterm - WORKDIR /opt/app - - # Update nodejs, rebar, and hex. - RUN apk update \ - && apk --no-cache --update add nodejs nodejs-npm \ - && mix local.rebar --force \ - && mix local.hex --force - COPY . . - - # Download and compile dependencies, then compile Web app. - RUN mix do deps.get, deps.compile, compile - RUN cd ${PHOENIX_SUBDIR}/assets \ - && npm install \ - && ./node_modules/brunch/bin/brunch build -p \ - && cd .. \ - && mix phx.digest - - # Create a release version of the application - RUN mix release --env=prod --verbose \ - && mv _build/prod/rel/${APP_NAME} /opt/release \ - && mv /opt/release/bin/${APP_NAME} /opt/release/bin/start_server - - # Prepare final layer - FROM alpine:latest - RUN apk update && apk --no-cache --update add bash openssl-dev ca-certificates - - # Add a user so the server will run as a non-root user. - RUN addgroup -g 1000 appuser && \ - adduser -S -u 1000 -G appuser appuser - # Pre-create necessary temp directory for erlang and set permissions. - RUN mkdir -p /opt/app/var - RUN chown appuser /opt/app/var - # Run everything else as 'appuser' - USER appuser - - ENV MIX_ENV=prod REPLACE_OS_VARS=true - WORKDIR /opt/app - COPY --from=0 /opt/release . - ENV RUNNER_LOG_DIR /var/log - - # Command to execute the application. - CMD ["/opt/app/bin/start_server", "foreground", "boot_var=/tmp"] - ``` - -1. Create a new file, `service.yaml` and copy the following Service definition - into the file. Make sure to replace `{username}` with your Docker Hub - username. - - ```yaml - apiVersion: serving.knative.dev/v1 - kind: Service - metadata: - name: helloworld-elixir - namespace: default - spec: - template: - spec: - containers: - - image: docker.io/{username}/helloworld-elixir - env: - - name: TARGET - value: "elixir Sample v1" - ``` - -## Building and deploying the sample - -The sample in this directory is ready to build and deploy without changes. You -can deploy the sample as is, or use you created version following the directions -above. - -1. Generate a new `secret_key_base` in the `config/prod.secret.exs` file. - Phoenix applications use a secrets file on production deployments and, by - default, that file is not checked into source control. We have provides - shell of an example on `config/prod.secret.exs.sample` and you can use the - following command to generate a new prod secrets file. - - ```bash - SECRET_KEY_BASE=$(elixir -e ":crypto.strong_rand_bytes(48) |> Base.encode64 |> IO.puts") - sed "s|SECRET+KEY+BASE|$SECRET_KEY_BASE|" config/prod.secret.exs.sample >config/prod.secret.exs - ``` - -1. Use Docker to build the sample code into a container. To build and push with - Docker Hub, run these commands replacing `{username}` with your Docker Hub - username: - - ```bash - # Build and push the container on your local machine. - docker buildx build --platform linux/arm64,linux/amd64 -t "{username}/helloworld-elixir" --push . - ``` - -1. After the build has completed and the container is pushed to docker hub, you - can deploy the app into your cluster. Ensure that the container image value - in `service.yaml` matches the container you built in the previous step. - Apply the configuration using `kubectl`: - - ```bash - kubectl apply --filename service.yaml - ``` - -1. Now that your service is created, Knative will perform the following steps: - - - Create a new immutable revision for this version of the app. - - Network programming to create a route, ingress, service, and load balance - for your app. - - Automatically scale your pods up and down (including to zero active pods). - -1. To find the URL for your service, use - - ``` - kubectl get ksvc helloworld-elixir --output=custom-columns=NAME:.metadata.name,URL:.status.url - - NAME URL - helloworld-elixir http://helloworld-elixir.default.1.2.3.4.sslip.io - ``` - -1. Now you can make a request to your app to see the results. Replace - `{IP_ADDRESS}` with the address you see returned in the previous step. - - ```bash - curl http://helloworld-elixir.default.1.2.3.4.sslip.io - - ... - # HTML from your application is returned. - ``` - - Here is the HTML returned from our deployed sample application: - - ```HTML - - - - - - - - - - Hello Knative - - - - -
-
- -
- - - - -
- -
-

Welcome to Knative and Elixir

- -

$TARGET = elixir Sample v1

-
- -

Environment

-
    -
  • BINDIR = /opt/app/erts-9.3.2/bin
  • -
  • DEST_SYS_CONFIG_PATH = /opt/app/var/sys.config
  • -
  • DEST_VMARGS_PATH = /opt/app/var/vm.args
  • -
  • DISTILLERY_TASK = foreground
  • -
  • EMU = beam
  • -
  • ERL_LIBS = /opt/app/lib
  • -
  • ERL_OPTS =
  • -
  • ERTS_DIR = /opt/app/erts-9.3.2
  • -
  • ERTS_LIB_DIR = /opt/app/erts-9.3.2/../lib
  • -
  • ERTS_VSN = 9.3.2
  • -
  • HELLOWORLD_ELIXIR_00001_SERVICE_PORT = tcp://10.35.241.50:80
  • -
  • HELLOWORLD_ELIXIR_00001_SERVICE_PORT_80_TCP = tcp://10.35.241.50:80
  • -
  • HELLOWORLD_ELIXIR_00001_SERVICE_PORT_80_TCP_ADDR = 10.35.241.50
  • -
  • HELLOWORLD_ELIXIR_00001_SERVICE_PORT_80_TCP_PORT = 80
  • -
  • HELLOWORLD_ELIXIR_00001_SERVICE_PORT_80_TCP_PROTO = tcp
  • -
  • HELLOWORLD_ELIXIR_00001_SERVICE_SERVICE_HOST = 10.35.241.50
  • -
  • HELLOWORLD_ELIXIR_00001_SERVICE_SERVICE_PORT = 80
  • -
  • HELLOWORLD_ELIXIR_00001_SERVICE_SERVICE_PORT_HTTP = 80
  • -
  • HELLOWORLD_ELIXIR_PORT = tcp://10.35.253.90:80
  • -
  • HELLOWORLD_ELIXIR_PORT_80_TCP = tcp://10.35.253.90:80
  • -
  • HELLOWORLD_ELIXIR_PORT_80_TCP_ADDR = 10.35.253.90
  • -
  • HELLOWORLD_ELIXIR_PORT_80_TCP_PORT = 80
  • -
  • HELLOWORLD_ELIXIR_PORT_80_TCP_PROTO = tcp
  • -
  • HELLOWORLD_ELIXIR_SERVICE_HOST = 10.35.253.90
  • -
  • HELLOWORLD_ELIXIR_SERVICE_PORT = 80
  • -
  • HELLOWORLD_ELIXIR_SERVICE_PORT_HTTP = 80
  • -
  • HOME = /root
  • -
  • HOSTNAME = helloworld-elixir-00001-deployment-84f68946b4-76hcv
  • -
  • KUBERNETES_PORT = tcp://10.35.240.1:443
  • -
  • KUBERNETES_PORT_443_TCP = tcp://10.35.240.1:443
  • -
  • KUBERNETES_PORT_443_TCP_ADDR = 10.35.240.1
  • -
  • KUBERNETES_PORT_443_TCP_PORT = 443
  • -
  • KUBERNETES_PORT_443_TCP_PROTO = tcp
  • -
  • KUBERNETES_SERVICE_HOST = 10.35.240.1
  • -
  • KUBERNETES_SERVICE_PORT = 443
  • -
  • KUBERNETES_SERVICE_PORT_HTTPS = 443
  • -
  • LD_LIBRARY_PATH = /opt/app/erts-9.3.2/lib:
  • -
  • MIX_ENV = prod
  • -
  • NAME = hello@127.0.0.1
  • -
  • NAME_ARG = -name hello@127.0.0.1
  • -
  • NAME_TYPE = -name
  • -
  • OLDPWD = /opt/app
  • -
  • OTP_VER = 20
  • -
  • PATH = /opt/app/erts-9.3.2/bin:/opt/app/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
  • -
  • PORT = 8080
  • -
  • PROGNAME = opt/app/releases/0.0.1/hello.sh
  • -
  • PWD = /opt/app
  • -
  • RELEASES_DIR = /opt/app/releases
  • -
  • RELEASE_CONFIG_DIR = /opt/app
  • -
  • RELEASE_ROOT_DIR = /opt/app
  • -
  • REL_NAME = hello
  • -
  • REL_VSN = 0.0.1
  • -
  • REPLACE_OS_VARS = true
  • -
  • ROOTDIR = /opt/app
  • -
  • RUNNER_LOG_DIR = /var/log
  • -
  • RUN_ERL_ENV =
  • -
  • SHLVL = 1
  • -
  • SRC_SYS_CONFIG_PATH = /opt/app/releases/0.0.1/sys.config
  • -
  • SRC_VMARGS_PATH = /opt/app/releases/0.0.1/vm.args
  • -
  • SYS_CONFIG_PATH = /opt/app/var/sys.config
  • -
  • TARGET = elixir Sample v1
  • -
  • TERM = xterm
  • -
  • VMARGS_PATH = /opt/app/var/vm.args
  • -
-
- -
- - - - - ``` - -## Removing the sample app deployment - -To remove the sample app from your cluster, delete the service record: - -```bash -kubectl delete --filename service.yaml -``` diff --git a/code-samples/community/serving/helloworld-haskell/index.md b/code-samples/community/serving/helloworld-haskell/index.md deleted file mode 100644 index fa31df862d..0000000000 --- a/code-samples/community/serving/helloworld-haskell/index.md +++ /dev/null @@ -1,180 +0,0 @@ -# Hello World - Haskell - -A simple web app written in Haskell that you can use for testing. It reads in an -env variable `TARGET` and prints "Hello \${TARGET}!". If TARGET is not -specified, it will use "World" as the TARGET. - -## Prerequisites - -- A Kubernetes cluster with Knative installed and DNS configured. Follow the - [Knative installation instructions](https://knative.dev/docs/install/) if you need to create - one. -- [Docker](https://www.docker.com) installed and running on your local machine, - and a Docker Hub account configured (we'll use it for a container registry). - -## Recreating the sample code - -While you can clone all of the code from this directory, hello world apps are -generally more useful if you build them step-by-step. The following instructions -recreate the source files from this folder. - -1. Create a new file named `stack.yaml` and paste the following code: - - ```yaml - flags: {} - packages: - - . - extra-deps: [] - resolver: lts-10.7 - ``` - -1. Create a new file named `package.yaml` and paste the following code - - ```yaml - name: helloworld-haskell - version: 0.1.0.0 - dependencies: - - base >= 4.7 && < 5 - - scotty - - text - - executables: - helloworld-haskell-exe: - main: Main.hs - source-dirs: app - ghc-options: - - -threaded - - -rtsopts - - -with-rtsopts=-N - ``` - -1. Create a `app` folder, then create a new file named `Main.hs` in that folder - and paste the following code. This code creates a basic web server which - listens on port 8080: - - ```haskell - {-# LANGUAGE OverloadedStrings #-} - - import Data.Maybe - import Data.Monoid ((<>)) - import Data.Text.Lazy (Text) - import Data.Text.Lazy - import System.Environment (lookupEnv) - import Web.Scotty (ActionM, ScottyM, scotty) - import Web.Scotty.Trans - - main :: IO () - main = do - t <- fromMaybe "World" <$> lookupEnv "TARGET" - pStr <- fromMaybe "8080" <$> lookupEnv "PORT" - let p = read pStr :: Int - scotty p (route t) - - route :: String -> ScottyM() - route t = get "/" $ hello t - - hello :: String -> ActionM() - hello t = text $ pack ("Hello " ++ t) - ``` - -1. In your project directory, create a file named `Dockerfile` and copy the code - block below into it. - - ```docker - # Use the official Haskell image to create a build artifact. - # https://hub.docker.com/_/haskell/ - FROM haskell:8.2.2 as builder - - # Copy local code to the container image. - WORKDIR /app - COPY . . - - # Build and test our code, then build the “helloworld-haskell-exe” executable. - RUN stack setup - RUN stack build --copy-bins - - # Use a Docker multi-stage build to create a lean production image. - # https://docs.docker.com/develop/develop-images/multistage-build/#use-multi-stage-builds - FROM fpco/haskell-scratch:integer-gmp - - # Copy the "helloworld-haskell-exe" executable from the builder stage to the production image. - WORKDIR /root/ - COPY --from=builder /root/.local/bin/helloworld-haskell-exe . - - # Run the web service on container startup. - CMD ["./helloworld-haskell-exe"] - ``` - -1. Create a new file, `service.yaml` and copy the following service definition - into the file. Make sure to replace `{username}` with your Docker Hub - username. - - ```yaml - apiVersion: serving.knative.dev/v1 - kind: Service - metadata: - name: helloworld-haskell - namespace: default - spec: - template: - spec: - containers: - - image: docker.io/{username}/helloworld-haskell - env: - - name: TARGET - value: "Haskell Sample v1" - ``` - -## Build and deploy this sample - -Once you have recreated the sample code files (or used the files in the sample -folder) you're ready to build and deploy the sample app. - -1. Use Docker to build the sample code into a container. To build and push with - Docker Hub, enter these commands replacing `{username}` with your Docker Hub - username: - - ```bash - # Build and push the container on your local machine. - docker buildx build --platform linux/arm64,linux/amd64 -t "{username}/helloworld-haskell" --push . - ``` - -1. After the build has completed and the container is pushed to Docker Hub, you - can deploy the app into your cluster. Ensure that the container image value - in `service.yaml` matches the container you built in the previous step. Apply - the configuration using `kubectl`: - - ```bash - kubectl apply --filename service.yaml - ``` - -1. Now that your service is created, Knative will perform the following steps: - - - Create a new immutable revision for this version of the app. - - Network programming to create a route, ingress, service, and load balance - for your app. - - Automatically scale your pods up and down (including to zero active pods). - -1. To find the URL for your service, enter: - - ``` - kubectl get ksvc helloworld-haskell --output=custom-columns=NAME:.metadata.name,URL:.status.url - NAME URL - helloworld-haskell http://helloworld-haskell.default.1.2.3.4.sslip.io - ``` - -1. Now you can make a request to your app and see the result. Replace - the URL below with the URL returned in the previous command. - - ```bash - curl http://helloworld-haskell.default.1.2.3.4.sslip.io - Hello world: Haskell Sample v1 - ``` - -## Removing the sample app deployment - -To remove the sample app from your cluster, delete the service record: - -```bash -kubectl delete --filename service.yaml -``` diff --git a/code-samples/community/serving/helloworld-java-micronaut/.dockerignore b/code-samples/community/serving/helloworld-java-micronaut/.dockerignore deleted file mode 100644 index 2f7896d1d1..0000000000 --- a/code-samples/community/serving/helloworld-java-micronaut/.dockerignore +++ /dev/null @@ -1 +0,0 @@ -target/ diff --git a/code-samples/community/serving/helloworld-java-micronaut/index.md b/code-samples/community/serving/helloworld-java-micronaut/index.md deleted file mode 100644 index 20ef5e25d1..0000000000 --- a/code-samples/community/serving/helloworld-java-micronaut/index.md +++ /dev/null @@ -1,281 +0,0 @@ -# Hello World - Java (Micronaut) - -Learn how to deploy a simple web app that is written in Java and uses Micronaut. - -This samples uses Docker to build locally. The app reads in a `TARGET` env -variable and then prints "Hello World: \${TARGET}!". If a value for `TARGET` is -not specified, the "NOT SPECIFIED" default value is used. - -Use this sample to walk you through the steps of creating and modifying the -sample app, building and pushing your container image to a registry, and then -deploying your app to your Knative cluster. - -## Before you begin - -You must meet the following requirements to complete this sample: - -- A version of the Knative Serving component installed and DNS configured. Follow the - [Knative installation instructions](https://knative.dev/docs/install/) if you need - to create a Knative cluster. -- The following software downloaded and install on your loacal machine: - - [Java SE 8 or later JDK](http://www.oracle.com/technetwork/java/javase/downloads/index.html). - - [Micronaut 1.1](https://micronaut.io/). - - [Docker](https://www.docker.com) for building and pushing your container - image. - - [curl](https://curl.haxx.se/) to test the sample app after deployment. -- A [Docker Hub](https://hub.docker.com/) account where you can push your - container image. - -**Tip**: You can clone the [Knatve/docs repo](https://github.com/knative/docs) -and then modify the source files. Alternatively, learn more by manually creating -the files yourself. - -## Creating and configuring the sample code - -To create and configure the source files in the root of your working directory: - -1. Create the `pom.xml` file: - - ```xml - 4.0.0 - com.example.micronaut - helloworld - 1.0.0-SNAPSHOT - - 1.1.0 - 1.8 - UTF-8 - com.example.helloworld.Application - - - - - io.micronaut - micronaut-bom - ${micronaut.version} - pom - import - - - - - - io.micronaut - micronaut-inject - compile - - - io.micronaut - micronaut-validation - compile - - - io.micronaut - micronaut-runtime - compile - - - io.micronaut - micronaut-http-client - compile - - - io.micronaut - micronaut-http-server-netty - compile - - - ch.qos.logback - logback-classic - 1.2.3 - runtime - - - - - - - org.apache.maven.plugins - maven-shade-plugin - 3.1.0 - - - package - - shade - - - - - ${exec.mainClass} - - - - - - - - - -
- ``` - -1. Create the `HelloWorldController.java` file in the - `src/main/java/com/example/helloworld` directory. The - `[ROOT]/src/main/java/com/example/helloworld/HelloWorldController.java` file - handles requests to the root URI `/`. - - ```java - package com.example.helloworld; - - import io.micronaut.http.MediaType; - import io.micronaut.http.annotation.Controller; - import io.micronaut.http.annotation.Get; - - @Controller("/") - public class HelloWorldController { - - @Get(value = "/", produces = MediaType.TEXT_PLAIN) - public String index() { - String target = System.getenv("TARGET"); - if (target == null) { - target = "NOT SPECIFIED"; - } - return "Hello World: " + target; - } - } - ``` - -1. The Micronaut application is configured via - `src/main/resources/application.yml`: - - ```yaml - micronaut: - application: - name: helloworld-java-micronaut - server: - port: ${PORT:8080} - ``` - -1. Create the `Dockerfile` file: - - ```docker - # Use the official maven/Java 8 image to create a build artifact. - # https://hub.docker.com/_/maven - FROM maven:3.5-jdk-8-alpine as builder - - # Copy local code to the container image. - WORKDIR /app - COPY pom.xml . - COPY src ./src - - # Build a release artifact. - RUN mvn package -DskipTests - - # Use AdoptOpenJDK for base image. - # It's important to use OpenJDK 8u191 or above that has container support enabled. - # https://hub.docker.com/r/adoptopenjdk/openjdk8 - # https://docs.docker.com/develop/develop-images/multistage-build/#use-multi-stage-builds - FROM adoptopenjdk/openjdk8:jdk8u202-b08-alpine-slim - - # Copy the jar to the production image from the builder stage. - COPY --from=builder /app/target/helloworld-1.0.0-SNAPSHOT.jar /helloworld.jar - - # Run the web service on container startup. - CMD ["java","-jar","/helloworld.jar"] - ``` - -1. Create the `service.yaml` file. You must specify your Docker Hub username in - `{username}`. You can also configure the `TARGET`, for example you can modify - the `Micronaut Sample v1` value. - - ```yaml - apiVersion: serving.knative.dev/v1 - kind: Service - metadata: - name: helloworld-java-micronaut - namespace: default - spec: - template: - spec: - containers: - - image: docker.io/{username}/helloworld-java-micronaut - env: - - name: TARGET - value: "Micronaut Sample v1" - ``` - -## Building and deploying the sample - -To build a container image, push your image to the registry, and then deploy -your sample app to your cluster: - -1. Use Docker to build your container image and then push that image to your - Docker Hub registry. You must replace the `{username}` variables in the - following commands with your Docker Hub username. - - ```bash - # Build and push the container on your local machine. - docker buildx build --platform linux/arm64,linux/amd64 -t "{username}/helloworld-java-micronaut" --push . - ``` - -1. Now that your container image is in the registry, you can deploy it to your - Knative cluster by running the `kubectl apply` command: - - ```bash - kubectl apply --filename service.yaml - ``` - - Result: A service name `helloworld-java-micronaut` is created in your cluster - along with the following resources: - - - A new immutable revision for the version of the app that you just deployed. - - The following networking resources are created for your app: - - route - - ingress - - service - - load balancer - - Auto scaling is enable to allow your pods to scale up to meet traffic, and - also back down to zero when there is no traffic. - -## Testing the sample app - -To verify that your sample app has been successfully deployed: - -1. Retrieve the URL for your service, by running the following `kubectl get` - command: - - ```bash - kubectl get ksvc helloworld-java-micronaut --output=custom-columns=NAME:.metadata.name,URL:.status.url - ``` - - Example result: - - ```bash - NAME URL - helloworld-java-micronaut http://helloworld-java-micronaut.default.1.2.3.4.sslip.io - ``` - -1. Now you can make a request to your app and see the result. Replace - the URL below with the URL returned in the previous command. - - ```bash - curl http://helloworld-java-micronaut.default.1.2.3.4.sslip.io - ``` - - Example result: - - ```bash - Hello World: Micronaut Sample v1 - ``` - -Congratulations on deploying your sample Java app to Knative! - -## Removing the sample app deployment - -To remove the sample app from your cluster, run the following `kubectl delete` -command: - -```bash -kubectl delete --filename service.yaml -``` diff --git a/code-samples/community/serving/helloworld-java-quarkus/.dockerignore b/code-samples/community/serving/helloworld-java-quarkus/.dockerignore deleted file mode 100644 index eb1e379988..0000000000 --- a/code-samples/community/serving/helloworld-java-quarkus/.dockerignore +++ /dev/null @@ -1,11 +0,0 @@ -README.md -_index.md -.editorconfig -.gitignore -.git -target/ -.settings -.idea -.project -.classpath -*.iml diff --git a/code-samples/community/serving/helloworld-java-quarkus/index.md b/code-samples/community/serving/helloworld-java-quarkus/index.md deleted file mode 100644 index 1e5ea02651..0000000000 --- a/code-samples/community/serving/helloworld-java-quarkus/index.md +++ /dev/null @@ -1,268 +0,0 @@ -# Hello World - Java (Quarkus) - -A simple [JAX-RS REST API](https://github.com/jax-rs) application that is -written in Java and uses [Quarkus](https://quarkus.io/). - -This samples uses Docker to build locally. The app reads in a `TARGET` env -variable and then prints "Hello World: \${TARGET}!". If a value for `TARGET` is -not specified, the "NOT SPECIFIED" default value is used. - -## Before you begin - -You must meet the following requirements to run this sample: - -- Have a Kubernetes cluster running with the Knative Serving component - installed. For more information, see the - [Knative installation instructions](https://knative.dev/docs/install/). -- An installed version of the following tools: - - [Docker](https://www.docker.com) - - [Java SE 8 or later JDK](https://www.eclipse.org/openj9/) - - [Maven](https://maven.apache.org/download.cgi) -- A [Docker Hub account](https://hub.docker.com/) to which you are able to - upload your sample's container image. - -## Getting the code - -You can either clone a working copy of the sample code from the repository, or -following the steps in the -[Recreating the sample code](#recreating-the-sample-code) to walk through the -steps of updating all the files. - -### Cloning the sample code - -Use this method to clone and then immediate run the sample. To clone the sample -code, run the following commands: - -```bash -git clone https://github.com/knative/docs.git knative/docs -cd knative/docs/code-samples/community/serving/helloworld-java-quarkus -``` - -You are now ready to [run the sample locally](#locally-testing-your-sample). - -### Recreating the sample code - -Use the following steps to obtain an incomplete copy of the sample code for -which you update and create the necessary build and configuration files: - -1. From the console, create a new empty web project using the Maven archetype - commands: - - ```bash - mvn io.quarkus:quarkus-maven-plugin:0.13.3:create \ - -DprojectGroupId=com.redhat.developer.demos \ - -DprojectArtifactId=helloworld-java-quarkus \ - -DclassName="com.redhat.developer.demos.GreetingResource" \ - -Dpath="/" - ``` - -1. Update the `GreetingResource` class in - `src/main/java/com/redhat/developer/demos/GreetingResource.java` to handle - the "/" mapping and also add a `@ConfigProperty` field to provide the TARGET - environment variable: - - ```java - package com.redhat.developer.demos; - - import javax.ws.rs.GET; - import javax.ws.rs.Path; - import javax.ws.rs.Produces; - import javax.ws.rs.core.MediaType; - import org.eclipse.microprofile.config.inject.ConfigProperty; - - @Path("/") - public class GreeterResource { - @ConfigProperty(name = "TARGET", defaultValue="World") - String target; - - @GET - @Produces(MediaType.TEXT_PLAIN) - public String greet() { - return "Hello " + target + "!"; - } - } - ``` - -1. Update `src/main/resources/application.properties` to configuration the - application to default to port 8080, but allow the port to be overridden by - the `PORT` environmental variable: - - ``` - # Configuration file - # key = value - - quarkus.http.port=${PORT:8080} - ``` - -1. Update `src/test/java/com/redhat/developer/demos/GreetingResourceTest.java` - test to reflect the change: - - ```java - package com.redhat.developer.demos; - - import io.quarkus.test.junit.QuarkusTest; - import org.junit.jupiter.api.Test; - - import static io.restassured.RestAssured.given; - import static org.hamcrest.CoreMatchers.is; - - @QuarkusTest - public class GreetingResourceTest { - - @Test - public void testHelloEndpoint() { - given() - .when().get("/") - .then() - .statusCode(200) - .body(is("Hello World!")); - } - } - - ``` - -1. Remove `src/main/resources/META-INF/resources/index.html` file since it's - unncessary for this example. - - ```bash - rm src/main/resources/META-INF/resources/index.html - ``` - -1. Remove `.dockerignore` file since it's unncessary for this example. - - ```bash - rm .dockerignore - ``` - -1. In your project directory, create a file named `Dockerfile` and copy the code - block below into it. - - ```docker - FROM quay.io/rhdevelopers/quarkus-java-builder:graal-1.0.0-rc15 as builder - COPY . /project - WORKDIR /project - # uncomment this to set the MAVEN_MIRROR_URL of your choice, to make faster builds - # ARG MAVEN_MIRROR_URL= - # e.g. - #ARG MAVEN_MIRROR_URL=http://192.168.64.1:8081/nexus/content/groups/public - - RUN /usr/local/bin/entrypoint-run.sh mvn -DskipTests clean package - - FROM fabric8/java-jboss-openjdk8-jdk:1.5.4 - USER jboss - ENV JAVA_APP_DIR=/deployments - - COPY --from=builder /project/target/lib/* /deployments/lib/ - COPY --from=builder /project/target/*-runner.jar /deployments/app.jar - - ENTRYPOINT [ "/deployments/run-java.sh" ] - ``` - - If you want to build Quarkus native image, then copy the following code block - in to file called `Dockerfile.native` - - ```docker - FROM quay.io/rhdevelopers/quarkus-java-builder:graal-1.0.0-rc15 as builder - COPY . /project - # uncomment this to set the MAVEN_MIRROR_URL of your choice, to make faster builds - # ARG MAVEN_MIRROR_URL= - # e.g. - # ARG MAVEN_MIRROR_URL=http://192.168.64.1:8081/nexus/content/groups/public - - RUN /usr/local/bin/entrypoint-run.sh mvn -DskipTests clean package -Pnative - - FROM registry.fedoraproject.org/fedora-minimal - - COPY --from=builder /project/target/helloworld-java-quarkus-runner /app - - ENTRYPOINT [ "/app" ] - ``` - -1. Create a new file, `service.yaml` and copy the following service definition - into the file. Make sure to replace `{username}` with your Docker Hub - username. - - ```yaml - apiVersion: serving.knative.dev/v1 - kind: Service - metadata: - name: helloworld-java-quarkus - spec: - template: - spec: - containers: - - image: docker.io/{username}/helloworld-java-quarkus - env: - - name: TARGET - value: "Quarkus Sample v1" - ``` - -## Locally testing your sample - -1. Run the application locally: - - ```bash - ./mvnw compile quarkus:dev - ``` - - Go to `http://localhost:8080/` to see your `Hello World!` message. - -## Building and deploying the sample - -Once you have recreated the sample code files (or used the files in the sample -folder) you're ready to build and deploy the sample app. - -1. Use Docker to build the sample code into a container. To build and push with - Docker Hub, run these commands replacing `{username}` with your Docker Hub - username: - - ```bash - # Build and push the container on your local machine. - docker buildx build --platform linux/arm64,linux/amd64 -t "{username}/helloworld-java-quarkus" --push . - - # (OR) - # Build and push the container on your local machine. - Quarkus native mode - docker buildx build --platform linux/arm64,linux/amd64 -t "{username}/helloworld-java-quarkus" --push . -f Dockerfile.native - ``` - -1. After the build has completed and the container is pushed to docker hub, you - can deploy the app into your cluster. Ensure that the container image value - in `service.yaml` matches the container you built in the previous step. Apply - the configuration using `kubectl`: - - ```bash - kubectl apply --filename service.yaml - ``` - -1. Now that your service is created, Knative will perform the following steps: - - - Create a new immutable revision for this version of the app. - - Network programming to create a route, ingress, service, and load balancer - for your app. - - Automatically scale your pods up and down (including to zero active pods). - -1. To find the URL for your service, use - - ```bash - kubectl get ksvc helloworld-java-quarkus - - NAME URL - helloworld-java-quarkus http://helloworld-java-quarkus.default.1.2.3.4.sslip.io - ``` - -1. Now you can make a request to your app and see the result. Replace - the URL below with the URL returned in the previous command. - - ```bash - curl http://helloworld-java-quarkus.default.1.2.3.4.sslip.io - - Namaste Knative World! - ``` - -## Removing the sample app deployment - -To remove the sample app from your cluster, delete the service record: - -```bash -kubectl delete --filename service.yaml -``` diff --git a/code-samples/community/serving/helloworld-rserver/index.md b/code-samples/community/serving/helloworld-rserver/index.md deleted file mode 100644 index 9f43b0d536..0000000000 --- a/code-samples/community/serving/helloworld-rserver/index.md +++ /dev/null @@ -1,160 +0,0 @@ -# Hello World - R server - -A simple web app created with R package, [plumber](https://www.rplumber.io). -plumber creates a REST API by adding annotations to your R code. The R script -reads an environment variable `TARGET` and prints `Hello ${TARGET}!`. If the -`TARGET` environment variable is not specified, the script uses `World`. - -Follow the steps below to create the sample code and then deploy the app to your -cluster. You can also download a working copy of the sample, by running the -following commands: - -```bash -git clone https://github.com/knative/docs.git knative-docs -cd knative-docs/code-samples/community/serving/hello-world/helloworld-r -``` - -## Before you begin - -- A Kubernetes cluster with Knative installed. Follow the - [Knative installation instructions](https://knative.dev/docs/install/) if you need to - create one. -- [Docker](https://www.docker.com) installed and running on your local machine, - and a Docker Hub account configured (we'll use it for a container registry). - -## Recreating the sample code - -1. Create a new file named `HelloWorld.R` and paste the following script: - - ```R - #' HelloWorld function - #' @get / - #' @html - function() { - TARGET <- Sys.getenv("TARGET", "World") - - message = paste("Hello ", TARGET, "!", sep = "") - print(message) - } - ``` - - This file defines the endpoint `/`, using plumber annotations. - -1. Create a new file named `server.R` and paste the following code: - - ```R - library(plumber) # https://www.rplumber.io/ - - # Translate the HelloWorld file into a Plumber API - r <- plumb("HelloWorld.R") - # Get the PORT env var - PORT <- strtoi(Sys.getenv("PORT", 8080)) - # Run the API - r$run(port=PORT, host="0.0.0.0") - ``` - -1. Create a new file named `Dockerfile` and paste the following code: - - ```docker - # The official R base image - # https://hub.docker.com/_/r-base - FROM r-base:3.6.0 - - # Copy local code to the container image. - WORKDIR /usr/src/app - COPY . . - - # Install R packages - RUN Rscript -e "install.packages('plumber', repos='http://cran.us.r-project.org/')" - - # Run the web service on container startup. - CMD ["Rscript", "server.R"] - ``` - - -1. Create a new file, `service.yaml` and copy the following service definition - into the file. Make sure to replace `{username}` with your Docker Hub - username. - - ```yaml - apiVersion: serving.knative.dev/v1 - kind: Service - metadata: - name: helloworld-rserver - namespace: default - spec: - template: - spec: - containers: - - image: docker.io/{username}/helloworld-rserver - env: - - name: TARGET - value: "R Server Sample v1" - ``` - -## Building and deploying the sample - -Once you have recreated the sample code files (or used the files in the sample -folder) you're ready to build and deploy the sample app. - -1. Use Docker to build the sample code into a container. To build and push with - Docker Hub, run these commands replacing `{username}` with your Docker Hub - username: - - ```bash - # Build and push the container on your local machine. - docker buildx build --platform linux/arm64,linux/amd64 -t "{username}/helloworld-rserver" --push . - ``` - -1. After the build has completed and the container is pushed to docker hub, you - can deploy the app into your cluster. Ensure that the container image value - in `service.yaml` matches the container you built in the previous step. Apply - the configuration using `kubectl`: - - ```bash - kubectl apply --filename service.yaml - ``` - -1. Now that your service is created, Knative performs the following steps: - - - Create a new immutable revision for this version of the app. - - Network programming to create a route, ingress, service, and load balance - for your app. - - Automatically scale your pods up and down (including to zero active pods). - -1. Run the following command to find the domain URL for your service: - - ```bash - kubectl get ksvc helloworld-r --output=custom-columns=NAME:.metadata.name,URL:.status.url - ``` - - Example: - - ```bash - NAME URL - helloworld-r http://helloworld-r.default.1.2.3.4.sslip.io - ``` - -1. Now you can make a request to your app and see the result. Replace - the URL below with the URL returned in the previous command. - - ```bash - curl http://helloworld-rserver.default.1.2.3.4.sslip.io - ``` - - Example: - - ```bash - curl http://helloworld-rserver.default.1.2.3.4.sslip.io - [1] "Hello R Sample v1!" - ``` - - > Note: Add `-v` option to get more detail if the `curl` command failed. - -## Removing the sample app deployment - -To remove the sample app from your cluster, delete the service record: - -```bash -kubectl delete --filename service.yaml -``` diff --git a/code-samples/community/serving/helloworld-rust/index.md b/code-samples/community/serving/helloworld-rust/index.md deleted file mode 100644 index 5370e14222..0000000000 --- a/code-samples/community/serving/helloworld-rust/index.md +++ /dev/null @@ -1,181 +0,0 @@ -# Hello World - Rust - -A simple web app written in Rust that you can use for testing. It reads in an -env variable `TARGET` and prints "Hello \${TARGET}!". If - -TARGET is not specified, it will use "World" as the TARGET. - -## Prerequisites - -- A Kubernetes cluster with Knative installed and DNS configured. Follow the - [Knative installation instructions](https://knative.dev/docs/install/) if you need to create - one. -- [Docker](https://www.docker.com) installed and running on your local machine, - and a Docker Hub account configured (we'll use it for a container registry). - -## Steps to recreate the sample code - -While you can clone all of the code from this directory, hello world apps are -generally more useful if you build them step-by-step. The following instructions -recreate the source files from this folder. - -1. Create a new file named `Cargo.toml` and paste the following code: - - ```toml - [package] - name = "hellorust" - version = "0.0.0" - edition = "2018" - publish = false - - [dependencies] - hyper = { version = "0.14.7", features = ["full"]} - tokio = { version = "1.5.0", features = ["macros", "rt-multi-thread"] } - pretty_env_logger = "0.4.0" - ``` - -1. Create a `src` folder, then create a new file named `main.rs` in that folder - and paste the following code. This code creates a basic web server which - listens on port 8080: - - ```rust - use hyper::{ - server::conn::AddrStream, - service::{make_service_fn, service_fn}, - Body, Request, Response, Server, - }; - use std::convert::Infallible; - use std::env; - - #[tokio::main] - async fn main() { - pretty_env_logger::init(); - - let mut port: u16 = 8080; - match env::var("PORT") { - Ok(p) => { - match p.parse::() { - Ok(n) => { - port = n; - } - Err(_e) => {} - }; - } - Err(_e) => {} - }; - let addr = ([0, 0, 0, 0], port).into(); - - let make_svc = make_service_fn(|_socket: &AddrStream| async move { - Ok::<_, Infallible>(service_fn(move |_: Request| async move { - let mut hello = "Hello ".to_string(); - match env::var("TARGET") { - Ok(target) => { - hello.push_str(&target); - } - Err(_e) => hello.push_str("World"), - }; - Ok::<_, Infallible>(Response::new(Body::from(hello))) - })) - }); - - let server = Server::bind(&addr).serve(make_svc); - - println!("Listening on http://{}", addr); - if let Err(e) = server.await { - eprintln!("server error: {}", e); - } - } - ``` - -1. In your project directory, create a file named `Dockerfile` and copy the code - block below into it. - - ```docker - # Use the official Rust image. - # https://hub.docker.com/_/rust - FROM rust:1.51.0 - - # Copy local code to the container image. - WORKDIR /usr/src/app - COPY . . - - # Install production dependencies and build a release artifact. - RUN cargo install --path . - - # Run the web service on container startup. - CMD ["hellorust"] - ``` - -1. Create a new file, `service.yaml` and copy the following service definition - into the file. Make sure to replace `{username}` with your Docker Hub - username. - - ```yaml - apiVersion: serving.knative.dev/v1 - kind: Service - metadata: - name: helloworld-rust - namespace: default - spec: - template: - spec: - containers: - - image: docker.io/{username}/helloworld-rust - env: - - name: TARGET - value: "Rust Sample v1" - ``` - -## Build and deploy this sample - -Once you have recreated the sample code files (or used the files in the sample -folder) you're ready to build and deploy the sample app. - -1. Use Docker to build the sample code into a container. To build and push with - Docker Hub, enter these commands replacing `{username}` with your Docker Hub - username: - - ```bash - # Build and push the container on your local machine. - docker buildx build --platform linux/arm64,linux/amd64 -t "{username}/helloworld-rust" --push . - ``` - -1. After the build has completed and the container is pushed to Docker Hub, you - can deploy the app into your cluster. Ensure that the container image value - in `service.yaml` matches the container you built in the previous step. Apply - the configuration using `kubectl`: - - ```bash - kubectl apply --filename service.yaml - ``` - -1. Now that your service is created, Knative will perform the following steps: - - - Create a new immutable revision for this version of the app. - - Network programming to create a route, ingress, service, and load balance - for your app. - - Automatically scale your pods up and down (including to zero active pods). - -1. To find the URL for your service, enter: - - ```bash - kubectl get ksvc helloworld-rust --output=custom-columns=NAME:.metadata.name,URL:.status.url - NAME URL - helloworld-rust http://helloworld-rust.default.1.2.3.4.sslip.io - ``` - -1. Now you can make a request to your app and see the result. Replace - the URL below with the URL returned in the previous command. - - ```bash - curl http://helloworld-rust.default.1.2.3.4.sslip.io - Hello World! - ``` - -## Removing the sample app deployment - -To remove the sample app from your cluster, delete the service record: - -```bash -kubectl delete --filename service.yaml -``` diff --git a/code-samples/community/serving/helloworld-swift/index.md b/code-samples/community/serving/helloworld-swift/index.md deleted file mode 100644 index 28174a81a7..0000000000 --- a/code-samples/community/serving/helloworld-swift/index.md +++ /dev/null @@ -1,161 +0,0 @@ -# Hello World - Swift - -A simple web app written in Swift that you can use for testing. The app reads in -an env variable `TARGET` and prints "Hello \${TARGET}!". If TARGET is not -specified, the app uses "World" as the TARGET. - -## Prerequisites - -- You must have a Kubernetes cluster with Knative installed and DNS configured. - If you need to create a cluster, follow the - [Knative installation instructions](https://knative.dev/docs/install/). -- You must have [Docker](https://www.docker.com) installed and running on your - local machine, and a Docker Hub account configured (used for container - registry). - -## Recreating the sample code - -While you can clone all of the code from this directory, it might be more useful -if you build this app step-by-step. The following instructions recreate the -source files from this folder. - -1. Create a the `Package.swift` to declare your package and its dependencies. - This app uses [Swifter](https://github.com/httpswift/swifter), a tiny http - server engine for Swift. - - ```swift - // swift-tools-version:4.0 - - import PackageDescription - - let package = Package( - name: "HelloSwift", - dependencies: [ - .package(url: "https://github.com/httpswift/swifter.git", .upToNextMajor(from: "1.4.5")) - ], - targets: [ - .target( - name: "HelloSwift", - dependencies: ["Swifter"], - path: "./Sources") - ] - ) - ``` - -1. Add the web server code to a file named `main.swift` in a - `Sources/HelloSwift/` folder: - - ```swift - import Swifter - import Dispatch - import Foundation - - let server = HttpServer() - server["/"] = { r in - let target = ProcessInfo.processInfo.environment["TARGET"] ?? "World" - return HttpResponse.ok(.html("Hello \(target)")) - } - - let semaphore = DispatchSemaphore(value: 0) - do { - let port = UInt16(ProcessInfo.processInfo.environment["PORT"] ?? "8080") - try server.start(port!, forceIPv4: true) - print("Server has started ( port = \(try server.port()) ). Try to connect now...") - semaphore.wait() - } catch { - print("Server start error: \(error)") - semaphore.signal() - } - ``` - -1. In your project directory, create a file named `Dockerfile` and copy the code - block below into it. - - ```Dockerfile - # Use the official Swift image. - # https://hub.docker.com/_/swift - FROM swift:4.2 - - # Copy local code to the container image. - WORKDIR /app - COPY . . - - # Install dependencies and build. - RUN swift build -c release - - # Run the web service on container startup. - CMD [ ".build/release/HelloSwift"] - ``` - -1. Create a new file, `service.yaml`, and copy the following service definition - into the file. Replace `{username}` with your Docker Hub username. - - ```yaml - apiVersion: serving.knative.dev/v1 - kind: Service - metadata: - name: helloworld-swift - namespace: default - spec: - template: - spec: - containers: - - image: docker.io/{username}/helloworld-swift - env: - - name: TARGET - value: "Swift" - ``` - -## Building and deploying the sample - -Once you have recreated the sample code files (or used the files in the sample -folder) you're ready to build and deploy the sample app. - -1. Use Docker to build the sample code into a container. To build and push with - Docker Hub, run these commands, replacing `{username}` with your Docker Hub - username: - - ```bash - # Build and push the container on your local machine. - docker buildx build --platform linux/arm64,linux/amd64 -t "{username}/helloworld-swift" --push . - ``` - -1. After the build has completed and the container is pushed to Docker Hub, you - can deploy the app into your cluster. Ensure that the container image value - in the `service.yaml` file matches the container you built in the previous - step. Apply the configuration using the `kubectl` command: - - ```bash - kubectl apply --filename service.yaml - ``` - -1. Now that your service is created, Knative performs the following steps: - - - Creates a new immutable revision for this version of the app. - - Network programming to create a route, ingress, service, and load balancing - for your app. - - Automatically scales your pods up and down (including to zero active pods). - -1. To find the URL for your service, use the following command: - - ``` - kubectl get ksvc helloworld-swift --output=custom-columns=NAME:.metadata.name,URL:.status.url - NAME URL - helloworld-swift http://helloworld-swift.default.1.2.3.4.sslip.io - ``` - -1. Now you can make a request to your app and see the result. Replace - the URL below with the URL returned in the previous command. - - ```bash - curl http://helloworld-swift.default.1.2.3.4.sslip.io - Hello Swift - ``` - -## Removing the sample app deployment - -To remove the sample app from your cluster, delete the service record: - -```bash -kubectl delete --filename service.yaml -``` diff --git a/code-samples/community/serving/helloworld-vertx/index.md b/code-samples/community/serving/helloworld-vertx/index.md deleted file mode 100644 index d0976761a6..0000000000 --- a/code-samples/community/serving/helloworld-vertx/index.md +++ /dev/null @@ -1,249 +0,0 @@ -# Hello World - Eclipse Vert.x - -Learn how to deploy a simple web app that is written in Java and uses Eclipse -Vert.x. This samples uses Docker to build locally. The app reads in a `TARGET` -env variable and then prints "Hello World: \${TARGET}!". If a value for `TARGET` -is not specified, the "NOT SPECIFIED" default value is used. - -Use this sample to walk you through the steps of creating and modifying the -sample app, building and pushing your container image to a registry, and then -deploying your app to your Knative cluster. - -## Before you begin - -You must meet the following requirements to complete this sample: - -- A version of the Knative Serving component installed and running on your - Kubernetes cluster. Follow the - [Knative installation instructions](https://knative.dev/docs/install/) if you need to - create a Knative cluster. -- The following software downloaded and install on your loacal machine: - - [Java SE 8 or later JDK](http://www.oracle.com/technetwork/java/javase/downloads/index.html). - - [Eclipse Vert.x v3.5.4](https://vertx.io/). - - [Docker](https://www.docker.com) for building and pushing your container - image. - - [curl](https://curl.haxx.se/) to test the sample app after deployment. -- A [Docker Hub](https://hub.docker.com/) account where you can push your - container image. - -**Tip**: You can clone the [Knative/docs repo](https://github.com/knative/docs) -and then modify the source files. Alternatively, learn more by manually creating -the files yourself. - -## Creating and configuring the sample code - -To create and configure the source files in the root of your working directory: - -1. Create the `pom.xml` file: - - ```xml - - 4.0.0 - com.example.vertx - helloworld - 1.0.0-SNAPSHOT - - - - io.vertx - vertx-core - ${version.vertx} - - - io.vertx - vertx-rx-java2 - ${version.vertx} - - - - - - - maven-compiler-plugin - 3.8.0 - - 1.8 - 1.8 - - - - org.apache.maven.plugins - maven-shade-plugin - 3.2.0 - - - package - - shade - - - - - - io.vertx.core.Launcher - com.example.helloworld.HelloWorld - - - - - - - - - - - - - 3.5.4 - - - ``` - -1. Create the `HelloWorld.java` file in the - `src/main/java/com/example/helloworld` directory. The - `[ROOT]/src/main/java/com/example/helloworld/HelloWorld.java` file creates a - basic web server that listens on port `8080`. - - ```java - package com.example.helloworld; - - import io.reactivex.Flowable; - import io.vertx.reactivex.core.AbstractVerticle; - import io.vertx.reactivex.core.http.HttpServer; - import io.vertx.reactivex.core.http.HttpServerRequest; - - public class HelloWorld extends AbstractVerticle { - - public void start() { - - final HttpServer server = vertx.createHttpServer(); - final Flowable requestFlowable = server.requestStream().toFlowable(); - - requestFlowable.subscribe(httpServerRequest -> { - - String target = System.getenv("TARGET"); - if (target == null) { - target = "NOT SPECIFIED"; - } - - httpServerRequest.response().setChunked(true) - .putHeader("content-type", "text/plain") - .setStatusCode(200) // OK - .end("Hello World: " + target); - }); - - server.listen(8080); - } - } - ``` - -1. Create the `Dockerfile` file: - - ```docker - # Use fabric8's s2i Builder image. - # https://hub.docker.com/r/fabric8/s2i-java - FROM fabric8/s2i-java:2.0 - - # Copy the JAR file to the deployment directory. - ENV JAVA_APP_DIR=/deployments - COPY target/helloworld-1.0.0-SNAPSHOT.jar /deployments/ - ``` - -1. Create the `service.yaml` file. You must specify your Docker Hub username in - `{username}`. You can also configure the `TARGET`, for example you can modify - the `Eclipse Vert.x Sample v1` value. - - ```yaml - apiVersion: serving.knative.dev/v1 - kind: Service - metadata: - name: helloworld-vertx - namespace: default - spec: - template: - spec: - containers: - - image: docker.io/{username}/helloworld-vertx - env: - - name: TARGET - value: "Eclipse Vert.x Sample v1" - ``` - -## Building and deploying the sample - -To build a container image, push your image to the registry, and then deploy -your sample app to your cluster: - -1. Use Docker to build your container image and then push that image to your - Docker Hub registry. You must replace the `{username}` variables in the - following commands with your Docker Hub username. - - ```bash - # Build and push the container on your local machine. - docker buildx build --platform linux/arm64,linux/amd64 -t "{username}/helloworld-vertx" --push . - ``` - -1. Now that your container image is in the registry, you can deploy it to your - Knative cluster by running the `kubectl apply` command: - - ```bash - kubectl apply --filename service.yaml - ``` - - Result: A service name `helloworld-vertx` is created in your cluster along - with the following resources: - - - A new immutable revision for the version of the app that you just deployed. - - The following networking resources are created for your app: - - route - - ingress - - service - - load balancer - - Auto scaling is enable to allow your pods to scale up to meet traffic, and - also back down to zero when there is no traffic. - -## Testing the sample app - -To verify that your sample app has been successfully deployed: - -1. Retrieve the URL for your service, by running the following `kubectl get` - command: - - ```bash - kubectl get ksvc helloworld-vertx --output=custom-columns=NAME:.metadata.name,URL:.status.url - ``` - - Example result: - - ```bash - NAME URL - helloworld-vertx http://helloworld-vertx.default.1.2.3.4.sslip.io - ``` - -1. Now you can make a request to your app and see the result. Replace - the URL below with the URL returned in the previous command. - - ```bash - curl http://helloworld-vertx.default.1.2.3.4.sslip.io - ``` - - Example result: - - ```bash - Hello World: Eclipse Vert.x Sample v1 - ``` - -Congratulations on deploying your sample Java app to Knative! - -## Removing the sample app deployment - -To remove the sample app from your cluster, run the following `kubectl delete` -command: - -```bash -kubectl delete --filename service.yaml -``` diff --git a/code-samples/serving/cloudevents/cloudevents-nodejs/.dockerignore b/code-samples/serving/cloudevents/cloudevents-nodejs/.dockerignore deleted file mode 100644 index a5963b6282..0000000000 --- a/code-samples/serving/cloudevents/cloudevents-nodejs/.dockerignore +++ /dev/null @@ -1,4 +0,0 @@ -Dockerfile -README.md -node_modules/ -npm-debug.log diff --git a/code-samples/serving/hello-world/helloworld-csharp/.dockerignore b/code-samples/serving/hello-world/helloworld-csharp/.dockerignore deleted file mode 100644 index 2a82a9c3f3..0000000000 --- a/code-samples/serving/hello-world/helloworld-csharp/.dockerignore +++ /dev/null @@ -1,4 +0,0 @@ -Dockerfile -README.md -**/obj/ -**/bin/ \ No newline at end of file diff --git a/code-samples/serving/hello-world/helloworld-java-spring/.dockerignore b/code-samples/serving/hello-world/helloworld-java-spring/.dockerignore deleted file mode 100644 index 2f7896d1d1..0000000000 --- a/code-samples/serving/hello-world/helloworld-java-spring/.dockerignore +++ /dev/null @@ -1 +0,0 @@ -target/ diff --git a/code-samples/serving/hello-world/helloworld-nodejs/.dockerignore b/code-samples/serving/hello-world/helloworld-nodejs/.dockerignore deleted file mode 100644 index 07efc8ee24..0000000000 --- a/code-samples/serving/hello-world/helloworld-nodejs/.dockerignore +++ /dev/null @@ -1,4 +0,0 @@ -Dockerfile -README.md -node_modules -npm-debug.log diff --git a/code-samples/serving/hello-world/helloworld-php/.dockerignore b/code-samples/serving/hello-world/helloworld-php/.dockerignore deleted file mode 100644 index 5d2dbb3aab..0000000000 --- a/code-samples/serving/hello-world/helloworld-php/.dockerignore +++ /dev/null @@ -1,3 +0,0 @@ -Dockerfile -README.md -vendor diff --git a/code-samples/serving/hello-world/helloworld-python/.dockerignore b/code-samples/serving/hello-world/helloworld-python/.dockerignore deleted file mode 100644 index 8c89c00bb1..0000000000 --- a/code-samples/serving/hello-world/helloworld-python/.dockerignore +++ /dev/null @@ -1,6 +0,0 @@ -Dockerfile -README.md -*.pyc -*.pyo -*.pyd -__pycache__ diff --git a/config/redirects.yml b/config/redirects.yml index 4ac3fe5788..b4b63ca0d6 100644 --- a/config/redirects.yml +++ b/config/redirects.yml @@ -143,14 +143,14 @@ plugins: versioned/eventing/samples/github-source/README.md: https://github.com/knative/docs/tree/main/code-samples/eventing/github-source versioned/eventing/samples/gitlab-source/README.md: https://github.com/knative/docs/tree/main/code-samples/eventing/gitlab-source versioned/eventing/samples/helloworld/README.md: https://github.com/knative/docs/tree/main/code-samples/eventing/helloworld - versioned/eventing/samples/helloworld/helloworld-go/README.md: https://github.com/knative/docs/tree/main/code-samples/eventing/helloworld/helloworld-go + versioned/eventing/samples/helloworld/helloworld-go/README.md: https://github.com/knative/docs/tree/main/code-samples/serving/cloudevents/cloudevents-go/eventing versioned/eventing/samples/helloworld/helloworld-python/README.md: https://github.com/knative/docs/tree/main/code-samples/eventing/helloworld/helloworld-python versioned/eventing/samples/kafka/binding/README.md: versioned/eventing/sources/kafka-source/README.md versioned/eventing/samples/kafka/channel/README.md: https://github.com/knative/docs/tree/main/code-samples/eventing/kafka/channel versioned/eventing/samples/kubernetes-event-source/index.md: versioned/eventing/sources/apiserversource/README.md versioned/eventing/samples/parallel/README.md: https://github.com/knative/docs/tree/main/code-samples/eventing/parallel - versioned/eventing/samples/parallel/multiple-branches/README.md: https://github.com/knative/docs/tree/main/code-samples/eventing/parallel/multiple-branches - versioned/eventing/samples/parallel/mutual-exclusivity/README.md: https://github.com/knative/docs/tree/main/code-samples/eventing/parallel/mutual-exclusivity + versioned/eventing/samples/parallel/multiple-branches/README.md: https://github.com/knative/docs/tree/main/code-samples/eventing/parallel#filter-routing + versioned/eventing/samples/parallel/mutual-exclusivity/README.md: https://github.com/knative/docs/tree/main/code-samples/eventing/parallel#switch-routing versioned/eventing/samples/ping-source/index.md: versioned/eventing/sources/ping-source/README.md versioned/eventing/samples/sequence/index.md: versioned/eventing/flows/sequence/README.md versioned/eventing/samples/sequence/sequence-replay-to-event-display/index.md: versioned/eventing/flows/sequence/sequence-reply-to-event-display/README.md diff --git a/docs/versioned/samples/eventing.md b/docs/versioned/samples/eventing.md index fc5c0737af..02ecf3994c 100644 --- a/docs/versioned/samples/eventing.md +++ b/docs/versioned/samples/eventing.md @@ -15,10 +15,9 @@ See [all Knative code samples](https://github.com/knative/docs/tree/main/code-sa | Name | Description | Languages | | -------------------- | --------------------------------------------------- | --------------------------------------------| -| Hello World | A quick introduction that highlights how to deploy an app using Knative. | [Go](https://github.com/knative/docs/tree/main/code-samples/eventing/helloworld/helloworld-go) and [Python](https://github.com/knative/docs/tree/main/code-samples/eventing/helloworld/helloworld-python) | +| Hello World | A quick introduction that highlights how to deploy an app using Knative. | [Go](https://github.com/knative/docs/tree/main/code-samples/serving/cloudevents/cloudevents-go/eventing) and [Python](https://github.com/knative/docs/tree/main/code-samples/eventing/helloworld/helloworld-python) | | GitHub source | Shows how to wire GitHub events for consumption by a Knative Service. | [YAML](https://github.com/knative/docs/tree/main/code-samples/eventing/github-source) | | GitLab source | Shows how to wire GitLab events for consumption by a Knative Service. | [YAML](https://github.com/knative/docs/tree/main/code-samples/eventing/gitlab-source) | | Apache Kafka Channel | Install and configure the Apache Kafka Channel as the default Channel configuration for Knative Eventing. | [YAML](https://github.com/knative/docs/tree/main/code-samples/eventing/kafka/channel) | | Writing an event source using JavaScript | This tutorial provides instructions to build an event source in JavaScript and implement it with a ContainerSource or SinkBinding. | [JavaScript](https://github.com/knative/docs/tree/main/code-samples/eventing/writing-event-source-easy-way) | -| Parallel with multiple cases | Create a Parallel with two branches. | [YAML](https://github.com/knative/docs/tree/main/code-samples/eventing/parallel/multiple-branches) | -| Parallel with mutually exclusive cases | Create a Parallel with mutually exclusive branches. | [YAML](https://github.com/knative/docs/tree/main/code-samples/eventing/parallel/mutual-exclusivity) | +| Parallel event routing | Compare independent filters and mutually exclusive switch cases in one tutorial with shared helpers. | [Go and YAML](https://github.com/knative/docs/tree/main/code-samples/eventing/parallel) | diff --git a/docs/versioned/samples/serving.md b/docs/versioned/samples/serving.md index 7cac98682f..4766691fe8 100644 --- a/docs/versioned/samples/serving.md +++ b/docs/versioned/samples/serving.md @@ -16,7 +16,7 @@ See [all Knative code samples](https://github.com/knative/docs/tree/main/code-sa | Name | Description | Languages | | ------------------ | ----------------------------| :-----------------------: | | Hello World | A quick introduction that highlights how to deploy an app using Knative Serving. | [C#](https://github.com/knative/docs/tree/main/code-samples/serving/hello-world/helloworld-csharp/), [Go](https://github.com/knative/docs/tree/main/code-samples/serving/hello-world/helloworld-go), [Java (Spring)](https://github.com/knative/docs/tree/main/code-samples/serving/hello-world/helloworld-java-spring), [Kotlin](https://github.com/knative/docs/tree/main/code-samples/serving/hello-world/helloworld-kotlin), [Node.js](https://github.com/knative/docs/tree/main/code-samples/serving/hello-world/helloworld-nodejs), [PHP](https://github.com/knative/docs/tree/main/code-samples/serving/hello-world/helloworld-php), [Python](https://github.com/knative/docs/tree/main/code-samples/serving/hello-world/helloworld-python), [Ruby](https://github.com/knative/docs/tree/main/code-samples/serving/hello-world/helloworld-ruby), [Shell](https://github.com/knative/docs/tree/main/code-samples/serving/hello-world/helloworld-shell) | -| Cloud Events | A quick introduction that highlights how to send and receive Cloud Events. | [C#](https://github.com/knative/docs/tree/main/code-samples/serving/cloudevents/cloudevents-dotnet), [Go](https://github.com/knative/docs/tree/main/code-samples/serving/cloudevents/cloudevents-go), [Node.js](https://github.com/knative/docs/tree/main/code-samples/serving/cloudevents/cloudevents-nodejs), [Rust](https://github.com/knative/docs/tree/main/code-samples/serving/cloudevents/cloudevents-rust), [Java (Vert.x)](https://github.com/knative/docs/tree/main/code-samples/serving/cloudevents/cloudevents-vertx) | +| Cloud Events | A quick introduction that highlights how to send and receive Cloud Events. | [C#](https://github.com/knative/docs/tree/main/code-samples/serving/cloudevents/cloudevents-dotnet), [Go](https://github.com/knative/docs/tree/main/code-samples/serving/cloudevents/cloudevents-go), [Node.js](https://github.com/knative/docs/tree/main/code-samples/serving/cloudevents/cloudevents-nodejs), [Rust](https://github.com/knative/docs/tree/main/code-samples/serving/cloudevents/cloudevents-rust), [Java (Spring)](https://github.com/knative/docs/tree/main/code-samples/serving/cloudevents/cloudevents-spring), [Java (Vert.x)](https://github.com/knative/docs/tree/main/code-samples/serving/cloudevents/cloudevents-vertx) | | Traffic Splitting | An example of manual traffic splitting. | [YAML](../serving/traffic-management.md) | | Advanced Deployment | Simple blue/green-like application deployment pattern illustrating the process of updating a live application without dropping any traffic. | [YAML](../serving/traffic-management.md#routing-and-managing-traffic-with-bluegreen-deployment) | | Autoscale | A demonstration of the autoscaling capabilities of Knative. | [Go](../serving/autoscaling/autoscale-go/README.md) | @@ -25,4 +25,4 @@ See [all Knative code samples](https://github.com/knative/docs/tree/main/code-sa | Knative Routing | An example of mapping multiple Knative services to different paths under a single domain name using the Istio VirtualService concept. | [Go](https://github.com/knative/docs/tree/main/code-samples/serving/knative-routing-go) | | Knative Secrets | A simple app that demonstrates how to use a Kubernetes secret as a Volume in Knative. | [Go](https://github.com/knative/docs/tree/main/code-samples/serving/secrets-go) | | Multi Container | A quick introduction that highlights how to build and deploy an app using Knative Serving for multiple containers. | [Go](https://github.com/knative/docs/tree/main/code-samples/serving/multi-container) | -| WebSocket Server | A simple WebSocket server. | [Go](https://github.com/knative/docs/tree/main/code-samples/serving/websocket-go) | +| WebSocket Server | A simple WebSocket server. | [Go](https://github.com/knative/docs/tree/main/code-samples/serving/websockets-go) | diff --git a/test/flag.go b/test/flag.go deleted file mode 100644 index 0b0476528a..0000000000 --- a/test/flag.go +++ /dev/null @@ -1,49 +0,0 @@ -/* -Copyright 2019 The Knative Authors - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -package test - -import ( - "flag" - "strings" -) - -// Flags holds the command line flags or defaults for settings in the user's environment. -// See EnvironmentFlags for a list of supported fields. -var Flags = initializeFlags() - -// EnvironmentFlags define the flags that are needed to run the e2e tests. -type EnvironmentFlags struct { - Languages string // Allowed languages to run -} - -func initializeFlags() *EnvironmentFlags { - var f EnvironmentFlags - flag.StringVar(&f.Languages, "languages", "", "Comma separated languages to run e2e test on.") - - return &f -} - -// GetAllowedLanguages is a helper function to return a map of allowed languages based on Languages filter -func GetAllowedLanguages() map[string]bool { - allowed := make(map[string]bool) - if "" != Flags.Languages { - for _, l := range strings.Split(Flags.Languages, ",") { - allowed[l] = true - } - } - return allowed -} diff --git a/test/sampleapp/config.go b/test/sampleapp/config.go deleted file mode 100644 index 20ca10e621..0000000000 --- a/test/sampleapp/config.go +++ /dev/null @@ -1,68 +0,0 @@ -/* -Copyright 2019 The Knative Authors - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -package sampleapp - -import ( - "fmt" - "os" - - yaml "gopkg.in/yaml.v2" -) - -const ( - // using these defaults if not provided, see useDefaultIfNotProvided function below - defaultSrcDir = "../../code-samples/serving/hello-world/helloworld-%s" - - // ActionMsg serves as documentation purpose, which will be referenced for - // clearly displaying error messages. - ActionMsg = "All files required for running sample apps are checked " + - "against README.md, the content of source files should be identical with what's " + - "in README.md file, the list of the files to be verified is the same set of files " + - "used for running sample apps, they are configured in `/test/sampleapp/config.yaml`. " + - "If an exception is needed the file can be configured to be copied as a separate step " + - "in `PreCommand` such as: " + - "https://github.com/knative/docs/blob/65f7b402fee7f94dfbd9e4512ef3beed7b85de66/test/sampleapp/config.yaml#L4" -) - -// AllConfigs contains all LanguageConfig -type AllConfigs struct { - Languages []LanguageConfig `yaml:"languages"` -} - -// LanguageConfig contains all information for building/deploying an app -type LanguageConfig struct { - Language string `yaml:"language"` - SrcDir string `yaml:"srcDir"` // Directory contains sample code - Copies []string `yaml:"copies"` // Files to be copied by the user from SrcDir -} - -// UseDefaultIfNotProvided sets default value of SrcDir if not provided -func (lc *LanguageConfig) UseDefaultIfNotProvided() { - if "" == lc.SrcDir { - lc.SrcDir = fmt.Sprintf(defaultSrcDir, lc.Language) - } -} - -// GetConfigs parses a config yaml file and return AllConfigs struct -func GetConfigs(configPath string) (AllConfigs, error) { - var lcs AllConfigs - content, err := os.ReadFile(configPath) - if nil == err { - err = yaml.Unmarshal(content, &lcs) - } - return lcs, err -} diff --git a/test/sampleapp/config.yaml b/test/sampleapp/config.yaml deleted file mode 100644 index f23dfd7a3b..0000000000 --- a/test/sampleapp/config.yaml +++ /dev/null @@ -1,44 +0,0 @@ -languages: - - language: "java-spring" - copies: - - "/src/main/java/com/example/helloworld/HelloworldApplication.java" - - "service.yaml" - - "Dockerfile" - - language: "go" - copies: - - "helloworld.go" - - "service.yaml" - - "Dockerfile" - - language: "kotlin" - copies: - - "/src/main/kotlin/com/example/hello/Main.kt" - - "build.gradle" - - "service.yaml" - - "Dockerfile" - - language: "nodejs" - copies: - - "index.js" - - "package.json" - - "service.yaml" - - "Dockerfile" - - language: "php" - copies: - - "index.php" - - "service.yaml" - - "Dockerfile" - - language: "python" - copies: - - "app.py" - - "service.yaml" - - "Dockerfile" - - language: "ruby" - copies: - - "app.rb" - - "Gemfile" - - "service.yaml" - - "Dockerfile" - - language: "shell" - copies: - - "script.sh" - - "service.yaml" - - "Dockerfile" diff --git a/test/sampleapp/helloworld-java-spring.zip b/test/sampleapp/helloworld-java-spring.zip deleted file mode 100644 index c4b768e52a4fac5a0835d7c5879e30914e0565d9..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 53343 zcma&N1CZ!U)Ga#p%o*E0W81cE+qP}nwr$(S8QZqr`F_;B_r3ppbyroAO0sr$XYcA> zy_1fdBrpgR000C80B3o(4CRan-7+))Kn4*200;mwfVGQ_DHT31M#L& zqV#@H1JmNx;?3gLw$JgOw|peomX`8wSYCYBy-tRxVo`CSaUlgqQZ28iLU{%>i3Rth zkQ}j3@}e!1AFtS33KZdd5Qic&Ou(_|0iicn^MK$!cw%}L1sUVCAr^UE3F+>|ZkyZ| zvqSKx>$iIT!8=FZbbz5)bKG)IO#g&iuH0X!fNun-B^j{0zu8eI9tP>*a;TGdbBK|r z1$U`7;j-DNP~WKs6WDcO&PF52YH8AoXrrqePriA^f1J!G#MM(8G-^u})v55{yvs zIA^?gT7!>QVwTp%haGA17G-*s!ACKVtkK8VLNdOkyg|3Nsl}%H;wZ2COz@Ab*sSra zUF_&o=bYHK!5y+Rd#thrG}&F@J7563Az7lAFBLm&=O;~iw{}W5pPw$SH!VA?pFO5$ zoI96GZDM6Qvm2kCJ2(3`*_kPONf?oq_`o52Wfr^cGE}C29`^*}*RfN4V~! zI=;tT2{hPbhSe6>JmM&fex-{uKN+%^!N7694lT(x5e@fgB~E^?AUt0YJu7gH-G;q~ z%RzQ$&r8O)Aw_5(%|?wrI&`*jus-4|d3D7@UTNB9uuk`1TpLH8BMa3)IcQzb6MN59vC z<$qI#^}HN69tefg`EYoQ}iGe2om6(QcPWs$3iuarTKWwR$6Z&p!Vls#MV*FW@~5a>lT+M!?8J zU}vz;&dMbLpq!|=)Hgp&MX1Ky+^BE#wnTAv<|XR$y)E}a=(6W9v?f}xyrsdET4ckR z%U((e_-^hQ5sF8|dyhIEIjzIm?No z`9aR!1Jkxr+e|Ld&#zy;Ty|hQ44Z}A;wAniimw9<`}*Yx`3`ZS4J?uMP%>G?`n80? zOjUa(OZmxrVl6|9rxzS!2z%7Uv}*z*1Js^w>JZ>SrPIOV&P#$*%72-f9JbMAaR#JUnaj?)jMZu8!BXoWJ z{;Ae*(+qZ4f;}4b*h8o|i!omX-;CXx&AMHil~*v1vg7)GdHL8M!*E|xmp(FSVEn_s zU@kCSEQd!tEWZzaq? zN>Cq7DS(U+&ywY}Ypu#oW`C2_u--FLOUra5LikUOyL$w^M>-5qX=V>%mU1ZPDL@_| z3ol3!BO#-@37|Z+EPf|Tg8-+RsGjXSl4}(<3(BLkPeDgtu0L>c&)-+_i4wk_WxyxE%F#Uu>eK<@2uDx## zr;gTQeED6iMMp)t15`}h!2f!5)xbYlXAsiww0#8Wcdjbv&E1Y=Tf)rH-lTQo6%lyp z^KLMcy4w!+5G)m#mFo@^S9}^#iCWiAPWfUHZ@x-H7sB4nwP|NsGbHxZqNM@_8WtG2 z5Kg91PDz5_sq=9}Lc?kEdskC>$a})teTw8+jixh*?dD93rWrYv7_2h7bVjS&lcoOw zrWjy_LfwFBPcojpNhRsID$YaM`u)02LqhnxZAsbK{&+kdZ(qTcsJJK-1bVX|Hy4lk zF3g6cozO^)sIzE44dxz!b!(3-VVZ)mRiL%c*v05|x03v@9;Vf<2kt9MG-F@i zXY)!s>j@lfx;1-owOWTr!Eh-vCo7DpMc#{NU-G2Sy$Fc51voxLY;B@v4EGhv&wrk_ zz8V1xo=X9`#p9gR3nxm6sW?4C>-y8Ik|vsBnk={2|l+*ed#ZP zjL=R444{0!NbkqzId(X=SXiZvdMY~tvr||n43=FStIO;8(1J$&D_ns}HMNe0`3saC zPYuHoXZqguM;Zp#Ixu*kZcM;kM@$4Brns6Hfr z?tz)adP0z0bZhckn+JmT?{eTe{|;!=mG81YpL?Pk)mi{Dc++j2tqz~yq~G?;sh0Xu z)5EdkOH6<$Nfn6|D%32?BkazPN?_)6_o`j-f@V?~ubF2h72ja36k9xBJ zFpmu^M(u*!Rrl%H^_k=qqhqyuaCg1F=40Jdr}meBb&;9P@&$9y`GA2Z6{O4s%6A-L zVl=&uo1S^z^@C5iTJ%&@mS!(nPM(P@o)ke=TD$+L9&LKi>$Oi@Wl}Mh@-YEo^X0VN zneRMad`mb&ju2qW(ng4>8{ND!KnjB))log|t_}qhXPfjJhgg)|jM5sp}jU6KD89`ozU<@x1%;SyM~??2K=DE1KvGf&FV3(j9<` zHj9?~h)DvN_K&zz!CU0wGi6H%iWP_fI|$%|3Meck9u+xzR#jM#snXBiO&tT`mDl?Q!Kh(?Z(%qM=aK}5E``(t_FB82* zUd1b{6^Nm2jT6mJL+X>clrH69Tra&TfQn784UEbDii!(zdz$}R6C9c#yRa&9YDWybY*0D z&t!7R*U}@Dc^Ri}Yss;ZM+#0QQaKly#6Bm-B!S(HF_Rj-fa;V|c61cMWNV z&08I}eLzQk46VQL96`Y!taQY7O4hihnS{V#mX|B-L(rf+R$W&AI8B>!q> zW^83;>uT#@W%MsaApP$}I2b$HIy)E|JN}Eo|Iya}W#H(f?_~aOZpQhaD{?aa`;7Wd z|61jLN9Zrh>0es*-x2x`+W%#Q{ulTEE<*n&|NlNh|Fhly5TXA}jp^I}giro;7%dNHcH=+vf#_hc_;jyoU}2-_rLr6f4aeMa0nO|3;+P(Z-*lP#|{4s3;*5crS)Bm zZB+kr01&cuwXw3*H!^mh`6uxIIO!jofAEj}Kgd?qcHCe?_I{~to0OE2kZNg^oqp`6 zrZZ;shrye6apAzwhn6#x)Da&sc6r}~A)T)H>i(7ywikmr$H& zh5{%_(zu*=WBFVQC6O#cV^pRYsD>_CW%K_{9^~auvLDB0VxpJ;aKIL)0hLokNoY8t z+GgI8Y}LGi4<%Eq(Fo(3kq%K3m=QuDe-(nhqjHGiO|~H62j?Y%jC8!Yi|UoN+gu@9 z{hY0xkw9&iPoU})3;pOR#28H#uaH3!bsOfPEX3(l^js_e4=ruSP>Wh0LaSWg8^pt{9TcFkSEmho&GSAH_>E|3f){xg#%YkG?;L%L_i zaw)6NZ_9=iBl>Cyuy-2yGHSPU0U+#dhxE>!7{ZBYB*KBoIj(SH)6n0R&wyD^ z5=lqgGcg`9AYoN@NQ%Kgp;!{-3DrJq<~z|ejee9pgygoWJv#^+ymT9FCDtcI$47aS z0T#+N`J^N%u`qISpp~0Thy7yhf*(I;!)K8B z@igHu9L&f{fxPBeLtYWsK{1KEpOsMmx9|pF%aDeDrN^}vsVZqa!*3QUkwVJLny(q+ zO?YX3j77pKO2wD5^1GH*yr74Vbw}GzrVhN&Zqvbi@Aj_urt^S!CM193!UBcBXxk0w1Bt>;XU{N7;JS+*owm?hN8iU!CP+NIkHnMFar9m(eIDMBM%hs)u(J?X zOW_$|XPGFV$n>1?kN^@$syG53de~Fbw;x=YZ`wPP4-w+x<{Hi?$-*U#LvgFMR&CMtZk@%IvoJp|`OGz$q&RgApNfC`h9}T{%# z;c&fAK^I#d45vkDOWx|VhkrY+97{wlf$mVkV8o{(d6?=@3!OiXRz%ud0gEOEhH<1? z&Y;vdO%u<3jKH<#PNaOZ6Y`e>K%KnmEV{zgG{(0%(v@+EU&Glkk@gWZymcW~CyZfm zV~KrS8zp|#FE{04(6p}odW9vQmIPJ=G#zIP!h)BaoY^%HSrz+Uf4@eLB09u_M~KE5 zbG3oti?wb}UsQAW8V9M>B&AAJQ7u`aY5ifH>#)d43bGOxKkr z(5AaUCk;c)H%nY(@k^Xvy|CaZIjL%MFNO@*MV_?$emY2|US%`97P-l@3Nc2-Zo$^K zO+?F_GT-E~;e3)`=P@Jfy{xy++3(KX)b;tW!h4G~K7)RK2{ATpVzd0D@h5s*>&j5# zNE!`g8jX`agI1CeGM~B4Wsr_ss2jYJjj6%BZBjai4pispgXtsPE&RX%zY8-%D#zXa zjsdPX&iCEubX4IK1ywfJEnvUI)G6Vk zDfj~U^oM`4#OUcqS^n{9=J&&iR0Y@mI8`|6A=yusXJ5YJx!-DghG^?E((2aboJ+84 zE?Ou%B&vt|6+ z;udYa*>cnRvVOAtkc;($X1Wto7FKgQb2(9Q>2Y6PrH}*&DhM195C8-OLEqv31$~5PC?B=SK%x@Jf;PCs z^F|%QTTm8L{2=>W?Weg5`yQ3cQs!QDr+LpfYrHK>#Z z*WbT%wxcll%&U5#XDfNLYxJ{P9a&t?7!8B-v^3;IvLqrhyPbbjyp?8sdUB`n6ix{@r4)=ZoS=_TW2- zzvep*fDJH$TJn=Ze*dHT_+wr7Lks$oC=d8;8=>|s6JOWe9oaaki^A?#oE-t(H$J=+ zzP$#vdzn?ma~?wBzk-7pAs+_^X0In^@W=_cunY*rMo~?>3r=5SA18;2t=%sDP#bet z8RUT);U@bfnCR4u3NY?TXEEsX2T)KTRk<-*DWcJ9Q9L*`ZN2%F%Tw`Ei$hO`=vmwobAx@-T33%=Cll(P%B{*H+9lZrV>;YZyZEgn zZKv+&E+MNr0CwRik5n+^acaB;{E&a0qy;?rapZ5R`#%-PC($|ghG$d-LGPTjl20mb zHKk^}Br04G9Ut3#=A?98%8D{kf9$d3;Se(cwa0}!<_x$=&2<{Q)quasKg2{f+)0T0 zBrLnzY1y?-$(%z6XTP;#AocGyV4B=?+f}S;y*C>y5N_$Hg5d6j)+R~N8fyq~-}czm zj>DwcDm{^^>eeT^6#5eLDRSf<@lGE|QlmHnPHI433{U`Uljx^1pD(eUFMaGNwF`@7 z%qHO#w_|(Kk9%Y&d>-+s_BqwI!YO9@mZpfRfL>e3G(9+v;2q#T7Q~Zd#Muw^^Q(wW zsQid6t$yF5hZqe}6n@$$;+H%JXch)Lx^CyFq3V%zyTBH&){OC+)(VJ%4ofM+9J6&< z=#@<)mxQWdO*j`ZXP{H<@k0%#sT0I~OGOJrp06yO&zK`QW#Mv98X+rp5#WiRGt)6t zNHYZ>vQt}3`L~KvQZf?Yn5-(bM;&h&rEH{Aq=abn%EAV>z0R9e=x9{eE(wp9JTl-f zA>d+lwG0=n&<1B@**DRU`;RM$DzB3psj^(Ypv9guY4C1T26w%pKGch0wkM&i_6?wm zN2e)bC8r50`B*nYvzqqrx{Q0|P-#y8N-uFPB^+EELyfiP}-0hCa@H}-3 z#~I=_m?Cq|Vs0>)JPgw^=a#cbeW%M4=YdGYfd!aX>@bKqjIt}YBau3*KY}rrz+$ueN(_BYJVvk(Z0z>7D_L!^ zi0>$!c&NN-Z_40s>k*eE79j5~w8+0g%BS6w)#4Y%C)}D~;7(O*4e+6YhX`O&*oa@) zFG2sbi98UOsU4UC$==&`VGlaCJkI8X4_UeQgM?0E-2VKpxx%r6i}8C6)z|wDP30S%vPLzis7)pK*3+g zy{B!6)wLu%>aSR}#6Rx8yVd9%@dnRr9%E#Mbh7Aspn~;&Mq=SoPubPY;QjQOzM5VK ztlJTf2hi+Y%fs?o`B>-w<~L2EmPpE47nb`LL0Ww>SODN~erk{srnI{5Do+XDJnHB2 zegXd;Mdoh3r>BB}12KYla8+!Y*pZ$uC@}$0#X^(!bmheI^b0RXDvu~_j4ZF-{|TAx zz2@Qy*3zbuy3(SG2BdlZ$rz|ie7uwoniFW{&ot6dO;W!2%*Y*iQd;gz|5%8#latRh zSVELwQmO+;oFX*QAKF=XW(h_B>V=eU^i!@bS}C~1%$kmA;3`$mn$C%}yaM%e`%uEq z|EQj9_MgQXVkEfR0vk4As#y6|t{MZujB&hzg&ro#=wwa18%^rYQEab)z-8U#K#<^g zne_mUf1KP-9Df6$8n54<@=*o>D4UZskn4Z<3-6DR#khm&5Wt612VDD)c%%rL?H^P!q{?_! zYYMXXyRA{OgpO}|;`9BDPFt?|Z1Yh@w-{?56%IgEkm zq;n#o$a_YT&dwSAuP;8(0GJ@JQkp5uO}r^G>DY!I-W#5hMbF+HBu{>}K<6_1Ib4;) zw=1)H74CQpJB7e`Pue;?{SP7t`t0FJ*IP{tr@qx6KFKghsOZ2OGJHn8@`ndUcj)xn zd?bG&n<|OPGsvx68f+mdHM@^@9Be9>iauf#P?N+aY>!~viMu-V%qvq>GXM>>g}_#M^<%94orKD z5n2&9-kd7Aef@lzk-)V9!~CN~LfoK^*waOhg$k?WaB7Zm*0sWbTjw0bJpEVRx&e(` z^!MDyjo|1|&WCKld2rzN#~9wBDaflWM{+s#A%@YW`iODJlG>tR0X2om9*bh(!=M#y zNHyU3r4^7#Y<5(?GEuB^h&y!5heUl4J#0ePI_iZS36pw-z&jv!C*1xFFghYVW)^~9 zTvePcC(~%Gax8@54ZqCDgqGwJvD?XL)97-VVCxxI5(5ST9R@^^|AU!Opnp?&c z*jV$?&zAaZIhfV>>@^78WD58o0`rwdl+4bH8-Z1|-o ztt{1T(MWj(M^6rF8o-gQ5R-Euo=%U=7T-IXjdD~?0l38DD4kAfzR7I5)PuzfOt$*i z17jOl^_283HA(99la9wCI1DYd?=p?n>hr_4hPN0WYA49zl0v0^cD|v_{J2q(cDLuY zS~Iud9%9_pgI9wS&c=PjH?fLRKN*_ihJ?<7nXrSY^~VMw_IVQ|uSzgFeJfR);`p@6 z9K5=PBVYqYi@>3b>Rx6hWjR(ic5qEScI6F5UE*qC+G?bE_(nu?w1vs3| zXYec>JuahGJr$KU=M=Pv_BuU()|lhXY!abcIOXEi85c?gx1G|HWKjDZ0Nb7Q3(#}6 zUGnAYMSACbw(|AHxLc*o`z1AR%=&{SKDN9Ojo24S{{$69WqLUtb&^*7^S<-BV><0S z_4b}w^AkDFz4v<<{Gx(nwYXKVbLQ{@;Qq-13doRmB?%S7-C6l9o{VFPmskDAD6Tdm z6Ahzf`SOvP_j#qjR!VXm@0AEp7o=|}D{+$2pRkqxK8xEPX@>+XE7TYL`c~|G(4tp_ zGmSC?(V;mxBh<^)amr@8v(Us=%VMklLrA9ci=bo5c4@YaO6sLhiusdCb>=Z>(75~4Bv-|;GTn8BXD48O;XLSx zvN%}>nG8bmk~a;o7%Ia^hgcBQxm!!6kNcsk^F`Pouhl-s`Xh%9!va65$}h_%sDHid*7P;5Lq#brMDY z!WBIKGMs172Km^Zb7GveU1-y~Gq`yk`jANNntF-i>q&c$<^@f;u}xom_@MiX)3G81 z*Os(Yd!`JDy0>O=!P0~H21E4*(S2U-ByVXRsMYJ)XK&}&{&o4_2Pr(5G0cv}?2^Pr zmQdply1^pRLq?WLdjnu-*0QbaC`->Wl=p6@pA3zPk4I2zj*uUf4!_mKT>9E?6OG4F zoJ-26HoSEH@7xW+^t4#SdRH~!=l571=aaDmgTd!883o^RWFfv*gac80|l;lUPZ< ztFQ_#Gc*8LnLn4oR??D!Tf(cgYAfu2(y(LZ!8k-1^X`(4oA;0jj`zSC5a6k!kQGak zlg(IyJ5f?T|^^YHUOGx(F{-tA3&pW*UU@ zm`u)9(-MKn_agrY8F2F9A@CsZw1oJ4W@t}@2rXgwp*AeL*`%LuZUkN42H17-Xd2sG zFtP5yy=H7^fva9TdqKk&4-fPRWvDe5q^B@NAcuYUU%(?R$=CH?~Tc)Bpv ziSB$i0BuudaiVYlvFcw zG%H9@0ksZUf80gdJB*D^PO1KD_0yi<0>+wkvYA((^ptPt;Su1_y=75WNzeNSXo?f@CkkFr5TH>$5EcC7p-1LL=;$RlM1{Hzvtv$gHE2{?|5 zV8XT|w_nM?27;3Am~&+*4o-8xUFw-*XgmXGpJ{fm{dV z-ua~x!h5_HH!pt0?BE{*0l!_3Dx8=yQ5g!slw=d9K$j)DHsBkUUt5?pP=yuI%0&ZX zm|6yrqm<5Ty<4wkxko&&UL#X=o}~xwUV88Xs)SJP7Tj{>>i}&0loOVuEMNlymGk?kM)Hi4qBUo36SdWVv7dAGUzw|r$ zLXsH#k%zhUmSjqJ37K%la3o$DvPm3DAWnSk(s3vqQyMOg3-#WuD8ZgS)^I~jN+J=D zTdtx!_`iGx&G_-6Z`F*H*~w&-Nb!}JV9MHz@DMORZcxS%g()r1F;9g zB6Y%-2R=sDuuzR&JHSe-yyZ}#(sq7x8gys6a3>*nerac=Zh zH0;YlV2tn=mo&N!t$z%)g|v!0jdww5RVv5K?C4YR<41S#LEqJk7P%19HVBX`uw~I# z`TsO3fYtypB4oKy8RakRJ=vMNV}ti`x(5*iG75mw6hIvD$vhOyAfcQTTa@krZ$?KE z57-%7A*2n;PWIDXQU?gm$6a|;9SCoHyH0MaxIM!Tyw~ahR&BB-e7VStS;UsDxK0y; zIrocdnc2gCJuvyo7-gMg$j`k6JbJ^bZ>O1)65T;hqkv{^Xc3m0dKVU>wY3HpDzM|} zIZ9)cq29k@UsmjM?X34NVJ-FqwHzY(FuFl0UG2YENXt~QT|8FxESxmtjymw{!ieeO zvRa2iD}!Jw(#a;mXTTR{bvkc#&g!GT%)W>(~cQ0t2E^(N;wJA7g6|;R*FHAJU(NK?I%I$?*fuyxo7qqTzPFiec z*B-AK>S$Q!cw1U@NQKYhWFIQX>rJA(GflooTwz7ccJOqpYSc$*H@TPIdr8v54_%W= zX1Q=_Hz4BaN6FAzN;nTInezS?rh*s`!AhZ=*g%=r^6z)Wje+J2HYO3mw5o&pvo1C# znajjWrGjygkj`oY5mQpd?rfOC0ZKd)39$3#N)UJgF|zhh$yu$WG#kD7xFqS?Wpjx? z$N}ETX0)k1Pg5&0;#)$~v8zV7MADTg3Z(UF^#$;NG};)Cu_PKKn9K^|ds$gBNbsRz z!8#rgg-<(l8C{&5N#;$meN_%kuIPvLg{Lgn8Fd>8lIST5@zw&l43|~MmE6%b+Q4h+ zYDJYZqZuQJiX!l7yQaFyRVzb(nD))2ZlbJ|EFR;X8{JM$H?KuMr*z<@GD(h^Ax&q2 z3*+Tto|E%zv!k3~7W?~6oGuVA4xqy%#IlWo@OGgvy*hV}U^W4Oi`Zvw%aOE~YAnh?|Ka~aSsn(V%?(_`ykNN{Wh z_tC1Yh$%1_$fAGtYz;Wm*zCYiJ?;vh0xgRR*P}QT2PT`6KFJ2(AVVUsm=GF6KfEOT zl-$*ajH%B2JjT;T)Nrv~1kKwR?EsW3QKDtso1;JQ(uVfESbEh}df+HBVG+M;_Srn> z3w0>QsMOw4Z;`Wy$Ia;`PWwpq$eBtLnP`Nve88=*hh1GAlxUCg#-oQPc5`CKSn%t2To85N%iG3qW?EuluBKf23AV@!zkf)R_m-jBr zHGV2wmIRAQyKbCb5VsA~ce}&6t1EAx1s%RV3kp4uSZ~c+7;gru*r9uKdU?NQfbwEP z;+!P8r_5(0w7swjxo8z_Z@*J&EYY)d#1ZxA#&DJ7J9zqGPLx}JI;l={lRcqdix+MR zbmHka<5G_4+_St-mtweXRA<_zqW@A!mf6jK5|81om(`iEkjuLyhVej+^YRJ2k4{b9 z6G(9P$O6{1dFn=fim|YS{?$1N2$hvXAl(15+wKe;&bNV_@k+RoLLhdGEx@;wLk6~a zp$sDiwL-=ZM-6?@OzRpQFX7=wcxMkIDxTz&Mmkfv+-ddcX?4m8M?WF+SGm)dzQz$W zi{#4@qO2CaQ;93TylN7v#3j*%~|?s8ko%DSY2GCrGR-X zrZu7-C$piVBCYm%ay!MR+OjIc45CUTyC|oI+-$&OH(@gGuUhq`m!LE&86KCZt^VdZ z-(zG_@`zOxKY}FoS?H#HL2ryG)#K7*r|x3t&H$Pc`83ssXdxv_2RZZ9A9$76Ne_HU zukadf$>r*4-8nTwXFj>O&34v0+RBo{NXAr^FVJbtb~^6Lcuv%vNsNW32WcxIt9+3G z#8r6S&7@cxbuVpIfl}jdG*=wlnxo$|hHZZ&kZBxp(>Fa_unJ~ibU1;L3RvA(Tgb zan)OzPPxkm>C2&Mc^0_d5}!Jc@8CedK!q~}WBK_D3rP5$f;F)H(*tRt}*=p*doZY!SDLx8@{x3etKWQ-DC&Zcz3_`B=hG_NajI zeLUJ!WJ}&`%u;r9rLIu_u|Y}!lh!PAipshA#1xD zR97EXlOONKQuh48XGHM_b}@?Iy7quvASIA|X0H7RloH2H@#?@@b2Q?WGgmT)DqCGv zD7JhaX9rQ+$-7fzGxf$pQzTpA!eBfK;cp+F-G!`&L<_z#t8LSg-|roB1TMI`jdkXG z-V5NA_20MY5pQ*Tf8r8QFL{CN@FASh*F~HCDB+gv-8nV>Ix`wOZl&>ewqT{HKS#89 z(#`5#vIk04OtK+cAY4tW>}*m$brIM(3{$dQty%XzQ2IGg(plTrL2s|5%B6-!Vz&>- zW}?yd=?YF~mc*g_0!U=F^mHT`a>+>J49861+ZRd5bzBqeEA&1{J|q~6{rt! zn)|pCd~P4q49egBAk%Z0nT=U%ug&>}XDq!1pT{2ELaMdenrV+7v#PnZQ> zVs6@;+c%C;Etya_@$dJ~C30C;)5%pqu6Uan=3l=`(r5)y%me5J_AlMP5Y3k$pA93$ z&92!MZt!8L5xg;nS>6>wt?<-=Ser72+Uz5EkK8Z~iCqUn`SQV6R#H-c)aOW+S4k0-iKs0Qfhkp3(lAP@^XX4@6Y(gz`$Z^!_-}!RehoU& zY~aXRHBJm3cs(-6|G0l+r$P(WVIe>>;7d)Sg{-ZN$DzcMyZDh!$AUVa8!$yzP$9(M zJaJk9ZyfkVo*+53g3L^Q`!G;^L|{8@rP-T5xc(UhZV~%B$52H4*mmsI6Z5aY$iR*) zk$E{P`QYKDklZLh%pF=WRY0&$+y~H^lMkvW6EkaAr^hyL*x`3(Y=?SN*X;k!KCw$( zr*V!>=$Q=?^;*^THzD?dQ##FD3u*mbXhTWl29sx`I}ir`tlf5U`%s#y{5+8_mlf52 z6qZD-d)-3rKzTLfXR!ao`RF%a4F5n``lnlnvfH(}1&Md9qm)=WjaIfP%age-4CXIM(qzI~MiR@TuL55}RB z6uw`JyFVva8U}>ihJ;o^Jh>tk2ikWpG(z-^gw$QT##8lMDl^VAYA`ldd|M!v2$a^# znt3%s)=lk41$vg8CGORX;p(m>O>orHbZ-J?XIKA$DZItq=kB2>|M%h)e=^U?r2^J7 z1Z!yu9TZkDLZ*IQ+v8mZdzpyij2Y?JfHel;z>O~{yZR9&0A3#-UrV0vi44r&&Ecoa z{8RJ@PgDLoS#u%iv5$H5`9r522Zs^IS(v`IOhmg^F)KHdpmm7f%JrZ#xE^Uc>fT)c z)w!9Tmm*hnnQyr~%@jB>X$z#MV@^mJV}PsW2sQy?XAh^tmh`KpZut_OI^75pO_h2?3@5LZFZDQ0)6&X{_}@P&ZmyX=-Am9riRZ`x zatgvD;B0;NSM=Xemn{L^je&<3P;Y>KT`WP-E|9=oi|6tAMcoPx6L zE-Z!>ZKl?KsLJ+T*RJt8_pVNj7_&3V>``4r)&#)7bj~shr9G_f$uB!gihAGrY9ln; zhA`U$<0vHP9bdrHTddaHDnYAb4VU1JEw^sH&^dd6c)n9`noiCL`!upH#{Fd9jTSnh&=~L0CHNB`s@|0EwyQLO+N;Y@Ki5d&c*ELI} zeoMKAyhu16m%#9V-6{8|R#0K?BT?+c7=?^^Zl}TyvC`W)G9?E)xGs(vN62SQy-Jh> zoFSu87or`7u=!MEOBpV{5We7V7X#E+xiM` zsvyfS;x)VEis$peoHon_PJ59Qpl(|(ibes%Ze#*FL?zMl4|mybAe==s3|=eWEP60A zPvCx$Ol78EFB~3~uzsRYC1dd}5S+hSi`w=pSE+>RlYS1(lwzsNb3lAegr_fpa1^5_7r4^#?JD~4h`3-=~CoVq7K z83jJRY{D@Q@4EZG zTW;Uqg?RloSq_J!1ocgEL(c4oi79bY!lh*?;CzM00JiY?tyL>_q@(=7$C(UEn}~5( z36^Z?=XOvX=*`WEd%x&Yv1uO{AH~k@ZTz>gfz!MomYApgvR0X7fX$qeI|<Luw_7tt;xvlpixq7ovDfJQ-ZBUC$K%D))Gl==J6t1tamuS-YzsbpKO&bo;qpJ~ z*)a~(_N%uW+HUeO46)fMHKagfR&u6r(j_kbzKoZ=DOE}Lh=6Y(*S4zNgJ>64YBlKu zysy-iQ0%T>h*93hLjR~%bS4_;MyT?`hYq^VJTw`bgaVGoGqLX)OlJ6$PhYjyVlVs!exGPH(n=09avo@#ewTzjuRh(t~;IGy{}W}^xTNR_6;@y&U9E$Q4>Ed*+2LfJwlL0)J$o#AG!L2=w@2M;-nYQT@5sRf~io_uH9QgjO z3sV`bi~_aklV1r4#Jk*#r+3%f%;ff1zG2L>z>?dCusnPv`bs$Qgz%sGNSGZ7jY^ zc#!E4va4w`EtSy@I;LEnuK*n%*TXfHJ`XRp4smLa=7%|~x3x9w83+^U+CrFRU<+dTJ3!K2)?bLh_om*So-0BE~ zljJtz1#iB1y-Lm=cYtuz&n)U?NsxYx@8ILlk6zMPG0ao3bC3}jq8_1^A6k_BhU6%1 zU6vL#TaTOPNRyx_gx@y5Ogo^lE~dICE52LTx0}dUfMfMwE}C224hl?}>n-GO=^}^-3%~W;EDVt;6|Wr1g%>6D;9&?MH3oIWQ2t395P5wk{mbfS zwua9Gi~cbyJA;ps^`Qt}_OS*tXgs?!5*69C1c{S`M^DEn8tY0d<6X;mEZUAP2#zvf zfG%yrgSxA+!ipY6%J)j-;T)=uH6Dnus=2e2%r zl+fs2aS7h)r%ati_10=zwj7lFe*if^#=r1u%}KS&F(8Fno0%GC-YHWrSDACO)t#|5;p7tdS3jomixtea{B9V&yAd6#qhFgFL~lH~ zAO4F7jDH8d&VkX+?B2T~t1jAE?$EaRO|7$~!51Hi!xCFF0Wi^Atc@#ddz|%W(FjnB zakwHrU^tYeT|16gi8S)H3IFl5lQ}7Jv7hD&K{vteMM+ua=#zW3Ejfwu5{fU_b*_@R ze(C;j0UFFED9kK`r&yDy`|+6Xw0GnAq*AooBVc@^1GAb_Q~v^8 zL32lSA-3Lu295vWVpLfKF9?-EBy!ad{7T^d2Gf{+ej$M|ZL#AR&VpmYJNy-YfVIbV zAMu$m-X`PbA5-j<0x?}$YCXKOAv+8>g|9NCLs46FGBu;R5xF(F^dhYX#eyyl#j%tn zWG|$OOkik#!EAK30sMBEyCb^`*Tf)jDXE*~{4>(NoPGpZV?=p#ER}u&$3z+mQOPT` z=I?)|I#EI%CJteMfYvboE`sL&y9inl;9?=<>}+alWMk??Dr;{Huo1O%c5$*aa&@t^ z|5rdAqpYWZs)WSXNY|;Tq1o~XDj)=9iRp4UNy$Wn6eCqgp27cO=(MiCwr=B|*{SoP zld+QavV*uIfqbNoV-yh9b1{*g&SHKwnv?VS32BI!Nr2a1Df?{1gp%D89kwT0a*IeO zPEx)Fcxh1LtZp1yb06a3elwWhld`)TZwq^ zVaa6$tzRk1tJxa2a>fpZZ?b(i=|blHDa-n%MmhBs%GR`82A?4Yn|AOWU@2KE+@*q_w~f4W-x!!4P+YG3>d0#&^Ql$`N7QuG6s!)R5M_EV+JDYv z-)+Yzz;!XY_nq)aU&c{`qqED^vhf4LprQWi4=mDDco!pY;5U(6F~2O>vNIxX@a1bP zX_Ya@Q2(}KtUa1%WHX{pY?h?dLW5-r;Qv77uW zE3W?PkNS-4Yb}#Piva?Ywq{5@HI_gZLf!-h8o^MfJnrTj;E3Chg5fE&rRJsq(Q27V zKx#YpE-&M?T{=&mfXk8pjQCl&)}u9=U@OXBA|Uy`x%oQP<#})R^nU%_^~(M;@kIkj z4n|(njlLYcX(BL3cOk`v#dCfM27FUK*7`~~cmE{n-g6V-cd6mCtF4sG5gSV?US@BN zxh}ZvX-|6oY~_5S2&zWd$ef4^}7+<(L zIHIj%p5gM#;EIX}sEV3~P#Fx!qPfd%K$e?}_lov{rCx-w<|wSVzIk*?W2j}@F*>V6 zry1G{L{$}mFbjeDEs`6{PB`GKaA?SLGiNR>L$H}y+fTd^zP*^>>#p*oX4xPCE_qGb$Zya zn+Q*s^rt(`iDzCKHNp-fEa7Fvf!UdzlNYg=KTK=|XXrkRn<@+LViAEvK_*@2)nb5+ zUtA<*hh$VWgSO1u44noCeq0Lbthf8dh?K4vhgbOT7Fyvt8mJOnP7lM9>sus2<$#X* z-~Fdj=+{GqFa%r5n|_l-3Ei{rKmx^kz8<}SNUaf6^~>qj@h=@kARfAXU)AcLVkEhX zwte%Jdp0Dz1#w7I`=dZPD+?p&h}3U0)ATwCEqI+R#Zbxm_PW9ISV=ub>sQub{^U?o zAY@dbROkx3pf-v`8xzWr>lH3+JPhjELuEynF4be>NGMpCf1wpY>w^IaXa8(bB$Xra zfdLate=l#2lB4B-F@W}`7I^9Rb_wh*^AXs37@F?J6P{&~h;M1$gTzVWegBmpGo${a7fv zS|jA(Wv9soOg2AC0(-@acH4;P@JP2Flzpw($A_JABs?_Gymio=_CoW>mR)*^`Hp=H zt|t|FcU^RuHm!68s_Vu?06oPuL&vbb7g@F`I$@uLR2u6=L*@PWPL$i^BW!$J`3on1 zpK4S{)e&>B0Qd4X8|rTgE@0jfQbVFu`vc{Y%Htv>d>`#0^Fe^j5-dvpy@ycK9WkMp zN`nD`ul&PT!h?FnuH+kUFT>*qs@Oam2A2$rmyi|yASOBvMb1s1R%5mK&vvZso z4l>9iO30)vI|9rx6$5!70&Nnv#C1N&3lU|Z>)8`VPFswpaNJ^-$YLH#T9T99Z;X{o zP_mAsdDVG3dH^6FWJW-dIg22m?SZ+YhZ@+0Xxp~kM>s8{3;7xi$@F!-yII&p-SuW{ z8fHRBraLx;xG~=?vyr)vwdBNDrNo$r4ScD2 zJ8xs8vngCKLlWc0)xH5ViD`89ZSA`Jf^BK*npfr3TS*F=Zca?|`?}6pTZ6JpX;9s7 zR0SF@Nx?v9Ql_604pCvAQnqa%9Vu(JK3EDY#p=9al^g4WdNLlNm1f#$4-3Lp5HpA| zXfLPc6i4kSGC7qMCc4MCgtcKtqAOa$CDGP}fPrs}4D-zW#KxYSQMC=tRpkzqc#_z> zNHw=*Mf^J?$}Q;|wpnJlagjyR++?c_o(&2f)ZKpS3s%ogwzW-_erIbBzaYNa;->D~8+RFx$BjOon-Pt@Nl2L&Kz3?f z>Lil2OMpIo!g&&=fbyzNR4=AL_0y#M2b{pO(QTGqjOo1ghf92%K~bj|S8dL<$?vP= zEBS2}vX2ge`pI$H$!{em-rf|4{#nil2>igRz zAq%Yx1PCd0cQAqV%d9h{Ao|<#%Y?)h3Xq_bkek6Q7mbSHqe2T5JK=WI z;K*6_O^9bEuPQIq>>v8a?|8clx}l1B6}qqEtlo1-SZ2S^G(-&*guOD`K_s{fJniLpd=_O#CCzu!9w$^i1|7LELIruQn!mp zeq!`nm@QBt?l*;sfOyN8NdsOTk>GJ+p)MFWm~FVU;`Aj+E+uGUsW0MY6DNh_s6UTg zn)bnn6jq0;eg`Bdrl)t=&~UrBRM0LBtGJ0b%n&c@kgsUv40TU@QcG~QL=n!7&i z;svNI0+?bI+b#0BmmAv_)5Dn^g+}gAFw34VKoaxZZPfB@kU+=sqd@wo5t5b26KeQs zsWbGK_QBm`%?cUqpIsincs|>LX>jOkL6a5gXi%Mqcy{J-DEU@D zM04};m8kr*M%ds`vpSua$obTu4`E2faBiu^Sw+EHG#Fi?jiS@iSj{|IXzFT(-P39K7wU8%|H zy6Vv0MR}smt+yqi92Gm-XtTE|XRO=!^BHP$p|RM5TIqn$2>puj^kMXf~n9_0znLTik_k9E5hZ%8}r zRdXNu!grGSd;8L1M77011twUV{qfdM{1vHkJ4C6Yw!?aVCAU7Y~` zhX5xhZL8p_p^fxn3Zu-01cOzCH_utE4u;YRH)%)0OHtd3ph$s^oiJi*4lLDWu5SKT z{U#%H5g(34`~>|d#GBsS6l~bweIWAo%Y9$q18H?7Vk87Uap(KB8Ds1_=vW~w`*yB+ok#ZW12>#{v0X5im2Tz zUjML{r@U?|sZo2E@fWSRlz>xNm~w1Cy}FZEh%-(A06N=r%JDBQ(QsqNruq~qLS+q8 z&%K+ix-~Jn_Z6}1q{iPY;b5+_?w94qi`?Hs0xnr(-)kCN?c8YnWVuE{MNGo@Ztc*X zBjh$0wRxt)+1aqR4`K#+ncSVtx?&lG)m)M@=qGfprGX|enKB6Gc0>WNogl|6dMyQbilqWd0#&X@Kv zZ3Gfv0vho`sutETX1^i<_zNR$3ZKz_2}I z7Jngvd6Y9te#sa4WJLmzN&|^ZO04qSn!%(4;&x`<#^)CSMV8P&9GK-)-fWZC)32`u zq=AgoNh2)>z%P2!tS@JBrZ{NP(a2wHp`5D`!0!;B7Wce-*peMm3UdXX%&31>1*bfj zrT9)d3y$ASKE?gS8{*$_@7rBI;4!ugZ$V_*A7XlPCW{3+1^H8#=Hy=kZ~s z>2d@#1zik*VR6MSp;cpIo>sv&4Nd52?_8@p{jf|w^%)_Hy{o~#ZAqb&{vU`%KNJpr z$N=KmDyX7?(qRN6SCW1veL)5ACIzbAJm5gZRH8v>m1c~P2o7RI1oaN3fRPeHcoKtq zh8{D=`(PExvLz$`u)GEf#Z-<@X8eK1Zgx3EHh~mzL&Eh)&=_DIQ&D(BO8yj!(~SccLbwed`sA5g5j zoXV+A)rA5>D9kU*1W~F@(1|!usH2)FU$S6t-ZZOdzfk9*5>`M&W$~zGV=q?1 z*nhwplr&$KnPM2;;6k;u-BPJfI<~EmSe+6$`iT#B)+e@Kn2pZ{Mm@VDbV{6BA`A{^ z*+^gbm@RthF0li;p*D0f=PA?XAvL3IH#OH#r_;8MrMW1tTtn*dZXoyhWqP^PD^3O3 zK=hLqF)`YyT$#4Wz|TuT!yOZq(L3j;{I8YfE{#Nz3>h|p*sJmmYQ%9y2%6X$V&E&+ z{G$zBgoiDw5Gx5#tmX+-W6RV-9}9LUP>9$kJ|{CYNzW{{ZR{d};4EUw-G5|xRNklL ztWtNknigRUF&-DhEnW@H?|t$R&LC6b4oWB%=WXi3f>9YYFAPi)WGUYHnqO{=YkHVf zu?%|$sK%+re*;UApN`Iw=~?{lnk)F6&JC()Ah~3#vUe;#^tMQe`xL1xEq>>#DKaD1 z`_5at4Fq&`euB?iUGQM-feUoK9mF(G~??d>qo?kf#?R`%fUe5K?iinAeZ zpaAEjA)7x@_QuuA0}~)`;4$OMt%Dk$q#=s(l^9M94nK|vSJsuoLKe-aXrX@!j6f6N zhi#FF&7uQqG%v2q9kQHk+BfMxs=d$LV`F|l`)UX)P^;=G-&e`snj&SPc?(k@moM8w z67cTSW*ph&Q~pL1c5@5KbLCWi6A1Q76e8tANLuS$RLGv?=G=W5eoL0XhMi2ZB z8Z7hLT6ehi)~ZE3BW=x*l8POnbCTPR@X_wh6oJk5$>dk0&hX8xmuek;3slQ9q3S4J zgJ>fu2aW0r1hU_tMAFI*+{(LX)ElVlW@Moq3Ohp4l7Ul3v)Xn)2Upp+jcD#%gvLq8 ztoJNC+lL>;DJFBFBU(1YW%=pBYn2lS1v`kqtZ;PJ4KwYo3a7?I5;ojSpG8GxQZ_J= z?2n!3XFXXW%{hOsHfYCI2!iXuoDZbyRL|d&ZF+m_wTHgRX1jo}HgS6##8rrwLlQw= zkL90A#j{>_aF)XDSVh-_ysI?)7+QTkMt5V!NG-H^E>(b*79`G-qV2196y2dO6Zl)V z=E7h%2|PoRE(E8`vNcxp#JKaY*@J$xde?8ABZOYSwMKVq`|jtMzmw;jbe^N%IL=3xGyO}{qlhm&uB zc5p_H$9*V7Bby(E9uqjn8@OkWQoF4iG7nAU;SDS$5k(`>jL?R(q)wm;*fcz^?q_O3 z_(?$*`u>5w3r_ywpfM2T4Ps5eISY%>AlDO)Dv?Nqo;7Ob@k?aE1XnNJs#29!kVc>y z946j2Rv%e0=iJ7D$O>PfC!%(KmGG{ygcMC`Cc+d zdxyJpx!VW%b_~Pt=$aW>vDGreQ3qV)MbqeBYti66sIa-6c3&|A>H9X3&DH&LKMCOb zOV&jjtuLKaLhYg6`cZeoBXds4_&SWxT>2TP!s_^@#2GJTdsqFyW^@D6fFg2@VNS}A zyLl(9lW)8tZ3LkbtLIwuClVBgB}G43sM}s4)p`X^SEvzc*U@e#40zzf_BQo(nNq3=zI;_k?xD*y11BHk$q=InR=H?hy zXLf(Lvwxo%uDEki{yxpJ@Kdp6*m1u;!q|GmX~AHNLB*kM$Aw(gFV(3j-s2;P3h|>d z**f7GdkcTGj}L2$fgTk>MmZc@S^x9aum7%``2L^%Ou+E3RZvFGLUuMb2DZl17PcmTN+?&8|6B@{t#ToY zDS*yH6nQGyje=-%QLLukzpS}QxwN#FktT0NNO0B*ckyGjPcTK~Lh3=|5>I-g2!1Zq z@^zY4B#EYg&y{x9+iB)y24?VvkI(m;`iLlm4~!K`XW2a^8(gsN@#w&UoV9K>lop$0 zNsmSu2P<1vwPgELbx@T>cy)vewYAt90Xa;e*n8Rqi7m=wY%q}lJHZ1JwHw$Yh|%e6 zTFRL!M=;W`1(4blLCJ&JI3v{Ad9SfWz1YA;g{2ZEqKc#6y* z>VRUf)2hhj`Zo`I@TN#d3?iS8^NG%f^GmAjImmX7u3U`4<8kwxg%lg=;9UI>y>WOV zPUW%vBz`B6L$0RDtCPhJl+fo0AltpiRrf%NjCQc&X8Sa`)+}|L-U2hbTu6)-`-S@j z2z)C`e``|ZiOOXYVY9?r>$tNUHxemw&xy2k2eNY>D$wJ+QNhPS7aW6gjO(0nd=O zpQ0MHEcZRUT<)apNkEF#UQddanSQnJpb_<`E)$i&1X&o%rTnC;TE>#YGlX3MW{&$? zw0Kb#`J?WNI+ay_b)@eD??6IL9gk-Go3f3775;MHul3|#r8=;<9UBXINUzYEz4U@M zOoq#$H^q}r(W*brjbw;io0FIcNutGd4WF{o@+r+E`m-tp?|-)_4`s-X%~N%erhHQ* z=7DUR;65>=ku#K$>w0H-pos7WCEvHuE~UGQVVoYz?EQL;MqI68~>W z`_Fdo|DL&ORVytlksrRH)@#Jpn+O3Bgu$AYwsEMZ(egh6HkRz`m+)yUQ%xYH61QF4 z28TD{@6Milm+8)ecz>Un`33cg@H9dO7#H$Wd!(gjbDT`Q<#3qIyx*4D=LY2h%GC1rdVfGBr8H`T6%7~QfoM*PzRM@%QTxs87i!>HDuJxpufV& zRHQ%z?9G(Juqc_C=Po%UI#n4e_!e1Gh`}kO#+}<9sb)> z*i1!i1?n7JNH38^tG=mXIM47jQ?thyF`)j`PlylPS)#>S5Mwg@)D(iv5Gr7=0C3;d zC)SNL;Qc_mgFHV<@7SV4{BVGjouClRjpnoOB6ztpM4;GTiE=|1PT^N6^tZxE8ocKFAn^TK3g@DAR8LEa?oSWb@; zYz>DJmdHx}fEUkc#be-R_j>n~US>QtnuK(`ao8^FF+_&Fb)mIzIfyuhl6=B_dA zx`5Lb`<;+6DdLd|iB}|rAaDMInhU&GQ7+rHgi{U`wT{>@e#%RY#5*KZncCT9)}m_; zZM9WpSmOI_Sim-HJ~Rg;)pMYr#v^ z=o+aq2xPQ+$IVSLUEZCoye(#{;bgU&k)>#ErKfG6v`r?O<(6&iC}yfE-bH-qD8u~H zp8cE5P?axHDT(1YD~1y}ZsHIgNH&Ma*q-FL0T#!x28D(nk2xcVWU^177<(-kRf{}k z85(KU<19)R261_BeP4mh1#IZegg(V(rSzeEpGX0E=5rwk%~#jP@WFieDxhV2-ywkg z0^5ArHr#&Y7!$fzS}qY=^sFxfzTBLvU-wNOsB58?y1TVUxbDXp!H*LkJes3UC+2Gq zA(nn05YpzugP<1>&LF}lSE(Sh6%vQMc!jxMk3v{Lzsr~PUTB5vr!PU#1Dk{q#*fB=@16 zm;^wDA-$nDI~Zo+TT4jKnTMO=A{))4D$UIBrQ+yMQcETfM#I+kL1XI35f!ZW-c}+w$38%Mke>Ab1y>*2rvdyt!%O>q+oDu( zY2wyd+DIf^C}fYoSLAHP&C-EJJf-+s|LSYVC@Wg`1(LEH4!sUv!uDIvX##;Bw3zf@ zSZfpoe*yW0Y}^I?p+o-|Z&7&%!PHkF%J!MmVQuD}({ z*S> z&h7*4z2*7@`JI=HfNTO7$|EI}Vh*^~9dSvq-c}rWXnpw!e=CL|#UNfZUmsf1ZT6Jq zw0VE`@*57wg}tSpP(Id#Ut{3itO#*ZehzIs0hHuXNS4dt2Lr=HTc0d7V-kfV78Ga& zQ3?YlBjQ|WKg36p|JCd#zI>$z-)*<0K@ft@=-c)IQrT9|Yg!REsu9)%Xw9#U_LP2N z!T6as$fQng1nO*YVtdPmIn6Q`rE!s;Ejp#EO1v4Olan#gLRlxMN}5dFnHf(fMX}N` z4sG@+<(PPMN17??Z+n^8W}*MPq|&a zQe9YxaHvz2zcMcwsSg!YARlhr0$tj4VSGsHO==^wM?=rsyc3G{LQ}`*SH9Jw*@Iov z$BgkCs$4tKTTVVg~>?Gf)HDlGy@rp+nq*^>|13OX`^U9ex~$GIp?+tp~cTvbuw84<-svp$?2LHP+6url3Yc|@2-dy4-6%H*3s6I!>2QA%E!q6RaO)08!oV2U+P!&@QFs3=m!AY3NK zsW79;PJZ>%+sx&wi{oxw-&{7RBK;}rebyhBLJC7Z;=VM9mh?qC?mNva{bbZ6OD)TM zZQ%1{RZlwnvOs4RF0x{n$92v;;uB2++6YQIoh# zx;D=$H_adU8CNEM8&L`VbII*JXU_mOe7jXDeR>cP^e}bjO0OBesL^7nC9RU`y(g;` zU%9!!Pk9fAlrU3Xs!Gox{=+2a+k2LYPx$L zHrO#AQAC^QQN1H^eOu5E(Osh2Ki#s@qdx)%OLY&lp;OwQYa4ts1kHC^{1WMGLYg1gw+)4c;wM|a^ z8j|AOs|LBEEXM1OhOAY!WH=6{3}|}G2emcIusiaOU$LY^h|D5bR3GLWLqsQ$Er24D zA9-%V7?Ea*avEaa{b7Xa#4E}Y{^E(0nwjtnllwNaYF-U&QUA<(jI|%TXfY6UN|Bx{ zRb$Yv1n4v5NaXXOe*t%+2s$EW>Ucu+)Q;v-*Ew$cM4f+z!}X2T@AA_Bes<(eT=+X_ zzT*jz!Dmx!x1e^1^(6hWucpW|I1ua&PyeQK?SzMuzFBXpxCsHZmAcni>7Jz^_2qIcCAwUGIfB??@nCOQGuMq6LUMS z_1A_P7x)TIG}_i{2aKbHSR_UA_;kFCK<+AF$Yg;W=9A`~J}MbqM|fQw-rDPi47z6w zi~1HDZc890$QG64Sk;*m3nhly)yQ>A%44&f<<+l$gupwpGwRN5LR_Yy|*RViwlKe8FZ$#iCCwssc? z#!ytiKhAzDiMC-to#`=ZjoHR$5@o&W>|AtX0a=!m=yI83l2s3i&8%bZ8Ee*=jGFC) zgiSTV5R71;O-?9MW7hHVImJFQtB8y{QU0o0qx}}hKz}B882tiHtK}#fAxXgdH=BA|!5Mk4hYd1(Gvac#M5n45ULT$l@lrW`np zf5afTRFSNjb-c@bT=0Hd=7Gc66$0!2?3}e0v4VPqtY<=TA_$>;rN*6YV5+ELQ0nX=e29{40ju z@H4{B*L}GQt&^xKPOE5OhBK@7U{>8BxfRvobt(ao`C^MrM|jiNk#vp=8r8b-;{=7i zz)@&mAgIBE-3eT24X_+lwG&t(Air^yQ0|DRGv+`O10Jo0)i*;yDbZW@mNLxWTb8~z z#Z9Rz)sY721C#~nYCGU}z|%M8Tfd+r1z;8C5Kc@nqWcZ?k&}6VDSq!hLm&4Hux6y{ zZv6+sqWdAy(yaN#M_}bQ@6oH^9z64P5qFM>gc*pdJTEV_c@qy?G@r?NUDGq4w*jxs z?=?;0LCn#n83I((gwppOZiK<`IBm$ILfnRIhvy8nz*BgA$lM}zkTBk5r-o=ki5Zf( zPL~Ky&B~@z(H=4HuqR)+27oh!OnhH8ZZp}WGxA0JMR#(4H0e>n2Ax(TY)aBJ+2BKf z^(ga_-d~I!_fA^T{EQug6#tBa(54K|o}v$hjxPQR2)Xn?jQ{cuzV)I|RhJn^SFiNvGvTSZ2{6?MKRL zWP}kUiy~PJBr4I9jSPm6)R-0wb_6L z5K}<2-G-bQ;+OH>NxBTZ023UTQ{D?&LsV5Mi)cP?Z>gX*W`khZQ*C0hNd)kU;Rsl} z@H_j9KJxUnme7Vr0*b@ZTpkrTIxC`04S%FQ#^@4YJ4fIfmOCpUE7-(Fj@PaNkgugC z6eeR{TcxP7Sv#;wWulA-!?6JA=H-$Hq@?Xp!tL+-i6;>SWww(>l<^1UV!X{d*$LM3 zhxh5r$qpg5GaBBL+kBQcf|qO`G?E z=$H6XI2h5`YLe&&)I-mRKc)EfRJJYx@)HLVba83!#(uIU7y6{Nzq>tL-Ocfbe0^*r zt;`8s(3gjOOM(4A*f~+P!lXI?yt-Wip*am%1_ZIf2z3t@Bc)Apk5jL?0= za&D7x5T_||G%68mhO`@KUJR6_!v`B#dmfL3 z+`?2^Vu7`)GG#XxT!on|Rzll7XFSh54h3Ec5KE&LdQ3H5nQYd7XaurgE^NAdDK| zPh;U*W(K@48FVm34=MTriV1VsYlSK|AhZ4J#JPYTFf<%U7`M29xE zTzcHLWJ(Kb4_7(L3*m3f$M&l(W1RqI1|NubTHzZ!SJSx5zfIO1&??yF(spWlIcgWm zAV+h*`jKSJc-q9-F`nhXTE$u>DkxgZ<1jgN@Y3ZOWW;;cC@_Du73}}QRe+YoX?#wj zGwvy|ByHVshS-17+l~%M389!8@6aK72z8G~(2nKxwG9TY$oB_(X}}ULZsiuzb;`fo zY%_W6yyK>E2DL}_eVuiSXl(== z8y_><|nS#0)? zL>pM#gs$4B8kp+O%uFreDK(<&w2n7aKj`98Vx(B*@r@uNUFA8(+g~K}PI2VPenHXX zWaK?<^MLot3DdfEj1x0uO+~)DzKQceu7*ICR$<0PA zphr;`d-=Vh;79xh$I){PjYTn(KLu-sao-SqHpfz!yqOoOa=n_O%N=*Yj^MQ^M;nVr z)7A%;$u`N8ibY)I*XOhR9=NxVv(QhuLp=l3vLe|i0$E9?Q_poBeFh)p20W%H3Yf*@w&VHC#?eg z<9=G@e?O=Q8913p*gBcmI$1bdxSA+AJ6hP9{rQ=_f#ZL9tAWxV6)f=M)3nvO*t)pI zKSEVr!7~U&PbwpglvD!tRdmVHi!?o1XVAw<~LXn?DcTXkhTN zeTrlE&(+B1>+=Dw56=Zh9TLP3&%=8h#IzUvtW17WczjSlx6_xZ%Y`1@#(5@50@n@~ zykmJU@2pI&Oisjs(k(8>N1S7PUCD(r9#PYz4of|1|h(B3}s za@nKIXvwgaY?({22q!BH$C{$e-Zoli9f2n;Ix2{FXQW5}qqE=t&8McgUdfD z;K}xd86!);S|R#M@+m?CX+7&nnTsQ-?p*(9gE&D8&B}dDUXmd;a7G?XGmREq)vjM| z4!2XC%_B0^V@ZuZ#yIcy6#wx8ZSC%Yx$NN?1<1vge%xgV#|;AuhHe}A&u>Dtj;l=7 zzhOU-glf>FANadcO@pkQ_&ZCO2)7f3h6}?r`agGHd+%y~pNK6K8VMI>z zX6oMjw$NEW`{C@uGG)vHJ{w@D?<2-Fi6@E1#c7v%@SoCuK-2m(j*R;f%5`Ld{^U)O zU~CLven~1|B943<0gI^qMMj-uChEuBDNLCG@f%A6dTf67O4Bt;Q;JbZxsa!Su}u-t zWP$p;f0)U-kG88YBhz9UhtM!_YV~|q!E&y>?|%JpQ1fo^Sl88vbt*zWDKZI*+yGk&@9l4g*OlCz`$#0cGhe?2v0)XwT6W;H*MIaf6UIf zI(40nE@3gg7Viu(c_qrOIX;IH1kRKMQ1TZG^0o*~`3T;eT9B14DbW2e9;F zj)~=UMMeg@-)c1gd8`eqOIq?@*k+P0l9`o}OAU%F5N-K81F^<&n4pt~T%F^7(4=P-tIj0wTux;pb=oJQu?~517CYJ4 zlxM5OK>(V)<%OXk33S>aELt}6Z`>%r2~S011Nk7ClT{Omf7=s_QXpw5QCN}VMZw+w zG7Im$Z8BunO4kExg|McWue%C4h+F944t$c1(x+o~^{DM5>J^}&M9D8x{9$eo*SPAY z?i5}GVJp-N+=0hDd!QUoUl$94A3ujvYFgb_!|^MTPE7=f7EGzXFMXi+r+7kLT*-a5 zqoJ66Fo!y^ffNt=Y#cS1CFmx*unlBrrQjF?oT9P`eUZMr{pWXDzyX)@`yowJo`C25lYH z?I|?!EJEci`dO#$2=G+yVNJAI$gb8{)*)^NTGxbLx+_)2!WEzoZFX=CDQ@p>inl6<-8s?yz%m zC!iNhw8mSz`SAuLZir2`Y%@7*ROtoPiqmw&4yS>RUSX)QH%lR6jj5_EEYd zzavI6mg_R3a$!SB!vkdwy6gPx%F2ZmlL6mSU}pAI{2T_}NelMcu%6lbHRC7MHBJlJ znZk$B?{}*QW_MC!!6cLVjK`%VaW6k%TpA;ZZ3Y-VT$+)#L&!jU$1{T3L22{69?cL& zb%3IoysVqjeu=Z7Lvz}GbgBe>sJP1^5_JCj1Wmh0chA6L*zEz`IT?J#?l^YV#Nf); zGrQgHtN8bm@;=E@ zWU7Lk)k=I0K<*3kTdm68Ss%?x*OpcNH9_t0hFYAfOxTHwa@QkHJ@rbI{xI>%qws2| z>|RCAueHv$qI=>R+|d&Fi^jFA%pFyB8JlKbU>lQD(mb3(52_ROv+FI)nRRt}>)T`C&-8tcL(#hd@2~fVfHSe13L>U#_@j-(rzJmp<)%ziP4)4CxTZ ziND5#aYxQAFkh=kA{tsE-pKOb6z%MuTDJ-K0iC)BD|7Q%N8g=r+dA96liJtvk1_~j zk=^?DiJ9-{?#2!CmA#eqjm`2s)5rP+us2u8?w?6uUx)Y5$Dmlu04$9l)_d30Clmj7V**$hTTACowANdUD92^H2LDR0<~_FNI*T~Nnn_y`-l zoPQqSAdQ=QREr4TI`!r^@%90Px4?Ex(nlY!Bw8D4;0^Osonx%1O{}PwZQJ%yTV*3@zKe7_9rBI@_vp zbkM4+JS^3d(ce**>gbT9Mf`+!HzIU<=1+Q!F3xv7L8m9HKcpe5QD~LX+(4GF9R_*F zSrCSNoGRHp$3Lb?Pr@TF9Tism8%_c>^iX&Wfo-p}V@|%w8CI31csbEShOA9=2KFs@@4{S{ z+p5TUwnHsAHp8P9zqi2xd|=%|QB#EQ3~0`T(c3O{i1Cy(K;{Uw7Z$Zohp^(V9+pS2 zLl#}lU~dWD;+W@u9JLMHjY@$Y-9q-!_4!$=twpN}@RXM9$wW7>m*9+pDwsYrw6!wMjOrL$Wwul;N`i$fv*w+orurgt7A~aQRkmAP5H?No z;WLBpCYIg5h|v2w1UI%-In0>|qiK6ZC_9fTBR)$94s*2FLl@|fxLpE1jv;l=#x!db zT(K54WK?rHj8Df^1S%~Dffdywcg8Hhouow&tIs!Ffv3@zAQ#hZ68x;yFb(ZHj!NFj zC46GJ1@07@`Telu3xgmfl886;w`8PA^-Iv>nD}!Kq}Hg+pkN;C+cWt{0d9@?@#*>^ zP`if-rr`G?Sv~f`w}noJkTr31-^exw4;ldJx2<~#h@&D-_#G(Vc@kSLwxTknp2z)Q zi^;~Hy0345mr`xTMcG9~vs{m)!GEACDBZ*{N6;ZXClYsvqR=0VD*O$Q^)n+NWq1|I zQsa~;eCOU>MvkU_$Z)@-j460_AmlMXh{yG_`*X?}07mD|$T31_Bw6i^zPF6=I|dL8 z?(|OpZ#C?#XVq{n;K1wHALtFBQRN+y?#0$5zl_XI{RpgofkD~+K^=cZ)>x~~zCu7| z51=e18L7??aQ#y9lO=wdf&&rAcqpw0kev80GpJjZMp`SVpQ;wP)-GoW7Jw8;`22bx zUllJ*BPPyQJ40M*%W%rFs$KuCR{dSl%unX}LJ00hD3rA4@*?MOM%VMc1b){m6g&u0 z!7AiBuDa$#xuCTgg{Fw6m+`{-v)yAf+${-jV;pMmq_QV+>Lbl??v2l&*xwmD?J#an zye24>6{8gv>A7ObE}v6+Nh#;>t#K!BpLv2R1aYWM0fRsolO1~I1W~wA^d`>G5K!)P z9DJ`#4aIH>L1vI*vRt~o6eiula<3AiUlcYiUO@?qzi1%-#|zF$Xj>alYC(!V3HD7f z*BFKY{I#FA$7ephCZ+H(f|7G_xRxfl7uv9RA$tC@70qv9a0Mr#``LAK`pG~(y zovYl`8-*2^MY)v%Jyi2FzwyeAE6vVimjIE~Ya)IGRt0};p=>nbhRB7;zs+n@_!)!=F*Pz_eLj23-tgIAemE-_77Da2;>!q&+9>tu#V?U zrVY>hoy)YD=ZakY+dy*o`V>%IPTBp<``2Z8AiO z_m%7*h&I{bcFn3ICRzM7k8+M(%4wQ>K@nk_U=+Uf8?Ui24E2vP<%eqSdW_cbRd(C6 zWL-NaCK#5={41Q$HXBYFwK9gW#zc8ayK_&I6PY5-g^=ELFP<%gE zflYFpxCv4H5GcRJgoS^~!oOZegc}}yd2*{H9gF*aAwvx-M5>Sx$uRLN64L?cY$$*1 zN$I%~_BJ|tx_iB_Jv-rJ=m42MBh=P{d<~~V<$zgmHpYxkEIFBuR|X4CPah$p@ohpO zVfZtOd8qTF_Y|vP4;$Ef8x+b@mkOlwM z38v&t_wdam2C>BRWx(P!%SS9f&k#+Yf*g^~`us_O@+$z*f43m!_h86k7}pZ*T%@~r zepy~-j72wM{}?v0$k9jsm@!&97)C3ajtJ(7jNv}bz16d6lI}ru1 zViw^_L^=pSPmCy&5161|l1tpt(y+YrS-c3lUT7A!U_&I8&`)e&$ilIwpkg+Me<>G9 z61{!;CG?@3*m3py#qIec`9~JE>NN?#dEC4a+4eM=bIbTKULQaX80jB~W4UlQOuIs&K$#C8V0e)R%}m45XE$>r5uw8PvTs(%iL3meqG$nMo?$yn&~ z2TSOuKkxWbgR^PkNmbGUSXLnmKy;D=rw-xR*ccIcn?1@VrFT%#2{->Q>|@KSOJ>3_ zU@;&@_#P7U^$6n(!LlMkQ5nqtJAl8T|1k()fTM=zviP^?| zV-=KOfS+(!2|&2dX;vaOD*7zTR{b8{H>WXs1YSRG)KF-J2?iaBW!OBgc~P#v*@w$T zYuW@lgERWSo3WfxRO3f9qt%k+D_qSJ5g=JKMzY$+Q zft3aN$_hrpZ32l!o}cVV&c22xa<~vSyt=z z;?)|6UD!)eQEgJaV8G;RJ>OJ1pAMQ3TJk!Zflu9+v?vMF|EBEwG-&*#bh&WIuc+k_ zr4J0?hvFPUsw1S@&$84Hu;r8D3d;P$wQO}MRP@JAw_9!5m66yBQ9t&TX`QH-@8#@x zV0U6Qf$)a#1OUM)-#X~@_xVA-yz3~W*p8on_}9JT%8xeXK^*atz>p?XM7+Lbl=;jdt`vT z)iNJ(pRj;yUDwah-1D`C5foN2$htsYDC2I)k0XBLL*fk>&c>`uYeM$MtmGQ$L`x1=GZJ9MlO0Ong7!PJ3NdAZHqC1@mVd!tn>L@|k64%)FzYY$QUZiV@ zqXBh7Y-`5Bgj@mnNhDcM8$iAJAI!^nG%@p2Ov%s{6#U_glZ4PfO29{h_11`#e&YhE zjcK4^vc1#ZHb4H2+InR)N*oQ)2KCb)ds`|#l=jN=T*aTy1^#SH7Vr?~kx@~BJhgLjaj+L+pPMK=7s zn{{8`5V_t*PlMoWkdcxv|7MC>Fm6OF6LbWinGsklXx$L%7hQPNX7J1pnNZ?5p?bb5q!Jj zqLd!%iU~CY5E+m*vnq_~Dai68#rbqbXrh^w9eYexmlf|S}2zAnVZ{LH7%)>g8 zIrG9t;=pK$e-5^~6prF1H)GU<7^Y~z4UqlsvOH607WX!#@G$x~4ThsH zNh_bx<%k{W3bV{#n4HERjyb?@ktM)!uZe$;imTAp!cccHx*5Q60jR@G#8x9W18nt^ zHk>UQjUDEfDZ)PS#(M?CMwmbxO`{cOZt-^-`S^bxyjly9;d}Xf@qglcXMb-$zkf4# zkjre)-~B*lC#)ShC`kQ{WZ#ZIB@i%IlJfVW_K?HZP^hhzOd8SQNFRry*NAptLi!?= zI)V2EIid%K|J0FJm^E|hc-$W|nsjw(cl86$uVW)ev>Rm~E5*R*OVoeMD_M_PiN?{o zUf(!vD)WaKa`>c91q>xz$ilXg3O8bqNEc#Dwt|ntx3|6_Jq0tnYb!l|qavzxUV&;x z0BfNPbt4t*$Yt1B%cWZ8yWKFu%VqZ>%*4C_ywMI`8VCpuiA7$&RG1;WV|3LOpQH$DU>pxKt*;1*iI+q9tN9#DuLx;WX1M%5%WPqo0p zT2(HkFEqKO%UV~~#`N2zQsU(APBxqVkYev-rv%ddj)ivk;dGCGedd-JT0O%L{xwMX zr5IdVQWzhucrj4ObjiSrjJ;Lw#`o}z%Q^%ZvJ<~Gf`StI)6zJ733-E^ zwunTGUCt|xDb+rCQTRgK4}#UA zl!$8{qw&Zl0Wh#79N)A9u^g9}t(AvR%2vHin5>!J62#EEQes%!LgvOhl?SaxfGJmv7Sge3_v}{2lAvkuzy5|%1w4aMr!q)r(E&-<`8_xBEakA&Po8l+li99 z#`At}x+q>4b*;*>&caxw?t3H1_)wD<^0 z9aJUv16aP%R@z7QBrbygGVB(Ct7nJV1{xaRq}sItzx<4s?wxzeU<6&>vyN2VjZCB4 z72I~3=+VFs$Npf{zq-rC84#*3xe`g~G=NvjwEH2FjQa5MeUP)#{~7_l+;Xqnv(f${ z#<3K1r=%_RevQP`mBAH2ER5U@3qV4`J&sS3_y_wJ9)1;p3IP2LUmy95mM|;+&hvRh zqd2Zo08%5`T;{b|#h&cPZ`Zc%IDBK6wL6z( z!|Al!>D1dHw|8yRrGNWL7-=C~0wQ{nf(!ubPb+E~;8mjSsiFy_=gGyzIZxPFsketrIM;6YO;l4dN!1 z-#%^cLSkvccZMN=Xse*HRxg{^A*+`$J4lj1QS~Z)E_ec%zC@Icc z^XkP^a1n#cE2XZ9{Mh#lcASx~@r^{w2`*pZPd_<@>J`P$i! z2%%~?ik1G`z4Q-yg!4iOXl|Bb_;JRth~gRxLc|+Vb76yHa7SJ&GJCrsPbnj@!Fc;| zCve(}aKEz*u={QlQuz@?6fWkz)23EQc)sivdY3pW#h66`WNc-BWHd{Vi?yus#TVXa zZq-$DL|CSV4&P6C;D* ztTZlu5GLC}x(MH~>S0iVT1{k0TAp>aC1sDREM0X6aZa&90!K=uo2Rmul1KSRfjt#s zwPt}M63SF5$DU3+Wr<&RAq-q>aD^rJ%c-t9{kW}bFBU101>_RcqNMBDUsuFxG@(FZ6{ojXy8d#bdSh4bb<_u4;A;M z&Sa&AFRUMW#c-N$h$77~52I}{(JEIXXq=3+5$Sq>vNksmH#9Dl?65u#iQoWUXpn+Z z6klmp>+X0|`lL8)s&IF<7cO8{VGW`Aj+|&HCbDt?kfIvTu^dP`RuMfGj@H&pB5=C^ zkW%Rnd$Y~tR16kbq&W2zMMO}E3jvOd@9Po$CEj9eVi>>#@*>Yoh)l<)ZqQgUrb zYAqF?56k6p8lwEF>=97^i)@J##(^-r(HW@PMT*3f^e=^^#T$RyL+!Lv`55;`4xkNY zbI*vp)B4wIUNdyQZUbmK78M6Fwz#}yW33rY3!{@}8&5U!a4msyuRP&05&N0fw)%MHH+` zGu?#vT1p!A&(xSq7frpH0`_k>kE(L@b9+YKcHYA3{@u!YF2D2ax}onD^z}$R!G7+r z=X5G{HZIE>bx?6Q1gH8LU3j9WFR=g8xt>U%(>;(PsZW*@OnazU`ar4kiB7nythn9> zP|n%8=2AiuuEQM>IFf5X!nzAO0A-w?`8TC4DNR2%zud?$6RfSo0WL#m`X zwX?XhE07160kYVqqdq#6Jyq0-%R*j5;E5AB43y##Kiw?Vt&AMwnwzz_I7JB>S_*IaE=$zf}=$?Idv+ z6dJnUD^ud-6+?EC#8sN)eA-+nUdgGHwdyVNT3T^-qMht{{4zmH>J(1A@;!qy4UUbldC5Bp+L9leYO+?)`afXDa<(&DH)!jZ|rN~ z&hj8ldqoum{vb4y#>>qpu^NIiMO+DqN>^lWm9bj%V!)tKsfg^M%8ojPNZHT+Il)f( z1=L(1zLEK9^`#|Em?5H59eGiF2cw}gvXn~1EYvjCsc`4SkZ>mdnxd6l7S+B9kygQ`^G)7H*E zwAqVA8F}lKy=fOOx%1OTGNjO32B|u=N&U}B+N_DpcOFuVdI#(-@d;8hI%yiXxzg<* z#0Mu5ZIrU_xrHv`x+_$L6m_5MAEsVRvF3lmBt5tfJ{>?Jd=L8^>ZNTxgcOX4uoj7? zaUSJKtnXi@!C1?ot~A*9DPoBHnqm?=TDwLbMAa(PsYN&MY-3!(oE;7FnYM9Rttapz>0)R7ULXwuO0o;1O%yi0V z`HrTq>o`AdFPbSc?VP#GxMr=W+3>;zN3{lzn<}S1Y8z1yZMDX907a}UyGrjSt4k#K zga3DKlw0iLqie=t--tcrruS8s@zfyfkwcWCSJ743BN|5%Oo#7bdkc=N8#O6|30?-% zJPmp_XnVm>M`P-+@Mc;$DZ?cMI7_8QLkPJZE+6trpVFB&4y@TY4v`PA=*`TV#LKA940cz>tA^BqF2SWm)uUs#&7t(gGDa7A>JtJa>f0A3THfs2ZV^iq3j_ng=b_taQ zhH_ln+$={~KX>k;M>!%X5^IDuEyb-48!#q$A?`J0KO#Q;=(`h53F}>jq)L$R4+z!! zOH;H7ij1ZBMm#8R;PuT}AS(#2TkjRWo4$OJslcmUf|{5G6+I%>GYw$3?W#9|R%BP8 zx|A#iVwsdOo-XZ7a^XuswhYq5XT~O?eC)q+mlT!SDN0l3`%1jq?!q$9w39vT|J*lAc5)HZXtEsL}f#=W#!%^ zygo1`cbJi>PZ`}ow@1rr(MYn$zy$L!*^YYD={J(F#BXmC!( zsqu+*3(Szx;nVEJJa4tH&^pX~yTOw>1vW7@@yL6Kw+%ZTUd(nUDuB0c_R3=7p-F0J z$ga?Z|Jm6;>Yy6Znz!*Tm8)`pyf4LM5hhm&s**ftS5KdWv5EyvAEna1js}Q_1yuoZ zCj^lSZhwo@LCwk8YdOpty$?c`3%02f(anF{cx0$ydk!Edv8ds<3-vgVJwsy(eZsh= zV0gkFY+XRR5ktmfIc(?VS7I4qc$||K+IAtmh8o-0VH~lbSGR&jm-U?C+UCrx&)%6j zow}XbyEkcrEyra(Y=>x{Q}N6b<3gv21r z@kB%>vi`E+x-j75Y^BK<3ZBYjqZ1`gi-yU$2d=!c@-|XjJ+r}D(m-WGi=@DO_Nu=M zHXNj@T$vEUPM2L1kB8MjPkb6K$y3V(O{Qa%;5B^}XxqpJJstzRuj{f*jzoJY1+#su z*^Wa(AJ*35R7=UYB@Rp29c7b&BW{V%Zvg{ujL#o`GQQswM7-vuFcMAk0C@K=-e8v6 z%$K_9KUW!g6OPwqG0yjGFXrfG_}RuW4_%6I3#?pTnR)F^@Sh4p#1Y{nT9nuy7@n;; z9o2R6Kx#sfxCnn`#!vY~9LgA|NtAke)-&>gAjW$5M>h~_$5+&(jTNmIIr6?0X%bsp z+4pF63pLQPNl>~B=c*=J4Z!1x%~sT?L9d*v zEF+WVe2`D(;kr^yMo+PGw6luYm9JBrRFa3!VjgZkMoaFTR5&Q%ytl6X%TX3pQ0-(m zcT0Q^VfC=Ld8KDV6AQC!Q0*EdkWLW?OmPN$x0OK9i85}z>8FZlOclF2f)|#TmF=d< z3R2i?LUKt$pm!R=8#aYP4QtDlZ)pB;2Px98qZb0fl~kvPEq1YbKVYR`z_fTd{`}a; zSXK}Zgh!2+cUWRog;nvXGfZA@VG6pRaNCLpRGpBV!3hjFxIXLbLo=d$*LjQLtvoZr zynKGa?`D8pEg6qC&afjf@0w_Z(A9+-l3aR_7a&rjd2ab61{sk&0CNvFM`fr1XsJp@yk$pWLv@j90XCPm8EUA@HA;TNzqL?sY?RO$uEH5J|j7`zz>7ZPGS*Wwm@5IZS12M-8HYKG^gQ;&t;< ziva;rlzP^yiTG+qFnlmJIr5uorTM*sdie?e!@RePIV=rIY&)lbE2M{S`KhU0`1>!2grVuzZ8H`LK!Y#CVQL zhr#N@%62nZGPy$Q8zJ>Q6$Z%ZYBf=jJ&U;o`qHhjLjLGL0{{bam=~SQ}qJ zS;M-Mt^DFF&u~WPCD%#IHVYWgaET^eP`eooAmRbShdzI0l;@}sWsqeR7<#H&9ca#J zWcR0F^Jn`!sVE}0YI-v_Vd&_(oFZ1Ei<$L2VqD1eyjzqS2dxcAwL{WX#HduI+S3k@ z>n#cqBclrV?Jo{}H=*vO8PlR>6j2ly`1ni$!FYB|xJsqzdLjORccqU{dME7$JZQJF zB#r`rkGYxzQE6Lqi3@`&Bq!W8RHw4hux+zDGTJ>=^?ZlGCLcwjFP%0@ApSo}#G<2? zG^G`9e?>1=BKR|MXIl1WF5ma%m71@O!T59sJ?-l>IKgyrlVh)0l-V2c;r9Kp6Qh9} z)`x3nf~x|daT44>kHQ(+exU%?2pvl!Wrz|^b>LInBpZKMOQf>dJLqzo5|AWd2}cc53mw{G8* zyP^GOrdNiMkpy-wSGJ+eI61}xv_v_WC(f*ny8i`dtlDr~kL5oS$u{rR{LNY_&xj8( z7zI>9j~&#f(#zLUb9eoe)rX^<70jI^x%4Y594u|V+9*?LpENQyJ%^XOc3s{+kh*Nmn60^2{}1)Z&h zD#L2*&*kyTIFgVl(27$e+2J9gVNW14Omg~_cY!zD!n1v54^XgF0_;r~@KgcC7jpT6 zE6DCcS24KLn0B+a%H5~w+i{NN8UXZ>CIe{6@MWo)eRbf{a$s|=UN1Ob@_2URK{{@l zi@0wmCrtp$`Rmf9Eqgz_5v%B^maM$ZIqeqq*`(->r%gKiNUpN+`;58_f3BMt%k#m< znWpD3*i2Tq8+8{4^G!fhMgY zZc=Y`*MKt$lp&U()n7!D_j|N^<+x*5x)JNEMs~kUs{WOX)h?0KGY9tO*Rj-v?&Haq zVC*wU+Co)MEz4;{EWFncbn^4(?KmRb>!eXT%?Nu?4)?<;N+Ho^X^$AWL4=jPpzd&aq@fg5V+r!>PRqkqS%ItT$ttp#7Xgl>>A zYxd`al>U1RsZ0s8=rEsBB*vH7pGr8h!Dd2d51Dz8kOBtp&BAilQPQu=Ny8H zm{=p(#1y((Att*4gqMnU29VJ$QIF8ROO9CxaSkt5qws)s_Xs&XYUyYE{lw-+oTCF; zAX8aeyV0K@&51*zyKC1PvP&u=#Jly^*62hVdbNNMyW}E7;-ekwe%&ILQ#!R5&?7Sc zD)|tR%eXl%Y6qn~4@zJMcB-EPg2K0QiRXey|C-<~kb$D$KoH5No&4jE5Vb-Dsr975 z1rFJShbj2W^OnZvIr7sAPeam#v91OX zQ?nX9?7v4%w7Wz1Fw-aFsXNj5i-JTt4PJE~bM3cRsnr>lDbBbEEq3|6w8smoxP#{C zn_IC~rCawjADkI>B`PRYK8iiJIOt-oB#cA+%mP!+*^_I^BfHG^EXa zk6SbBW_H-=#o4w^w?yOgiqlLBKl`{E`>O!k+5gZSV^JQDc5}P*!5s?~S^(KFu?PeG zLP=ht0bY7XZ&$ z1MClGA62!?4LK(IqrH-aDu-e5`&qy*j@i2z-Du@PSmz97(N`lT=O!BO@5Kll#qZxW zu)u1N^cd%jmiDl$u0UI*mt;TWSGT8S&3L#H16ocsN2j711`QFeZ@grczGuA>O?)9~ z1mWPa3a zcx@!bh39MKxfb|XE;Q#thw7pJ7-y~xR$f}T0*_>g#kRC>yZNz#*GwW;p`B47=+Eb& zcMVcwNM+1f1<;4llVr&%U!&AphPixilXwO`eoBIzb5#>Se{DT&XZ#7BZW{3?1nR!I zDd=K7y#11YkY*`A|I(4dFIytMi}nFr{YiH`7kK@S3EOs+a+3;UFUG`0P{HJx1BDCF zL*lYtqBqgBJx_x&-`Y?P0v<-)l~as;G=)@f^a;vN53xjYu!p9QW)B~&b?qG)AX{?M z2s>d(lXbVVvjZuIAoC$1*r=k--X_^OI#f`89lbm$@eB_QMG)7k(z__pTkFDjyYhG~ zi+F_MYZ#-!fC%5Cf0ga}DH|M}pP6{lV+xv%OGzO|6omQ+1tRn$ufA+FUAurL9(2Q& zG9j}Q+&Q;JKQiy1n6Qfg{-q$56R-#W;5Xs^sI6nvW&7bjT68`zX~>=|WBqYOsI65% zf-#q_*o=(lmj#w(J}fU`d)igHcp)H%qtX6gs*E-tmZN^`L=nEdB*uDlsywU06hET9 zxfJEOqOq)7k_b@~87Ai?;9A-4BKawON)2FU$43)ND3AWy*RnPFjak9O2$?t_urxcu zkXkfP2e*nAPdmRhVxSM1Ud}P7l&y1@5HGQ`7*vQ8EwT$&3v#g;+Qxzce?qzc>5i9W zVVsO)i+1%#+1(@)a*gzP9NCK|k*cYs&?z|*SM@_#e@J-vcgu4^Vry1IjFBP4r}3K0 zn!C!F{9NE(W$u8}l_IF)3K=BMArJAsyQXeRT^s?1&S!PiW||H*dbIZ zc-~|k19=fR`s5R=3`Tp*Yq^U3%R!@k$;C%ecx$6E^T$pOI!-@vxPR5)GX-G`&}iO~1_tl20(Qc} z^>QU?c4gv8Ue)m@anabuf$BJBES7?R&{UWpxp)LuRBUN?@Jx5kt@BahpX+S+eMG`B zoiIFBZGXoft>;|2EvhXBjEF&Tm@s1Y3|a%PFvY%ND-7~Pv9 ztfu*Uk z4{40a{EQ{NiSLiuKYo)ae8@WdTrnr_mm&J#NT9%j!$b6z_g0aLzLQzpPR+Q`^swzuu0ReLp4H8^1Ve z`INnhh^bH-5XaC-?z$)Aay^Wu{?ttg}U~ zw*XjOL&-{Gjc3co^6IvDg0-`0r2%JBNBlNrKf3^Zw0HI%==u3W^#c7F09o>}Z+0#E z_*CKT+k`8=Dq3G2^RUFX@t)N#euZD0&ctxT^McSL06>DF#8+Ui(Hj#1$jyYXvwo#g zWNL@*Zcg8tz6ar8#a*(W&?^~TPfmW*jus1_qxlnsQF`N(zYnkKLOOohLVgGR^W3UR5+-+h61dHHYIY>ShoDTpUnMK})OfOAu+3;=-AqkcMD)_hq~_Jg>2l#PGa%TCOuy)`rUQRSJM zelwQ%=)E6sUX~8Dks!dz-mtCsxJP+ePQZNQq%D72vOb*Fl$0!F3DzP${I0&faUPaL zQ(z$yi{A6vRrtucSpqA6h3Fs1+fAN3oPHEjOVF_WE0#&niFD{;NzLj{zqr9Ck-uXG^6wq@{qk;}a$KM5x@r($nT^@a0C>duekA?5V;Uj;;@jl>&AxNS z#Z5p(%Trgn%L6*?~Kmg(e9tp*}}vL5CGx-8~n2Y_|N!1ks7C=qnwbYq8*=_ zg{GZon5Lklq-b4OrI(}~Tb-1lk(yqVq7j##5nrC9ms|yh#yAg3M^FfdNG^ij$GFTm z$IOQ~4-9cmVtjn6WQ1-5_`h!6LT@*a0s;V_0097i|8rAZL|UHC#`r%+{!{%Y|1JL~ zJ_!x7gA54#ZhpfO8We@&JFrmBmGPNSU_*PHX+xEVQQoIuKX~K?@~O4$ZnwE9Fsu}Y zKENes1nQJ2CYV?CBmO%C+=&smp7Vy|Rc27~qXe{I5N0QEo?)4YFdnTcSS1`wM>w!W z8S~MTU5XcIJ9-xzSXH=NuNx$`RdQzq9o&x&i;+)VRFSqgC!R!3P%`sF&i>Gs)L&Ct z=UawwJJP$$KfC*wV3*p)keUdZSo{!%ktP@aXk3Qk(voa|pxdk3?+7(jqG6%iO6_$; zL2TUo5&OolKcJ5INGV#JX)M|#c{CnmQN!k0D~Z<1)#Fm06fmlq6lE@JJ+*bz9%wY+ z+%(fP>C?SAMx3%FHN+wvdZ3p6fDaKt zc})<%GDCv4>iq@uD;iVAM2ze&}pVg=5c7uQ0I* z{7vg8-98U?!@Y)DW5K~L<5sYJdc?U>%vdp6#})&PV|Uo}Zk}@syD=Dl(qK5!9U+Da zCBUSqd}%@W|aH^n~PnY(=7*D32&{r*(lhwi*n9zh4NcamFlUcR$1@sSvO8GIS zH}4rRbYpGH?DxY9`LW}IAY+8g;lg?ao$E6XmGQm}?y}sj_Sdfh#R3Yue6i4vp*FJ> zKs>f7n5Ez<3w2JVaKt5wAQfb-J5HzKq#lHzW2VT@M5!n$c1SU^j1+2+DxzvtWWakn zKfmv8B=hkXC%RASy1T+5k3&JaacDDQ@(5s}O6*iu)>lUAyTj90Q*eEJZS=mHsTFMk zY{W;30|TX!2VL%{xg$|ig%uK-Cufz`fM7Ds>?SoTj^L)KTbCGHM1H&q57hYK!8`(YF^ra3ifFexGJg=}o}J<${ndInQc)MDnx^RHueLjQnhqu%omYL@WV- z%z8tsj~`%$+=CY2&O*3kHi{megM=Ja>r>Mqvff#(_bpq!4D8`~{YIF{S2L+NQc-rg zg5A*R6vn-K+vo}sN#)XM2Gf=X&rfNVtp1c%Z0^dRy}Bp48TU6bqK7;ga!`vrO_@|3 zfu^?qwMntE4xkya3$I$@-l;P)Gm{Y`(*r;JN4nHQqtv2uN>mgI(r<{Ul2k{e%%}EK zN7sHJH898o;wg~`g{ZB6JUbBIryBg>rGN#&n<#J*aPc3ZyJ#C1F2pzQtpH0-9X2#N zcN!h)PgYbBt)jh`OsV_ZA>FNxJ`QAkdTlidHVdp}%t~QlFK)0r!m)%ATWNCDkDhw4 zN22vBaGI)^Pth!o9UC!TN-WoX783N?r=U0b^EA;Q7k#&vA{O4SKBg`Vs=5k9jG47l zN2NJUn|VAT6e4-+(N0 zO5tAvxn+*3#uM`KP7N13u`Q-##gr_AWG&65IwWStcPhq{cekijrNm{XqaO}grZ`!s z4{pE?WwxzAe>Wp-UB*rzmB0vNmuRqwd_cHCsw~h3j$#Pf-d7+98lqPmA-f(3tM&;r zsJ}?FX<>P$jK%%cXM7Q+)b?rz#*-IX$XTP*B6RxA9FtpDhagHD6*;ToM@Rvs;Ed%i zi-gvEq#(A>8f3)cB>|N%e{eG3=gYAgeVDA&S;0!gy$M;qVDi6z|w%ae8eErZZ`S?N2HZa~e;)@$d{SE9B^S~xFCvAw? z(>-|dsueLXbJz>{>9>huIRyD1bjHNU#aS9f1s_qqVjB7$muJL5qq2W9aAnl1Gg#Jv7^a zy#ua8NgF2 zSPpl2*~L}^XLy{$aL`7eT@d8Cm;3|%xKfRNt;G`Rc%-4d4pAM%Jc6RZ?L1t_(ou^c zqfi3#vStp?K0dhEInlA}b%2e}p5G}C^-h{r*kO%zl@rCoc>qqAq;|)$XDz%Zw}}=WcpZn*DVj{ak~F{pRmv_t`-Oc3 zJr6cc7#4W!uiBz40a|55-K`zsUD$WOT@&u9`@#3u&--jK1bQ6JO6c!H2F&TVlNk8h zce-?7#dw+^+EnE0pPzFq=mz-e+H>J=%D5PlDFzq|Y4GPeq}2%atr z)il}R163MJJD)lDzv0Lo*PcaT`_p5$(>DXyv$eLhptB(ccG~*SE_=*~Wr;k*wz)U! z&YmYflRUe8R#i+kHu!dN%AiQE&(%*o%`;V;@*#VJ`VgajJV@bjZ*pW^+yZ8MYabHN z9fwLq*oB=$Edh?rr!|M#fhzPyrT;oxNGLS`wx$O9F6Va}M}_|tddu- zJ!C;y{a(5vY963WAgx97`qF=^fP|S#!BQIcEM^n z(g#>d2?0wPNc>)1fxJTjmk6q)Ua`o=l?ZYAT&m%zo5w&ecp@KOw(24n<=SHox}2Hob;5mv0Ic)^(z$bm@80{D#5yg-Dt2j~z^I*m7q4CC z-jptLri|BwuTtV)dcRioU-~exTPdi--8x%g-{0pc!&<`jK;8slzX8Rx6a_i~F}U2B zn*9_eTm#@^lgNJKI^5A~i`cjDwnZ&nzQDId=iklR5-(r!CEShs6nl1e#>Yo(jAozf zVO4da)hLGbnlD+^bQI!k$)l z|1bMd8#!rbd%nK_whS?|+ zTfiYAE;}ue`I703-HXDHrU`A|r0UT%_ut=?mEY#Dx?Vh-r}EXq&MxT9s|fBZO=eSb z#5}#yIGdJDNWJ<-?Bx61`9F+yo=RG_rKaVOQhVMRrJK>KpG~>8r?su%Xt%Av%JP!h zYG?b2k?}{BpSuV5zF7C#rz$MJ z%z3?H-!dM@H-$%jdWyNPnWozD+o1H>ds|+@{F(Awu4O`=OW?o5rAuyU&wBLd`B$s|b=BriPf4kt_hgkX;8&gD6uv4x=bLC& zXWQvpUcE*7;=dlL&Q*F`Cm*F%U~{0tP_FXy$qR<6(|hK*{JAli=YEu9@UGW#UOkP^ z@6|q^b^lH=`|QoCmorcQ_dnQVX}zUm=Ao05TDK?651hTLxn{Pr>LC#?nX-4M+p-RR z{8s&P|0(;Ln*l%P*#Esi&<`$H-*7W4am``_8~rU=M5s7Z)W1 zdqA0a`dNu(iTcU;x%#OUiMa(isrnhIIXU^|`9(P?`W|qmV?jYqW^!UlW`3R?NCzlk zfs6tHkg*`p+P|0Ukb%JQ=l6nB)+kgq>b-jMPJ{2dN$)~Wy(iABFLz643m$E{{qg^g zyK~(Q7fkY*@bpvLHR+S*FG=hQGKghfuy*+b3D%>F`ERYXTQK#gY=!rfABjTS?@8+# zWEP#h&-KiuDQ0~rUlH4zu8*vZXBRQrZBI=*l+cs&f8Q77L+==_%5<;vXo~eHKeMRp ztD^0~)}xx{Be! z4>At~KxTtLYX3&QLk2u9&wptfT{2H?jpKq!@T!* z=k5&&znQskh5O$Ld$&uSzZJDBctb90LY03bYb&3AvvCi*?9N})4?md0w?q2Q#SfKf z9d;7e=Qh15_1c@{eB^{Co2cbA{$sUeYbO7j{*U|oj+ni3KE%dNP`iG)P@n zS3Hk>(0)ux^;2k$-Td`?{5PdIzsv1@#2Ij9riR)Rw`HQ2C+VIv(>wJ<3m6-50p5&E zBFugauz-voG6x6Lv?`jB0g<=DB2bwYXfIte0qgY^q#}e z3(EfZ^h=@W|A$pStoXoZjw*^dX24)4su;p&q%ew+mB>aSaxXpuQ2n+Fy8*=|z@iU- zw92A5YJEY$7oC;|*@q6e0_Zc~CHc%M5%zK}{hp$l6FuAq5zENlPIMu<9p0 zh1|ev4pK#h2o}h|pW)6sou`2jq7N)1f#nJuENPVH2bKrvnI)O&dHF@D==vHCA7Peb zV_*;j<`@mQE(V4rjYr9xEnI)mZ7! best { // Consider it a match if it's empty line - best = is - } - continue - } - if "" == nr { - ir++ - continue - } - if nr != ns { // Start over if a non-match is found - is = 0 - } else { - is++ - if is > best { - best = is - } - } - ir++ - } - - if best == -1 { - // missing line is line 0 - best = 0 - } - if best != len(sl) { - t.Fatalf("README.md/index.md file is missing line %d ('%s') from file '%s'\nAdditional info:\n%s", best, sl[best], src, sampleapp.ActionMsg) - } -} - -func checkDoc(t *testing.T, lc sampleapp.LanguageConfig) { - readme, err := getDocFile(lc.SrcDir) - if err != nil { - t.Fatalf("Error: %v.", err) - return - } - rl := readlines(t, readme) - for _, f := range lc.Copies { - src := path.Join(lc.SrcDir, f) - checkContains(t, rl, src) - } -} - -func getDocFile(dir string) (string, error) { - for _, f := range []string{"README.md", "index.md"} { - path := path.Join(dir, f) - _, err := os.Stat(path) - if err == nil { - return path, nil - } - if !os.IsNotExist(err) { - return "", nil - } - } - return "", fmt.Errorf("cannot find README.md or index.md in %s", dir) -} - -// TestDocSrc checks content of README.md files, and ensures that the real code of the samples -// is properly embedded in the docs. -func TestDocSrc(t *testing.T) { - lcs, err := sampleapp.GetConfigs(configFile) - if nil != err { - t.Fatalf("Failed reading config file %s: '%v'", configFile, err) - } - - allowed := test.GetAllowedLanguages() - for _, lc := range lcs.Languages { - if _, ok := allowed[lc.Language]; len(allowed) > 0 && !ok { - continue - } - lc.UseDefaultIfNotProvided() - t.Run(lc.Language, func(t *testing.T) { - checkDoc(t, lc) - }) - } -} From 4d924c3e5c2b43f2a222b7cce98aae28dcbc2bcf Mon Sep 17 00:00:00 2001 From: Dave Protasowski Date: Mon, 26 Oct 2026 22:41:32 -0400 Subject: [PATCH 42/44] Reject invalid PORT values in the Rust CloudEvents sample --- .../cloudevents/cloudevents-rust/src/main.rs | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/code-samples/serving/cloudevents/cloudevents-rust/src/main.rs b/code-samples/serving/cloudevents/cloudevents-rust/src/main.rs index 9c5995fc2d..9232c7c0d9 100644 --- a/code-samples/serving/cloudevents/cloudevents-rust/src/main.rs +++ b/code-samples/serving/cloudevents/cloudevents-rust/src/main.rs @@ -18,7 +18,7 @@ use actix_web::{post, web, App, HttpRequest, HttpResponse, HttpServer}; use cloudevents::binding::actix::{HttpRequestExt, HttpResponseBuilderExt}; use cloudevents::binding::reqwest::RequestBuilderExt; use cloudevents::{EventBuilder, EventBuilderV10}; -use std::time::Duration; +use std::{num::NonZeroU16, time::Duration}; const SOURCE: &str = "https://github.com/knative/docs/code-samples/serving/cloudevents/cloudevents-rust"; @@ -41,10 +41,6 @@ impl AppState { } } -fn port(value: Option) -> u16 { - value.and_then(|value| value.parse().ok()).unwrap_or(8080) -} - #[post("/")] async fn handle_event( req: HttpRequest, @@ -88,7 +84,12 @@ async fn handle_event( #[actix_web::main] async fn main() -> std::io::Result<()> { env_logger::Builder::from_env(env_logger::Env::default().default_filter_or("info")).init(); - let port = port(std::env::var("PORT").ok()); + let port = std::env::var_os("PORT") + .unwrap_or_else(|| "8080".into()) + .to_string_lossy() + .parse::() + .expect("PORT must be an integer from 1 to 65535") + .get(); let state = web::Data::new(AppState::new(std::env::var("K_SINK").ok()).map_err(std::io::Error::other)?); From 35790a42dc694de500cfde5c1a806a45453bc56e Mon Sep 17 00:00:00 2001 From: Dave Protasowski Date: Mon, 26 Oct 2026 23:05:39 -0400 Subject: [PATCH 43/44] Simplify sample CI check names with consistent section prefixes --- .github/workflows/samples-test.yaml | 20 ++++++++++++++------ 1 file changed, 14 insertions(+), 6 deletions(-) diff --git a/.github/workflows/samples-test.yaml b/.github/workflows/samples-test.yaml index 29b8d44c6b..5582f1a73d 100644 --- a/.github/workflows/samples-test.yaml +++ b/.github/workflows/samples-test.yaml @@ -32,12 +32,20 @@ jobs: printf '%s\n' "NOT TESTED (no ci.sh): $path" >> "$GITHUB_STEP_SUMMARY" fi done < "$RUNNER_TEMP/changed-samples" - collisions=$(find code-samples -type f -name ci.sh -print0 | - jq -Rs 'split("\u0000")[:-1] | map(split("/")[-2]) | group_by(.) | map(select(length > 1) | .[0])') - echo "directories=$(jq -cn --argjson collisions "$collisions" --args ' - $ARGS.positional | unique | map(split("/") as $path | - {directory: ., name: (if $collisions | index($path[-1]) - then $path[1] + "/" + $path[-1] else $path[-1] end)})' "${directories[@]}")" >> "$GITHUB_OUTPUT" + # Use consistent section/sample names without collision detection. + matrix=$( + for directory in "${directories[@]}"; do + relative=${directory#code-samples/} + section=${relative%%/*} + name="$section/${directory##*/}" + + jq -cn \ + --arg directory "$directory" \ + --arg name "$name" \ + '{directory: $directory, name: $name}' + done | jq -sc 'unique_by(.directory)' + ) + echo "directories=$matrix" >> "$GITHUB_OUTPUT" test: name: ${{ matrix.name }} From 20abec2154a9c620e3030c561297a754144572e0 Mon Sep 17 00:00:00 2001 From: Dave Protasowski Date: Mon, 26 Oct 2026 23:12:40 -0400 Subject: [PATCH 44/44] Simplify changed-sample discovery by checking tracked CI directories --- .github/workflows/samples-test.yaml | 17 +++++++---------- 1 file changed, 7 insertions(+), 10 deletions(-) diff --git a/.github/workflows/samples-test.yaml b/.github/workflows/samples-test.yaml index 5582f1a73d..12c4cb3446 100644 --- a/.github/workflows/samples-test.yaml +++ b/.github/workflows/samples-test.yaml @@ -19,19 +19,16 @@ jobs: - id: samples name: Find changed sample directories run: | - git diff --name-only --no-renames -z "${{ github.event.pull_request.base.sha }}"...HEAD -- code-samples/ > "$RUNNER_TEMP/changed-samples" + diff_range="${{ github.event.pull_request.base.sha }}...HEAD" directories=() - while IFS= read -r -d '' path; do - directory=${path%/*} - while [[ "$directory" == code-samples/* && ! -f "$directory/ci.sh" ]]; do - directory=${directory%/*} - done - if [[ "$directory" == code-samples/* && -f "$directory/ci.sh" ]]; then + while IFS= read -r -d '' script; do + directory=${script%/ci.sh} + changed=$(git diff --name-only --no-renames "$diff_range" -- "$directory/") + if [[ -n "$changed" ]]; then directories+=("$directory") - else - printf '%s\n' "NOT TESTED (no ci.sh): $path" >> "$GITHUB_STEP_SUMMARY" fi - done < "$RUNNER_TEMP/changed-samples" + done < <(git ls-files -z -- 'code-samples/**/ci.sh') + echo 'Directories without ci.sh are NOT TESTED.' >> "$GITHUB_STEP_SUMMARY" # Use consistent section/sample names without collision detection. matrix=$( for directory in "${directories[@]}"; do