diff --git a/.github/workflows/bench.yml b/.github/workflows/bench.yml index 00164e7792..68e7df8796 100644 --- a/.github/workflows/bench.yml +++ b/.github/workflows/bench.yml @@ -11,7 +11,6 @@ concurrency: env: NODE_NO_WARNINGS: 1 - CI: true jobs: bench: @@ -22,7 +21,7 @@ jobs: - 10 - 100 - 1000 - name: Benchmark / ${{matrix.e2e_runner}} / ${{matrix.products_size}} items + name: ${{matrix.e2e_runner}} / ${{matrix.products_size}} items runs-on: ubuntu-latest steps: - name: Checkout @@ -36,4 +35,3 @@ jobs: env: PRODUCTS_SIZE: ${{matrix.products_size}} E2E_GATEWAY_RUNNER: ${{matrix.e2e_runner}} - CI: true diff --git a/.github/workflows/memtest.yml b/.github/workflows/memtest.yml new file mode 100644 index 0000000000..166a391e98 --- /dev/null +++ b/.github/workflows/memtest.yml @@ -0,0 +1,44 @@ +name: Memtest +on: + push: + branches: + - main + pull_request: + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +env: + NODE_NO_WARNINGS: 1 + K6_VERSION: v0.56.0 + +jobs: + memtest: + strategy: + matrix: + e2e_runner: + - node + # - bun TODO: get memory snaps and heap sampling for bun. is it even necessary? + name: ${{matrix.e2e_runner}} + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + - name: Install k6 + run: | + mkdir -p "$HOME/.local/bin" + cd "$HOME/.local/bin" + curl https://github.com/grafana/k6/releases/download/${{ env.K6_VERSION }}/k6-${{ env.K6_VERSION }}-linux-amd64.tar.gz -L | tar xvz --strip-components 1 + echo "$PWD" >> $GITHUB_PATH + - name: Set up env + uses: the-guild-org/shared-config/setup@v1 + with: + # TODO: should we test more node versions? we usually always recommend upgrading to + # latest when people suspect leaks - latest is always the most stable + node-version-file: .node-version + - name: Test + run: yarn test:mem + # TODO: publish heap allocation sampling profile to artifact + env: + E2E_GATEWAY_RUNNER: ${{matrix.e2e_runner}} diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 90a6b7013c..1c8c3b4295 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -175,6 +175,8 @@ jobs: uses: the-guild-org/shared-config/setup@v1 with: node-version-file: .node-version + # we skip-build on ubuntu arm because the "canvas" package wont build and there are no prebuilts + install-command: ${{ matrix.os == 'ubuntu-24.04-arm' && 'yarn install --immutable --mode=skip-build' || '' }} - name: Build run: yarn build - name: Bundle diff --git a/.gitignore b/.gitignore index 76e136a261..38ea75755b 100644 --- a/.gitignore +++ b/.gitignore @@ -14,3 +14,7 @@ hive-gateway /examples/**/*/supergraph.graphql .helix/config.toml .helix/languages.toml +/e2e/**/*/memtest-memory-usage_*.svg +/e2e/**/*/*.heapsnapshot +/e2e/**/*/*.heapprofile +/e2e/**/*/loadtest.out diff --git a/DEPS_RESOLUTIONS_NOTES.md b/DEPS_RESOLUTIONS_NOTES.md index 776a89e1d1..3d46565a50 100644 --- a/DEPS_RESOLUTIONS_NOTES.md +++ b/DEPS_RESOLUTIONS_NOTES.md @@ -14,3 +14,8 @@ Here we collect reasons and write explanations about why some resolutions or pat ### vitest-tsconfig-paths 1. Resolve tsconfig paths in modules that have been [inlined](https://vitest.dev/config/#server-deps-inline). + +### @memlab/core + +1. Define package.json#export for `@memlab/core/Types` +1. Define package.json#export for `@memlab/core/Utils` diff --git a/e2e/auto-type-merging/auto-type-merging.memtest.ts b/e2e/auto-type-merging/auto-type-merging.memtest.ts new file mode 100644 index 0000000000..1079b1eb6d --- /dev/null +++ b/e2e/auto-type-merging/auto-type-merging.memtest.ts @@ -0,0 +1,39 @@ +import { createTenv, type Container } from '@internal/e2e'; +import { memtest } from '@internal/perf/memtest'; +import { beforeAll } from 'vitest'; + +const cwd = __dirname; +const { service, gateway, container } = createTenv(cwd); + +let petstore!: Container; +beforeAll(async () => { + petstore = await container({ + name: 'petstore', + image: 'swaggerapi/petstore3:1.0.7', + containerPort: 8080, + healthcheck: ['CMD-SHELL', 'wget --spider http://localhost:8080'], + }); +}); + +memtest( + { + cwd, + query: /* GraphQL */ ` + query GetPet { + getPetById(petId: 1) { + __typename + id + name + vaccinated + } + } + `, + }, + async () => + gateway({ + supergraph: { + with: 'mesh', + services: [petstore, await service('vaccination')], + }, + }), +); diff --git a/e2e/cloudflare-workers/cloudflare-workers.e2e.ts b/e2e/cloudflare-workers/cloudflare-workers.e2e.ts index 9214093961..f5f5c0be5e 100644 --- a/e2e/cloudflare-workers/cloudflare-workers.e2e.ts +++ b/e2e/cloudflare-workers/cloudflare-workers.e2e.ts @@ -78,18 +78,18 @@ describe.skipIf(gatewayRunner !== 'node')('Cloudflare Workers', () => { OTLP_SERVICE_NAME: string; }) { const port = await getAvailablePort(); - await spawn('yarn wrangler', { - args: [ - 'dev', - '--port', - port.toString(), - '--var', - 'OTLP_EXPORTER_URL:' + env.OTLP_EXPORTER_URL, - '--var', - 'OTLP_SERVICE_NAME:' + env.OTLP_SERVICE_NAME, - ...(isDebug() ? ['--var', 'DEBUG:1'] : []), - ], - }); + await spawn([ + 'yarn', + 'wrangler', + 'dev', + '--port', + port.toString(), + '--var', + 'OTLP_EXPORTER_URL:' + env.OTLP_EXPORTER_URL, + '--var', + 'OTLP_SERVICE_NAME:' + env.OTLP_SERVICE_NAME, + ...(isDebug() ? ['--var', 'DEBUG:1'] : []), + ]); const hostname = await getLocalhost(port); return { url: `${hostname}:${port}`, diff --git a/e2e/federation-example/federation-example.memtest.ts b/e2e/federation-example/federation-example.memtest.ts new file mode 100644 index 0000000000..1c546eb14c --- /dev/null +++ b/e2e/federation-example/federation-example.memtest.ts @@ -0,0 +1,18 @@ +import { createExampleSetup, createTenv } from '@internal/e2e'; +import { memtest } from '@internal/perf/memtest'; + +const cwd = __dirname; + +const { gateway } = createTenv(cwd); +const { supergraph, query } = createExampleSetup(cwd); + +memtest( + { + cwd, + query, + }, + async () => + gateway({ + supergraph: await supergraph(), + }), +); diff --git a/e2e/federation-mixed/federation-mixed.memtest.ts b/e2e/federation-mixed/federation-mixed.memtest.ts new file mode 100644 index 0000000000..558e72899a --- /dev/null +++ b/e2e/federation-mixed/federation-mixed.memtest.ts @@ -0,0 +1,26 @@ +import { createExampleSetup, createTenv } from '@internal/e2e'; +import { memtest } from '@internal/perf/memtest'; + +const cwd = __dirname; + +const { service, gateway } = createTenv(cwd); +const exampleSetup = createExampleSetup(cwd); + +memtest( + { + cwd, + query: exampleSetup.query, + }, + async () => + gateway({ + supergraph: { + with: 'mesh', + services: [ + await service('accounts'), + await exampleSetup.service('inventory'), + await exampleSetup.service('products'), + await exampleSetup.service('reviews'), + ], + }, + }), +); diff --git a/e2e/federation-subscriptions-passthrough/federation-subscriptions-passthrough.memtest.ts b/e2e/federation-subscriptions-passthrough/federation-subscriptions-passthrough.memtest.ts new file mode 100644 index 0000000000..873a52a9ef --- /dev/null +++ b/e2e/federation-subscriptions-passthrough/federation-subscriptions-passthrough.memtest.ts @@ -0,0 +1,79 @@ +import { createTenv } from '@internal/e2e'; +import { memtest } from '@internal/perf/memtest'; +import { getAvailablePort } from '@internal/testing'; +import { describe } from 'vitest'; + +const cwd = __dirname; + +const { service, gateway } = createTenv(cwd); + +describe('upstream subscriptions via websockets', () => { + memtest( + { + cwd, + query: /* GraphQL */ ` + subscription { + newProduct { + id + name + price + reviews { + score + } + } + } + `, + }, + async () => + gateway({ + supergraph: { + with: 'apollo', + services: [ + await service('products', { env: { MEMTEST: 1 } }), + await service('reviews'), + ], + }, + }), + ); +}); + +describe('upstream subscriptions via http callbacks', () => { + memtest( + { + cwd, + query: /* GraphQL */ ` + subscription { + newReview { + score + } + } + `, + expectedHeavyFrame: (frame) => + // these frames are not leaks and have been confirmed to be stable analysing the heap snapshots (they do allocate a lot, but they all of their memory gets freed) + [ + 'delete', + 'get pathname', + 'onRequest', + 'Repeater.next', + 'Set', + ].includes(frame.name), + }, + async () => { + const availablePort = await getAvailablePort(); + const publicUrl = `http://0.0.0.0:${availablePort}`; + return gateway({ + supergraph: { + with: 'apollo', + services: [ + await service('products', { env: { MEMTEST: 1 } }), + await service('reviews'), + ], + }, + port: availablePort, + env: { + PUBLIC_URL: publicUrl, + }, + }); + }, + ); +}); diff --git a/e2e/federation-subscriptions-passthrough/services/products/server.ts b/e2e/federation-subscriptions-passthrough/services/products/server.ts index d8f23438fb..1550988287 100755 --- a/e2e/federation-subscriptions-passthrough/services/products/server.ts +++ b/e2e/federation-subscriptions-passthrough/services/products/server.ts @@ -53,6 +53,13 @@ const schema = buildSubgraphSchema([ clearInterval(interval); }), }, + newProduct: { + async *subscribe() { + for (const product of products) { + yield { newProduct: product }; + } + }, + }, }, }, }, @@ -76,6 +83,10 @@ const graphqlWsServer = useServer( console.error('Multiple WebSocket connections attempted'); process.exit(1); } + if (process.env['MEMTEST']) { + // no need to authenticate in memtests + return true; + } // make sure the authorization header is propagated by the gateway if (connectionParams?.['token'] !== TOKEN) { return false; diff --git a/e2e/federation-subscriptions-passthrough/services/products/typeDefs.graphql b/e2e/federation-subscriptions-passthrough/services/products/typeDefs.graphql index 5cc9564412..959ac7eb88 100644 --- a/e2e/federation-subscriptions-passthrough/services/products/typeDefs.graphql +++ b/e2e/federation-subscriptions-passthrough/services/products/typeDefs.graphql @@ -4,6 +4,7 @@ extend type Query { type Subscription { productPriceChanged: Product! + newProduct: Product! } type Product @key(fields: "id") { diff --git a/e2e/federation-subscriptions-passthrough/services/reviews/server.ts b/e2e/federation-subscriptions-passthrough/services/reviews/server.ts index 328bd1fc43..827485e36e 100755 --- a/e2e/federation-subscriptions-passthrough/services/reviews/server.ts +++ b/e2e/federation-subscriptions-passthrough/services/reviews/server.ts @@ -26,6 +26,13 @@ const schema = buildSubgraphSchema([ }, resolve: (count: number) => count, }, + newReview: { + async *subscribe() { + for (const review of reviews) { + yield { newReview: review }; + } + }, + }, }, }, }, diff --git a/e2e/federation-subscriptions-passthrough/services/reviews/typeDefs.graphql b/e2e/federation-subscriptions-passthrough/services/reviews/typeDefs.graphql index 2aae4d78bf..c8abf8f818 100644 --- a/e2e/federation-subscriptions-passthrough/services/reviews/typeDefs.graphql +++ b/e2e/federation-subscriptions-passthrough/services/reviews/typeDefs.graphql @@ -9,4 +9,5 @@ type Review { type Subscription { countdown(from: Int!): Int + newReview: Review! } diff --git a/e2e/interface-additional-resolvers/interface-additional-resolvers.memtest.ts b/e2e/interface-additional-resolvers/interface-additional-resolvers.memtest.ts new file mode 100644 index 0000000000..b506593f62 --- /dev/null +++ b/e2e/interface-additional-resolvers/interface-additional-resolvers.memtest.ts @@ -0,0 +1,35 @@ +import { createTenv } from '@internal/e2e'; +import { memtest } from '@internal/perf/memtest'; + +const cwd = __dirname; + +const { gateway, service } = createTenv(cwd); + +memtest( + { + cwd, + query: /* GraphQL */ ` + query { + node(id: "1") { + id + ... on User { + name + } + self { + id + ... on User { + name + } + } + } + } + `, + }, + async () => + await gateway({ + supergraph: { + with: 'mesh', + services: [await service('Test')], + }, + }), +); diff --git a/e2e/nestjs/nestjs.e2e.ts b/e2e/nestjs/nestjs.e2e.ts index 614a1ae901..73fb18d193 100644 --- a/e2e/nestjs/nestjs.e2e.ts +++ b/e2e/nestjs/nestjs.e2e.ts @@ -6,6 +6,7 @@ import { expect, it } from 'vitest'; const { service } = createTenv(__dirname); const { supergraph, query, result } = createExampleSetup(__dirname); +// TODO: run tests without needing to build the project it.todo('executes the query', async () => { const supergraphPath = await supergraph(); const { port } = await service('nestjs', { diff --git a/e2e/opentelemetry/gateway.config.ts b/e2e/opentelemetry/gateway.config.ts index 17014145fe..071b4fd244 100644 --- a/e2e/opentelemetry/gateway.config.ts +++ b/e2e/opentelemetry/gateway.config.ts @@ -51,5 +51,10 @@ export const gatewayConfig = defineConfig({ ], serviceName: process.env['OTLP_SERVICE_NAME'], }, - plugins: () => [useOnFetchTracer()], + plugins: () => + process.env['MEMTEST'] + ? [ + // disable the plugin in memtests because the upstreamCallHeaders will grew forever reporting a false positive leak + ] + : [useOnFetchTracer()], }); diff --git a/e2e/opentelemetry/opentelemetry.memtest.ts b/e2e/opentelemetry/opentelemetry.memtest.ts new file mode 100644 index 0000000000..5f3c6e92be --- /dev/null +++ b/e2e/opentelemetry/opentelemetry.memtest.ts @@ -0,0 +1,51 @@ +import { Container, createExampleSetup, createTenv } from '@internal/e2e'; +import { memtest } from '@internal/perf/memtest'; +import { beforeAll, describe } from 'vitest'; + +const cwd = __dirname; + +const { gateway, container } = createTenv(cwd); +const { supergraph, query } = createExampleSetup(cwd); + +(['grpc', 'http'] as const).forEach((OTLP_EXPORTER_TYPE) => { + describe(`OpenTelemetry ${OTLP_EXPORTER_TYPE} exporter`, () => { + let jaeger: Container; + beforeAll(async () => { + jaeger = await container({ + name: `jaeger-${OTLP_EXPORTER_TYPE}`, + image: 'jaegertracing/all-in-one:1.56', + env: { + COLLECTOR_OTLP_ENABLED: 'true', + }, + containerPort: 4318, + additionalContainerPorts: [16686, 4317], + healthcheck: ['CMD-SHELL', 'wget --spider http://0.0.0.0:14269'], + }); + }); + const jaegerUrls = { + get http() { + return `http://0.0.0.0:${jaeger.port}/v1/traces`; + }, + get grpc() { + return `http://0.0.0.0:${jaeger.additionalPorts[4317]}`; + }, + }; + + memtest( + { + cwd, + query, + }, + async () => + gateway({ + supergraph: await supergraph(), + env: { + MEMTEST: 1, + OTLP_EXPORTER_TYPE, + OTLP_EXPORTER_URL: jaegerUrls[OTLP_EXPORTER_TYPE], + OTLP_SERVICE_NAME: `memtest-${OTLP_EXPORTER_TYPE}`, + }, + }), + ); + }); +}); diff --git a/e2e/programmatic-batching/programmatic-batching.memtest.ts b/e2e/programmatic-batching/programmatic-batching.memtest.ts new file mode 100644 index 0000000000..e89377ad04 --- /dev/null +++ b/e2e/programmatic-batching/programmatic-batching.memtest.ts @@ -0,0 +1,39 @@ +import { createTenv } from '@internal/e2e'; +import { memtest } from '@internal/perf/memtest'; + +const cwd = __dirname; + +const { service, gateway } = createTenv(cwd); + +memtest( + { + cwd, + query: /* GraphQL */ ` + fragment UserF on User { + id + name + } + query User { + john: user(id: 1) { + ...UserF + } + jane: user(id: 2) { + ...UserF + } + } + `, + expectedHeavyFrame: (frame) => + [ + // heap snapshots were analyised and concluded that the memory is stable considering the given heavy frames + 'onwrite', + 'leave', + ].includes(frame.name), + }, + async () => + gateway({ + supergraph: { + with: 'mesh', + services: [await service('api')], + }, + }), +); diff --git a/examples/federation-subscriptions-passthrough/example.tar.gz b/examples/federation-subscriptions-passthrough/example.tar.gz index 064860d1b2..5a134a8ebf 100644 Binary files a/examples/federation-subscriptions-passthrough/example.tar.gz and b/examples/federation-subscriptions-passthrough/example.tar.gz differ diff --git a/examples/federation-subscriptions-passthrough/services/products/server.ts b/examples/federation-subscriptions-passthrough/services/products/server.ts index d8f23438fb..1550988287 100644 --- a/examples/federation-subscriptions-passthrough/services/products/server.ts +++ b/examples/federation-subscriptions-passthrough/services/products/server.ts @@ -53,6 +53,13 @@ const schema = buildSubgraphSchema([ clearInterval(interval); }), }, + newProduct: { + async *subscribe() { + for (const product of products) { + yield { newProduct: product }; + } + }, + }, }, }, }, @@ -76,6 +83,10 @@ const graphqlWsServer = useServer( console.error('Multiple WebSocket connections attempted'); process.exit(1); } + if (process.env['MEMTEST']) { + // no need to authenticate in memtests + return true; + } // make sure the authorization header is propagated by the gateway if (connectionParams?.['token'] !== TOKEN) { return false; diff --git a/examples/federation-subscriptions-passthrough/services/products/typeDefs.graphql b/examples/federation-subscriptions-passthrough/services/products/typeDefs.graphql index 5cc9564412..959ac7eb88 100644 --- a/examples/federation-subscriptions-passthrough/services/products/typeDefs.graphql +++ b/examples/federation-subscriptions-passthrough/services/products/typeDefs.graphql @@ -4,6 +4,7 @@ extend type Query { type Subscription { productPriceChanged: Product! + newProduct: Product! } type Product @key(fields: "id") { diff --git a/examples/federation-subscriptions-passthrough/services/reviews/server.ts b/examples/federation-subscriptions-passthrough/services/reviews/server.ts index 328bd1fc43..827485e36e 100644 --- a/examples/federation-subscriptions-passthrough/services/reviews/server.ts +++ b/examples/federation-subscriptions-passthrough/services/reviews/server.ts @@ -26,6 +26,13 @@ const schema = buildSubgraphSchema([ }, resolve: (count: number) => count, }, + newReview: { + async *subscribe() { + for (const review of reviews) { + yield { newReview: review }; + } + }, + }, }, }, }, diff --git a/examples/federation-subscriptions-passthrough/services/reviews/typeDefs.graphql b/examples/federation-subscriptions-passthrough/services/reviews/typeDefs.graphql index 2aae4d78bf..c8abf8f818 100644 --- a/examples/federation-subscriptions-passthrough/services/reviews/typeDefs.graphql +++ b/examples/federation-subscriptions-passthrough/services/reviews/typeDefs.graphql @@ -9,4 +9,5 @@ type Review { type Subscription { countdown(from: Int!): Int + newReview: Review! } diff --git a/internal/e2e/src/tenv.ts b/internal/e2e/src/tenv.ts index 2038c6f95e..3b28c9f513 100644 --- a/internal/e2e/src/tenv.ts +++ b/internal/e2e/src/tenv.ts @@ -14,7 +14,7 @@ import { fakePromise, registerAbortSignalListener, } from '@graphql-tools/utils'; -import { Proc, ProcOptions, spawn, waitForPort } from '@internal/proc'; +import { Proc, ProcOptions, Server, spawn, waitForPort } from '@internal/proc'; import { boolEnv, createOpt, @@ -82,13 +82,10 @@ yarn build && E2E_GATEWAY_RUNNER=bun-docker yarn workspace @graphql-hive/gateway return runner as ServeRunner; })(); -export interface Server extends Proc { - port: number; - protocol: string; -} - export interface GatewayOptions extends ProcOptions { port?: number; + /** Extra args to pass to the process. */ + args?: (string | number | boolean)[]; /** * Path to the supergraph file or {@link ComposeOptions} which will be used for composition with GraphQL Mesh. * If {@link ComposeOptions} is provided, its {@link ComposeOptions.output output} will always be set to `graphql`; @@ -140,6 +137,8 @@ export interface Gateway extends Server { } export interface ServiceOptions extends ProcOptions { + /** Extra args to pass to the process. */ + args?: (string | number | boolean)[]; /** * Custom port of this service. * @@ -163,6 +162,8 @@ export interface Service extends Server { } export interface ComposeOptions extends ProcOptions { + /** Extra args to pass to the process. */ + args?: (string | number | boolean)[]; /** * Write the compose output/result to a temporary unique file with the extension. * The file will be deleted after the tests complete. @@ -189,6 +190,8 @@ export interface Compose extends Proc { } export interface ContainerOptions extends ProcOptions { + /** Extra args to pass to the process. */ + args?: (string | number | boolean)[]; /** * Name of the service. * Note that the actual Docker container name will have a unique suffix @@ -336,7 +339,7 @@ export function createTenv(cwd: string): Tenv { return fs.writeFile(filePath, content, 'utf-8'); }, }, - spawn(command, { args: extraArgs = [], ...opts } = {}) { + spawn(command, opts) { const [cmd, ...args] = Array.isArray(command) ? command : command.split(' '); @@ -349,7 +352,6 @@ export function createTenv(cwd: string): Tenv { }, String(cmd), ...args, - ...extraArgs, ); }, gatewayRunner, @@ -521,6 +523,9 @@ export function createTenv(cwd: string): Tenv { replaceStderr: (str) => str.replaceAll(__project, ''), }, 'node', + // use next available port when starting inspector (note that this does not start inspect, this still needs to be done manually) + // it's not set because in JIT mode because it does not work together (why? no clue) + args.includes('--jit') ? null : '--inspect-port=0', '--import', 'tsx', path.resolve(__project, 'packages', 'gateway', 'src', 'bin.ts'), @@ -713,7 +718,12 @@ export function createTenv(cwd: string): Tenv { gatewayPort && createPortOpt(gatewayPort), ...args, ); - const service: Service = { ...proc, name, port, protocol }; + const service: Service = { + ...proc, + name, + port, + protocol, + }; await Promise.race([ waitForExit .then(() => { @@ -872,6 +882,10 @@ export function createTenv(cwd: string): Tenv { await ctr.start(); const container: Container = { + kill() { + throw new Error('Cannot send signals to containers.'); + }, + waitForExit: ctr.wait(), containerName, name, port: hostPort, diff --git a/internal/examples/src/convert.ts b/internal/examples/src/convert.ts index 66a0c957b3..9602b530d7 100644 --- a/internal/examples/src/convert.ts +++ b/internal/examples/src/convert.ts @@ -180,6 +180,8 @@ export async function convertE2EToExample(config: ConvertE2EToExampleConfig) { !path.basename(extraDirOrFile).includes('.e2e.') && // not a bench !path.basename(extraDirOrFile).includes('.bench.') && + // not a memtest + !path.basename(extraDirOrFile).includes('.memtest.') && // not a dockerile !path.basename(extraDirOrFile).includes('Dockerfile') && // not test snapshots diff --git a/internal/perf/package.json b/internal/perf/package.json new file mode 100644 index 0000000000..f1b5317443 --- /dev/null +++ b/internal/perf/package.json @@ -0,0 +1,22 @@ +{ + "name": "@internal/perf", + "type": "module", + "private": true, + "main": "./src/index.ts", + "dependencies": { + "@memlab/core": "^1.1.39", + "@memlab/heap-analysis": "^1.0.36", + "@whatwg-node/promise-helpers": "^1.2.1", + "canvas": "^3.1.0", + "chart.js": "^4.4.7", + "chartjs-plugin-trendline": "^2.1.6", + "memlab": "^1.1.56", + "parse-duration": "^2.0.0", + "speedscope": "./speedscope-1.23.0-alpha.4.tgz", + "ws": "^8.18.0" + }, + "devDependencies": { + "@types/k6": "^0.54.2", + "@types/ws": "^8.5.12" + } +} diff --git a/internal/perf/speedscope-1.23.0-alpha.4.tgz b/internal/perf/speedscope-1.23.0-alpha.4.tgz new file mode 100644 index 0000000000..86f1934eb9 Binary files /dev/null and b/internal/perf/speedscope-1.23.0-alpha.4.tgz differ diff --git a/internal/perf/src/chart.ts b/internal/perf/src/chart.ts new file mode 100644 index 0000000000..b8609fcd7a --- /dev/null +++ b/internal/perf/src/chart.ts @@ -0,0 +1,170 @@ +import { Canvas, createCanvas } from 'canvas'; +import { Chart, ChartConfiguration, ChartData } from 'chart.js/auto'; +import chartTrendline from // @ts-expect-error no type definitions +'chartjs-plugin-trendline'; +import { LoadtestMemorySample } from './loadtest'; + +export interface LineChartOptions { + /** The tick label callbacks of the Y scale entries. */ + yTicksCallback?: (tickValue: number | string) => string; +} + +export function createLineChart( + data: ChartData<'line'>, + options: LineChartOptions = {}, +): Canvas { + const canvas = createCanvas(1366, 768, 'svg'); + + const chartConfig: ChartConfiguration = { + type: 'line', + data, + options: { + responsive: false, // because we're rendering the chart statically + plugins: { + legend: { + labels: { + // hide legend labels that have no text + filter: ({ text }) => !!text, + }, + }, + }, + scales: { + y: { + ticks: { + callback: options.yTicksCallback, + }, + }, + }, + }, + plugins: [ + chartTrendline, + { + id: 'set-white-background', + beforeDraw: (chart) => { + chart.ctx.fillStyle = 'white'; + chart.ctx.fillRect(0, 0, chart.width, chart.height); + chart.ctx.restore(); + }, + }, + ], + }; + + new Chart( + // @ts-expect-error canvas types are of a different instance, but they fit + canvas.getContext('2d'), + chartConfig, + ); + + return canvas; +} + +export function createMemorySampleLineChart(samples: LoadtestMemorySample[]) { + const chart = createLineChart( + { + labels: samples.map(({ time }) => toTimeString(time)), + datasets: [ + { + label: 'Idle', + borderColor: 'blue', + data: [], + }, + { + label: 'Loadtest', + borderColor: 'red', + data: [], + }, + { + label: 'Calmdown', + borderColor: 'orange', + data: [], + }, + { + pointStyle: false, + cubicInterpolationMode: 'monotone', + data: samples.map(({ mem }) => mem), + // @ts-expect-error plugin is provided but the options are not typed + trendlineLinear: { + colorMin: 'black', + colorMax: 'black', + width: 1, + lineStyle: 'dashed', + label: { + color: 'black', + text: 'Memory trend', + }, + }, + segment: { + // color the diffrent phase segments + borderColor: (ctx) => { + // already if the second point is in the phase, + // we want to color from the first point + const p1 = samples[ctx.p1DataIndex]; + switch (p1?.phase) { + case 'idle': + return 'blue'; + case 'loadtest': + return 'red'; + case 'calmdown': + return 'orange'; + default: + throw new Error(`Unexpected phase ${p1?.phase}`); + } + }, + }, + }, + ], + }, + { + yTicksCallback: (tickValue) => `${tickValue} MB`, + }, + ); + return chart; +} + +function toTimeString(date: Date) { + let hours = date.getUTCHours().toString(); + if (hours.length === 1) { + hours = `0${hours}`; + } + + let minutes = date.getUTCMinutes().toString(); + if (minutes.length === 1) { + minutes = `0${minutes}`; + } + + let seconds = date.getUTCSeconds().toString(); + if (seconds.length === 1) { + seconds = `0${seconds}`; + } + + return `${hours}:${minutes}:${seconds}`; +} + +/** + * Calculates the increase trend usind linear regression. + * + * It will clamp the snapshots to from 0 to 1 to get a percentage based slope. + * + * TODO: match the trend calculation with the 'chartjs-plugin-trendline' plugin + * + * @param snapshots - An array of memory snapshots in MB. + * + * @returns The slope of the linear regression line. + */ +export function calculateTrendSlope(yValues: number[]) { + if (yValues.length < 2) { + throw new Error( + 'At least two points are required to calculate the trend slope', + ); + } + const n = yValues.length; + const xValues = yValues.map((_, i) => i + 1); + + const sumX = xValues.reduce((acc, x) => acc + x, 0); + const sumY = yValues.reduce((acc, y) => acc + y, 0); + const sumXY = xValues.reduce((acc, x, i) => acc + x * yValues[i]!, 0); + const sumX2 = xValues.reduce((acc, x) => acc + x * x, 0); + + const slope = (n * sumXY - sumX * sumY) / (n * sumX2 - sumX * sumX); + return slope; +} diff --git a/internal/perf/src/heap.ts b/internal/perf/src/heap.ts new file mode 100644 index 0000000000..69ea864e4e --- /dev/null +++ b/internal/perf/src/heap.ts @@ -0,0 +1,199 @@ +import { HeapProfiler } from 'inspector'; +import path from 'path'; +import { serializer } from '@memlab/core'; +import { getFullHeapFromFile, PluginUtils } from '@memlab/heap-analysis'; +import { CallTreeNode, Frame } from 'speedscope/profile'; +import { importFromChromeHeapProfile } from 'speedscope/profile/v8heapalloc'; + +const __project = path.resolve(__dirname, '..', '..', '..') + path.sep; + +/** + * Analyses the {@link file heap snapshot file} logging the largest single objects and summed objects. + * + * TODO: Leak detection and return something. + */ +export async function analyzeHeapSnapshot(file: string) { + const snap = await getFullHeapFromFile(file); + + // these are largest _single_ objects in the heap + const largestSingleObjects = PluginUtils.filterOutLargestObjects( + snap, + PluginUtils.isNodeWorthInspecting, + 5, + ); + console.group('Largest single objects'); + for (const node of largestSingleObjects) { + console.group(`(${node.type}) ${node.name}`.trim()); + + console.log('self size', (node.self_size / 1024).toFixed(2), 'kB'); + console.log( + 'retained size', + (node.retainedSize / (1024 * 1024)).toFixed(2), + 'MB', + ); + + console.groupEnd(); + } + console.groupEnd(); + + // + + // a summed node is a node whose sizes are summed up for all instances of that node + type SummedObject = { + type: string; + name: string; + selfSize: number; + retainedSize: number; + }; + + const summedObjects: { + [key: string]: SummedObject; + } = {}; + + snap.nodes.forEach((node) => { + if (!PluginUtils.isNodeWorthInspecting(node)) { + // we only care about nodes we have control over, like objects and strings + return; + } + + const key = serializer.summarizeNodeShape(node); + + if (summedObjects[key]) { + summedObjects[key].selfSize += node.self_size; + summedObjects[key].retainedSize += node.retainedSize; + } else { + summedObjects[key] = { + type: node.type, + name: node.name, + selfSize: node.self_size, + retainedSize: node.retainedSize, + }; + } + }); + + const largestSummedObjects: SummedObject[] = []; + + for (const object of Object.values(summedObjects)) { + // only the top 10 nodes with the highest retained size + largestSummedObjects.push(object); + largestSummedObjects.sort((n1, n2) => n2.retainedSize - n1.retainedSize); + if (largestSummedObjects.length > 10) { + largestSummedObjects.pop(); + } + } + + console.group('Largest summed objects'); + for (const node of largestSummedObjects) { + console.group(`(${node.type}) ${node.name}`.trim()); + + console.log('self size', (node.selfSize / (1024 * 1024)).toFixed(2), 'MB'); + console.log( + 'retained size', + (node.retainedSize / (1024 * 1024)).toFixed(2), + 'MB', + ); + + console.groupEnd(); + } + console.groupEnd(); +} + +export interface HeapSamplingProfileNode { + name: string; + /** + * The project relative path to the file with the node. + * Optionally suffixed with a line number if available. + */ + file: string | null; + /** The size in memory the frame itself allocated in bytes. */ + selfSize: number; + /** {@link selfSize Self size} in MBs. */ + selfSizeInMB: number; + /** The size in memory the frame and its callees allocated in bytes. */ + totalSize: number; + /** {@link totalSize Total size} in MBs. */ + totalSizeInMB: number; +} + +export interface HeapSamplingProfileFrame extends HeapSamplingProfileNode { + /** + * Callstack of heaviest frames ordered from the leaf (this frame) to the root. + * The leaf node is not included. + */ + callstack: HeapSamplingProfileNode[]; +} + +/** + * Analyses the {@link v8Profile v8 heap sampling profile}. + * + * @param v8Profile - The v8 heap sampling profile. + * @param threshold - The exclusive threshold of {@link HeapSamplingProfileNode.selfSize node's self size} usage of the whole memory that's considered heavy. Defaults to `0.03` (3%). + * + * @returns The heaviest leaf frames. + */ +export function getHeaviestFramesFromHeapSamplingProfile( + v8Profile: HeapProfiler.SamplingHeapProfile, + threshold = 0.03, +): HeapSamplingProfileFrame[] { + const profile = importFromChromeHeapProfile( + // @ts-expect-error is ok, speedscope will add the id and totalSize properties + v8Profile, + ); + + // frames that on their own have allocated a lot of memory out of all memory + const totalSize = profile.getTotalNonIdleWeight(); + const highSelfSizeFrames: Frame[] = []; + profile.forEachFrame((frame) => { + const selfPerc = frame.getSelfWeight() / totalSize; + if (selfPerc > threshold) { + highSelfSizeFrames.push(frame); + } + }); + highSelfSizeFrames.sort((a, b) => b.getSelfWeight() - a.getSelfWeight()); + + const heaviestFrames: HeapSamplingProfileFrame[] = []; + + function toHeapSamplingProfileNode(frame: Frame): HeapSamplingProfileNode { + let file = frame.file?.split(__project)[1] || null; + if (file && frame.line) { + file += `:${frame.line + 1}`; // we increment because the line is weirdly off by one + } + return { + name: frame.name, + // we split then take the first element to remove the project path even if it has file:/// prefix + file, + selfSize: frame.getSelfWeight(), + selfSizeInMB: Number((frame.getSelfWeight() / (1024 * 1024)).toFixed(2)), + totalSize: frame.getTotalWeight(), + totalSizeInMB: Number( + (frame.getTotalWeight() / (1024 * 1024)).toFixed(2), + ), + }; + } + + for (const frame of highSelfSizeFrames) { + // get the callers of this frame (this frame is the leaf node) + const callers = profile.getInvertedProfileForCallersOf(frame); + + // "grouped" call tree is one in which each node has at most one child per + // frame. nodes are ordered in decreasing order of weight + const calltree = callers.getGroupedCalltreeRoot(); + + // the call stack with the biggest size from the root to the leaf node (this frame) + const biggestWeightStack: Frame[] = []; + let node: CallTreeNode | undefined = + // we want to omit this frame from the callstack, so we take the first child of first child + calltree.children[0]?.children[0]; + while (node) { + biggestWeightStack.push(node.frame); + node = node.children[0]; + } + + heaviestFrames.push({ + ...toHeapSamplingProfileNode(frame), + callstack: biggestWeightStack.map(toHeapSamplingProfileNode), + }); + } + + return heaviestFrames; +} diff --git a/internal/perf/src/index.ts b/internal/perf/src/index.ts new file mode 100644 index 0000000000..344059e4a7 --- /dev/null +++ b/internal/perf/src/index.ts @@ -0,0 +1,3 @@ +export * from './loadtest'; +export * from './heap'; +export * from './inspector'; diff --git a/internal/perf/src/inspector.ts b/internal/perf/src/inspector.ts new file mode 100644 index 0000000000..cb2812f90a --- /dev/null +++ b/internal/perf/src/inspector.ts @@ -0,0 +1,190 @@ +import fs from 'fs/promises'; +import { HeapProfiler } from 'inspector'; +import { setTimeout } from 'timers/promises'; +import { Proc } from '@internal/proc'; +import { createDeferredPromise } from '@whatwg-node/promise-helpers'; +import { WebSocket } from 'ws'; + +export interface Inspector { + collectGarbage(): Promise; + /** + * Takes and writes the heap snapshot to the provided {@link path}. + * + * BEWARE: Taking heap snapshots is a blocking operation and often introduces a leak to the process (memory + * is much more stable when no snapshots are taken, can cause false positives). Where possible, consider + * using {@link startHeapSampling heap sampling} instead. + */ + writeHeapSnapshot(path: string): Promise; + /** @returns Function that stops the heap sampling and returns the {@link InspectorHeapSamplingProfile sampling profile}. */ + startHeapSampling(): Promise<() => Promise>; + [Symbol.dispose](): void; +} + +/** + * Activates the inspector protocol on the provided Node process, waits for + * it to become available and connects. + */ +export async function connectInspector(proc: Proc): Promise { + proc.kill('SIGUSR1'); // activate inspector protocol + + // wait for the debugger to start + let debuggerUrl = ''; + let attempts = 0; + while (!debuggerUrl) { + if (attempts++ > 10) { + throw new Error('Debugger URL not found within reasonable time'); + } + await setTimeout(100); + for (const line of proc.getStd('err').split('\n')) { + debuggerUrl = line.split('Debugger listening on ')?.[1] || ''; + if (debuggerUrl) { + break; + } + } + } + + const ws = new WebSocket(debuggerUrl, { + maxPayload: 10000000000, // messages can be huge, increase max buffer size like here https://nodejs.org/en/learn/diagnostics/memory/using-heap-snapshot#4-trigger-heap-snapshot-using-inspector-protocol + }); + await new Promise((resolve, reject) => { + ws.once('open', resolve); + ws.once('close', reject); + }); + + const { promise: throwOnClosed, reject: closed } = createDeferredPromise(); + ws.once('close', closed); + + let closedError: unknown = null; + function throwIfClosed() { + if (closedError) { + throw closedError; + } + } + throwOnClosed.catch((err) => { + closedError = err; + }); + + // enable heap profiler on connect + // TODO: do we always need it? does it influence the performance? + await call(ws, 'HeapProfiler.enable'); + + return { + async collectGarbage() { + throwIfClosed(); + await call(ws, 'HeapProfiler.collectGarbage'); + }, + async writeHeapSnapshot(path: string) { + throwIfClosed(); + + // replace existing snapshot + await fs.rm(path, { force: true }); + + const fd = await fs.open(path, 'w'); + await using _0 = { + async [Symbol.asyncDispose]() { + await fd.close(); + }, + }; + + const id = genId(); + + const writes: Promise[] = []; + const { promise: waitForHeapSnapshotDone, resolve: heapSnapshotDone } = + createDeferredPromise(); + function onMessage(m: WebSocket.Data) { + const data = JSON.parse(m.toString()); + if (data.params?.chunk) { + // write chunks to file asyncronously + writes.push(fd.write(data.params.chunk)); + } + if (data.id === id) { + // receiving a message with the id of the takeHeapSnapshot means the snapshotting is done + heapSnapshotDone(); + } + } + ws.on('message', onMessage); + using _1 = { + [Symbol.dispose]() { + ws.off('message', onMessage); + }, + }; + + // make sure socket is still open + throwIfClosed(); + + // initiate heap snapshot taking + ws.send(`{"id":${id},"method":"HeapProfiler.takeHeapSnapshot"}`); + + await Promise.race([throwOnClosed, waitForHeapSnapshotDone]); + + // wait for all writes to complete + await Promise.all(writes); + }, + async startHeapSampling() { + throwIfClosed(); + await call(ws, 'HeapProfiler.startSampling'); + return async function stopSampling() { + throwIfClosed(); + const msg = await call(ws, 'HeapProfiler.stopSampling'); + const data = JSON.parse(msg.toString()); + if (!data.result?.profile) { + throw new Error( + 'No heap sampling profile found after stopping\n' + msg.toString(), + ); + } + return data.result.profile; + }; + }, + [Symbol.dispose]() { + // TODO: should we throw if closed here? we already dont care at this point + ws.close(); + }, + }; +} + +function genId() { + return Math.floor(Math.random() * 1000); +} + +/** Calls/invokes a method on the inspector protocol and waits for confirmation. */ +function call( + ws: WebSocket, + method: + | 'HeapProfiler.enable' + | 'HeapProfiler.disable' + | 'HeapProfiler.collectGarbage' + | 'HeapProfiler.startSampling' + | 'HeapProfiler.stopSampling' + | 'HeapProfiler.startTrackingHeapObjects' + | 'HeapProfiler.stopTrackingHeapObjects', +): Promise { + if (ws.readyState !== WebSocket.OPEN) { + throw new Error('WebSocket is not open'); + } + const id = genId(); + ws.send(`{"id":${id},"method":"${method}"}`); + return waitForMessage(ws, (m) => m.toString().includes(`"id":${id}`)); +} + +function waitForMessage( + ws: WebSocket, + check: (m: WebSocket.Data) => boolean, +): Promise { + const { promise, resolve, reject } = createDeferredPromise(); + ws.once('close', reject); + function onMessage(m: WebSocket.Data) { + if (check(m)) { + resolve(m); + } + } + ws.once('message', onMessage); + return Promise.race([ + promise, + setTimeout(10_000).then(() => { + throw new Error('Timeout waiting for message'); + }), + ]).finally(() => { + ws.off('close', reject); + ws.off('message', onMessage); + }); +} diff --git a/internal/perf/src/loadtest-script.ts b/internal/perf/src/loadtest-script.ts new file mode 100644 index 0000000000..46114c935d --- /dev/null +++ b/internal/perf/src/loadtest-script.ts @@ -0,0 +1,35 @@ +import { check } from 'k6'; +import { test } from 'k6/execution'; +import http from 'k6/http'; + +export default function () { + const url = __ENV['URL']; + if (!url) { + return test.abort('Environment variable "URL" not provided'); + } + + const query = __ENV['QUERY']; + if (!query) { + return test.abort('Environment variable "QUERY" not provided'); + } + + const res = http.post(url, { query }); + + if (__ENV['ALLOW_FAILING_REQUESTS']) { + check(res, { + 'status is 200': (res) => res.status === 200, + 'body contains data': (res) => + !!res.body?.toString().includes('"data":{'), + }); + } else { + const body = res.body?.toString() || ''; + if (res.status !== 200) { + return test.abort( + `Status is not 200, got status ${res.status} and body:\n${body}`, + ); + } + if (!body.includes('"data":{')) { + return test.abort(`Body does not contain "data":\n${body}`); + } + } +} diff --git a/internal/perf/src/loadtest.ts b/internal/perf/src/loadtest.ts new file mode 100644 index 0000000000..df4069bf2d --- /dev/null +++ b/internal/perf/src/loadtest.ts @@ -0,0 +1,269 @@ +import fs from 'fs/promises'; +import { HeapProfiler } from 'inspector'; +import os from 'os'; +import path from 'path'; +import { setTimeout } from 'timers/promises'; +import { ProcOptions, Server, spawn } from '@internal/proc'; +import { trimError } from '@internal/testing'; +import { fetch } from '@whatwg-node/fetch'; +import { connectInspector, Inspector } from './inspector'; + +export interface LoadtestOptions extends ProcOptions { + cwd: string; + /** @default 100 */ + vus?: number; + /** Idling duration before loadtest in milliseconds. */ + idle: number; + /** Duration of the loadtest in milliseconds. */ + duration: number; + /** Calmdown duration of the loadtest in milliseconds. This should be enough allowing the GC to kick in. */ + calmdown: number; + /** How many times to run the loadtests? */ + runs: number; + /** The snapshotting window of the GraphQL server memory in milliseconds. */ + memorySnapshotWindow: number; + /** The GraphQL server on which the loadtest is running. */ + server: Server; + /** + * The GraphQL query to execute for the loadtest. + */ + query: string; + /** + * Whether to take heap snapshots on the end of the `idle` phase and then at the end + * of the `calmdown` {@link LoadtestPhase phase} in each of the {@link runs}. + * + * @default false + */ + takeHeapSnapshots?: boolean; + /** + * Should the loadtest immediatelly error out on the first failed request? + * + * This is useful and disabled by default because we want to guarantee that the gateway + * does not yield under pressure. However, for testing purposes, it's useful to allow + * failing requests to debug what's happening. + * + * @default false + */ + allowFailingRequests?: boolean; + /** Callback for memory sampling during the loadtest. */ + onMemorySample?(samples: LoadtestMemorySample[]): Promise | void; + /** Callback when the heapsnapshot has been written on disk. */ + onHeapSnapshot?(snapshot: LoadtestHeapSnapshot): Promise | void; +} + +export type LoadtestPhase = 'idle' | 'loadtest' | 'calmdown'; + +/** Memory usage snapshot in MB of the {@link LoadtestOptions.server GraphQL server} during the given {@link phase}.*/ +export interface LoadtestMemorySample { + phase: LoadtestPhase; + /** The {@link LoadtestOptions.runs run} number, starts with 1. */ + run: number; + /** Moment in time when the sample was taken. */ + time: Date; + /** + * CPU usage as a percentage. The percentage accompanies all cores: if the CPU + * has 2 cores, 100% means 100% of 1 core; and 200% means 100% of both cores. + */ + cpu: number; + /** Memory usage in MB. */ + mem: number; +} + +/** Memory heap snapshot in MB of the {@link LoadtestOptions.server GraphQL server} at the end of the given {@link phase}.*/ +export interface LoadtestHeapSnapshot { + phase: LoadtestPhase; + /** The {@link LoadtestOptions.runs run} number, starts with 1. */ + run: number; + /** Moment in time when the sample was taken. */ + time: Date; + /** Path to the file where the .heapsnapshot is located. */ + file: string; +} + +export async function loadtest(opts: LoadtestOptions): Promise<{ + samples: LoadtestMemorySample[]; + heapsnapshots: LoadtestHeapSnapshot[]; + profile: HeapProfiler.SamplingHeapProfile; +}> { + const { + cwd, + vus = 100, + idle, + duration, + calmdown, + runs, + memorySnapshotWindow, + server, + query, + takeHeapSnapshots, + allowFailingRequests, + onMemorySample, + onHeapSnapshot, + ...procOptions + } = opts; + + if (duration < 3_000) { + throw new Error(`Duration has to be at least 3s, got "${duration}"`); + } + + if (runs < 1) { + throw new Error(`At least one run is necessary, got "${runs}"`); + } + + // make sure the query works before starting the loadtests + // the request here matches the request done in loadtest-script.ts + const res = await fetch( + `${server.protocol}://localhost:${server.port}/graphql`, + { + method: 'POST', + headers: { + 'content-type': 'application/json', + }, + body: JSON.stringify({ query }), + }, + ); + const text = await res.text(); + if (!res.ok) { + const err = new Error( + `Status is not 200, got status ${res.status} ${res.statusText} and body:\n${text}`, + ); + err.name = 'ResponseError'; + throw err; + } + if (!text.includes('"data":{')) { + const err = new Error(`Body does not contain "data":\n${text}`); + err.name = 'ResponseError'; + throw err; + } + + const ctrl = new AbortController(); + using _ = { + [Symbol.dispose]() { + ctrl.abort(); + }, + }; + + const heapsnapshotCwd = await fs.mkdtemp( + path.join(os.tmpdir(), 'hive-gateway_perf_loadtest_heapsnapshots'), + ); + + using inspector = await connectInspector(server); + + let phase: LoadtestPhase = 'idle'; + let run = 1; + + // we dont use a `setInterval` because the proc.getStats is async and we want stats ordered by time + const samples: LoadtestMemorySample[] = []; + const memorySnapshotting = (async () => { + while (!ctrl.signal.aborted) { + await setTimeout(memorySnapshotWindow); + try { + const stats = await server.getStats(); + if (ctrl.signal.aborted) return; + const sample: LoadtestMemorySample = { + phase, + run, + time: new Date(), + ...stats, + }; + samples.push(sample); + await onMemorySample?.(samples); + } catch (err) { + if (!ctrl.signal.aborted) { + throw err; + } + return; // couldve been aborted after timeout or while waiting for stats + } + } + })(); + + const heapsnapshots: LoadtestHeapSnapshot[] = []; + const serverThrowOnExit = server.waitForExit.then(() => { + throw new Error( + `Server exited before the loadtest finished\n${trimError(server.getStd('both'))}`, + ); + }); + + await Promise.race([setTimeout(idle), serverThrowOnExit, memorySnapshotting]); + + if (takeHeapSnapshots) { + const heapsnapshot = await createHeapSnapshot( + heapsnapshotCwd, + inspector, + phase, + run, + ); + heapsnapshots.push(heapsnapshot); + await onHeapSnapshot?.(heapsnapshot); + } + + // start heap sampling after idling (no need to sample anything during the idling phase) + const stopHeapSampling = await inspector.startHeapSampling(); + + for (; run <= runs; run++) { + phase = 'loadtest'; + const [, waitForExit] = await spawn( + { + cwd, + ...procOptions, + env: { + ...procOptions.env, + ALLOW_FAILING_REQUESTS: allowFailingRequests ? 1 : null, + }, + signal: AbortSignal.any([ + ctrl.signal, + AbortSignal.timeout( + duration + + // allow 5s for the k6 process to exit gracefully + 5_000, + ), + ]), + }, + 'k6', + 'run', + `--vus=${vus}`, + `--duration=${duration}ms`, + `--env=URL=${server.protocol}://localhost:${server.port}/graphql`, + `--env=QUERY=${query}`, + path.join(__dirname, 'loadtest-script.ts'), + ); + await Promise.race([waitForExit, serverThrowOnExit, memorySnapshotting]); + + phase = 'calmdown'; + await inspector.collectGarbage(); + await Promise.race([ + setTimeout(calmdown), + serverThrowOnExit, + memorySnapshotting, + ]); + + if (takeHeapSnapshots) { + const heapsnapshot = await createHeapSnapshot( + heapsnapshotCwd, + inspector, + phase, + run, + ); + heapsnapshots.push(heapsnapshot); + await onHeapSnapshot?.(heapsnapshot); + } + } + + return { + samples, + heapsnapshots, + profile: await stopHeapSampling(), + }; +} + +async function createHeapSnapshot( + cwd: string, + inspector: Inspector, + phase: LoadtestPhase, + run: number, +): Promise { + const time = new Date(); + const file = path.join(cwd, `${phase}-run-${run}-${Date.now()}.heapsnapshot`); + await inspector.writeHeapSnapshot(file); + return { phase, run, time, file }; +} diff --git a/internal/perf/src/memtest.ts b/internal/perf/src/memtest.ts new file mode 100644 index 0000000000..ec239fcd72 --- /dev/null +++ b/internal/perf/src/memtest.ts @@ -0,0 +1,194 @@ +import fs from 'fs/promises'; +import path from 'path'; +import { Server } from '@internal/proc'; +import { isDebug } from '@internal/testing'; +import { it } from 'vitest'; +import { createMemorySampleLineChart } from './chart'; +import { + getHeaviestFramesFromHeapSamplingProfile, + HeapSamplingProfileFrame, +} from './heap'; +import { loadtest, LoadtestOptions } from './loadtest'; + +export interface MemtestOptions + extends Omit< + LoadtestOptions, + | 'memorySnapshotWindow' + | 'idle' + | 'duration' + | 'calmdown' + | 'runs' + | 'server' + > { + /** + * The snapshotting window of the GraphQL server memory in milliseconds. + * + * @default 1_000 + */ + memorySnapshotWindow?: number; + /** + * Idling duration before loadtests {@link runs run} in milliseconds. + * + * @default 10_000 + */ + idle?: number; + /** + * Duration of the loadtest for each {@link runs run} in milliseconds. + * + * @default 120_000 + */ + duration?: number; + /** + * Calmdown duration after loadtesting {@link runs run} in milliseconds. + * + * @default 30_000 + */ + calmdown?: number; + /** + * How many times to run the loadtests? + * + * @default 3 + */ + runs?: number; + /** + * The heap allocation sampling profile gathered during the loadtests is analysed + * to find the heaviest frames (frames that allocated most of the memory). These, + * high allocation frames, are often the ones that contain a leak. But not always, + * a frame can simply be heavy... There are some usual suspects which we safely ignore; + * but, if the profile contains any other unexpected heavy frames, the test will fail. + * + * Using this callback check, you can add more "expected" heavy frames for a given test. + * + * BEWARE: Please be diligent when adding expected heavy frames. Carefully analyse the + * heap sampling profile and make sure that the frame you're adding is 100% not leaking. + */ + expectedHeavyFrame?: (frame: HeapSamplingProfileFrame) => boolean; +} + +export function memtest(opts: MemtestOptions, setup: () => Promise) { + const { + cwd, + memorySnapshotWindow = 1_000, + idle = 10_000, + duration = 120_000, + calmdown = 30_000, + runs = 3, + onMemorySample, + onHeapSnapshot, + expectedHeavyFrame, + ...loadtestOpts + } = opts; + it( + 'should have stable memory usage', + { + timeout: + (idle + + duration + + calmdown + + // allow 30s for the test teardown in each run + 30_000) * + runs, + }, + async ({ expect }) => { + const server = await setup(); + + const startTime = new Date() + .toISOString() + // replace time colons with dashes to make it a valid filename + .replaceAll(':', '-') + // remove milliseconds + .split('.')[0]; + + const loadtestResult = await loadtest({ + ...loadtestOpts, + cwd, + memorySnapshotWindow, + idle, + duration, + calmdown, + runs, + server, + pipeLogs: isDebug('memtest') ? 'loadtest.out' : undefined, + async onMemorySample(samples) { + if (isDebug('memtest')) { + const chart = createMemorySampleLineChart(samples); + await fs.writeFile( + path.join(cwd, `memtest-memory-usage_${startTime}.svg`), + chart.toBuffer(), + ); + } + return onMemorySample?.(samples); + }, + async onHeapSnapshot(heapsnapshot) { + if (isDebug('memtest')) { + await fs.copyFile( + heapsnapshot.file, + path.join( + cwd, + `memtest-run-${heapsnapshot.run}-${heapsnapshot.phase}_${startTime}.heapsnapshot`, + ), + ); + } + return onHeapSnapshot?.(heapsnapshot); + }, + }); + + // TODO: track failed requests during the loadtest, if any + + const heapSamplingProfileFile = path.join( + cwd, + `memtest_${startTime}.heapprofile`, + ); + if (isDebug('memtest')) { + await fs.writeFile( + heapSamplingProfileFile, + JSON.stringify(loadtestResult.profile), + ); + } + + // NOTE: memory usage slop trend check is disabled allowing us to run the tests in parallel in the CI + // and we dont want to disable it _only_ in the CI because we want consistant tests locally and in the CI + // import { calculateTrendSlope } from './chart' + // const slope = calculateTrendSlope(loadtestResult.samples.map(({ mem }) => mem)); + // expect + // .soft(slope, 'Consistent memory increase detected') + // .toBeLessThan(10); + + const unexpectedHeavyFrames = getHeaviestFramesFromHeapSamplingProfile( + loadtestResult.profile, + ) + .filter( + (frame) => + // these frames are expected to be big + // TODO: inspect the callstack making sure we're filtering out precisely the right frames + !['register', 'WeakRef', 'any', 'set'].includes(frame.name), + ) + .filter((frame) => { + if (expectedHeavyFrame) { + // user-provided heavy frames check + return !expectedHeavyFrame(frame); + } + return true; + }); + + if (unexpectedHeavyFrames.length) { + let msg = `Unexpected heavy frames detected! In total ${unexpectedHeavyFrames.length} and they are:\n\n`; + let i = 1; + for (const frame of unexpectedHeavyFrames) { + msg += `${i++}. ${frame.name} (${frame.file || ''})\n`; + for (const stack of frame.callstack) { + msg += ` ${stack.name} (${stack.file || ''})\n`; + } + msg += '\n'; + } + msg += `Writing heap sampling profile to ${heapSamplingProfileFile}`; + expect.fail(msg); + + await fs.writeFile( + heapSamplingProfileFile, + JSON.stringify(loadtestResult.profile), + ); + } + }, + ); +} diff --git a/internal/proc/src/index.ts b/internal/proc/src/index.ts index 3acd4a0877..8fbd0943ed 100644 --- a/internal/proc/src/index.ts +++ b/internal/proc/src/index.ts @@ -9,6 +9,9 @@ import { fetch } from '@whatwg-node/fetch'; import terminate from 'terminate/promise'; export interface Proc extends AsyncDisposable { + waitForExit: Promise; + /** Sends a signal to the process. */ + kill(signal?: NodeJS.Signals): void; getStd(o: 'out' | 'err' | 'both'): string; getStats(): Promise<{ // Total CPU utilization (of all cores) as a percentage. @@ -18,6 +21,11 @@ export interface Proc extends AsyncDisposable { }>; } +export interface Server extends Proc { + port: number; + protocol: string; +} + export interface ProcOptions { /** * Pipe the logs from the spawned process to the current process, or to a file @@ -33,9 +41,7 @@ export interface ProcOptions { * * They will be merged with `process.env` overriding any existing value. */ - env?: Record; - /** Extra args to pass to the process. */ - args?: (string | number | boolean)[]; + env?: Record; /** Custom replacer of stderr coming from he process. */ replaceStderr?: (str: string) => string; } @@ -65,7 +71,13 @@ export function spawn( // ignore stdin, pipe stdout and stderr stdio: ['ignore', 'pipe', 'pipe'], env: Object.entries(env).reduce( - (acc, [key, val]) => ({ ...acc, [key]: String(val) }), + (acc, [key, val]) => { + if (val == null) { + // omit nullish envionment variables + return acc; + } + return { ...acc, [key]: String(val) }; + }, { ...process.env }, ), shell, @@ -79,6 +91,10 @@ export function spawn( let stderr = ''; let stdboth = ''; const proc: Proc = { + waitForExit, + kill(signal) { + child.kill(signal); + }, getStd(o) { switch (o) { case 'out': diff --git a/internal/testing/src/env.ts b/internal/testing/src/env.ts index 73413e6d4f..82cc3e7604 100644 --- a/internal/testing/src/env.ts +++ b/internal/testing/src/env.ts @@ -1,7 +1,18 @@ import os from 'node:os'; -/** Checks whether the `DEBUG` environment variable is truthy. */ -export function isDebug() { +/** + * Checks whether the `DEBUG` environment variable is truthy. + * If the {@link module} is provided, then the debug will only + * be activated for the specific module in code. + * + * For example, `isDebug('loadtest')` will only activate when the + * variable `loadtest` is listed in the `DEBUG` environment variable. + */ +export function isDebug(module?: string) { + if (module) { + const modules = process.env['DEBUG'] ?? ''.split(',').map((m) => m.trim()); + return modules.includes(module); + } return boolEnv('DEBUG'); } diff --git a/package.json b/package.json index 6633009a61..64ad337769 100644 --- a/package.json +++ b/package.json @@ -23,7 +23,8 @@ "test": "vitest --project unit", "test:bun": "bun test --bail", "test:e2e": "vitest --project e2e", - "test:leaks": "cross-env \"LEAK_TEST=1\" jest --detectOpenHandles --detectLeaks" + "test:leaks": "cross-env \"LEAK_TEST=1\" jest --detectOpenHandles --detectLeaks", + "test:mem": "vitest --project memtest" }, "devDependencies": { "@babel/core": "7.26.10", diff --git a/tsconfig.json b/tsconfig.json index 444baad406..5199965243 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -14,6 +14,8 @@ "@internal/testing": ["./internal/testing/src/index.ts"], "@internal/e2e": ["./internal/e2e/src/index.ts"], "@internal/proc": ["./internal/proc/src/index.ts"], + "@internal/perf": ["./internal/perf/src/index.ts"], + "@internal/perf/memtest": ["./internal/perf/src/memtest.ts"], "@internal/testing/to-be-similar-string": [ "./internal/testing/src/to-be-similar-string.ts" ], diff --git a/vitest.projects.ts b/vitest.projects.ts index 3e7df6045d..b7d8df15ae 100644 --- a/vitest.projects.ts +++ b/vitest.projects.ts @@ -44,4 +44,13 @@ export default defineWorkspace([ }, }, }, + { + extends: './vitest.config.ts', + test: { + name: 'memtest', + include: ['**/*.memtest.ts'], + hookTimeout: testTimeout, + testTimeout, + }, + }, ]); diff --git a/yarn.lock b/yarn.lock index f42eb43990..6695c21550 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1044,7 +1044,7 @@ __metadata: languageName: node linkType: hard -"@babel/code-frame@npm:^7.0.0, @babel/code-frame@npm:^7.12.13, @babel/code-frame@npm:^7.26.2": +"@babel/code-frame@npm:^7.0.0, @babel/code-frame@npm:^7.12.13, @babel/code-frame@npm:^7.25.9, @babel/code-frame@npm:^7.26.2": version: 7.26.2 resolution: "@babel/code-frame@npm:7.26.2" dependencies: @@ -1062,7 +1062,7 @@ __metadata: languageName: node linkType: hard -"@babel/core@npm:7.26.10, @babel/core@npm:^7.11.6, @babel/core@npm:^7.12.3, @babel/core@npm:^7.23.9, @babel/core@npm:^7.24.7, @babel/core@npm:^7.26.10": +"@babel/core@npm:7.26.10, @babel/core@npm:^7.26.10": version: 7.26.10 resolution: "@babel/core@npm:7.26.10" dependencies: @@ -1085,7 +1085,43 @@ __metadata: languageName: node linkType: hard -"@babel/generator@npm:^7.26.10, @babel/generator@npm:^7.26.2, @babel/generator@npm:^7.7.2": +"@babel/core@npm:^7.11.6, @babel/core@npm:^7.12.3, @babel/core@npm:^7.22.9, @babel/core@npm:^7.23.9, @babel/core@npm:^7.24.7": + version: 7.26.9 + resolution: "@babel/core@npm:7.26.9" + dependencies: + "@ampproject/remapping": "npm:^2.2.0" + "@babel/code-frame": "npm:^7.26.2" + "@babel/generator": "npm:^7.26.9" + "@babel/helper-compilation-targets": "npm:^7.26.5" + "@babel/helper-module-transforms": "npm:^7.26.0" + "@babel/helpers": "npm:^7.26.9" + "@babel/parser": "npm:^7.26.9" + "@babel/template": "npm:^7.26.9" + "@babel/traverse": "npm:^7.26.9" + "@babel/types": "npm:^7.26.9" + convert-source-map: "npm:^2.0.0" + debug: "npm:^4.1.0" + gensync: "npm:^1.0.0-beta.2" + json5: "npm:^2.2.3" + semver: "npm:^6.3.1" + checksum: 10c0/ed7212ff42a9453765787019b7d191b167afcacd4bd8fec10b055344ef53fa0cc648c9a80159ae4ecf870016a6318731e087042dcb68d1a2a9d34eb290dc014b + languageName: node + linkType: hard + +"@babel/generator@npm:^7.16.0, @babel/generator@npm:^7.26.8": + version: 7.26.8 + resolution: "@babel/generator@npm:7.26.8" + dependencies: + "@babel/parser": "npm:^7.26.8" + "@babel/types": "npm:^7.26.8" + "@jridgewell/gen-mapping": "npm:^0.3.5" + "@jridgewell/trace-mapping": "npm:^0.3.25" + jsesc: "npm:^3.0.2" + checksum: 10c0/9467f197d285ac315d1fa419138d36a3bfd69ca4baf763e914acab12f5f38e5d231497f6528e80613b28e73bb28c66fcc50b250b1f277b1a4d38ac14b03e9674 + languageName: node + linkType: hard + +"@babel/generator@npm:^7.26.10": version: 7.26.10 resolution: "@babel/generator@npm:7.26.10" dependencies: @@ -1098,6 +1134,19 @@ __metadata: languageName: node linkType: hard +"@babel/generator@npm:^7.26.2, @babel/generator@npm:^7.26.9, @babel/generator@npm:^7.7.2": + version: 7.26.9 + resolution: "@babel/generator@npm:7.26.9" + dependencies: + "@babel/parser": "npm:^7.26.9" + "@babel/types": "npm:^7.26.9" + "@jridgewell/gen-mapping": "npm:^0.3.5" + "@jridgewell/trace-mapping": "npm:^0.3.25" + jsesc: "npm:^3.0.2" + checksum: 10c0/6b78872128205224a9a9761b9ea7543a9a7902a04b82fc2f6801ead4de8f59056bab3fd17b1f834ca7b049555fc4c79234b9a6230dd9531a06525306050becad + languageName: node + linkType: hard + "@babel/helper-annotate-as-pure@npm:^7.25.9": version: 7.25.9 resolution: "@babel/helper-annotate-as-pure@npm:7.25.9" @@ -1292,7 +1341,17 @@ __metadata: languageName: node linkType: hard -"@babel/parser@npm:^7.1.0, @babel/parser@npm:^7.14.7, @babel/parser@npm:^7.20.7, @babel/parser@npm:^7.23.9, @babel/parser@npm:^7.24.7, @babel/parser@npm:^7.26.10, @babel/parser@npm:^7.26.2, @babel/parser@npm:^7.26.3, @babel/parser@npm:^7.26.9": +"@babel/helpers@npm:^7.26.9": + version: 7.26.9 + resolution: "@babel/helpers@npm:7.26.9" + dependencies: + "@babel/template": "npm:^7.26.9" + "@babel/types": "npm:^7.26.9" + checksum: 10c0/3d4dbc4a33fe4181ed810cac52318b578294745ceaec07e2f6ecccf6cda55d25e4bfcea8f085f333bf911c9e1fc13320248dd1d5315ab47ad82ce1077410df05 + languageName: node + linkType: hard + +"@babel/parser@npm:^7.1.0, @babel/parser@npm:^7.14.7, @babel/parser@npm:^7.20.7, @babel/parser@npm:^7.23.9, @babel/parser@npm:^7.24.7, @babel/parser@npm:^7.26.10, @babel/parser@npm:^7.26.2, @babel/parser@npm:^7.26.3, @babel/parser@npm:^7.26.8, @babel/parser@npm:^7.26.9": version: 7.26.10 resolution: "@babel/parser@npm:7.26.10" dependencies: @@ -1303,6 +1362,28 @@ __metadata: languageName: node linkType: hard +"@babel/parser@npm:^7.16.4, @babel/parser@npm:^7.25.9": + version: 7.26.8 + resolution: "@babel/parser@npm:7.26.8" + dependencies: + "@babel/types": "npm:^7.26.8" + bin: + parser: ./bin/babel-parser.js + checksum: 10c0/da04f26bae732a5b6790775a736b58c7876c28e62203c5097f043fd7273ef6debe5bfd7a4e670a6819f4549b215c7b9762c6358e44797b3c4d733defc8290781 + languageName: node + linkType: hard + +"@babel/parser@npm:^7.16.8": + version: 7.26.9 + resolution: "@babel/parser@npm:7.26.9" + dependencies: + "@babel/types": "npm:^7.26.9" + bin: + parser: ./bin/babel-parser.js + checksum: 10c0/4b9ef3c9a0d4c328e5e5544f50fe8932c36f8a2c851e7f14a85401487cd3da75cad72c2e1bcec1eac55599a6bbb2fdc091f274c4fcafa6bdd112d4915ff087fc + languageName: node + linkType: hard + "@babel/plugin-bugfix-firefox-class-in-computed-class-key@npm:^7.25.9": version: 7.25.9 resolution: "@babel/plugin-bugfix-firefox-class-in-computed-class-key@npm:7.25.9" @@ -1461,7 +1542,7 @@ __metadata: languageName: node linkType: hard -"@babel/plugin-syntax-import-assertions@npm:^7.26.0": +"@babel/plugin-syntax-import-assertions@npm:^7.20.0, @babel/plugin-syntax-import-assertions@npm:^7.26.0": version: 7.26.0 resolution: "@babel/plugin-syntax-import-assertions@npm:7.26.0" dependencies: @@ -2385,7 +2466,29 @@ __metadata: languageName: node linkType: hard -"@babel/template@npm:^7.25.9, @babel/template@npm:^7.26.9, @babel/template@npm:^7.3.3": +"@babel/template@npm:^7.16.0, @babel/template@npm:^7.26.8": + version: 7.26.8 + resolution: "@babel/template@npm:7.26.8" + dependencies: + "@babel/code-frame": "npm:^7.26.2" + "@babel/parser": "npm:^7.26.8" + "@babel/types": "npm:^7.26.8" + checksum: 10c0/90bc1085cbc090cbdd43af7b9dbb98e6bda96e55e0f565f17ebb8e97c2dfce866dc727ca02b8e08bd2662ba4fd3851907ba3c48618162c291221af17fb258213 + languageName: node + linkType: hard + +"@babel/template@npm:^7.25.9, @babel/template@npm:^7.3.3": + version: 7.25.9 + resolution: "@babel/template@npm:7.25.9" + dependencies: + "@babel/code-frame": "npm:^7.25.9" + "@babel/parser": "npm:^7.25.9" + "@babel/types": "npm:^7.25.9" + checksum: 10c0/ebe677273f96a36c92cc15b7aa7b11cc8bc8a3bb7a01d55b2125baca8f19cae94ff3ce15f1b1880fb8437f3a690d9f89d4e91f16fc1dc4d3eb66226d128983ab + languageName: node + linkType: hard + +"@babel/template@npm:^7.26.9": version: 7.26.9 resolution: "@babel/template@npm:7.26.9" dependencies: @@ -2396,6 +2499,36 @@ __metadata: languageName: node linkType: hard +"@babel/traverse@npm:^7.16.3": + version: 7.26.8 + resolution: "@babel/traverse@npm:7.26.8" + dependencies: + "@babel/code-frame": "npm:^7.26.2" + "@babel/generator": "npm:^7.26.8" + "@babel/parser": "npm:^7.26.8" + "@babel/template": "npm:^7.26.8" + "@babel/types": "npm:^7.26.8" + debug: "npm:^4.3.1" + globals: "npm:^11.1.0" + checksum: 10c0/0771d1ce0351628ad2e8dac56f0d59f706eb125c83fbcc039bde83088ba0a1477244ad5fb060802f90366cc4d7fa871e5009a292aef6205bcf83f2e01d1a0a5d + languageName: node + linkType: hard + +"@babel/traverse@npm:^7.16.8": + version: 7.26.9 + resolution: "@babel/traverse@npm:7.26.9" + dependencies: + "@babel/code-frame": "npm:^7.26.2" + "@babel/generator": "npm:^7.26.9" + "@babel/parser": "npm:^7.26.9" + "@babel/template": "npm:^7.26.9" + "@babel/types": "npm:^7.26.9" + debug: "npm:^4.3.1" + globals: "npm:^11.1.0" + checksum: 10c0/51dd57fa39ea34d04816806bfead04c74f37301269d24c192d1406dc6e244fea99713b3b9c5f3e926d9ef6aa9cd5c062ad4f2fc1caa9cf843d5e864484ac955e + languageName: node + linkType: hard + "@babel/traverse@npm:^7.25.9, @babel/traverse@npm:^7.26.10, @babel/traverse@npm:^7.26.5, @babel/traverse@npm:^7.26.8, @babel/traverse@npm:^7.26.9": version: 7.26.10 resolution: "@babel/traverse@npm:7.26.10" @@ -2421,6 +2554,26 @@ __metadata: languageName: node linkType: hard +"@babel/types@npm:^7.16.8": + version: 7.26.9 + resolution: "@babel/types@npm:7.26.9" + dependencies: + "@babel/helper-string-parser": "npm:^7.25.9" + "@babel/helper-validator-identifier": "npm:^7.25.9" + checksum: 10c0/999c56269ba00e5c57aa711fbe7ff071cd6990bafd1b978341ea7572cc78919986e2aa6ee51dacf4b6a7a6fa63ba4eb3f1a03cf55eee31b896a56d068b895964 + languageName: node + linkType: hard + +"@babel/types@npm:^7.26.8": + version: 7.26.8 + resolution: "@babel/types@npm:7.26.8" + dependencies: + "@babel/helper-string-parser": "npm:^7.25.9" + "@babel/helper-validator-identifier": "npm:^7.25.9" + checksum: 10c0/cd41ea47bb3d7baf2b3bf5e70e9c3a16f2eab699fab8575b2b31a7b1cb64166eb52c97124313863dde0581747bfc7a1810c838ad60b5b7ad1897d8004c7b95a9 + languageName: node + linkType: hard + "@balena/dockerignore@npm:^1.0.2": version: 1.0.2 resolution: "@balena/dockerignore@npm:1.0.2" @@ -4181,7 +4334,7 @@ __metadata: languageName: node linkType: hard -"@graphql-mesh/cross-helpers@npm:^0.4.10, @graphql-mesh/cross-helpers@npm:^0.4.9": +"@graphql-mesh/cross-helpers@npm:^0.4.10": version: 0.4.10 resolution: "@graphql-mesh/cross-helpers@npm:0.4.10" dependencies: @@ -4193,6 +4346,18 @@ __metadata: languageName: node linkType: hard +"@graphql-mesh/cross-helpers@npm:^0.4.9": + version: 0.4.9 + resolution: "@graphql-mesh/cross-helpers@npm:0.4.9" + dependencies: + "@graphql-tools/utils": "npm:^10.6.0" + path-browserify: "npm:1.0.1" + peerDependencies: + graphql: "*" + checksum: 10c0/291a13c4af52ca40e13f9d1edb797f36a5aaa3aee4554970eaf60db38c8309eb3663bbab1f364e2745a2289bd59f102b7a90c45968b5b3f6cac1866827945494 + languageName: node + linkType: hard + "@graphql-mesh/fusion-composition@npm:^0.8.0, @graphql-mesh/fusion-composition@npm:^0.8.1": version: 0.8.1 resolution: "@graphql-mesh/fusion-composition@npm:0.8.1" @@ -4720,7 +4885,7 @@ __metadata: languageName: unknown linkType: soft -"@graphql-tools/code-file-loader@npm:^8.1.15, @graphql-tools/code-file-loader@npm:^8.1.7": +"@graphql-tools/code-file-loader@npm:^8.1.15": version: 8.1.20 resolution: "@graphql-tools/code-file-loader@npm:8.1.20" dependencies: @@ -4735,6 +4900,21 @@ __metadata: languageName: node linkType: hard +"@graphql-tools/code-file-loader@npm:^8.1.7": + version: 8.1.15 + resolution: "@graphql-tools/code-file-loader@npm:8.1.15" + dependencies: + "@graphql-tools/graphql-tag-pluck": "npm:8.3.14" + "@graphql-tools/utils": "npm:^10.8.1" + globby: "npm:^11.0.3" + tslib: "npm:^2.4.0" + unixify: "npm:^1.0.0" + peerDependencies: + graphql: ^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.0.0 + checksum: 10c0/47ed75f414755e8c8d26dc3886d1cbff4d300a60246d214648f30a0be1bb9bb929ad46c9df0355282217e2164eb9e206b0e3d75dd0b603639a052359eddd44fe + languageName: node + linkType: hard + "@graphql-tools/delegate@workspace:^, @graphql-tools/delegate@workspace:packages/delegate": version: 0.0.0-use.local resolution: "@graphql-tools/delegate@workspace:packages/delegate" @@ -4857,7 +5037,7 @@ __metadata: languageName: unknown linkType: soft -"@graphql-tools/graphql-file-loader@npm:^8.0.14, @graphql-tools/graphql-file-loader@npm:^8.0.5": +"@graphql-tools/graphql-file-loader@npm:^8.0.14": version: 8.0.19 resolution: "@graphql-tools/graphql-file-loader@npm:8.0.19" dependencies: @@ -4872,6 +5052,38 @@ __metadata: languageName: node linkType: hard +"@graphql-tools/graphql-file-loader@npm:^8.0.5": + version: 8.0.14 + resolution: "@graphql-tools/graphql-file-loader@npm:8.0.14" + dependencies: + "@graphql-tools/import": "npm:7.0.13" + "@graphql-tools/utils": "npm:^10.8.1" + globby: "npm:^11.0.3" + tslib: "npm:^2.4.0" + unixify: "npm:^1.0.0" + peerDependencies: + graphql: ^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.0.0 + checksum: 10c0/647673e16d68980eeaf78e680d1a728489181f12592889fac5aa0361a0684ecf617d30a949c3a491d50501645c02c670d61d28879a3c7495659e0852f897dd7d + languageName: node + linkType: hard + +"@graphql-tools/graphql-tag-pluck@npm:8.3.14": + version: 8.3.14 + resolution: "@graphql-tools/graphql-tag-pluck@npm:8.3.14" + dependencies: + "@babel/core": "npm:^7.22.9" + "@babel/parser": "npm:^7.16.8" + "@babel/plugin-syntax-import-assertions": "npm:^7.20.0" + "@babel/traverse": "npm:^7.16.8" + "@babel/types": "npm:^7.16.8" + "@graphql-tools/utils": "npm:^10.8.1" + tslib: "npm:^2.4.0" + peerDependencies: + graphql: ^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.0.0 + checksum: 10c0/e089d588a47068de7a4a18e678d306644eedd27001e99f9846288ca8ae9799ba4ed288af522ffa56d69cdeb6b3587106aa5dc6210318ce5e6484c22681a02794 + languageName: node + linkType: hard + "@graphql-tools/graphql-tag-pluck@npm:8.3.19": version: 8.3.19 resolution: "@graphql-tools/graphql-tag-pluck@npm:8.3.19" @@ -4889,6 +5101,19 @@ __metadata: languageName: node linkType: hard +"@graphql-tools/import@npm:7.0.13": + version: 7.0.13 + resolution: "@graphql-tools/import@npm:7.0.13" + dependencies: + "@graphql-tools/utils": "npm:^10.8.1" + resolve-from: "npm:5.0.0" + tslib: "npm:^2.4.0" + peerDependencies: + graphql: ^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.0.0 + checksum: 10c0/680b52f2854b49122580fb86e7edc8843d9e5feb96c4e25e9cb1fb34911aa466f6fa2ded06e5a5a6771f3d23b58e144a34c8ca293ec88e614cc8e98c9d74a2c5 + languageName: node + linkType: hard + "@graphql-tools/import@npm:7.0.18": version: 7.0.18 resolution: "@graphql-tools/import@npm:7.0.18" @@ -4902,7 +5127,7 @@ __metadata: languageName: node linkType: hard -"@graphql-tools/load@npm:^8.0.1, @graphql-tools/load@npm:^8.0.14": +"@graphql-tools/load@npm:^8.0.1": version: 8.0.19 resolution: "@graphql-tools/load@npm:8.0.19" dependencies: @@ -4916,6 +5141,20 @@ __metadata: languageName: node linkType: hard +"@graphql-tools/load@npm:^8.0.14": + version: 8.0.16 + resolution: "@graphql-tools/load@npm:8.0.16" + dependencies: + "@graphql-tools/schema": "npm:^10.0.20" + "@graphql-tools/utils": "npm:^10.8.3" + p-limit: "npm:3.1.0" + tslib: "npm:^2.4.0" + peerDependencies: + graphql: ^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.0.0 + checksum: 10c0/96d4532fe5210a56726a0fcfcb89a72822c2e53f1f3acbd8de9d0f95019de375a749ab8717258784c50115f4cde7081ef1ffdd0ba1b1e135fdf13a10bdfc1b2a + languageName: node + linkType: hard + "@graphql-tools/merge@npm:9.0.19": version: 9.0.19 resolution: "@graphql-tools/merge@npm:9.0.19" @@ -4952,6 +5191,30 @@ __metadata: languageName: node linkType: hard +"@graphql-tools/merge@npm:^9.0.21": + version: 9.0.21 + resolution: "@graphql-tools/merge@npm:9.0.21" + dependencies: + "@graphql-tools/utils": "npm:^10.8.3" + tslib: "npm:^2.4.0" + peerDependencies: + graphql: ^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.0.0 + checksum: 10c0/35bbaea2bbc9e86a631019397e3ca1f53705e2fb608c688aed1d162426de826192b74fc7a8f90d95aa91e2d2bef12ded88674c91d5f42a26b6eb9f516c07fbd6 + languageName: node + linkType: hard + +"@graphql-tools/merge@npm:^9.0.23": + version: 9.0.23 + resolution: "@graphql-tools/merge@npm:9.0.23" + dependencies: + "@graphql-tools/utils": "npm:^10.8.5" + tslib: "npm:^2.4.0" + peerDependencies: + graphql: ^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.0.0 + checksum: 10c0/5991850d1355fd984e35936c445d4e00c3f57e4a4b9b9187415e41d5f028064a904dbe8e01b0ed438d607a3596e654bc6088ea14a0ab71a8fad405ddae1e3df2 + languageName: node + linkType: hard + "@graphql-tools/mock@npm:^9.0.3": version: 9.0.21 resolution: "@graphql-tools/mock@npm:9.0.21" @@ -4979,7 +5242,33 @@ __metadata: languageName: node linkType: hard -"@graphql-tools/schema@npm:^10.0.11, @graphql-tools/schema@npm:^10.0.22, @graphql-tools/schema@npm:^10.0.23, @graphql-tools/schema@npm:^10.0.5": +"@graphql-tools/schema@npm:^10.0.11, @graphql-tools/schema@npm:^10.0.22, @graphql-tools/schema@npm:^10.0.5": + version: 10.0.22 + resolution: "@graphql-tools/schema@npm:10.0.22" + dependencies: + "@graphql-tools/merge": "npm:^9.0.23" + "@graphql-tools/utils": "npm:^10.8.5" + tslib: "npm:^2.4.0" + peerDependencies: + graphql: ^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.0.0 + checksum: 10c0/e4329977d7769cdd3866d2f13055f0da4dcc7c53207dccb492146564e4e4b6ef87b952e3ad1dff9410382d52b9ee1450c7b294e92ba799e86b49a9638a238568 + languageName: node + linkType: hard + +"@graphql-tools/schema@npm:^10.0.20": + version: 10.0.20 + resolution: "@graphql-tools/schema@npm:10.0.20" + dependencies: + "@graphql-tools/merge": "npm:^9.0.21" + "@graphql-tools/utils": "npm:^10.8.3" + tslib: "npm:^2.4.0" + peerDependencies: + graphql: ^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.0.0 + checksum: 10c0/69911f6fa6601307a6a33a2d949d5bf029026667e0aec105b5cc7be5fde760dbc9c969cf41cfee955100f64f70cc1f527556f2de47e3a2a1d065dc5d11f7c2f9 + languageName: node + linkType: hard + +"@graphql-tools/schema@npm:^10.0.23": version: 10.0.23 resolution: "@graphql-tools/schema@npm:10.0.23" dependencies: @@ -5071,6 +5360,35 @@ __metadata: languageName: node linkType: hard +"@graphql-tools/utils@npm:^10.6.0": + version: 10.8.0 + resolution: "@graphql-tools/utils@npm:10.8.0" + dependencies: + "@graphql-typed-document-node/core": "npm:^3.1.1" + cross-inspect: "npm:1.0.1" + dset: "npm:^3.1.4" + tslib: "npm:^2.4.0" + peerDependencies: + graphql: ^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.0.0 + checksum: 10c0/2bfc3b0ef4f00168a4ee6405c482318008ba020cccf6ecd833aff425f1099ec91480158ddef418ca89a11df5aa917ef37410abbb10619c64b84aae342928e778 + languageName: node + linkType: hard + +"@graphql-tools/utils@npm:^10.8.3": + version: 10.8.4 + resolution: "@graphql-tools/utils@npm:10.8.4" + dependencies: + "@graphql-typed-document-node/core": "npm:^3.1.1" + "@whatwg-node/promise-helpers": "npm:^1.0.0" + cross-inspect: "npm:1.0.1" + dset: "npm:^3.1.4" + tslib: "npm:^2.4.0" + peerDependencies: + graphql: ^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.0.0 + checksum: 10c0/c9bd473b79a6029194934965284df0c752cd44d607c65ebcfc20a2cacb9351859e97bab759faac1282db72e31ea6a7a87b0007671064bf3cbc89d907510e503a + languageName: node + linkType: hard + "@graphql-tools/utils@npm:^8.5.2": version: 8.13.1 resolution: "@graphql-tools/utils@npm:8.13.1" @@ -5560,6 +5878,25 @@ __metadata: languageName: unknown linkType: soft +"@internal/perf@workspace:internal/perf": + version: 0.0.0-use.local + resolution: "@internal/perf@workspace:internal/perf" + dependencies: + "@memlab/core": "npm:^1.1.39" + "@memlab/heap-analysis": "npm:^1.0.36" + "@types/k6": "npm:^0.54.2" + "@types/ws": "npm:^8.5.12" + "@whatwg-node/promise-helpers": "npm:^1.2.1" + canvas: "npm:^3.1.0" + chart.js: "npm:^4.4.7" + chartjs-plugin-trendline: "npm:^2.1.6" + memlab: "npm:^1.1.56" + parse-duration: "npm:^2.0.0" + speedscope: ./speedscope-1.23.0-alpha.4.tgz + ws: "npm:^8.18.0" + languageName: unknown + linkType: soft + "@internal/proc@workspace:internal/proc": version: 0.0.0-use.local resolution: "@internal/proc@workspace:internal/proc" @@ -5950,6 +6287,13 @@ __metadata: languageName: node linkType: hard +"@kurkle/color@npm:^0.3.0": + version: 0.3.4 + resolution: "@kurkle/color@npm:0.3.4" + checksum: 10c0/0e9fd55c614b005c5f0c4c755bca19ec0293bc7513b4ea3ec1725234f9c2fa81afbc78156baf555c8b9cb0d305619253c3f5bca016067daeebb3d00ebb4ea683 + languageName: node + linkType: hard + "@lukeed/csprng@npm:^1.0.0": version: 1.1.0 resolution: "@lukeed/csprng@npm:1.1.0" @@ -5983,6 +6327,113 @@ __metadata: languageName: node linkType: hard +"@memlab/api@npm:^1.0.38": + version: 1.0.38 + resolution: "@memlab/api@npm:1.0.38" + dependencies: + "@memlab/core": "npm:^1.1.39" + "@memlab/e2e": "npm:^1.0.39" + "@memlab/heap-analysis": "npm:^1.0.36" + ansi: "npm:^0.3.1" + babar: "npm:^0.2.0" + chalk: "npm:^4.0.0" + fs-extra: "npm:^4.0.2" + minimist: "npm:^1.2.8" + puppeteer: "npm:^22.12.1" + puppeteer-core: "npm:^22.12.1" + string-width: "npm:^4.2.0" + util.promisify: "npm:^1.1.1" + xvfb: "npm:^0.4.0" + checksum: 10c0/9eb5d4d4ca1c03297618c79c03dbe84026ff5348b291003246d50b6c091dfe65e06aaa1d8c046fb42e91631f0acfb887b1afcc943ffc5cabdf54ab6de4d1e756 + languageName: node + linkType: hard + +"@memlab/cli@npm:^1.0.41": + version: 1.0.41 + resolution: "@memlab/cli@npm:1.0.41" + dependencies: + "@memlab/api": "npm:^1.0.38" + "@memlab/core": "npm:^1.1.39" + "@memlab/e2e": "npm:^1.0.39" + "@memlab/heap-analysis": "npm:^1.0.36" + ansi: "npm:^0.3.1" + babar: "npm:^0.2.0" + blessed: "npm:^0.1.81" + chalk: "npm:^4.0.0" + fs-extra: "npm:^4.0.2" + minimist: "npm:^1.2.8" + puppeteer: "npm:^22.12.1" + puppeteer-core: "npm:^22.12.1" + string-width: "npm:^4.2.0" + util.promisify: "npm:^1.1.1" + xvfb: "npm:^0.4.0" + bin: + memlab: bin/memlab.js + checksum: 10c0/8be641530db188d76a97d7b62b92b053e4a895909a6fc7173cbabb9ede05f352dc1da6eb82e42ffefc901597722832f3d0e6af218310dcc63632d033034f6c93 + languageName: node + linkType: hard + +"@memlab/core@npm:^1.1.39": + version: 1.1.39 + resolution: "@memlab/core@npm:1.1.39" + dependencies: + ansi: "npm:^0.3.1" + babar: "npm:^0.2.0" + chalk: "npm:^4.0.0" + fs-extra: "npm:^4.0.2" + minimist: "npm:^1.2.8" + puppeteer: "npm:^22.12.1" + puppeteer-core: "npm:^22.12.1" + string-width: "npm:^4.2.0" + util.promisify: "npm:^1.1.1" + xvfb: "npm:^0.4.0" + checksum: 10c0/2a02a4f137e846e60541eccc735a3f34238df9d93bb35a98aa9d0f0065a8d812c8a1db13d3fca3d9ec2f279cff356fac9c8ca3df903613dfd35004c904bf239d + languageName: node + linkType: hard + +"@memlab/e2e@npm:^1.0.39": + version: 1.0.39 + resolution: "@memlab/e2e@npm:1.0.39" + dependencies: + "@babel/generator": "npm:^7.16.0" + "@babel/parser": "npm:^7.16.4" + "@babel/template": "npm:^7.16.0" + "@babel/traverse": "npm:^7.16.3" + "@memlab/core": "npm:^1.1.39" + ansi: "npm:^0.3.1" + babar: "npm:^0.2.0" + chalk: "npm:^4.0.0" + fs-extra: "npm:^4.0.2" + minimist: "npm:^1.2.8" + puppeteer: "npm:^22.12.1" + puppeteer-core: "npm:^22.12.1" + string-width: "npm:^4.2.0" + util.promisify: "npm:^1.1.1" + xvfb: "npm:^0.4.0" + checksum: 10c0/61b18d5eb36f8964aefe4a1eb685fa1a4bd6cefe28eec9aa93c31562212e6d1d45e2a4141ca25ea7f30f9ef0d6ded1e845c2868c166cac04018ef9bcfe54d612 + languageName: node + linkType: hard + +"@memlab/heap-analysis@npm:^1.0.36": + version: 1.0.36 + resolution: "@memlab/heap-analysis@npm:1.0.36" + dependencies: + "@memlab/core": "npm:^1.1.39" + "@memlab/e2e": "npm:^1.0.39" + ansi: "npm:^0.3.1" + babar: "npm:^0.2.0" + chalk: "npm:^4.0.0" + fs-extra: "npm:^4.0.2" + minimist: "npm:^1.2.8" + puppeteer: "npm:^22.12.1" + puppeteer-core: "npm:^22.12.1" + string-width: "npm:^4.2.0" + util.promisify: "npm:^1.1.1" + xvfb: "npm:^0.4.0" + checksum: 10c0/cb30e06f60715064ab28b4c68a2220f13d2055c3a0a941ccc4198eb0986aea73c0db3eddfc6b333f97f65029a2746d8f344d5183b89a99150d69c89dbbc04df3 + languageName: node + linkType: hard + "@n1ru4l/graphql-live-query-patch@npm:^0.7.0": version: 0.7.0 resolution: "@n1ru4l/graphql-live-query-patch@npm:0.7.0" @@ -6601,6 +7052,13 @@ __metadata: languageName: node linkType: hard +"@oven/bun-darwin-aarch64@npm:1.2.2": + version: 1.2.2 + resolution: "@oven/bun-darwin-aarch64@npm:1.2.2" + conditions: os=darwin & cpu=arm64 + languageName: node + linkType: hard + "@oven/bun-darwin-aarch64@npm:1.2.5": version: 1.2.5 resolution: "@oven/bun-darwin-aarch64@npm:1.2.5" @@ -6608,6 +7066,13 @@ __metadata: languageName: node linkType: hard +"@oven/bun-darwin-x64-baseline@npm:1.2.2": + version: 1.2.2 + resolution: "@oven/bun-darwin-x64-baseline@npm:1.2.2" + conditions: os=darwin & cpu=x64 + languageName: node + linkType: hard + "@oven/bun-darwin-x64-baseline@npm:1.2.5": version: 1.2.5 resolution: "@oven/bun-darwin-x64-baseline@npm:1.2.5" @@ -6615,6 +7080,13 @@ __metadata: languageName: node linkType: hard +"@oven/bun-darwin-x64@npm:1.2.2": + version: 1.2.2 + resolution: "@oven/bun-darwin-x64@npm:1.2.2" + conditions: os=darwin & cpu=x64 + languageName: node + linkType: hard + "@oven/bun-darwin-x64@npm:1.2.5": version: 1.2.5 resolution: "@oven/bun-darwin-x64@npm:1.2.5" @@ -6622,6 +7094,13 @@ __metadata: languageName: node linkType: hard +"@oven/bun-linux-aarch64-musl@npm:1.2.2": + version: 1.2.2 + resolution: "@oven/bun-linux-aarch64-musl@npm:1.2.2" + conditions: os=linux & cpu=aarch64 + languageName: node + linkType: hard + "@oven/bun-linux-aarch64-musl@npm:1.2.5": version: 1.2.5 resolution: "@oven/bun-linux-aarch64-musl@npm:1.2.5" @@ -6629,6 +7108,13 @@ __metadata: languageName: node linkType: hard +"@oven/bun-linux-aarch64@npm:1.2.2": + version: 1.2.2 + resolution: "@oven/bun-linux-aarch64@npm:1.2.2" + conditions: os=linux & cpu=arm64 + languageName: node + linkType: hard + "@oven/bun-linux-aarch64@npm:1.2.5": version: 1.2.5 resolution: "@oven/bun-linux-aarch64@npm:1.2.5" @@ -6636,6 +7122,13 @@ __metadata: languageName: node linkType: hard +"@oven/bun-linux-x64-baseline@npm:1.2.2": + version: 1.2.2 + resolution: "@oven/bun-linux-x64-baseline@npm:1.2.2" + conditions: os=linux & cpu=x64 + languageName: node + linkType: hard + "@oven/bun-linux-x64-baseline@npm:1.2.5": version: 1.2.5 resolution: "@oven/bun-linux-x64-baseline@npm:1.2.5" @@ -6643,6 +7136,13 @@ __metadata: languageName: node linkType: hard +"@oven/bun-linux-x64-musl-baseline@npm:1.2.2": + version: 1.2.2 + resolution: "@oven/bun-linux-x64-musl-baseline@npm:1.2.2" + conditions: os=linux & cpu=x64 + languageName: node + linkType: hard + "@oven/bun-linux-x64-musl-baseline@npm:1.2.5": version: 1.2.5 resolution: "@oven/bun-linux-x64-musl-baseline@npm:1.2.5" @@ -6650,6 +7150,13 @@ __metadata: languageName: node linkType: hard +"@oven/bun-linux-x64-musl@npm:1.2.2": + version: 1.2.2 + resolution: "@oven/bun-linux-x64-musl@npm:1.2.2" + conditions: os=linux & cpu=x64 + languageName: node + linkType: hard + "@oven/bun-linux-x64-musl@npm:1.2.5": version: 1.2.5 resolution: "@oven/bun-linux-x64-musl@npm:1.2.5" @@ -6657,6 +7164,13 @@ __metadata: languageName: node linkType: hard +"@oven/bun-linux-x64@npm:1.2.2": + version: 1.2.2 + resolution: "@oven/bun-linux-x64@npm:1.2.2" + conditions: os=linux & cpu=x64 + languageName: node + linkType: hard + "@oven/bun-linux-x64@npm:1.2.5": version: 1.2.5 resolution: "@oven/bun-linux-x64@npm:1.2.5" @@ -6664,6 +7178,13 @@ __metadata: languageName: node linkType: hard +"@oven/bun-windows-x64-baseline@npm:1.2.2": + version: 1.2.2 + resolution: "@oven/bun-windows-x64-baseline@npm:1.2.2" + conditions: os=win32 & cpu=x64 + languageName: node + linkType: hard + "@oven/bun-windows-x64-baseline@npm:1.2.5": version: 1.2.5 resolution: "@oven/bun-windows-x64-baseline@npm:1.2.5" @@ -6671,6 +7192,13 @@ __metadata: languageName: node linkType: hard +"@oven/bun-windows-x64@npm:1.2.2": + version: 1.2.2 + resolution: "@oven/bun-windows-x64@npm:1.2.2" + conditions: os=win32 & cpu=x64 + languageName: node + linkType: hard + "@oven/bun-windows-x64@npm:1.2.5": version: 1.2.5 resolution: "@oven/bun-windows-x64@npm:1.2.5" @@ -6902,6 +7430,24 @@ __metadata: languageName: node linkType: hard +"@puppeteer/browsers@npm:2.3.0": + version: 2.3.0 + resolution: "@puppeteer/browsers@npm:2.3.0" + dependencies: + debug: "npm:^4.3.5" + extract-zip: "npm:^2.0.1" + progress: "npm:^2.0.3" + proxy-agent: "npm:^6.4.0" + semver: "npm:^7.6.3" + tar-fs: "npm:^3.0.6" + unbzip2-stream: "npm:^1.4.3" + yargs: "npm:^17.7.2" + bin: + browsers: lib/cjs/main-cli.js + checksum: 10c0/8665a7d5be5e1489855780b7684bf94a55647b54a8391474cbdc1defdb2e4e6642722ef1d20bfabe49d3aed3eec2c8db41d6eabc24440f4a16d071effc5a1049 + languageName: node + linkType: hard + "@repeaterjs/repeater@npm:^3.0.4, @repeaterjs/repeater@npm:^3.0.6": version: 3.0.6 resolution: "@repeaterjs/repeater@npm:3.0.6" @@ -7781,6 +8327,13 @@ __metadata: languageName: node linkType: hard +"@tootallnate/quickjs-emscripten@npm:^0.23.0": + version: 0.23.0 + resolution: "@tootallnate/quickjs-emscripten@npm:0.23.0" + checksum: 10c0/2a939b781826fb5fd3edd0f2ec3b321d259d760464cf20611c9877205aaca3ccc0b7304dea68416baa0d568e82cd86b17d29548d1e5139fa3155a4a86a2b4b49 + languageName: node + linkType: hard + "@tsconfig/node10@npm:^1.0.7": version: 1.0.11 resolution: "@tsconfig/node10@npm:1.0.11" @@ -8117,6 +8670,13 @@ __metadata: languageName: node linkType: hard +"@types/k6@npm:^0.54.2": + version: 0.54.2 + resolution: "@types/k6@npm:0.54.2" + checksum: 10c0/8d44dbd11ac53cf426abed3dd3bba0a35b355b3ad18b9d220ec5295259d6a4ee5bb97d0b2c38ff6896b085a1ad151678498df137c66aafae6b760b746bb84be8 + languageName: node + linkType: hard + "@types/keyv@npm:^3.1.4": version: 3.1.4 resolution: "@types/keyv@npm:3.1.4" @@ -8320,16 +8880,7 @@ __metadata: languageName: node linkType: hard -"@types/ws@npm:^8, @types/ws@npm:^8.0.0, @types/ws@npm:^8.5.12": - version: 8.18.0 - resolution: "@types/ws@npm:8.18.0" - dependencies: - "@types/node": "npm:*" - checksum: 10c0/a56d2e0d1da7411a1f3548ce02b51a50cbe9e23f025677d03df48f87e4a3c72e1342fbf1d12e487d7eafa8dc670c605152b61bbf9165891ec0e9694b0d3ea8d4 - languageName: node - linkType: hard - -"@types/ws@npm:~8.5.10": +"@types/ws@npm:^8, @types/ws@npm:^8.0.0, @types/ws@npm:^8.5.12, @types/ws@npm:~8.5.10": version: 8.5.14 resolution: "@types/ws@npm:8.5.14" dependencies: @@ -8354,6 +8905,15 @@ __metadata: languageName: node linkType: hard +"@types/yauzl@npm:^2.9.1": + version: 2.10.3 + resolution: "@types/yauzl@npm:2.10.3" + dependencies: + "@types/node": "npm:*" + checksum: 10c0/f1b7c1b99fef9f2fe7f1985ef7426d0cebe48cd031f1780fcdc7451eec7e31ac97028f16f50121a59bcf53086a1fc8c856fd5b7d3e00970e43d92ae27d6b43dc + languageName: node + linkType: hard + "@types/yoga-layout@npm:1.9.2": version: 1.9.2 resolution: "@types/yoga-layout@npm:1.9.2" @@ -9480,6 +10040,13 @@ __metadata: languageName: node linkType: hard +"ansi@npm:^0.3.1": + version: 0.3.1 + resolution: "ansi@npm:0.3.1" + checksum: 10c0/ffc90fa753aca5a2f283b890ed6244f0d3597b4cf8d2d3c0cc428019ad3b5fe69533ee71a3cc00e107bb8d4f03987134044b8dfdca88f2cc6fd35443536ffec7 + languageName: node + linkType: hard + "any-promise@npm:^1.0.0": version: 1.3.0 resolution: "any-promise@npm:1.3.0" @@ -9603,6 +10170,21 @@ __metadata: languageName: node linkType: hard +"array.prototype.reduce@npm:^1.0.6": + version: 1.0.7 + resolution: "array.prototype.reduce@npm:1.0.7" + dependencies: + call-bind: "npm:^1.0.7" + define-properties: "npm:^1.2.1" + es-abstract: "npm:^1.23.2" + es-array-method-boxes-properly: "npm:^1.0.0" + es-errors: "npm:^1.3.0" + es-object-atoms: "npm:^1.0.0" + is-string: "npm:^1.0.7" + checksum: 10c0/97aac907d7b15088d5b991bad79de96f95ea0d47a701a034e2dc816e0aabaed2fb401d7fe65ab6fda05eafa58319aa2d1bac404f515e162b81b3b61a51224db2 + languageName: node + linkType: hard + "arraybuffer.prototype.slice@npm:^1.0.4": version: 1.0.4 resolution: "arraybuffer.prototype.slice@npm:1.0.4" @@ -9659,6 +10241,15 @@ __metadata: languageName: node linkType: hard +"ast-types@npm:^0.13.4": + version: 0.13.4 + resolution: "ast-types@npm:0.13.4" + dependencies: + tslib: "npm:^2.0.1" + checksum: 10c0/3a1a409764faa1471601a0ad01b3aa699292991aa9c8a30c7717002cabdf5d98008e7b53ae61f6e058f757fc6ba965e147967a93c13e62692c907d79cfb245f8 + languageName: node + linkType: hard + "ast-types@npm:^0.16.1": version: 0.16.1 resolution: "ast-types@npm:0.16.1" @@ -9774,6 +10365,22 @@ __metadata: languageName: node linkType: hard +"b4a@npm:^1.6.4": + version: 1.6.7 + resolution: "b4a@npm:1.6.7" + checksum: 10c0/ec2f004d1daae04be8c5a1f8aeb7fea213c34025e279db4958eb0b82c1729ee25f7c6e89f92a5f65c8a9cf2d017ce27e3dda912403341d1781bd74528a4849d4 + languageName: node + linkType: hard + +"babar@npm:^0.2.0": + version: 0.2.3 + resolution: "babar@npm:0.2.3" + dependencies: + colors: "npm:~1.4.0" + checksum: 10c0/0ecba1ecc6f88403c355788b61a762bbed9a26072c8d5f09f560021ab06d46526a1b41dd4eb165db3cae48c51bf4a3149d496103ea3127513f71144b8b9c1446 + languageName: node + linkType: hard + "babel-jest@npm:^29.7.0": version: 29.7.0 resolution: "babel-jest@npm:29.7.0" @@ -9903,6 +10510,57 @@ __metadata: languageName: node linkType: hard +"bare-events@npm:^2.0.0, bare-events@npm:^2.2.0": + version: 2.5.4 + resolution: "bare-events@npm:2.5.4" + checksum: 10c0/877a9cea73d545e2588cdbd6fd01653e27dac48ad6b44985cdbae73e1f57f292d4ba52e25d1fba53674c1053c463d159f3d5c7bc36a2e6e192e389b499ddd627 + languageName: node + linkType: hard + +"bare-fs@npm:^4.0.1": + version: 4.0.1 + resolution: "bare-fs@npm:4.0.1" + dependencies: + bare-events: "npm:^2.0.0" + bare-path: "npm:^3.0.0" + bare-stream: "npm:^2.0.0" + checksum: 10c0/db2f4e2646faa011e322cbdc4615fe0cac865a03c2f76d7c686eccf96b0b5eea2bc71dfa37e8cfb14f4f61f8cd3ca95ff7b745d37c55fca319e40ec351d4ae0c + languageName: node + linkType: hard + +"bare-os@npm:^3.0.1": + version: 3.4.0 + resolution: "bare-os@npm:3.4.0" + checksum: 10c0/2d1a4467ef8aff0a13d738e549aac30bbecf7631721f7099de78d6f8fc0ced9334ab391e489de28d69809f788f64081ac25108303a9a9e122f9bf87a8d589025 + languageName: node + linkType: hard + +"bare-path@npm:^3.0.0": + version: 3.0.0 + resolution: "bare-path@npm:3.0.0" + dependencies: + bare-os: "npm:^3.0.1" + checksum: 10c0/56a3ca82a9f808f4976cb1188640ac206546ce0ddff582afafc7bd2a6a5b31c3bd16422653aec656eeada2830cfbaa433c6cbf6d6b4d9eba033d5e06d60d9a68 + languageName: node + linkType: hard + +"bare-stream@npm:^2.0.0": + version: 2.6.5 + resolution: "bare-stream@npm:2.6.5" + dependencies: + streamx: "npm:^2.21.0" + peerDependencies: + bare-buffer: "*" + bare-events: "*" + peerDependenciesMeta: + bare-buffer: + optional: true + bare-events: + optional: true + checksum: 10c0/1242286f8f3147e9fd353cdaa9cf53226a807ac0dde8177c13f1463aa4cd1f88e07407c883a1b322b901e9af2d1cd30aacd873529031132c384622972e0419df + languageName: node + linkType: hard + "base64-js@npm:^1.3.1": version: 1.5.1 resolution: "base64-js@npm:1.5.1" @@ -9910,6 +10568,13 @@ __metadata: languageName: node linkType: hard +"basic-ftp@npm:^5.0.2": + version: 5.0.5 + resolution: "basic-ftp@npm:5.0.5" + checksum: 10c0/be983a3997749856da87b839ffce6b8ed6c7dbf91ea991d5c980d8add275f9f2926c19f80217ac3e7f353815be879371d636407ca72b038cea8cab30e53928a6 + languageName: node + linkType: hard + "bcrypt-pbkdf@npm:^1.0.2": version: 1.0.2 resolution: "bcrypt-pbkdf@npm:1.0.2" @@ -9953,6 +10618,15 @@ __metadata: languageName: node linkType: hard +"blessed@npm:^0.1.81": + version: 0.1.81 + resolution: "blessed@npm:0.1.81" + bin: + blessed: ./bin/tput.js + checksum: 10c0/19515ff7899e8af0dd6c080e30e849833ee9518508cc4eabae5a2ea5f17440537a2526169081f20c79d73fbbeca26cc798cc4b037ec250f91f4952b3a75a2143 + languageName: node + linkType: hard + "body-parser@npm:1.20.3": version: 1.20.3 resolution: "body-parser@npm:1.20.3" @@ -10057,6 +10731,13 @@ __metadata: languageName: node linkType: hard +"buffer-crc32@npm:~0.2.3": + version: 0.2.13 + resolution: "buffer-crc32@npm:0.2.13" + checksum: 10c0/cb0a8ddf5cf4f766466db63279e47761eb825693eeba6a5a95ee4ec8cb8f81ede70aa7f9d8aeec083e781d47154290eb5d4d26b3f7a465ec57fb9e7d59c47150 + languageName: node + linkType: hard + "buffer-equal-constant-time@npm:1.0.1": version: 1.0.1 resolution: "buffer-equal-constant-time@npm:1.0.1" @@ -10071,7 +10752,7 @@ __metadata: languageName: node linkType: hard -"buffer@npm:^5.5.0": +"buffer@npm:^5.2.1, buffer@npm:^5.5.0": version: 5.7.1 resolution: "buffer@npm:5.7.1" dependencies: @@ -10098,7 +10779,7 @@ __metadata: languageName: node linkType: hard -"bun@npm:1.2.5, bun@npm:^1.1.34": +"bun@npm:1.2.5": version: 1.2.5 resolution: "bun@npm:1.2.5" dependencies: @@ -10144,6 +10825,61 @@ __metadata: languageName: node linkType: hard +"bun@npm:^1.1.34": + version: 1.2.2 + resolution: "bun@npm:1.2.2" + dependencies: + "@oven/bun-darwin-aarch64": "npm:1.2.2" + "@oven/bun-darwin-x64": "npm:1.2.2" + "@oven/bun-darwin-x64-baseline": "npm:1.2.2" + "@oven/bun-linux-aarch64": "npm:1.2.2" + "@oven/bun-linux-aarch64-musl": "npm:1.2.2" + "@oven/bun-linux-x64": "npm:1.2.2" + "@oven/bun-linux-x64-baseline": "npm:1.2.2" + "@oven/bun-linux-x64-musl": "npm:1.2.2" + "@oven/bun-linux-x64-musl-baseline": "npm:1.2.2" + "@oven/bun-windows-x64": "npm:1.2.2" + "@oven/bun-windows-x64-baseline": "npm:1.2.2" + dependenciesMeta: + "@oven/bun-darwin-aarch64": + optional: true + "@oven/bun-darwin-x64": + optional: true + "@oven/bun-darwin-x64-baseline": + optional: true + "@oven/bun-linux-aarch64": + optional: true + "@oven/bun-linux-aarch64-musl": + optional: true + "@oven/bun-linux-x64": + optional: true + "@oven/bun-linux-x64-baseline": + optional: true + "@oven/bun-linux-x64-musl": + optional: true + "@oven/bun-linux-x64-musl-baseline": + optional: true + "@oven/bun-windows-x64": + optional: true + "@oven/bun-windows-x64-baseline": + optional: true + bin: + bun: bin/bun.exe + bunx: bin/bun.exe + checksum: 10c0/e24f39912a448d95a7c8c6f70896685b0db3f18b10e24d06b28b8419958bf1e787c25c09e87899a044f99bd9badaf8ffa1792b84bc4d0890a3d8db51689de519 + conditions: (os=darwin | os=linux | os=win32) & (cpu=arm64 | cpu=x64 | cpu=aarch64) + languageName: node + linkType: hard + +"bundle-name@npm:^4.1.0": + version: 4.1.0 + resolution: "bundle-name@npm:4.1.0" + dependencies: + run-applescript: "npm:^7.0.0" + checksum: 10c0/8e575981e79c2bcf14d8b1c027a3775c095d362d1382312f444a7c861b0e21513c0bd8db5bd2b16e50ba0709fa622d4eab6b53192d222120305e68359daece29 + languageName: node + linkType: hard + "busboy@npm:^1.0.0, busboy@npm:^1.6.0": version: 1.6.0 resolution: "busboy@npm:1.6.0" @@ -10306,6 +11042,17 @@ __metadata: languageName: node linkType: hard +"canvas@npm:^3.1.0": + version: 3.1.0 + resolution: "canvas@npm:3.1.0" + dependencies: + node-addon-api: "npm:^7.0.0" + node-gyp: "npm:latest" + prebuild-install: "npm:^7.1.1" + checksum: 10c0/28da5184c1d7e97049ba6a24f10690b9ed4b303bbd25517d95c892fa3a6331417791657a3a7467068e40af0dda2dcc9120d062f7426a3d796131e69a30e3cbf1 + languageName: node + linkType: hard + "capital-case@npm:^1.0.4": version: 1.0.4 resolution: "capital-case@npm:1.0.4" @@ -10391,6 +11138,22 @@ __metadata: languageName: node linkType: hard +"chart.js@npm:^4.4.7": + version: 4.4.7 + resolution: "chart.js@npm:4.4.7" + dependencies: + "@kurkle/color": "npm:^0.3.0" + checksum: 10c0/9db499993c561f11184112003956ba96cf00513e025f58846be36e75ebddc6cbab2f93626c3734c305bc801e9362a9ef193b9591e679c59903b2ecb48cfcb317 + languageName: node + linkType: hard + +"chartjs-plugin-trendline@npm:^2.1.6": + version: 2.1.6 + resolution: "chartjs-plugin-trendline@npm:2.1.6" + checksum: 10c0/26a8a6cb7d28a64a282afb2a1eed019f58086c7b789b9ba4de014a641086dfd9dd5d41b54fbfd003eae7741294683166ab214b0c62d5d82d96656ab6e32b03c2 + languageName: node + linkType: hard + "check-error@npm:^2.1.1": version: 2.1.1 resolution: "check-error@npm:2.1.1" @@ -10428,6 +11191,19 @@ __metadata: languageName: node linkType: hard +"chromium-bidi@npm:0.6.3": + version: 0.6.3 + resolution: "chromium-bidi@npm:0.6.3" + dependencies: + mitt: "npm:3.0.1" + urlpattern-polyfill: "npm:10.0.0" + zod: "npm:3.23.8" + peerDependencies: + devtools-protocol: "*" + checksum: 10c0/226829bfc3c9de54803cfbce5cb3075f729aa2f862b22e2e91c75d35425b537f85c49d36793d69bf4778115c4bd31ab3e9eaee1cbc28a1506a6d4b1752e34b9a + languageName: node + linkType: hard + "ci-info@npm:^2.0.0": version: 2.0.0 resolution: "ci-info@npm:2.0.0" @@ -10646,6 +11422,13 @@ __metadata: languageName: node linkType: hard +"colors@npm:~1.4.0": + version: 1.4.0 + resolution: "colors@npm:1.4.0" + checksum: 10c0/9af357c019da3c5a098a301cf64e3799d27549d8f185d86f79af23069e4f4303110d115da98483519331f6fb71c8568d5688fa1c6523600044fd4a54e97c4efb + languageName: node + linkType: hard + "colorspace@npm:1.1.x": version: 1.1.4 resolution: "colorspace@npm:1.1.4" @@ -10860,6 +11643,23 @@ __metadata: languageName: node linkType: hard +"cosmiconfig@npm:^9.0.0": + version: 9.0.0 + resolution: "cosmiconfig@npm:9.0.0" + dependencies: + env-paths: "npm:^2.2.1" + import-fresh: "npm:^3.3.0" + js-yaml: "npm:^4.1.0" + parse-json: "npm:^5.2.0" + peerDependencies: + typescript: ">=4.9.5" + peerDependenciesMeta: + typescript: + optional: true + checksum: 10c0/1c1703be4f02a250b1d6ca3267e408ce16abfe8364193891afc94c2d5c060b69611fdc8d97af74b7e6d5d1aac0ab2fb94d6b079573146bc2d756c2484ce5f0ee + languageName: node + linkType: hard + "cpu-features@npm:~0.0.10": version: 0.0.10 resolution: "cpu-features@npm:0.0.10" @@ -10941,6 +11741,13 @@ __metadata: languageName: node linkType: hard +"data-uri-to-buffer@npm:^6.0.2": + version: 6.0.2 + resolution: "data-uri-to-buffer@npm:6.0.2" + checksum: 10c0/f76922bf895b3d7d443059ff278c9cc5efc89d70b8b80cd9de0aa79b3adc6d7a17948eefb8692e30398c43635f70ece1673d6085cc9eba2878dbc6c6da5292ac + languageName: node + linkType: hard + "data-view-buffer@npm:^1.0.2": version: 1.0.2 resolution: "data-view-buffer@npm:1.0.2" @@ -11004,7 +11811,7 @@ __metadata: languageName: node linkType: hard -"debug@npm:4, debug@npm:^4.1.0, debug@npm:^4.1.1, debug@npm:^4.3.1, debug@npm:^4.3.2, debug@npm:^4.3.3, debug@npm:^4.3.4, debug@npm:^4.3.5, debug@npm:^4.4.0": +"debug@npm:4, debug@npm:^4.1.0, debug@npm:^4.1.1, debug@npm:^4.3.1, debug@npm:^4.3.2, debug@npm:^4.3.3, debug@npm:^4.3.4, debug@npm:^4.3.5, debug@npm:^4.3.6, debug@npm:^4.4.0": version: 4.4.0 resolution: "debug@npm:4.4.0" dependencies: @@ -11103,6 +11910,13 @@ __metadata: languageName: node linkType: hard +"deep-extend@npm:^0.6.0": + version: 0.6.0 + resolution: "deep-extend@npm:0.6.0" + checksum: 10c0/1c6b0abcdb901e13a44c7d699116d3d4279fdb261983122a3783e7273844d5f2537dc2e1c454a23fcf645917f93fbf8d07101c1d03c015a87faa662755212566 + languageName: node + linkType: hard + "deep-is@npm:^0.1.3": version: 0.1.4 resolution: "deep-is@npm:0.1.4" @@ -11117,6 +11931,23 @@ __metadata: languageName: node linkType: hard +"default-browser-id@npm:^5.0.0": + version: 5.0.0 + resolution: "default-browser-id@npm:5.0.0" + checksum: 10c0/957fb886502594c8e645e812dfe93dba30ed82e8460d20ce39c53c5b0f3e2afb6ceaec2249083b90bdfbb4cb0f34e1f73fde3d68cac00becdbcfd894156b5ead + languageName: node + linkType: hard + +"default-browser@npm:^5.2.1": + version: 5.2.1 + resolution: "default-browser@npm:5.2.1" + dependencies: + bundle-name: "npm:^4.1.0" + default-browser-id: "npm:^5.0.0" + checksum: 10c0/73f17dc3c58026c55bb5538749597db31f9561c0193cd98604144b704a981c95a466f8ecc3c2db63d8bfd04fb0d426904834cfc91ae510c6aeb97e13c5167c4d + languageName: node + linkType: hard + "defaults@npm:^1.0.3": version: 1.0.4 resolution: "defaults@npm:1.0.4" @@ -11144,6 +11975,13 @@ __metadata: languageName: node linkType: hard +"define-lazy-prop@npm:^3.0.0": + version: 3.0.0 + resolution: "define-lazy-prop@npm:3.0.0" + checksum: 10c0/5ab0b2bf3fa58b3a443140bbd4cd3db1f91b985cc8a246d330b9ac3fc0b6a325a6d82bddc0b055123d745b3f9931afeea74a5ec545439a1630b9c8512b0eeb49 + languageName: node + linkType: hard + "define-properties@npm:^1.2.1": version: 1.2.1 resolution: "define-properties@npm:1.2.1" @@ -11162,6 +12000,17 @@ __metadata: languageName: node linkType: hard +"degenerator@npm:^5.0.0": + version: 5.0.1 + resolution: "degenerator@npm:5.0.1" + dependencies: + ast-types: "npm:^0.13.4" + escodegen: "npm:^2.1.0" + esprima: "npm:^4.0.1" + checksum: 10c0/e48d8a651edeb512a648711a09afec269aac6de97d442a4bb9cf121a66877e0eec11b9727100a10252335c0666ae1c84a8bc1e3a3f47788742c975064d2c7b1c + languageName: node + linkType: hard + "delayed-stream@npm:~1.0.0": version: 1.0.0 resolution: "delayed-stream@npm:1.0.0" @@ -11246,6 +12095,13 @@ __metadata: languageName: node linkType: hard +"devtools-protocol@npm:0.0.1312386": + version: 0.0.1312386 + resolution: "devtools-protocol@npm:0.0.1312386" + checksum: 10c0/1073b2edcee76db094fdce97fe8869f3469866513e864379e04311a429b439ba51e54809fdffb09b67bf0c37b5ac5bfd2b0536ae217b7ea2cbe2e571fbed7e8e + languageName: node + linkType: hard + "dezalgo@npm:^1.0.4": version: 1.0.4 resolution: "dezalgo@npm:1.0.4" @@ -11511,7 +12367,7 @@ __metadata: languageName: node linkType: hard -"env-paths@npm:^2.2.0": +"env-paths@npm:^2.2.0, env-paths@npm:^2.2.1": version: 2.2.1 resolution: "env-paths@npm:2.2.1" checksum: 10c0/285325677bf00e30845e330eec32894f5105529db97496ee3f598478e50f008c5352a41a30e5e72ec9de8a542b5a570b85699cd63bd2bc646dbcb9f311d83bc4 @@ -11593,6 +12449,13 @@ __metadata: languageName: node linkType: hard +"es-array-method-boxes-properly@npm:^1.0.0": + version: 1.0.0 + resolution: "es-array-method-boxes-properly@npm:1.0.0" + checksum: 10c0/4b7617d3fbd460d6f051f684ceca6cf7e88e6724671d9480388d3ecdd72119ddaa46ca31f2c69c5426a82e4b3091c1e81867c71dcdc453565cd90005ff2c382d + languageName: node + linkType: hard + "es-define-property@npm:^1.0.0, es-define-property@npm:^1.0.1": version: 1.0.1 resolution: "es-define-property@npm:1.0.1" @@ -11786,6 +12649,24 @@ __metadata: languageName: node linkType: hard +"escodegen@npm:^2.1.0": + version: 2.1.0 + resolution: "escodegen@npm:2.1.0" + dependencies: + esprima: "npm:^4.0.1" + estraverse: "npm:^5.2.0" + esutils: "npm:^2.0.2" + source-map: "npm:~0.6.1" + dependenciesMeta: + source-map: + optional: true + bin: + escodegen: bin/escodegen.js + esgenerate: bin/esgenerate.js + checksum: 10c0/e1450a1f75f67d35c061bf0d60888b15f62ab63aef9df1901cffc81cffbbb9e8b3de237c5502cf8613a017c1df3a3003881307c78835a1ab54d8c8d2206e01d3 + languageName: node + linkType: hard + "eslint-compat-utils@npm:^0.5.1": version: 0.5.1 resolution: "eslint-compat-utils@npm:0.5.1" @@ -11965,7 +12846,7 @@ __metadata: languageName: node linkType: hard -"esprima@npm:^4.0.0, esprima@npm:~4.0.0": +"esprima@npm:^4.0.0, esprima@npm:^4.0.1, esprima@npm:~4.0.0": version: 4.0.1 resolution: "esprima@npm:4.0.1" bin: @@ -12098,6 +12979,13 @@ __metadata: languageName: node linkType: hard +"expand-template@npm:^2.0.3": + version: 2.0.3 + resolution: "expand-template@npm:2.0.3" + checksum: 10c0/1c9e7afe9acadf9d373301d27f6a47b34e89b3391b1ef38b7471d381812537ef2457e620ae7f819d2642ce9c43b189b3583813ec395e2938319abe356a9b2f51 + languageName: node + linkType: hard + "expect-type@npm:^1.1.0": version: 1.2.0 resolution: "expect-type@npm:1.2.0" @@ -12233,8 +13121,25 @@ __metadata: version: 13.0.0 resolution: "extract-files@npm:13.0.0" dependencies: - is-plain-obj: "npm:^4.1.0" - checksum: 10c0/ee1e6e37f24fcb7de5019c0dc054f3e075664f63b5a9bd38c9ba1a32481ed1e77fd237adf826a7f6c7a1db406d9db4428f8ae4395fb2dfb602235b15f8f4bc3e + is-plain-obj: "npm:^4.1.0" + checksum: 10c0/ee1e6e37f24fcb7de5019c0dc054f3e075664f63b5a9bd38c9ba1a32481ed1e77fd237adf826a7f6c7a1db406d9db4428f8ae4395fb2dfb602235b15f8f4bc3e + languageName: node + linkType: hard + +"extract-zip@npm:^2.0.1": + version: 2.0.1 + resolution: "extract-zip@npm:2.0.1" + dependencies: + "@types/yauzl": "npm:^2.9.1" + debug: "npm:^4.1.1" + get-stream: "npm:^5.1.0" + yauzl: "npm:^2.10.0" + dependenciesMeta: + "@types/yauzl": + optional: true + bin: + extract-zip: cli.js + checksum: 10c0/9afbd46854aa15a857ae0341a63a92743a7b89c8779102c3b4ffc207516b2019337353962309f85c66ee3d9092202a83cdc26dbf449a11981272038443974aee languageName: node linkType: hard @@ -12259,6 +13164,13 @@ __metadata: languageName: node linkType: hard +"fast-fifo@npm:^1.2.0, fast-fifo@npm:^1.3.2": + version: 1.3.2 + resolution: "fast-fifo@npm:1.3.2" + checksum: 10c0/d53f6f786875e8b0529f784b59b4b05d4b5c31c651710496440006a398389a579c8dbcd2081311478b5bf77f4b0b21de69109c5a4eabea9d8e8783d1eb864e4c + languageName: node + linkType: hard + "fast-glob@npm:3.3.3, fast-glob@npm:^3.2.2, fast-glob@npm:^3.2.9, fast-glob@npm:^3.3.2": version: 3.3.3 resolution: "fast-glob@npm:3.3.3" @@ -12415,6 +13327,15 @@ __metadata: languageName: node linkType: hard +"fd-slicer@npm:~1.1.0": + version: 1.1.0 + resolution: "fd-slicer@npm:1.1.0" + dependencies: + pend: "npm:~1.2.0" + checksum: 10c0/304dd70270298e3ffe3bcc05e6f7ade2511acc278bc52d025f8918b48b6aa3b77f10361bddfadfe2a28163f7af7adbdce96f4d22c31b2f648ba2901f0c5fc20e + languageName: node + linkType: hard + "fdir@npm:^6.2.0": version: 6.4.3 resolution: "fdir@npm:6.4.3" @@ -12710,6 +13631,17 @@ __metadata: languageName: node linkType: hard +"fs-extra@npm:^4.0.2": + version: 4.0.3 + resolution: "fs-extra@npm:4.0.3" + dependencies: + graceful-fs: "npm:^4.1.2" + jsonfile: "npm:^4.0.0" + universalify: "npm:^0.1.0" + checksum: 10c0/b34344de77adaccb294e6dc116e8b247ae0a4da45b79749814893549e6f15e3baace2998db06a966a9f8d5a39b6b2d8e51543bd0a565a8927c37d6373dfd20b9 + languageName: node + linkType: hard + "fs-extra@npm:^7.0.1": version: 7.0.1 resolution: "fs-extra@npm:7.0.1" @@ -12969,6 +13901,17 @@ __metadata: languageName: node linkType: hard +"get-uri@npm:^6.0.1": + version: 6.0.4 + resolution: "get-uri@npm:6.0.4" + dependencies: + basic-ftp: "npm:^5.0.2" + data-uri-to-buffer: "npm:^6.0.2" + debug: "npm:^4.3.4" + checksum: 10c0/07c87abe1f97a4545fae329a37a45e276ec57e6ad48dad2a97780f87c96b00a82c2043ab49e1a991f99bb5cff8f8ed975e44e4f8b3c9600f35493a97f123499f + languageName: node + linkType: hard + "git-up@npm:^7.0.0": version: 7.0.0 resolution: "git-up@npm:7.0.0" @@ -12988,6 +13931,13 @@ __metadata: languageName: node linkType: hard +"github-from-package@npm:0.0.0": + version: 0.0.0 + resolution: "github-from-package@npm:0.0.0" + checksum: 10c0/737ee3f52d0a27e26332cde85b533c21fcdc0b09fb716c3f8e522cfaa9c600d4a631dec9fcde179ec9d47cca89017b7848ed4d6ae6b6b78f936c06825b1fcc12 + languageName: node + linkType: hard + "glob-parent@npm:^5.1.2": version: 5.1.2 resolution: "glob-parent@npm:5.1.2" @@ -13470,7 +14420,7 @@ __metadata: languageName: node linkType: hard -"http-proxy-agent@npm:^7.0.0": +"http-proxy-agent@npm:^7.0.0, http-proxy-agent@npm:^7.0.1": version: 7.0.2 resolution: "http-proxy-agent@npm:7.0.2" dependencies: @@ -13500,7 +14450,7 @@ __metadata: languageName: node linkType: hard -"https-proxy-agent@npm:^7.0.0, https-proxy-agent@npm:^7.0.1": +"https-proxy-agent@npm:^7.0.0, https-proxy-agent@npm:^7.0.1, https-proxy-agent@npm:^7.0.6": version: 7.0.6 resolution: "https-proxy-agent@npm:7.0.6" dependencies: @@ -13581,7 +14531,7 @@ __metadata: languageName: node linkType: hard -"import-fresh@npm:^3.2.1": +"import-fresh@npm:^3.2.1, import-fresh@npm:^3.3.0": version: 3.3.1 resolution: "import-fresh@npm:3.3.1" dependencies: @@ -13646,6 +14596,13 @@ __metadata: languageName: node linkType: hard +"ini@npm:~1.3.0": + version: 1.3.8 + resolution: "ini@npm:1.3.8" + checksum: 10c0/ec93838d2328b619532e4f1ff05df7909760b6f66d9c9e2ded11e5c1897d6f2f9980c54dd638f88654b00919ce31e827040631eab0a3969e4d1abefa0719516a + languageName: node + linkType: hard + "ink-text-input@npm:^4.0.3": version: 4.0.3 resolution: "ink-text-input@npm:4.0.3" @@ -13895,6 +14852,15 @@ __metadata: languageName: node linkType: hard +"is-docker@npm:^3.0.0": + version: 3.0.0 + resolution: "is-docker@npm:3.0.0" + bin: + is-docker: cli.js + checksum: 10c0/d2c4f8e6d3e34df75a5defd44991b6068afad4835bb783b902fa12d13ebdb8f41b2a199dcb0b5ed2cb78bfee9e4c0bbdb69c2d9646f4106464674d3e697a5856 + languageName: node + linkType: hard + "is-extglob@npm:^2.1.1": version: 2.1.1 resolution: "is-extglob@npm:2.1.1" @@ -13946,6 +14912,17 @@ __metadata: languageName: node linkType: hard +"is-inside-container@npm:^1.0.0": + version: 1.0.0 + resolution: "is-inside-container@npm:1.0.0" + dependencies: + is-docker: "npm:^3.0.0" + bin: + is-inside-container: cli.js + checksum: 10c0/a8efb0e84f6197e6ff5c64c52890fa9acb49b7b74fed4da7c95383965da6f0fa592b4dbd5e38a79f87fc108196937acdbcd758fcefc9b140e479b39ce1fcd1cd + languageName: node + linkType: hard + "is-lambda@npm:^1.0.1": version: 1.0.1 resolution: "is-lambda@npm:1.0.1" @@ -14146,6 +15123,15 @@ __metadata: languageName: node linkType: hard +"is-wsl@npm:^3.1.0": + version: 3.1.0 + resolution: "is-wsl@npm:3.1.0" + dependencies: + is-inside-container: "npm:^1.0.0" + checksum: 10c0/d3317c11995690a32c362100225e22ba793678fe8732660c6de511ae71a0ff05b06980cf21f98a6bf40d7be0e9e9506f859abe00a1118287d63e53d0a3d06947 + languageName: node + linkType: hard + "isarray@npm:^2.0.5": version: 2.0.5 resolution: "isarray@npm:2.0.5" @@ -15606,6 +16592,31 @@ __metadata: languageName: node linkType: hard +"memlab@npm:^1.1.56": + version: 1.1.56 + resolution: "memlab@npm:1.1.56" + dependencies: + "@memlab/api": "npm:^1.0.38" + "@memlab/cli": "npm:^1.0.41" + "@memlab/core": "npm:^1.1.39" + "@memlab/e2e": "npm:^1.0.39" + "@memlab/heap-analysis": "npm:^1.0.36" + ansi: "npm:^0.3.1" + babar: "npm:^0.2.0" + chalk: "npm:^4.0.0" + fs-extra: "npm:^4.0.2" + minimist: "npm:^1.2.8" + puppeteer: "npm:^22.12.1" + puppeteer-core: "npm:^22.12.1" + string-width: "npm:^4.2.0" + util.promisify: "npm:^1.1.1" + xvfb: "npm:^0.4.0" + bin: + memlab: bin/memlab + checksum: 10c0/7669af83b641c01c64df65fa79e27303ba40427b18fa3cbe63efa8129c05cfddbdae06486b1ffb6077ba06bd60c2d2c45bb74070715cf5e9b88b39c8b4590616 + languageName: node + linkType: hard + "merge-descriptors@npm:1.0.3": version: 1.0.3 resolution: "merge-descriptors@npm:1.0.3" @@ -15800,7 +16811,7 @@ __metadata: languageName: node linkType: hard -"minimist@npm:^1.2.0, minimist@npm:^1.2.6, minimist@npm:^1.2.8": +"minimist@npm:^1.2.0, minimist@npm:^1.2.3, minimist@npm:^1.2.6, minimist@npm:^1.2.8": version: 1.2.8 resolution: "minimist@npm:1.2.8" checksum: 10c0/19d3fcdca050087b84c2029841a093691a91259a47def2f18222f41e7645a0b7c44ef4b40e88a1e58a40c84d2ef0ee6047c55594d298146d0eb3f6b737c20ce6 @@ -15925,7 +16936,14 @@ __metadata: languageName: node linkType: hard -"mkdirp-classic@npm:^0.5.2": +"mitt@npm:3.0.1": + version: 3.0.1 + resolution: "mitt@npm:3.0.1" + checksum: 10c0/3ab4fdecf3be8c5255536faa07064d05caa3dd332bd318ff02e04621f7b3069ca1de9106cfe8e7ced675abfc2bec2ce4c4ef321c4a1bb1fb29df8ae090741913 + languageName: node + linkType: hard + +"mkdirp-classic@npm:^0.5.2, mkdirp-classic@npm:^0.5.3": version: 0.5.3 resolution: "mkdirp-classic@npm:0.5.3" checksum: 10c0/95371d831d196960ddc3833cc6907e6b8f67ac5501a6582f47dfae5eb0f092e9f8ce88e0d83afcae95d6e2b61a01741ba03714eeafb6f7a6e9dcc158ac85b168 @@ -16045,6 +17063,15 @@ __metadata: languageName: node linkType: hard +"nan@npm:^2.13.2": + version: 2.22.0 + resolution: "nan@npm:2.22.0" + dependencies: + node-gyp: "npm:latest" + checksum: 10c0/d5d31aefdb218deba308d44867c5f432b4d3aabeb57c70a2b236d62652e9fee7044e5d5afd380d9fef022fe7ebb2f2d6c85ca3cbcac5031aaca3592c844526bb + languageName: node + linkType: hard + "nan@npm:^2.19.0, nan@npm:^2.20.0": version: 2.22.2 resolution: "nan@npm:2.22.2" @@ -16063,6 +17090,13 @@ __metadata: languageName: node linkType: hard +"napi-build-utils@npm:^2.0.0": + version: 2.0.0 + resolution: "napi-build-utils@npm:2.0.0" + checksum: 10c0/5833aaeb5cc5c173da47a102efa4680a95842c13e0d9cc70428bd3ee8d96bb2172f8860d2811799b5daa5cbeda779933601492a2028a6a5351c6d0fcf6de83db + languageName: node + linkType: hard + "natural-compare@npm:^1.4.0": version: 1.4.0 resolution: "natural-compare@npm:1.4.0" @@ -16098,6 +17132,13 @@ __metadata: languageName: node linkType: hard +"netmask@npm:^2.0.2": + version: 2.0.2 + resolution: "netmask@npm:2.0.2" + checksum: 10c0/cafd28388e698e1138ace947929f842944d0f1c0b87d3fa2601a61b38dc89397d33c0ce2c8e7b99e968584b91d15f6810b91bef3f3826adf71b1833b61d4bf4f + languageName: node + linkType: hard + "no-case@npm:^3.0.4": version: 3.0.4 resolution: "no-case@npm:3.0.4" @@ -16108,6 +17149,15 @@ __metadata: languageName: node linkType: hard +"node-abi@npm:^3.3.0": + version: 3.74.0 + resolution: "node-abi@npm:3.74.0" + dependencies: + semver: "npm:^7.3.5" + checksum: 10c0/a6c83c448d5e8b591f749a0157c9ec02f653021cdf3415c1a44fcb5fc8afc124acad186bc1ec76cb4db2485cc2dcdda187aacd382c54b6e3093ffc0389603643 + languageName: node + linkType: hard + "node-abort-controller@npm:^3.0.1, node-abort-controller@npm:^3.1.1": version: 3.1.1 resolution: "node-abort-controller@npm:3.1.1" @@ -16295,6 +17345,21 @@ __metadata: languageName: node linkType: hard +"object.getownpropertydescriptors@npm:^2.1.8": + version: 2.1.8 + resolution: "object.getownpropertydescriptors@npm:2.1.8" + dependencies: + array.prototype.reduce: "npm:^1.0.6" + call-bind: "npm:^1.0.7" + define-properties: "npm:^1.2.1" + es-abstract: "npm:^1.23.2" + es-object-atoms: "npm:^1.0.0" + gopd: "npm:^1.0.1" + safe-array-concat: "npm:^1.1.2" + checksum: 10c0/553e9562fd86637c9c169df23a56f1d810d8c9b580a6d4be11552c009f32469310c9347f3d10325abf0cd9cfe4afc521a1e903fbd24148ae7ec860e1e7c75cf3 + languageName: node + linkType: hard + "object.groupby@npm:^1.0.3": version: 1.0.3 resolution: "object.groupby@npm:1.0.3" @@ -16368,6 +17433,18 @@ __metadata: languageName: node linkType: hard +"open@npm:10.1.0": + version: 10.1.0 + resolution: "open@npm:10.1.0" + dependencies: + default-browser: "npm:^5.2.1" + define-lazy-prop: "npm:^3.0.0" + is-inside-container: "npm:^1.0.0" + is-wsl: "npm:^3.1.0" + checksum: 10c0/c86d0b94503d5f735f674158d5c5d339c25ec2927562f00ee74590727292ed23e1b8d9336cb41ffa7e1fa4d3641d29b199b4ea37c78cb557d72b511743e90ebb + languageName: node + linkType: hard + "openapi-types@npm:^12.1.0": version: 12.1.3 resolution: "openapi-types@npm:12.1.3" @@ -16512,6 +17589,32 @@ __metadata: languageName: node linkType: hard +"pac-proxy-agent@npm:^7.1.0": + version: 7.1.0 + resolution: "pac-proxy-agent@npm:7.1.0" + dependencies: + "@tootallnate/quickjs-emscripten": "npm:^0.23.0" + agent-base: "npm:^7.1.2" + debug: "npm:^4.3.4" + get-uri: "npm:^6.0.1" + http-proxy-agent: "npm:^7.0.0" + https-proxy-agent: "npm:^7.0.6" + pac-resolver: "npm:^7.0.1" + socks-proxy-agent: "npm:^8.0.5" + checksum: 10c0/072528e3e7a0bb1187d5c09687a112ae230f6fa0d974e7460eaa0c1406666930ed53ffadfbfadfe8e1c7a8cc8d6ae26a4db96e27723d40a918c8454f0f1a012a + languageName: node + linkType: hard + +"pac-resolver@npm:^7.0.1": + version: 7.0.1 + resolution: "pac-resolver@npm:7.0.1" + dependencies: + degenerator: "npm:^5.0.0" + netmask: "npm:^2.0.2" + checksum: 10c0/5f3edd1dd10fded31e7d1f95776442c3ee51aa098c28b74ede4927d9677ebe7cebb2636750c24e945f5b84445e41ae39093d3a1014a994e5ceb9f0b1b88ebff5 + languageName: node + linkType: hard + "package-json-from-dist@npm:^1.0.0": version: 1.0.1 resolution: "package-json-from-dist@npm:1.0.1" @@ -16738,6 +17841,13 @@ __metadata: languageName: node linkType: hard +"pend@npm:~1.2.0": + version: 1.2.0 + resolution: "pend@npm:1.2.0" + checksum: 10c0/8a87e63f7a4afcfb0f9f77b39bb92374afc723418b9cb716ee4257689224171002e07768eeade4ecd0e86f1fa3d8f022994219fb45634f2dbd78c6803e452458 + languageName: node + linkType: hard + "picocolors@npm:^1.0.0, picocolors@npm:^1.0.1, picocolors@npm:^1.1.0, picocolors@npm:^1.1.1": version: 1.1.1 resolution: "picocolors@npm:1.1.1" @@ -16923,6 +18033,28 @@ __metadata: languageName: node linkType: hard +"prebuild-install@npm:^7.1.1": + version: 7.1.3 + resolution: "prebuild-install@npm:7.1.3" + dependencies: + detect-libc: "npm:^2.0.0" + expand-template: "npm:^2.0.3" + github-from-package: "npm:0.0.0" + minimist: "npm:^1.2.3" + mkdirp-classic: "npm:^0.5.3" + napi-build-utils: "npm:^2.0.0" + node-abi: "npm:^3.3.0" + pump: "npm:^3.0.0" + rc: "npm:^1.2.7" + simple-get: "npm:^4.0.0" + tar-fs: "npm:^2.0.0" + tunnel-agent: "npm:^0.6.0" + bin: + prebuild-install: bin.js + checksum: 10c0/25919a42b52734606a4036ab492d37cfe8b601273d8dfb1fa3c84e141a0a475e7bad3ab848c741d2f810cef892fcf6059b8c7fe5b29f98d30e0c29ad009bedff + languageName: node + linkType: hard + "prelude-ls@npm:^1.2.1": version: 1.2.1 resolution: "prelude-ls@npm:1.2.1" @@ -17008,6 +18140,13 @@ __metadata: languageName: node linkType: hard +"progress@npm:^2.0.3": + version: 2.0.3 + resolution: "progress@npm:2.0.3" + checksum: 10c0/1697e07cb1068055dbe9fe858d242368ff5d2073639e652b75a7eb1f2a1a8d4afd404d719de23c7b48481a6aa0040686310e2dac2f53d776daa2176d3f96369c + languageName: node + linkType: hard + "prom-client@npm:15.1.3": version: 15.1.3 resolution: "prom-client@npm:15.1.3" @@ -17075,6 +18214,22 @@ __metadata: languageName: node linkType: hard +"proxy-agent@npm:^6.4.0": + version: 6.5.0 + resolution: "proxy-agent@npm:6.5.0" + dependencies: + agent-base: "npm:^7.1.2" + debug: "npm:^4.3.4" + http-proxy-agent: "npm:^7.0.1" + https-proxy-agent: "npm:^7.0.6" + lru-cache: "npm:^7.14.1" + pac-proxy-agent: "npm:^7.1.0" + proxy-from-env: "npm:^1.1.0" + socks-proxy-agent: "npm:^8.0.5" + checksum: 10c0/7fd4e6f36bf17098a686d4aee3b8394abfc0b0537c2174ce96b0a4223198b9fafb16576c90108a3fcfc2af0168bd7747152bfa1f58e8fee91d3780e79aab7fd8 + languageName: node + linkType: hard + "proxy-from-env@npm:^1.1.0": version: 1.1.0 resolution: "proxy-from-env@npm:1.1.0" @@ -17110,6 +18265,33 @@ __metadata: languageName: node linkType: hard +"puppeteer-core@npm:22.15.0, puppeteer-core@npm:^22.12.1": + version: 22.15.0 + resolution: "puppeteer-core@npm:22.15.0" + dependencies: + "@puppeteer/browsers": "npm:2.3.0" + chromium-bidi: "npm:0.6.3" + debug: "npm:^4.3.6" + devtools-protocol: "npm:0.0.1312386" + ws: "npm:^8.18.0" + checksum: 10c0/6d041db5f654088857a39e592672fe8cce1e974a1547020d404d3bd5f0e1568eecb2de9b4626b6a48cbe15da1c6ee9d33962cb473dcb67ff08927f4d4ec1e461 + languageName: node + linkType: hard + +"puppeteer@npm:^22.12.1": + version: 22.15.0 + resolution: "puppeteer@npm:22.15.0" + dependencies: + "@puppeteer/browsers": "npm:2.3.0" + cosmiconfig: "npm:^9.0.0" + devtools-protocol: "npm:0.0.1312386" + puppeteer-core: "npm:22.15.0" + bin: + puppeteer: lib/esm/puppeteer/node/cli.js + checksum: 10c0/c31ec024dd7722c32a681c3e2ae23751021abb3f4c39fbdd895859327e855ae2b89e5682fcdb789de7412314701d882bd37e8545e45cf0a97cd5df06449987b9 + languageName: node + linkType: hard + "pure-rand@npm:^6.0.0": version: 6.1.0 resolution: "pure-rand@npm:6.1.0" @@ -17207,6 +18389,20 @@ __metadata: languageName: node linkType: hard +"rc@npm:^1.2.7": + version: 1.2.8 + resolution: "rc@npm:1.2.8" + dependencies: + deep-extend: "npm:^0.6.0" + ini: "npm:~1.3.0" + minimist: "npm:^1.2.0" + strip-json-comments: "npm:~2.0.1" + bin: + rc: ./cli.js + checksum: 10c0/24a07653150f0d9ac7168e52943cc3cb4b7a22c0e43c7dff3219977c2fdca5a2760a304a029c20811a0e79d351f57d46c9bde216193a0f73978496afc2b85b15 + languageName: node + linkType: hard + "react-devtools-core@npm:^4.19.1": version: 4.28.5 resolution: "react-devtools-core@npm:4.28.5" @@ -17735,6 +18931,13 @@ __metadata: languageName: node linkType: hard +"run-applescript@npm:^7.0.0": + version: 7.0.0 + resolution: "run-applescript@npm:7.0.0" + checksum: 10c0/bd821bbf154b8e6c8ecffeaf0c33cebbb78eb2987476c3f6b420d67ab4c5301faa905dec99ded76ebb3a7042b4e440189ae6d85bbbd3fc6e8d493347ecda8bfe + languageName: node + linkType: hard + "run-parallel@npm:^1.1.9": version: 1.2.0 resolution: "run-parallel@npm:1.2.0" @@ -17753,7 +18956,7 @@ __metadata: languageName: node linkType: hard -"safe-array-concat@npm:^1.1.3": +"safe-array-concat@npm:^1.1.2, safe-array-concat@npm:^1.1.3": version: 1.1.3 resolution: "safe-array-concat@npm:1.1.3" dependencies: @@ -18193,6 +19396,24 @@ __metadata: languageName: node linkType: hard +"simple-concat@npm:^1.0.0": + version: 1.0.1 + resolution: "simple-concat@npm:1.0.1" + checksum: 10c0/62f7508e674414008910b5397c1811941d457dfa0db4fd5aa7fa0409eb02c3609608dfcd7508cace75b3a0bf67a2a77990711e32cd213d2c76f4fd12ee86d776 + languageName: node + linkType: hard + +"simple-get@npm:^4.0.0": + version: 4.0.1 + resolution: "simple-get@npm:4.0.1" + dependencies: + decompress-response: "npm:^6.0.0" + once: "npm:^1.3.1" + simple-concat: "npm:^1.0.0" + checksum: 10c0/b0649a581dbca741babb960423248899203165769747142033479a7dc5e77d7b0fced0253c731cd57cf21e31e4d77c9157c3069f4448d558ebc96cf9e1eebcf0 + languageName: node + linkType: hard + "simple-swizzle@npm:^0.2.2": version: 0.2.2 resolution: "simple-swizzle@npm:0.2.2" @@ -18216,6 +19437,16 @@ __metadata: languageName: node linkType: hard +"sleep@npm:6.1.0": + version: 6.1.0 + resolution: "sleep@npm:6.1.0" + dependencies: + nan: "npm:^2.13.2" + node-gyp: "npm:latest" + checksum: 10c0/4d519c6edf77c3b1f07c02dbf6c2fa7022f8555dbacdabab3ef8a670157d74357fe9fcfdb62a8f47e0361859bd7d55b85a7dcd15a42ddca01429cd9cf31dbe45 + languageName: node + linkType: hard + "slice-ansi@npm:^3.0.0": version: 3.0.0 resolution: "slice-ansi@npm:3.0.0" @@ -18255,7 +19486,7 @@ __metadata: languageName: node linkType: hard -"socks-proxy-agent@npm:^8.0.3": +"socks-proxy-agent@npm:^8.0.3, socks-proxy-agent@npm:^8.0.5": version: 8.0.5 resolution: "socks-proxy-agent@npm:8.0.5" dependencies: @@ -18339,6 +19570,17 @@ __metadata: languageName: node linkType: hard +"speedscope@file:./speedscope-1.23.0-alpha.4.tgz::locator=%40internal%2Fperf%40workspace%3Ainternal%2Fperf": + version: 1.23.0-alpha.4 + resolution: "speedscope@file:./speedscope-1.23.0-alpha.4.tgz#./speedscope-1.23.0-alpha.4.tgz::hash=0dbd8f&locator=%40internal%2Fperf%40workspace%3Ainternal%2Fperf" + dependencies: + open: "npm:10.1.0" + bin: + speedscope: ./bin/cli.mjs + checksum: 10c0/3b689396e134f2f2bfd62d65462e7bfbd2a84ecb11f038bfc6136f476b30473306ab94bb385b00d0e01beca6b2432151e0a3ffa3ee85ff1a612717a895f4f49f + languageName: node + linkType: hard + "split-ca@npm:^1.0.1": version: 1.0.1 resolution: "split-ca@npm:1.0.1" @@ -18507,6 +19749,20 @@ __metadata: languageName: node linkType: hard +"streamx@npm:^2.15.0, streamx@npm:^2.21.0": + version: 2.22.0 + resolution: "streamx@npm:2.22.0" + dependencies: + bare-events: "npm:^2.2.0" + fast-fifo: "npm:^1.3.2" + text-decoder: "npm:^1.1.0" + dependenciesMeta: + bare-events: + optional: true + checksum: 10c0/f5017998a5b6360ba652599d20ef308c8c8ab0e26c8e5f624f0706f0ea12624e94fdf1ec18318124498529a1b106a1ab1c94a1b1e1ad6c2eec7cb9c8ac1b9198 + languageName: node + linkType: hard + "string-length@npm:^4.0.1": version: 4.0.2 resolution: "string-length@npm:4.0.2" @@ -18648,6 +19904,13 @@ __metadata: languageName: node linkType: hard +"strip-json-comments@npm:~2.0.1": + version: 2.0.1 + resolution: "strip-json-comments@npm:2.0.1" + checksum: 10c0/b509231cbdee45064ff4f9fd73609e2bcc4e84a4d508e9dd0f31f70356473fde18abfb5838c17d56fb236f5a06b102ef115438de0600b749e818a35fbbc48c43 + languageName: node + linkType: hard + "strnum@npm:^1.0.5": version: 1.1.2 resolution: "strnum@npm:1.1.2" @@ -18761,6 +20024,35 @@ __metadata: languageName: node linkType: hard +"tar-fs@npm:^2.0.0": + version: 2.1.2 + resolution: "tar-fs@npm:2.1.2" + dependencies: + chownr: "npm:^1.1.1" + mkdirp-classic: "npm:^0.5.2" + pump: "npm:^3.0.0" + tar-stream: "npm:^2.1.4" + checksum: 10c0/9c704bd4a53be7565caf34ed001d1428532457fe3546d8fc1233f0f0882c3d2403f8602e8046e0b0adeb31fe95336572a69fb28851a391523126b697537670fc + languageName: node + linkType: hard + +"tar-fs@npm:^3.0.6": + version: 3.0.8 + resolution: "tar-fs@npm:3.0.8" + dependencies: + bare-fs: "npm:^4.0.1" + bare-path: "npm:^3.0.0" + pump: "npm:^3.0.0" + tar-stream: "npm:^3.1.5" + dependenciesMeta: + bare-fs: + optional: true + bare-path: + optional: true + checksum: 10c0/b70bb2ad0490ab13b48edd10bd648bb54c52b681981cdcdc3aa4517e98ad94c94659ddca1925872ee658d781b9fcdd2b1c808050647f06b1bca157dd2fcae038 + languageName: node + linkType: hard + "tar-fs@npm:~2.0.1": version: 2.0.1 resolution: "tar-fs@npm:2.0.1" @@ -18773,7 +20065,7 @@ __metadata: languageName: node linkType: hard -"tar-stream@npm:^2.0.0, tar-stream@npm:^2.0.1": +"tar-stream@npm:^2.0.0, tar-stream@npm:^2.0.1, tar-stream@npm:^2.1.4": version: 2.2.0 resolution: "tar-stream@npm:2.2.0" dependencies: @@ -18786,6 +20078,17 @@ __metadata: languageName: node linkType: hard +"tar-stream@npm:^3.1.5": + version: 3.1.7 + resolution: "tar-stream@npm:3.1.7" + dependencies: + b4a: "npm:^1.6.4" + fast-fifo: "npm:^1.2.0" + streamx: "npm:^2.15.0" + checksum: 10c0/a09199d21f8714bd729993ac49b6c8efcb808b544b89f23378ad6ffff6d1cb540878614ba9d4cfec11a64ef39e1a6f009a5398371491eb1fda606ffc7f70f718 + languageName: node + linkType: hard + "tar@npm:^6.0.5, tar@npm:^6.1.11": version: 6.2.1 resolution: "tar@npm:6.2.1" @@ -18874,6 +20177,15 @@ __metadata: languageName: node linkType: hard +"text-decoder@npm:^1.1.0": + version: 1.2.3 + resolution: "text-decoder@npm:1.2.3" + dependencies: + b4a: "npm:^1.6.4" + checksum: 10c0/569d776b9250158681c83656ef2c3e0a5d5c660c27ca69f87eedef921749a4fbf02095e5f9a0f862a25cf35258379b06e31dee9c125c9f72e273b7ca1a6d1977 + languageName: node + linkType: hard + "text-hex@npm:1.0.x": version: 1.0.0 resolution: "text-hex@npm:1.0.0" @@ -18908,7 +20220,7 @@ __metadata: languageName: node linkType: hard -"through@npm:2, through@npm:~2.3, through@npm:~2.3.1": +"through@npm:2, through@npm:^2.3.8, through@npm:~2.3, through@npm:~2.3.1": version: 2.3.8 resolution: "through@npm:2.3.8" checksum: 10c0/4b09f3774099de0d4df26d95c5821a62faee32c7e96fb1f4ebd54a2d7c11c57fe88b0a0d49cf375de5fee5ae6bf4eb56dbbf29d07366864e2ee805349970d3cc @@ -19253,6 +20565,15 @@ __metadata: languageName: node linkType: hard +"tunnel-agent@npm:^0.6.0": + version: 0.6.0 + resolution: "tunnel-agent@npm:0.6.0" + dependencies: + safe-buffer: "npm:^5.0.1" + checksum: 10c0/4c7a1b813e7beae66fdbf567a65ec6d46313643753d0beefb3c7973d66fcec3a1e7f39759f0a0b4465883499c6dc8b0750ab8b287399af2e583823e40410a17a + languageName: node + linkType: hard + "tunnel@npm:^0.0.6": version: 0.0.6 resolution: "tunnel@npm:0.0.6" @@ -19463,6 +20784,16 @@ __metadata: languageName: node linkType: hard +"unbzip2-stream@npm:^1.4.3": + version: 1.4.3 + resolution: "unbzip2-stream@npm:1.4.3" + dependencies: + buffer: "npm:^5.2.1" + through: "npm:^2.3.8" + checksum: 10c0/2ea2048f3c9db3499316ccc1d95ff757017ccb6f46c812d7c42466247e3b863fb178864267482f7f178254214247779daf68e85f50bd7736c3c97ba2d58b910a + languageName: node + linkType: hard + "undici-types@npm:~5.26.4": version: 5.26.5 resolution: "undici-types@npm:5.26.5" @@ -19644,7 +20975,7 @@ __metadata: languageName: node linkType: hard -"urlpattern-polyfill@npm:^10.0.0": +"urlpattern-polyfill@npm:10.0.0, urlpattern-polyfill@npm:^10.0.0": version: 10.0.0 resolution: "urlpattern-polyfill@npm:10.0.0" checksum: 10c0/43593f2a89bd54f2d5b5105ef4896ac5c5db66aef723759fbd15cd5eb1ea6cdae9d112e257eda9bbc3fb0cd90be6ac6e9689abe4ca69caa33114f42a27363531 @@ -19658,6 +20989,26 @@ __metadata: languageName: node linkType: hard +"util.promisify@npm:^1.1.1": + version: 1.1.3 + resolution: "util.promisify@npm:1.1.3" + dependencies: + call-bind: "npm:^1.0.8" + call-bound: "npm:^1.0.3" + define-data-property: "npm:^1.1.4" + define-properties: "npm:^1.2.1" + es-errors: "npm:^1.3.0" + es-object-atoms: "npm:^1.0.0" + for-each: "npm:^0.3.3" + get-intrinsic: "npm:^1.2.6" + has-proto: "npm:^1.2.0" + has-symbols: "npm:^1.1.0" + object.getownpropertydescriptors: "npm:^2.1.8" + safe-array-concat: "npm:^1.1.3" + checksum: 10c0/b7a2f721222ffc26f8a12a1cdc44a81b16a614b81f2d6f71f50c8b214a20afe8324d5f0700f04759d6ea33446ba0730e4ef0b4e2c4f4ef5adca16dabd502fa2b + languageName: node + linkType: hard + "utils-merge@npm:1.0.1": version: 1.0.1 resolution: "utils-merge@npm:1.0.1" @@ -20234,6 +21585,18 @@ __metadata: languageName: node linkType: hard +"xvfb@npm:^0.4.0": + version: 0.4.0 + resolution: "xvfb@npm:0.4.0" + dependencies: + sleep: "npm:6.1.0" + dependenciesMeta: + sleep: + optional: true + checksum: 10c0/275cbf3d24b44563cbf62afe6648cb4466a768a7725515ee0df44efe67e8b799bed57810ad104fb29ed80b3e1c6080551c51de78d1382cb1acab8e42064f9474 + languageName: node + linkType: hard + "y18n@npm:^5.0.5": version: 5.0.8 resolution: "y18n@npm:5.0.8" @@ -20284,6 +21647,16 @@ __metadata: languageName: node linkType: hard +"yauzl@npm:^2.10.0": + version: 2.10.0 + resolution: "yauzl@npm:2.10.0" + dependencies: + buffer-crc32: "npm:~0.2.3" + fd-slicer: "npm:~1.1.0" + checksum: 10c0/f265002af7541b9ec3589a27f5fb8f11cf348b53cc15e2751272e3c062cd73f3e715bc72d43257de71bbaecae446c3f1b14af7559e8ab0261625375541816422 + languageName: node + linkType: hard + "yn@npm:3.1.1": version: 3.1.1 resolution: "yn@npm:3.1.1" @@ -20325,6 +21698,13 @@ __metadata: languageName: node linkType: hard +"zod@npm:3.23.8": + version: 3.23.8 + resolution: "zod@npm:3.23.8" + checksum: 10c0/8f14c87d6b1b53c944c25ce7a28616896319d95bc46a9660fe441adc0ed0a81253b02b5abdaeffedbeb23bdd25a0bf1c29d2c12dd919aef6447652dd295e3e69 + languageName: node + linkType: hard + "zod@npm:^3.24.1": version: 3.24.2 resolution: "zod@npm:3.24.2"