diff --git a/guides/esm-migration.md b/guides/esm-migration.md index f3fbae0fd9a..eee986ccc44 100644 --- a/guides/esm-migration.md +++ b/guides/esm-migration.md @@ -65,7 +65,7 @@ When migrating some of these projects away from the `ts-node` entry [see `@packa - [ ] packages/server **PARTIAL** - many source/test files in JS. highest priority - [ ] packages/socket **PARTIAL** - entry point is JS. Tests are JS - [x] packages/stderr-filtering ✅ **COMPLETED** -- [ ] packages/telemetry **PARTIAL** - entry point is JS +- [x] packages/telemetry ✅ **COMPLETED** - [ ] packages/ts **PARTIAL** - ultimate goal is removal and likely not worth the effort to convert - [x] packages/types ✅ **COMPLETED** - [x] packages/v8-snapshot-require diff --git a/packages/app/src/main.ts b/packages/app/src/main.ts index 7d5f77e0d95..27b54273237 100644 --- a/packages/app/src/main.ts +++ b/packages/app/src/main.ts @@ -13,7 +13,7 @@ import Toast, { POSITION } from 'vue-toastification' import 'vue-toastification/dist/index.css' import { createWebsocket } from './runner' import { getRunnerConfigFromWindow } from './runner/get-runner-config-from-window' -import { telemetry } from '@packages/telemetry/src/browser' +import { telemetry } from '@packages/telemetry/browser/client' // Grab the time just before loading config to include that in the cypress:app span const now = performance.now() diff --git a/packages/app/src/runner/event-manager.ts b/packages/app/src/runner/event-manager.ts index 4bddeb5e828..163faa0ceb8 100644 --- a/packages/app/src/runner/event-manager.ts +++ b/packages/app/src/runner/event-manager.ts @@ -14,7 +14,7 @@ import { useStudioStore } from '../store/studio-store' import { getAutIframeModel } from '.' import { handlePausing } from './events/pausing' import { addTelemetryListeners } from './events/telemetry' -import { telemetry } from '@packages/telemetry/src/browser' +import { telemetry } from '@packages/telemetry/browser/client' import { addCaptureProtocolListeners } from './events/capture-protocol' import { getRunnerConfigFromWindow } from './get-runner-config-from-window' import { usePromptStore } from '../store/prompt-store' diff --git a/packages/app/src/runner/events/telemetry.ts b/packages/app/src/runner/events/telemetry.ts index 2a4346f9163..013d8bc132f 100644 --- a/packages/app/src/runner/events/telemetry.ts +++ b/packages/app/src/runner/events/telemetry.ts @@ -1,4 +1,4 @@ -import { telemetry } from '@packages/telemetry/src/browser' +import { telemetry } from '@packages/telemetry/browser/client' export const addTelemetryListeners = (Cypress: Cypress.Cypress) => { Cypress.on('test:before:run', (attributes, test) => { diff --git a/packages/driver/src/cypress.ts b/packages/driver/src/cypress.ts index fc84aafe8ed..4eed2d64715 100644 --- a/packages/driver/src/cypress.ts +++ b/packages/driver/src/cypress.ts @@ -48,7 +48,7 @@ import type { CachedTestState, ReporterRunState, RunState } from '@packages/type import { DocumentDomainInjection } from '@packages/network/lib/document-domain-injection' import { setSpecContentSecurityPolicy } from './util/privileged_channel' -import { telemetry } from '@packages/telemetry/src/browser' +import { telemetry } from '@packages/telemetry/browser/client' const debug = debugFn('cypress:driver:cypress') diff --git a/packages/driver/src/main.ts b/packages/driver/src/main.ts index 35f66cbeb63..b91546b8205 100644 --- a/packages/driver/src/main.ts +++ b/packages/driver/src/main.ts @@ -4,7 +4,7 @@ import './config/bluebird' import './config/jquery' import './config/lodash' import $Cypress from './cypress' -import { telemetry } from '@packages/telemetry/src/browser' +import { telemetry } from '@packages/telemetry/browser/client' // Telemetry has already been initialized in the 'app' package // but since this is a different package we have to link up the instances. diff --git a/packages/proxy/lib/network-proxy.ts b/packages/proxy/lib/network-proxy.ts index b353458da63..13490b9271c 100644 --- a/packages/proxy/lib/network-proxy.ts +++ b/packages/proxy/lib/network-proxy.ts @@ -55,7 +55,7 @@ export class NetworkProxy { isVerbose: true, }) - await this.http.handleHttpRequest(req, res, span).finally(() => { + await this.http.handleHttpRequest(req, res, span || undefined).finally(() => { span?.end() }) } diff --git a/packages/telemetry/.gitignore b/packages/telemetry/.gitignore new file mode 100644 index 00000000000..cba40675de7 --- /dev/null +++ b/packages/telemetry/.gitignore @@ -0,0 +1,4 @@ +cjs/ +esm/ +browser/ +!src/browser \ No newline at end of file diff --git a/packages/telemetry/README.md b/packages/telemetry/README.md index c5233488ef2..19521670bc3 100644 --- a/packages/telemetry/README.md +++ b/packages/telemetry/README.md @@ -140,10 +140,10 @@ const { OTLPTraceExporterIPC } = require('@packages/telemetry') ### Browser -To access the browser telemetry singleton use the browser export directly. +To access the browser telemetry singleton use the browser export. ```js -import { telemetry } from '@packages/telemetry/src/browser' +import { telemetry } from '@packages/telemetry/browser/client' telemetry.init({options}) ``` @@ -151,7 +151,7 @@ telemetry.init({options}) The browser singleton is also stored on window, in some cases when the telemetry package is included in multiple packages you can use the `attach` method to retrieve and setup the singleton from the instance saved on window. ```js -import { telemetry } from '@packages/telemetry/src/browser' +import { telemetry } from '@packages/telemetry/browser/client' telemetry.attach() ``` @@ -198,7 +198,7 @@ const { telemetry } = require('@packages/telemetry') Browser: ```js -import { telemetry } from '@packages/telemetry/src/browser' +import { telemetry } from '@packages/telemetry/browser/client' ``` ### Spans @@ -290,4 +290,12 @@ The metrics api is tbd. ## Open Telemetry Links * [otel docs](https://opentelemetry.io/docs/) -* [otel sdk](https://open-telemetry.github.io/opentelemetry-js/index.html) \ No newline at end of file +* [otel sdk](https://open-telemetry.github.io/opentelemetry-js/index.html) + +### Bundling + +For the frontend telemetry collector, we use `rollup` to bundle the telemetry collector for frontend consumption. This DOES ship with the binary. + +For the server, we bundle a CommonJS version to be used in the Node.js context. + +We also build an ESM version of `@packages/scaffold-config` for the `node` client but it isn't currently in use as the consumption server-side is CommonJS currently. \ No newline at end of file diff --git a/packages/telemetry/index.js b/packages/telemetry/index.js deleted file mode 100644 index 3e040a203c8..00000000000 --- a/packages/telemetry/index.js +++ /dev/null @@ -1,5 +0,0 @@ -if (process.env.CYPRESS_INTERNAL_ENV !== 'production') { - require('@packages/ts/register') -} - -module.exports = require('./src/node') diff --git a/packages/telemetry/package.json b/packages/telemetry/package.json index 158a4584f32..c089d7c2d54 100644 --- a/packages/telemetry/package.json +++ b/packages/telemetry/package.json @@ -3,12 +3,16 @@ "version": "0.0.0-development", "description": "open telemetry wrapper used throughout the cypress monorepo to instrument the cypress app", "private": true, - "main": "dist/node.js", - "browser": "src/browser.ts", + "main": "cjs/node.js", "scripts": { - "build": "tsc", - "check-ts": "tsc --noEmit && yarn -s tslint", - "clean": "rimraf dist", + "build": "yarn build:esm && yarn build:cjs && yarn build:browser", + "build-prod": "yarn build", + "build:browser": "rimraf browser && rollup -c rollup.config.mjs", + "build:cjs": "rimraf cjs && tsc -p tsconfig.cjs.json", + "build:esm": "rimraf esm && tsc -p tsconfig.esm.json", + "check-ts": "tsc -p tsconfig.cjs.json --noEmit && yarn -s tslint -p tsconfig.cjs.json", + "clean": "rimraf esm cjs", + "clean-deps": "rimraf node_modules", "test": "yarn test-unit", "test-debug": "vitest --inspect-brk --no-file-parallelism --test-timeout=0", "test-unit": "vitest run", @@ -28,12 +32,18 @@ }, "devDependencies": { "@packages/ts": "0.0.0-development", + "@rollup/plugin-typescript": "12.0.0", + "rimraf": "6.0.1", + "rollup": "4.52.0", + "typescript": "5.6.3", "vitest": "^3.2.4" }, "files": [ - "dist", - "src" + "esm", + "cjs", + "browser" ], - "types": "src/node.ts", + "types": "cjs/node.d.ts", + "module": "esm/node.js", "nx": {} } diff --git a/packages/telemetry/rollup.config.mjs b/packages/telemetry/rollup.config.mjs new file mode 100644 index 00000000000..771583f3efe --- /dev/null +++ b/packages/telemetry/rollup.config.mjs @@ -0,0 +1,20 @@ +import typescript from '@rollup/plugin-typescript' + +// inline all the values/imports from the entry point client.ts into the browser/client.js bundle +// and provides declarations for the browser/client.d.ts bundle +const config = [ + { + input: 'src/client.ts', + output: { + file: 'browser/client.js', + format: 'esm', + }, + plugins: [ + typescript({ + tsconfig: 'tsconfig.browser.json', + }), + ], + }, +] + +export default config diff --git a/packages/telemetry/src/browser.ts b/packages/telemetry/src/client.ts similarity index 96% rename from packages/telemetry/src/browser.ts rename to packages/telemetry/src/client.ts index 2391c5d5da1..2062583b864 100644 --- a/packages/telemetry/src/browser.ts +++ b/packages/telemetry/src/client.ts @@ -1,6 +1,6 @@ import type { Span, Attributes } from '@opentelemetry/api' -import type { startSpanOptions, findActiveSpanOptions, contextObject } from './index' -import { Telemetry as TelemetryClass, TelemetryNoop } from './index' +import type { startSpanOptions, findActiveSpanOptions, contextObject } from './telemetry/index' +import { Telemetry as TelemetryClass, TelemetryNoop } from './telemetry/index' import { WebTracerProvider } from '@opentelemetry/sdk-trace-web' import { browserDetectorSync } from '@opentelemetry/resources' import { SimpleSpanProcessor } from '@opentelemetry/sdk-trace-base' diff --git a/packages/telemetry/src/node.ts b/packages/telemetry/src/node.ts index d8132e05e5e..743771fa9b3 100644 --- a/packages/telemetry/src/node.ts +++ b/packages/telemetry/src/node.ts @@ -1,12 +1,12 @@ import type { Span } from '@opentelemetry/api' -import type { startSpanOptions, findActiveSpanOptions, contextObject } from './index' +import type { startSpanOptions, findActiveSpanOptions, contextObject } from './telemetry/index' import { envDetectorSync, hostDetectorSync, osDetectorSync, processDetectorSync, } from '@opentelemetry/resources' import { BatchSpanProcessor } from '@opentelemetry/sdk-trace-base' import { NodeTracerProvider } from '@opentelemetry/sdk-trace-node' import { circleCiDetectorSync } from './detectors/circleCiDetectorSync' -import { enabledValues, Telemetry as TelemetryClass, TelemetryNoop } from './index' +import { enabledValues, Telemetry as TelemetryClass, TelemetryNoop } from './telemetry/index' import { OTLPTraceExporter as OTLPTraceExporterCloud } from './span-exporters/cloud-span-exporter' import { OTLPTraceExporter as OTLPTraceExporterIpc } from './span-exporters/ipc-span-exporter' diff --git a/packages/telemetry/src/index.ts b/packages/telemetry/src/telemetry/index.ts similarity index 94% rename from packages/telemetry/src/index.ts rename to packages/telemetry/src/telemetry/index.ts index 8d0c0b4bd2d..bbcdf965699 100644 --- a/packages/telemetry/src/index.ts +++ b/packages/telemetry/src/telemetry/index.ts @@ -1,8 +1,8 @@ import openTelemetry from '@opentelemetry/api' import { detectResourcesSync, Resource } from '@opentelemetry/resources' import { SemanticResourceAttributes } from '@opentelemetry/semantic-conventions' -import { OnStartSpanProcessor } from './processors/on-start-span-processor' -import { ConsoleTraceLinkExporter } from './span-exporters/console-trace-link-exporter' +import { OnStartSpanProcessor } from '../processors/on-start-span-processor' +import { ConsoleTraceLinkExporter } from '../span-exporters/console-trace-link-exporter' import type { Span, SpanOptions, Tracer, Context, Attributes } from '@opentelemetry/api' import type { BasicTracerProvider, SimpleSpanProcessor, BatchSpanProcessor, SpanExporter } from '@opentelemetry/sdk-trace-base' @@ -350,29 +350,31 @@ export class Telemetry implements TelemetryApi { * all operations. */ export class TelemetryNoop implements TelemetryApi { - startSpan () { + startSpan (arg: startSpanOptions): Span | undefined { return undefined } - getSpan () { + getSpan (name: string): Span | undefined { return undefined } - findActiveSpan () { + findActiveSpan (fn: findActiveSpanOptions): Span | undefined { return undefined } - endActiveSpanAndChildren () { + endActiveSpanAndChildren (span?: Span | undefined): void { return undefined } getActiveContextObject (): contextObject { return {} } - getResources () { + getResources (): Attributes { return {} } - shutdown () { + shutdown (): Promise { return Promise.resolve() } - getExporter () { + getExporter (): SpanExporter | undefined { + return undefined + } + setRootContext (rootContextObject?: contextObject): void { return undefined } - setRootContext () {} } diff --git a/packages/telemetry/test/browser.spec.ts b/packages/telemetry/test/browser.spec.ts index 2ed590b9215..09f38dd4379 100644 --- a/packages/telemetry/test/browser.spec.ts +++ b/packages/telemetry/test/browser.spec.ts @@ -2,8 +2,8 @@ global.window = {} import { describe, it, expect, beforeAll } from 'vitest' -import { telemetry } from '../src/browser' -import { Telemetry as TelemetryClass } from '../src/index' +import { telemetry } from '../src/client' +import { Telemetry as TelemetryClass } from '../src/telemetry/index' describe('telemetry is disabled', () => { describe('init', () => { diff --git a/packages/telemetry/test/index.spec.ts b/packages/telemetry/test/index.spec.ts index 69ab3bf3113..9fc1675f031 100644 --- a/packages/telemetry/test/index.spec.ts +++ b/packages/telemetry/test/index.spec.ts @@ -1,5 +1,5 @@ import { describe, it, expect } from 'vitest' -import { Telemetry } from '../src' +import { Telemetry } from '../src/telemetry' import { NodeTracerProvider } from '@opentelemetry/sdk-trace-node' import { BatchSpanProcessor } from '@opentelemetry/sdk-trace-base' @@ -16,6 +16,7 @@ describe('init', () => { exporter, version: 'version', SpanProcessor: BatchSpanProcessor, + isVerbose: false, }) expect(tel).toBeDefined() @@ -40,6 +41,7 @@ describe('init', () => { version: 'version', rootContextObject: { context: { traceparent: '00-a14c8519972996a2a0748f2c8db5a775-4ad8bd26672a01b0-01' }, attributes: { yes: 'no' } }, SpanProcessor: BatchSpanProcessor, + isVerbose: false, }) expect(tel).toBeDefined() @@ -60,6 +62,7 @@ describe('startSpan', () => { version: 'version', rootContextObject: { context: { traceparent: '00-a14c8519972996a2a0748f2c8db5a775-4ad8bd26672a01b0-01' } }, SpanProcessor: BatchSpanProcessor, + isVerbose: false, }) const span = tel.startSpan({ name: 'span' }) @@ -83,6 +86,7 @@ describe('startSpan', () => { exporter, version: 'version', SpanProcessor: BatchSpanProcessor, + isVerbose: false, }) const span = tel.startSpan({ name: 'span' }) @@ -103,6 +107,7 @@ describe('startSpan', () => { exporter, version: 'version', SpanProcessor: BatchSpanProcessor, + isVerbose: false, }) const parentSpan = tel.startSpan({ name: 'parentSpan' }) @@ -126,6 +131,7 @@ describe('startSpan', () => { exporter, version: 'version', SpanProcessor: BatchSpanProcessor, + isVerbose: false, }) const span = tel.startSpan({ name: 'span', active: true }) @@ -169,6 +175,7 @@ describe('startSpan', () => { exporter, version: 'version', SpanProcessor: BatchSpanProcessor, + isVerbose: false, }) const span = tel.startSpan({ name: 'span', key: 'key' }) @@ -192,6 +199,7 @@ describe('getSpan', () => { detectors: [], exporter, version: 'version', + // @ts-expect-error rootContextObject: { traceparent: 'id' }, SpanProcessor: BatchSpanProcessor, }) @@ -216,6 +224,7 @@ describe('findActiveSpan', () => { version: 'version', rootContextObject: { context: { traceparent: 'id' } }, SpanProcessor: BatchSpanProcessor, + isVerbose: false, }) const spanny = tel.startSpan({ name: 'spanny', active: true }) @@ -242,6 +251,7 @@ describe('endActiveSpanAndChildren', () => { version: 'version', rootContextObject: { context: { traceparent: 'id' } }, SpanProcessor: BatchSpanProcessor, + isVerbose: false, }) const spanny = tel.startSpan({ name: 'spanny', active: true }) @@ -274,6 +284,7 @@ describe('getActiveContextObject', () => { version: 'version', rootContextObject: { context: { traceparent: 'id' } }, SpanProcessor: BatchSpanProcessor, + isVerbose: false, }) const emptyContext = tel.getActiveContextObject() @@ -298,12 +309,14 @@ describe('getResources', () => { detectors: [], exporter, version: 'version', + // @ts-expect-error rootContextObject: { traceparent: 'id' }, SpanProcessor: BatchSpanProcessor, resources: { herp: 'derp', 'service.name': 'not overridden', }, + isVerbose: false, }) expect(tel.getResources()).toEqual(expect.objectContaining({ @@ -327,8 +340,10 @@ describe('shutdown', () => { detectors: [], exporter, version: 'version', + // @ts-expect-error rootContextObject: { traceparent: 'id' }, SpanProcessor: BatchSpanProcessor, + isVerbose: false, }) let shutdownCalled = false @@ -356,8 +371,10 @@ describe('getExporter', () => { detectors: [], exporter, version: 'version', + // @ts-expect-error rootContextObject: { traceparent: 'id' }, SpanProcessor: BatchSpanProcessor, + isVerbose: false, }) expect(tel.getExporter()).toEqual(exporter) @@ -376,6 +393,7 @@ describe('setRootContext', () => { version: 'version', rootContextObject: { context: { traceparent: '00-a14c8519972996a2a0748f2c8db5a775-4ad8bd26672a01b0-01' } }, SpanProcessor: BatchSpanProcessor, + isVerbose: false, }) // @ts-expect-error @@ -398,6 +416,7 @@ describe('setRootContext', () => { version: 'version', rootContextObject: { context: { traceparent: '00-a14c8519972996a2a0748f2c8db5a775-4ad8bd26672a01b0-01' } }, SpanProcessor: BatchSpanProcessor, + isVerbose: false, }) // @ts-expect-error diff --git a/packages/telemetry/tsconfig.base.json b/packages/telemetry/tsconfig.base.json new file mode 100644 index 00000000000..3c2b1a10f93 --- /dev/null +++ b/packages/telemetry/tsconfig.base.json @@ -0,0 +1,18 @@ +{ + "compilerOptions": { + "lib": [ + "esnext", + "DOM" + ], + "allowJs": false, + "noImplicitAny": true, + "noUncheckedIndexedAccess": true, + "types": [ + "cypress" + ], + "esModuleInterop": true, + "resolveJsonModule": true, + "skipLibCheck": true, + "declaration": true + } +} diff --git a/packages/telemetry/tsconfig.browser.json b/packages/telemetry/tsconfig.browser.json new file mode 100644 index 00000000000..809e3c30977 --- /dev/null +++ b/packages/telemetry/tsconfig.browser.json @@ -0,0 +1,13 @@ +{ + "extends": "./tsconfig.base.json", + "include": [ + "src/client.ts" + ], + "compilerOptions": { + "outDir": "./browser", + "rootDir": "./src", + "target": "ES2022", + "module": "ES2022", + "moduleResolution": "bundler" + } +} \ No newline at end of file diff --git a/packages/telemetry/tsconfig.cjs.json b/packages/telemetry/tsconfig.cjs.json new file mode 100644 index 00000000000..618894c0ec2 --- /dev/null +++ b/packages/telemetry/tsconfig.cjs.json @@ -0,0 +1,16 @@ +{ + "extends": "./tsconfig.base.json", + "include": [ + "src/**/*.ts" + ], + "exclude": [ + "src/client.ts" + ], + "compilerOptions": { + "rootDir": "./src", + "outDir": "./cjs", + "target": "ES2022", + "module": "CommonJS", + "moduleResolution": "node" + } +} \ No newline at end of file diff --git a/packages/telemetry/tsconfig.esm.json b/packages/telemetry/tsconfig.esm.json new file mode 100644 index 00000000000..98c9c43ed46 --- /dev/null +++ b/packages/telemetry/tsconfig.esm.json @@ -0,0 +1,17 @@ +{ + "extends": "./tsconfig.base.json", + "include": [ + "src/**/*.ts" + ], + "exclude": [ + "src/client.ts" + ], + "compilerOptions": { + "rootDir": "./src", + "outDir": "./esm", + "target": "ES2022", + "module": "ES2022", + "moduleResolution": "node", + "noEmit": true + } +} \ No newline at end of file diff --git a/packages/telemetry/tsconfig.json b/packages/telemetry/tsconfig.json deleted file mode 100644 index 50c4b856c19..00000000000 --- a/packages/telemetry/tsconfig.json +++ /dev/null @@ -1,12 +0,0 @@ -{ - "extends": "../ts/tsconfig.json", - "include": ["src"], - "compilerOptions": { - "outDir": "./dist", - "lib": ["esnext", "DOM"], - "allowJs": false, - "noImplicitAny": true, - "noUncheckedIndexedAccess": true, - "types": ["cypress"], - } -} diff --git a/yarn.lock b/yarn.lock index 6ac4e67f682..f0359edf456 100644 --- a/yarn.lock +++ b/yarn.lock @@ -6720,6 +6720,14 @@ is-module "^1.0.0" resolve "^1.19.0" +"@rollup/plugin-typescript@12.0.0": + version "12.0.0" + resolved "https://registry.yarnpkg.com/@rollup/plugin-typescript/-/plugin-typescript-12.0.0.tgz#e92dee7ac5a686864067e34daa5a7df1e3b82c55" + integrity sha512-w5PTCahNqP8bokY9/7DO+hjbJF7ru0yBuWM9QMNb9m+GQgwb7zYsOBbPxqdjDqYSVCgvvDprcNH2kevsev8IBg== + dependencies: + "@rollup/pluginutils" "^5.1.0" + resolve "^1.22.1" + "@rollup/pluginutils@^3.0.4", "@rollup/pluginutils@^3.1.0": version "3.1.0" resolved "https://registry.yarnpkg.com/@rollup/pluginutils/-/pluginutils-3.1.0.tgz#706b4524ee6dc8b103b3c995533e5ad680c02b9b" @@ -28782,7 +28790,7 @@ rollup@3.29.5: optionalDependencies: fsevents "~2.3.2" -rollup@^4.20.0, rollup@^4.24.4, rollup@^4.34.9, rollup@^4.43.0, rollup@^4.52.0: +rollup@4.52.0, rollup@^4.20.0, rollup@^4.24.4, rollup@^4.34.9, rollup@^4.43.0, rollup@^4.52.0: version "4.52.0" resolved "https://registry.yarnpkg.com/rollup/-/rollup-4.52.0.tgz#5a906bf98f7c7a2c08d2b18fbfa52955552423d7" integrity sha512-+IuescNkTJQgX7AkIDtITipZdIGcWF0pnVvZTWStiazUmcGA2ag8dfg0urest2XlXUi9kuhfQ+qmdc5Stc3z7g== @@ -32181,6 +32189,11 @@ typescript@5.4.5, typescript@~5.4.5: resolved "https://registry.yarnpkg.com/typescript/-/typescript-5.4.5.tgz#42ccef2c571fdbd0f6718b1d1f5e6e5ef006f611" integrity sha512-vcI4UpRgg81oIRUFwR0WSIHKt11nJ7SAVlYNIu+QpqeyXP+gpQJy/Z4+F0aGxSE4MqwjyXvW/TzgkLAx2AGHwQ== +typescript@5.6.3: + version "5.6.3" + resolved "https://registry.yarnpkg.com/typescript/-/typescript-5.6.3.tgz#5f3449e31c9d94febb17de03cc081dd56d81db5b" + integrity sha512-hjcS1mhfuyi4WW8IWtjP7brDrG2cuDZukyrYrSauoXGNgx0S7zceP07adYkJycEr56BOUTNPzbInooiN3fn1qw== + "typescript@>=3 < 6", typescript@^5.4.3, typescript@^5.6.3, typescript@^5.8.2, typescript@~5.9.2: version "5.9.2" resolved "https://registry.yarnpkg.com/typescript/-/typescript-5.9.2.tgz#d93450cddec5154a2d5cabe3b8102b83316fb2a6"