diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 00000000..0df89607 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,55 @@ +# The common multistage Dockerfile for all packages in the monorepo. The idea is to create a "build" stage that is a +# base for building all packages. The advantage is that Docker reuses the "build" resulting in faster builds. Some pnpm +# features also make Docker caching more efficient. That said, pnpm documentation regarding Docker is not very good and +# their official example for monorepo (https://pnpm.io/docker) doesn't work at the time of writing. See also: +# - https://github.com/pnpm/pnpm/issues/3114#issuecomment-1177144513 +# - https://github.com/pnpm/pnpm/issues/3114#issuecomment-1162821282 +# +# Note, that Dockerfile assumes the context (path) is the root of the monorepo in order to generate the common "build" +# stage. +# +# Debugging tips (assuming CWD = ./packages/pusher): +# 1. Build: docker build --target pusher --tag pusher:latest ../../ +# 2. Inspect: docker run -it --init -v $(pwd)/config:/app/config --env-file .env --entrypoint /bin/sh pusher:latest +# The above commands will allow you to inspect the output of the build stage. You can change the target to debug other +# stages and verify that the image is correct. + +# We use the alpine image because of its small size. The alternative considered was the "slim" image, but it is larger +# and we already use alpine (without issues) in other projects, so the size reduction seems worth it. +FROM node:18-alpine AS build +WORKDIR /app +RUN npm install -g pnpm +# Copy just the "pnpm-lock.yaml" file and use "pnpm fetch" to download all dependencies just from the lockfile. This +# command is specifically designed to improve building a docker image because it only installs the dependencies if the +# lockfile has changed (otherwise uses the cached value). +COPY pnpm-lock.yaml /app +RUN pnpm fetch +# Copies all of the contents (without files listed in .dockerignore) of the monorepo into the image. +COPY . /app +# Ideally, we would use "--offline" option, but it seems pnpm has a bug. Fortunately, the instalation times are similar. +# See: https://github.com/pnpm/pnpm/issues/6058 for details. +RUN pnpm install --recursive --prefer-offline +# Build all packages in the monorepo. +RUN pnpm run --recursive build + +# Create a separate stage for pusher package. We create a temporary stage for deployment and then copy the result into +# the final stage. Only the production dependencies and package implementation is part of this last stage. +FROM build AS deployed-pusher +RUN pnpm --filter=pusher --prod deploy deployed-pusher +FROM node:18-alpine as pusher +WORKDIR /app +ENV NODE_ENV=production +COPY --from=deployed-pusher /app/deployed-pusher . +# Enabling source maps adds small runtime overhead, but improves debugging experience and the performance is worth it. +ENTRYPOINT ["node", "--enable-source-maps", "dist/index.js"] + +# Create a separate stage for api package. We create a temporary stage for deployment and then copy the result into +# the final stage. Only the production dependencies and package implementation is part of this last stage. +FROM build AS deployed-api +RUN pnpm --filter=api --prod deploy deployed-api +FROM node:18-alpine as api +WORKDIR /app +ENV NODE_ENV=production +COPY --from=deployed-api /app/deployed-api . +# Enabling source maps adds small runtime overhead, but improves debugging experience and the performance is worth it. +ENTRYPOINT ["node", "--enable-source-maps", "dist/index.js"] diff --git a/package.json b/package.json index f57118c8..43f615ee 100644 --- a/package.json +++ b/package.json @@ -8,6 +8,7 @@ "scripts": { "build": "pnpm recursive run build", "clean": "pnpm recursive run clean", + "docker:build": "pnpm recursive run docker:build", "eslint:check": "pnpm recursive run eslint:check", "eslint:fix": "pnpm recursive run eslint:fix", "prettier:check": "pnpm recursive run prettier:check", diff --git a/packages/api/README.md b/packages/api/README.md index 5ab19fce..5a90a59b 100644 --- a/packages/api/README.md +++ b/packages/api/README.md @@ -50,8 +50,6 @@ AWS_S3_BUCKET_NAME=my-config-bucket AWS_S3_BUCKET_PATH=configs/my-app/signed-api.json ``` - - #### `LOGGER_ENABLED` @@ -158,27 +156,43 @@ To deploy on premise you can use the Docker instructions below. ## Docker -The API is also dockerized. Docker needs to publish the port of the server (running inside the docker) to the port on -the host machine. By default, it expects the server is running on port `8090` and publishes it to the `8090` on the -host. To change this, you need to modify both `signed-api.json` configuration and use `PORT` environment variable with -the same value. +The API is also dockerized. To run the dockerized APi, you need to: + +1. Publish the port of the API to the host machine using the `--publish` flag. +2. Mount config folder to `/app/config`. The folder should contain the `signed-api.json` file. +3. Pass the `-it --init` flags to the docker run command. This is needed to ensure the docker is stopped gracefully. See + [this](https://github.com/nodejs/docker-node/blob/main/docs/BestPractices.md#handling-kernel-signals) for details. +4. Specify the `--env-file` with the path to the `.env` file containing the [ENV configuration](#environment-variables). +5. Optionally, pass the `--rm` flag to remove the container after it is stopped. -In order to run the API from a docker, run: +For example: -```bash -# Starts the API on port 8090 -pnpm run docker:start -# Or in a detached mode -pnpm run docker:detach:start -# Optionally specify port (also make sure the same port is specified inside `signed-api.json`) -PORT=5123 pnpm run docker:start +```sh +# Assuming the current folder contains the "config" folder and ".env" file and the API port is 8090. +docker run --publish 8090:8090 -it --init --volume $(pwd)/config:/app/config --env-file .env --rm api:latest +``` + +As of now, the docker image is not published anywhere. You need to build it locally. To build the image run: + +```sh +docker build --target api --tag api:latest ../../ +``` + +### Development only docker instructions + +You can use shorthands from package.json. To understand how the docker image is built, read the +[Dockerfile](../../Dockerfile). + +```sh +pnpm run docker:build +pnpm run docker:run ``` ### Examples Here are some examples of how to use the API with `curl`. Note, the port may differ based on the configuration. -```bash +```sh # Upsert batch of signed data (HTTP POST) curl --location 'http://localhost:8090' \ --header 'Content-Type: application/json' \ diff --git a/packages/api/docker/Dockerfile b/packages/api/docker/Dockerfile deleted file mode 100644 index 82173448..00000000 --- a/packages/api/docker/Dockerfile +++ /dev/null @@ -1,29 +0,0 @@ -# Step 1: Build the TypeScript application. -FROM node:18-alpine AS builder - -WORKDIR /usr/src/app -# Assumes the context is the root of the monorepo. Copies all of the contents (without files listed in .dockerignore) of -# the monorepo into the image. -COPY . . -RUN npm install -g pnpm -# Installs all dependencies only for the api package. -RUN pnpm install --recursive --filter api -# Builds the api package. -RUN pnpm run --filter api build - -# Step 2: Run the built application. -FROM node:18-alpine - -WORKDIR /usr/src/app/packages/api -# Copies the built application from the builder image. -COPY --from=builder /usr/src/app/packages/api/dist ./dist -# Copies the package.json from the builder image. -COPY --from=builder /usr/src/app/packages/api/package.json . -# This Dockerfile copies the pnpm-lock.yaml file from the monorepo root to install locked dependency versions. This -# guarantees consistency by utilizing identical sets of dependencies. -COPY pnpm-lock.yaml . -RUN npm install -g pnpm -# Only install dependencies for production (ignore "devDependencies" section in package.json). -RUN pnpm install --prod - -CMD [ "node", "dist/src/index.js" ] diff --git a/packages/api/docker/docker-compose.yml b/packages/api/docker/docker-compose.yml deleted file mode 100644 index a902f6ba..00000000 --- a/packages/api/docker/docker-compose.yml +++ /dev/null @@ -1,23 +0,0 @@ -version: '3.8' - -services: - express-server: - build: - context: ../../../ - dockerfile: ./packages/api/docker/Dockerfile - ports: - - '${PORT-8090}:${PORT:-8090}' - environment: - - NODE_ENV=production - volumes: - # Mount the config file path to the container /dist/config folder where the API expects it to be. - # - # Environment variables specified in the docker-compose.yml file are applied to all operations that affect the - # service defined in the compose file, including the build process ("docker compose build"). Docker Compose will - # expect that variable to be defined in the environment where it's being run, and it will throw an error if it is - # not. For this reason we provide a default value. - # - # Docker Compose doesn't allow setting default values based on the current shell's environment, so we can't access - # current working directory and default to "$(pwd)/config". For this reason we default to the config folder - # relative to the docker-compose.yml file. - - ${CONFIG_PATH:-../config}:/usr/src/app/packages/api/dist/config diff --git a/packages/api/package.json b/packages/api/package.json index 1d920608..459d8c09 100644 --- a/packages/api/package.json +++ b/packages/api/package.json @@ -10,15 +10,13 @@ "build": "tsc --project tsconfig.build.json", "clean": "rm -rf coverage dist", "dev": "nodemon --ext ts,js,json,env --exec \"pnpm ts-node src/index.ts\"", - "docker:build": "docker compose --file docker/docker-compose.yml build", - "docker:start:detach": "docker compose --file docker/docker-compose.yml up --detach", - "docker:start": "docker compose --file docker/docker-compose.yml up", - "docker:stop": "docker compose --file docker/docker-compose.yml down", + "docker:build": "docker build --target api --tag api:latest ../../", + "docker:start": "docker run --publish 8090:8090 -it --init --volume $(pwd)/config:/app/config --env-file .env --rm api:latest", "eslint:check": "eslint . --ext .js,.ts --max-warnings 0", "eslint:fix": "eslint . --ext .js,.ts --fix", "prettier:check": "prettier --check \"./**/*.{js,ts,md,json}\"", "prettier:fix": "prettier --write \"./**/*.{js,ts,md,json}\"", - "start-prod": "node dist/src/index.js", + "start-prod": "node --enable-source-maps dist/index.js", "test": "jest", "tsc": "tsc --project ." }, @@ -26,7 +24,6 @@ "devDependencies": { "@types/express": "^4.17.18", "@types/lodash": "^4.14.199", - "@types/source-map-support": "^0.5.8", "nodemon": "^3.0.1" }, "dependencies": { @@ -37,7 +34,6 @@ "express": "^4.18.2", "lodash": "^4.17.21", "signed-api/common": "workspace:common@*", - "source-map-support": "^0.5.21", "zod": "^3.22.2" } } diff --git a/packages/api/src/index.ts b/packages/api/src/index.ts index ea256869..a71645e3 100644 --- a/packages/api/src/index.ts +++ b/packages/api/src/index.ts @@ -1,9 +1,3 @@ -// Imports the source-map-support module and registers it to enable source map support in Node.js. This allows stack -// traces to show information from the original code, rather than the compiled JavaScript. -// -// You can check how this works by following the demo from https://github.com/evanw/node-source-map-support#demos. Just -// create a test script with/without the source map support, build the project and run the built script using node. -import 'source-map-support/register'; import { startServer } from './server'; import { logger } from './logger'; import { fetchAndCacheConfig } from './config'; diff --git a/packages/pusher/README.md b/packages/pusher/README.md index 0476a96c..ae2bbc97 100644 --- a/packages/pusher/README.md +++ b/packages/pusher/README.md @@ -21,7 +21,13 @@ The pusher needs a configuration in order to run. The `config` folder contains e To start the the pusher in dev mode run the following: 1. `cp pusher.example.json pusher.json` - To copy the pusher configuration from the example. Note, the `pusher.json` - file is ignored by git. + file is ignored by git. If you are using Docker Desktop, you need to change the URL from localhost to + `host.docker.internal`. For example: + + ```jsonc + "url": "http://host.docker.internal:8090" + ``` + 2. `cp secrets.example.env secrets.env` - To copy the secrets.env needed for the configuration. This file is also ignored by git. 3. Set the `NODARY_API_KEY` inside the secrets file. Ask someone from development team for the key. @@ -69,8 +75,6 @@ LOG_FORMAT=json LOG_LEVEL=info ``` - - #### `LOGGER_ENABLED` @@ -341,31 +345,33 @@ To deploy on premise you can use the Docker instructions below. ## Docker -Pusher is also dockerized. The dockerized pusher needs expects environment variable `CONFIG_PATH` to be defined, -pointing to a directory with `pusher.json` and `secrets.env` files. +Pusher is also dockerized. To run the dockerized pusher you need to: -In order to run the pusher from a docker, run: +1. Mount config folder to `/app/config`. The folder should contain the `pusher.json` and `secrets.env` files. +2. Pass the `-it --init` flags to the docker run command. This is needed to ensure the docker is stopped gracefully. See + [this](https://github.com/nodejs/docker-node/blob/main/docs/BestPractices.md#handling-kernel-signals) for details. +3. Specify the `--env-file` with the path to the `.env` file containing the [ENV configuration](#environment-variables). +4. Optionally, pass the `--rm` flag to remove the container after it is stopped. + +For example: -```bash -CONFIG_PATH=$(pwd)/config pnpm run docker:start -# or in a detached mode -CONFIG_PATH=$(pwd)/config pnpm run docker:detach:start +```sh +# Assuming the current folder contains the "config" folder and ".env" file. +docker run -it --init --volume $(pwd)/config:/app/config --env-file .env --rm pusher:latest ``` -To stop a running pusher in a detached mode, run: +As of now, the docker image is not published anywhere. You need to build it locally. To build the image run: -```bash -pnpm run docker:stop +```sh +docker build --target pusher --tag pusher:latest ../../ ``` ### Development only docker instructions -By default the `CONFIG_PATH` is points to the `pusher/config` directory. This means it's possible to run: +You can use shorthands from package.json. To understand how the docker image is built, read the +[Dockerfile](../../Dockerfile). -```bash +```sh pnpm run docker:build -# or -pnpm run docker:start +pnpm run docker:run ``` - -without the need to set `CONFIG_PATH` explicitly. diff --git a/packages/pusher/docker/Dockerfile b/packages/pusher/docker/Dockerfile deleted file mode 100644 index 3d21df92..00000000 --- a/packages/pusher/docker/Dockerfile +++ /dev/null @@ -1,29 +0,0 @@ -# Step 1: Build the TypeScript application. -FROM node:18-alpine AS builder - -WORKDIR /usr/src/app -# Assumes the context is the root of the monorepo. Copies all of the contents (without files listed in .dockerignore) of -# the monorepo into the image. -COPY . . -RUN npm install -g pnpm -# Installs all dependencies only for the pusher package. -RUN pnpm install --recursive --filter pusher -# Builds the pusher package. -RUN pnpm run --filter pusher build - -# Step 2: Run the built application. -FROM node:18-alpine - -WORKDIR /usr/src/app/packages/pusher -# Copies the built application from the builder image. -COPY --from=builder /usr/src/app/packages/pusher/dist ./dist -# Copies the package.json from the builder image. -COPY --from=builder /usr/src/app/packages/pusher/package.json . -# This Dockerfile copies the pnpm-lock.yaml file from the monorepo root to install locked dependency versions. This -# guarantees consistency by utilizing identical sets of dependencies. -COPY pnpm-lock.yaml . -RUN npm install -g pnpm -# Only install dependencies for production (ignore "devDependencies" section in package.json). -RUN pnpm install --prod - -CMD [ "node", "dist/src/index.js" ] diff --git a/packages/pusher/docker/docker-compose.yml b/packages/pusher/docker/docker-compose.yml deleted file mode 100644 index cab65461..00000000 --- a/packages/pusher/docker/docker-compose.yml +++ /dev/null @@ -1,21 +0,0 @@ -version: '3.8' - -services: - pusher: - build: - context: ../../../ - dockerfile: ./packages/pusher/docker/Dockerfile - environment: - - NODE_ENV=production - volumes: - # Mount the config file path to the container /dist/config folder where the pusher expects it to be. - # - # Environment variables specified in the docker-compose.yml file are applied to all operations that affect the - # service defined in the compose file, including the build process ("docker compose build"). Docker Compose will - # expect that variable to be defined in the environment where it's being run, and it will throw an error if it is - # not. For this reason we provide a default value. - # - # Docker Compose doesn't allow setting default values based on the current shell's environment, so we can't access - # current working directory and default to "$(pwd)/config". For this reason we default to the config folder - # relative to the docker-compose.yml file. - - ${CONFIG_PATH:-../config}:/usr/src/app/packages/pusher/dist/config diff --git a/packages/pusher/package.json b/packages/pusher/package.json index 4ae23fe8..57dabcba 100644 --- a/packages/pusher/package.json +++ b/packages/pusher/package.json @@ -9,15 +9,13 @@ "build": "tsc --project tsconfig.build.json", "clean": "rm -rf coverage dist", "dev": "nodemon --ext ts,js,json,env --exec \"pnpm ts-node src/index.ts\"", - "docker:build": "docker compose --file docker/docker-compose.yml build", - "docker:start:detach": "docker compose --file docker/docker-compose.yml up --detach", - "docker:start": "docker compose --file docker/docker-compose.yml up", - "docker:stop": "docker compose --file docker/docker-compose.yml down", + "docker:build": "docker build --target pusher --tag pusher:latest ../../", + "docker:start": "docker run -it --init --volume $(pwd)/config:/app/config --env-file .env --rm pusher:latest", "eslint:check": "eslint . --ext .js,.ts --max-warnings 0", "eslint:fix": "eslint . --ext .js,.ts --fix", "prettier:check": "prettier --check \"./**/*.{js,ts,md,yml,json}\"", "prettier:fix": "prettier --write \"./**/*.{js,ts,md,yml,json}\"", - "start-prod": "node dist/src/index.js", + "start-prod": "node --enable-source-maps dist/index.js", "test": "jest", "tsc": "tsc --project ." }, @@ -36,13 +34,11 @@ "express": "^4.18.2", "lodash": "^4.17.21", "signed-api/common": "workspace:common@*", - "source-map-support": "^0.5.21", "zod": "^3.22.2" }, "devDependencies": { "@types/express": "^4.17.18", "@types/lodash": "^4.14.199", - "@types/source-map-support": "^0.5.8", "nodemon": "^3.0.1" } } diff --git a/packages/pusher/src/api-requests/signed-api.test.ts b/packages/pusher/src/api-requests/signed-api.test.ts index daed6bd3..9668c5f0 100644 --- a/packages/pusher/src/api-requests/signed-api.test.ts +++ b/packages/pusher/src/api-requests/signed-api.test.ts @@ -68,13 +68,13 @@ describe(postSignedApiData.name, () => { ); jest.spyOn(stateModule, 'getState').mockReturnValue(state); jest.spyOn(logger, 'warn'); - jest.spyOn(axios, 'post').mockRejectedValue('simulated-network-error'); + jest.spyOn(axios, 'post').mockRejectedValue(new Error('simulated-network-error')); const response = await postSignedApiData(config.triggers.signedApiUpdates[0]!); expect(response).toEqual({ success: false }); expect(logger.warn).toHaveBeenCalledWith('Failed to make update signed API request.', { - axiosResponse: undefined, + error: new Error('simulated-network-error'), signedApiName: 'localhost', updateDelay: 5, }); diff --git a/packages/pusher/src/api-requests/signed-api.ts b/packages/pusher/src/api-requests/signed-api.ts index cbd03521..7605a013 100644 --- a/packages/pusher/src/api-requests/signed-api.ts +++ b/packages/pusher/src/api-requests/signed-api.ts @@ -15,9 +15,6 @@ export const postSignedApiData = async (group: SignedApiNameUpdateDelayGroup) => } = getState(); const { signedApiName, templateIds, updateDelay } = group; const logContext = { signedApiName, updateDelay }; - logger.debug('Posting signed API data.', { group, ...logContext }); - - const provider = signedApis.find((a) => a.name === signedApiName)!; const airnode = ethers.Wallet.fromMnemonic(airnodeWalletMnemonic).address; const batchPayloadOrNull = templateIds.map((templateId): SignedApiPayload | null => { @@ -36,6 +33,9 @@ export const postSignedApiData = async (group: SignedApiNameUpdateDelayGroup) => logger.debug('No batch payload found to post. Skipping.', logContext); return { success: true, count: 0 }; } + + logger.debug('Posting signed API data.', { group, ...logContext }); + const provider = signedApis.find((a) => a.name === signedApiName)!; const goAxiosRequest = await go, AxiosError>(async () => { logger.debug('Posting batch payload.', { ...logContext, batchPayload }); const axiosResponse = await axios.post(provider.url, batchPayload, { @@ -50,7 +50,7 @@ export const postSignedApiData = async (group: SignedApiNameUpdateDelayGroup) => logger.warn( `Failed to make update signed API request.`, // See: https://axios-http.com/docs/handling_errors - { ...logContext, axiosResponse: goAxiosRequest.error.response } + { ...logContext, error: goAxiosRequest.error.response ?? goAxiosRequest.error } ); return { success: false }; } diff --git a/packages/pusher/src/fetch-beacon-data.ts b/packages/pusher/src/fetch-beacon-data.ts index d1b5fe37..77cfe19e 100644 --- a/packages/pusher/src/fetch-beacon-data.ts +++ b/packages/pusher/src/fetch-beacon-data.ts @@ -1,4 +1,5 @@ import { isEmpty } from 'lodash'; +import { go } from '@api3/promise-utils'; import { logger } from './logger'; import { getState } from './state'; import { sleep } from './utils'; @@ -29,7 +30,15 @@ const fetchBeaconDataInLoop = async (signedApiUpdate: SignedApiUpdate) => { const startTimestamp = Date.now(); const templateResponses = await makeTemplateRequests(signedApiUpdate); const signedResponses = await signTemplateResponses(templateResponses); - signedResponses.forEach(([templateId, signedResponse]) => templateValues[templateId]!.put(signedResponse)); + signedResponses.forEach(async ([templateId, signedResponse]) => { + const goPut = await go(() => templateValues[templateId]!.put(signedResponse)); + if (!goPut.success) { + // Because there can be multiple triggers for the same template ID it is possible that a race condition occurs, + // where the (newer) response from a different trigger is put first. This throws, because the signed data must + // be inserted increasingly by timestamp. + logger.warn(`Could not put signed response`, { templateId, signedResponse, errorMessage: goPut.error.message }); + } + }); const duration = Date.now() - startTimestamp; await sleep(signedApiUpdate.fetchInterval * 1_000 - duration); diff --git a/packages/pusher/src/index.ts b/packages/pusher/src/index.ts index ebce301c..ca2264a9 100644 --- a/packages/pusher/src/index.ts +++ b/packages/pusher/src/index.ts @@ -1,9 +1,3 @@ -// Imports the source-map-support module and registers it to enable source map support in Node.js. This allows stack -// traces to show information from the original code, rather than the compiled JavaScript. -// -// You can check how this works by following the demo from https://github.com/evanw/node-source-map-support#demos. Just -// create a test script with/without the source map support, build the project and run the built script using node. -import 'source-map-support/register'; import { loadConfig } from './validation/config'; import { initiateFetchingBeaconData } from './fetch-beacon-data'; import { initiateUpdatingSignedApi } from './update-signed-api'; diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 4ff0e885..9c6d67da 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -74,9 +74,6 @@ importers: signed-api/common: specifier: workspace:common@* version: link:../common - source-map-support: - specifier: ^0.5.21 - version: 0.5.21 zod: specifier: ^3.22.2 version: 3.22.2 @@ -87,9 +84,6 @@ importers: '@types/lodash': specifier: ^4.14.199 version: 4.14.199 - '@types/source-map-support': - specifier: ^0.5.8 - version: 0.5.8 nodemon: specifier: ^3.0.1 version: 3.0.1 @@ -147,9 +141,6 @@ importers: signed-api/common: specifier: workspace:common@* version: link:../common - source-map-support: - specifier: ^0.5.21 - version: 0.5.21 zod: specifier: ^3.22.2 version: 3.22.2 @@ -160,9 +151,6 @@ importers: '@types/lodash': specifier: ^4.14.199 version: 4.14.199 - '@types/source-map-support': - specifier: ^0.5.8 - version: 0.5.8 nodemon: specifier: ^3.0.1 version: 3.0.1 @@ -2889,12 +2877,6 @@ packages: '@types/node': 20.8.0 dev: true - /@types/source-map-support@0.5.8: - resolution: {integrity: sha512-u5nwLcaENciDwebPwwZb2AM1LvdlgFQfqCKxWQxcgNsQhUQciGuUnJ2LjGFAkInY2APXQzIypiUSa9zB6Epddg==} - dependencies: - source-map: 0.6.1 - dev: true - /@types/stack-utils@2.0.1: resolution: {integrity: sha512-Hl219/BT5fLAaz6NDkSuhzasy49dwQS/DSdu4MdggFB8zcXv7vflBI3xp7FEmkmdDkBUI2bPUNeMttp2knYdxw==} dev: true @@ -3510,6 +3492,7 @@ packages: /buffer-from@1.1.2: resolution: {integrity: sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==} + dev: true /buffer@5.7.1: resolution: {integrity: sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==} @@ -6407,16 +6390,10 @@ packages: source-map: 0.6.1 dev: true - /source-map-support@0.5.21: - resolution: {integrity: sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==} - dependencies: - buffer-from: 1.1.2 - source-map: 0.6.1 - dev: false - /source-map@0.6.1: resolution: {integrity: sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==} engines: {node: '>=0.10.0'} + dev: true /sprintf-js@1.0.3: resolution: {integrity: sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==}