diff --git a/packages/ember/.eslintrc.js b/packages/ember/.eslintrc.js index 9a53dffde69e..83d1d1583fdb 100644 --- a/packages/ember/.eslintrc.js +++ b/packages/ember/.eslintrc.js @@ -1,34 +1,38 @@ 'use strict'; module.exports = { - root: true, - parser: 'babel-eslint', - parserOptions: { - ecmaVersion: 2018, - sourceType: 'module', - ecmaFeatures: { - legacyDecorators: true, - }, - }, - plugins: ['ember'], - extends: ['eslint:recommended', 'plugin:ember/recommended'], - env: { - browser: true, - }, - globals: { - QUnit: true, - }, - rules: {}, + extends: ['../../.eslintrc.js'], + overrides: [ { - files: ['addon/**'], - plugins: ['@sentry-internal/eslint-plugin-sdk'], + // addon files + files: ['{addon,app,tests}/**/*.{js,ts,d.ts}'], + parserOptions: { + sourceType: 'module', + babelOptions: { + plugins: [['@babel/plugin-proposal-decorators', { decoratorsBeforeExport: true }]], + }, + }, + plugins: ['ember'], + extends: ['plugin:ember/recommended'], + rules: { + 'import/no-unresolved': 'off', + }, + }, + { + // test files + files: ['tests/**/*-test.{js,ts}', 'tests/helpers/**/*.{js,ts}'], + extends: ['plugin:qunit/recommended'], + /* globals: { + QUnit: true, + }, */ + rules: { + 'qunit/require-expect': 'off', + }, }, - // node files { files: [ './.eslintrc.js', - './.prettierrc.js', './.template-lintrc.js', './ember-cli-build.js', './index.js', @@ -44,13 +48,7 @@ module.exports = { browser: false, node: true, }, - plugins: ['node'], - extends: ['plugin:node/recommended'], - }, - { - // test files - files: ['tests/**/*-test.{js,ts}'], - extends: ['plugin:qunit/recommended'], + extends: ['plugin:n/recommended'], }, ], }; diff --git a/packages/ember/addon/index.ts b/packages/ember/addon/index.ts index 2db7ac4192f6..fbcea14d5533 100644 --- a/packages/ember/addon/index.ts +++ b/packages/ember/addon/index.ts @@ -1,19 +1,23 @@ -import * as Sentry from '@sentry/browser'; -import { SDK_VERSION, BrowserOptions } from '@sentry/browser'; -import { macroCondition, isDevelopingApp, getOwnConfig } from '@embroider/macros'; -import { next } from '@ember/runloop'; import { assert, warn } from '@ember/debug'; +import type Route from '@ember/routing/route'; +import { next } from '@ember/runloop'; +import { getOwnConfig, isDevelopingApp, macroCondition } from '@embroider/macros'; +import type { BrowserOptions } from '@sentry/browser'; +import * as Sentry from '@sentry/browser'; +import { SDK_VERSION } from '@sentry/browser'; +import type { Transaction } from '@sentry/types'; +import { GLOBAL_OBJ, timestampInSeconds } from '@sentry/utils'; import Ember from 'ember'; -import { timestampInSeconds, GLOBAL_OBJ } from '@sentry/utils'; -import { GlobalConfig, OwnConfig } from './types'; -function _getSentryInitConfig() { +import type { EmberSentryConfig, GlobalConfig, OwnConfig } from './types'; + +function _getSentryInitConfig(): EmberSentryConfig['sentry'] { const _global = GLOBAL_OBJ as typeof GLOBAL_OBJ & GlobalConfig; _global.__sentryEmberConfig = _global.__sentryEmberConfig ?? {}; return _global.__sentryEmberConfig; } -export function InitSentryForEmber(_runtimeConfig?: BrowserOptions) { +export function InitSentryForEmber(_runtimeConfig?: BrowserOptions): void { const environmentConfig = getOwnConfig().sentryConfig; assert('Missing configuration.', environmentConfig); @@ -21,7 +25,7 @@ export function InitSentryForEmber(_runtimeConfig?: BrowserOptions) { if (!environmentConfig.sentry) { // If environment config is not specified but the above assertion passes, use runtime config. - environmentConfig.sentry = { ..._runtimeConfig } as any; + environmentConfig.sentry = { ..._runtimeConfig }; } // Merge runtime config into environment config, preferring runtime. @@ -62,12 +66,20 @@ export function InitSentryForEmber(_runtimeConfig?: BrowserOptions) { } } -export const getActiveTransaction = () => { +export const getActiveTransaction = (): Transaction | undefined => { return Sentry.getCurrentHub().getScope().getTransaction(); }; -export const instrumentRoutePerformance = (BaseRoute: any) => { - const instrumentFunction = async (op: string, description: string, fn: Function, args: any) => { +type RouteConstructor = new (...args: ConstructorParameters) => Route; + +export const instrumentRoutePerformance = (BaseRoute: T): T => { + // eslint-disable-next-line @typescript-eslint/no-explicit-any + const instrumentFunction = async any>( + op: string, + description: string, + fn: X, + args: Parameters, + ): Promise> => { const startTimestamp = timestampInSeconds(); const result = await fn(...args); @@ -79,40 +91,38 @@ export const instrumentRoutePerformance = (BaseRoute: any) => { return result; }; + const routeName = BaseRoute.name; + return { - [BaseRoute.name]: class extends BaseRoute { - beforeModel(...args: any[]) { + // @ts-expect-error TS2545 We do not need to redefine a constructor here + [routeName]: class extends BaseRoute { + public beforeModel(...args: unknown[]): void | Promise { return instrumentFunction( 'ui.ember.route.before_model', - (this).fullRouteName, + this.fullRouteName, super.beforeModel.bind(this), args, ); } - async model(...args: any[]) { - return instrumentFunction('ui.ember.route.model', (this).fullRouteName, super.model.bind(this), args); + public async model(...args: unknown[]): Promise { + return instrumentFunction('ui.ember.route.model', this.fullRouteName, super.model.bind(this), args); } - async afterModel(...args: any[]) { - return instrumentFunction( - 'ui.ember.route.after_model', - (this).fullRouteName, - super.afterModel.bind(this), - args, - ); + public afterModel(...args: unknown[]): void | Promise { + return instrumentFunction('ui.ember.route.after_model', this.fullRouteName, super.afterModel.bind(this), args); } - async setupController(...args: any[]) { + public setupController(...args: unknown[]): void | Promise { return instrumentFunction( 'ui.ember.route.setup_controller', - (this).fullRouteName, + this.fullRouteName, super.setupController.bind(this), args, ); } }, - }[BaseRoute.name]; + }[routeName] as T; }; export * from '@sentry/browser'; diff --git a/packages/ember/addon/instance-initializers/sentry-performance.ts b/packages/ember/addon/instance-initializers/sentry-performance.ts index 1e575f71fe76..2b962f4df2ae 100644 --- a/packages/ember/addon/instance-initializers/sentry-performance.ts +++ b/packages/ember/addon/instance-initializers/sentry-performance.ts @@ -1,18 +1,26 @@ -import ApplicationInstance from '@ember/application/instance'; -import { run, _backburner, scheduleOnce } from '@ember/runloop'; +/* eslint-disable max-lines */ +import type ApplicationInstance from '@ember/application/instance'; import { subscribe } from '@ember/instrumentation'; +import type Transition from '@ember/routing/-private/transition'; +import type RouterService from '@ember/routing/router-service'; +import { _backburner, run, scheduleOnce } from '@ember/runloop'; +import type { EmberRunQueues } from '@ember/runloop/-private/types'; +import { getOwnConfig, isTesting, macroCondition } from '@embroider/macros'; import * as Sentry from '@sentry/browser'; -import { ExtendedBackburner } from '@sentry/ember/runloop'; -import { Span, Transaction } from '@sentry/types'; -import { EmberRunQueues } from '@ember/runloop/-private/types'; -import { getActiveTransaction } from '..'; +import type { ExtendedBackburner } from '@sentry/ember/runloop'; +import type { Span, Transaction } from '@sentry/types'; import { browserPerformanceTimeOrigin, GLOBAL_OBJ, timestampInSeconds } from '@sentry/utils'; -import { macroCondition, isTesting, getOwnConfig } from '@embroider/macros'; -import { EmberSentryConfig, GlobalConfig, OwnConfig } from '../types'; -import RouterService from '@ember/routing/router-service'; -import type { BaseClient } from '@sentry/core'; -function getSentryConfig() { +import type { BrowserClient } from '..'; +import { getActiveTransaction } from '..'; +import type { EmberRouterMain, EmberSentryConfig, GlobalConfig, OwnConfig, StartTransactionFunction } from '../types'; + +type SentryTestRouterService = RouterService & { + _startTransaction?: StartTransactionFunction; + _sentryInstrumented?: boolean; +}; + +function getSentryConfig(): EmberSentryConfig { const _global = GLOBAL_OBJ as typeof GLOBAL_OBJ & GlobalConfig; _global.__sentryEmberConfig = _global.__sentryEmberConfig ?? {}; const environmentConfig = getOwnConfig().sentryConfig; @@ -27,7 +35,7 @@ function getSentryConfig() { export function initialize(appInstance: ApplicationInstance): void { // Disable in fastboot - we only want to run Sentry client-side - const fastboot = appInstance.lookup('service:fastboot') as { isFastBoot: boolean } | undefined; + const fastboot = appInstance.lookup('service:fastboot') as unknown as { isFastBoot: boolean } | undefined; if (fastboot?.isFastBoot) { return; } @@ -38,35 +46,42 @@ export function initialize(appInstance: ApplicationInstance): void { } const performancePromise = instrumentForPerformance(appInstance); if (macroCondition(isTesting())) { - (window)._sentryPerformanceLoad = performancePromise; + (window as typeof window & { _sentryPerformanceLoad?: Promise })._sentryPerformanceLoad = performancePromise; } } function getBackburner(): Pick { if (_backburner) { - return _backburner; + return _backburner as unknown as Pick; } - if (run.backburner) { - return run.backburner; + if ((run as unknown as { backburner?: Pick }).backburner) { + return (run as unknown as { backburner: Pick }).backburner; } return { - on() {}, - off() {}, + on() { + // noop + }, + off() { + // noop + }, }; } -function getTransitionInformation(transition: any, router: any) { +function getTransitionInformation( + transition: Transition | undefined, + router: RouterService, +): { fromRoute?: string; toRoute?: string } { const fromRoute = transition?.from?.name; - const toRoute = transition && transition.to ? transition.to.name : router.currentRouteName; + const toRoute = transition?.to?.name || router.currentRouteName; return { fromRoute, toRoute, }; } -function getLocationURL(location: any) { +function getLocationURL(location: EmberRouterMain['location']): string { if (!location || !location.getURL || !location.formatURL) { return ''; } @@ -79,22 +94,24 @@ function getLocationURL(location: any) { } export function _instrumentEmberRouter( - routerService: any, - routerMain: any, + routerService: RouterService, + routerMain: EmberRouterMain, config: EmberSentryConfig, - startTransaction: Function, + startTransaction: StartTransactionFunction, startTransactionOnPageLoad?: boolean, -) { +): { + startTransaction: StartTransactionFunction; +} { const { disableRunloopPerformance } = config; const location = routerMain.location; - let activeTransaction: Transaction; - let transitionSpan: Span; + let activeTransaction: Transaction | undefined; + let transitionSpan: Span | undefined; const url = getLocationURL(location); if (macroCondition(isTesting())) { - routerService._sentryInstrumented = true; - routerService._startTransaction = startTransaction; + (routerService as SentryTestRouterService)._sentryInstrumented = true; + (routerService as SentryTestRouterService)._startTransaction = startTransaction; } if (startTransactionOnPageLoad && url) { @@ -110,15 +127,15 @@ export function _instrumentEmberRouter( }); } - const finishActiveTransaction = function (_: any, nextInstance: any) { + const finishActiveTransaction = (_: unknown, nextInstance: unknown): void => { if (nextInstance) { return; } - activeTransaction.finish(); + activeTransaction?.finish(); getBackburner().off('end', finishActiveTransaction); }; - routerService.on('routeWillChange', (transition: any) => { + routerService.on('routeWillChange', (transition: Transition) => { const { fromRoute, toRoute } = getTransitionInformation(transition, routerService); activeTransaction?.finish(); activeTransaction = startTransaction({ @@ -130,7 +147,7 @@ export function _instrumentEmberRouter( 'routing.instrumentation': '@sentry/ember', }, }); - transitionSpan = activeTransaction.startChild({ + transitionSpan = activeTransaction?.startChild({ op: 'ui.ember.transition', description: `route:${fromRoute} -> route:${toRoute}`, }); @@ -155,7 +172,7 @@ export function _instrumentEmberRouter( }; } -function _instrumentEmberRunloop(config: EmberSentryConfig) { +function _instrumentEmberRunloop(config: EmberSentryConfig): void { const { disableRunloopPerformance, minimumRunloopQueueDuration } = config; if (disableRunloopPerformance) { return; @@ -171,7 +188,7 @@ function _instrumentEmberRunloop(config: EmberSentryConfig) { 'destroy', ] as EmberRunQueues[]; - getBackburner().on('begin', (_: any, previousInstance: any) => { + getBackburner().on('begin', (_: unknown, previousInstance: unknown) => { if (previousInstance) { return; } @@ -184,38 +201,38 @@ function _instrumentEmberRunloop(config: EmberSentryConfig) { } currentQueueStart = timestampInSeconds(); + const processQueue = (queue: EmberRunQueues): void => { + // Process this queue using the end of the previous queue. + if (currentQueueStart) { + const now = timestampInSeconds(); + const minQueueDuration = minimumRunloopQueueDuration ?? 5; + + if ((now - currentQueueStart) * 1000 >= minQueueDuration) { + activeTransaction + ?.startChild({ + op: `ui.ember.runloop.${queue}`, + startTimestamp: currentQueueStart, + endTimestamp: now, + }) + .finish(); + } + currentQueueStart = undefined; + } + + // Setup for next queue + + const stillActiveTransaction = getActiveTransaction(); + if (!stillActiveTransaction) { + return; + } + currentQueueStart = timestampInSeconds(); + }; + instrumentedEmberQueues.forEach(queue => { - scheduleOnce(queue, null, () => { - scheduleOnce(queue, null, () => { - // Process this queue using the end of the previous queue. - if (currentQueueStart) { - const now = timestampInSeconds(); - const minQueueDuration = minimumRunloopQueueDuration ?? 5; - - if ((now - currentQueueStart) * 1000 >= minQueueDuration) { - activeTransaction - ?.startChild({ - op: `ui.ember.runloop.${queue}`, - startTimestamp: currentQueueStart, - endTimestamp: now, - }) - .finish(); - } - currentQueueStart = undefined; - } - - // Setup for next queue - - const stillActiveTransaction = getActiveTransaction(); - if (!stillActiveTransaction) { - return; - } - currentQueueStart = timestampInSeconds(); - }); - }); + scheduleOnce(queue, null, processQueue, queue); }); }); - getBackburner().on('end', (_: any, nextInstance: any) => { + getBackburner().on('end', (_: unknown, nextInstance: unknown) => { if (nextInstance) { return; } @@ -241,7 +258,7 @@ interface RenderEntries { [name: string]: RenderEntry; } -function processComponentRenderBefore(payload: Payload, beforeEntries: RenderEntries) { +function processComponentRenderBefore(payload: Payload, beforeEntries: RenderEntries): void { const info = { payload, now: timestampInSeconds(), @@ -254,7 +271,7 @@ function processComponentRenderAfter( beforeEntries: RenderEntries, op: string, minComponentDuration: number, -) { +): void { const begin = beforeEntries[payload.object]; if (!begin) { @@ -276,7 +293,7 @@ function processComponentRenderAfter( } } -function _instrumentComponents(config: EmberSentryConfig) { +function _instrumentComponents(config: EmberSentryConfig): void { const { disableInstrumentComponents, minimumComponentRenderDuration, enableComponentDefinitions } = config; if (disableInstrumentComponents) { return; @@ -287,13 +304,13 @@ function _instrumentComponents(config: EmberSentryConfig) { const beforeEntries = {} as RenderEntries; const beforeComponentDefinitionEntries = {} as RenderEntries; - function _subscribeToRenderEvents() { + function _subscribeToRenderEvents(): void { subscribe('render.component', { before(_name: string, _timestamp: number, payload: Payload) { processComponentRenderBefore(payload, beforeEntries); }, - after(_name: string, _timestamp: number, payload: any, _beganIndex: number) { + after(_name: string, _timestamp: number, payload: Payload, _beganIndex: number) { processComponentRenderAfter(payload, beforeEntries, 'ui.ember.component.render', minComponentDuration); }, }); @@ -303,7 +320,7 @@ function _instrumentComponents(config: EmberSentryConfig) { processComponentRenderBefore(payload, beforeComponentDefinitionEntries); }, - after(_name: string, _timestamp: number, payload: any, _beganIndex: number) { + after(_name: string, _timestamp: number, payload: Payload, _beganIndex: number) { processComponentRenderAfter(payload, beforeComponentDefinitionEntries, 'ui.ember.component.definition', 0); }, }); @@ -312,11 +329,11 @@ function _instrumentComponents(config: EmberSentryConfig) { _subscribeToRenderEvents(); } -function _instrumentInitialLoad(config: EmberSentryConfig) { +function _instrumentInitialLoad(config: EmberSentryConfig): void { const startName = '@sentry/ember:initial-load-start'; const endName = '@sentry/ember:initial-load-end'; - let { HAS_PERFORMANCE, HAS_PERFORMANCE_TIMING } = _hasPerformanceSupport(); + const { HAS_PERFORMANCE, HAS_PERFORMANCE_TIMING } = _hasPerformanceSupport(); if (!HAS_PERFORMANCE) { return; @@ -331,7 +348,7 @@ function _instrumentInitialLoad(config: EmberSentryConfig) { } // Split performance check in two so clearMarks still happens even if timeOrigin isn't available. - if (!HAS_PERFORMANCE_TIMING) { + if (!HAS_PERFORMANCE_TIMING || browserPerformanceTimeOrigin === undefined) { return; } const measureName = '@sentry/ember:initial-load'; @@ -344,9 +361,10 @@ function _instrumentInitialLoad(config: EmberSentryConfig) { performance.measure(measureName, startName, endName); const measures = performance.getEntriesByName(measureName); - const measure = measures[0]; + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + const measure = measures[0]!; - const startTimestamp = (measure.startTime + browserPerformanceTimeOrigin!) / 1000; + const startTimestamp = (measure.startTime + browserPerformanceTimeOrigin) / 1000; const endTimestamp = startTimestamp + measure.duration / 1000; const transaction = getActiveTransaction(); @@ -361,7 +379,7 @@ function _instrumentInitialLoad(config: EmberSentryConfig) { performance.clearMeasures(measureName); } -function _hasPerformanceSupport() { +function _hasPerformanceSupport(): { HAS_PERFORMANCE: boolean; HAS_PERFORMANCE_TIMING: boolean } { // TS says that all of these methods are always available, but some of them may not be supported in older browsers // So we "pretend" they are all optional in order to be able to check this properly without TS complaining const _performance = window.performance as { @@ -381,9 +399,8 @@ function _hasPerformanceSupport() { }; } -export async function instrumentForPerformance(appInstance: ApplicationInstance) { +export async function instrumentForPerformance(appInstance: ApplicationInstance): Promise { const config = getSentryConfig(); - const sentryConfig = config.sentry; // Maintaining backwards compatibility with config.browserTracingOptions, but passing it with Sentry options is preferred. const browserTracingOptions = config.browserTracingOptions || config.sentry.browserTracingOptions || {}; @@ -393,7 +410,8 @@ export async function instrumentForPerformance(appInstance: ApplicationInstance) const browserTracing = new BrowserTracing({ routingInstrumentation: (customStartTransaction, startTransactionOnPageLoad) => { - const routerMain = appInstance.lookup('router:main'); + // eslint-disable-next-line ember/no-private-routing-service + const routerMain = appInstance.lookup('router:main') as EmberRouterMain; let routerService = appInstance.lookup('service:router') as | RouterService & { externalRouter?: RouterService; _hasMountedSentryPerformanceRouting?: boolean }; @@ -421,8 +439,8 @@ export async function instrumentForPerformance(appInstance: ApplicationInstance) if ( client && - (client as BaseClient).getIntegrationById && - (client as BaseClient).getIntegrationById('BrowserTracing') + (client as BrowserClient).getIntegrationById && + (client as BrowserClient).getIntegrationById('BrowserTracing') ) { // Initializers are called more than once in tests, causing the integrations to not be setup correctly. return; diff --git a/packages/ember/addon/runloop.d.ts b/packages/ember/addon/runloop.d.ts index 7d1fbcf5949d..2e2964487b69 100644 --- a/packages/ember/addon/runloop.d.ts +++ b/packages/ember/addon/runloop.d.ts @@ -1,10 +1,10 @@ -import { Backburner } from '@ember/runloop/-private/backburner'; +import type { Backburner } from '@ember/runloop/-private/backburner'; /** * Backburner needs to be extended as it's missing the 'off' method. */ interface ExtendedBackburner extends Backburner { - off(...args: any[]): void; + off(...args: unknown[]): void; } /** diff --git a/packages/ember/addon/types.ts b/packages/ember/addon/types.ts index 7490bb2d8129..787eecc7e4cf 100644 --- a/packages/ember/addon/types.ts +++ b/packages/ember/addon/types.ts @@ -1,7 +1,10 @@ -import { BrowserOptions } from '@sentry/browser'; +import type { BrowserOptions, BrowserTracing } from '@sentry/browser'; +import type { Transaction, TransactionContext } from '@sentry/types'; + +type BrowserTracingOptions = ConstructorParameters[0]; export type EmberSentryConfig = { - sentry: BrowserOptions & { browserTracingOptions: Object }; + sentry: BrowserOptions & { browserTracingOptions?: BrowserTracingOptions }; transitionTimeout: number; ignoreEmberOnErrorWarning: boolean; disableInstrumentComponents: boolean; @@ -12,13 +15,25 @@ export type EmberSentryConfig = { enableComponentDefinitions: boolean; minimumRunloopQueueDuration: number; minimumComponentRenderDuration: number; - browserTracingOptions: Object; + browserTracingOptions: BrowserTracingOptions; }; export type OwnConfig = { sentryConfig: EmberSentryConfig; }; +// This is private in Ember and not really exported, so we "mock" these types here. +export interface EmberRouterMain { + location: { + getURL?: () => string; + formatURL?: (url: string) => string; + implementation: string; + rootURL: string; + }; +} + +export type StartTransactionFunction = (context: TransactionContext) => Transaction | undefined; + export type GlobalConfig = { __sentryEmberConfig: EmberSentryConfig['sentry']; }; diff --git a/packages/ember/index.js b/packages/ember/index.js index 53a6fb2eded7..de05e5d6089f 100644 --- a/packages/ember/index.js +++ b/packages/ember/index.js @@ -33,6 +33,7 @@ module.exports = { const addonConfig = config['@sentry/ember'] || {}; if (!isSerializable(addonConfig)) { + // eslint-disable-next-line no-console console.warn( `Warning: You passed a non-serializable config to \`ENV['@sentry/ember'].sentry\`. Non-serializable config (e.g. RegExp, ...) can only be passed directly to \`Sentry.init()\`, which is usually defined in app/app.js. @@ -73,6 +74,7 @@ function isSerializable(obj) { } if (isPlainObject(obj)) { + // eslint-disable-next-line guard-for-in for (let property in obj) { let value = obj[property]; if (!isSerializable(value)) { diff --git a/packages/ember/package.json b/packages/ember/package.json index a396e5ebbba1..b2e1ed29d041 100644 --- a/packages/ember/package.json +++ b/packages/ember/package.json @@ -23,9 +23,14 @@ "lint:hbs": "ember-template-lint .", "lint:js": "eslint .", "lint:ts": "tsc", + "fix": "run-s fix:eslint fix:prettier", + "fix:eslint": "eslint . --format stylish --fix", + "fix:prettier": "prettier --write \"{addon,app,tests,config}/**/**.{ts,js}\"", "start": "ember serve", "test": "ember test", - "test:all": "ember try:each" + "test:all": "ember try:each", + "prepack": "ember ts:precompile", + "postpack": "ember ts:clean" }, "dependencies": { "@embroider/macros": "^1.9.0", @@ -39,14 +44,14 @@ }, "devDependencies": { "@ember/optional-features": "~1.3.0", - "@ember/test-helpers": "~2.8.1", + "@ember/test-helpers": "2.9.4", "@embroider/test-setup": "~1.8.3", "@glimmer/component": "~1.1.2", "@glimmer/tracking": "~1.1.2", "@types/ember": "~3.16.5", "@types/ember-qunit": "~3.4.9", "@types/ember__debug": "^3.16.5", - "@types/ember__test-helpers": "~1.7.0", + "@types/ember-resolver": "5.0.13", "@types/qunit": "~2.9.1", "@types/rsvp": "~4.0.3", "babel-eslint": "~10.1.0", @@ -69,9 +74,9 @@ "ember-test-selectors": "~6.0.0", "ember-try": "~2.0.0", "ember-window-mock": "~0.8.1", - "eslint-plugin-ember": "~11.1.0", - "eslint-plugin-node": "~11.1.0", - "eslint-plugin-qunit": "~7.3.1", + "eslint-plugin-ember": "11.9.0", + "eslint-plugin-n": "16.0.1", + "eslint-plugin-qunit": "8.0.0", "loader.js": "~4.7.0", "qunit": "~2.19.2", "qunit-dom": "~2.0.0", diff --git a/packages/ember/tests/acceptance/sentry-errors-test.js b/packages/ember/tests/acceptance/sentry-errors-test.js deleted file mode 100644 index 25f5510d34f7..000000000000 --- a/packages/ember/tests/acceptance/sentry-errors-test.js +++ /dev/null @@ -1,134 +0,0 @@ -import { test, module } from 'qunit'; -import { setupApplicationTest } from 'ember-qunit'; -import { find, click, visit } from '@ember/test-helpers'; -import { next } from '@ember/runloop'; -import { setupSentryTest } from '../helpers/setup-sentry'; - -const defaultAssertOptions = { - method: 'POST', - errorBodyContains: [], -}; - -function getTestSentryErrors() { - return window._sentryTestEvents.filter(event => event['type'] !== 'transaction'); -} - -function assertSentryErrorCount(assert, count) { - assert.equal(getTestSentryErrors().length, count, 'Check correct number of Sentry events were sent'); -} - -function assertSentryCall(assert, callNumber, options) { - const sentryTestEvents = getTestSentryErrors(); - const assertOptions = Object.assign({}, defaultAssertOptions, options); - - const event = sentryTestEvents[callNumber]; - - /** - * Body could be parsed here to check exact properties, but that requires too much implementation specific detail, - * instead this loosely matches on contents to check the correct error is being sent. - */ - assert.ok(assertOptions.errorBodyContains.length, 'Must pass strings to check against error body'); - const errorBody = JSON.stringify(event); - assertOptions.errorBodyContains.forEach(bodyContent => { - assert.ok(errorBody.includes(bodyContent), `Checking that error body includes ${bodyContent}`); - }); -} - -module('Acceptance | Sentry Errors', function (hooks) { - setupApplicationTest(hooks); - setupSentryTest(hooks); - - test('Check "Throw Generic Javascript Error"', async function (assert) { - assert.expect(3); - - await visit('/'); - const button = find('[data-test-button="Throw Generic Javascript Error"]'); - - await click(button); - - assertSentryErrorCount(assert, 1); - assertSentryCall(assert, 0, { errorBodyContains: [...this.errorMessages] }); - }); - - test('Check "Throw EmberError"', async function (assert) { - assert.expect(3); - - await visit('/'); - const button = find('[data-test-button="Throw EmberError"]'); - - await click(button); - - assertSentryErrorCount(assert, 1); - assertSentryCall(assert, 0, { errorBodyContains: [...this.errorMessages] }); - }); - - test('Check "Caught Thrown EmberError"', async function (assert) { - assert.expect(1); - - await visit('/'); - const button = find('[data-test-button="Caught Thrown EmberError"]'); - - await click(button); - - assertSentryErrorCount(assert, 0); - }); - - test('Check "Error From Fetch"', async function (assert) { - assert.expect(3); - - this.fetchStub.onFirstCall().callsFake((...args) => { - return this.fetchStub.callsThrough(args); - }); - await visit('/'); - const button = find('[data-test-button="Error From Fetch"]'); - - await click(button); - - const done = assert.async(); - - next(() => { - assertSentryErrorCount(assert, 1); - assertSentryCall(assert, 0, { errorBodyContains: [...this.errorMessages] }); - done(); - }); - }); - - test('Check "Error in AfterRender"', async function (assert) { - assert.expect(4); - - await visit('/'); - const button = find('[data-test-button="Error in AfterRender"]'); - - await click(button); - - assertSentryErrorCount(assert, 1); - assert.ok(this.qunitOnUnhandledRejection.calledOnce, 'Uncaught rejection should only be called once'); - assertSentryCall(assert, 0, { errorBodyContains: [...this.errorMessages] }); - }); - - test('Check "RSVP Rejection"', async function (assert) { - assert.expect(4); - - await visit('/'); - const button = find('[data-test-button="RSVP Rejection"]'); - - await click(button); - - assertSentryErrorCount(assert, 1); - assert.ok(this.qunitOnUnhandledRejection.calledOnce, 'Uncaught rejection should only be called once'); - assertSentryCall(assert, 0, { errorBodyContains: [this.qunitOnUnhandledRejection.getCall(0).args[0]] }); - }); - - test('Check "Error inside RSVP"', async function (assert) { - assert.expect(4); - - await visit('/'); - const button = find('[data-test-button="Error inside RSVP"]'); - - await click(button); - - assertSentryErrorCount(assert, 1); - assert.ok(this.qunitOnUnhandledRejection.calledOnce, 'Uncaught rejection should only be called once'); - assertSentryCall(assert, 0, { errorBodyContains: [...this.errorMessages] }); - }); -}); diff --git a/packages/ember/tests/acceptance/sentry-errors-test.ts b/packages/ember/tests/acceptance/sentry-errors-test.ts new file mode 100644 index 000000000000..193f56e495cf --- /dev/null +++ b/packages/ember/tests/acceptance/sentry-errors-test.ts @@ -0,0 +1,101 @@ +import { next } from '@ember/runloop'; +import { click, visit } from '@ember/test-helpers'; +import { setupApplicationTest } from 'ember-qunit'; +import { module, test } from 'qunit'; + +import type { SentryTestContext } from '../helpers/setup-sentry'; +import { setupSentryTest } from '../helpers/setup-sentry'; +import { assertSentryErrorCount, assertSentryErrors } from '../helpers/utils'; + +module('Acceptance | Sentry Errors', function (hooks) { + setupApplicationTest(hooks); + setupSentryTest(hooks); + + test('Check "Throw Generic Javascript Error"', async function (this: SentryTestContext, assert) { + assert.expect(3); + + await visit('/'); + + await click('[data-test-button="Throw Generic Javascript Error"]'); + + assertSentryErrorCount(assert, 1); + assertSentryErrors(assert, 0, { errorBodyContains: [...this.errorMessages] }); + }); + + test('Check "Throw EmberError"', async function (this: SentryTestContext, assert) { + assert.expect(3); + + await visit('/'); + + await click('[data-test-button="Throw EmberError"]'); + + assertSentryErrorCount(assert, 1); + assertSentryErrors(assert, 0, { errorBodyContains: [...this.errorMessages] }); + }); + + test('Check "Caught Thrown EmberError"', async function (this: SentryTestContext, assert) { + assert.expect(1); + + await visit('/'); + + await click('[data-test-button="Caught Thrown EmberError"]'); + + assertSentryErrorCount(assert, 0); + }); + + test('Check "Error From Fetch"', async function (this: SentryTestContext, assert) { + assert.expect(3); + + this.fetchStub.onFirstCall().callsFake(() => { + throw new Error('Test error...'); + }); + + await visit('/'); + + await click('[data-test-button="Error From Fetch"]'); + + const done = assert.async(); + + next(() => { + assertSentryErrorCount(assert, 1); + assertSentryErrors(assert, 0, { errorBodyContains: ['Test error...'] }); + done(); + }); + }); + + test('Check "Error in AfterRender"', async function (this: SentryTestContext, assert) { + assert.expect(4); + + await visit('/'); + + await click('[data-test-button="Error in AfterRender"]'); + + assertSentryErrorCount(assert, 1); + assert.ok(this.qunitOnUnhandledRejection.calledOnce, 'Uncaught rejection should only be called once'); + assertSentryErrors(assert, 0, { errorBodyContains: [...this.errorMessages] }); + }); + + test('Check "RSVP Rejection"', async function (this: SentryTestContext, assert) { + assert.expect(4); + + await visit('/'); + + await click('[data-test-button="RSVP Rejection"]'); + + assertSentryErrorCount(assert, 1); + assert.ok(this.qunitOnUnhandledRejection.calledOnce, 'Uncaught rejection should only be called once'); + assertSentryErrors(assert, 0, { errorBodyContains: [this.qunitOnUnhandledRejection.getCall(0).args[0]] }); + }); + + test('Check "Error inside RSVP"', async function (this: SentryTestContext, assert) { + assert.expect(4); + + await visit('/'); + + await click('[data-test-button="Error inside RSVP"]'); + + assertSentryErrorCount(assert, 1); + assert.ok(this.qunitOnUnhandledRejection.calledOnce, 'Uncaught rejection should only be called once'); + assertSentryErrors(assert, 0, { errorBodyContains: [...this.errorMessages] }); + }); +}); diff --git a/packages/ember/tests/acceptance/sentry-performance-test.js b/packages/ember/tests/acceptance/sentry-performance-test.js deleted file mode 100644 index a1be11440217..000000000000 --- a/packages/ember/tests/acceptance/sentry-performance-test.js +++ /dev/null @@ -1,104 +0,0 @@ -import { test, module } from 'qunit'; -import { setupApplicationTest } from 'ember-qunit'; -import { find, click, visit } from '@ember/test-helpers'; -import { setupSentryTest } from '../helpers/setup-sentry'; - -const SLOW_TRANSITION_WAIT = 3000; - -function getTestSentryTransactions() { - return window._sentryTestEvents.filter(event => event['type'] === 'transaction'); -} - -function assertSentryTransactionCount(assert, count) { - assert.equal(getTestSentryTransactions().length, count, 'Check correct number of Sentry events were sent'); -} - -function assertSentryCall(assert, callNumber, options) { - const sentryTestEvents = getTestSentryTransactions(); - const event = sentryTestEvents[callNumber]; - - assert.ok(options.spanCount || options.spans, 'Must add spanCount or spans to assertion'); - if (options.spanCount) { - assert.equal(event.spans.length, options.spanCount); - } - if (options.spans) { - // instead of checking the specific order of runloop spans (which is brittle), - // we check (below) that _any_ runloop spans are added - const spans = event.spans - .filter(span => !span.op.startsWith('ui.ember.runloop.')) - .map(s => { - return `${s.op} | ${s.description}`; - }); - - assert.true( - event.spans.some(span => span.op.startsWith('ui.ember.runloop.')), - 'it captures runloop spans', - ); - assert.deepEqual(spans, options.spans, `Has correct spans`); - } - - assert.equal(event.transaction, options.transaction); - assert.equal(event.tags.fromRoute, options.tags.fromRoute); - assert.equal(event.tags.toRoute, options.tags.toRoute); - - if (options.durationCheck) { - const duration = (event.timestamp - event.start_timestamp) * 1000; - assert.ok(options.durationCheck(duration), `duration (${duration}ms) passes duration check`); - } -} - -module('Acceptance | Sentry Performance', function (hooks) { - setupApplicationTest(hooks); - setupSentryTest(hooks); - - test('Test transaction', async function (assert) { - assert.expect(7); - - await visit('/tracing'); - - assertSentryTransactionCount(assert, 1); - assertSentryCall(assert, 0, { - spans: [ - 'ui.ember.transition | route:undefined -> route:tracing', - 'ui.ember.component.render | component:test-section', - ], - transaction: 'route:tracing', - tags: { - fromRoute: undefined, - toRoute: 'tracing', - }, - }); - }); - - test('Test navigating to slow route', async function (assert) { - assert.expect(8); - - await visit('/tracing'); - const button = find('[data-test-button="Transition to slow loading route"]'); - - await click(button); - - assertSentryTransactionCount(assert, 2); - assertSentryCall(assert, 1, { - spans: [ - 'ui.ember.transition | route:tracing -> route:slow-loading-route.index', - 'ui.ember.route.before_model | slow-loading-route', - 'ui.ember.route.model | slow-loading-route', - 'ui.ember.route.after_model | slow-loading-route', - 'ui.ember.route.before_model | slow-loading-route.index', - 'ui.ember.route.model | slow-loading-route.index', - 'ui.ember.route.after_model | slow-loading-route.index', - 'ui.ember.route.setup_controller | slow-loading-route', - 'ui.ember.route.setup_controller | slow-loading-route.index', - 'ui.ember.component.render | component:slow-loading-list', - 'ui.ember.component.render | component:slow-loading-list', - ], - transaction: 'route:slow-loading-route.index', - durationCheck: duration => duration > SLOW_TRANSITION_WAIT, - tags: { - fromRoute: 'tracing', - toRoute: 'slow-loading-route.index', - }, - }); - }); -}); diff --git a/packages/ember/tests/acceptance/sentry-performance-test.ts b/packages/ember/tests/acceptance/sentry-performance-test.ts new file mode 100644 index 000000000000..7ee5ccfcc14a --- /dev/null +++ b/packages/ember/tests/acceptance/sentry-performance-test.ts @@ -0,0 +1,59 @@ +import { click, visit } from '@ember/test-helpers'; +import { setupApplicationTest } from 'ember-qunit'; +import { module, test } from 'qunit'; + +import { setupSentryTest } from '../helpers/setup-sentry'; +import { assertSentryTransactionCount, assertSentryTransactions } from '../helpers/utils'; + +const SLOW_TRANSITION_WAIT = 3000; + +module('Acceptance | Sentry Performance', function (hooks) { + setupApplicationTest(hooks); + setupSentryTest(hooks); + + test('Test transaction', async function (assert) { + await visit('/tracing'); + + assertSentryTransactionCount(assert, 1); + assertSentryTransactions(assert, 0, { + spans: [ + 'ui.ember.transition | route:undefined -> route:tracing', + 'ui.ember.component.render | component:test-section', + ], + transaction: 'route:tracing', + tags: { + fromRoute: undefined, + toRoute: 'tracing', + }, + }); + }); + + test('Test navigating to slow route', async function (assert) { + await visit('/tracing'); + + await click('[data-test-button="Transition to slow loading route"]'); + + assertSentryTransactionCount(assert, 2); + assertSentryTransactions(assert, 1, { + spans: [ + 'ui.ember.transition | route:tracing -> route:slow-loading-route.index', + 'ui.ember.route.before_model | slow-loading-route', + 'ui.ember.route.model | slow-loading-route', + 'ui.ember.route.after_model | slow-loading-route', + 'ui.ember.route.before_model | slow-loading-route.index', + 'ui.ember.route.model | slow-loading-route.index', + 'ui.ember.route.after_model | slow-loading-route.index', + 'ui.ember.route.setup_controller | slow-loading-route', + 'ui.ember.route.setup_controller | slow-loading-route.index', + 'ui.ember.component.render | component:slow-loading-list', + 'ui.ember.component.render | component:slow-loading-list', + ], + transaction: 'route:slow-loading-route.index', + durationCheck: duration => duration > SLOW_TRANSITION_WAIT, + tags: { + fromRoute: 'tracing', + toRoute: 'slow-loading-route.index', + }, + }); + }); +}); diff --git a/packages/ember/tests/acceptance/sentry-replay-test.js b/packages/ember/tests/acceptance/sentry-replay-test.ts similarity index 51% rename from packages/ember/tests/acceptance/sentry-replay-test.js rename to packages/ember/tests/acceptance/sentry-replay-test.ts index f6c7272a7e62..1614dc22817a 100644 --- a/packages/ember/tests/acceptance/sentry-replay-test.js +++ b/packages/ember/tests/acceptance/sentry-replay-test.ts @@ -1,8 +1,10 @@ -import { test, module } from 'qunit'; -import { setupApplicationTest } from 'ember-qunit'; import { visit } from '@ember/test-helpers'; -import { setupSentryTest } from '../helpers/setup-sentry'; import * as Sentry from '@sentry/ember'; +import type { ReplayContainer } from '@sentry/replay/build/npm/types/types'; +import { setupApplicationTest } from 'ember-qunit'; +import { module, test } from 'qunit'; + +import { setupSentryTest } from '../helpers/setup-sentry'; module('Acceptance | Sentry Session Replay', function (hooks) { setupApplicationTest(hooks); @@ -11,10 +13,12 @@ module('Acceptance | Sentry Session Replay', function (hooks) { test('Test replay', async function (assert) { await visit('/replay'); - const replay = Sentry.getCurrentHub().getIntegration(Sentry.Replay); - assert.ok(replay); + const integration = Sentry.getCurrentHub().getIntegration(Sentry.Replay); + assert.ok(integration); + + const replay = (integration as Sentry.Replay)['_replay'] as ReplayContainer; - assert.true(replay._replay.isEnabled()); - assert.false(replay._replay.isPaused()); + assert.true(replay.isEnabled()); + assert.false(replay.isPaused()); }); }); diff --git a/packages/ember/tests/dummy/app/app.js b/packages/ember/tests/dummy/app/app.ts similarity index 60% rename from packages/ember/tests/dummy/app/app.js rename to packages/ember/tests/dummy/app/app.ts index 322931b8d45a..2ff7875df400 100644 --- a/packages/ember/tests/dummy/app/app.js +++ b/packages/ember/tests/dummy/app/app.ts @@ -1,24 +1,19 @@ import Application from '@ember/application'; -import Resolver from 'ember-resolver'; +import * as Sentry from '@sentry/ember'; import loadInitializers from 'ember-load-initializers'; +import Resolver from 'ember-resolver'; + import config from './config/environment'; -import * as Sentry from '@sentry/ember'; Sentry.init({ replaysSessionSampleRate: 1, replaysOnErrorSampleRate: 1, - browserTracingOptions: { - _experiments: { - // This lead to some flaky tests, as that is sometimes logged - enableLongTask: false, - }, - }, }); export default class App extends Application { - modulePrefix = config.modulePrefix; - podModulePrefix = config.podModulePrefix; - Resolver = Resolver; + public modulePrefix = config.modulePrefix; + public podModulePrefix = config.podModulePrefix; + public Resolver = Resolver; } loadInitializers(App, config.modulePrefix); diff --git a/packages/ember/tests/dummy/app/components/link.ts b/packages/ember/tests/dummy/app/components/link.ts index e67158350840..1ba66df216fc 100644 --- a/packages/ember/tests/dummy/app/components/link.ts +++ b/packages/ember/tests/dummy/app/components/link.ts @@ -1,7 +1,7 @@ -import Component from '@glimmer/component'; -import RouterService from '@ember/routing/router-service'; -import { inject as service } from '@ember/service'; import { action } from '@ember/object'; +import type RouterService from '@ember/routing/router-service'; +import { inject as service } from '@ember/service'; +import Component from '@glimmer/component'; interface Args { route: string; @@ -14,20 +14,20 @@ interface Args { Since glimmer components are, as of now, not instrumented, this leads to different test results. */ export default class LinkComponent extends Component { - @service router: RouterService; + @service public declare router: RouterService; - get href() { + public get href(): string { return this.router.urlFor(this.args.route); } - get isActive() { + public get isActive(): boolean { return this.router.currentRouteName === this.args.route; } @action - onClick(event: MouseEvent) { + public onClick(event: MouseEvent): void { event.preventDefault(); - this.router.transitionTo(this.args.route); + void this.router.transitionTo(this.args.route); } } diff --git a/packages/ember/tests/dummy/app/components/slow-loading-gc-list.ts b/packages/ember/tests/dummy/app/components/slow-loading-gc-list.ts index c5205d47e4f6..3ac89dc43ca7 100644 --- a/packages/ember/tests/dummy/app/components/slow-loading-gc-list.ts +++ b/packages/ember/tests/dummy/app/components/slow-loading-gc-list.ts @@ -1,3 +1,4 @@ +/* eslint-disable ember/no-empty-glimmer-component-classes */ import Component from '@glimmer/component'; export default class SlowLoadingGCList extends Component {} diff --git a/packages/ember/tests/dummy/app/components/slow-loading-list.ts b/packages/ember/tests/dummy/app/components/slow-loading-list.ts index 9ef19af6da88..e766fe78609f 100644 --- a/packages/ember/tests/dummy/app/components/slow-loading-list.ts +++ b/packages/ember/tests/dummy/app/components/slow-loading-list.ts @@ -1,12 +1,22 @@ +/* eslint-disable ember/no-classic-classes */ +/* eslint-disable ember/no-classic-components */ import Component from '@ember/component'; import { computed } from '@ember/object'; +interface Args { + title?: string; + items: number; +} + export default Component.extend({ + tagName: '', + _title: computed('title', function () { - return this.title || 'Slow Loading List'; + return (this as Args).title || 'Slow Loading List'; }), + rowItems: computed('items', function () { - return new Array(parseInt(this.items)).fill(0).map((_, index) => { + return new Array((this as Args).items).fill(0).map((_, index) => { return { index: index + 1, }; diff --git a/packages/ember/tests/dummy/app/components/test-section.ts b/packages/ember/tests/dummy/app/components/test-section.ts index 55706477346c..d0ca7e8edabc 100644 --- a/packages/ember/tests/dummy/app/components/test-section.ts +++ b/packages/ember/tests/dummy/app/components/test-section.ts @@ -1,3 +1,6 @@ +/* eslint-disable ember/no-classic-classes */ +/* eslint-disable ember/no-classic-components */ +/* eslint-disable ember/require-tagless-components */ import Component from '@ember/component'; export default Component.extend({}); diff --git a/packages/ember/tests/dummy/app/config/environment.d.ts b/packages/ember/tests/dummy/app/config/environment.d.ts index 3252cc3dec43..8a8a687909e4 100644 --- a/packages/ember/tests/dummy/app/config/environment.d.ts +++ b/packages/ember/tests/dummy/app/config/environment.d.ts @@ -1,5 +1,3 @@ -export default config; - /** * Type declarations for * import config from './config/environment' @@ -8,9 +6,12 @@ export default config; * since different ember addons can materialize new entries. */ declare const config: { - environment: any; + environment: string; modulePrefix: string; podModulePrefix: string; - locationType: string; + locationType: 'history' | 'hash' | 'none' | 'auto'; rootURL: string; + APP: Record; }; + +export default config; diff --git a/packages/ember/tests/dummy/app/controllers/application.js b/packages/ember/tests/dummy/app/controllers/application.js deleted file mode 100644 index 304707936f81..000000000000 --- a/packages/ember/tests/dummy/app/controllers/application.js +++ /dev/null @@ -1,3 +0,0 @@ -import Controller from '@ember/controller'; - -export default class ApplicationController extends Controller {} diff --git a/packages/ember/tests/dummy/app/controllers/index.js b/packages/ember/tests/dummy/app/controllers/index.ts similarity index 54% rename from packages/ember/tests/dummy/app/controllers/index.js rename to packages/ember/tests/dummy/app/controllers/index.ts index a1e01de99a8c..bd28b635e1a1 100644 --- a/packages/ember/tests/dummy/app/controllers/index.js +++ b/packages/ember/tests/dummy/app/controllers/index.ts @@ -1,63 +1,64 @@ import Controller from '@ember/controller'; -import { tracked } from '@glimmer/tracking'; -import { action } from '@ember/object'; import EmberError from '@ember/error'; +import { action } from '@ember/object'; import { scheduleOnce } from '@ember/runloop'; -import RSVP from 'rsvp'; +import { tracked } from '@glimmer/tracking'; +import { Promise } from 'rsvp'; export default class IndexController extends Controller { - @tracked showComponents; + @tracked public showComponents = false; @action - createError() { + public createError(): void { + // @ts-expect-error this is fine this.nonExistentFunction(); } @action - createEmberError() { + public createEmberError(): void { throw new EmberError('Whoops, looks like you have an EmberError'); } @action - createCaughtEmberError() { + public createCaughtEmberError(): void { try { throw new EmberError('Looks like you have a caught EmberError'); } catch (e) { - console.log(e); + // do nothing } } @action - createFetchError() { - fetch('http://doesntexist.example'); + public createFetchError(): void { + void fetch('http://doesntexist.example'); } @action - createAfterRenderError() { - function throwAfterRender() { + public createAfterRenderError(): void { + function throwAfterRender(): void { throw new Error('After Render Error'); } - scheduleOnce('afterRender', throwAfterRender); + scheduleOnce('afterRender', null, throwAfterRender); } @action - createRSVPRejection() { - const promise = new RSVP.Promise((resolve, reject) => { + public createRSVPRejection(): Promise { + const promise = new Promise((resolve, reject) => { reject('Promise rejected'); }); return promise; } @action - createRSVPError() { - const promise = new RSVP.Promise(() => { + public createRSVPError(): Promise { + const promise = new Promise(() => { throw new Error('Error within RSVP Promise'); }); return promise; } @action - toggleShowComponents() { + public toggleShowComponents(): void { this.showComponents = !this.showComponents; } } diff --git a/packages/ember/tests/dummy/app/controllers/slow-loading-route.js b/packages/ember/tests/dummy/app/controllers/slow-loading-route.js deleted file mode 100644 index afe644bffd41..000000000000 --- a/packages/ember/tests/dummy/app/controllers/slow-loading-route.js +++ /dev/null @@ -1,9 +0,0 @@ -import Controller from '@ember/controller'; -import { action } from '@ember/object'; - -export default class SlowLoadingRouteController extends Controller { - @action - back() { - this.transitionToRoute('tracing'); - } -} diff --git a/packages/ember/tests/dummy/app/controllers/slow-loading-route.ts b/packages/ember/tests/dummy/app/controllers/slow-loading-route.ts new file mode 100644 index 000000000000..01a523ea0985 --- /dev/null +++ b/packages/ember/tests/dummy/app/controllers/slow-loading-route.ts @@ -0,0 +1,13 @@ +import Controller from '@ember/controller'; +import { action } from '@ember/object'; +import type RouterService from '@ember/routing/router-service'; +import { inject as service } from '@ember/service'; + +export default class SlowLoadingRouteController extends Controller { + @service public declare router: RouterService; + + @action + public back(): void { + void this.router.transitionTo('tracing'); + } +} diff --git a/packages/ember/tests/dummy/app/controllers/slow-loading-route/index.js b/packages/ember/tests/dummy/app/controllers/slow-loading-route/index.ts similarity index 56% rename from packages/ember/tests/dummy/app/controllers/slow-loading-route/index.js rename to packages/ember/tests/dummy/app/controllers/slow-loading-route/index.ts index 2c712dd18032..b66350b5c911 100644 --- a/packages/ember/tests/dummy/app/controllers/slow-loading-route/index.js +++ b/packages/ember/tests/dummy/app/controllers/slow-loading-route/index.ts @@ -1,5 +1,5 @@ import Controller from '@ember/controller'; export default class SlowLoadingRouteController extends Controller { - slowLoadingTemplateOnlyItems = new Array(2000).fill(0).map((_, index) => index); + public slowLoadingTemplateOnlyItems = new Array(2000).fill(0).map((_, index) => index); } diff --git a/packages/ember/tests/dummy/app/controllers/tracing.js b/packages/ember/tests/dummy/app/controllers/tracing.js deleted file mode 100644 index 539a989ac829..000000000000 --- a/packages/ember/tests/dummy/app/controllers/tracing.js +++ /dev/null @@ -1,9 +0,0 @@ -import Controller from '@ember/controller'; -import { action } from '@ember/object'; - -export default class TracingController extends Controller { - @action - navigateToSlowRoute() { - return this.transitionToRoute('slow-loading-route'); - } -} diff --git a/packages/ember/tests/dummy/app/controllers/tracing.ts b/packages/ember/tests/dummy/app/controllers/tracing.ts new file mode 100644 index 000000000000..72c0d635702e --- /dev/null +++ b/packages/ember/tests/dummy/app/controllers/tracing.ts @@ -0,0 +1,13 @@ +import Controller from '@ember/controller'; +import { action } from '@ember/object'; +import type RouterService from '@ember/routing/router-service'; +import { inject as service } from '@ember/service'; + +export default class TracingController extends Controller { + @service public declare router: RouterService; + + @action + public navigateToSlowRoute(): void { + void this.router.transitionTo('slow-loading-route'); + } +} diff --git a/packages/ember/tests/dummy/app/helpers/utils.js b/packages/ember/tests/dummy/app/helpers/utils.js deleted file mode 100644 index 7aa94999942c..000000000000 --- a/packages/ember/tests/dummy/app/helpers/utils.js +++ /dev/null @@ -1,3 +0,0 @@ -export default function timeout(time) { - return new Promise(resolve => setTimeout(resolve, time)); -} diff --git a/packages/ember/tests/dummy/app/helpers/utils.ts b/packages/ember/tests/dummy/app/helpers/utils.ts new file mode 100644 index 000000000000..60a3f2956224 --- /dev/null +++ b/packages/ember/tests/dummy/app/helpers/utils.ts @@ -0,0 +1,3 @@ +export default function timeout(time: number): Promise { + return new Promise(resolve => setTimeout(resolve, time)); +} diff --git a/packages/ember/tests/dummy/app/initializers/deprecation.js b/packages/ember/tests/dummy/app/initializers/deprecation.ts similarity index 79% rename from packages/ember/tests/dummy/app/initializers/deprecation.js rename to packages/ember/tests/dummy/app/initializers/deprecation.ts index 33c16ee62d4e..190b1a932397 100644 --- a/packages/ember/tests/dummy/app/initializers/deprecation.js +++ b/packages/ember/tests/dummy/app/initializers/deprecation.ts @@ -1,10 +1,11 @@ import { registerDeprecationHandler } from '@ember/debug'; -export function initialize() { +export function initialize(): void { registerDeprecationHandler((message, options, next) => { if (options && options.until && options.until !== '3.0.0') { return; } else { + // @ts-expect-error this is fine next(message, options); } }); diff --git a/packages/ember/tests/dummy/app/models/.gitkeep b/packages/ember/tests/dummy/app/models/.gitkeep deleted file mode 100644 index e69de29bb2d1..000000000000 diff --git a/packages/ember/tests/dummy/app/router.js b/packages/ember/tests/dummy/app/router.ts similarity index 64% rename from packages/ember/tests/dummy/app/router.js rename to packages/ember/tests/dummy/app/router.ts index 51b41107869e..e13dec6d82c5 100644 --- a/packages/ember/tests/dummy/app/router.js +++ b/packages/ember/tests/dummy/app/router.ts @@ -1,11 +1,14 @@ import EmberRouter from '@ember/routing/router'; + import config from './config/environment'; export default class Router extends EmberRouter { - location = config.locationType; - rootURL = config.rootURL; + public location = config.locationType; + public rootURL = config.rootURL; } +// This is a false positive of the eslint rule +// eslint-disable-next-line array-callback-return Router.map(function () { this.route('tracing'); this.route('replay'); diff --git a/packages/ember/tests/dummy/app/routes/replay.js b/packages/ember/tests/dummy/app/routes/replay.ts similarity index 61% rename from packages/ember/tests/dummy/app/routes/replay.js rename to packages/ember/tests/dummy/app/routes/replay.ts index ed3a95e9cee7..9702fe8aa1af 100644 --- a/packages/ember/tests/dummy/app/routes/replay.js +++ b/packages/ember/tests/dummy/app/routes/replay.ts @@ -1,12 +1,13 @@ import Route from '@ember/routing/route'; +import type { BrowserClient } from '@sentry/ember'; import * as Sentry from '@sentry/ember'; export default class ReplayRoute extends Route { - async beforeModel() { + public async beforeModel(): Promise { const { Replay } = Sentry; if (!Sentry.getCurrentHub().getIntegration(Replay)) { - const client = Sentry.getCurrentHub().getClient(); + const client = Sentry.getCurrentHub().getClient() as BrowserClient; client.addIntegration(new Replay()); } } diff --git a/packages/ember/tests/dummy/app/routes/slow-loading-route.js b/packages/ember/tests/dummy/app/routes/slow-loading-route.ts similarity index 63% rename from packages/ember/tests/dummy/app/routes/slow-loading-route.js rename to packages/ember/tests/dummy/app/routes/slow-loading-route.ts index a28b0fd7eff6..96f57bd9cf2d 100644 --- a/packages/ember/tests/dummy/app/routes/slow-loading-route.js +++ b/packages/ember/tests/dummy/app/routes/slow-loading-route.ts @@ -1,24 +1,25 @@ import Route from '@ember/routing/route'; -import timeout from '../helpers/utils'; import { instrumentRoutePerformance } from '@sentry/ember'; +import timeout from '../helpers/utils'; + const SLOW_TRANSITION_WAIT = 1500; class SlowDefaultLoadingRoute extends Route { - beforeModel() { + public beforeModel(): Promise { return timeout(SLOW_TRANSITION_WAIT / 3); } - model() { + public model(): Promise { return timeout(SLOW_TRANSITION_WAIT / 3); } - afterModel() { + public afterModel(): Promise { return timeout(SLOW_TRANSITION_WAIT / 3); } - setupController() { - super.setupController(); + public setupController(...rest: Parameters): ReturnType { + super.setupController(...rest); } } diff --git a/packages/ember/tests/dummy/app/routes/slow-loading-route/index.js b/packages/ember/tests/dummy/app/routes/slow-loading-route/index.ts similarity index 62% rename from packages/ember/tests/dummy/app/routes/slow-loading-route/index.js rename to packages/ember/tests/dummy/app/routes/slow-loading-route/index.ts index 658fe8680bc8..c810ca5e2505 100644 --- a/packages/ember/tests/dummy/app/routes/slow-loading-route/index.js +++ b/packages/ember/tests/dummy/app/routes/slow-loading-route/index.ts @@ -1,24 +1,25 @@ import Route from '@ember/routing/route'; -import timeout from '../../helpers/utils'; import { instrumentRoutePerformance } from '@sentry/ember'; +import timeout from '../../helpers/utils'; + const SLOW_TRANSITION_WAIT = 1500; class SlowLoadingRoute extends Route { - beforeModel() { + public beforeModel(): Promise { return timeout(SLOW_TRANSITION_WAIT / 3); } - model() { + public model(): Promise { return timeout(SLOW_TRANSITION_WAIT / 3); } - afterModel() { + public afterModel(): Promise { return timeout(SLOW_TRANSITION_WAIT / 3); } - setupController() { - super.setupController(); + public setupController(...rest: Parameters): ReturnType { + super.setupController(...rest); } } diff --git a/packages/ember/tests/dummy/config/environment.js b/packages/ember/tests/dummy/config/environment.js index d80432e025b6..70c45d17ff0c 100644 --- a/packages/ember/tests/dummy/config/environment.js +++ b/packages/ember/tests/dummy/config/environment.js @@ -26,6 +26,10 @@ module.exports = function (environment) { dsn: process.env.SENTRY_DSN || 'https://0@0.ingest.sentry.io/0', browserTracingOptions: { tracingOrigins: ['localhost', 'doesntexist.example'], + _experiments: { + // This lead to some flaky tests, as that is sometimes logged + enableLongTask: false, + }, }, }, ignoreEmberOnErrorWarning: true, diff --git a/packages/ember/tests/dummy/constants.js b/packages/ember/tests/dummy/constants.ts similarity index 100% rename from packages/ember/tests/dummy/constants.js rename to packages/ember/tests/dummy/constants.ts diff --git a/packages/ember/tests/helpers/index.js b/packages/ember/tests/helpers/index.js deleted file mode 100644 index 7f70de80f4d4..000000000000 --- a/packages/ember/tests/helpers/index.js +++ /dev/null @@ -1,42 +0,0 @@ -import { - setupApplicationTest as upstreamSetupApplicationTest, - setupRenderingTest as upstreamSetupRenderingTest, - setupTest as upstreamSetupTest, -} from 'ember-qunit'; - -// This file exists to provide wrappers around ember-qunit's / ember-mocha's -// test setup functions. This way, you can easily extend the setup that is -// needed per test type. - -function setupApplicationTest(hooks, options) { - upstreamSetupApplicationTest(hooks, options); - - // Additional setup for application tests can be done here. - // - // For example, if you need an authenticated session for each - // application test, you could do: - // - // hooks.beforeEach(async function () { - // await authenticateSession(); // ember-simple-auth - // }); - // - // This is also a good place to call test setup functions coming - // from other addons: - // - // setupIntl(hooks); // ember-intl - // setupMirage(hooks); // ember-cli-mirage -} - -function setupRenderingTest(hooks, options) { - upstreamSetupRenderingTest(hooks, options); - - // Additional setup for rendering tests can be done here. -} - -function setupTest(hooks, options) { - upstreamSetupTest(hooks, options); - - // Additional setup for unit tests can be done here. -} - -export { setupApplicationTest, setupRenderingTest, setupTest }; diff --git a/packages/ember/tests/helpers/setup-sentry.js b/packages/ember/tests/helpers/setup-sentry.ts similarity index 53% rename from packages/ember/tests/helpers/setup-sentry.js rename to packages/ember/tests/helpers/setup-sentry.ts index a9e241c5d41a..d8bb513dcd00 100644 --- a/packages/ember/tests/helpers/setup-sentry.js +++ b/packages/ember/tests/helpers/setup-sentry.ts @@ -1,26 +1,41 @@ -import sinon from 'sinon'; -import { _instrumentEmberRouter } from '@sentry/ember/instance-initializers/sentry-performance'; +import type RouterService from '@ember/routing/router-service'; +import type { TestContext } from '@ember/test-helpers'; import { resetOnerror, setupOnerror } from '@ember/test-helpers'; +import { _instrumentEmberRouter } from '@sentry/ember/instance-initializers/sentry-performance'; +import type { EmberRouterMain, EmberSentryConfig, StartTransactionFunction } from '@sentry/ember/types'; +import sinon from 'sinon'; // Keep a reference to the original startTransaction as the application gets re-initialized and setup for // the integration doesn't occur again after the first time. -let _routerStartTransaction; +let _routerStartTransaction: StartTransactionFunction | undefined; + +export type SentryTestContext = TestContext & { + errorMessages: string[]; + fetchStub: sinon.SinonStub; + qunitOnUnhandledRejection: sinon.SinonStub; + _windowOnError: OnErrorEventHandler; +}; + +type SentryRouterService = RouterService & { + _startTransaction: StartTransactionFunction; + _sentryInstrumented?: boolean; +}; -export function setupSentryTest(hooks) { - hooks.beforeEach(async function () { +export function setupSentryTest(hooks: NestedHooks): void { + hooks.beforeEach(async function (this: SentryTestContext) { await window._sentryPerformanceLoad; window._sentryTestEvents = []; - const errorMessages = []; + const errorMessages: string[] = []; this.errorMessages = errorMessages; // eslint-disable-next-line ember/no-private-routing-service - const routerMain = this.owner.lookup('router:main'); - const routerService = this.owner.lookup('service:router'); + const routerMain = this.owner.lookup('router:main') as EmberRouterMain; + const routerService = this.owner.lookup('service:router') as SentryRouterService; if (routerService._sentryInstrumented) { _routerStartTransaction = routerService._startTransaction; - } else { - _instrumentEmberRouter(routerService, routerMain, {}, _routerStartTransaction); + } else if (_routerStartTransaction) { + _instrumentEmberRouter(routerService, routerMain, {} as EmberSentryConfig, _routerStartTransaction); } /** @@ -34,10 +49,12 @@ export function setupSentryTest(hooks) { */ this.qunitOnUnhandledRejection = sinon.stub( QUnit, + // @ts-expect-error this is OK QUnit.onUncaughtException ? 'onUncaughtException' : 'onUnhandledRejection', ); - QUnit.onError = function ({ message }) { + // @ts-expect-error this is fine + QUnit.onError = function ({ message }: { message: string }) { errorMessages.push(message.split('Error: ')[1]); return true; }; @@ -52,15 +69,12 @@ export function setupSentryTest(hooks) { /** * Will collect errors when run via testem in cli */ - window.onerror = function (error, ...args) { - errorMessages.push(error.split('Error: ')[1]); - if (this._windowOnError) { - return this._windowOnError(error, ...args); - } + window.onerror = error => { + errorMessages.push(error.toString().split('Error: ')[1]); }; }); - hooks.afterEach(function () { + hooks.afterEach(function (this: SentryTestContext) { this.fetchStub.restore(); this.qunitOnUnhandledRejection.restore(); window.onerror = this._windowOnError; diff --git a/packages/ember/tests/helpers/utils.ts b/packages/ember/tests/helpers/utils.ts new file mode 100644 index 000000000000..3ec336cfa59c --- /dev/null +++ b/packages/ember/tests/helpers/utils.ts @@ -0,0 +1,87 @@ +import type { Event } from '@sentry/types'; + +const defaultAssertOptions = { + method: 'POST', + errorBodyContains: [], +}; + +function getTestSentryErrors(): Event[] { + return window._sentryTestEvents.filter(event => event['type'] !== 'transaction'); +} + +function getTestSentryTransactions(): Event[] { + return window._sentryTestEvents.filter(event => event['type'] === 'transaction'); +} + +export function assertSentryErrorCount(assert: Assert, count: number): void { + assert.equal(getTestSentryErrors().length, count, 'Check correct number of Sentry events were sent'); +} + +export function assertSentryTransactionCount(assert: Assert, count: number): void { + assert.equal(getTestSentryTransactions().length, count, 'Check correct number of Sentry events were sent'); +} + +export function assertSentryErrors( + assert: Assert, + callNumber: number, + options: { + errorBodyContains: string[]; + }, +): void { + const sentryTestEvents = getTestSentryErrors(); + const assertOptions = Object.assign({}, defaultAssertOptions, options); + + const event = sentryTestEvents[callNumber]; + + /** + * Body could be parsed here to check exact properties, but that requires too much implementation specific detail, + * instead this loosely matches on contents to check the correct error is being sent. + */ + assert.ok(assertOptions.errorBodyContains.length, 'Must pass strings to check against error body'); + const errorBody = JSON.stringify(event); + assertOptions.errorBodyContains.forEach(bodyContent => { + assert.ok(errorBody.includes(bodyContent), `Checking that error body includes ${bodyContent}`); + }); +} + +export function assertSentryTransactions( + assert: Assert, + callNumber: number, + options: { + spans: string[]; + transaction: string; + tags: Record; + durationCheck?: (duration: number) => boolean; + }, +): void { + const sentryTestEvents = getTestSentryTransactions(); + const event = sentryTestEvents[callNumber]; + + assert.ok(event); + assert.ok(event.spans); + + const spans = event.spans || []; + + // instead of checking the specific order of runloop spans (which is brittle), + // we check (below) that _any_ runloop spans are added + const filteredSpans = spans + .filter(span => !span.op?.startsWith('ui.ember.runloop.')) + .map(s => { + return `${s.op} | ${s.description}`; + }); + + assert.true( + spans.some(span => span.op?.startsWith('ui.ember.runloop.')), + 'it captures runloop spans', + ); + assert.deepEqual(filteredSpans, options.spans, 'Has correct spans'); + + assert.equal(event.transaction, options.transaction); + assert.equal(event.tags?.fromRoute, options.tags.fromRoute); + assert.equal(event.tags?.toRoute, options.tags.toRoute); + + if (options.durationCheck && event.timestamp && event.start_timestamp) { + const duration = (event.timestamp - event.start_timestamp) * 1000; + assert.ok(options.durationCheck(duration), `duration (${duration}ms) passes duration check`); + } +} diff --git a/packages/ember/tests/test-helper.js b/packages/ember/tests/test-helper.ts similarity index 68% rename from packages/ember/tests/test-helper.js rename to packages/ember/tests/test-helper.ts index 80fbd39cf110..9c04e37d68b5 100644 --- a/packages/ember/tests/test-helper.js +++ b/packages/ember/tests/test-helper.ts @@ -1,10 +1,17 @@ -import * as Sentry from '@sentry/browser'; -import setupSinon from 'ember-sinon-qunit'; -import Application from '../app'; -import config from '../config/environment'; import { setApplication } from '@ember/test-helpers'; -import { start } from 'ember-qunit'; import { isTesting } from '@embroider/macros'; +import * as Sentry from '@sentry/browser'; +import Application from 'dummy/app'; +import config from 'dummy/config/environment'; +import { start } from 'ember-qunit'; +import setupSinon from 'ember-sinon-qunit'; + +declare global { + interface Window { + _sentryTestEvents: Sentry.Event[]; + _sentryPerformanceLoad?: Promise; + } +} Sentry.addGlobalEventProcessor(event => { if (isTesting()) { @@ -21,4 +28,5 @@ setApplication(Application.create(config.APP)); setupSinon(); start(); +// @ts-expect-error TODO: Is this needed ??? QUnit.config.ignoreGlobalErrors = true; diff --git a/packages/ember/tests/unit/instrument-route-performance-test.ts b/packages/ember/tests/unit/instrument-route-performance-test.ts index 548a6e819db2..6ab88a646f23 100644 --- a/packages/ember/tests/unit/instrument-route-performance-test.ts +++ b/packages/ember/tests/unit/instrument-route-performance-test.ts @@ -1,8 +1,9 @@ -import { module, test } from 'qunit'; -import { setupTest } from 'ember-qunit'; import Route from '@ember/routing/route'; import { instrumentRoutePerformance } from '@sentry/ember'; +import { setupTest } from 'ember-qunit'; +import { module, test } from 'qunit'; import sinon from 'sinon'; + import { setupSentryTest } from '../helpers/setup-sentry'; module('Unit | Utility | instrument-route-performance', function (hooks) { @@ -16,19 +17,19 @@ module('Unit | Utility | instrument-route-performance', function (hooks) { const setupController = sinon.spy(); class DummyRoute extends Route { - beforeModel(...args: any[]) { + public beforeModel(...args: unknown[]): unknown { return beforeModel.call(this, ...args); } - model(...args: any[]) { + public model(...args: unknown[]): unknown { return model.call(this, ...args); } - afterModel(...args: any[]) { + public afterModel(...args: unknown[]): unknown { return afterModel.call(this, ...args); } - setupController(...args: any[]) { + public setupController(...args: unknown[]): unknown { return setupController.call(this, ...args); } } @@ -37,7 +38,7 @@ module('Unit | Utility | instrument-route-performance', function (hooks) { this.owner.register('route:dummy', InstrumentedDummyRoute); - const route = this.owner.lookup('route:dummy'); + const route = this.owner.lookup('route:dummy') as DummyRoute; route.beforeModel('foo'); diff --git a/packages/ember/tsconfig.json b/packages/ember/tsconfig.json index c89e04bc98a4..95bb38c78628 100644 --- a/packages/ember/tsconfig.json +++ b/packages/ember/tsconfig.json @@ -1,7 +1,7 @@ { "extends": "../../tsconfig.json", "compilerOptions": { - "target": "es2017", + "target": "es2022", "allowJs": true, "moduleResolution": "node", "allowSyntheticDefaultImports": true, @@ -24,5 +24,5 @@ "*": ["types/*"] } }, - "include": ["app/**/*", "addon/**/*", "types/**/*", "addon-test-support/**/*"] + "include": ["app/**/*", "addon/**/*", "tests/**/*", "types/**/*", "test-support/**/*", "addon-test-support/**/*"] } diff --git a/yarn.lock b/yarn.lock index b8336f9c8063..adbbc6797a04 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2304,18 +2304,18 @@ silent-error "^1.1.1" util.promisify "^1.0.0" -"@ember/test-helpers@~2.8.1": - version "2.8.1" - resolved "https://registry.yarnpkg.com/@ember/test-helpers/-/test-helpers-2.8.1.tgz#20f2e30d48172c2ff713e1db7fbec5352f918d4e" - integrity sha512-jbsYwWyAdhL/pdPu7Gb3SG1gvIXY70FWMtC/Us0Kmvk82Y+5YUQ1SOC0io75qmOGYQmH7eQrd/bquEVd+4XtdQ== +"@ember/test-helpers@2.9.4": + version "2.9.4" + resolved "https://registry.yarnpkg.com/@ember/test-helpers/-/test-helpers-2.9.4.tgz#985022e9ba05cfc918bcf08b77cbb355f85b723e" + integrity sha512-z+Qs1NYWyIVDmrY6WdmOS5mdG1lJ5CFfzh6dRhLfs9lq45deDaDrVNcaCYhnNeJZTvUBK2XR2SvPcZm0RloXdA== dependencies: "@ember/test-waiters" "^3.0.0" - "@embroider/macros" "^1.6.0" - "@embroider/util" "^1.6.0" + "@embroider/macros" "^1.10.0" + "@embroider/util" "^1.9.0" broccoli-debug "^0.6.5" broccoli-funnel "^3.0.8" - ember-cli-babel "^7.26.6" - ember-cli-htmlbars "^5.7.1" + ember-cli-babel "^7.26.11" + ember-cli-htmlbars "^6.1.1" ember-destroyable-polyfill "^2.0.3" "@ember/test-waiters@^3.0.0": @@ -2328,7 +2328,7 @@ ember-cli-version-checker "^5.1.2" semver "^7.3.5" -"@embroider/macros@^1.0.0", "@embroider/macros@^1.6.0", "@embroider/macros@^1.9.0": +"@embroider/macros@^1.0.0", "@embroider/macros@^1.9.0": version "1.9.0" resolved "https://registry.yarnpkg.com/@embroider/macros/-/macros-1.9.0.tgz#0df2a56fdd93f11fddea450b6ca83cc2119b5008" integrity sha512-12ElrRT+mX3aSixGHjHnfsnyoH1hw5nM+P+Ax0ITZdp6TaAvWZ8dENnVHltdnv4ssHiX0EsVEXmqbIIdMN4nLA== @@ -2342,6 +2342,20 @@ resolve "^1.20.0" semver "^7.3.2" +"@embroider/macros@^1.10.0", "@embroider/macros@^1.11.0": + version "1.13.1" + resolved "https://registry.yarnpkg.com/@embroider/macros/-/macros-1.13.1.tgz#aee17e5af0e0086bd36873bdb4e49ea346bab3fa" + integrity sha512-4htraP/rNIht8uCxXoc59Bw2EsBFfc4YUQD9XSpzJ4xUr1V0GQf9wL/noeSuYSxIhwRfZOErnJhsdyf1hH+I/A== + dependencies: + "@embroider/shared-internals" "2.4.0" + assert-never "^1.2.1" + babel-import-util "^2.0.0" + ember-cli-babel "^7.26.6" + find-up "^5.0.0" + lodash "^4.17.21" + resolve "^1.20.0" + semver "^7.3.2" + "@embroider/shared-internals@1.8.3": version "1.8.3" resolved "https://registry.yarnpkg.com/@embroider/shared-internals/-/shared-internals-1.8.3.tgz#52d868dc80016e9fe983552c0e516f437bf9b9f9" @@ -2356,6 +2370,21 @@ semver "^7.3.5" typescript-memoize "^1.0.1" +"@embroider/shared-internals@2.4.0": + version "2.4.0" + resolved "https://registry.yarnpkg.com/@embroider/shared-internals/-/shared-internals-2.4.0.tgz#0e9fdb0b2df9bad45fab8c54cbb70d8a2cbf01fc" + integrity sha512-pFE05ebenWMC9XAPRjadYCXXb6VmqjkhYN5uqkhPo+VUmMHnx7sZYYxqGjxfVuhC/ghS/BNlOffOCXDOoE7k7g== + dependencies: + babel-import-util "^2.0.0" + debug "^4.3.2" + ember-rfc176-data "^0.3.17" + fs-extra "^9.1.0" + js-string-escape "^1.0.1" + lodash "^4.17.21" + resolve-package-path "^4.0.1" + semver "^7.3.5" + typescript-memoize "^1.0.1" + "@embroider/shared-internals@^2.0.0": version "2.3.0" resolved "https://registry.yarnpkg.com/@embroider/shared-internals/-/shared-internals-2.3.0.tgz#97215f6263a4013fbdb3d1e4890cc069f2d9df12" @@ -2379,14 +2408,14 @@ lodash "^4.17.21" resolve "^1.20.0" -"@embroider/util@^1.6.0": - version "1.9.0" - resolved "https://registry.yarnpkg.com/@embroider/util/-/util-1.9.0.tgz#331c46bdf106c44cb1dd6baaa9030d322c13cfca" - integrity sha512-9I63iJK6N01OHJafmS/BX0msUkTlmhFMIEmDl/SRNACVi0nS6QfNyTgTTeji1P/DALf6eobg/9t/N4VhS9G9QA== +"@embroider/util@^1.9.0": + version "1.11.1" + resolved "https://registry.yarnpkg.com/@embroider/util/-/util-1.11.1.tgz#622390932542e6b7f8d5d28e956891306e664eb3" + integrity sha512-IqzlEQahM2cfLvo4PULA2WyvROqr9jRmeSv0GGZzpitWCh6l4FDwweOLSArdlKSXdQxHkKhwBMCi//7DhKjRlg== dependencies: - "@embroider/macros" "^1.9.0" + "@embroider/macros" "^1.11.0" broccoli-funnel "^3.0.5" - ember-cli-babel "^7.23.1" + ember-cli-babel "^7.26.11" "@esbuild/android-arm64@0.16.17": version "0.16.17" @@ -2498,13 +2527,18 @@ resolved "https://registry.yarnpkg.com/@esbuild/win32-x64/-/win32-x64-0.16.17.tgz#c5a1a4bfe1b57f0c3e61b29883525c6da3e5c091" integrity sha512-y+EHuSchhL7FjHgvQL/0fnnFmO4T1bhvWANX6gcnqTjtnKWbTvUMCpGnv2+t+31d7RzyEAYAd4u2fnIhHL6N/Q== -"@eslint-community/eslint-utils@^4.2.0": +"@eslint-community/eslint-utils@^4.1.2", "@eslint-community/eslint-utils@^4.2.0", "@eslint-community/eslint-utils@^4.4.0": version "4.4.0" resolved "https://registry.yarnpkg.com/@eslint-community/eslint-utils/-/eslint-utils-4.4.0.tgz#a23514e8fb9af1269d5f7788aa556798d61c6b59" integrity sha512-1/sA4dwrzBAyeUoQ6oxahHKmrZvsnLCg4RfxW3ZFGGmQkSNQPFNLV9CUEFQP1x9EYXHTo5p6xdhZM1Ne9p/AfA== dependencies: eslint-visitor-keys "^3.3.0" +"@eslint-community/regexpp@^4.5.0": + version "4.6.2" + resolved "https://registry.yarnpkg.com/@eslint-community/regexpp/-/regexpp-4.6.2.tgz#1816b5f6948029c5eaacb0703b850ee0cb37d8f8" + integrity sha512-pPTNuaAG3QMH+buKyBIGJs3g/S5y0caxw0ygM3YyE6yJFySwiGGSzA+mM3KJ8QQvzeLh3blwgSonkFjgQdxzMw== + "@eslint/eslintrc@^0.4.3": version "0.4.3" resolved "https://registry.yarnpkg.com/@eslint/eslintrc/-/eslintrc-0.4.3.tgz#9e42981ef035beb3dd49add17acb96e8ff6f394c" @@ -2562,6 +2596,13 @@ dependencies: "@glimmer/env" "^0.1.7" +"@glimmer/global-context@0.84.3": + version "0.84.3" + resolved "https://registry.yarnpkg.com/@glimmer/global-context/-/global-context-0.84.3.tgz#f8bf2cda9562716f2ddf3f96837e7559600635c4" + integrity sha512-8Oy9Wg5IZxMEeAnVmzD2NkObf89BeHoFSzJgJROE/deutd3rxg83mvlOez4zBBGYwnTb+VGU2LYRpet92egJjA== + dependencies: + "@glimmer/env" "^0.1.7" + "@glimmer/interfaces@0.83.1": version "0.83.1" resolved "https://registry.yarnpkg.com/@glimmer/interfaces/-/interfaces-0.83.1.tgz#fb16f5f683ddc55f130887b6141f58c0751350fe" @@ -2587,6 +2628,17 @@ "@glimmer/util" "0.83.1" "@glimmer/validator" "0.83.1" +"@glimmer/reference@^0.84.3": + version "0.84.3" + resolved "https://registry.yarnpkg.com/@glimmer/reference/-/reference-0.84.3.tgz#6420ad9c102633ac83939fd1b2457269d21fb632" + integrity sha512-lV+p/aWPVC8vUjmlvYVU7WQJsLh319SdXuAWoX/SE3pq340BJlAJiEcAc6q52y9JNhT57gMwtjMX96W5Xcx/qw== + dependencies: + "@glimmer/env" "^0.1.7" + "@glimmer/global-context" "0.84.3" + "@glimmer/interfaces" "0.84.3" + "@glimmer/util" "0.84.3" + "@glimmer/validator" "0.84.3" + "@glimmer/syntax@^0.83.1": version "0.83.1" resolved "https://registry.yarnpkg.com/@glimmer/syntax/-/syntax-0.83.1.tgz#7e18dd445871c157ba0281f12a4fbf316fa49b41" @@ -2597,7 +2649,7 @@ "@handlebars/parser" "~2.0.0" simple-html-tokenizer "^0.5.11" -"@glimmer/syntax@^0.84.3": +"@glimmer/syntax@^0.84.2", "@glimmer/syntax@^0.84.3": version "0.84.3" resolved "https://registry.yarnpkg.com/@glimmer/syntax/-/syntax-0.84.3.tgz#4045a1708cef7fd810cff42fe6deeba40c7286d0" integrity sha512-ioVbTic6ZisLxqTgRBL2PCjYZTFIwobifCustrozRU2xGDiYvVIL0vt25h2c1ioDsX59UgVlDkIK4YTAQQSd2A== @@ -2646,6 +2698,14 @@ "@glimmer/env" "^0.1.7" "@glimmer/global-context" "0.83.1" +"@glimmer/validator@0.84.3", "@glimmer/validator@^0.84.3": + version "0.84.3" + resolved "https://registry.yarnpkg.com/@glimmer/validator/-/validator-0.84.3.tgz#cd83b7f9ab78953f23cc11a32d83d7f729c54df2" + integrity sha512-RTBV4TokUB0vI31UC7ikpV7lOYpWUlyqaKV//pRC4pexYMlmqnVhkFrdiimB/R1XyNdUOQUmnIAcdic39NkbhQ== + dependencies: + "@glimmer/env" "^0.1.7" + "@glimmer/global-context" "0.84.3" + "@glimmer/validator@^0.44.0": version "0.44.0" resolved "https://registry.yarnpkg.com/@glimmer/validator/-/validator-0.44.0.tgz#03d127097dc9cb23052cdb7fcae59d0a9dca53e1" @@ -4833,6 +4893,14 @@ "@types/ember-test-helpers" "*" "@types/qunit" "*" +"@types/ember-resolver@5.0.13": + version "5.0.13" + resolved "https://registry.yarnpkg.com/@types/ember-resolver/-/ember-resolver-5.0.13.tgz#db66678076ca625ed80b629c09619ae85c1c1f7a" + integrity sha512-pO964cAPhAaFJoS28M8+b5MzAhQ/tVuNM4GDUIAexheQat36axG2WTG8LQ5ea07MSFPesrRFk2T3z88pfvdYKA== + dependencies: + "@types/ember__object" "*" + "@types/ember__owner" "*" + "@types/ember-test-helpers@*": version "1.0.9" resolved "https://registry.yarnpkg.com/@types/ember-test-helpers/-/ember-test-helpers-1.0.9.tgz#4279c5f3b390f25fbfb3f9f210785d36a336b8a7" @@ -4938,6 +5006,11 @@ "@types/ember__object" "*" "@types/rsvp" "*" +"@types/ember__owner@*": + version "4.0.4" + resolved "https://registry.yarnpkg.com/@types/ember__owner/-/ember__owner-4.0.4.tgz#f118ef4cdcca62c39426aa8032280d45b912148a" + integrity sha512-FD0XuAlIfeVEwpKcAeGczQxa6D0huKxvPHuPE+FIm+zWZmqnI6yhxDhZgeGjnhmCCLAHRp8+1HRoKOFwnmaW3Q== + "@types/ember__polyfills@*": version "3.12.1" resolved "https://registry.yarnpkg.com/@types/ember__polyfills/-/ember__polyfills-3.12.1.tgz#aed838e35a3e8670d247333d4c7ea2c2f7b3c43e" @@ -4980,16 +5053,6 @@ resolved "https://registry.yarnpkg.com/@types/ember__template/-/ember__template-3.16.1.tgz#30d7f50a49b190934db0f5a56dd76ad86c21efc6" integrity sha512-APQINizzizl2LHWGMFBCanRjKZQsdzqn7b+us17zbNhnx/R0IZAJq901x/i7eozCRwxsDKmGzNABSCIu6uc1Tg== -"@types/ember__test-helpers@~1.7.0": - version "1.7.5" - resolved "https://registry.yarnpkg.com/@types/ember__test-helpers/-/ember__test-helpers-1.7.5.tgz#6a9f5e517869fa396dc037f729f1b56a00bf5d92" - integrity sha512-Hs3/9DTwJp8QPr2Jt8ZOuoxogSFH4Agi++mszutzd8GLk9Skeo1nN5IJY5FjaS8j6eDAYaPv71sRvuRa+CNA7A== - dependencies: - "@types/ember" "*" - "@types/ember__application" "*" - "@types/ember__error" "*" - "@types/htmlbars-inline-precompile" "*" - "@types/ember__test@*": version "3.16.1" resolved "https://registry.yarnpkg.com/@types/ember__test/-/ember__test-3.16.1.tgz#8407e42b9835a13ef0c6ef7a7ce3aa3d7ebcb7ed" @@ -7495,7 +7558,7 @@ babel-plugin-filter-imports@^4.0.0: "@babel/types" "^7.7.2" lodash "^4.17.15" -babel-plugin-htmlbars-inline-precompile@^5.0.0, babel-plugin-htmlbars-inline-precompile@^5.2.1, babel-plugin-htmlbars-inline-precompile@^5.3.0: +babel-plugin-htmlbars-inline-precompile@^5.2.1, babel-plugin-htmlbars-inline-precompile@^5.3.0: version "5.3.1" resolved "https://registry.yarnpkg.com/babel-plugin-htmlbars-inline-precompile/-/babel-plugin-htmlbars-inline-precompile-5.3.1.tgz#5ba272e2e4b6221522401f5f1d98a73b1de38787" integrity sha512-QWjjFgSKtSRIcsBhJmEwS2laIdrA6na8HAlc/pEAhjHgQsah/gMiBFRZvbQTy//hWxR4BMwV7/Mya7q5H8uHeA== @@ -8965,7 +9028,7 @@ builtins@^1.0.3: resolved "https://registry.yarnpkg.com/builtins/-/builtins-1.0.3.tgz#cb94faeb61c8696451db36534e1422f94f0aee88" integrity sha1-y5T662HIaWRR2zZTThQi+U8K7og= -builtins@^5.0.0: +builtins@^5.0.0, builtins@^5.0.1: version "5.0.1" resolved "https://registry.yarnpkg.com/builtins/-/builtins-5.0.1.tgz#87f6db9ab0458be728564fa81d876d8d74552fa9" integrity sha512-qwVpFEHNfhYJIzNRBvd2C1kyo6jz3ZSMPyyuR47OPdiKWlbYnZNyDWuyR175qDnAJLiCo5fBBqPb3RiXgWlkOQ== @@ -11708,7 +11771,7 @@ ember-cli-babel-plugin-helpers@^1.0.0, ember-cli-babel-plugin-helpers@^1.1.1: resolved "https://registry.yarnpkg.com/ember-cli-babel-plugin-helpers/-/ember-cli-babel-plugin-helpers-1.1.1.tgz#5016b80cdef37036c4282eef2d863e1d73576879" integrity sha512-sKvOiPNHr5F/60NLd7SFzMpYPte/nnGkq/tMIfXejfKHIhaiIkYFqX8Z9UFTKWLLn+V7NOaby6niNPZUdvKCRw== -ember-cli-babel@^7.0.0, ember-cli-babel@^7.13.0, ember-cli-babel@^7.13.2, ember-cli-babel@^7.22.1, ember-cli-babel@^7.23.0, ember-cli-babel@^7.23.1, ember-cli-babel@^7.26.11, ember-cli-babel@^7.26.4, ember-cli-babel@^7.26.6, ember-cli-babel@^7.7.3: +ember-cli-babel@^7.0.0, ember-cli-babel@^7.13.0, ember-cli-babel@^7.13.2, ember-cli-babel@^7.22.1, ember-cli-babel@^7.23.0, ember-cli-babel@^7.26.11, ember-cli-babel@^7.26.4, ember-cli-babel@^7.26.6, ember-cli-babel@^7.7.3: version "7.26.11" resolved "https://registry.yarnpkg.com/ember-cli-babel/-/ember-cli-babel-7.26.11.tgz#50da0fe4dcd99aada499843940fec75076249a9f" integrity sha512-JJYeYjiz/JTn34q7F5DSOjkkZqy8qwFOOxXfE6pe9yEJqWGu4qErKxlz8I22JoVEQ/aBUO+OcKTpmctvykM9YA== @@ -11760,28 +11823,6 @@ ember-cli-get-component-path-option@^1.0.0: resolved "https://registry.yarnpkg.com/ember-cli-get-component-path-option/-/ember-cli-get-component-path-option-1.0.0.tgz#0d7b595559e2f9050abed804f1d8eff1b08bc771" integrity sha1-DXtZVVni+QUKvtgE8djv8bCLx3E= -ember-cli-htmlbars@^5.7.1: - version "5.7.2" - resolved "https://registry.yarnpkg.com/ember-cli-htmlbars/-/ember-cli-htmlbars-5.7.2.tgz#e0cd2fb3c20d85fe4c3e228e6f0590ee1c645ba8" - integrity sha512-Uj6R+3TtBV5RZoJY14oZn/sNPnc+UgmC8nb5rI4P3fR/gYoyTFIZSXiIM7zl++IpMoIrocxOrgt+mhonKphgGg== - dependencies: - "@ember/edition-utils" "^1.2.0" - babel-plugin-htmlbars-inline-precompile "^5.0.0" - broccoli-debug "^0.6.5" - broccoli-persistent-filter "^3.1.2" - broccoli-plugin "^4.0.3" - common-tags "^1.8.0" - ember-cli-babel-plugin-helpers "^1.1.1" - ember-cli-version-checker "^5.1.2" - fs-tree-diff "^2.0.1" - hash-for-dep "^1.5.1" - heimdalljs-logger "^0.1.10" - json-stable-stringify "^1.0.1" - semver "^7.3.4" - silent-error "^1.1.1" - strip-bom "^4.0.0" - walk-sync "^2.2.0" - ember-cli-htmlbars@^6.0.1, ember-cli-htmlbars@^6.1.1: version "6.1.1" resolved "https://registry.yarnpkg.com/ember-cli-htmlbars/-/ember-cli-htmlbars-6.1.1.tgz#f5b588572a5d18ad087560122b8dabc90145173d" @@ -12254,6 +12295,21 @@ ember-template-imports@^3.1.1: string.prototype.matchall "^4.0.6" validate-peer-dependencies "^1.1.0" +ember-template-imports@^3.4.2: + version "3.4.2" + resolved "https://registry.yarnpkg.com/ember-template-imports/-/ember-template-imports-3.4.2.tgz#6cf7de7d4b8348a0fddf3aaec4947aa1211289e6" + integrity sha512-OS8TUVG2kQYYwP3netunLVfeijPoOKIs1SvPQRTNOQX4Pu8xGGBEZmrv0U1YTnQn12Eg+p6w/0UdGbUnITjyzw== + dependencies: + babel-import-util "^0.2.0" + broccoli-stew "^3.0.0" + ember-cli-babel-plugin-helpers "^1.1.1" + ember-cli-version-checker "^5.1.2" + line-column "^1.0.2" + magic-string "^0.25.7" + parse-static-imports "^1.1.0" + string.prototype.matchall "^4.0.6" + validate-peer-dependencies "^1.1.0" + ember-template-lint@~4.16.1: version "4.16.1" resolved "https://registry.yarnpkg.com/ember-template-lint/-/ember-template-lint-4.16.1.tgz#16d59916b1b1f2c5f0fd4bc86a1c4d91b3ae2c24" @@ -12294,6 +12350,23 @@ ember-template-recast@^6.1.3: tmp "^0.2.1" workerpool "^6.1.5" +ember-template-recast@^6.1.4: + version "6.1.4" + resolved "https://registry.yarnpkg.com/ember-template-recast/-/ember-template-recast-6.1.4.tgz#e964c184adfd876878009f8aa0b84c95633fce20" + integrity sha512-fCh+rOK6z+/tsdkTbOE+e7f84P6ObnIRQrCCrnu21E4X05hPeradikIkRMhJdxn4NWrxitfZskQDd37TR/lsNQ== + dependencies: + "@glimmer/reference" "^0.84.3" + "@glimmer/syntax" "^0.84.3" + "@glimmer/validator" "^0.84.3" + async-promise-queue "^1.0.5" + colors "^1.4.0" + commander "^8.3.0" + globby "^11.0.3" + ora "^5.4.0" + slash "^3.0.0" + tmp "^0.2.1" + workerpool "^6.4.0" + ember-test-selectors@~6.0.0: version "6.0.0" resolved "https://registry.yarnpkg.com/ember-test-selectors/-/ember-test-selectors-6.0.0.tgz#ba9bb19550d9dec6e4037d86d2b13c2cfd329341" @@ -12857,27 +12930,32 @@ eslint-plugin-deprecation@^1.1.0: tslib "^1.10.0" tsutils "^3.0.0" -eslint-plugin-ember@~11.1.0: - version "11.1.0" - resolved "https://registry.yarnpkg.com/eslint-plugin-ember/-/eslint-plugin-ember-11.1.0.tgz#98349676f2b5e317cdd9207ce9f65036e3ec7c9a" - integrity sha512-g1pDwgw2sUTJDfbFVoI5u6fbhs2v0jrTiq5cChQ0DqzTqZchlPtCj7ySSFrqfcSp8MLOuX2bx8lOH9uKeb5N1w== +eslint-plugin-ember@11.9.0: + version "11.9.0" + resolved "https://registry.yarnpkg.com/eslint-plugin-ember/-/eslint-plugin-ember-11.9.0.tgz#b02f8d7c2d78ff2b8f13d1eaff55d13edfa8cfee" + integrity sha512-kpsvbdQOFw9ikzwmhxR8mmsAXtwwj+DPMGn1NOHAHJOnhTkyioKEiNO6tQK/b33VtQy1VpJP6zFu+Bt6m/cYxA== dependencies: "@ember-data/rfc395-data" "^0.0.4" + "@glimmer/syntax" "^0.84.2" css-tree "^2.0.4" ember-rfc176-data "^0.3.15" + ember-template-imports "^3.4.2" + ember-template-recast "^6.1.4" eslint-utils "^3.0.0" estraverse "^5.2.0" + lodash.camelcase "^4.1.1" lodash.kebabcase "^4.1.1" + magic-string "^0.30.0" requireindex "^1.2.0" snake-case "^3.0.3" -eslint-plugin-es@^3.0.0: - version "3.0.1" - resolved "https://registry.yarnpkg.com/eslint-plugin-es/-/eslint-plugin-es-3.0.1.tgz#75a7cdfdccddc0589934aeeb384175f221c57893" - integrity sha512-GUmAsJaN4Fc7Gbtl8uOBlayo2DqhwWvEzykMHSCZHU3XdJ+NSzzZcVhXh3VxX5icqQ+oQdIEawXX8xkR3mIFmQ== +eslint-plugin-es-x@^7.1.0: + version "7.1.0" + resolved "https://registry.yarnpkg.com/eslint-plugin-es-x/-/eslint-plugin-es-x-7.1.0.tgz#f0d5421e658cca95c1cfb2355831851bdc83322d" + integrity sha512-AhiaF31syh4CCQ+C5ccJA0VG6+kJK8+5mXKKE7Qs1xcPRg02CDPOj3mWlQxuWS/AYtg7kxrDNgW9YW3vc0Q+Mw== dependencies: - eslint-utils "^2.0.0" - regexpp "^3.0.0" + "@eslint-community/eslint-utils" "^4.1.2" + "@eslint-community/regexpp" "^4.5.0" eslint-plugin-import@^2.22.0: version "2.22.1" @@ -12918,22 +12996,24 @@ eslint-plugin-jsdoc@^30.0.3: semver "^7.3.4" spdx-expression-parse "^3.0.1" -eslint-plugin-node@~11.1.0: - version "11.1.0" - resolved "https://registry.yarnpkg.com/eslint-plugin-node/-/eslint-plugin-node-11.1.0.tgz#c95544416ee4ada26740a30474eefc5402dc671d" - integrity sha512-oUwtPJ1W0SKD0Tr+wqu92c5xuCeQqB3hSCHasn/ZgjFdA9iDGNkNf2Zi9ztY7X+hNuMib23LNGRm6+uN+KLE3g== +eslint-plugin-n@16.0.1: + version "16.0.1" + resolved "https://registry.yarnpkg.com/eslint-plugin-n/-/eslint-plugin-n-16.0.1.tgz#baa62bb3af52940a53ba15386348ad9b0b425ada" + integrity sha512-CDmHegJN0OF3L5cz5tATH84RPQm9kG+Yx39wIqIwPR2C0uhBGMWfbbOtetR83PQjjidA5aXMu+LEFw1jaSwvTA== dependencies: - eslint-plugin-es "^3.0.0" - eslint-utils "^2.0.0" - ignore "^5.1.1" - minimatch "^3.0.4" - resolve "^1.10.1" - semver "^6.1.0" + "@eslint-community/eslint-utils" "^4.4.0" + builtins "^5.0.1" + eslint-plugin-es-x "^7.1.0" + ignore "^5.2.4" + is-core-module "^2.12.1" + minimatch "^3.1.2" + resolve "^1.22.2" + semver "^7.5.3" -eslint-plugin-qunit@~7.3.1: - version "7.3.1" - resolved "https://registry.yarnpkg.com/eslint-plugin-qunit/-/eslint-plugin-qunit-7.3.1.tgz#cb7c0012199a8db8ff43916d07b0361e999a53b1" - integrity sha512-L1yutkLqCgr70ZmMAbBKPvUOUwhKryZ0RaJKOzw72Bmn8no3JNBL9hhbX2aTvfZqYM/wLXIT0nICZiGrV4xVJw== +eslint-plugin-qunit@8.0.0: + version "8.0.0" + resolved "https://registry.yarnpkg.com/eslint-plugin-qunit/-/eslint-plugin-qunit-8.0.0.tgz#92df9b8cc144a67edaf961e9c4db75d98065ce85" + integrity sha512-ly2x/pmJPcS0ztGAPap6qLC13GjOFwhBbvun0K1dAjaxaC6KB3TYjeBo+5pGvXqL3WdicmYxEKhTGwmhvoxMBQ== dependencies: eslint-utils "^3.0.0" requireindex "^1.2.0" @@ -15625,6 +15705,11 @@ ignore@^5.0.4, ignore@^5.1.1, ignore@^5.2.0: resolved "https://registry.yarnpkg.com/ignore/-/ignore-5.2.1.tgz#c2b1f76cb999ede1502f3a226a9310fdfe88d46c" integrity sha512-d2qQLzTJ9WxQftPAuEQpSPmKqzxePjzVbpAVv62AQ64NTL+wR4JkrVqR/LqFsFEUsHDAiId52mJteHDFuDkElA== +ignore@^5.2.4: + version "5.2.4" + resolved "https://registry.yarnpkg.com/ignore/-/ignore-5.2.4.tgz#a291c0c6178ff1b960befe47fcdec301674a6324" + integrity sha512-MAb38BcSbH0eHNBxn7ql2NH/kX33OkB3lZ1BNdh7ENeRChHTYsTvWrMubiIAMNS2llXEEgZ1MUOBtXChP3kaFQ== + image-size@~0.5.0: version "0.5.5" resolved "https://registry.yarnpkg.com/image-size/-/image-size-0.5.5.tgz#09dfd4ab9d20e29eb1c3e80b8990378df9e3cb9c" @@ -16067,6 +16152,13 @@ is-color-stop@^1.0.0: rgb-regex "^1.0.1" rgba-regex "^1.0.0" +is-core-module@^2.11.0, is-core-module@^2.12.1, is-core-module@^2.5.0: + version "2.12.1" + resolved "https://registry.yarnpkg.com/is-core-module/-/is-core-module-2.12.1.tgz#0c0b6885b6f80011c71541ce15c8d66cf5a4f9fd" + integrity sha512-Q4ZuBAe2FUsKtyQJoQHlvP8OvBERxO3jEmy1I7hcRXcJBGGHFh/aJBswbXuS9sgrDH2QUO8ilkwNPHvHMd8clg== + dependencies: + has "^1.0.3" + is-core-module@^2.2.0, is-core-module@^2.8.1, is-core-module@^2.9.0: version "2.11.0" resolved "https://registry.yarnpkg.com/is-core-module/-/is-core-module-2.11.0.tgz#ad4cb3e3863e814523c96f3f58d26cc570ff0144" @@ -16074,13 +16166,6 @@ is-core-module@^2.2.0, is-core-module@^2.8.1, is-core-module@^2.9.0: dependencies: has "^1.0.3" -is-core-module@^2.5.0: - version "2.12.1" - resolved "https://registry.yarnpkg.com/is-core-module/-/is-core-module-2.12.1.tgz#0c0b6885b6f80011c71541ce15c8d66cf5a4f9fd" - integrity sha512-Q4ZuBAe2FUsKtyQJoQHlvP8OvBERxO3jEmy1I7hcRXcJBGGHFh/aJBswbXuS9sgrDH2QUO8ilkwNPHvHMd8clg== - dependencies: - has "^1.0.3" - is-data-descriptor@^0.1.4: version "0.1.4" resolved "https://registry.yarnpkg.com/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz#0b5ee648388e2c860282e793f1856fec3f301b56" @@ -18218,7 +18303,7 @@ lodash.assignin@^4.1.0: resolved "https://registry.yarnpkg.com/lodash.assignin/-/lodash.assignin-4.2.0.tgz#ba8df5fb841eb0a3e8044232b0e263a8dc6a28a2" integrity sha1-uo31+4QesKPoBEIysOJjqNxqKKI= -lodash.camelcase@^4.3.0: +lodash.camelcase@^4.1.1, lodash.camelcase@^4.3.0: version "4.3.0" resolved "https://registry.yarnpkg.com/lodash.camelcase/-/lodash.camelcase-4.3.0.tgz#b28aa6288a2b9fc651035c7711f65ab6190331a6" integrity sha1-soqmKIorn8ZRA1x3EfZathkDMaY= @@ -23749,7 +23834,7 @@ regexp.prototype.flags@^1.2.0, regexp.prototype.flags@^1.4.3: define-properties "^1.1.3" functions-have-names "^1.2.2" -regexpp@^3.0.0, regexpp@^3.1.0, regexpp@^3.2.0: +regexpp@^3.1.0, regexpp@^3.2.0: version "3.2.0" resolved "https://registry.yarnpkg.com/regexpp/-/regexpp-3.2.0.tgz#0425a2768d8f23bad70ca4b90461fa2f1213e1b2" integrity sha512-pq2bWo9mVD43nbts2wGv17XLiNLya+GklZ8kaDLV2Z08gDCsGpnKn9BFMepvWuHCbyVvY7J5o5+BVvoQbmlJLg== @@ -24137,7 +24222,7 @@ resolve@1.20.0: is-core-module "^2.2.0" path-parse "^1.0.6" -resolve@^1.1.6, resolve@^1.1.7, resolve@^1.10.0, resolve@^1.10.1, resolve@^1.11.1, resolve@^1.12.0, resolve@^1.13.1, resolve@^1.14.2, resolve@^1.17.0, resolve@^1.19.0, resolve@^1.20.0, resolve@^1.22.0, resolve@^1.22.1, resolve@^1.3.2, resolve@^1.4.0, resolve@^1.5.0, resolve@^1.8.1: +resolve@^1.1.6, resolve@^1.1.7, resolve@^1.10.0, resolve@^1.11.1, resolve@^1.12.0, resolve@^1.13.1, resolve@^1.14.2, resolve@^1.17.0, resolve@^1.19.0, resolve@^1.20.0, resolve@^1.22.0, resolve@^1.22.1, resolve@^1.3.2, resolve@^1.4.0, resolve@^1.5.0, resolve@^1.8.1: version "1.22.1" resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.22.1.tgz#27cb2ebb53f91abb49470a928bba7558066ac177" integrity sha512-nBpuuYuY5jFsli/JIs1oldw6fOQCBioohqWZg/2hiaOybXOft4lonv85uDOKXdf8rhyK159cxU5cDcK/NKk8zw== @@ -24146,6 +24231,15 @@ resolve@^1.1.6, resolve@^1.1.7, resolve@^1.10.0, resolve@^1.10.1, resolve@^1.11. path-parse "^1.0.7" supports-preserve-symlinks-flag "^1.0.0" +resolve@^1.22.2: + version "1.22.2" + resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.22.2.tgz#0ed0943d4e301867955766c9f3e1ae6d01c6845f" + integrity sha512-Sb+mjNHOULsBv818T40qSPeRiuWLyaGMa5ewydRLFimneixmVy2zdivRl+AF6jaYPC8ERxGDmFSiqui6SfPd+g== + dependencies: + is-core-module "^2.11.0" + path-parse "^1.0.7" + supports-preserve-symlinks-flag "^1.0.0" + resolve@^2.0.0-next.3: version "2.0.0-next.3" resolved "https://registry.yarnpkg.com/resolve/-/resolve-2.0.0-next.3.tgz#d41016293d4a8586a39ca5d9b5f15cbea1f55e46" @@ -24718,14 +24812,14 @@ semver@7.5.3: dependencies: lru-cache "^6.0.0" -semver@7.x, semver@^7.0.0, semver@^7.1.1, semver@^7.1.3, semver@^7.2.1, semver@^7.3.2, semver@^7.3.4, semver@^7.3.5, semver@^7.3.7, semver@^7.3.8, semver@^7.5.1: +semver@7.x, semver@^7.0.0, semver@^7.1.1, semver@^7.1.3, semver@^7.2.1, semver@^7.3.2, semver@^7.3.4, semver@^7.3.5, semver@^7.3.7, semver@^7.3.8, semver@^7.5.1, semver@^7.5.3: version "7.5.4" resolved "https://registry.yarnpkg.com/semver/-/semver-7.5.4.tgz#483986ec4ed38e1c6c48c34894a9182dbff68a6e" integrity sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA== dependencies: lru-cache "^6.0.0" -semver@^6.0.0, semver@^6.1.0, semver@^6.1.1, semver@^6.1.2, semver@^6.2.0, semver@^6.3.0: +semver@^6.0.0, semver@^6.1.1, semver@^6.1.2, semver@^6.2.0, semver@^6.3.0: version "6.3.1" resolved "https://registry.yarnpkg.com/semver/-/semver-6.3.1.tgz#556d2ef8689146e46dcea4bfdd095f3434dffcb4" integrity sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA== @@ -28589,6 +28683,11 @@ workerpool@^6.1.5, workerpool@^6.2.1: resolved "https://registry.yarnpkg.com/workerpool/-/workerpool-6.2.1.tgz#46fc150c17d826b86a008e5a4508656777e9c343" integrity sha512-ILEIE97kDZvF9Wb9f6h5aXK4swSlKGUcOEGiIYb2OOu/IrDU9iwj0fD//SsA6E5ibwJxpEvhullJY4Sl4GcpAw== +workerpool@^6.4.0: + version "6.4.0" + resolved "https://registry.yarnpkg.com/workerpool/-/workerpool-6.4.0.tgz#f8d5cfb45fde32fa3b7af72ad617c3369567a462" + integrity sha512-i3KR1mQMNwY2wx20ozq2EjISGtQWDIfV56We+yGJ5yDs8jTwQiLLaqHlkBHITlCuJnYlVRmXegxFxZg7gqI++A== + "wrap-ansi-cjs@npm:wrap-ansi@^7.0.0", wrap-ansi@^7.0.0: version "7.0.0" resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43"