diff --git a/broccoli/amd-compat-entrypoints/ember.debug.js b/broccoli/amd-compat-entrypoints/ember.debug.js index 9674971a6a7..cf6b79c6422 100644 --- a/broccoli/amd-compat-entrypoints/ember.debug.js +++ b/broccoli/amd-compat-entrypoints/ember.debug.js @@ -197,6 +197,9 @@ d('@ember/controller/index', emberControllerIndex); import * as emberDebugIndex from '@ember/debug/index'; d('@ember/debug/index', emberDebugIndex); +import * as emberDebugInspectorSupportIndex from '@ember/debug/ember-inspector-support/index'; +d('@ember/debug/ember-inspector-support/index', emberDebugInspectorSupportIndex); + import * as emberDebugLibCaptureRenderTree from '@ember/debug/lib/capture-render-tree'; d('@ember/debug/lib/capture-render-tree', emberDebugLibCaptureRenderTree); diff --git a/package.json b/package.json index 00d12c22c9c..14fa7f528f4 100644 --- a/package.json +++ b/package.json @@ -103,7 +103,8 @@ "router_js": "^8.0.5", "semver": "^7.5.2", "silent-error": "^1.1.1", - "simple-html-tokenizer": "^0.5.11" + "simple-html-tokenizer": "^0.5.11", + "source-map-js": "^1.2.1" }, "devDependencies": { "@aws-sdk/client-s3": "^3.321.1", @@ -113,6 +114,7 @@ "@embroider/shared-internals": "^2.5.0", "@glimmer/component": "^1.1.2", "@rollup/plugin-babel": "^6.0.4", + "@rollup/plugin-commonjs": "^28.0.0", "@simple-dom/document": "^1.4.0", "@swc-node/register": "^1.6.8", "@swc/core": "^1.3.100", @@ -235,6 +237,46 @@ "@ember/controller/index.js": "ember-source/@ember/controller/index.js", "@ember/debug/container-debug-adapter.js": "ember-source/@ember/debug/container-debug-adapter.js", "@ember/debug/data-adapter.js": "ember-source/@ember/debug/data-adapter.js", + "@ember/debug/ember-inspector-support/adapters/basic.js": "ember-source/@ember/debug/ember-inspector-support/adapters/basic.js", + "@ember/debug/ember-inspector-support/adapters/bookmarklet.js": "ember-source/@ember/debug/ember-inspector-support/adapters/bookmarklet.js", + "@ember/debug/ember-inspector-support/adapters/chrome.js": "ember-source/@ember/debug/ember-inspector-support/adapters/chrome.js", + "@ember/debug/ember-inspector-support/adapters/firefox.js": "ember-source/@ember/debug/ember-inspector-support/adapters/firefox.js", + "@ember/debug/ember-inspector-support/adapters/index.js": "ember-source/@ember/debug/ember-inspector-support/adapters/index.js", + "@ember/debug/ember-inspector-support/adapters/web-extension.js": "ember-source/@ember/debug/ember-inspector-support/adapters/web-extension.js", + "@ember/debug/ember-inspector-support/adapters/websocket.js": "ember-source/@ember/debug/ember-inspector-support/adapters/websocket.js", + "@ember/debug/ember-inspector-support/container-debug.js": "ember-source/@ember/debug/ember-inspector-support/container-debug.js", + "@ember/debug/ember-inspector-support/data-debug.js": "ember-source/@ember/debug/ember-inspector-support/data-debug.js", + "@ember/debug/ember-inspector-support/debug-port.js": "ember-source/@ember/debug/ember-inspector-support/debug-port.js", + "@ember/debug/ember-inspector-support/deprecation-debug.js": "ember-source/@ember/debug/ember-inspector-support/deprecation-debug.js", + "@ember/debug/ember-inspector-support/general-debug.js": "ember-source/@ember/debug/ember-inspector-support/general-debug.js", + "@ember/debug/ember-inspector-support/index.js": "ember-source/@ember/debug/ember-inspector-support/index.js", + "@ember/debug/ember-inspector-support/libs/capture-render-tree.js": "ember-source/@ember/debug/ember-inspector-support/libs/capture-render-tree.js", + "@ember/debug/ember-inspector-support/libs/promise-assembler.js": "ember-source/@ember/debug/ember-inspector-support/libs/promise-assembler.js", + "@ember/debug/ember-inspector-support/libs/render-tree.js": "ember-source/@ember/debug/ember-inspector-support/libs/render-tree.js", + "@ember/debug/ember-inspector-support/libs/source-map.js": "ember-source/@ember/debug/ember-inspector-support/libs/source-map.js", + "@ember/debug/ember-inspector-support/libs/view-inspection.js": "ember-source/@ember/debug/ember-inspector-support/libs/view-inspection.js", + "@ember/debug/ember-inspector-support/main.js": "ember-source/@ember/debug/ember-inspector-support/main.js", + "@ember/debug/ember-inspector-support/models/profile-manager.js": "ember-source/@ember/debug/ember-inspector-support/models/profile-manager.js", + "@ember/debug/ember-inspector-support/models/profile-node.js": "ember-source/@ember/debug/ember-inspector-support/models/profile-node.js", + "@ember/debug/ember-inspector-support/models/promise.js": "ember-source/@ember/debug/ember-inspector-support/models/promise.js", + "@ember/debug/ember-inspector-support/object-inspector.js": "ember-source/@ember/debug/ember-inspector-support/object-inspector.js", + "@ember/debug/ember-inspector-support/port.js": "ember-source/@ember/debug/ember-inspector-support/port.js", + "@ember/debug/ember-inspector-support/promise-debug.js": "ember-source/@ember/debug/ember-inspector-support/promise-debug.js", + "@ember/debug/ember-inspector-support/render-debug.js": "ember-source/@ember/debug/ember-inspector-support/render-debug.js", + "@ember/debug/ember-inspector-support/route-debug.js": "ember-source/@ember/debug/ember-inspector-support/route-debug.js", + "@ember/debug/ember-inspector-support/services/session.js": "ember-source/@ember/debug/ember-inspector-support/services/session.js", + "@ember/debug/ember-inspector-support/utils/base-object.js": "ember-source/@ember/debug/ember-inspector-support/utils/base-object.js", + "@ember/debug/ember-inspector-support/utils/bound-method.js": "ember-source/@ember/debug/ember-inspector-support/utils/bound-method.js", + "@ember/debug/ember-inspector-support/utils/classify.js": "ember-source/@ember/debug/ember-inspector-support/utils/classify.js", + "@ember/debug/ember-inspector-support/utils/dasherize.js": "ember-source/@ember/debug/ember-inspector-support/utils/dasherize.js", + "@ember/debug/ember-inspector-support/utils/ember-object-names.js": "ember-source/@ember/debug/ember-inspector-support/utils/ember-object-names.js", + "@ember/debug/ember-inspector-support/utils/ember/object/internals.js": "ember-source/@ember/debug/ember-inspector-support/utils/ember/object/internals.js", + "@ember/debug/ember-inspector-support/utils/evented.js": "ember-source/@ember/debug/ember-inspector-support/utils/evented.js", + "@ember/debug/ember-inspector-support/utils/get-object-name.js": "ember-source/@ember/debug/ember-inspector-support/utils/get-object-name.js", + "@ember/debug/ember-inspector-support/utils/name-functions.js": "ember-source/@ember/debug/ember-inspector-support/utils/name-functions.js", + "@ember/debug/ember-inspector-support/utils/on-ready.js": "ember-source/@ember/debug/ember-inspector-support/utils/on-ready.js", + "@ember/debug/ember-inspector-support/utils/type-check.js": "ember-source/@ember/debug/ember-inspector-support/utils/type-check.js", + "@ember/debug/ember-inspector-support/view-debug.js": "ember-source/@ember/debug/ember-inspector-support/view-debug.js", "@ember/debug/index.js": "ember-source/@ember/debug/index.js", "@ember/debug/lib/assert.js": "ember-source/@ember/debug/lib/assert.js", "@ember/debug/lib/capture-render-tree.js": "ember-source/@ember/debug/lib/capture-render-tree.js", @@ -372,4 +414,4 @@ "node": "16.20.0", "pnpm": "8.10.0" } -} +} \ No newline at end of file diff --git a/packages/@ember/debug/ember-inspector-support/adapters/basic.ts b/packages/@ember/debug/ember-inspector-support/adapters/basic.ts new file mode 100644 index 00000000000..b5f1e88a14c --- /dev/null +++ b/packages/@ember/debug/ember-inspector-support/adapters/basic.ts @@ -0,0 +1,156 @@ +/* eslint no-console: 0 */ +import BaseObject from '../utils/base-object'; +import { DEBUG } from '@glimmer/env'; +import { onReady } from '../utils/on-ready'; + +export default class BasicAdapter extends BaseObject { + private _messageCallbacks: any[] = []; + private __environment = ''; + declare interval: number | undefined; + init() { + Promise.resolve(this.connect()).then(() => { + this.onConnectionReady(); + }, null); + + this._messageCallbacks = []; + } + + /** + * Uses the current build's config module to determine + * the environment. + */ + get environment() { + if (!this.__environment) { + this.__environment = DEBUG ? 'development' : 'production'; + } + return this.__environment; + } + + debug(...args: any[]) { + return console.debug(...args); + } + + log(...args: any[]) { + return console.log(...args); + } + + /** + * A wrapper for `console.warn`. + * + */ + warn(...args: any[]) { + return console.warn(...args); + } + + /** + Used to send messages to EmberExtension + + @param {Object} type the message to the send + */ + sendMessage(_options: any) {} + + /** + Register functions to be called + when a message from EmberExtension is received + + @param {Function} callback + */ + onMessageReceived(callback: (msg: any) => void) { + this._messageCallbacks.push(callback); + } + + /** + Inspect a js value or specific DOM node. This usually + means using the current environment's tools + to inspect the node in the DOM. + + For example, in chrome, `inspect(node)` + will open the Elements tab in dev tools + and highlight the DOM node. + For functions, it will open the sources tab and goto the definition + @param {Node|Function} node + */ + inspectValue(_value: any) {} + + _messageReceived(message: any) { + this._messageCallbacks.forEach((callback) => { + callback(message); + }); + } + + /** + * Handle an error caused by EmberDebug. + * + * This function rethrows in development and test envs, + * but warns instead in production. + * + * The idea is to control errors triggered by the inspector + * and make sure that users don't get mislead by inspector-caused + * bugs. + * @param {Error} error + */ + handleError(error: any) { + if (this.environment === 'production') { + if (error && error instanceof Error) { + error = `Error message: ${error.message}\nStack trace: ${error.stack}`; + } + this.warn( + `Ember Inspector has errored.\n` + + `This is likely a bug in the inspector itself.\n` + + `You can report bugs at https://github.com/emberjs/ember-inspector.\n${error}` + ); + } else { + this.warn('EmberDebug has errored:'); + throw error; + } + } + + /** + + A promise that resolves when the connection + with the inspector is set up and ready. + */ + connect() { + return new Promise((resolve, reject) => { + onReady(() => { + if (this.isDestroyed) { + reject(); + } + this.interval = setInterval(() => { + if (document.documentElement.dataset['emberExtension']) { + clearInterval(this.interval); + resolve(true); + } + }, 10); + }); + }); + } + + willDestroy() { + super.willDestroy(); + clearInterval(this.interval); + } + + _isReady = false; + _pendingMessages: any[] = []; + + send(options: any) { + if (this._isReady) { + this.sendMessage(options); + } else { + this._pendingMessages.push(options); + } + } + + /** + Called when the connection is set up. + Flushes the pending messages. + */ + onConnectionReady() { + // Flush pending messages + const messages = this._pendingMessages; + messages.forEach((options) => this.sendMessage(options)); + messages.length = 0; + this._isReady = true; + } +} diff --git a/packages/@ember/debug/ember-inspector-support/adapters/bookmarklet.ts b/packages/@ember/debug/ember-inspector-support/adapters/bookmarklet.ts new file mode 100644 index 00000000000..3bd23cb365a --- /dev/null +++ b/packages/@ember/debug/ember-inspector-support/adapters/bookmarklet.ts @@ -0,0 +1,33 @@ +import BasicAdapter from './basic'; + +export default class BookmarkletAdapter extends BasicAdapter { + init() { + super.init(); + this._listen(); + } + + sendMessage(options: any) { + options = options || {}; + const w = window as any; + w.emberInspector.w.postMessage(options, w.emberInspector.url); + } + + _listen() { + const w = window as any; + window.addEventListener('message', (e) => { + if (e.origin !== w.emberInspector.url) { + return; + } + const message = e.data; + if (message.from === 'devtools') { + this._messageReceived(message); + } + }); + + window.onunload = () => { + this.sendMessage({ + unloading: true, + }); + }; + } +} diff --git a/packages/@ember/debug/ember-inspector-support/adapters/chrome.ts b/packages/@ember/debug/ember-inspector-support/adapters/chrome.ts new file mode 100644 index 00000000000..8d7af1ed068 --- /dev/null +++ b/packages/@ember/debug/ember-inspector-support/adapters/chrome.ts @@ -0,0 +1,2 @@ +import WebExtension from './web-extension'; +export default class ChromeAdapter extends WebExtension {} diff --git a/packages/@ember/debug/ember-inspector-support/adapters/firefox.ts b/packages/@ember/debug/ember-inspector-support/adapters/firefox.ts new file mode 100644 index 00000000000..f1b9d276f0c --- /dev/null +++ b/packages/@ember/debug/ember-inspector-support/adapters/firefox.ts @@ -0,0 +1,24 @@ +/* eslint no-empty:0 */ +import WebExtension from './web-extension'; + +export default class FirefoxAdapter extends WebExtension { + debug(...args: unknown[]) { + // WORKAROUND: temporarily workaround issues with firebug console object: + // - https://github.com/tildeio/ember-extension/issues/94 + // - https://github.com/firebug/firebug/pull/109 + // - https://code.google.com/p/fbug/issues/detail?id=7045 + try { + super.debug(...args); + } catch {} + } + + log(...args: any[]) { + // WORKAROUND: temporarily workaround issues with firebug console object: + // - https://github.com/tildeio/ember-extension/issues/94 + // - https://github.com/firebug/firebug/pull/109 + // - https://code.google.com/p/fbug/issues/detail?id=7045 + try { + super.log(...args); + } catch {} + } +} diff --git a/packages/@ember/debug/ember-inspector-support/adapters/index.ts b/packages/@ember/debug/ember-inspector-support/adapters/index.ts new file mode 100644 index 00000000000..68a6a1dd93a --- /dev/null +++ b/packages/@ember/debug/ember-inspector-support/adapters/index.ts @@ -0,0 +1,15 @@ +import { default as BasicAdapter } from './basic'; +import { default as BookmarkletAdapter } from './bookmarklet'; +import { default as ChromeAdapter } from './chrome'; +import { default as FirefoxAdapter } from './firefox'; +import { default as WebsocketAdapter } from './websocket'; +import { default as WebExtensionAdapter } from './web-extension'; + +export default { + basic: BasicAdapter, + bookmarklet: BookmarkletAdapter, + chrome: ChromeAdapter, + firefox: FirefoxAdapter, + webExtension: WebExtensionAdapter, + websocket: WebsocketAdapter, +}; diff --git a/packages/@ember/debug/ember-inspector-support/adapters/web-extension.ts b/packages/@ember/debug/ember-inspector-support/adapters/web-extension.ts new file mode 100644 index 00000000000..4f7d1637181 --- /dev/null +++ b/packages/@ember/debug/ember-inspector-support/adapters/web-extension.ts @@ -0,0 +1,93 @@ +import BasicAdapter from './basic'; +import { run } from '@ember/runloop'; + +export default class WebExtension extends BasicAdapter { + private declare _channel: MessageChannel; + private declare _chromePort: MessagePort; + init() { + this._channel = new MessageChannel(); + this._chromePort = this._channel?.port1; + super.init(); + } + + connect() { + const channel = this._channel; + return super.connect().then(() => { + window.postMessage('debugger-client', '*', [channel.port2]); + this._listen(); + }, null); + } + + sendMessage(options = {}) { + // If prototype extensions are disabled, `Ember.A()` arrays + // would not be considered native arrays, so it's not possible to + // "clone" them through postMessage unless they are converted to a + // native array. + try { + this._chromePort.postMessage(options); + } catch (e) { + // eslint-disable-next-line no-console + console.log('failed to send message', e); + } + } + + /** + * Open the devtools "Elements" and select an DOM node. + * + * @param {Node|Function} value The DOM node to select + */ + inspectValue(value: any) { + // NOTE: + // + // Basically, we are just trying to call `inspect(node)` here. + // However, `inspect` is a special function that is in the global + // scope but not on the global object (i.e. `window.inspect`) does + // not work. This sometimes causes problems, because, e.g. if the + // page has a div with the ID `inspect`, `window.inspect` will point + // to that div and shadow the "global" inspect function with no way + // to get it back. That causes "`inspect` is not a function" errors. + // + // As it turns out, however, when the extension page evals, the + // `inspect` function does not get shadowed. So, we can ask the + // inspector extension page to call that function for us, using + // `inspected.Window.eval('inspect(node)')`. + // + // However, since we cannot just send the DOM node directly to the + // extension, we will have to store it in a temporary global variable + // so that the extension can find it. + + let name = `__EMBER_INSPECTOR_${(Math.random() * 100000000).toFixed(0)}`; + + (window as any)[name] = value; + this.namespace.port.send('view:inspectJSValue', { name }); + } + + _listen() { + let chromePort = this._chromePort; + + chromePort.addEventListener('message', (event) => { + const message = event.data; + + // We should generally not be run-wrapping here. Starting a runloop in + // ember-debug will cause the inspected app to revalidate/rerender. We + // are generally not intending to cause changes to the rendered output + // of the app, so this is generally unnecessary, and in big apps this + // could be quite slow. There is nothing special about the `view:*` + // messages – I (GC) just happened to have reviewed all of them recently + // and can be quite sure that they don't need the runloop. We should + // audit the rest of them and see if we can remove the else branch. I + // think we most likely can. In the limited cases (if any) where the + // runloop is needed, the callback code should just do the wrapping + // themselves. + if (message.type.startsWith('view:')) { + this._messageReceived(message); + } else { + run(() => { + this._messageReceived(message); + }); + } + }); + + chromePort.start(); + } +} diff --git a/packages/@ember/debug/ember-inspector-support/adapters/websocket.ts b/packages/@ember/debug/ember-inspector-support/adapters/websocket.ts new file mode 100644 index 00000000000..4d6664c5da9 --- /dev/null +++ b/packages/@ember/debug/ember-inspector-support/adapters/websocket.ts @@ -0,0 +1,63 @@ +import BasicAdapter from './basic'; +import { onReady } from '@ember/debug/ember-inspector-support/utils/on-ready'; +import { run } from '@ember/runloop'; + +export default class WebsocketAdapter extends BasicAdapter { + sendMessage(options = {}) { + this.socket.emit('emberInspectorMessage', options); + } + + get socket() { + return (window as any).EMBER_INSPECTOR_CONFIG.remoteDebugSocket; + } + + _listen() { + this.socket.on('emberInspectorMessage', (message: any) => { + // We should generally not be run-wrapping here. Starting a runloop in + // ember-debug will cause the inspected app to revalidate/rerender. We + // are generally not intending to cause changes to the rendered output + // of the app, so this is generally unnecessary, and in big apps this + // could be quite slow. There is nothing special about the `view:*` + // messages – I (GC) just happened to have reviewed all of them recently + // and can be quite sure that they don't need the runloop. We should + // audit the rest of them and see if we can remove the else branch. I + // think we most likely can. In the limited cases (if any) where the + // runloop is needed, the callback code should just do the wrapping + // themselves. + if (message.type.startsWith('view:')) { + this._messageReceived(message); + } else { + run(() => { + this._messageReceived(message); + }); + } + }); + } + + _disconnect() { + this.socket.removeAllListeners('emberInspectorMessage'); + } + + connect() { + return new Promise((resolve, reject) => { + onReady(() => { + if (this.isDestroyed) { + reject(); + } + const EMBER_INSPECTOR_CONFIG = (window as any).EMBER_INSPECTOR_CONFIG; + if ( + typeof EMBER_INSPECTOR_CONFIG === 'object' && + EMBER_INSPECTOR_CONFIG.remoteDebugSocket + ) { + resolve(true); + } + }); + }).then(() => { + this._listen(); + }); + } + + willDestroy() { + this._disconnect(); + } +} diff --git a/packages/@ember/debug/ember-inspector-support/container-debug.ts b/packages/@ember/debug/ember-inspector-support/container-debug.ts new file mode 100644 index 00000000000..d38e0d53a62 --- /dev/null +++ b/packages/@ember/debug/ember-inspector-support/container-debug.ts @@ -0,0 +1,105 @@ +import DebugPort from './debug-port'; + +export default class ContainerDebug extends DebugPort { + declare objectToConsole: any; + get objectInspector() { + return this.namespace?.objectInspector; + } + + get container() { + return this.namespace?.owner?.__container__; + } + + TYPES_TO_SKIP = [ + 'component-lookup', + 'container-debug-adapter', + 'resolver-for-debugging', + 'event_dispatcher', + ]; + + static { + this.prototype.portNamespace = 'container'; + this.prototype.messages = { + getTypes(this: ContainerDebug) { + this.sendMessage('types', { + types: this.getTypes(), + }); + }, + getInstances(this: ContainerDebug, message: any) { + let instances = this.getInstances(message.containerType); + if (instances) { + this.sendMessage('instances', { + instances, + status: 200, + }); + } else { + this.sendMessage('instances', { + status: 404, + }); + } + }, + sendInstanceToConsole(this: ContainerDebug, message: any) { + const instance = this.container.lookup(message.name); + this.objectToConsole.sendValueToConsole(instance); + }, + }; + } + + typeFromKey(key: string) { + return key.split(':').shift()!; + } + + nameFromKey(key: string) { + return key.split(':').pop(); + } + + shouldHide(type: string) { + return type[0] === '-' || this.TYPES_TO_SKIP.indexOf(type) !== -1; + } + + instancesByType() { + let key; + let instancesByType: Record = {}; + let cache = this.container.cache; + // Detect if InheritingDict (from Ember < 1.8) + if (typeof cache.dict !== 'undefined' && typeof cache.eachLocal !== 'undefined') { + cache = cache.dict; + } + for (key in cache) { + const type = this.typeFromKey(key); + if (this.shouldHide(type)) { + continue; + } + if (instancesByType[type] === undefined) { + instancesByType[type] = []; + } + instancesByType[type].push({ + fullName: key, + instance: cache[key], + }); + } + return instancesByType; + } + + getTypes() { + let key; + let types = []; + const instancesByType = this.instancesByType(); + for (key in instancesByType) { + types.push({ name: key, count: instancesByType[key].length }); + } + return types; + } + + getInstances(type: any) { + const instances = this.instancesByType()[type]; + if (!instances) { + return null; + } + return instances.map((item: any) => ({ + name: this.nameFromKey(item.fullName), + fullName: item.fullName, + inspectable: this.objectInspector.canSend(item.instance), + })); + } +} diff --git a/packages/@ember/debug/ember-inspector-support/data-debug.ts b/packages/@ember/debug/ember-inspector-support/data-debug.ts new file mode 100644 index 00000000000..04fcc0f4139 --- /dev/null +++ b/packages/@ember/debug/ember-inspector-support/data-debug.ts @@ -0,0 +1,191 @@ +import DebugPort from './debug-port'; +import { guidFor } from '@ember/debug/ember-inspector-support/utils/ember/object/internals'; + +export default class DataDebug extends DebugPort { + declare portNamespace: string; + declare sentTypes: Record; + declare sentRecords: Record; + init() { + super.init(); + this.sentTypes = {}; + this.sentRecords = {}; + } + + releaseTypesMethod: Function | null = null; + releaseRecordsMethod: Function | null = null; + + get adapter() { + const owner = this.namespace?.owner; + + // dataAdapter:main is deprecated + let adapter = this._resolve('data-adapter:main') && owner.lookup('data-adapter:main'); + // column limit is now supported at the inspector level + if (adapter) { + adapter.attributeLimit = 100; + return adapter; + } + + return null; + } + + _resolve(name: string) { + const owner = this.namespace?.owner; + + return owner.resolveRegistration(name); + } + + get objectInspector() { + return this.namespace?.objectInspector; + } + + modelTypesAdded(types: any[]) { + let typesToSend; + typesToSend = types.map((type) => this.wrapType(type)); + this.sendMessage('modelTypesAdded', { + modelTypes: typesToSend, + }); + } + + modelTypesUpdated(types: any[]) { + let typesToSend = types.map((type) => this.wrapType(type)); + this.sendMessage('modelTypesUpdated', { + modelTypes: typesToSend, + }); + } + + wrapType(type: any) { + const objectId = guidFor(type.object); + this.sentTypes[objectId] = type; + + return { + columns: type.columns, + count: type.count, + name: type.name, + objectId, + }; + } + + recordsAdded(recordsReceived: any[]) { + let records = recordsReceived.map((record) => this.wrapRecord(record)); + this.sendMessage('recordsAdded', { records }); + } + + recordsUpdated(recordsReceived: any[]) { + let records = recordsReceived.map((record) => this.wrapRecord(record)); + this.sendMessage('recordsUpdated', { records }); + } + + recordsRemoved(index: number, count: number) { + this.sendMessage('recordsRemoved', { index, count }); + } + + wrapRecord(record: any) { + const objectId = guidFor(record.object); + let columnValues: Record = {}; + let searchKeywords: any[] = []; + this.sentRecords[objectId] = record; + // make objects clonable + for (let i in record.columnValues) { + columnValues[i] = this.objectInspector.inspect(record.columnValues[i]); + } + // make sure keywords can be searched and clonable + searchKeywords = record.searchKeywords.filter( + (keyword: any) => typeof keyword === 'string' || typeof keyword === 'number' + ); + return { + columnValues, + searchKeywords, + filterValues: record.filterValues, + color: record.color, + objectId, + }; + } + + releaseTypes() { + if (this.releaseTypesMethod) { + this.releaseTypesMethod(); + this.releaseTypesMethod = null; + this.sentTypes = {}; + } + } + + releaseRecords() { + if (this.releaseRecordsMethod) { + this.releaseRecordsMethod(); + this.releaseRecordsMethod = null; + this.sentRecords = {}; + } + } + + willDestroy() { + super.willDestroy(); + this.releaseRecords(); + this.releaseTypes(); + } + + static { + this.prototype.portNamespace = 'data'; + this.prototype.messages = { + checkAdapter(this: DataDebug) { + this.sendMessage('hasAdapter', { hasAdapter: Boolean(this.adapter) }); + }, + + getModelTypes(this: DataDebug) { + this.modelTypesAdded([]); + this.releaseTypes(); + this.releaseTypesMethod = this.adapter.watchModelTypes( + (types: any) => { + this.modelTypesAdded(types); + }, + (types: any) => { + this.modelTypesUpdated(types); + } + ); + }, + + releaseModelTypes(this: DataDebug) { + this.releaseTypes(); + }, + + getRecords(this: DataDebug, message: any) { + const type = this.sentTypes[message.objectId]; + this.releaseRecords(); + + let typeOrName; + if (this.adapter.acceptsModelName) { + // Ember >= 1.3 + typeOrName = type.name; + } + + this.recordsAdded([]); + let releaseMethod = this.adapter.watchRecords( + typeOrName, + (recordsReceived: any) => { + this.recordsAdded(recordsReceived); + }, + (recordsUpdated: any) => { + this.recordsUpdated(recordsUpdated); + }, + (index: number, count: number) => { + this.recordsRemoved(index, count); + } + ); + this.releaseRecordsMethod = releaseMethod; + }, + + releaseRecords(this: DataDebug) { + this.releaseRecords(); + }, + + inspectModel(this: DataDebug, message: any) { + this.objectInspector.sendObject(this.sentRecords[message.objectId].object); + }, + + getFilters(this: DataDebug) { + this.sendMessage('filters', { + filters: this.adapter.getFilters(), + }); + }, + }; + } +} diff --git a/packages/@ember/debug/ember-inspector-support/debug-port.ts b/packages/@ember/debug/ember-inspector-support/debug-port.ts new file mode 100644 index 00000000000..016215f88d5 --- /dev/null +++ b/packages/@ember/debug/ember-inspector-support/debug-port.ts @@ -0,0 +1,48 @@ +import BaseObject from '@ember/debug/ember-inspector-support/utils/base-object'; + +export default class DebugPort extends BaseObject { + declare port: any; + declare portNamespace: string; + declare messages: Record; + constructor(data: any) { + super(data); + if (!data) { + throw new Error('need to pass data'); + } + this.port = this.namespace?.port; + this.setupOrRemovePortListeners('on'); + } + + willDestroy() { + super.willDestroy(); + this.setupOrRemovePortListeners('off'); + } + + sendMessage(name: string, message?: any) { + if (this.isDestroyed) return; + this.port.send(this.messageName(name), message); + } + + messageName(name: string) { + let messageName = name; + if (this.portNamespace) { + messageName = `${this.portNamespace}:${messageName}`; + } + return messageName; + } + + /** + * Setup or tear down port listeners. Call on `init` and `willDestroy` + * @param {String} onOrOff 'on' or 'off' the functions to call i.e. port.on or port.off for adding or removing listeners + */ + setupOrRemovePortListeners(onOrOff: 'on' | 'off') { + let port = this.port; + let messages = this.messages; + + for (let name in messages) { + if (Object.prototype.hasOwnProperty.call(messages, name)) { + port[onOrOff](this.messageName(name), this, messages[name]); + } + } + } +} diff --git a/packages/@ember/debug/ember-inspector-support/deprecation-debug.ts b/packages/@ember/debug/ember-inspector-support/deprecation-debug.ts new file mode 100644 index 00000000000..01812ddd7b7 --- /dev/null +++ b/packages/@ember/debug/ember-inspector-support/deprecation-debug.ts @@ -0,0 +1,270 @@ +import DebugPort from './debug-port'; +import SourceMap from '@ember/debug/ember-inspector-support/libs/source-map'; + +import { registerDeprecationHandler } from '@ember/debug'; +import { guidFor } from '@ember/debug/ember-inspector-support/utils/ember/object/internals'; +import { cancel, debounce } from '@ember/runloop'; +import type SourceMapSupport from '@ember/debug/ember-inspector-support/libs/source-map'; + +export default class DeprecationDebug extends DebugPort { + declare options: any; + private declare _warned: boolean; + declare debounce: any; + private declare _watching: any; + declare deprecationsToSend: { + stackStr: string; + message: string; + url: string; + count: number; + id: string; + sources: any[]; + }[]; + private declare sourceMap: SourceMapSupport; + declare groupedDeprecations: any; + declare deprecations: any; + private declare __emberCliConfig: any; + static { + this.prototype.portNamespace = 'deprecation'; + this.prototype.sourceMap = new SourceMap(); + this.prototype.messages = { + watch(this: DeprecationDebug) { + this._watching = true; + let grouped = this.groupedDeprecations; + let deprecations = []; + for (let i in grouped) { + if (!Object.prototype.hasOwnProperty.call(grouped, i)) { + continue; + } + deprecations.push(grouped[i]); + } + this.sendMessage('deprecationsAdded', { + deprecations, + }); + this.sendPending(); + }, + + sendStackTraces( + this: DeprecationDebug, + message: { deprecation: { message: string; sources: { stackStr: string }[] } } + ) { + let deprecation = message.deprecation; + deprecation.sources.forEach((source) => { + let stack = source.stackStr; + let stackArray = stack.split('\n'); + stackArray.unshift(`Ember Inspector (Deprecation Trace): ${deprecation.message || ''}`); + this.adapter.log(stackArray.join('\n')); + }); + }, + + getCount(this: DeprecationDebug) { + this.sendCount(); + }, + + clear(this: DeprecationDebug) { + cancel(this.debounce); + this.deprecations.length = 0; + this.groupedDeprecations = {}; + this.sendCount(); + }, + + release(this: DeprecationDebug) { + this._watching = false; + }, + + setOptions(this: DeprecationDebug, { options }: any) { + this.options.toggleDeprecationWorkflow = options.toggleDeprecationWorkflow; + }, + }; + } + + get adapter() { + return this.port?.adapter; + } + + get emberCliConfig() { + return this.__emberCliConfig || this.namespace?.generalDebug.emberCliConfig; + } + + set emberCliConfig(value) { + this.__emberCliConfig = value; + } + + constructor(data: any) { + super(data); + + this.deprecations = []; + this.deprecationsToSend = []; + this.groupedDeprecations = {}; + this.options = { + toggleDeprecationWorkflow: false, + }; + + this.handleDeprecations(); + } + + /** + * Checks if ember-cli and looks for source maps. + */ + fetchSourceMap(stackStr: string) { + if (this.emberCliConfig && this.emberCliConfig.environment === 'development') { + return this.sourceMap.map(stackStr).then((mapped: any[]) => { + if (mapped && mapped.length > 0) { + let source = mapped.find( + (item: any) => + item.source && + Boolean(item.source.match(new RegExp(this.emberCliConfig.modulePrefix))) + ); + + if (source) { + source.found = true; + } else { + source = mapped[0]; + source.found = false; + } + return source; + } + }, null); + } else { + return Promise.resolve(null); + } + } + + sendPending() { + if (this.isDestroyed) { + return; + } + + let deprecations: { stackStr: string }[] = []; + + let promises = Promise.all( + this.deprecationsToSend.map((deprecation) => { + let obj: any; + let promise = Promise.resolve(undefined); + let grouped = this.groupedDeprecations; + this.deprecations.push(deprecation); + const id = guidFor(deprecation.message); + obj = grouped[id]; + if (obj) { + obj.count++; + obj.url = obj.url || deprecation.url; + } else { + obj = deprecation; + obj.count = 1; + obj.id = id; + obj.sources = []; + grouped[id] = obj; + } + let found = obj.sources.find((s: any) => s.stackStr === deprecation.stackStr); + if (!found) { + let stackStr = deprecation.stackStr; + promise = this.fetchSourceMap(stackStr).then((map) => { + obj.sources.push({ map, stackStr }); + if (map) { + obj.hasSourceMap = true; + } + return undefined; + }, null); + } + return promise.then(() => { + delete obj.stackStr; + if (!deprecations.includes(obj)) { + deprecations.push(obj); + } + }, null); + }) + ); + + promises.then(() => { + this.sendMessage('deprecationsAdded', { deprecations }); + this.deprecationsToSend.length = 0; + this.sendCount(); + }, null); + } + + sendCount() { + if (this.isDestroyed) { + return; + } + + this.sendMessage('count', { + count: this.deprecations.length + this.deprecationsToSend.length, + }); + } + + willDestroy() { + cancel(this.debounce); + return super.willDestroy(); + } + + handleDeprecations() { + registerDeprecationHandler((message, options, next) => { + if (!this.adapter) { + next(message, options); + return; + } + + /* global __fail__*/ + + let error: any; + + try { + // @ts-expect-error When using new Error, we can't do the arguments check for Chrome. Alternatives are welcome + __fail__.fail(); + } catch (e) { + error = e; + } + + let stack; + let stackStr = ''; + if (error.stack) { + // var stack; + if (error['arguments']) { + // Chrome + stack = error.stack + .replace(/^\s+at\s+/gm, '') + .replace(/^([^(]+?)([\n$])/gm, '{anonymous}($1)$2') + .replace(/^Object.\s*\(([^)]+)\)/gm, '{anonymous}($1)') + .split('\n'); + stack.shift(); + } else { + // Firefox + stack = error.stack + .replace(/(?:\n@:0)?\s+$/m, '') + .replace(/^\(/gm, '{anonymous}(') + .split('\n'); + } + + stackStr = `\n ${stack.slice(2).join('\n ')}`; + } + + let url; + if (options && typeof options === 'object') { + url = options.url; + } + + const deprecation = { message, stackStr, url } as any; + + // For ember-debug testing we usually don't want + // to catch deprecations + if (!this.namespace?.IGNORE_DEPRECATIONS) { + this.deprecationsToSend.push(deprecation); + cancel(this.debounce); + if (this._watching) { + this.debounce = debounce(this, this.sendPending, 100); + } else { + this.debounce = debounce(this, this.sendCount, 100); + } + if (!this._warned) { + this.adapter.warn( + 'Deprecations were detected, see the Ember Inspector deprecations tab for more details.' + ); + this._warned = true; + } + } + + if (this.options.toggleDeprecationWorkflow) { + next(message, options); + } + }); + } +} diff --git a/packages/@ember/debug/ember-inspector-support/general-debug.ts b/packages/@ember/debug/ember-inspector-support/general-debug.ts new file mode 100644 index 00000000000..bbe8d2d93fc --- /dev/null +++ b/packages/@ember/debug/ember-inspector-support/general-debug.ts @@ -0,0 +1,137 @@ +/* eslint no-empty:0 */ +import libraries from '@ember/-internals/metal/lib/libraries'; +import DebugPort from './debug-port'; + +/** + * Class that handles gathering general information of the inspected app. + * ex: + * - Determines if the app was booted + * - Gathers the libraries. Found in the info tab of the inspector. + * - Gathers ember-cli configuration information from the meta tags. + * + * @module ember-debug/general-debug + */ +export default class GeneralDebug extends DebugPort { + declare portNamespace: string; + declare messages: { + /** + * Called from the inspector to check if the inspected app has been booted. + */ + applicationBooted(): void; + /** + * Called from the inspector to fetch the libraries that are displayed in + * the info tab. + */ + getLibraries(): void; + getEmberCliConfig(): void; + /** + * Called from the inspector to refresh the inspected app. + * Used in case the inspector was opened late and therefore missed capturing + * all info. + */ + refresh(): void; + }; + /** + * Fetches the ember-cli configuration info and sets them on + * the `emberCliConfig` property. + */ + getAppConfig() { + let found = findMetaTag('name', /environment$/); + if (found) { + try { + return JSON.parse(decodeURI(found.getAttribute('content')!)); + } catch {} + } + } + + /** + * Passed on creation. + * + * @type {EmberDebug} + */ + + /** + * Set on creation. + * Contains ember-cli configuration info. + * + * Info used to determine the file paths of an ember-cli app. + */ + emberCliConfig = this.getAppConfig(); + + /** + * Sends a reply back indicating if the app has been booted. + * + * `__inspector__booted` is a property set on the application instance + * when the ember-debug is inserted into the target app. + * see: startup-wrapper. + */ + sendBooted() { + this.sendMessage('applicationBooted', { + booted: this.namespace.owner.__inspector__booted, + }); + } + + /** + * Sends a reply back indicating that ember-debug has been reset. + * We need to reset ember-debug to remove state between tests. + */ + sendReset() { + this.sendMessage('reset'); + } + + static { + /** + * Used by the DebugPort + * + * @type {String} + */ + this.prototype.portNamespace = 'general'; + this.prototype.messages = { + /** + * Called from the inspector to check if the inspected app has been booted. + */ + applicationBooted(this: GeneralDebug) { + this.sendBooted(); + }, + + /** + * Called from the inspector to fetch the libraries that are displayed in + * the info tab. + */ + getLibraries(this: GeneralDebug) { + this.sendMessage('libraries', { + libraries: libraries?._registry, + }); + }, + + getEmberCliConfig(this: GeneralDebug) { + this.sendMessage('emberCliConfig', { + emberCliConfig: this.emberCliConfig, + }); + }, + + /** + * Called from the inspector to refresh the inspected app. + * Used in case the inspector was opened late and therefore missed capturing + * all info. + */ + refresh() { + window.location.reload(); + }, + }; + } +} + +/** + * Finds a meta tag by searching through a certain meta attribute. + */ +function findMetaTag(attribute: string, regExp = /.*/) { + let metas = document.querySelectorAll(`meta[${attribute}]`); + for (let i = 0; i < metas.length; i++) { + let match = metas[i]!.getAttribute(attribute)?.match(regExp); + if (match) { + return metas[i]; + } + } + return null; +} diff --git a/packages/@ember/debug/ember-inspector-support/index.ts b/packages/@ember/debug/ember-inspector-support/index.ts new file mode 100644 index 00000000000..6b98b7d007c --- /dev/null +++ b/packages/@ember/debug/ember-inspector-support/index.ts @@ -0,0 +1,314 @@ +import { VERSION } from '@ember/version'; +import type Adapters from './adapters'; +import { guidFor } from '@ember/object/internals'; +import { A } from '@ember/array'; +import Namespace from '@ember/application/namespace'; +import Application from '@ember/application'; +import type ApplicationInstance from '@ember/application/instance'; + +export function setupEmberInspectorSupport() { + if ((window as any).EMBER_INSPECTOR_SUPPORT_BUNDLED) { + return; + } + (window as any).EMBER_INSPECTOR_SUPPORT_BUNDLED = true; + window.addEventListener('ember-inspector-loaded' as any, (event: CustomEvent) => { + const adapter = event.detail.adapter; + const EMBER_VERSIONS_SUPPORTED = event.detail.EMBER_VERSIONS_SUPPORTED; + void loadEmberDebug(adapter, EMBER_VERSIONS_SUPPORTED); + }); + + const e = new Event('ember-inspector-support-setup'); + window.dispatchEvent(e); +} + +// eslint-disable-next-line disable-features/disable-async-await +async function loadEmberDebug( + adapter: keyof typeof Adapters, + EMBER_VERSIONS_SUPPORTED: [string, string] +) { + const w = window as any; + // global to prevent injection + if (w.NO_EMBER_DEBUG) { + return; + } + + // @ts-ignore + const Adapters = await import('./adapters'); + // @ts-ignore + const MainModule = await import('./main'); + + if (!versionTest(VERSION, EMBER_VERSIONS_SUPPORTED)) { + // Wrong inspector version. Redirect to the correct version. + sendVersionMiss(); + return; + } + + // prevent from injecting twice + if (!w.EmberInspector) { + w.EmberInspector = new MainModule(); + w.EmberInspector.Adapter = Adapters[adapter]; + + onApplicationStart(function appStarted(instance: ApplicationInstance) { + let app = instance.application; + if (!('__inspector__booted' in app)) { + // Watch for app reset/destroy + app.reopen({ + reset: function (this: Application) { + (this as any).__inspector__booted = false; + this._super.apply(this, arguments as any); + }, + }); + } + + if (instance && !('__inspector__booted' in instance)) { + instance.reopen({ + // Clean up on instance destruction + willDestroy() { + if (w.EmberInspector.owner === instance) { + w.EmberInspector.destroyContainer(); + w.EmberInspector.clear(); + } + return (this as any)._super.apply(this, arguments); + }, + }); + + if (!w.EmberInspector._application) { + setTimeout(() => bootEmberInspector(instance), 0); + } + } + }); + } + + function bootEmberInspector(appInstance: ApplicationInstance) { + (appInstance.application as any).__inspector__booted = true; + (appInstance as any).__inspector__booted = true; + + // Boot the inspector (or re-boot if already booted, for example in tests) + w.EmberInspector._application = appInstance.application; + w.EmberInspector.owner = appInstance; + w.EmberInspector.start(true); + } + + // There's probably a better way + // to determine when the application starts + // but this definitely works + function onApplicationStart(callback: Function) { + const adapterInstance = new Adapters[adapter](); + + adapterInstance.onMessageReceived(function (message) { + if (message.type === 'app-picker-loaded') { + sendApps(adapterInstance, getApplications()); + } + + if (message.type === 'app-selected') { + let current = w.EmberInspector._application; + let selected = getApplications().find((app: any) => guidFor(app) === message.applicationId); + + if (selected && current !== selected && selected.__deprecatedInstance__) { + bootEmberInspector(selected.__deprecatedInstance__); + } + } + }); + + let apps = getApplications(); + + sendApps(adapterInstance, apps); + + function loadInstance(app: Application) { + const applicationInstances = app._applicationInstances && [...app._applicationInstances]; + let instance = app.__deprecatedInstance__ || applicationInstances[0]; + if (instance) { + // App started + setupInstanceInitializer(app, callback); + callback(instance); + return true; + } + return; + } + + let app: Application; + for (let i = 0, l = apps.length; i < l; i++) { + app = apps[i]; + // We check for the existance of an application instance because + // in Ember > 3 tests don't destroy the app when they're done but the app has no booted instances. + if (app._readinessDeferrals === 0) { + if (loadInstance(app)) { + break; + } + } + + // app already run initializers, but no instance, use _bootPromise and didBecomeReady + if (app._bootPromise) { + app._bootPromise.then((app) => { + loadInstance(app); + }); + } + + app.reopen({ + didBecomeReady(this: Application) { + this._super.apply(this, arguments as any); + setTimeout(() => loadInstance(app), 0); + }, + }); + } + Application.initializer({ + name: 'ember-inspector-booted', + initialize: function (app) { + setupInstanceInitializer(app, callback); + }, + }); + } + + function setupInstanceInitializer(app: Application, callback: Function) { + if (!(app as any).__inspector__setup) { + (app as any).__inspector__setup = true; + + // We include the app's guid in the initializer name because in Ember versions < 3 + // registering an instance initializer with the same name, even if on a different app, + // triggers an error because instance initializers seem to be global instead of per app. + app.instanceInitializer({ + name: 'ember-inspector-app-instance-booted-' + guidFor(app), + initialize: function (instance) { + callback(instance); + }, + }); + } + } + + /** + * Get all the Ember.Application instances from Ember.Namespace.NAMESPACES + * and add our own applicationId and applicationName to them + */ + function getApplications() { + let namespaces = A(Namespace.NAMESPACES); + + let apps = namespaces.filter(function (namespace) { + return namespace instanceof Application; + }); + + return apps.map(function (app: any) { + // Add applicationId and applicationName to the app + let applicationId = guidFor(app); + let applicationName = app.name || app.modulePrefix || `(unknown app - ${applicationId})`; + + Object.assign(app, { + applicationId, + applicationName, + }); + + return app; + }); + } + + let channel = new MessageChannel(); + let port = channel.port1; + window.postMessage('debugger-client', '*', [channel.port2]); + + let registeredMiss = false; + + /** + * This function is called if the app's Ember version + * is not supported by this version of the inspector. + * + * It sends a message to the inspector app to redirect + * to an inspector version that supports this Ember version. + */ + function sendVersionMiss() { + if (registeredMiss) { + return; + } + + registeredMiss = true; + + port.addEventListener('message', (message) => { + if (message.type === 'check-version') { + sendVersionMismatch(); + } + }); + + sendVersionMismatch(); + + port.start(); + + function sendVersionMismatch() { + port.postMessage({ + name: 'version-mismatch', + version: VERSION, + from: 'inspectedWindow', + }); + } + } + + function sendApps(adapter: any, apps: any[]) { + const serializedApps = apps.map((app) => { + return { + applicationName: app.applicationName, + applicationId: app.applicationId, + }; + }); + + adapter.sendMessage({ + type: 'apps-loaded', + apps: serializedApps, + from: 'inspectedWindow', + }); + } + + /** + * Checks if a version is between two different versions. + * version should be >= left side, < right side + */ + function versionTest(version: string, between: [string, string]) { + let fromVersion = between[0]; + let toVersion = between[1]; + + if (compareVersion(version, fromVersion) === -1) { + return false; + } + return !toVersion || compareVersion(version, toVersion) === -1; + } + + /** + * Compares two Ember versions. + * + * Returns: + * `-1` if version1 < version + * 0 if version1 == version2 + * 1 if version1 > version2 + */ + function compareVersion(version1: string, version2: string) { + let compared, i; + let version1Split = cleanupVersion(version1).split('.'); + let version2Split = cleanupVersion(version2).split('.'); + for (i = 0; i < 3; i++) { + compared = compare(Number(version1Split[i]), Number(version2Split[i])); + if (compared !== 0) { + return compared; + } + } + return 0; + } + + /** + * Remove -alpha, -beta, etc from versions + */ + function cleanupVersion(version: string) { + return version.replace(/-.*/g, ''); + } + + /** + * 0: same + * -1: < + * 1: > + */ + function compare(val: number, number: number) { + if (val === number) { + return 0; + } else if (val < number) { + return -1; + } else if (val > number) { + return 1; + } + return; + } +} diff --git a/packages/@ember/debug/ember-inspector-support/libs/capture-render-tree.ts b/packages/@ember/debug/ember-inspector-support/libs/capture-render-tree.ts new file mode 100644 index 00000000000..65e9656c02a --- /dev/null +++ b/packages/@ember/debug/ember-inspector-support/libs/capture-render-tree.ts @@ -0,0 +1,15 @@ +import captureRenderTree from '@ember/debug/lib/capture-render-tree'; +import { ENV } from '@ember/-internals/environment'; + +let capture = captureRenderTree; +// Ember 3.14+ comes with debug render tree, but the version in 3.14.0/3.14.1 is buggy +if (captureRenderTree) { + if (ENV._DEBUG_RENDER_TREE) { + capture = captureRenderTree; + } else { + capture = function captureRenderTree() { + return []; + }; + } +} +export default capture; diff --git a/packages/@ember/debug/ember-inspector-support/libs/promise-assembler.ts b/packages/@ember/debug/ember-inspector-support/libs/promise-assembler.ts new file mode 100644 index 00000000000..3ef27ddf3e1 --- /dev/null +++ b/packages/@ember/debug/ember-inspector-support/libs/promise-assembler.ts @@ -0,0 +1,190 @@ +/** + Original implementation and the idea behind the `PromiseAssembler`, + `Promise` model, and other work related to promise inspection was done + by Stefan Penner (@stefanpenner) thanks to McGraw Hill Education (@mhelabs) + and Yapp Labs (@yapplabs). + */ + +import PromiseModel from '@ember/debug/ember-inspector-support/models/promise'; +import RSVP from 'rsvp'; +import BaseObject from '@ember/debug/ember-inspector-support/utils/base-object'; +import Evented from '../utils/evented'; + +export type PromiseUpdatedEvent = { + promise: PromiseModel; +}; + +export type PromiseChainedEvent = { + promise: PromiseModel; + child: PromiseModel; +}; + +class PromiseAssembler extends Evented.extend(BaseObject) { + // RSVP lib to debug + isStarted = false; + declare RSVP: any; + declare all: any[]; + declare promiseIndex: Record; + promiseChained: ((e: any) => void) | null = null; + promiseRejected: ((e: any) => void) | null = null; + promiseFulfilled: ((e: any) => void) | null = null; + promiseCreated: ((e: any) => void) | null = null; + + static { + this.prototype.RSVP = RSVP; + } + + constructor(data?: any) { + super(data); + Evented.applyTo(this); + } + + init() { + super.init(); + this.all = []; + this.promiseIndex = {}; + } + + start() { + this.RSVP.configure('instrument', true); + + this.promiseChained = (e: any) => { + chain.call(this, e); + }; + this.promiseRejected = (e: any) => { + reject.call(this, e); + }; + this.promiseFulfilled = (e: any) => { + fulfill.call(this, e); + }; + this.promiseCreated = (e: any) => { + create.bind(this)(e); + }; + + this.RSVP.on('chained', this.promiseChained); + this.RSVP.on('rejected', this.promiseRejected); + this.RSVP.on('fulfilled', this.promiseFulfilled); + this.RSVP.on('created', this.promiseCreated); + + this.isStarted = true; + } + + stop() { + if (this.isStarted) { + this.RSVP.configure('instrument', false); + this.RSVP.off('chained', this.promiseChained); + this.RSVP.off('rejected', this.promiseRejected); + this.RSVP.off('fulfilled', this.promiseFulfilled); + this.RSVP.off('created', this.promiseCreated); + + this.all.forEach((item) => { + item.destroy(); + }); + + this.all = []; + this.promiseIndex = {}; + this.promiseChained = null; + this.promiseRejected = null; + this.promiseFulfilled = null; + this.promiseCreated = null; + this.isStarted = false; + } + } + + willDestroy() { + this.stop(); + super.willDestroy(); + } + + createPromise(props: any) { + let promise = new PromiseModel(props); + let index = this.all.length; + + this.all.push(promise); + this.promiseIndex[promise.guid] = index; + return promise; + } + + find(guid?: string) { + if (guid) { + const index = this.promiseIndex[guid]; + if (index !== undefined) { + return this.all[index]; + } + } else { + return this.all; + } + } + + findOrCreate(guid: string) { + return this.find(guid) || this.createPromise({ guid }); + } + + updateOrCreate(guid: string, properties: any) { + let entry = this.find(guid); + if (entry) { + Object.assign(entry, properties); + } else { + properties = Object.assign({}, properties); + properties.guid = guid; + entry = this.createPromise(properties); + } + + return entry; + } +} + +export default PromiseAssembler; + +function fulfill(this: PromiseAssembler, event: any) { + const guid = event.guid; + const promise = this.updateOrCreate(guid, { + label: event.label, + settledAt: event.timeStamp, + state: 'fulfilled', + value: event.detail, + }); + this.trigger('fulfilled', { promise } as PromiseUpdatedEvent); +} + +function reject(this: PromiseAssembler, event: any) { + const guid = event.guid; + const promise = this.updateOrCreate(guid, { + label: event.label, + settledAt: event.timeStamp, + state: 'rejected', + reason: event.detail, + }); + this.trigger('rejected', { promise } as PromiseUpdatedEvent); +} + +function chain(this: PromiseAssembler, event: any) { + let guid = event.guid; + let promise = this.updateOrCreate(guid, { + label: event.label, + chainedAt: event.timeStamp, + }); + let children = promise.children; + let child = this.findOrCreate(event.childGuid); + + child.parent = promise; + children.push(child); + + this.trigger('chained', { promise, child } as PromiseChainedEvent); +} + +function create(this: PromiseAssembler, event: any) { + const guid = event.guid; + + const promise = this.updateOrCreate(guid, { + label: event.label, + createdAt: event.timeStamp, + stack: event.stack, + }); + + // todo fix ordering + if (!promise.state) { + promise.state = 'created'; + } + this.trigger('created', { promise } as PromiseUpdatedEvent); +} diff --git a/packages/@ember/debug/ember-inspector-support/libs/render-tree.ts b/packages/@ember/debug/ember-inspector-support/libs/render-tree.ts new file mode 100644 index 00000000000..edc5c6ba3b5 --- /dev/null +++ b/packages/@ember/debug/ember-inspector-support/libs/render-tree.ts @@ -0,0 +1,711 @@ +import captureRenderTree from './capture-render-tree'; +import { guidFor } from '@ember/debug/ember-inspector-support/utils/ember/object/internals'; +import { inspect } from '@ember/debug/ember-inspector-support/utils/type-check'; +import { CustomModifierManager } from '@glimmer/manager'; +import * as GlimmerRuntime from '@glimmer/runtime'; +import type { CapturedRenderNode } from '@glimmer/interfaces/lib/runtime/debug-render-tree'; + +declare module '@glimmer/interfaces/lib/runtime/debug-render-tree' { + interface CapturedRenderNode { + meta: { + parentElement: HTMLBaseElement; + }; + } +} + +class InElementSupportProvider { + nodeMap: Map; + remoteRoots: CapturedRenderNode[]; + Wormhole: any; + debugRenderTree: any; + NewElementBuilder: any; + debugRenderTreeFunctions: { exit: any; enter: any } | undefined; + NewElementBuilderFunctions: any; + constructor(owner: any) { + this.nodeMap = new Map(); + this.remoteRoots = []; + try { + // @ts-expect-error expected error + this.Wormhole = requireModule('ember-wormhole/components/ember-wormhole'); + } catch { + // nope + } + + CustomModifierManager.prototype.getDebugInstance = (args) => args.modifier || args.delegate; + + this.debugRenderTree = + owner.lookup('renderer:-dom')?.debugRenderTree || + owner.lookup('service:-glimmer-environment')._debugRenderTree; + this.NewElementBuilder = GlimmerRuntime.NewElementBuilder; + + this.patch(); + } + + reset() { + this.nodeMap.clear(); + this.remoteRoots.length = 0; + } + + patch() { + const NewElementBuilder = GlimmerRuntime.NewElementBuilder; + const componentStack: any[] = []; + let currentElement: any = null; + + const captureNode = this.debugRenderTree.captureNode; + // this adds meta to the capture + // https://github.com/glimmerjs/glimmer-vm/pull/1575 + this.debugRenderTree.captureNode = function (id: string, state: any) { + const node = this.nodeFor(state); + const res = captureNode.call(this, id, state); + res.meta = node.meta; + return res; + }; + + const exit = this.debugRenderTree.exit; + this.debugRenderTree.exit = function (state: any) { + const node = this.nodeFor(this.stack.current); + if (node?.type === 'component' || node.type === 'keyword') { + componentStack.pop(); + } + return exit.call(this, state); + }; + + const enter = this.debugRenderTree.enter; + // this is required to have the original parentElement for in-element + // https://github.com/glimmerjs/glimmer-vm/pull/1575 + this.debugRenderTree.enter = function (...args: any) { + enter.call(this, ...args); + const node = this.nodeFor(args[0]); + if (node?.type === 'keyword' && node.name === 'in-element') { + node.meta = { + parentElement: currentElement, + }; + } + return node; + }; + + const didAppendNode = NewElementBuilder.prototype.didAppendNode; + // just some optimization for search later, not really needed + // @ts-expect-error expected error + NewElementBuilder.prototype.didAppendNode = function (...args: any) { + args[0].__emberInspectorParentNode = componentStack.slice(-1)[0]; + // @ts-expect-error expected error + return didAppendNode.call(this, ...args); + }; + + const pushElement = NewElementBuilder.prototype['pushElement']; + NewElementBuilder.prototype['pushElement'] = function (...args: any) { + // @ts-expect-error monkey patching... could be removed, just some perf gain + pushElement.call(this, ...args); + args[0].__emberInspectorParentNode = componentStack.slice(-1)[0]; + }; + + // https://github.com/glimmerjs/glimmer-vm/pull/1575 + const pushRemoteElement = NewElementBuilder.prototype.pushRemoteElement; + NewElementBuilder.prototype.pushRemoteElement = function (...args: any) { + currentElement = this.element; + // @ts-expect-error monkey patching... + return pushRemoteElement.call(this, ...args); + }; + + this.debugRenderTreeFunctions = { + enter, + exit, + }; + this.NewElementBuilderFunctions = { + pushElement, + pushRemoteElement, + didAppendNode, + }; + } + + teardown() { + if (!this.NewElementBuilderFunctions) { + return; + } + Object.assign(this.debugRenderTree, this.debugRenderTreeFunctions); + Object.assign(this.NewElementBuilder.prototype, this.NewElementBuilderFunctions); + this.NewElementBuilderFunctions = null; + } +} + +export default class RenderTree { + declare tree: CapturedRenderNode[]; + declare owner: any; + declare retainObject: any; + declare releaseObject: any; + declare inspectNode: (node: Node) => void; + declare renderNodeIdPrefix: string; + declare nodes: Record; + declare serialized: Record; + declare ranges: Record; + declare parentNodes: any; + declare previouslyRetainedObjects: any; + declare retainedObjects: any; + declare inElementSupport: InElementSupportProvider | undefined; + /** + * Sets up the initial options. + * + * @param {Object} options + * - {owner} owner The Ember app's owner. + * - {Function} retainObject Called to retain an object for future inspection. + */ + constructor({ + owner, + retainObject, + releaseObject, + inspectNode, + }: { + owner: any; + retainObject: any; + releaseObject: any; + inspectNode: (node: Node) => void; + }) { + this.owner = owner; + this.retainObject = retainObject; + this.releaseObject = releaseObject; + this.inspectNode = inspectNode; + this._reset(); + try { + this.inElementSupport = new InElementSupportProvider(owner); + } catch (e) { + // eslint-disable-next-line no-console + console.error('failed to setup in element support', e); + // not supported + } + + // need to have different ids per application / iframe + // to distinguish the render nodes it in the inspector + // between apps + this.renderNodeIdPrefix = guidFor(this); + } + + /** + * Capture the render tree and serialize it for sending. + * + * This returns an array of `SerializedRenderNode`: + * + * type SerializedItem = string | number | bigint | boolean | null | undefined | { id: string }; + * + * interface SerializedRenderNode { + * id: string; + * type: 'outlet' | 'engine' | 'route-template' | 'component'; + * name: string; + * args: { + * named: Dict; + * positional: SerializedItem[]; + * }; + * instance: SerializedItem; + * template: Option; + * bounds: Option<'single' | 'range'>; + * children: SerializedRenderNode[]; + * } + */ + build() { + this._reset(); + + this.tree = captureRenderTree(this.owner); + let serialized = this._serializeRenderNodes(this.tree); + + this._releaseStaleObjects(); + + return serialized; + } + + /** + * Find a render node by id. + */ + find(id: string): CapturedRenderNode | null { + let node = this.nodes[id]; + + if (node) { + return this._serializeRenderNode(node); + } else { + return null; + } + } + + /** + * Find the deepest enclosing render node for a given DOM node. + * + * @param {Node} node A DOM node. + * @param {string} hint The id of the last-matched render node (see comment below). + * @return {Option} The deepest enclosing render node, if any. + */ + findNearest( + node: Node & { __emberInspectorParentElement: any; __emberInspectorParentNode: any }, + hint?: string + ) { + // Use the hint if we are given one. When doing "live" inspecting, the mouse likely + // hasn't moved far from its last location. Therefore, the matching render node is + // likely to be the same render node, one of its children, or its parent. Knowing this, + // we can heuristically start the search from the parent render node (which would also + // match against this node and its children), then only fallback to matching the entire + // tree when there is no match in this subtree. + + if (node.__emberInspectorParentElement) { + node = node.__emberInspectorParentElement; + } + + let hintNode = this._findUp(this.nodes[hint!]); + let hints = [hintNode!]; + if (node.__emberInspectorParentNode) { + const remoteNode = this.inElementSupport?.nodeMap.get(node); + const n = remoteNode && this.nodes[remoteNode]; + hints.push(n); + } + + hints = hints.filter((h) => Boolean(h)); + let renderNode; + + const remoteRoots = this.inElementSupport?.remoteRoots || []; + + renderNode = this._matchRenderNodes([...hints, ...remoteRoots, ...this.tree], node); + + if (renderNode) { + return this._serializeRenderNode(renderNode); + } else { + return null; + } + } + + /** + * Get the bounding rect for a given render node id. + * + * @param {*} id A render node id. + * @return {Option} The bounding rect, if the render node is found and has valid `bounds`. + */ + getBoundingClientRect(id: string) { + let node = this.nodes[id]; + + if (!node || !node.bounds) { + return null; + } + + // Element.getBoundingClientRect seems to be less buggy when it comes + // to taking hidden (clipped) content into account, so prefer that over + // Range.getBoundingClientRect when possible. + + let rect; + let { bounds } = node; + let { firstNode } = bounds; + + if (isSingleNode(bounds) && (firstNode as unknown as HTMLElement).getBoundingClientRect) { + rect = (firstNode as unknown as HTMLElement).getBoundingClientRect(); + } else { + rect = this.getRange(id)?.getBoundingClientRect(); + } + + if (rect && !isEmptyRect(rect)) { + return rect; + } + + return null; + } + + /** + * Get the DOM range for a give render node id. + * + * @param {string} id A render node id. + * @return {Option} The DOM range, if the render node is found and has valid `bounds`. + */ + getRange(id: string) { + let range = this.ranges[id]; + + if (range === undefined) { + let node = this.nodes[id]; + + if (node && node.bounds && isAttached(node.bounds)) { + range = document.createRange(); + range.setStartBefore(node.bounds.firstNode as unknown as Node); + range.setEndAfter(node.bounds.lastNode as unknown as Node); + } else { + // If the node has already been detached, we probably have a stale tree + range = null; + } + + this.ranges[id] = range; + } + + return range; + } + + /** + * Scroll the given render node id into view (if the render node is found and has valid `bounds`). + * + * @param {string} id A render node id. + */ + scrollIntoView(id: string) { + let node = this.nodes[id]; + + if (!node || node.bounds === null) { + return; + } + + let element = this._findNode(node, [Node.ELEMENT_NODE]); + + if (element) { + element.scrollIntoView({ + behavior: 'smooth', + block: 'center', + inline: 'nearest', + }); + } + } + + /** + * Inspect the bounds for the given render node id in the "Elements" panel (if the render node + * is found and has valid `bounds`). + * + * @param {string} id A render node id. + */ + inspectElement(id: string) { + let node = this.nodes[id]; + + if (!node || node.bounds === null) { + return; + } + + // We cannot inspect text nodes + let target = this._findNode(node, [Node.ELEMENT_NODE, Node.COMMENT_NODE]); + + this.inspectNode(target); + } + + teardown() { + this._reset(); + this._releaseStaleObjects(); + } + + _reset() { + this.tree = []; + this.nodes = Object.create(null); + this.parentNodes = Object.create(null); + this.serialized = Object.create(null); + this.ranges = Object.create(null); + this.previouslyRetainedObjects = this.retainedObjects || new Map(); + this.retainedObjects = new Map(); + } + + _createSimpleInstance(name: string, args: any) { + const obj = Object.create(null); + obj.args = args; + obj.constructor = { + name: name, + comment: 'fake constructor', + }; + return obj; + } + + _insertHtmlElementNode(node: CapturedRenderNode, parentNode?: CapturedRenderNode | null): any { + const element = node.bounds!.firstNode as unknown as HTMLElement; + const htmlNode = { + id: node.id + 'html-element', + type: 'html-element', + name: element.tagName.toLowerCase(), + instance: element, + template: null, + bounds: { + firstNode: element, + lastNode: element, + parentElement: element.parentElement, + }, + args: { + named: {}, + positional: [], + }, + children: [], + } as unknown as CapturedRenderNode; + const idx = parentNode!.children.indexOf(node); + parentNode!.children.splice(idx, 0, htmlNode); + return this._serializeRenderNode(htmlNode, parentNode); + } + + _serializeRenderNodes(nodes: CapturedRenderNode[], parentNode: CapturedRenderNode | null = null) { + const mapped = []; + // nodes can be mutated during serialize, which is why we use indexing instead of .map + for (let i = 0; i < nodes.length; i++) { + mapped.push(this._serializeRenderNode(nodes[i]!, parentNode)); + } + return mapped; + } + + _serializeRenderNode(node: CapturedRenderNode, parentNode: CapturedRenderNode | null = null) { + if (!node.id.startsWith(this.renderNodeIdPrefix)) { + node.id = `${this.renderNodeIdPrefix}-${node.id}`; + } + let serialized = this.serialized[node.id]; + + if (serialized === undefined) { + this.nodes[node.id] = node; + if (node.type === 'keyword' && node.name === 'in-element') { + node.type = 'component'; + node.instance = { + args: node.args, + constructor: { + name: 'InElement', + }, + }; + } + + if ( + this.inElementSupport?.Wormhole && + node.instance instanceof this.inElementSupport.Wormhole.default + ) { + this.inElementSupport?.remoteRoots.push(node); + const bounds = node.bounds; + Object.defineProperty(node, 'bounds', { + get() { + const instance = node.instance as any; + if ((node.instance as any)._destination) { + return { + firstNode: instance._destination, + lastNode: instance._destination, + parentElement: instance._destination.parentElement, + }; + } + return bounds; + }, + }); + } + + if (parentNode) { + this.parentNodes[node.id] = parentNode; + } + + if ((node.type as string) === 'html-element') { + // show set attributes in inspector + const instance = node.instance as HTMLElement; + Array.from(instance.attributes).forEach((attr) => { + node.args.named[attr.nodeName] = attr.nodeValue; + }); + // move modifiers and components into the element children + parentNode!.children.forEach((child) => { + if ( + (child.bounds!.parentElement as unknown as HTMLElement) === instance || + child.meta?.parentElement === instance || + (child.type === 'modifier' && (child as any).bounds.firstNode === instance) + ) { + node.children.push(child); + } + }); + node.children.forEach((child) => { + const idx = parentNode!.children.indexOf(child); + if (idx >= 0) { + parentNode!.children.splice(idx, 1); + } + }); + } + + if (node.type === 'component' && !node.instance) { + if (node.name === '(unknown template-only component)' && node.template?.endsWith('.hbs')) { + node.name = node.template.split(/\\|\//).slice(-1)[0]!.slice(0, -'.hbs'.length); + } + node.instance = this._createSimpleInstance('TemplateOnlyComponent', node.args.named); + } + + if (node.type === 'modifier') { + node.name = node.name + ?.replace(/[A-Z]/g, (m) => '-' + m.toLowerCase()) + .replace(/^-/, '') + .replace('-modifier', ''); + node.instance = node.instance || this._createSimpleInstance(node.name, node.args); + (node.instance as any).toString = () => node.name; + if (parentNode!.instance !== node.bounds!.firstNode) { + return this._insertHtmlElementNode(node, parentNode); + } + } + + this.serialized[node.id] = serialized = { + ...node, + meta: null, + args: this._serializeArgs(node.args), + instance: this._serializeItem(node.instance), + bounds: this._serializeBounds(node.bounds), + children: this._serializeRenderNodes(node.children, node), + }; + } + + return serialized; + } + + _serializeArgs({ named, positional }: any) { + return { + named: this._serializeDict(named), + positional: this._serializeArray(positional), + }; + } + + _serializeBounds(bounds: any) { + if (bounds === null) { + return null; + } else if (isSingleNode(bounds)) { + return 'single'; + } else { + return 'range'; + } + } + + _serializeDict(dict: any) { + let result = Object.create(null); + + if ('__ARGS__' in dict) { + dict = dict['__ARGS__']; + } + + Object.keys(dict).forEach((key) => { + result[key] = this._serializeItem(dict[key]); + }); + + return result; + } + + _serializeArray(array: any[]) { + return array.map((item) => this._serializeItem(item)); + } + + _serializeItem(item: any) { + switch (typeof item) { + case 'string': + case 'number': + case 'bigint': + case 'boolean': + case 'undefined': + return item; + + default: + return item && this._serializeObject(item); + } + } + + _serializeObject(object: any) { + let id = this.previouslyRetainedObjects.get(object); + + if (id === undefined) { + id = this.retainObject(object); + } + + this.retainedObjects.set(object, id); + + return { id, type: typeof object, inspect: inspect(object) }; + } + + _releaseStaleObjects() { + // The object inspector already handles ref-counting. So doing the same + // bookkeeping here may seem redundant, and it is. However, in practice, + // calling `retainObject` and `dropObject` could be quite expensive and + // we call them a lot. Also, temporarily dropping the ref-count to 0 just + // to re-increment it later (which is what would happen if we release all + // current objects before the walk, then re-retain them as we walk the + // new tree) is especially bad, as it triggers the initialization and + // clean up logic on each of these objects. In my (GC's) opinion, the + // object inspector is likely overly eager and doing too much bookkeeping + // when we can be using weakmaps. Until we have a chance to revamp the + // object inspector, the logic here tries to reduce the number of retain + // and release calls by diffing the object set betweeen walks. Feel free + // to remove this code and revert to the old release-then-retain method + // when the object inspector is not slow anymore. + + let { previouslyRetainedObjects, retainedObjects, releaseObject } = this; + + // The object inspector should make its own GC async, but until then... + window.setTimeout(function () { + for (let [object, id] of previouslyRetainedObjects) { + if (!retainedObjects.has(object)) { + releaseObject(id); + } + } + }, 0); + + this.previouslyRetainedObjects = null; + } + + _getParent(id: string) { + return this.parentNodes[id] || null; + } + + _matchRenderNodes( + renderNodes: CapturedRenderNode[], + dom: Node, + deep = true + ): CapturedRenderNode | null { + let candidates = [...renderNodes]; + + while (candidates.length > 0) { + let candidate = candidates.shift()!; + let range = this.getRange(candidate.id); + const isAllowed = candidate.type !== 'modifier' && (candidate as any).type !== 'html-element'; + + if (!isAllowed) { + candidates.push(...candidate.children); + continue; + } + + if (isAllowed && range && range.isPointInRange(dom, 0)) { + // We may be able to find a more exact match in one of the children. + return this._matchRenderNodes(candidate.children, dom, false) || candidate; + } else if (!range || deep) { + // There are some edge cases of non-containing parent nodes (e.g. "worm + // hole") so we can't rule out the entire subtree just because the parent + // didn't match. However, we should come back to this subtree at the end + // since we are unlikely to find a match here. + candidates.push(...candidate.children); + } else { + // deep = false: In this case, we already found a matching parent, + // we are just trying to find a more precise match here. If the child + // does not contain the DOM node, we don't need to travese further. + } + } + + return null; + } + + _findNode(capturedNode: CapturedRenderNode, nodeTypes: number[]): HTMLBaseElement { + let node = capturedNode.bounds!.firstNode; + + do { + if (nodeTypes.indexOf(node.nodeType) > -1) { + return node as unknown as HTMLBaseElement; + } else { + node = node.nextSibling!; + } + } while (node && node !== capturedNode.bounds!.lastNode); + + return capturedNode.meta?.parentElement || capturedNode.bounds!.parentElement; + } + + _findUp(node?: CapturedRenderNode) { + // Find the first parent render node with a different enclosing DOM element. + // Usually, this is just the first parent render node, but there are cases where + // multiple render nodes share the same bounds (e.g. outlet -> route template). + let parentElement = node?.meta?.parentElement || node?.bounds?.parentElement; + + while (node && parentElement) { + let parentNode = this._getParent(node.id); + + if (parentNode) { + node = parentNode; + + if (parentElement === (node?.meta?.parentElement || node?.bounds?.parentElement)) { + continue; + } + } + + break; + } + + return node; + } +} + +function isSingleNode({ firstNode, lastNode }: any) { + return firstNode === lastNode; +} + +function isAttached({ firstNode, lastNode }: any) { + return firstNode.isConnected && lastNode.isConnected; +} + +function isEmptyRect({ x, y, width, height }: any) { + return x === 0 && y === 0 && width === 0 && height === 0; +} diff --git a/packages/@ember/debug/ember-inspector-support/libs/source-map.ts b/packages/@ember/debug/ember-inspector-support/libs/source-map.ts new file mode 100644 index 00000000000..0ffd7d99706 --- /dev/null +++ b/packages/@ember/debug/ember-inspector-support/libs/source-map.ts @@ -0,0 +1,175 @@ +import BaseObject from '@ember/debug/ember-inspector-support/utils/base-object'; +import { SourceMapConsumer } from 'source-map-js'; +const notFoundError = new Error('Source map url not found'); + +export default class SourceMapSupport extends BaseObject { + private declare _lastPromise: Promise; + init() { + super.init(); + this._lastPromise = Promise.resolve(undefined); + } + + /** + * Returns a promise that resolves to an array + * of mapped sourcew. + */ + map(stack: string): Promise { + let parsed = fromStackProperty(stack); + let array: any = []; + let lastPromise = null; + parsed?.forEach((item) => { + lastPromise = this._lastPromise + .then(() => this.getSourceMap(item.url), null) + .then((smc) => { + if (smc) { + let source = smc.originalPositionFor({ + line: item.line, + column: item.column, + }); + source.fullSource = relativeToAbsolute(item.url, source.source); + array.push(source); + return array; + } + return; + }, null); + this._lastPromise = lastPromise!; + }); + return Promise.resolve(lastPromise).catch(function (e) { + if (e === notFoundError) { + return null; + } + throw e; + }); + } + + sourceMapCache: Record = {}; + + getSourceMap(url: string) { + let sourceMaps = this.sourceMapCache; + if (sourceMaps[url] !== undefined) { + return Promise.resolve(sourceMaps[url]); + } + return retrieveSourceMap(url).then( + (response) => { + if (response) { + const map = JSON.parse(response.map); + const sm = new SourceMapConsumer(map); + sourceMaps[url] = sm; + return sm; + } + return; + }, + function () { + sourceMaps[url] = null; + } + ); + } +} + +function retrieveSourceMap(source: string) { + let mapURL: string; + return retrieveSourceMapURL(source) + .then((sourceMappingURL) => { + if (!sourceMappingURL) { + throw notFoundError; + } + + // Support source map URLs relative to the source URL + mapURL = relativeToAbsolute(source, sourceMappingURL); + return mapURL; + }, null) + .then(retrieveFile, null) + .then((sourceMapData) => { + if (!sourceMapData) { + return null; + } + return { + url: mapURL, + map: sourceMapData, + }; + }, null); +} + +function relativeToAbsolute(file: string, url: string) { + // Regex from https://stackoverflow.com/a/19709846 + // This will match the most common prefixes we care about: "http://", "https://", "//" + let absoluteUrlRegex = new RegExp('^(?:[a-z]+:)?//', 'i'); + + // If we don't have a file URL or the sourcemap URL is absolute, then return the sourcemap URL. + if (!file || absoluteUrlRegex.test(url)) { + return url; + } + + // Otherwise, find the sourcemap URL relative to the original file. + let dir = file.split('/'); + dir.pop(); + dir.push(url); + return dir.join('/'); +} + +function retrieveFile(source: string) { + return new Promise(function (resolve) { + const xhr = new XMLHttpRequest(); + xhr.onload = function () { + resolve(this.responseText); + }; + xhr.open('GET', source, true); + xhr.send(); + }); +} + +function retrieveSourceMapURL(source: string) { + return retrieveFile(source).then(function (fileData: string) { + let match = /\/\/[#@]\s*sourceMappingURL=(.*)\s*$/g.exec(fileData); + if (!match) { + return null; + } + let url = match[1] as string; + // check not data URL + if (url.match(/^data/)) { + return null; + } + return url; + }, null); +} + +const UNKNOWN_FUNCTION = ''; + +// Taken from https://github.com/errorception/browser-stack-parser/ +function fromStackProperty(stackString: string) { + let chrome = + /^\s*at (?:((?:\[object object\])?\S+(?: \[as \S+\])?) )?\(?((?:file|http|https):.*?):(\d+)(?::(\d+))?\)?\s*$/i; + let gecko = /^\s*(\S*)(?:\((.*?)\))?@((?:file|http|https).*?):(\d+)(?::(\d+))?\s*$/i; + let lines = stackString.split('\n'); + let stack = []; + let parts; + + for (let i = 0, j = lines.length; i < j; ++i) { + if ((parts = gecko.exec(lines[i]!))) { + stack.push({ + url: parts[3]!, + func: parts[1] || UNKNOWN_FUNCTION, + args: parts[2] ? parts[2].split(',') : [], + line: Number(parts[4]), + column: parts[5] ? Number(parts[5]) : null, + }); + } else if ((parts = chrome.exec(lines[i]!))) { + stack.push({ + url: parts[2]!, + func: parts[1] || UNKNOWN_FUNCTION, + line: Number(parts[3]), + column: parts[4] ? Number(parts[4]) : null, + }); + } + } + + return stack.length + ? (stack as { + url: string; + func: string; + args: string[] | undefined; + line: number; + column: number | null; + }[]) + : null; +} diff --git a/packages/@ember/debug/ember-inspector-support/libs/view-inspection.ts b/packages/@ember/debug/ember-inspector-support/libs/view-inspection.ts new file mode 100644 index 00000000000..2b75a3752e4 --- /dev/null +++ b/packages/@ember/debug/ember-inspector-support/libs/view-inspection.ts @@ -0,0 +1,756 @@ +import classify from '@ember/debug/ember-inspector-support/utils/classify'; +import bound from '@ember/debug/ember-inspector-support/utils/bound-method'; +import getObjectName from '../utils/get-object-name'; +import type RenderTree from './render-tree'; +import type ObjectInspector from '../object-inspector'; +import type { CapturedRenderNode } from '@glimmer/interfaces'; + +function makeHighlight(id: string) { + return ``; +} + +function makeTooltip(id: string) { + let prefix = 'ember-inspector-tooltip'; + + return ` +
+ + + + + + + +
+ +
+ `; +} + +function makeStylesheet(id: string) { + let prefix = 'ember-inspector'; + + return ` + #${prefix}-highlight-${id} { + display: none; + pointer-events: none; + box-sizing: border-box; + position: absolute; + margin: 0px; + padding: 0px; + border: none; + z-index: 1000000; + /* https://github.com/ChromeDevTools/devtools-frontend/blob/b336f0440a8fb539352ac223ef466c3475618cf1/front_end/common/Color.js#L904 */ + background: rgba(111, 168, 220, .66); + } + + #${prefix}-tooltip-${id} { + display: none; + box-sizing: border-box; + position: absolute; + margin: 8px 0px; + padding: 4px 8px; + border: none; + border-radius: 3px; + z-index: 1000000; + font-family: sans-serif; + font-size: 12px; + font-weight: normal; + background: white; + box-shadow: 0px 2px 8px 0px rgba(0,0,0,0.25); + } + + #${prefix}-tooltip-${id} .${prefix}-tooltip-header { + display: block; + margin: 4px 0px; + padding: 0px; + border: none; + font-family: sans-serif; + font-size: 12px; + font-weight: normal; + background: transparent; + } + + #${prefix}-tooltip-${id} .${prefix}-tooltip-title { + font-weight: bold; + } + + #${prefix}-tooltip-${id} .${prefix}-tooltip-token-tag, + #${prefix}-tooltip-${id} .${prefix}-tooltip-token-namespace { + /* https://github.com/ChromeDevTools/devtools-frontend/blob/103326238685ac582d3bf2a02f1627a80e3fce5f/front_end/ui/inspectorSyntaxHighlight.css#L69-L71 */ + color: rgb(168, 148, 166); + } + + #${prefix}-tooltip-${id} .${prefix}-tooltip-detail-instance > .${prefix}-tooltip-token-tag { + cursor: pointer; + } + + #${prefix}-tooltip-${id} .${prefix}-tooltip-detail-instance > .${prefix}-tooltip-token-tag:after { + content: "\\1F517" + } + + #${prefix}-tooltip-${id} .${prefix}-tooltip-detail-controller > .${prefix}-tooltip-token-tag { + cursor: pointer; + } + + #${prefix}-tooltip-${id} .${prefix}-tooltip-detail-controller > .${prefix}-tooltip-token-tag:after { + content: "\\1F517" + } + + #${prefix}-tooltip-${id} .${prefix}-tooltip-token-name { + /* https://github.com/ChromeDevTools/devtools-frontend/blob/103326238685ac582d3bf2a02f1627a80e3fce5f/front_end/ui/inspectorSyntaxHighlight.css#L60 */ + color: rgb(136, 18, 128); + } + + #${prefix}-tooltip-${id} .${prefix}-tooltip-token-id { + /* https://github.com/ChromeDevTools/devtools-frontend/blob/103326238685ac582d3bf2a02f1627a80e3fce5f/front_end/ui/inspectorSyntaxHighlight.css#L109-L113 */ + color: rgb(26, 26, 166); + } + + #${prefix}-tooltip-${id} .${prefix}-tooltip-details { + display: table; + table-layout: auto; + width: auto; + height: auto; + margin: 0px; + padding: 0px; + border: none; + border-spacing: 0px; + border-collapse: collapse; + background: transparent; + } + + #${prefix}-tooltip-${id} .${prefix}-tooltip-details tbody { + display: table-row-group; + vertical-align: middle; + width: auto; + height: auto; + margin: 0px; + padding: 0px; + border: none; + border-spacing: 0px; + border-collapse: collapse; + background: transparent; + } + + #${prefix}-tooltip-${id} .${prefix}-tooltip-details tr { + display: table-row; + vertical-align: middle; + width: auto; + height: auto; + margin: 0px; + padding: 0px; + border: none; + border-spacing: 0px; + border-collapse: collapse; + background: transparent; + } + + #${prefix}-tooltip-${id} .${prefix}-tooltip-details th { + display: block; + width: auto; + height: auto; + margin: 4px 8px 4px 0px; + padding: 0px; + border: none; + border-spacing: 0px; + border-collapse: collapse; + white-space: nowrap; + font-family: sans-serif; + font-size: 12px; + font-weight: normal; + text-align: left; + color: #666; + background: transparent; + } + + #${prefix}-tooltip-${id} .${prefix}-tooltip-details td { + display: table-cell; + width: auto; + height: auto; + margin: 0px; + padding: 0px; + border: none; + border-spacing: 0px; + border-collapse: collapse; + font-family: sans-serif; + font-size: 12px; + font-weight: normal; + text-align: right; + color: #000; + background: transparent; + max-width: 400px; + word-wrap: break-word; + } + + #${prefix}-tooltip-${id} .${prefix}-tooltip-arrow { + display: block; + box-sizing: border-box; + position: absolute; + top: auto; + right: auto; + bottom: -20px; + left: 0px; + width: 40px; + height: 20px; + margin: 0px 0px 0px -20px; + padding: 0px; + border: none; + background: transparent; + overflow-x: visible; + overflow-y: hidden; + } + + #${prefix}-tooltip-${id}.${prefix}-tooltip-attach-below .${prefix}-tooltip-arrow { + top: -20px; + bottom: auto; + transform: rotate(180deg); + } + + #${prefix}-tooltip-${id} .${prefix}-tooltip-arrow::after { + content: ""; + display: block; + box-sizing: border-box; + position: absolute; + top: 0px; + right: auto; + bottom: auto; + left: 50%; + width: 0px; + height: 0px; + margin: 0px 0px 0px -8px; + border: 6px solid white; + border-color: transparent transparent white white; + box-shadow: 0px 2px 8px 0px rgba(0, 0, 0, 0.25); + transform-origin: 0 0; + transform: rotate(-45deg); + } + `; +} + +export default class ViewInspection { + declare renderTree: RenderTree; + declare objectInspector: ObjectInspector; + private declare didShow: (id: string, pin: boolean) => void; + private declare didHide: (id: string, pinned: boolean) => void; + private declare didStartInspecting: () => void; + private declare didStopInspecting: () => void; + private declare id: string; + private declare currentId: string | null; + private declare lastMatchId: string | null; + private declare isInspecting: boolean; + private declare lastTarget: EventTarget | null; + private declare isShowing: boolean; + private declare isPinned: boolean; + + private declare highlight: HTMLElement; + private declare tooltip: HTMLElement; + private declare stylesheet: HTMLElement; + constructor({ + renderTree, + objectInspector, + didShow, + didHide, + didStartInspecting, + didStopInspecting, + }: { + renderTree: RenderTree; + objectInspector: ObjectInspector; + didShow: () => void; + didHide: () => void; + didStartInspecting: () => void; + didStopInspecting: () => void; + }) { + this.renderTree = renderTree; + this.objectInspector = objectInspector; + + this.didShow = didShow; + this.didHide = didHide; + this.didStartInspecting = didStartInspecting; + this.didStopInspecting = didStopInspecting; + + this.id = (Math.random() * 100000000).toFixed(0); + + this.isInspecting = false; + this.lastTarget = null; + this.lastMatchId = null; + + this.isShowing = false; + this.isPinned = false; + this.currentId = null; + + this.setup(); + } + + setup() { + let { id } = this; + this.highlight = this._insertHTML(makeHighlight(id)); + this.tooltip = this._insertHTML(makeTooltip(id)); + this.stylesheet = this._insertStylesheet(makeStylesheet(id)); + + document.body.addEventListener('keydown', bound(this, this.onKeyDown), { + capture: true, + }); + document.body.addEventListener('click', bound(this, this.onClick), { + capture: true, + }); + } + + teardown() { + this.stop(); + + document.body.removeEventListener('keydown', bound(this, this.onKeyDown), { + capture: true, + }); + document.body.removeEventListener('click', bound(this, this.onClick), { + capture: true, + }); + + this.highlight.remove(); + this.tooltip.remove(); + this.stylesheet.remove(); + } + + start() { + this.isInspecting = true; + this.lastTarget = null; + this.lastMatchId = null; + + document.body.addEventListener('mousemove', bound(this, this.onMouseMove), { + capture: true, + }); + + this.didStartInspecting(); + } + + stop(shouldHide = true) { + if (shouldHide) { + this.hide(); + } + + this.isInspecting = false; + this.lastTarget = null; + this.lastMatchId = null; + + document.body.removeEventListener('mousemove', bound(this, this.onMouseMove), { + capture: true, + }); + + this.didStopInspecting(); + } + + onMouseMove(event: MouseEvent) { + event.preventDefault(); + event.stopPropagation(); + this.inspectNearest(event.target, false); + } + + onKeyDown(event: KeyboardEvent) { + if (event.key === 'Escape' || event.key === 'Esc') { + if (this.isPinned) { + event.preventDefault(); + event.stopPropagation(); + this.hide(); + } else if (this.isInspecting) { + event.preventDefault(); + event.stopPropagation(); + this.stop(); + } + } + } + + onClick(event: MouseEvent) { + if (this.isPinned && !this.tooltip.contains(event.target as Node)) { + event.preventDefault(); + event.stopPropagation(); + this.hide(); + } else if (this.isInspecting && event.button === 0) { + event.preventDefault(); + event.stopPropagation(); + this.inspectNearest(event.target, true); + this.stop(false); + } + } + + inspectNearest(target: EventTarget | null, pin = true) { + let { isInspecting, lastTarget, lastMatchId } = this; + + let match; + + if (isInspecting && target === lastTarget) { + match = this.renderTree.find(lastMatchId!); + } + + if (!match) { + match = this.renderTree.findNearest(target as any, lastMatchId!); + } + + if (match) { + this.show(match.id, pin); + } else { + this.hide(); + } + + if (isInspecting) { + this.lastTarget = target; + this.lastMatchId = match && match.id; + } + + return match; + } + + show(id: string, pin = true) { + if (this.currentId === id) { + if (this.isPinned !== pin) { + this.isPinned = pin; + this.didShow(id, pin); + } + + return; + } + + let node = this.renderTree.find(id); + let rect = this.renderTree.getBoundingClientRect(id); + + if (node && rect) { + this._showTooltip(node, rect); + rect = this.renderTree.getBoundingClientRect(id); + this._showHighlight(node, rect!); + + this.isShowing = true; + this.isPinned = pin; + this.currentId = id; + + this.didShow(id, pin); + } else { + this.hide(); + } + } + + hide(notify = false) { + let { isShowing, isPinned, currentId } = this; + + if (isShowing) { + this._hideHighlight(); + this._hideTooltip(); + + this.isShowing = false; + this.isPinned = false; + this.currentId = null; + + if (notify) { + this.didHide(currentId!, isPinned); + } + } + } + + _showHighlight(_node: CapturedRenderNode, rect: DOMRect) { + let { style } = this.highlight; + let { top, left, width, height } = rect; + let { scrollX, scrollY } = window; + + style.display = 'block'; + style.top = `${top + scrollY}px`; + style.left = `${left + scrollX}px`; + style.width = `${width}px`; + style.height = `${height}px`; + } + + _hideHighlight() { + this.highlight.style.display = 'none'; + } + + _showTooltip(node: CapturedRenderNode, highlightRect: DOMRect) { + this._renderTooltipTitle(node); + this._renderTooltipCategory(node); + this._renderTooltipDetails(node); + this._positionTooltip(highlightRect); + } + + _hideTooltip() { + this.tooltip.style.display = 'none'; + } + + _renderTooltipTitle(node: CapturedRenderNode) { + let title = this.tooltip.querySelector('.ember-inspector-tooltip-title')! as HTMLElement; + + title.innerHTML = ''; + + if (node.type === 'component') { + this._renderTokens(title, this._tokenizeComponentNode(node)); + } else if (node.type === 'outlet') { + this._renderTokens(title, [ + ['tag', '{{'], + ['name', 'outlet'], + ['tag', ' '], + ['tag', '"'], + ['id', node.name], + ['tag', '"'], + ['tag', '}}'], + ]); + } else if (node.type === 'engine') { + this._renderTokens(title, [ + ['tag', '{{'], + ['name', 'mount'], + ['tag', ' '], + ['tag', '"'], + ['id', node.name], + ['tag', '"'], + ['tag', '}}'], + ]); + } else { + title.innerText = node.name; + } + } + + _renderTooltipCategory(node: CapturedRenderNode) { + let category = this.tooltip.querySelector('.ember-inspector-tooltip-category') as HTMLElement; + + switch (node.type) { + case 'component': + case 'outlet': + case 'engine': + category.innerHTML = ''; + break; + + case 'route-template': + category.innerText = 'route'; + break; + } + } + + _renderTooltipDetails(node: CapturedRenderNode) { + let tbody = this.tooltip.querySelector('.ember-inspector-tooltip-details tbody') as HTMLElement; + + tbody.innerHTML = ''; + + if (node.template) { + this._renderTooltipDetail(tbody, 'Template', node.template); + } + + if (node.instance) { + if (node.type === 'route-template') { + this._renderTooltipDetail(tbody, 'Controller', this._tokenizeItem(node.instance)); + } else { + this._renderTooltipDetail(tbody, 'Instance', this._tokenizeItem(node.instance)); + } + const detail: HTMLElement | null = + tbody.querySelector( + '.ember-inspector-tooltip-detail-instance > .ember-inspector-tooltip-token-tag' + ) || + tbody.querySelector( + '.ember-inspector-tooltip-detail-controller > .ember-inspector-tooltip-token-tag' + ); + if (detail) { + detail.onclick = () => { + this.objectInspector.sendToConsole((node.instance as any).id); + }; + } + } + } + + _renderTooltipDetail(tbody: HTMLElement, key: string, value: string | [string, string][]) { + let tr = document.createElement('tr'); + let th = document.createElement('th'); + let td = document.createElement('td'); + + th.innerText = key; + td.className = `ember-inspector-tooltip-detail-${key + .toLowerCase() + .replace(/[^a-z0-9]+/g, '-')}`; + + if (Array.isArray(value)) { + this._renderTokens(td, value); + } else { + td.innerText = value.replace(/\//g, '\u200B/\u200B'); + } + + tr.appendChild(th); + tr.appendChild(td); + tbody.appendChild(tr); + } + + _renderTokens(parent: HTMLElement, tokens: [string, string][]) { + for (let [type, value] of tokens) { + let span = document.createElement('span'); + span.innerText = value; + span.setAttribute('class', `ember-inspector-tooltip-token-${type}`); + parent.appendChild(span); + } + } + + _tokenizeComponentNode(node: CapturedRenderNode): [string, string][] { + let useAngleBracket = node.args.positional.length === 0; + let parts = node.name.split('/'); + + if (useAngleBracket) { + parts = parts.map(classify); + } + + let name = parts.pop()!; + let namespace = parts; + + let tokens = []; + + if (useAngleBracket) { + tokens.push(['tag', '<']); + } else { + tokens.push(['tag', '{{']); + } + + while (namespace.length > 0) { + tokens.push(['namespace', namespace.shift()!]); + tokens.push(['tag', '::']); + } + + tokens.push(['name', name]); + + if (useAngleBracket) { + tokens.push(['tag', '>']); + } else { + tokens.push(['tag', '}}']); + } + + return tokens as [string, string][]; + } + + _tokenizeItem(item: any): [string, string][] { + switch (typeof item) { + case 'string': + case 'number': + case 'bigint': + case 'boolean': + case 'undefined': + return [['id', `${item}`]]; + } + + if (item === null) { + return [['id', 'null']]; + } + + return this._tokenizeObject(item); + } + + _tokenizeObject(item: any): [string, string][] { + let object = this.objectInspector.sentObjects[item.id]; + let stringified; + + try { + stringified = getObjectName(object); + } catch { + // nope! + } + + if (!object || !stringified) { + return [['tag', '(unknown)']]; + } + + { + // + let match = stringified.match(/<([a-z0-9-_]+)@([a-z0-9-_]+):([a-z0-9-_]+)::([a-z0-9-_]+)>/i); + + if (match) { + return [ + ['tag', '<'], + ['namespace', match[1]!], + ['tag', '@'], + ['namespace', match[2]!], + ['tag', ':'], + ['name', match[3]!], + ['tag', '::'], + ['id', match[4]!], + ['tag', '>'], + ]; + } + } + + // TODO: support other ember object strings, `[object Object]`, `Symbol(hi)` etc + return [['tag', stringified]]; + } + + _positionTooltip(highlightRect: DOMRect) { + // Positioning the tooltip: the goal is to match the Chrome's Element + // inspection tooltip's positioning behavior as closely as possible. + + let { style: tooltipStyle } = this.tooltip; + let { scrollX, scrollY, innerWidth } = window; + + // Leave 20px safety margin in case of scrollbars + let safetyMargin = 20; + let viewportWidth = innerWidth - safetyMargin; + + // Start by attaching the tooltip below the highlight, and align it to the + // left edge of the highlight. + let attachmentTop = highlightRect.bottom; + let attachmentLeft = highlightRect.left; + + tooltipStyle.display = 'block'; + tooltipStyle.top = `${scrollY + attachmentTop}px`; + tooltipStyle.left = `${scrollX + attachmentLeft}px`; + + // Measure the tooltip + let tooltipRect = this.tooltip.getBoundingClientRect(); + + // Prefer to attach above the highlight instead, if space permits. This is + // visually more pleasing and matches the way Chrome attaches its Element + // inspection tooltips. We had to do this step here instead of setting it + // at the beginning, because it requires measuring the height of the rendered + // tooltip. + let top = highlightRect.top - tooltipRect.height - safetyMargin; + + if (top >= 0) { + attachmentTop = top; + this.tooltip.setAttribute('class', `ember-inspector-tooltip-attach-above`); + } else { + this.tooltip.setAttribute('class', `ember-inspector-tooltip-attach-below`); + } + + let leftOffset = 0; + + // Try to keep the entire tooltip onscreen. + if (tooltipRect.left < 0) { + // If the tooltip is partially offscreen to the left (because the higlight + // is partially offscreen to the left), then push it to the right to stay + // within the viewport, but not so much that it will become detached. + leftOffset = Math.max(highlightRect.left - safetyMargin, safetyMargin - highlightRect.width); + } else if (tooltipRect.right > viewportWidth) { + // If the tooltip is partially offscreen to the right (because the tooltip + // is too wide), then push it to the left to stay within the viewport, but + // not so much that it will become detached. + leftOffset = Math.min( + tooltipRect.right - viewportWidth, + tooltipRect.width - safetyMargin * 2 + ); + tooltipStyle.left = `${scrollX + attachmentLeft - leftOffset}px`; + } + + // Left-align the arrow 17px form the left edge of the highlight, unless the + // component is tiny, in which case, we center it. + let arrowLeft = Math.min(17, highlightRect.width / 2); + + // Try to maintain at least 17 pixels to the left/right of the arrow so it + // doesn't "poke outside" the tooltip. + if (arrowLeft < 17) { + leftOffset = Math.max(leftOffset, 17); + } + + tooltipStyle.top = `${scrollY + attachmentTop}px`; + tooltipStyle.left = `${scrollX + attachmentLeft - leftOffset}px`; + + let arrow = this.tooltip.querySelector('.ember-inspector-tooltip-arrow')! as HTMLElement; + + arrow.style.left = `${Math.max(leftOffset, 0) + arrowLeft}px`; + } + + _insertHTML(html: string) { + document.body.insertAdjacentHTML('beforeend', html.trim()); + return document.body.lastChild as HTMLElement; + } + + _insertStylesheet(content: string) { + let style = document.createElement('style'); + style.appendChild(document.createTextNode(content)); + document.head.appendChild(style); + return style as HTMLElement; + } +} diff --git a/packages/@ember/debug/ember-inspector-support/main.ts b/packages/@ember/debug/ember-inspector-support/main.ts new file mode 100644 index 00000000000..2c7d5308b25 --- /dev/null +++ b/packages/@ember/debug/ember-inspector-support/main.ts @@ -0,0 +1,179 @@ +import BasicAdapter from '@ember/debug/ember-inspector-support/adapters/basic'; +import Port from '@ember/debug/ember-inspector-support/port'; +import ObjectInspector from '@ember/debug/ember-inspector-support/object-inspector'; +import GeneralDebug from '@ember/debug/ember-inspector-support/general-debug'; +import RenderDebug from '@ember/debug/ember-inspector-support/render-debug'; +import ViewDebug from '@ember/debug/ember-inspector-support/view-debug'; +import RouteDebug from '@ember/debug/ember-inspector-support/route-debug'; +import DataDebug from '@ember/debug/ember-inspector-support/data-debug'; +import PromiseDebug from '@ember/debug/ember-inspector-support/promise-debug'; +import ContainerDebug from '@ember/debug/ember-inspector-support/container-debug'; +import DeprecationDebug from '@ember/debug/ember-inspector-support/deprecation-debug'; +import Session from '@ember/debug/ember-inspector-support/services/session'; + +import Application from '@ember/application'; +import { + guidFor, + setGuidPrefix, +} from '@ember/debug/ember-inspector-support/utils/ember/object/internals'; +import { run } from '@ember/runloop'; +import BaseObject from '@ember/debug/ember-inspector-support/utils/base-object'; +import Namespace from '@ember/application/namespace'; + +class EmberDebug extends BaseObject { + /** + * Set to true during testing. + * + * @type {Boolean} + * @default false + */ + isTesting = false; + private _application: any; + private owner: any; + private started!: boolean; + adapter!: BasicAdapter; + port!: Port; + generalDebug!: GeneralDebug; + objectInspector!: ObjectInspector; + + get applicationName() { + return this._application.name || this._application.modulePrefix; + } + + /** + * We use the application's id instead of the owner's id so that we use the same inspector + * instance for the same application even if it was reset (owner changes on reset). + */ + get applicationId() { + if (!this.isTesting) { + return guidFor(this._application, 'ember'); + } + return guidFor(this.owner, 'ember'); + } + + // Using object shorthand syntax here is somehow having strange side effects. + + Port = Port; + Adapter = BasicAdapter; + + start($keepAdapter: boolean) { + if (this.started) { + this.reset($keepAdapter); + return; + } + if (!this._application && !this.isTesting) { + this._application = getApplication(); + } + this.started = true; + + this.reset(); + + this.adapter.debug('Ember Inspector Active'); + this.adapter.sendMessage({ + type: 'inspectorLoaded', + }); + } + + destroyContainer() { + if (this.generalDebug) { + this.generalDebug.sendReset(); + } + [ + 'dataDebug', + 'viewDebug', + 'routeDebug', + 'generalDebug', + 'renderDebug', + 'promiseDebug', + 'containerDebug', + 'deprecationDebug', + 'objectInspector', + 'session', + ].forEach((prop) => { + let handler = (this as any)[prop]; + if (handler) { + run(handler, 'destroy'); + (this as any)[prop] = null; + } + }); + } + + startModule(prop: string, Module: any) { + (this as any)[prop] = new Module({ namespace: this }); + } + + willDestroy() { + this.destroyContainer(); + super.willDestroy(); + } + + reset($keepAdapter?: boolean) { + setGuidPrefix(Math.random().toString()); + if (!this.isTesting && !this.owner) { + this.owner = getOwner(this._application); + } + this.destroyContainer(); + run(() => { + // Adapters don't have state depending on the application itself. + // They also maintain connections with the inspector which we will + // lose if we destroy. + if (!this.adapter || !$keepAdapter) { + this.startModule('adapter', this.Adapter); + } + if (!this.port || !$keepAdapter) { + this.startModule('port', this.Port); + } + + this.startModule('session', Session); + this.startModule('generalDebug', GeneralDebug); + this.startModule('renderDebug', RenderDebug); + this.startModule('objectInspector', ObjectInspector); + this.startModule('routeDebug', RouteDebug); + this.startModule('viewDebug', ViewDebug); + this.startModule('dataDebug', DataDebug); + this.startModule('promiseDebug', PromiseDebug); + this.startModule('containerDebug', ContainerDebug); + this.startModule('deprecationDebug', DeprecationDebug); + + this.generalDebug.sendBooted(); + }); + } + + inspect(obj: any) { + this.objectInspector.sendObject(obj); + this.adapter.log('Sent to the Object Inspector'); + return obj; + } + + clear() { + Object.assign(this, { + _application: null, + owner: null, + }); + } +} + +function getApplication() { + let namespaces = Namespace.NAMESPACES; + let application; + + namespaces.forEach((namespace) => { + if (namespace instanceof Application) { + application = namespace; + return false; + } + return; + }); + return application; +} + +function getOwner(application: Application) { + if (application.autoboot) { + return application.__deprecatedInstance__; + } else if (application._applicationInstances /* Ember 3.1+ */) { + return [...application._applicationInstances][0]; + } + return null; +} + +export default EmberDebug; diff --git a/packages/@ember/debug/ember-inspector-support/models/profile-manager.ts b/packages/@ember/debug/ember-inspector-support/models/profile-manager.ts new file mode 100644 index 00000000000..7f72f920a24 --- /dev/null +++ b/packages/@ember/debug/ember-inspector-support/models/profile-manager.ts @@ -0,0 +1,288 @@ +import type { Payload } from './profile-node'; +import ProfileNode from './profile-node'; + +import { later, scheduleOnce, cancel } from '@ember/runloop'; + +type Bounds = { + first: HTMLElement; + last: HTMLElement; + parent: HTMLElement; +}; + +function getEdges(first: any, last: any, closest: any) { + let start = null; + let end = null; + for (let i = 0; i < closest.length; i++) { + if (closest.item(i) === first.node) start = i; + else if (closest.item(i) === last.node) end = i; + } + return [start!, end!]; +} + +function getUnfilteredRoots(first: any, last: any, closest: any) { + if (first.node === last.node) return [first.node]; + + const roots = []; + + const [start, end] = getEdges(first, last, closest); + + if (start === null || end === null) return []; + + for (let i = start!; i <= end!; i++) roots.push(closest.item(i)); + + return roots; +} + +function findRoots({ first, last, parent }: Bounds) { + const closest = parent.childNodes; + + const roots = getUnfilteredRoots(first, last, closest); + + return roots.filter((el) => el?.nodeType === 1); +} + +function makeHighlight() { + const node = document.createElement('div'); + node.setAttribute('role', 'presentation'); + node.setAttribute('class', 'ember-inspector-render-highlight'); + return node; +} +function insertHTML(node: HTMLElement) { + document.body.appendChild(node); +} + +function insertStylesheet() { + const content = ` + .ember-inspector-render-highlight { + border: 2px solid rgba(255,0,0,0.2); + box-shadow: 0px 0px 1px rgba(255,0,0,0.2); + z-index: 1000000; + pointer-events: none; + } + `; + const style = document.createElement('style'); + style.appendChild(document.createTextNode(content)); + document.head.appendChild(style); + return style; +} + +type Info = { + type: string; + endedIndex?: any; + now?: number; + timestamp: number; + payload: Payload; + profileNode?: ProfileNode; +}; + +type Highlight = { + el: HTMLElement; + timeout?: any; +}; + +/** + * A class for keeping track of active rendering profiles as a list. + */ +export default class ProfileManager { + profiles: ProfileNode[] = []; + declare current: ProfileNode | undefined; + declare queue: Info[]; + declare highlights: Highlight[]; + declare currentSet: ProfileNode[]; + private declare _profilesAddedCallbacks: { context: any; callback: Function }[]; + declare shouldHighlightRender: boolean; + declare isHighlightEnabled: boolean; + declare stylesheet: HTMLStyleElement; + constructor() { + this.currentSet = []; + this._profilesAddedCallbacks = []; + this.queue = []; + this.shouldHighlightRender = false; + // keep track of all the active highlights + this.highlights = []; + this.isHighlightEnabled = true; + } + + setup() { + this.stylesheet = insertStylesheet(); + } + + began(timestamp: number, payload: Payload, now: number) { + return this.wrapForErrors(this, () => { + this.current = new ProfileNode(timestamp, payload, this.current, now); + if (this.shouldHighlightRender && payload.view) { + this._highLightView(payload.view); + } + this.current.isHighlightEnabled = this.isHighlightEnabled; + return this.current; + }); + } + + ended(timestamp: number, payload: Payload, profileNode: ProfileNode) { + if (payload.exception) { + throw payload.exception; + } + return this.wrapForErrors(this, () => { + this.current = profileNode.parent; + profileNode.finish(timestamp); + + // Are we done profiling an entire tree? + if (!this.current) { + this.currentSet.push(profileNode); + // If so, schedule an update of the profile list + scheduleOnce('afterRender', this, this._profilesFinished); + } + }); + } + + wrapForErrors(context: any, callback: Function) { + return callback.call(context); + } + + _highLightView(view: any) { + const symbols = Object.getOwnPropertySymbols(view); + const bounds = view[symbols.find((sym) => sym.description === 'BOUNDS')!]; + if (!bounds) return; + + const elements = findRoots(bounds); + + elements.forEach((node) => { + this._renderHighlight(node); + }); + } + + /** + * Push a new profile into the queue + */ + addToQueue(info: Info) { + const index = this.queue.push(info); + if (index === 1) { + later(this._flush.bind(this), 50); + } + return index - 1; + } + + clearProfiles() { + this.profiles.length = 0; + } + + onProfilesAdded(context: any, callback: Function) { + this._profilesAddedCallbacks.push({ context, callback }); + } + + offProfilesAdded(context: any, callback: Function) { + let index = -1, + item; + for (let i = 0, l = this._profilesAddedCallbacks.length; i < l; i++) { + item = this._profilesAddedCallbacks[i]!; + if (item.context === context && item.callback === callback) { + index = i; + } + } + if (index > -1) { + this._profilesAddedCallbacks.splice(index, 1); + } + } + + teardown() { + this.stylesheet?.remove(); + // remove all the active highlighted components + this._removeAllHighlights(); + } + + _removeAllHighlights() { + const els = this.highlights.slice(0); + els.forEach((el) => { + this._removeHighlight(el); + }); + } + + _removeHighlight(highlight: Highlight) { + this.highlights = this.highlights.filter((item) => item !== highlight); + cancel(highlight.timeout); + highlight.el.remove(); + } + + _addHighlight(highlight: Highlight) { + insertHTML(highlight.el); + this.highlights.push(highlight); + + highlight.timeout = later(() => { + this._removeHighlight(highlight); + }, 500); + } + + _constructHighlight(renderedNode: Range) { + const rect = renderedNode.getBoundingClientRect(); + const highlight = makeHighlight(); + + const { top, left, width, height } = rect; + const { scrollX, scrollY } = window; + const { style } = highlight; + if (style) { + style.position = 'absolute'; + style.top = `${top + scrollY}px`; + style.left = `${left + scrollX}px`; + style.width = `${width}px`; + style.height = `${height}px`; + } + return highlight; + } + + _renderHighlight(renderedNode: Range) { + if (!renderedNode?.getBoundingClientRect) { + return; + } + + const highlight = this._constructHighlight(renderedNode); + + this._addHighlight({ el: highlight }); + } + + _flush() { + let entry, i; + for (i = 0; i < this.queue.length; i++) { + entry = this.queue[i]!; + if (entry.type === 'began') { + // If there was an error during rendering `entry.endedIndex` never gets set. + if (entry.endedIndex) { + this.queue[entry.endedIndex]!.profileNode = this.began( + entry.timestamp, + entry.payload, + entry.now! + ); + } + } else { + this.ended(entry.timestamp, entry.payload, entry.profileNode!); + } + } + this.queue.length = 0; + } + + _profilesFinished() { + return this.wrapForErrors(this, () => { + const firstNode = this.currentSet[0]!; + let parentNode = new ProfileNode(firstNode.start, { + template: 'View Rendering', + }); + + parentNode.time = 0; + this.currentSet.forEach((n) => { + parentNode.time += n.time; + parentNode.children.push(n); + }); + parentNode.calcDuration(); + + this.profiles.push(parentNode); + this.profiles = this.profiles.slice(0, 100); + this._triggerProfilesAdded([parentNode]); + this.currentSet = []; + }); + } + + _triggerProfilesAdded(profiles: ProfileNode[]) { + this._profilesAddedCallbacks.forEach(function (item) { + item.callback.call(item.context, profiles); + }); + } +} diff --git a/packages/@ember/debug/ember-inspector-support/models/profile-node.ts b/packages/@ember/debug/ember-inspector-support/models/profile-node.ts new file mode 100644 index 00000000000..8e90e165b3c --- /dev/null +++ b/packages/@ember/debug/ember-inspector-support/models/profile-node.ts @@ -0,0 +1,85 @@ +/** + A tree structure for assembling a list of render calls so they can be grouped and displayed nicely afterwards. + + @class ProfileNode +**/ +import { guidFor } from '@ember/object/internals'; + +function get(obj: any, key: string) { + return obj.get ? obj.get(key) : obj[key]; +} + +export type Payload = { + view?: any; + template: any; + object?: any; + exception?: any; +}; + +class ProfileNode { + isHighlightEnabled = true; + declare time: number; + declare start: number; + declare timestamp: any; + declare viewGuid: string | undefined; + declare name: string; + declare parent: ProfileNode | undefined; + declare children: ProfileNode[]; + declare duration: number | undefined; + constructor(start: number, payload: Payload, parent?: ProfileNode, now?: number) { + let name; + this.start = start; + this.timestamp = now || Date.now(); + + if (payload) { + if (payload.template) { + name = payload.template; + } else if (payload.view) { + const view = payload.view; + name = get(view, 'instrumentDisplay') || get(view, '_debugContainerKey'); + if (name) { + name = name.replace(/^view:/, ''); + } + this.viewGuid = guidFor(view); + } + + if (!name && payload.object) { + name = payload.object + .toString() + .replace(/:?:ember\d+>$/, '') + .replace(/^$/); + if (match && match.length > 1) { + this.viewGuid = match[1]; + } + } + } + } + + this.name = name || 'Unknown view'; + + if (parent) { + this.parent = parent; + } + this.children = []; + } + + finish(timestamp: number) { + this.time = timestamp - this.start; + this.calcDuration(); + + // Once we attach to our parent, we remove that reference + // to avoid a graph cycle when serializing: + if (this.parent) { + this.parent.children.push(this); + this.parent = undefined; + } + } + + calcDuration() { + this.duration = Math.round(this.time! * 100) / 100; + } +} + +export default ProfileNode; diff --git a/packages/@ember/debug/ember-inspector-support/models/promise.ts b/packages/@ember/debug/ember-inspector-support/models/promise.ts new file mode 100644 index 00000000000..2c3c9a68c00 --- /dev/null +++ b/packages/@ember/debug/ember-inspector-support/models/promise.ts @@ -0,0 +1,55 @@ +import { typeOf } from '@ember/debug/ember-inspector-support/utils/type-check'; +import BaseObject from '@ember/debug/ember-inspector-support/utils/base-object'; + +const dateComputed = function () { + return function (this: any, target: any, propertyKey: string) { + Object.defineProperty(target, propertyKey, { + get(this: any): any { + return this[`__${propertyKey}__`]; + }, + set(this: any, date: Date | number | string) { + if (typeOf(date) === 'date') { + this[`__${propertyKey}__`] = date; + return; + } else if (typeof date === 'number' || typeof date === 'string') { + this[`__${propertyKey}__`] = new Date(date); + return; + } + this[`__${propertyKey}__`] = null; + }, + }); + }; +}; + +export default class PromiseModel extends BaseObject { + @dateComputed() createdAt: Date | null = null; + @dateComputed() settledAt: Date | null = null; + @dateComputed() chainedAt: Date | null = null; + + declare value: any; + declare reason: any; + guid = ''; + label = ''; + parent: PromiseModel | null = null; + children = []; + stack = []; + state = ''; + + get level(): number { + const parent = this.parent; + if (!parent) { + return 0; + } + return parent.level + 1; + } + + get isSettled() { + return this.isFulfilled || this.isRejected; + } + get isFulfilled() { + return this.state === 'fulfilled'; + } + get isRejected() { + return this.state === 'rejected'; + } +} diff --git a/packages/@ember/debug/ember-inspector-support/object-inspector.ts b/packages/@ember/debug/ember-inspector-support/object-inspector.ts new file mode 100644 index 00000000000..3dc6df5267d --- /dev/null +++ b/packages/@ember/debug/ember-inspector-support/object-inspector.ts @@ -0,0 +1,1265 @@ +import DebugPort from './debug-port'; +import bound from '@ember/debug/ember-inspector-support/utils/bound-method'; +import { typeOf, inspect } from '@ember/debug/ember-inspector-support/utils/type-check'; +import { cacheFor } from '@ember/object/internals'; +import { _backburner, join } from '@ember/runloop'; +import EmberObject from '@ember/object'; +import Service from '@ember/service'; +import CoreObject from '@ember/object/core'; +import { meta as emberMeta } from '@ember/-internals/meta'; +import emberNames from './utils/ember-object-names'; +import getObjectName from './utils/get-object-name'; +import type { Tag } from '@glimmer/validator'; +import { + valueForTag as tagValue, + validateTag as tagValidate, + track, + tagFor, +} from '@glimmer/validator'; +import { isComputed, tagForProperty } from '@ember/-internals/metal'; +import { guidFor } from './utils/ember/object/internals'; +import type Mixin from '@ember/object/mixin'; +import ObjectProxy from '@ember/object/proxy'; +import ArrayProxy from '@ember/array/proxy'; +import Component from '@ember/component'; + +const keys = Object.keys; + +/** + * Determine the type and get the value of the passed property + * @param {*} object The parent object we will look for `key` on + * @param {string} key The key for the property which points to a computed, EmberObject, etc + * @param {*} computedValue A value that has already been computed with calculateCP + * @return {{inspect: (string|*), type: string}|{computed: boolean, inspect: string, type: string}|{inspect: string, type: string}} + */ +function inspectValue( + object: any, + key: string, + computedValue?: any +): { type: string; inspect: string; isCalculated?: boolean } { + let string; + const value = computedValue; + + if (arguments.length === 3 && computedValue === undefined) { + return { type: `type-undefined`, inspect: 'undefined' }; + } + + // TODO: this is not very clean. We should refactor calculateCP, etc, rather than passing computedValue + if (computedValue !== undefined) { + if (value instanceof HTMLElement) { + return { + type: 'type-object', + inspect: `<${value.tagName.toLowerCase()}>`, + }; + } + return { type: `type-${typeOf(value)}`, inspect: inspect(value) }; + } + + if (value instanceof EmberObject) { + return { type: 'type-ember-object', inspect: value.toString() }; + } else if (isComputed(object, key)) { + string = ''; + return { type: 'type-descriptor', inspect: string }; + } else if (value?.isDescriptor) { + return { type: 'type-descriptor', inspect: value.toString() }; + } else if (value instanceof HTMLElement) { + return { type: 'type-object', inspect: value.tagName.toLowerCase() }; + } else { + return { type: `type-${typeOf(value)}`, inspect: inspect(value) }; + } +} + +function isMandatorySetter(descriptor: PropertyDescriptor) { + if ( + descriptor.set && + Function.prototype.toString.call(descriptor.set).includes('You attempted to update') + ) { + return true; + } + return false; +} + +function getTagTrackedTags(tag: Tag, ownTag: Tag, level = 0) { + const props: Tag[] = []; + // do not include tracked properties from dependencies + if (!tag || level > 1) { + return props; + } + const subtags = Array.isArray(tag.subtag) ? tag.subtag : []; + if (tag.subtag && !Array.isArray(tag.subtag)) { + // if (tag.subtag._propertyKey) props.push(tag.subtag); + // TODO fetch tag metadata object and property key + props.push(...getTagTrackedTags(tag.subtag, ownTag, level + 1)); + } + if (subtags) { + subtags.forEach((t) => { + if (t === ownTag) return; + // if (t._propertyKey) props.push(t); + // TODO fetch tag metadata object and property key + props.push(...getTagTrackedTags(t, ownTag, level + 1)); + }); + } + return props; +} + +// TODO needs https://github.com/glimmerjs/glimmer-vm/pull/1489 +function getTrackedDependencies( + object: any, + property: string, + tagInfo: { tag: Tag; revision: number } +) { + const tag = tagInfo.tag; + const proto = Object.getPrototypeOf(object); + if (!proto) return []; + const cpDesc = emberMeta(object).peekDescriptors(property); + const dependentKeys = []; + if (cpDesc) { + dependentKeys.push(...(cpDesc._dependentKeys || []).map((k: string) => ({ name: k }))); + } + const ownTag = tagFor(object, property); + const tags = getTagTrackedTags(tag, ownTag); + const mapping: Record = {}; + let maxRevision = tagValue(tag); + tags.forEach(() => { + // TODO needs https://github.com/glimmerjs/glimmer-vm/pull/1489 + // const p = (t._object ? getObjectName(t._object) + '.' : '') + t._propertyKey; + // const [objName, prop] = p.split('.'); + // mapping[objName] = mapping[objName] || new Set(); + // const value = tagValue(t); + // if (prop) { + // mapping[objName].add([prop, value]); + // } + }); + const hasChange = (tagInfo.revision && maxRevision !== tagInfo.revision) || false; + const names = new Set(); + Object.entries(mapping).forEach(([objName, props]) => { + if (names.has(objName)) { + return; + } + names.add(objName); + if (props.length > 1) { + dependentKeys.push({ name: objName }); + props.forEach((p) => { + const changed = hasChange && p[1] > tagInfo.revision; + const obj = { + child: p[0], + changed: false, + }; + if (changed) { + obj.changed = true; + } + dependentKeys.push(obj); + }); + } + if (props.length === 1) { + const p = [...props][0]!; + const changed = hasChange && p[1] > tagInfo.revision; + const obj = { + name: objName + '.' + p[0], + changed: false, + }; + if (changed) { + obj.changed = true; + } + dependentKeys.push(obj); + } + if (props.length === 0) { + dependentKeys.push({ name: objName }); + } + }); + + return [...dependentKeys]; +} + +type DebugPropertyInfo = { + skipMixins: string[]; + skipProperties: string[]; + groups: { + name: string; + expand: boolean; + properties: string[]; + }[]; +}; + +type PropertyInfo = { + code: any; + isService: any; + dependentKeys: any; + isGetter: any; + isTracked: any; + isProperty: any; + isComputed: any; + auto: any; + readOnly: any; + isMandatorySetter: any; + canTrack: boolean; + name: any; + value: any; + isExpensive: boolean; + overridden: boolean; +}; + +type MixinDetails = { + id: string; + name: string; + properties: PropertyInfo[]; + expand?: boolean; + isEmberMixin?: boolean; +}; + +type TagInfo = { + revision: number; + tag: Tag; +}; + +export default class ObjectInspector extends DebugPort { + get adapter() { + return this.namespace?.adapter; + } + + currentObject: { + object: any; + mixinDetails: MixinDetails[]; + objectId: string; + } | null = null; + + updateCurrentObject() { + Object.values(this.sentObjects).forEach((obj) => { + if (obj instanceof CoreObject && obj.isDestroyed) { + this.dropObject(guidFor(obj)); + } + }); + if (this.currentObject) { + const { object, mixinDetails, objectId } = this.currentObject; + mixinDetails.forEach((mixin, mixinIndex) => { + mixin.properties.forEach((item) => { + if (item.overridden) { + return true; + } + try { + let cache = cacheFor(object, item.name); + if (item.isExpensive && !cache) return true; + if (item.value.type === 'type-function') return true; + + let value = null; + let changed = false; + const values = (this.objectPropertyValues[objectId] = + this.objectPropertyValues[objectId] || {}); + const tracked = (this.trackedTags[objectId] = this.trackedTags[objectId] || {}); + + const desc = Object.getOwnPropertyDescriptor(object, item.name); + const isSetter = desc && isMandatorySetter(desc); + + if (item.canTrack && !isSetter) { + let tagInfo = tracked[item.name] || { + tag: tagFor(object, item.name), + revision: 0, + }; + if (!tagInfo.tag) return false; + + changed = !tagValidate(tagInfo.tag, tagInfo.revision); + if (changed) { + tagInfo.tag = track(() => { + value = object.get?.(item.name) || object[item.name]; + }); + } + tracked[item.name] = tagInfo; + } else { + value = calculateCP(object, item, {}); + if (values[item.name] !== value) { + changed = true; + values[item.name] = value; + } + } + + if (changed) { + value = inspectValue(object, item.name, value) as any; + value.isCalculated = true; + let dependentKeys = null; + if (tracked[item.name]) { + dependentKeys = getTrackedDependencies(object, item.name, tracked[item.name]); + tracked[item.name].revision = tagValue(tracked[item.name].tag); + } + this.sendMessage('updateProperty', { + objectId, + property: + Array.isArray(object) && !Number.isNaN(parseInt(item.name)) + ? parseInt(item.name) + : item.name, + value, + mixinIndex, + dependentKeys, + }); + } + } catch { + // dont do anything + } + return false; + }); + }); + } + } + + init() { + super.init(); + this.sentObjects = {}; + _backburner.on('end', bound(this, this.updateCurrentObject)); + } + + willDestroy() { + super.willDestroy(); + for (let objectId in this.sentObjects) { + this.releaseObject(objectId); + } + _backburner.off('end', bound(this, this.updateCurrentObject)); + } + + sentObjects: Record = {}; + + parentObjects: Record = {}; + + objectPropertyValues: Record> = {}; + + trackedTags: Record> = {}; + + _errorsFor: Record> = {}; + + static { + this.prototype.portNamespace = 'objectInspector'; + this.prototype.messages = { + digDeeper(this: ObjectInspector, message: { objectId: any; property: any }) { + this.digIntoObject(message.objectId, message.property); + }, + releaseObject(this: ObjectInspector, message: { objectId: any }) { + this.releaseObject(message.objectId); + }, + calculate( + this: ObjectInspector, + message: { + objectId: string; + property: string; + mixinIndex: number; + isCalculated: boolean; + } + ) { + let value; + value = this.valueForObjectProperty(message.objectId, message.property, message.mixinIndex); + if (value) { + this.sendMessage('updateProperty', value); + message.isCalculated = true; + } + this.sendMessage('updateErrors', { + objectId: message.objectId, + errors: errorsToSend(this._errorsFor[message.objectId]!), + }); + }, + saveProperty( + this: ObjectInspector, + message: { value: any; dataType: string; objectId: string; property: string } + ) { + let value = message.value; + if (message.dataType && message.dataType === 'date') { + value = new Date(value); + } + this.saveProperty(message.objectId, message.property, value); + }, + sendToConsole(this: ObjectInspector, message: { objectId: any; property: string }) { + this.sendToConsole(message.objectId, message.property); + }, + gotoSource(this: ObjectInspector, message: { objectId: any; property: string }) { + this.gotoSource(message.objectId, message.property); + }, + sendControllerToConsole(this: ObjectInspector, message: { name: string }) { + const container = this.namespace?.owner; + this.sendValueToConsole(container.lookup(`controller:${message.name}`)); + }, + sendRouteHandlerToConsole(this: ObjectInspector, message: { name: string }) { + const container = this.namespace?.owner; + this.sendValueToConsole(container.lookup(`route:${message.name}`)); + }, + sendContainerToConsole(this: ObjectInspector) { + const container = this.namespace?.owner; + this.sendValueToConsole(container); + }, + /** + * Lookup the router instance, and find the route with the given name + * @param message The message sent + * @param {string} messsage.name The name of the route to lookup + */ + inspectRoute(this: ObjectInspector, message: { name: string }) { + const container = this.namespace?.owner; + const router = container.lookup('router:main'); + const routerLib = router._routerMicrolib || router.router; + // 3.9.0 removed intimate APIs from router + // https://github.com/emberjs/ember.js/pull/17843 + // https://deprecations.emberjs.com/v3.x/#toc_remove-handler-infos + // Ember >= 3.9.0 + this.sendObject(routerLib.getRoute(message.name)); + }, + inspectController(this: ObjectInspector, message: { name: string }) { + const container = this.namespace?.owner; + this.sendObject(container.lookup(`controller:${message.name}`)); + }, + inspectById(this: ObjectInspector, message: { objectId: string }) { + const obj = this.sentObjects[message.objectId]!; + if (obj) { + this.sendObject(obj); + } + }, + inspectByContainerLookup(this: ObjectInspector, message: { name: string }) { + const container = this.namespace?.owner; + this.sendObject(container.lookup(message.name)); + }, + traceErrors(this: ObjectInspector, message: { objectId: string }) { + let errors = this._errorsFor[message.objectId]!; + toArray(errors).forEach((error) => { + let stack = error.error; + if (stack && stack.stack) { + stack = stack.stack; + } else { + stack = error; + } + this.adapter.log(`Object Inspector error for ${error.property}`, stack); + }); + }, + }; + } + + canSend(val: any) { + return ( + val && + (val instanceof EmberObject || + val instanceof Object || + typeOf(val) === 'object' || + typeOf(val) === 'array') + ); + } + + saveProperty(objectId: string, prop: string, val: any) { + let object = this.sentObjects[objectId]; + join(() => { + if (object.set) { + object.set(prop, val); + } else { + object[prop] = val; + } + }); + } + + gotoSource(objectId: string, prop: string) { + let object = this.sentObjects[objectId]; + let value; + + if (prop === null || prop === undefined) { + value = this.sentObjects[objectId]; + } else { + value = calculateCP(object, { name: prop } as PropertyInfo, {}); + } + // for functions and classes we want to show the source + if (typeof value === 'function') { + this.adapter.inspectValue(value); + } + // use typeOf to distinguish basic objects/classes and Date, Error etc. + // objects like {...} have the constructor set to Object + if (typeOf(value) === 'object' && value.constructor !== Object) { + this.adapter.inspectValue(value.constructor); + } + } + + sendToConsole(objectId: string, prop?: string) { + let object = this.sentObjects[objectId]; + let value; + + if (prop === null || prop === undefined) { + value = this.sentObjects[objectId]; + } else { + value = calculateCP(object, { name: prop } as PropertyInfo, {}); + } + + this.sendValueToConsole(value); + } + + sendValueToConsole(value: any) { + (window as any).$E = value; + if (value instanceof Error) { + value = value.stack; + } + let args = [value]; + if (value instanceof EmberObject) { + args.unshift(inspect(value)); + } + this.adapter.log('Ember Inspector ($E): ', ...args); + } + + digIntoObject(objectId: string, property: string) { + let parentObject = this.sentObjects[objectId]; + let object = calculateCP(parentObject, { name: property } as PropertyInfo, {}); + + if (this.canSend(object)) { + const currentObject = this.currentObject; + let details = this.mixinsForObject(object); + this.parentObjects[details.objectId] = currentObject; + this.sendMessage('updateObject', { + parentObject: objectId, + property, + objectId: details.objectId, + name: getObjectName(object), + details: details.mixins, + errors: details.errors, + }); + } + } + + sendObject(object: any) { + if (!this.canSend(object)) { + throw new Error(`Can't inspect ${object}. Only Ember objects and arrays are supported.`); + } + let details = this.mixinsForObject(object); + this.sendMessage('updateObject', { + objectId: details.objectId, + name: getObjectName(object), + details: details.mixins, + errors: details.errors, + }); + } + + retainObject(object: any) { + let meta = emberMeta(object) as any; + let guid = guidFor(object); + + meta._debugReferences = meta._debugReferences || 0; + meta._debugReferences++; + + this.sentObjects[guid] = object; + + return guid; + } + + releaseObject(objectId: string) { + let object = this.sentObjects[objectId]; + if (!object) { + return; + } + let meta = emberMeta(object) as any; + let guid = guidFor(object); + + meta._debugReferences--; + + if (meta._debugReferences === 0) { + this.dropObject(guid); + } + } + + dropObject(objectId: string) { + if (this.parentObjects[objectId]) { + this.currentObject = this.parentObjects[objectId]; + } + delete this.parentObjects[objectId]; + + delete this.sentObjects[objectId]; + delete this.objectPropertyValues[objectId]; + delete this.trackedTags[objectId]; + if (this.currentObject && this.currentObject.objectId === objectId) { + this.currentObject = null; + } + + delete this._errorsFor[objectId]; + + this.sendMessage('droppedObject', { objectId }); + } + + /** + * This function, and the rest of Ember Inspector, currently refer to the + * output entirely as mixins. However, this is no longer accurate! This has + * been refactored to return a list of objects that represent both the classes + * themselves and their mixins. For instance, the following class definitions: + * + * ```js + * class Foo extends EmberObject {} + * + * class Bar extends Foo {} + * + * class Baz extends Bar.extend(Mixin1, Mixin2) {} + * + * let obj = Baz.create(); + * ``` + * + * Will result in this in the inspector: + * + * ``` + * - Own Properties + * - Baz + * - Mixin1 + * - Mixin2 + * - Bar + * - Foo + * - EmberObject + * ``` + * + * The "mixins" returned by this function directly represent these things too. + * Each class object consists of the actual own properties of that class's + * prototype, and is followed by the mixins (if any) that belong to that + * class. Own Properties represents the actual own properties of the object + * itself. + * + * TODO: The rest of the Inspector should be updated to reflect this new data + * model, and these functions should be updated with new names. Mixins should + * likely be embedded _on_ the class definitions, but this was designed to be + * backwards compatible. + */ + mixinDetailsForObject(object: any): MixinDetails[] { + const mixins = []; + + const own = ownMixins(object); + + const objectMixin = { + id: guidFor(object), + name: getObjectName(object), + properties: ownProperties(object, own), + } as MixinDetails; + + mixins.push(objectMixin); + + // insert ember mixins + for (let mixin of own) { + let name = (mixin.ownerConstructor || emberNames.get(mixin) || '').toString(); + + if (!name && typeof mixin.toString === 'function') { + try { + name = mixin.toString(); + + if (name === '(unknown)') { + name = '(unknown mixin)'; + } + } catch { + name = '(Unable to convert Object to string)'; + } + } + + const mix = { + properties: propertiesForMixin(mixin), + name, + isEmberMixin: true, + id: guidFor(mixin), + }; + + mixins.push(mix); + } + + const proto = Object.getPrototypeOf(object); + + if (proto && proto !== Object.prototype) { + mixins.push(...this.mixinDetailsForObject(proto)); + } + + return mixins; + } + + mixinsForObject(object: any) { + if (object instanceof ObjectProxy && object.content && !(object as any)._showProxyDetails) { + object = object.content; + } + + if (object instanceof ArrayProxy && object.content && !(object as any)._showProxyDetails) { + object = object.slice(0, 101); + } + + let mixinDetails = this.mixinDetailsForObject(object); + + mixinDetails[0]!.name = 'Own Properties'; + mixinDetails[0]!.expand = true; + + if (mixinDetails[1] && !mixinDetails[1].isEmberMixin) { + mixinDetails[1].expand = true; + } + + fixMandatorySetters(mixinDetails); + applyMixinOverrides(mixinDetails); + + let debugPropertyInfo = null; + let debugInfo = getDebugInfo(object); + if (debugInfo) { + debugPropertyInfo = getDebugInfo(object).propertyInfo; + mixinDetails = customizeProperties(mixinDetails, debugPropertyInfo); + } + + let expensiveProperties = null; + if (debugPropertyInfo) { + expensiveProperties = debugPropertyInfo.expensiveProperties; + } + + let objectId = this.retainObject(object); + + let errorsForObject = (this._errorsFor[objectId] = {}); + const tracked = (this.trackedTags[objectId] = this.trackedTags[objectId] || {}); + calculateCPs(object, mixinDetails, errorsForObject, expensiveProperties, tracked); + + this.currentObject = { object, mixinDetails, objectId }; + + let errors = errorsToSend(errorsForObject); + return { objectId, mixins: mixinDetails, errors }; + } + + valueForObjectProperty(objectId: string, property: string, mixinIndex: number) { + let object = this.sentObjects[objectId], + value; + + if (object.isDestroying) { + value = ''; + } else { + value = calculateCP(object, { name: property } as PropertyInfo, this._errorsFor[objectId]!); + } + + if (!value || !(value instanceof CalculateCPError)) { + value = inspectValue(object, property, value); + value.isCalculated = true; + + return { objectId, property, value, mixinIndex }; + } + + return null; + } + + inspect = inspect; + inspectValue = inspectValue; +} + +function ownMixins(object: any) { + // TODO: We need to expose an API for getting _just_ the own mixins directly + let meta = emberMeta(object); + let parentMeta = meta.parent; + let mixins = new Set(); + + // Filter out anonymous mixins that are directly in a `class.extend` + let baseMixins = + object.constructor && + object.constructor.PrototypeMixin && + object.constructor.PrototypeMixin.mixins; + + meta.forEachMixins((m: Mixin) => { + // Find mixins that: + // - Are not in the parent classes + // - Are not primitive (has mixins, doesn't have properties) + // - Don't include any of the base mixins from a class extend + if ( + (!parentMeta || !parentMeta.hasMixin(m)) && + !m.properties && + m.mixins && + (!baseMixins || !m.mixins.some((m) => baseMixins.includes(m))) + ) { + mixins.add(m); + } + }); + + return mixins; +} + +function ownProperties(object: any, ownMixins: Set) { + let meta = emberMeta(object); + + if (Array.isArray(object)) { + // slice to max 101, for performance and so that the object inspector will show a `more items` indicator above 100 + object = object.slice(0, 101); + } + + let props = Object.getOwnPropertyDescriptors(object) as any; + delete props.constructor; + + // meta has the correct descriptors for CPs + meta.forEachDescriptors((name, desc) => { + // only for own properties + if (props[name]) { + props[name] = desc; + } + }); + + // remove properties set by mixins + // especially for Object.extend(mixin1, mixin2), where a new class is created which holds the merged properties + // if all properties are removed, it will be marked as useless mixin and will not be shown + ownMixins.forEach((m) => { + if (m.mixins) { + m.mixins.forEach((mix) => { + Object.keys(mix.properties || {}).forEach((k) => { + const pDesc = Object.getOwnPropertyDescriptor(mix.properties, k) as PropertyDescriptor & { + _getter: () => any; + }; + if (pDesc && props[k] && pDesc.get && pDesc.get === props[k].get) { + delete props[k]; + } + if (pDesc && props[k] && 'value' in pDesc && pDesc.value === props[k].value) { + delete props[k]; + } + if (pDesc && props[k] && pDesc._getter === props[k]._getter) { + delete props[k]; + } + }); + }); + } + }); + + Object.keys(props).forEach((k) => { + if (typeof props[k].value === 'function') { + return; + } + props[k].isDescriptor = true; + }); + + // Clean the properties, removing private props and bindings, etc + return addProperties([], props); +} + +function propertiesForMixin(mixin: Mixin) { + let properties: PropertyInfo[] = []; + + if (mixin.mixins) { + mixin.mixins.forEach((mixin) => { + if (mixin.properties) { + addProperties(properties, mixin.properties); + } + }); + } + + return properties; +} + +function addProperties(properties: unknown[], hash: any) { + for (let prop in hash) { + if (!Object.prototype.hasOwnProperty.call(hash, prop)) { + continue; + } + + if (isInternalProperty(prop)) { + continue; + } + + // remove `fooBinding` type props + if (prop.match(/Binding$/)) { + continue; + } + + // when mandatory setter is removed, an `undefined` value may be set + const desc = Object.getOwnPropertyDescriptor(hash, prop) as any; + if (!desc) continue; + if (hash[prop] === undefined && desc.value === undefined && !desc.get && !desc._getter) { + continue; + } + + let options = { + isMandatorySetter: isMandatorySetter(desc), + isService: false, + isComputed: false, + code: undefined as string | undefined, + dependentKeys: undefined, + isCalculated: undefined as boolean | undefined, + readOnly: undefined as boolean | undefined, + auto: undefined as boolean | undefined, + canTrack: undefined as boolean | undefined, + isGetter: undefined as boolean | undefined, + isTracked: undefined as boolean | undefined, + isProperty: undefined as boolean | undefined, + }; + + if (typeof hash[prop] === 'object' && hash[prop] !== null) { + options.isService = !('type' in hash[prop]) && hash[prop].type === 'service'; + + if (!options.isService) { + if (hash[prop].constructor) { + options.isService = hash[prop].constructor.isServiceFactory; + } + } + + if (!options.isService) { + options.isService = desc.value instanceof Service; + } + } + if (options.isService) { + replaceProperty(properties, prop, inspectValue(hash, prop), options); + continue; + } + + if (isComputed(hash, prop)) { + options.isComputed = true; + options.dependentKeys = (desc._dependentKeys || []).map((key: string) => key.toString()); + + if (typeof desc.get === 'function') { + options.code = Function.prototype.toString.call(desc.get); + } + if (typeof desc._getter === 'function') { + options.isCalculated = true; + options.code = Function.prototype.toString.call(desc._getter); + } + if (!options.code) { + options.code = ''; + } + + options.readOnly = desc._readOnly; + options.auto = desc._auto; + options.canTrack = options.code !== ''; + } + + if (desc.get) { + options.isGetter = true; + options.canTrack = true; + if (!desc.set) { + options.readOnly = true; + } + } + if (Object.prototype.hasOwnProperty.call(desc, 'value') || options.isMandatorySetter) { + delete options.isGetter; + delete options.isTracked; + options.isProperty = true; + options.canTrack = false; + } + replaceProperty(properties, prop, inspectValue(hash, prop), options); + } + + return properties; +} + +function isInternalProperty(property: string) { + if ( + [ + '_state', + '_states', + '_target', + '_currentState', + '_super', + '_debugContainerKey', + '_transitionTo', + '_debugInfo', + '_showProxyDetails', + ].includes(property) + ) { + return true; + } + + let isInternalProp = [ + '__LEGACY_OWNER', + '__ARGS__', + '__HAS_BLOCK__', + '__PROPERTY_DID_CHANGE__', + ].some((internalProp) => property.startsWith(internalProp)); + + return isInternalProp; +} + +function replaceProperty(properties: any, name: string, value: any, options: any) { + let found; + + let i, l; + for (i = 0, l = properties.length; i < l; i++) { + if (properties[i].name === name) { + found = i; + break; + } + } + + if (found) { + properties.splice(i, 1); + } + + let prop = { name, value } as PropertyInfo; + prop.isMandatorySetter = options.isMandatorySetter; + prop.readOnly = options.readOnly; + prop.auto = options.auto; + prop.canTrack = options.canTrack; + prop.isComputed = options.isComputed; + prop.isProperty = options.isProperty; + prop.isTracked = options.isTracked; + prop.isGetter = options.isGetter; + prop.dependentKeys = options.dependentKeys || []; + let hasServiceFootprint = + prop.value && typeof prop.value.inspect === 'string' + ? prop.value.inspect.includes('@service:') + : false; + prop.isService = options.isService || hasServiceFootprint; + prop.code = options.code; + properties.push(prop); +} + +function fixMandatorySetters(mixinDetails: MixinDetails[]) { + let seen: any = {}; + let propertiesToRemove: any = []; + + mixinDetails.forEach((detail, detailIdx) => { + detail.properties.forEach((property) => { + if (property.isMandatorySetter) { + seen[property.name] = { + name: property.name, + value: property.value.inspect, + detailIdx, + property, + }; + } else if (Object.prototype.hasOwnProperty.call(seen, property.name) && seen[property.name]) { + propertiesToRemove.push(seen[property.name]); + delete seen[property.name]; + } + }); + }); + + propertiesToRemove.forEach((prop: any) => { + let detail = mixinDetails[prop.detailIdx]!; + let index = detail.properties.indexOf(prop.property); + if (index !== -1) { + detail.properties.splice(index, 1); + } + }); +} + +function applyMixinOverrides(mixinDetails: MixinDetails[]) { + let seen: any = {}; + mixinDetails.forEach((detail) => { + detail.properties.forEach((property) => { + if (Object.prototype.hasOwnProperty.call(Object.prototype, property.name)) { + return; + } + + if (seen[property.name]) { + property.overridden = seen[property.name]; + delete property.value.isCalculated; + } + + seen[property.name] = detail.name; + }); + }); +} + +function calculateCPs( + object: any, + mixinDetails: MixinDetails[], + errorsForObject: Record, + expensiveProperties: string[], + tracked: Record +) { + expensiveProperties = expensiveProperties || []; + mixinDetails.forEach((mixin) => { + mixin.properties.forEach((item) => { + if (item.overridden) { + return true; + } + if (!item.value.isCalculated) { + let cache = cacheFor(object, item.name); + item.isExpensive = expensiveProperties.indexOf(item.name) >= 0; + if (cache !== undefined || !item.isExpensive) { + let value; + if (item.canTrack) { + tracked[item.name] = tracked[item.name] || ({} as TagInfo); + const tagInfo = tracked[item.name]!; + tagInfo.tag = track(() => { + value = calculateCP(object, item, errorsForObject); + }); + if (tagInfo.tag === tagForProperty(object, item.name)) { + if (!item.isComputed && !item.isService) { + item.code = ''; + item.isTracked = true; + } + } + item.dependentKeys = getTrackedDependencies(object, item.name, tagInfo); + tagInfo.revision = tagValue(tagInfo.tag); + } else { + value = calculateCP(object, item, errorsForObject); + } + if (!value || !(value instanceof CalculateCPError)) { + item.value = inspectValue(object, item.name, value); + item.value.isCalculated = true; + if (item.value.type === 'type-function') { + item.code = ''; + } + } + } + } + return; + }); + }); +} + +/** + Customizes an object's properties + based on the property `propertyInfo` of + the object's `_debugInfo` method. + + Possible options: + - `groups` An array of groups that contains the properties for each group + For example: + ```javascript + groups: [ + { name: 'Attributes', properties: ['firstName', 'lastName'] }, + { name: 'Belongs To', properties: ['country'] } + ] + ``` + - `includeOtherProperties` Boolean, + - `true` to include other non-listed properties, + - `false` to only include given properties + - `skipProperties` Array containing list of properties *not* to include + - `skipMixins` Array containing list of mixins *not* to include + - `expensiveProperties` An array of computed properties that are too expensive. + Adding a property to this array makes sure the CP is not calculated automatically. + + Example: + ```javascript + { + propertyInfo: { + includeOtherProperties: true, + skipProperties: ['toString', 'send', 'withTransaction'], + skipMixins: [ 'Ember.Evented'], + calculate: ['firstName', 'lastName'], + groups: [ + { + name: 'Attributes', + properties: [ 'id', 'firstName', 'lastName' ], + expand: true // open by default + }, + { + name: 'Belongs To', + properties: [ 'maritalStatus', 'avatar' ], + expand: true + }, + { + name: 'Has Many', + properties: [ 'phoneNumbers' ], + expand: true + }, + { + name: 'Flags', + properties: ['isLoaded', 'isLoading', 'isNew', 'isDirty'] + } + ] + } + } + ``` + */ +function customizeProperties(mixinDetails: MixinDetails[], propertyInfo: DebugPropertyInfo) { + let newMixinDetails: MixinDetails[] = []; + let neededProperties: Record = {}; + let groups = propertyInfo.groups || []; + let skipProperties = propertyInfo.skipProperties || []; + let skipMixins = propertyInfo.skipMixins || []; + + if (groups.length) { + mixinDetails[0]!.expand = false; + } + + groups.forEach((group) => { + group.properties.forEach((prop) => { + neededProperties[prop] = true; + }); + }); + + mixinDetails.forEach((mixin) => { + let newProperties: PropertyInfo[] = []; + mixin.properties.forEach((item) => { + if (skipProperties.indexOf(item.name) !== -1) { + return true; + } + + if ( + !item.overridden && + Object.prototype.hasOwnProperty.call(neededProperties, item.name) && + neededProperties[item.name] + ) { + neededProperties[item.name] = item; + } else { + newProperties.push(item); + } + return; + }); + mixin.properties = newProperties; + if (mixin.properties.length === 0 && mixin.name.toLowerCase().includes('unknown')) { + // nothing useful for this mixin + return; + } + if (skipMixins.indexOf(mixin.name) === -1) { + newMixinDetails.push(mixin); + } + }); + + groups + .slice() + .reverse() + .forEach((group) => { + let newMixin = { + name: group.name, + expand: group.expand, + properties: [] as PropertyInfo[], + } as MixinDetails; + group.properties.forEach(function (prop) { + // make sure it's not `true` which means property wasn't found + if (neededProperties[prop] !== true) { + newMixin.properties.push(neededProperties[prop] as PropertyInfo); + } + }); + newMixinDetails.unshift(newMixin); + }); + + return newMixinDetails; +} + +function getDebugInfo(object: any) { + let debugInfo = null; + let objectDebugInfo = object._debugInfo; + if (objectDebugInfo && typeof objectDebugInfo === 'function') { + if (object instanceof ObjectProxy && object.content) { + object = object.content; + } + debugInfo = objectDebugInfo.call(object); + } + debugInfo = debugInfo || {}; + let propertyInfo = debugInfo.propertyInfo || (debugInfo.propertyInfo = {}); + let skipProperties = (propertyInfo.skipProperties = + propertyInfo.skipProperties || (propertyInfo.skipProperties = [])); + + skipProperties.push('isDestroyed', 'isDestroying', 'container'); + // 'currentState' and 'state' are un-observable private properties. + // The rest are skipped to reduce noise in the inspector. + if (Component && object instanceof Component) { + skipProperties.push( + 'currentState', + 'state', + 'buffer', + 'outletSource', + 'lengthBeforeRender', + 'lengthAfterRender', + 'template', + 'layout', + 'templateData', + 'domManager', + 'states', + 'element', + 'targetObject' + ); + } else if (object?.constructor?.name === 'GlimmerDebugComponent') { + // These properties don't really exist on Glimmer Components, but + // reading their values trigger a development mode assertion. The + // more correct long term fix is to make getters lazy (shows "..." + // in the UI and only computed them when requested (when the user + // clicked on the "..." in the UI). + skipProperties.push('bounds', 'debugName', 'element'); + } + return debugInfo; +} + +function toArray(errors: Record) { + return keys(errors).map((key) => errors[key]); +} + +function calculateCP(object: any, item: PropertyInfo, errorsForObject: Record) { + const property = item.name; + delete errorsForObject[property]; + try { + if (object instanceof ArrayProxy && property == parseInt(property)) { + return object.objectAt(property); + } + return item.isGetter || property.includes?.('.') + ? object[property] + : object.get?.(property) || object[property]; // need to use `get` to be able to detect tracked props + } catch (error) { + errorsForObject[property] = { property, error }; + return new CalculateCPError(); + } +} + +class CalculateCPError {} + +function errorsToSend(errors: Record) { + return toArray(errors).map((error) => ({ property: error.property })); +} diff --git a/packages/@ember/debug/ember-inspector-support/port.ts b/packages/@ember/debug/ember-inspector-support/port.ts new file mode 100644 index 00000000000..913643e7062 --- /dev/null +++ b/packages/@ember/debug/ember-inspector-support/port.ts @@ -0,0 +1,102 @@ +import { guidFor } from '@ember/debug/ember-inspector-support/utils/ember/object/internals'; +import { run } from '@ember/runloop'; +import BaseObject from '@ember/debug/ember-inspector-support/utils/base-object'; +import Evented from '@ember/debug/ember-inspector-support/utils/evented'; + +export default class Port extends Evented.extend(BaseObject) { + declare now: number; + constructor(data: any) { + super(data); + } + + get adapter() { + return this.namespace?.adapter; + } + get applicationId() { + return this.namespace?.applicationId; + } + + get applicationName() { + return this.namespace?._application?.name || this.namespace?._application?.modulePrefix; + } + + /** + * Unique id per application (not application instance). It's very important + * that this id doesn't change when the app is reset otherwise the inspector + * will no longer recognize the app. + */ + get uniqueId() { + return guidFor(this.namespace?._application, 'ember'); + } + + init() { + /** + * Stores the timestamp when it was first accessed. + */ + this.now = Date.now(); + + this.adapter.onMessageReceived((message: any) => { + if (this.uniqueId === message.applicationId || !message.applicationId) { + this.messageReceived(message.type, message); + } + }); + } + + messageReceived(name: string, message: any) { + // We should generally not be run-wrapping here. Starting a runloop in + // ember-debug will cause the inspected app to revalidate/rerender. We + // are generally not intending to cause changes to the rendered output + // of the app, so this is generally unnecessary, and in big apps this + // could be quite slow. There is nothing special about the `view:*` + // messages – I (GC) just happened to have reviewed all of them recently + // and can be quite sure that they don't need the runloop. We should + // audit the rest of them and see if we can remove the else branch. I + // think we most likely can. In the limited cases (if any) where the + // runloop is needed, the callback code should just do the wrapping + // themselves. + if (name.startsWith('view:')) { + try { + this.trigger(name, message); + } catch (error) { + this.adapter.handleError(error); + } + } else { + this.wrap(() => { + this.trigger(name, message); + }); + } + } + + send(messageType: string, options: any = {}) { + options.type = messageType; + options.from = 'inspectedWindow'; + options.applicationId = this.uniqueId; + options.applicationName = this.applicationName; + this.adapter.send(options); + } + + /** + * Wrap all code triggered from outside of + * EmberDebug with this method. + * + * `wrap` is called by default + * on all callbacks triggered by `port`, + * so no need to call it in this case. + * + * - Wraps a callback in `Ember.run`. + * - Catches all errors during production + * and displays them in a user friendly manner. + * + * @param {Function} fn + * @return {Mixed} The return value of the passed function + */ + wrap(fn: () => any) { + return run(this, function () { + try { + return fn(); + } catch (error) { + this.adapter.handleError(error); + } + }); + } +} diff --git a/packages/@ember/debug/ember-inspector-support/promise-debug.ts b/packages/@ember/debug/ember-inspector-support/promise-debug.ts new file mode 100644 index 00000000000..be8c3c957e1 --- /dev/null +++ b/packages/@ember/debug/ember-inspector-support/promise-debug.ts @@ -0,0 +1,219 @@ +import DebugPort from './debug-port'; +import type { + PromiseChainedEvent, + PromiseUpdatedEvent, +} from '@ember/debug/ember-inspector-support/libs/promise-assembler'; +import PromiseAssembler from '@ember/debug/ember-inspector-support/libs/promise-assembler'; +import { debounce } from '@ember/runloop'; +import RSVP from 'rsvp'; +import type PromiseModel from './models/promise'; + +export default class PromiseDebug extends DebugPort { + private declare __session: any; + declare promiseAssembler: PromiseAssembler; + declare updatedPromises: PromiseModel[]; + declare releaseMethods: (() => void)[]; + declare portNamespace: string; + declare messages: { + getAndObservePromises(): void; + releasePromises(): void; + sendValueToConsole(message: any): void; + tracePromise(message: any): void; + setInstrumentWithStack(message: any): void; + getInstrumentWithStack(): void; + }; + get objectInspector() { + return this.namespace?.objectInspector; + } + get adapter() { + return this.namespace?.adapter; + } + get session() { + return this.__session || this.namespace?.session; + } + + set session(value) { + this.__session = value; + } + + constructor(data?: any) { + super(data); + this.promiseAssembler = new PromiseAssembler(); + this.updatedPromises = []; + this.releaseMethods = []; + this.setInstrumentWithStack(); + this.sendInstrumentWithStack(); + this.promiseAssembler.start(); + } + + delay = 100; + + willDestroy() { + this.releaseAll(); + if (this.promiseAssembler) { + this.promiseAssembler.destroy(); + } + super.willDestroy(); + } + + static { + this.prototype.portNamespace = 'promise'; + this.prototype.messages = { + getAndObservePromises() { + this.getAndObservePromises(); + }, + + releasePromises(this: PromiseDebug) { + this.releaseAll(); + }, + + sendValueToConsole(this: PromiseDebug, message) { + let promiseId = message.promiseId; + let promise = this.promiseAssembler!.find(promiseId); + let value = promise.value; + if (value === undefined) { + value = promise.reason; + } + this.objectInspector.sendValueToConsole(value); + }, + + tracePromise(this: PromiseDebug, message) { + let id = message.promiseId; + let promise = this.promiseAssembler.find(id); + // Remove first two lines and add label + let stack = promise.stack; + if (stack) { + stack = stack.split('\n'); + stack.splice(0, 2, [`Ember Inspector (Promise Trace): ${promise.label || ''}`]); + this.adapter.log(stack.join('\n')); + } + }, + + setInstrumentWithStack(this: PromiseDebug, message) { + this.instrumentWithStack = message.instrumentWithStack; + this.setInstrumentWithStack(); + }, + + getInstrumentWithStack(this: PromiseDebug) { + this.sendInstrumentWithStack(); + }, + }; + } + + get instrumentWithStack() { + return Boolean(this.session.getItem('promise:stack')); + } + + set instrumentWithStack(value) { + this.session.setItem('promise:stack', value); + } + + sendInstrumentWithStack() { + this.sendMessage('instrumentWithStack', { + instrumentWithStack: this.instrumentWithStack, + }); + } + + setInstrumentWithStack() { + RSVP.configure('instrument-with-stack', this.instrumentWithStack); + this.sendInstrumentWithStack(); + } + + releaseAll() { + this.releaseMethods.forEach((fn: () => any) => { + fn(); + }); + this.releaseMethods.length = 0; + } + + getAndObservePromises() { + this.promiseAssembler.on('created', this, this.promiseUpdated); + this.promiseAssembler.on('fulfilled', this, this.promiseUpdated); + this.promiseAssembler.on('rejected', this, this.promiseUpdated); + this.promiseAssembler.on('chained', this, this.promiseChained); + + this.releaseMethods.push(() => { + this.promiseAssembler.off('created', this, this.promiseUpdated); + this.promiseAssembler.off('fulfilled', this, this.promiseUpdated); + this.promiseAssembler.off('rejected', this, this.promiseUpdated); + this.promiseAssembler.off('chained', this, this.promiseChained); + }); + + this.promisesUpdated(this.promiseAssembler.find()); + } + + promisesUpdated(uniquePromises?: PromiseModel[]) { + if (!uniquePromises) { + uniquePromises = [...new Set(this.updatedPromises)]; + } + // Remove inspector-created promises + uniquePromises = uniquePromises.filter((promise) => promise.label !== 'ember-inspector'); + const serialized = this.serializeArray(uniquePromises); + this.sendMessage('promisesUpdated', { + promises: serialized, + }); + this.updatedPromises.length = 0; + } + + promiseUpdated(event: PromiseUpdatedEvent) { + this.updatedPromises.push(event.promise); + debounce(this, this.promisesUpdated as any, this.delay); + } + + promiseChained(event: PromiseChainedEvent) { + this.updatedPromises.push(event.promise); + this.updatedPromises.push(event.child); + debounce(this, this.promisesUpdated as any, this.delay); + } + + serializeArray(promises: PromiseModel[]) { + return promises.map((item) => this.serialize(item)); + } + + serialize(promise: PromiseModel) { + let serialized: any = {}; + serialized.guid = promise.guid; + serialized.state = promise.state; + serialized.label = promise.label; + if (promise.children) { + serialized.children = this.promiseIds(promise.children); + } + serialized.parent = promise.parent?.guid; + serialized.value = this.inspectValue(promise, 'value'); + serialized.reason = this.inspectValue(promise, 'reason'); + if (promise.createdAt) { + serialized.createdAt = promise.createdAt?.getTime(); + } + if (promise.settledAt) { + serialized.settledAt = promise.settledAt?.getTime(); + } + serialized.hasStack = Boolean(promise.stack); + return serialized; + } + + promiseIds(promises: PromiseModel[]) { + return promises.map((promise) => promise.guid); + } + + /** + * Inspect the promise and pass to object inspector + * @param {Promise} promise The promise object + * @param {string} key The key for the property on the promise + * @return {*|{inspect: (string|*), type: string}|{computed: boolean, inspect: string, type: string}|{inspect: string, type: string}} + */ + inspectValue(promise: PromiseModel, key: keyof PromiseModel) { + let objectInspector = this.objectInspector; + let inspected = objectInspector.inspectValue(promise, key); + + if (inspected.type === 'type-ember-object' || inspected.type === 'type-array') { + // eslint-disable-next-line no-console + console.count('inspectValue'); + + inspected.objectId = objectInspector.retainObject(promise[key]); + this.releaseMethods.push(function () { + objectInspector.releaseObject(inspected.objectId); + }); + } + return inspected; + } +} diff --git a/packages/@ember/debug/ember-inspector-support/render-debug.ts b/packages/@ember/debug/ember-inspector-support/render-debug.ts new file mode 100644 index 00000000000..62c2cdaf757 --- /dev/null +++ b/packages/@ember/debug/ember-inspector-support/render-debug.ts @@ -0,0 +1,108 @@ +import DebugPort from './debug-port'; +import ProfileManager from './models/profile-manager'; + +import { _backburner } from '@ember/runloop'; +import bound from '@ember/debug/ember-inspector-support/utils/bound-method'; +import { subscribe } from '@ember/instrumentation'; + +// Initial setup, that has to occur before the EmberObject init for some reason +let profileManager = new ProfileManager(); +_subscribeToRenderEvents(); + +export default class RenderDebug extends DebugPort { + declare profileManager: ProfileManager; + constructor(data?: any) { + super(data); + this.profileManager = profileManager; + this.profileManager.setup(); + this.profileManager.wrapForErrors = (context, callback) => + this.port.wrap(() => callback.call(context)); + _backburner.on('end', bound(this, this._updateComponentTree)); + } + + willDestroy() { + super.willDestroy(); + + this.profileManager.wrapForErrors = function (context, callback) { + return callback.call(context); + }; + + this.profileManager.offProfilesAdded(this, this.sendAdded); + this.profileManager.teardown(); + + _backburner.off('end', bound(this, this._updateComponentTree)); + } + + sendAdded(profiles: any) { + this.sendMessage('profilesAdded', { + profiles, + isHighlightSupported: this.profileManager.isHighlightEnabled, + }); + } + + /** + * Update the components tree. Called on each `render.component` event. + * @private + */ + _updateComponentTree() { + this.namespace?.viewDebug?.sendTree(); + } + + static { + this.prototype.portNamespace = 'render'; + this.prototype.messages = { + clear(this: RenderDebug) { + this.profileManager.clearProfiles(); + this.sendMessage('profilesUpdated', { profiles: [] }); + }, + + releaseProfiles(this: RenderDebug) { + this.profileManager.offProfilesAdded(this, this.sendAdded); + }, + + watchProfiles(this: RenderDebug) { + this.sendMessage('profilesAdded', { + profiles: this.profileManager.profiles, + }); + this.profileManager.onProfilesAdded(this, this.sendAdded); + }, + + updateShouldHighlightRender( + this: RenderDebug, + { shouldHighlightRender }: { shouldHighlightRender: boolean } + ) { + this.profileManager.shouldHighlightRender = shouldHighlightRender; + }, + }; + } +} + +/** + * This subscribes to render events, so every time the page rerenders, it will push a new profile + * @return {*} + * @private + */ +function _subscribeToRenderEvents() { + subscribe('render', { + before(_name: string, timestamp: number, payload: any) { + const info = { + type: 'began', + timestamp, + payload, + now: Date.now(), + }; + return profileManager.addToQueue(info); + }, + + after(_name: string, timestamp, payload: any, beganIndex) { + const endedInfo = { + type: 'ended', + timestamp, + payload, + }; + + const index = profileManager.addToQueue(endedInfo); + profileManager.queue[beganIndex]!.endedIndex = index; + }, + }); +} diff --git a/packages/@ember/debug/ember-inspector-support/route-debug.ts b/packages/@ember/debug/ember-inspector-support/route-debug.ts new file mode 100644 index 00000000000..f56f4c8af37 --- /dev/null +++ b/packages/@ember/debug/ember-inspector-support/route-debug.ts @@ -0,0 +1,310 @@ +import DebugPort from './debug-port'; +import classify from '@ember/debug/ember-inspector-support/utils/classify'; +import dasherize from '@ember/debug/ember-inspector-support/utils/dasherize'; +import { _backburner, later } from '@ember/runloop'; +import bound from '@ember/debug/ember-inspector-support/utils/bound-method'; + +const { hasOwnProperty } = Object.prototype; + +export default class RouteDebug extends DebugPort { + _cachedRouteTree = null; + private declare __currentURL: any; + private declare __currentRouter: any; + init() { + super.init(); + this.__currentURL = this.currentURL; + this.__currentRouter = this.router; + _backburner.on('end', bound(this, this.checkForUpdate)); + } + + checkForUpdate() { + if (this.__currentURL !== this.currentURL) { + this.sendCurrentRoute(); + this.__currentURL = this.currentURL; + } + if (this.__currentRouter !== this.router) { + this._cachedRouteTree = null; + this.__currentRouter = this.router; + } + } + + willDestroy() { + _backburner.off('end', bound(this, this.checkForUpdate)); + super.willDestroy(); + } + + get router() { + if (this.namespace?.owner.isDestroyed || this.namespace?.owner.isDestroying) { + return null; + } + return this.namespace?.owner.lookup('router:main'); + } + + get currentPath() { + return this.namespace?.owner.router.currentPath; + } + get currentURL() { + return this.namespace?.owner.router.currentURL; + } + + get emberCliConfig() { + return this.namespace?.generalDebug.emberCliConfig; + } + + static { + this.prototype.portNamespace = 'route'; + this.prototype.messages = { + getTree(this: RouteDebug) { + this.sendTree(); + }, + getCurrentRoute(this: RouteDebug) { + this.sendCurrentRoute(); + }, + }; + } + + sendCurrentRoute() { + const { currentPath: name, currentURL: url } = this; + later(() => { + this.sendMessage('currentRoute', { name, url }); + }, 50); + } + + get routeTree() { + if (this.namespace?.owner.isDestroyed || this.namespace?.owner.isDestroying) { + return null; + } + if (!this._cachedRouteTree && this.router) { + const router = this.router; + const routerLib = router._routerMicrolib || router.router; + let routeNames = routerLib.recognizer.names; + let routeTree: Record = {}; + for (let routeName in routeNames) { + if (!hasOwnProperty.call(routeNames, routeName)) { + continue; + } + let route = routeNames[routeName]; + this.buildSubTree(routeTree, route); + } + this._cachedRouteTree = arrayizeChildren({ children: routeTree }); + } + return this._cachedRouteTree; + } + + sendTree() { + let routeTree; + let error; + try { + routeTree = this.routeTree; + } catch (e: any) { + error = e.message; + } + this.sendMessage('routeTree', { tree: routeTree, error }); + } + + getClassName(name: string, type: string) { + let container = this.namespace.owner; + let resolver = container.application.__registry__.resolver; + let prefix = this.emberCliConfig?.modulePrefix; + let podPrefix = this.emberCliConfig?.podModulePrefix; + let usePodsByDefault = this.emberCliConfig?.usePodsByDefault; + let className; + if (prefix || podPrefix) { + // Uses modules + name = dasherize(name); + let fullName = `${type}:${name}`; + if (resolver.lookupDescription) { + className = resolver.lookupDescription(fullName); + } else if (resolver.describe) { + className = resolver.describe(fullName); + } + if (className === fullName) { + // full name returned as is - this resolver does not look for the module. + className = className.replace(new RegExp(`^${type}:`), ''); + } else if (className) { + // Module exists and found + className = className.replace(new RegExp(`^/?(${prefix}|${podPrefix})/${type}s/`), ''); + } else { + // Module does not exist + if (usePodsByDefault) { + // we don't include the prefix since it's redundant + // and not part of the file path. + // (podPrefix - prefix) is part of the file path. + let currentPrefix = ''; + if (podPrefix) { + currentPrefix = podPrefix.replace(new RegExp(`^/?${prefix}/?`), ''); + } + className = `${currentPrefix}/${name}/${type}`; + } else { + className = name.replace(/\./g, '/'); + } + } + className = className.replace(/\./g, '/'); + } else { + // No modules + if (type !== 'template') { + className = classify(`${name.replace(/\./g, '_')}_${type}`); + } else { + className = name.replace(/\./g, '/'); + } + } + return className; + } + + buildSubTree(routeTree: Record, route: { handlers: any; segments: string[] }) { + let handlers = route.handlers; + let owner = this.namespace.owner; + let subTree = routeTree; + let item; + let routeClassName; + let routeHandler; + let controllerName; + let controllerClassName; + let templateName; + let controllerFactory; + + for (let i = 0; i < handlers.length; i++) { + item = handlers[i]; + let handler = item.handler; + if (handler.match(/(loading|error)$/)) { + // make sure it has been defined before calling `getHandler` because + // we don't want to generate sub routes as this has side-effects. + if (!routeHasBeenDefined(owner, handler)) { + continue; + } + } + + if (subTree[handler] === undefined) { + routeClassName = this.getClassName(handler, 'route'); + + const router = this.router; + const routerLib = router._routerMicrolib || router.router; + // 3.9.0 removed intimate APIs from router + // https://github.com/emberjs/ember.js/pull/17843 + // https://deprecations.emberjs.com/v3.x/#toc_remove-handler-infos + routeHandler = routerLib.getRoute(handler); + + // Skip when route is an unresolved promise + if (typeof routeHandler?.then === 'function') { + // ensure we rebuild the route tree when this route is resolved + routeHandler.then(() => (this._cachedRouteTree = null)); + controllerName = '(unresolved)'; + controllerClassName = '(unresolved)'; + templateName = '(unresolved)'; + } else { + const get = + routeHandler.get || + function (this: any, prop: any) { + return this[prop]; + }; + controllerName = get.call(routeHandler, 'controllerName') || routeHandler.routeName; + controllerFactory = owner.factoryFor + ? owner.factoryFor(`controller:${controllerName}`) + : owner._lookupFactory(`controller:${controllerName}`); + controllerClassName = this.getClassName(controllerName, 'controller'); + templateName = this.getClassName(handler, 'template'); + } + + subTree[handler] = { + value: { + name: handler, + routeHandler: { + className: routeClassName, + name: handler, + }, + controller: { + className: controllerClassName, + name: controllerName, + exists: Boolean(controllerFactory), + }, + template: { + name: templateName, + }, + }, + }; + + if (i === handlers.length - 1) { + // it is a route, get url + subTree[handler].value.url = getURL(owner, route.segments); + subTree[handler].value.type = 'route'; + } else { + // it is a resource, set children object + subTree[handler].children = {}; + subTree[handler].value.type = 'resource'; + } + } + subTree = subTree[handler].children; + } + } +} + +function arrayizeChildren(routeTree: { value?: any; children: Record }) { + let obj: any = {}; + // Top node doesn't have a value + if (routeTree.value) { + obj.value = routeTree.value; + } + + if (routeTree.children) { + let childrenArray = []; + for (let i in routeTree.children) { + let route = routeTree.children[i]; + childrenArray.push(arrayizeChildren(route)); + } + obj.children = childrenArray; + } + + return obj; +} + +/** + * + * @param {*} container + * @param {*} segments + * @return {String} + */ +function getURL(container: any, segments: any) { + const locationImplementation = container.lookup('router:main').location; + let url: string[] = []; + for (let i = 0; i < segments.length; i++) { + let name = null; + + if (typeof segments[i].generate !== 'function') { + let { type, value } = segments[i]; + if (type === 1) { + // dynamic + name = `:${value}`; + } else if (type === 2) { + // star + name = `*${value}`; + } else { + name = value; + } + } + + if (name) { + url.push(name); + } + } + + let fullUrl = url.join('/'); + + if (fullUrl.match(/_unused_dummy_/)) { + fullUrl = ''; + } else { + fullUrl = `/${fullUrl}`; + fullUrl = locationImplementation.formatURL(fullUrl); + } + + return fullUrl; +} + +/** + * + * @param {String} owner + * @param {String} name + * @return {Void} + */ +function routeHasBeenDefined(owner: any, name: string) { + return owner.hasRegistration(`template:${name}`) || owner.hasRegistration(`route:${name}`); +} diff --git a/packages/@ember/debug/ember-inspector-support/services/session.ts b/packages/@ember/debug/ember-inspector-support/services/session.ts new file mode 100644 index 00000000000..f292cfe6557 --- /dev/null +++ b/packages/@ember/debug/ember-inspector-support/services/session.ts @@ -0,0 +1,50 @@ +import BaseObject from '@ember/debug/ember-inspector-support/utils/base-object'; + +class Session extends BaseObject { + setItem(/*key, val*/) {} + removeItem(/*key*/) {} + getItem(/*key*/) {} +} + +let SESSION_STORAGE_SUPPORTED = false; + +try { + if (typeof sessionStorage !== 'undefined') { + SESSION_STORAGE_SUPPORTED = true; + } +} catch { + // This can be reached with the following succession of events: + // + // 1. On Google Chrome + // 2. Disable 3rd-party cookies + // 3. Open the browser inspector + // 4. Open on the Ember inspector + // 5. Visit a page showing an Ember app, on a frame + // loaded from a different domain + // + // It's important that the Ember inspector is already open when + // you land on the page (hence step 4 before 5). Reloading the iframe + // page with the Ember inspector open also reproduces the problem. +} + +// Feature detection +if (SESSION_STORAGE_SUPPORTED) { + Object.assign(Session.prototype, { + sessionStorage, + prefix: '__ember__inspector__', + makeKey(key: string) { + return this.prefix + key; + }, + setItem(key: string, val: any) { + return this.sessionStorage.setItem(this.makeKey(key), val); + }, + removeItem(key: string) { + return this.sessionStorage.removeItem(this.makeKey(key)); + }, + getItem(key: string) { + return JSON.parse(this.sessionStorage.getItem(this.makeKey(key)) as string); + }, + }); +} + +export default Session; diff --git a/packages/@ember/debug/ember-inspector-support/utils/base-object.ts b/packages/@ember/debug/ember-inspector-support/utils/base-object.ts new file mode 100644 index 00000000000..7831a34a254 --- /dev/null +++ b/packages/@ember/debug/ember-inspector-support/utils/base-object.ts @@ -0,0 +1,23 @@ +export default class BaseObject { + declare namespace: any; + isDestroyed = false; + isDestroying = false; + constructor(data?: any) { + Object.assign(this, data || {}); + this.init(); + } + + init() {} + willDestroy() { + this.isDestroying = true; + } + + destroy() { + this.willDestroy(); + this.isDestroyed = true; + } + + reopen(data: any) { + Object.assign(this, data); + } +} diff --git a/packages/@ember/debug/ember-inspector-support/utils/bound-method.ts b/packages/@ember/debug/ember-inspector-support/utils/bound-method.ts new file mode 100644 index 00000000000..1c876ad4c80 --- /dev/null +++ b/packages/@ember/debug/ember-inspector-support/utils/bound-method.ts @@ -0,0 +1,40 @@ +const ENTRIES = new WeakMap(); + +function entriesFor(obj: any) { + let entries = ENTRIES.get(obj); + + if (entries === undefined) { + entries = new WeakMap(); + ENTRIES.set(obj, entries); + } + + return entries; +} + +/** + * Return `method.bind(obj)` or `obj[method].bind(obj)`. When called multiple + * times, the same bound function will be returned. + */ +export default function bound(obj: any, method: string | Function) { + let func; + + if (typeof method === 'function') { + func = method; + } else { + func = obj[method]; + + if (typeof func !== 'function') { + throw new TypeError(`${obj}[${method}] is not a function (was ${func})`); + } + } + + let entries = entriesFor(obj); + let bound = entries.get(func); + + if (bound === undefined) { + bound = func.bind(obj); + entries.set(func, bound); + } + + return bound; +} diff --git a/packages/@ember/debug/ember-inspector-support/utils/classify.ts b/packages/@ember/debug/ember-inspector-support/utils/classify.ts new file mode 100644 index 00000000000..77200195237 --- /dev/null +++ b/packages/@ember/debug/ember-inspector-support/utils/classify.ts @@ -0,0 +1,24 @@ +const STRING_CLASSIFY_REGEXP_1 = /^([-_])+(.)?/; +const STRING_CLASSIFY_REGEXP_2 = /(.)(-|_|\.|\s)+(.)?/g; +const STRING_CLASSIFY_REGEXP_3 = /(^|\/|\.)([a-z])/g; + +export default function classify(str: string) { + let replace1 = (_match: string, _separator: string, chr?: string) => + chr ? `_${chr.toUpperCase()}` : ''; + let replace2 = (_match: string, initialChar: string, _separator: string, chr?: string) => + initialChar + (chr ? chr.toUpperCase() : ''); + let parts = str.split('/'); + + for (let i = 0; i < parts.length; i++) { + parts[i] = parts[i]!.replace(STRING_CLASSIFY_REGEXP_1, replace1).replace( + STRING_CLASSIFY_REGEXP_2, + replace2 + ); + } + + return parts + .join('/') + .replace(STRING_CLASSIFY_REGEXP_3, (match: string /*, separator, chr */) => + match.toUpperCase() + ); +} diff --git a/packages/@ember/debug/ember-inspector-support/utils/dasherize.ts b/packages/@ember/debug/ember-inspector-support/utils/dasherize.ts new file mode 100644 index 00000000000..f37cf15b944 --- /dev/null +++ b/packages/@ember/debug/ember-inspector-support/utils/dasherize.ts @@ -0,0 +1,5 @@ +const STRING_DASHERIZE_REGEXP = /[ _]/g; + +export default function dasherize(str: string) { + return str.replace(STRING_DASHERIZE_REGEXP, '-'); +} diff --git a/packages/@ember/debug/ember-inspector-support/utils/ember-object-names.ts b/packages/@ember/debug/ember-inspector-support/utils/ember-object-names.ts new file mode 100644 index 00000000000..91cb2f6c272 --- /dev/null +++ b/packages/@ember/debug/ember-inspector-support/utils/ember-object-names.ts @@ -0,0 +1,45 @@ +import MutableArray from '@ember/array/mutable'; +import Evented from '@ember/object/evented'; +import PromiseProxyMixin from '@ember/object/promise-proxy-mixin'; +import MutableEnumerable from '@ember/enumerable/mutable'; +import { NativeArray } from '@ember/array'; +import { ControllerMixin } from '@ember/controller'; +import Observable from '@ember/object/observable'; +import { ActionHandler, TargetActionSupport } from '@ember/-internals/runtime'; +import CoreObject from '@ember/object/core'; +import EmberObject from '@ember/object'; +import Component from '@ember/component'; +import { + ActionSupport, + ChildViewsSupport, + ClassNamesSupport, + CoreView, + ViewMixin, + ViewStateSupport, +} from '@ember/-internals/views'; +/** + * Add Known Ember Mixins and Classes so we can label them correctly in the inspector + */ +const emberNames = new Map([ + [Evented, 'Evented Mixin'], + [PromiseProxyMixin, 'PromiseProxy Mixin'], + [MutableArray, 'MutableArray Mixin'], + [MutableEnumerable, 'MutableEnumerable Mixin'], + [NativeArray, 'NativeArray Mixin'], + [Observable, 'Observable Mixin'], + [ControllerMixin, 'Controller Mixin'], + [ActionHandler, 'ActionHandler Mixin'], + [CoreObject, 'CoreObject'], + [EmberObject, 'EmberObject'], + [Component, 'Component'], + [TargetActionSupport, 'TargetActionSupport Mixin'], + [ViewStateSupport, 'ViewStateSupport Mixin'], + [ViewMixin, 'View Mixin'], + [ActionSupport, 'ActionSupport Mixin'], + [ClassNamesSupport, 'ClassNamesSupport Mixin'], + [ChildViewsSupport, 'ChildViewsSupport Mixin'], + [ViewStateSupport, 'ViewStateSupport Mixin'], + [CoreView, 'CoreView'], +]); + +export default emberNames; diff --git a/packages/@ember/debug/ember-inspector-support/utils/ember/object/internals.ts b/packages/@ember/debug/ember-inspector-support/utils/ember/object/internals.ts new file mode 100644 index 00000000000..59052e796c8 --- /dev/null +++ b/packages/@ember/debug/ember-inspector-support/utils/ember/object/internals.ts @@ -0,0 +1,14 @@ +import { guidFor as emberGuidFor } from '@ember/-internals/utils'; + +// it can happen that different ember apps/iframes have the same id for different objects +// since the implementation is just a counter, so we add a prefix per iframe & app +let perIframePrefix = parseInt(String(Math.random() * 1000)).toString() + '-'; +let prefix = ''; +let guidFor = (obj: any, pref?: string) => + `${perIframePrefix + (pref || prefix)}-${emberGuidFor(obj)}`; + +export function setGuidPrefix(pref: string) { + prefix = pref; +} + +export { guidFor }; diff --git a/packages/@ember/debug/ember-inspector-support/utils/evented.ts b/packages/@ember/debug/ember-inspector-support/utils/evented.ts new file mode 100644 index 00000000000..92000f447ef --- /dev/null +++ b/packages/@ember/debug/ember-inspector-support/utils/evented.ts @@ -0,0 +1,79 @@ +const ListenersSymbol = Symbol('listeners'); +const EventedSymbol = Symbol('evented'); + +type ClassConstructor = new (...args: any[]) => T; + +export default class Evented { + [ListenersSymbol]: Record< + string, + { target: any; method: string | Function; once?: boolean | undefined }[] + > = {}; + + on(name: string, target: any, method: string | Function) { + this[ListenersSymbol][name] = this[ListenersSymbol][name] || []; + this[ListenersSymbol][name]!.push({ + target: (method && target) || null, + method: (target && method) || target, + }); + return this; + } + + one(name: string, target: any, method: string | Function) { + this.on(name, target, method); + this[ListenersSymbol][name]!.slice(-1)[0]!.once = true; + return this; + } + + trigger(name: string, ...args: any) { + (this[ListenersSymbol][name] || []).forEach((l) => { + let m = l.method; + if (typeof l.method !== 'function') { + m = l.target[l.method]; + } + (m as Function).call(l.target, ...args); + if (l.once) { + const idx = this[ListenersSymbol][name]!.indexOf(l); + this[ListenersSymbol][name]!.splice(idx, 1); + } + }); + } + + off(name: string, target: any, method: string | Function) { + if (!method) { + method = target; + target = null; + } + const listeners = this[ListenersSymbol][name] || []; + const idx = listeners.findIndex((l) => l.target === target && l.method === method); + if (idx >= 0) { + listeners.splice(idx, 1); + } + return this; + } + + has(name: string) { + const listeners = this[ListenersSymbol][name] || []; + return listeners.length > 0; + } + + static extend( + this: Statics & ClassConstructor, + klass: M + ): Readonly & ClassConstructor & M extends infer T ? T & Evented : unknown; + + static extend(klass: any) { + const k = class extends klass {}; + Evented.applyTo(k.prototype); + return k; + } + + static applyTo(instance: any) { + const e = new Evented(); + instance[EventedSymbol] = e; + instance.one = e.one.bind(e); + instance.on = e.on.bind(e); + instance.trigger = e.trigger.bind(e); + instance.off = e.off.bind(e); + instance.has = e.has.bind(e); + } +} diff --git a/packages/@ember/debug/ember-inspector-support/utils/get-object-name.ts b/packages/@ember/debug/ember-inspector-support/utils/get-object-name.ts new file mode 100644 index 00000000000..84b27927e04 --- /dev/null +++ b/packages/@ember/debug/ember-inspector-support/utils/get-object-name.ts @@ -0,0 +1,63 @@ +import emberNames from './ember-object-names'; + +export default function getObjectName(object: any): string { + let name = ''; + let className = + (object.constructor && (emberNames.get(object.constructor) || object.constructor.name)) || ''; + + if (object instanceof Function) { + return 'Function ' + object.name; + } + + // check if object is a primitive value + if (object !== Object(object)) { + return typeof object; + } + + if (Array.isArray(object)) { + return 'array'; + } + + if (object.constructor && object.constructor.prototype === object) { + let { constructor } = object; + + if ( + constructor.toString && + constructor.toString !== Object.prototype.toString && + constructor.toString !== Function.prototype.toString + ) { + try { + name = constructor.toString(); + } catch { + name = constructor.name; + } + } else { + name = constructor.name; + } + } else if ( + 'toString' in object && + object.toString !== Object.prototype.toString && + object.toString !== Function.prototype.toString + ) { + try { + name = object.toString(); + } catch { + // + } + } + + // If the class has a decent looking name, and the `toString` is one of the + // default Ember toStrings, replace the constructor portion of the toString + // with the class name. We check the length of the class name to prevent doing + // this when the value is minified. + if ( + name.match(/<.*:.*>/) && + !className.startsWith('_') && + className.length > 2 && + className !== 'Class' + ) { + return name.replace(/<.*:/, `<${className}:`); + } + + return name || className || '(unknown class)'; +} diff --git a/packages/@ember/debug/ember-inspector-support/utils/name-functions.ts b/packages/@ember/debug/ember-inspector-support/utils/name-functions.ts new file mode 100644 index 00000000000..6b0025ee485 --- /dev/null +++ b/packages/@ember/debug/ember-inspector-support/utils/name-functions.ts @@ -0,0 +1,91 @@ +/** + * Returns a medium sized model name. Makes sure it's maximum 50 characters long. + * + * @param {Any} model + * @return {String} The model name. + */ +export function modelName(model: any) { + let name = ''; + if (model.toString) { + name = model.toString(); + } + + if (name.length > 50) { + name = `${name.slice(0, 50)}...`; + } + return name; +} + +/** + * Takes an Ember Data model and strips out the extra noise from the name. + * + * @param {DS.Model} model + * @return {String} The concise model name. + */ +export function shortModelName(model: any) { + let name = modelName(model); + // jj-abrams-resolver adds `app@model:` + return name.replace(/<[^>]+@model:/g, '<'); +} + +/** + * Returns the controller name. Strips out extra noise such as `subclass of`. + * + * @param {Controller} controller + * @return {String} The controller name + */ +export function controllerName(controller: any) { + return controller.toString(); +} + +/** + * Cleans up the controller name before returning it. + * + * @param {Controller} controller + * @return {String} The short controller name + */ +export function shortControllerName(controller: any) { + let name = cleanupInstanceName(controllerName(controller)); + let match = name!.match(/^\(generated (.+) controller\)/); + if (match) { + return match[1]; + } + return name; +} + +/** + * Cleans up an instance name to create shorter/less noisy names. + * Example: `` becomes `textarea`. + */ +function cleanupInstanceName(name: string) { + let match = name.match(/^.+:(.+)::/); + if (!match) { + // Support for Namespace names (instead of module) (for the tests). + // `` => `App.ApplicationController` + match = name.match(/^<(.+):/); + } + if (match) { + return match[1]; + } + return name; +} + +/** + * Cleans up the view name before returning it. + * + * @param {Component} view The component. + * @return {String} The short view name. + */ +export function shortViewName(view: any) { + return cleanupInstanceName(viewName(view)); +} + +/** + * Returns the view name. Removes the `subclass` noise. + * + * @param {Component} view The component. + * @return {String} The view name. + */ +export function viewName(view: any) { + return view.toString(); +} diff --git a/packages/@ember/debug/ember-inspector-support/utils/on-ready.ts b/packages/@ember/debug/ember-inspector-support/utils/on-ready.ts new file mode 100644 index 00000000000..d2731015cad --- /dev/null +++ b/packages/@ember/debug/ember-inspector-support/utils/on-ready.ts @@ -0,0 +1,15 @@ +export function onReady(callback: () => void) { + if (document.readyState === 'complete' || document.readyState === 'interactive') { + setTimeout(completed); + } else { + document.addEventListener('DOMContentLoaded', completed, false); + // For some reason DOMContentLoaded doesn't always work + window.addEventListener('load', completed, false); + } + + function completed() { + document.removeEventListener('DOMContentLoaded', completed, false); + window.removeEventListener('load', completed, false); + callback(); + } +} diff --git a/packages/@ember/debug/ember-inspector-support/utils/type-check.ts b/packages/@ember/debug/ember-inspector-support/utils/type-check.ts new file mode 100644 index 00000000000..4b183ca8b0c --- /dev/null +++ b/packages/@ember/debug/ember-inspector-support/utils/type-check.ts @@ -0,0 +1,83 @@ +import EmberObject from '@ember/object'; +import { inspect as emberInspect } from '@ember/debug'; + +export function typeOf(obj: any) { + return Object.prototype.toString + .call(obj) + .match(/\s([a-zA-Z]+)/)![1]! + .toLowerCase(); +} + +export function inspect(value: any): string { + if (typeof value === 'function') { + return `${value.name || 'function'}() { ... }`; + } else if (value instanceof EmberObject) { + return value.toString(); + } else if (value instanceof HTMLElement) { + return `<${value.tagName.toLowerCase()}>`; + } else if (typeOf(value) === 'array') { + if (value.length === 0) { + return '[]'; + } else if (value.length === 1) { + return `[ ${inspect(value[0])} ]`; + } else { + return `[ ${inspect(value[0])}, ... ]`; + } + } else if (value instanceof Error) { + return `Error: ${value.message}`; + } else if (value === null) { + return 'null'; + } else if (typeOf(value) === 'date') { + return value.toString(); + } else if (typeof value === 'object') { + // `Ember.inspect` is able to handle this use case, + // but it is very slow as it loops over all props, + // so summarize to just first 2 props + // if it defines a toString, we use that instead + if ( + typeof value.toString === 'function' && + value.toString !== Object.prototype.toString && + value.toString !== Function.prototype.toString + ) { + try { + return ``; + } catch { + // + } + } + let ret = []; + let v; + let count = 0; + let broken = false; + + for (let key in value) { + if (!('hasOwnProperty' in value) || Object.prototype.hasOwnProperty.call(value, key)) { + if (count++ > 1) { + broken = true; + break; + } + v = value[key]; + if (v === 'toString') { + continue; + } // ignore useless items + if (typeOf(v).includes('function')) { + v = `function ${v.name}() { ... }`; + } + if (typeOf(v) === 'array') { + v = `[Array : ${v.length}]`; + } + if (typeOf(v) === 'object') { + v = '[Object]'; + } + ret.push(`${key}: ${v}`); + } + } + let suffix = ' }'; + if (broken) { + suffix = ' ...}'; + } + return `{ ${ret.join(', ')}${suffix}`; + } else { + return emberInspect(value); + } +} diff --git a/packages/@ember/debug/ember-inspector-support/view-debug.ts b/packages/@ember/debug/ember-inspector-support/view-debug.ts new file mode 100644 index 00000000000..df155b6e1ea --- /dev/null +++ b/packages/@ember/debug/ember-inspector-support/view-debug.ts @@ -0,0 +1,192 @@ +/* eslint no-cond-assign:0 */ +import DebugPort from './debug-port'; +import RenderTree from '@ember/debug/ember-inspector-support/libs/render-tree'; +import ViewInspection from '@ember/debug/ember-inspector-support/libs/view-inspection'; +import bound from '@ember/debug/ember-inspector-support/utils/bound-method'; + +export default class ViewDebug extends DebugPort { + declare viewInspection: ViewInspection; + declare renderTree: RenderTree; + private scheduledSendTree: number | null = null; + declare lastRightClicked: any; + get adapter() { + return this.namespace?.adapter; + } + get objectInspector() { + return this.namespace?.objectInspector; + } + + static { + this.prototype.portNamespace = 'view'; + + this.prototype.messages = { + getTree(this: ViewDebug, { immediate }: { immediate: boolean }) { + this.sendTree(immediate); + }, + + showInspection(this: ViewDebug, { id, pin }: { id: string; pin: boolean }) { + this.viewInspection.show(id, pin); + }, + + hideInspection(this: ViewDebug) { + this.viewInspection.hide(); + }, + + inspectViews(this: ViewDebug, { inspect }: { inspect: boolean }) { + if (inspect) { + this.startInspecting(); + } else { + this.stopInspecting(); + } + }, + + scrollIntoView(this: ViewDebug, { id }: { id: string }) { + this.renderTree.scrollIntoView(id); + }, + + inspectElement(this: ViewDebug, { id }: { id: string }) { + this.renderTree.inspectElement(id); + }, + + contextMenu(this: ViewDebug) { + let { lastRightClicked } = this; + this.lastRightClicked = null; + this.inspectNearest(lastRightClicked); + }, + }; + } + + init() { + super.init(); + + let renderTree = (this.renderTree = new RenderTree({ + owner: this.getOwner(), + retainObject: bound(this.objectInspector, this.objectInspector.retainObject), + releaseObject: bound(this.objectInspector, this.objectInspector.releaseObject), + inspectNode: bound(this, this.inspectNode), + })); + + this.viewInspection = new ViewInspection({ + renderTree, + objectInspector: this.objectInspector, + didShow: bound(this, this.didShowInspection), + didHide: bound(this, this.didHideInspection), + didStartInspecting: bound(this, this.didStartInspecting), + didStopInspecting: bound(this, this.didStopInspecting), + }); + + this.setupListeners(); + } + + setupListeners() { + this.lastRightClicked = null; + this.scheduledSendTree = null; + window.addEventListener('mousedown', bound(this, this.onRightClick)); + window.addEventListener('resize', bound(this, this.onResize)); + } + + cleanupListeners() { + this.lastRightClicked = null; + + window.removeEventListener('mousedown', bound(this, this.onRightClick)); + window.removeEventListener('resize', bound(this, this.onResize)); + + if (this.scheduledSendTree) { + window.clearTimeout(this.scheduledSendTree); + this.scheduledSendTree = null; + } + } + + onRightClick(event: MouseEvent) { + if (event.button === 2) { + this.lastRightClicked = event.target; + } + } + + onResize() { + // TODO hide or redraw highlight/tooltip + } + + inspectNearest(node: Node) { + let renderNode = this.viewInspection.inspectNearest(node); + + if (!renderNode) { + this.adapter.log('No Ember component found.'); + } + } + + willDestroy() { + super.willDestroy(); + this.cleanupListeners(); + this.viewInspection.teardown(); + this.renderTree.teardown(); + } + + /** + * Opens the "Elements" tab and selects the given DOM node. Doesn't work in all + * browsers/addons (only in the Chrome and FF devtools addons at the time of writing). + * + * @param {Node} node The DOM node to inspect + */ + inspectNode(node: Node) { + this.adapter.inspectValue(node); + } + + sendTree(immediate = false) { + if (immediate) { + this.send(); + return; + } + + if (this.scheduledSendTree) { + return; + } + + this.scheduledSendTree = window.setTimeout(() => { + this.send(); + this.scheduledSendTree = null; + }, 250); + } + + send() { + if (this.isDestroying || this.isDestroyed) { + return; + } + + this.sendMessage('renderTree', { + tree: this.renderTree.build(), + }); + } + + startInspecting() { + this.viewInspection.start(); + } + + stopInspecting() { + this.viewInspection.stop(); + } + + didShowInspection(id: string, pin: boolean) { + if (pin) { + this.sendMessage('inspectComponent', { id }); + } else { + this.sendMessage('previewComponent', { id }); + } + } + + didHideInspection(id: string, pin: boolean) { + this.sendMessage('cancelSelection', { id, pin }); + } + + didStartInspecting() { + this.sendMessage('startInspecting', {}); + } + + didStopInspecting() { + this.sendMessage('stopInspecting', {}); + } + + getOwner() { + return this.namespace?.owner; + } +} diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index a015c9999e7..1f5fdbc20dd 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -14,7 +14,7 @@ importers: dependencies: '@babel/core': specifier: ^7.24.4 - version: 7.24.4 + version: 7.24.4(supports-color@8.1.1) '@ember/edition-utils': specifier: ^1.2.0 version: 1.2.0 @@ -89,7 +89,7 @@ importers: version: 4.1.2 ember-auto-import: specifier: ^2.6.3 - version: 2.7.2 + version: 2.7.2(webpack@5.91.0) ember-cli-babel: specifier: ^8.2.0 version: 8.2.0(@babel/core@7.24.4) @@ -135,6 +135,9 @@ importers: simple-html-tokenizer: specifier: ^0.5.11 version: 0.5.11 + source-map-js: + specifier: ^1.2.1 + version: 1.2.1 devDependencies: '@aws-sdk/client-s3': specifier: ^3.321.1 @@ -144,25 +147,28 @@ importers: version: 7.24.4(@babel/core@7.24.4) '@babel/preset-env': specifier: ^7.16.11 - version: 7.24.4(@babel/core@7.24.4) + version: 7.24.4(@babel/core@7.24.4)(supports-color@8.1.1) '@babel/types': specifier: ^7.22.5 version: 7.24.0 '@embroider/shared-internals': specifier: ^2.5.0 - version: 2.6.0 + version: 2.6.0(supports-color@8.1.1) '@glimmer/component': specifier: ^1.1.2 version: 1.1.2(@babel/core@7.24.4) '@rollup/plugin-babel': specifier: ^6.0.4 version: 6.0.4(@babel/core@7.24.4)(rollup@4.16.4) + '@rollup/plugin-commonjs': + specifier: ^28.0.0 + version: 28.0.0(rollup@4.16.4) '@simple-dom/document': specifier: ^1.4.0 version: 1.4.0 '@swc-node/register': specifier: ^1.6.8 - version: 1.9.0(@swc/core@1.5.0)(typescript@5.1.6) + version: 1.9.0(@swc/core@1.5.0)(@swc/types@0.1.6)(typescript@5.1.6) '@swc/core': specifier: ^1.3.100 version: 1.5.0 @@ -2449,28 +2455,6 @@ packages: resolution: {integrity: sha512-vg8Gih2MLK+kOkHJp4gBEIkyaIi00jgWot2D9QOmmfLC8jINSOzmCLta6Bvz/JSBCqnegV0L80jhxkol5GWNfQ==} engines: {node: '>=6.9.0'} - /@babel/core@7.24.4: - resolution: {integrity: sha512-MBVlMXP+kkl5394RBLSxxk/iLTeVGuXTV3cIDXavPpMMqnSnt6apKgan/U8O3USWZCWZT/TbgfEpKa4uMgN4Dg==} - engines: {node: '>=6.9.0'} - dependencies: - '@ampproject/remapping': 2.3.0 - '@babel/code-frame': 7.24.2 - '@babel/generator': 7.24.4 - '@babel/helper-compilation-targets': 7.23.6 - '@babel/helper-module-transforms': 7.23.3(@babel/core@7.24.4) - '@babel/helpers': 7.24.4 - '@babel/parser': 7.24.4 - '@babel/template': 7.24.0 - '@babel/traverse': 7.24.1 - '@babel/types': 7.24.0 - convert-source-map: 2.0.0 - debug: 4.3.4(supports-color@8.1.1) - gensync: 1.0.0-beta.2 - json5: 2.2.3 - semver: 6.3.1 - transitivePeerDependencies: - - supports-color - /@babel/core@7.24.4(supports-color@8.1.1): resolution: {integrity: sha512-MBVlMXP+kkl5394RBLSxxk/iLTeVGuXTV3cIDXavPpMMqnSnt6apKgan/U8O3USWZCWZT/TbgfEpKa4uMgN4Dg==} engines: {node: '>=6.9.0'} @@ -2492,7 +2476,6 @@ packages: semver: 6.3.1 transitivePeerDependencies: - supports-color - dev: true /@babel/eslint-parser@7.23.10(@babel/core@7.24.4)(eslint@8.57.0): resolution: {integrity: sha512-3wSYDPZVnhseRnxRJH6ZVTNknBz76AEnyC+AYYhasjP3Yy23qz0ERR7Fcd2SHmYuSFJ2kY9gaaDd3vyqU09eSw==} @@ -2555,7 +2538,7 @@ packages: peerDependencies: '@babel/core': ^7.0.0 dependencies: - '@babel/core': 7.24.4 + '@babel/core': 7.24.4(supports-color@8.1.1) '@babel/helper-annotate-as-pure': 7.22.5 '@babel/helper-environment-visitor': 7.22.20 '@babel/helper-function-name': 7.23.0 @@ -2572,25 +2555,11 @@ packages: peerDependencies: '@babel/core': ^7.0.0 dependencies: - '@babel/core': 7.24.4 + '@babel/core': 7.24.4(supports-color@8.1.1) '@babel/helper-annotate-as-pure': 7.22.5 regexpu-core: 5.3.2 semver: 6.3.1 - /@babel/helper-define-polyfill-provider@0.6.2(@babel/core@7.24.4): - resolution: {integrity: sha512-LV76g+C502biUK6AyZ3LK10vDpDyCzZnhZFXkH1L75zHPj68+qc8Zfpx2th+gzwA2MzyK+1g/3EPl62yFnVttQ==} - peerDependencies: - '@babel/core': ^7.4.0 || ^8.0.0-0 <8.0.0 - dependencies: - '@babel/core': 7.24.4 - '@babel/helper-compilation-targets': 7.23.6 - '@babel/helper-plugin-utils': 7.24.0 - debug: 4.3.4(supports-color@8.1.1) - lodash.debounce: 4.0.8 - resolve: 1.22.8 - transitivePeerDependencies: - - supports-color - /@babel/helper-define-polyfill-provider@0.6.2(@babel/core@7.24.4)(supports-color@8.1.1): resolution: {integrity: sha512-LV76g+C502biUK6AyZ3LK10vDpDyCzZnhZFXkH1L75zHPj68+qc8Zfpx2th+gzwA2MzyK+1g/3EPl62yFnVttQ==} peerDependencies: @@ -2604,7 +2573,6 @@ packages: resolve: 1.22.8 transitivePeerDependencies: - supports-color - dev: true /@babel/helper-environment-visitor@7.22.20: resolution: {integrity: sha512-zfedSIzFhat/gFhWfHtgWvlec0nqB9YEIVrpuwjruLlXfUSnA8cJB0miHKwqDnQ7d32aKo2xt88/xZptwxbfhA==} @@ -2641,7 +2609,7 @@ packages: peerDependencies: '@babel/core': ^7.0.0 dependencies: - '@babel/core': 7.24.4 + '@babel/core': 7.24.4(supports-color@8.1.1) '@babel/helper-environment-visitor': 7.22.20 '@babel/helper-module-imports': 7.24.3 '@babel/helper-simple-access': 7.22.5 @@ -2664,7 +2632,7 @@ packages: peerDependencies: '@babel/core': ^7.0.0 dependencies: - '@babel/core': 7.24.4 + '@babel/core': 7.24.4(supports-color@8.1.1) '@babel/helper-annotate-as-pure': 7.22.5 '@babel/helper-environment-visitor': 7.22.20 '@babel/helper-wrap-function': 7.22.20 @@ -2675,7 +2643,7 @@ packages: peerDependencies: '@babel/core': ^7.0.0 dependencies: - '@babel/core': 7.24.4 + '@babel/core': 7.24.4(supports-color@8.1.1) '@babel/helper-environment-visitor': 7.22.20 '@babel/helper-member-expression-to-functions': 7.23.0 '@babel/helper-optimise-call-expression': 7.22.5 @@ -2718,16 +2686,6 @@ packages: '@babel/template': 7.24.0 '@babel/types': 7.24.0 - /@babel/helpers@7.24.4: - resolution: {integrity: sha512-FewdlZbSiwaVGlgT1DPANDuCHaDMiOo+D/IDYRFYjHOuv66xMSJ7fQwwODwRNAPkADIO/z1EoF/l2BCWlWABDw==} - engines: {node: '>=6.9.0'} - dependencies: - '@babel/template': 7.24.0 - '@babel/traverse': 7.24.1 - '@babel/types': 7.24.0 - transitivePeerDependencies: - - supports-color - /@babel/helpers@7.24.4(supports-color@8.1.1): resolution: {integrity: sha512-FewdlZbSiwaVGlgT1DPANDuCHaDMiOo+D/IDYRFYjHOuv66xMSJ7fQwwODwRNAPkADIO/z1EoF/l2BCWlWABDw==} engines: {node: '>=6.9.0'} @@ -2737,7 +2695,6 @@ packages: '@babel/types': 7.24.0 transitivePeerDependencies: - supports-color - dev: true /@babel/highlight@7.24.2: resolution: {integrity: sha512-Yac1ao4flkTxTteCDZLEvdxg2fZfz1v8M4QpaGypq/WPDqg3ijHYbDfs+LG5hvzSoqaSZ9/Z9lKSP3CjZjv+pA==} @@ -2761,7 +2718,7 @@ packages: peerDependencies: '@babel/core': ^7.0.0 dependencies: - '@babel/core': 7.24.4 + '@babel/core': 7.24.4(supports-color@8.1.1) '@babel/helper-environment-visitor': 7.22.20 '@babel/helper-plugin-utils': 7.24.0 @@ -2771,7 +2728,7 @@ packages: peerDependencies: '@babel/core': ^7.0.0 dependencies: - '@babel/core': 7.24.4 + '@babel/core': 7.24.4(supports-color@8.1.1) '@babel/helper-plugin-utils': 7.24.0 /@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining@7.24.1(@babel/core@7.24.4): @@ -2780,7 +2737,7 @@ packages: peerDependencies: '@babel/core': ^7.13.0 dependencies: - '@babel/core': 7.24.4 + '@babel/core': 7.24.4(supports-color@8.1.1) '@babel/helper-plugin-utils': 7.24.0 '@babel/helper-skip-transparent-expression-wrappers': 7.22.5 '@babel/plugin-transform-optional-chaining': 7.24.1(@babel/core@7.24.4) @@ -2791,7 +2748,7 @@ packages: peerDependencies: '@babel/core': ^7.0.0 dependencies: - '@babel/core': 7.24.4 + '@babel/core': 7.24.4(supports-color@8.1.1) '@babel/helper-environment-visitor': 7.22.20 '@babel/helper-plugin-utils': 7.24.0 @@ -2802,7 +2759,7 @@ packages: peerDependencies: '@babel/core': ^7.0.0-0 dependencies: - '@babel/core': 7.24.4 + '@babel/core': 7.24.4(supports-color@8.1.1) '@babel/helper-create-class-features-plugin': 7.24.4(@babel/core@7.24.4) '@babel/helper-plugin-utils': 7.24.0 @@ -2812,7 +2769,7 @@ packages: peerDependencies: '@babel/core': ^7.0.0-0 dependencies: - '@babel/core': 7.24.4 + '@babel/core': 7.24.4(supports-color@8.1.1) '@babel/helper-create-class-features-plugin': 7.24.4(@babel/core@7.24.4) '@babel/helper-plugin-utils': 7.24.0 '@babel/plugin-syntax-decorators': 7.24.1(@babel/core@7.24.4) @@ -2824,7 +2781,7 @@ packages: peerDependencies: '@babel/core': ^7.0.0-0 dependencies: - '@babel/core': 7.24.4 + '@babel/core': 7.24.4(supports-color@8.1.1) '@babel/helper-create-class-features-plugin': 7.24.4(@babel/core@7.24.4) '@babel/helper-plugin-utils': 7.24.0 @@ -2834,7 +2791,7 @@ packages: peerDependencies: '@babel/core': ^7.0.0-0 dependencies: - '@babel/core': 7.24.4 + '@babel/core': 7.24.4(supports-color@8.1.1) /@babel/plugin-proposal-private-property-in-object@7.21.11(@babel/core@7.24.4): resolution: {integrity: sha512-0QZ8qP/3RLDVBwBFoWAwCtgcDZJVwA5LUJRZU8x2YFfKNuFq161wK3cuGrALu5yiPu+vzwTAg/sMWVNeWeNyaw==} @@ -2843,7 +2800,7 @@ packages: peerDependencies: '@babel/core': ^7.0.0-0 dependencies: - '@babel/core': 7.24.4 + '@babel/core': 7.24.4(supports-color@8.1.1) '@babel/helper-annotate-as-pure': 7.22.5 '@babel/helper-create-class-features-plugin': 7.24.4(@babel/core@7.24.4) '@babel/helper-plugin-utils': 7.24.0 @@ -2854,7 +2811,7 @@ packages: peerDependencies: '@babel/core': ^7.0.0-0 dependencies: - '@babel/core': 7.24.4 + '@babel/core': 7.24.4(supports-color@8.1.1) '@babel/helper-plugin-utils': 7.24.0 /@babel/plugin-syntax-class-properties@7.12.13(@babel/core@7.24.4): @@ -2862,7 +2819,7 @@ packages: peerDependencies: '@babel/core': ^7.0.0-0 dependencies: - '@babel/core': 7.24.4 + '@babel/core': 7.24.4(supports-color@8.1.1) '@babel/helper-plugin-utils': 7.24.0 /@babel/plugin-syntax-class-static-block@7.14.5(@babel/core@7.24.4): @@ -2871,7 +2828,7 @@ packages: peerDependencies: '@babel/core': ^7.0.0-0 dependencies: - '@babel/core': 7.24.4 + '@babel/core': 7.24.4(supports-color@8.1.1) '@babel/helper-plugin-utils': 7.24.0 /@babel/plugin-syntax-decorators@7.24.1(@babel/core@7.24.4): @@ -2880,7 +2837,7 @@ packages: peerDependencies: '@babel/core': ^7.0.0-0 dependencies: - '@babel/core': 7.24.4 + '@babel/core': 7.24.4(supports-color@8.1.1) '@babel/helper-plugin-utils': 7.24.0 /@babel/plugin-syntax-dynamic-import@7.8.3(@babel/core@7.24.4): @@ -2888,7 +2845,7 @@ packages: peerDependencies: '@babel/core': ^7.0.0-0 dependencies: - '@babel/core': 7.24.4 + '@babel/core': 7.24.4(supports-color@8.1.1) '@babel/helper-plugin-utils': 7.24.0 /@babel/plugin-syntax-export-namespace-from@7.8.3(@babel/core@7.24.4): @@ -2896,7 +2853,7 @@ packages: peerDependencies: '@babel/core': ^7.0.0-0 dependencies: - '@babel/core': 7.24.4 + '@babel/core': 7.24.4(supports-color@8.1.1) '@babel/helper-plugin-utils': 7.24.0 /@babel/plugin-syntax-import-assertions@7.24.1(@babel/core@7.24.4): @@ -2905,7 +2862,7 @@ packages: peerDependencies: '@babel/core': ^7.0.0-0 dependencies: - '@babel/core': 7.24.4 + '@babel/core': 7.24.4(supports-color@8.1.1) '@babel/helper-plugin-utils': 7.24.0 /@babel/plugin-syntax-import-attributes@7.24.1(@babel/core@7.24.4): @@ -2914,7 +2871,7 @@ packages: peerDependencies: '@babel/core': ^7.0.0-0 dependencies: - '@babel/core': 7.24.4 + '@babel/core': 7.24.4(supports-color@8.1.1) '@babel/helper-plugin-utils': 7.24.0 /@babel/plugin-syntax-import-meta@7.10.4(@babel/core@7.24.4): @@ -2922,7 +2879,7 @@ packages: peerDependencies: '@babel/core': ^7.0.0-0 dependencies: - '@babel/core': 7.24.4 + '@babel/core': 7.24.4(supports-color@8.1.1) '@babel/helper-plugin-utils': 7.24.0 /@babel/plugin-syntax-json-strings@7.8.3(@babel/core@7.24.4): @@ -2930,7 +2887,7 @@ packages: peerDependencies: '@babel/core': ^7.0.0-0 dependencies: - '@babel/core': 7.24.4 + '@babel/core': 7.24.4(supports-color@8.1.1) '@babel/helper-plugin-utils': 7.24.0 /@babel/plugin-syntax-logical-assignment-operators@7.10.4(@babel/core@7.24.4): @@ -2938,7 +2895,7 @@ packages: peerDependencies: '@babel/core': ^7.0.0-0 dependencies: - '@babel/core': 7.24.4 + '@babel/core': 7.24.4(supports-color@8.1.1) '@babel/helper-plugin-utils': 7.24.0 /@babel/plugin-syntax-nullish-coalescing-operator@7.8.3(@babel/core@7.24.4): @@ -2946,7 +2903,7 @@ packages: peerDependencies: '@babel/core': ^7.0.0-0 dependencies: - '@babel/core': 7.24.4 + '@babel/core': 7.24.4(supports-color@8.1.1) '@babel/helper-plugin-utils': 7.24.0 /@babel/plugin-syntax-numeric-separator@7.10.4(@babel/core@7.24.4): @@ -2954,7 +2911,7 @@ packages: peerDependencies: '@babel/core': ^7.0.0-0 dependencies: - '@babel/core': 7.24.4 + '@babel/core': 7.24.4(supports-color@8.1.1) '@babel/helper-plugin-utils': 7.24.0 /@babel/plugin-syntax-object-rest-spread@7.8.3(@babel/core@7.24.4): @@ -2962,7 +2919,7 @@ packages: peerDependencies: '@babel/core': ^7.0.0-0 dependencies: - '@babel/core': 7.24.4 + '@babel/core': 7.24.4(supports-color@8.1.1) '@babel/helper-plugin-utils': 7.24.0 /@babel/plugin-syntax-optional-catch-binding@7.8.3(@babel/core@7.24.4): @@ -2970,7 +2927,7 @@ packages: peerDependencies: '@babel/core': ^7.0.0-0 dependencies: - '@babel/core': 7.24.4 + '@babel/core': 7.24.4(supports-color@8.1.1) '@babel/helper-plugin-utils': 7.24.0 /@babel/plugin-syntax-optional-chaining@7.8.3(@babel/core@7.24.4): @@ -2978,7 +2935,7 @@ packages: peerDependencies: '@babel/core': ^7.0.0-0 dependencies: - '@babel/core': 7.24.4 + '@babel/core': 7.24.4(supports-color@8.1.1) '@babel/helper-plugin-utils': 7.24.0 /@babel/plugin-syntax-private-property-in-object@7.14.5(@babel/core@7.24.4): @@ -2987,7 +2944,7 @@ packages: peerDependencies: '@babel/core': ^7.0.0-0 dependencies: - '@babel/core': 7.24.4 + '@babel/core': 7.24.4(supports-color@8.1.1) '@babel/helper-plugin-utils': 7.24.0 /@babel/plugin-syntax-top-level-await@7.14.5(@babel/core@7.24.4): @@ -2996,7 +2953,7 @@ packages: peerDependencies: '@babel/core': ^7.0.0-0 dependencies: - '@babel/core': 7.24.4 + '@babel/core': 7.24.4(supports-color@8.1.1) '@babel/helper-plugin-utils': 7.24.0 /@babel/plugin-syntax-typescript@7.24.1(@babel/core@7.24.4): @@ -3005,7 +2962,7 @@ packages: peerDependencies: '@babel/core': ^7.0.0-0 dependencies: - '@babel/core': 7.24.4 + '@babel/core': 7.24.4(supports-color@8.1.1) '@babel/helper-plugin-utils': 7.24.0 /@babel/plugin-syntax-unicode-sets-regex@7.18.6(@babel/core@7.24.4): @@ -3014,7 +2971,7 @@ packages: peerDependencies: '@babel/core': ^7.0.0 dependencies: - '@babel/core': 7.24.4 + '@babel/core': 7.24.4(supports-color@8.1.1) '@babel/helper-create-regexp-features-plugin': 7.22.15(@babel/core@7.24.4) '@babel/helper-plugin-utils': 7.24.0 @@ -3024,7 +2981,7 @@ packages: peerDependencies: '@babel/core': ^7.0.0-0 dependencies: - '@babel/core': 7.24.4 + '@babel/core': 7.24.4(supports-color@8.1.1) '@babel/helper-plugin-utils': 7.24.0 /@babel/plugin-transform-async-generator-functions@7.24.3(@babel/core@7.24.4): @@ -3033,7 +2990,7 @@ packages: peerDependencies: '@babel/core': ^7.0.0-0 dependencies: - '@babel/core': 7.24.4 + '@babel/core': 7.24.4(supports-color@8.1.1) '@babel/helper-environment-visitor': 7.22.20 '@babel/helper-plugin-utils': 7.24.0 '@babel/helper-remap-async-to-generator': 7.22.20(@babel/core@7.24.4) @@ -3045,7 +3002,7 @@ packages: peerDependencies: '@babel/core': ^7.0.0-0 dependencies: - '@babel/core': 7.24.4 + '@babel/core': 7.24.4(supports-color@8.1.1) '@babel/helper-module-imports': 7.24.3 '@babel/helper-plugin-utils': 7.24.0 '@babel/helper-remap-async-to-generator': 7.22.20(@babel/core@7.24.4) @@ -3056,7 +3013,7 @@ packages: peerDependencies: '@babel/core': ^7.0.0-0 dependencies: - '@babel/core': 7.24.4 + '@babel/core': 7.24.4(supports-color@8.1.1) '@babel/helper-plugin-utils': 7.24.0 /@babel/plugin-transform-block-scoping@7.24.4(@babel/core@7.24.4): @@ -3065,7 +3022,7 @@ packages: peerDependencies: '@babel/core': ^7.0.0-0 dependencies: - '@babel/core': 7.24.4 + '@babel/core': 7.24.4(supports-color@8.1.1) '@babel/helper-plugin-utils': 7.24.0 /@babel/plugin-transform-class-properties@7.24.1(@babel/core@7.24.4): @@ -3074,7 +3031,7 @@ packages: peerDependencies: '@babel/core': ^7.0.0-0 dependencies: - '@babel/core': 7.24.4 + '@babel/core': 7.24.4(supports-color@8.1.1) '@babel/helper-create-class-features-plugin': 7.24.4(@babel/core@7.24.4) '@babel/helper-plugin-utils': 7.24.0 @@ -3084,7 +3041,7 @@ packages: peerDependencies: '@babel/core': ^7.12.0 dependencies: - '@babel/core': 7.24.4 + '@babel/core': 7.24.4(supports-color@8.1.1) '@babel/helper-create-class-features-plugin': 7.24.4(@babel/core@7.24.4) '@babel/helper-plugin-utils': 7.24.0 '@babel/plugin-syntax-class-static-block': 7.14.5(@babel/core@7.24.4) @@ -3095,7 +3052,7 @@ packages: peerDependencies: '@babel/core': ^7.0.0-0 dependencies: - '@babel/core': 7.24.4 + '@babel/core': 7.24.4(supports-color@8.1.1) '@babel/helper-annotate-as-pure': 7.22.5 '@babel/helper-compilation-targets': 7.23.6 '@babel/helper-environment-visitor': 7.22.20 @@ -3111,7 +3068,7 @@ packages: peerDependencies: '@babel/core': ^7.0.0-0 dependencies: - '@babel/core': 7.24.4 + '@babel/core': 7.24.4(supports-color@8.1.1) '@babel/helper-plugin-utils': 7.24.0 '@babel/template': 7.24.0 @@ -3121,7 +3078,7 @@ packages: peerDependencies: '@babel/core': ^7.0.0-0 dependencies: - '@babel/core': 7.24.4 + '@babel/core': 7.24.4(supports-color@8.1.1) '@babel/helper-plugin-utils': 7.24.0 /@babel/plugin-transform-dotall-regex@7.24.1(@babel/core@7.24.4): @@ -3130,7 +3087,7 @@ packages: peerDependencies: '@babel/core': ^7.0.0-0 dependencies: - '@babel/core': 7.24.4 + '@babel/core': 7.24.4(supports-color@8.1.1) '@babel/helper-create-regexp-features-plugin': 7.22.15(@babel/core@7.24.4) '@babel/helper-plugin-utils': 7.24.0 @@ -3140,7 +3097,7 @@ packages: peerDependencies: '@babel/core': ^7.0.0-0 dependencies: - '@babel/core': 7.24.4 + '@babel/core': 7.24.4(supports-color@8.1.1) '@babel/helper-plugin-utils': 7.24.0 /@babel/plugin-transform-dynamic-import@7.24.1(@babel/core@7.24.4): @@ -3149,7 +3106,7 @@ packages: peerDependencies: '@babel/core': ^7.0.0-0 dependencies: - '@babel/core': 7.24.4 + '@babel/core': 7.24.4(supports-color@8.1.1) '@babel/helper-plugin-utils': 7.24.0 '@babel/plugin-syntax-dynamic-import': 7.8.3(@babel/core@7.24.4) @@ -3159,7 +3116,7 @@ packages: peerDependencies: '@babel/core': ^7.0.0-0 dependencies: - '@babel/core': 7.24.4 + '@babel/core': 7.24.4(supports-color@8.1.1) '@babel/helper-builder-binary-assignment-operator-visitor': 7.22.15 '@babel/helper-plugin-utils': 7.24.0 @@ -3169,7 +3126,7 @@ packages: peerDependencies: '@babel/core': ^7.0.0-0 dependencies: - '@babel/core': 7.24.4 + '@babel/core': 7.24.4(supports-color@8.1.1) '@babel/helper-plugin-utils': 7.24.0 '@babel/plugin-syntax-export-namespace-from': 7.8.3(@babel/core@7.24.4) @@ -3179,7 +3136,7 @@ packages: peerDependencies: '@babel/core': ^7.0.0-0 dependencies: - '@babel/core': 7.24.4 + '@babel/core': 7.24.4(supports-color@8.1.1) '@babel/helper-plugin-utils': 7.24.0 '@babel/helper-skip-transparent-expression-wrappers': 7.22.5 @@ -3189,7 +3146,7 @@ packages: peerDependencies: '@babel/core': ^7.0.0-0 dependencies: - '@babel/core': 7.24.4 + '@babel/core': 7.24.4(supports-color@8.1.1) '@babel/helper-compilation-targets': 7.23.6 '@babel/helper-function-name': 7.23.0 '@babel/helper-plugin-utils': 7.24.0 @@ -3200,7 +3157,7 @@ packages: peerDependencies: '@babel/core': ^7.0.0-0 dependencies: - '@babel/core': 7.24.4 + '@babel/core': 7.24.4(supports-color@8.1.1) '@babel/helper-plugin-utils': 7.24.0 '@babel/plugin-syntax-json-strings': 7.8.3(@babel/core@7.24.4) @@ -3210,7 +3167,7 @@ packages: peerDependencies: '@babel/core': ^7.0.0-0 dependencies: - '@babel/core': 7.24.4 + '@babel/core': 7.24.4(supports-color@8.1.1) '@babel/helper-plugin-utils': 7.24.0 /@babel/plugin-transform-logical-assignment-operators@7.24.1(@babel/core@7.24.4): @@ -3219,7 +3176,7 @@ packages: peerDependencies: '@babel/core': ^7.0.0-0 dependencies: - '@babel/core': 7.24.4 + '@babel/core': 7.24.4(supports-color@8.1.1) '@babel/helper-plugin-utils': 7.24.0 '@babel/plugin-syntax-logical-assignment-operators': 7.10.4(@babel/core@7.24.4) @@ -3229,7 +3186,7 @@ packages: peerDependencies: '@babel/core': ^7.0.0-0 dependencies: - '@babel/core': 7.24.4 + '@babel/core': 7.24.4(supports-color@8.1.1) '@babel/helper-plugin-utils': 7.24.0 /@babel/plugin-transform-modules-amd@7.24.1(@babel/core@7.24.4): @@ -3238,7 +3195,7 @@ packages: peerDependencies: '@babel/core': ^7.0.0-0 dependencies: - '@babel/core': 7.24.4 + '@babel/core': 7.24.4(supports-color@8.1.1) '@babel/helper-module-transforms': 7.23.3(@babel/core@7.24.4) '@babel/helper-plugin-utils': 7.24.0 @@ -3248,7 +3205,7 @@ packages: peerDependencies: '@babel/core': ^7.0.0-0 dependencies: - '@babel/core': 7.24.4 + '@babel/core': 7.24.4(supports-color@8.1.1) '@babel/helper-module-transforms': 7.23.3(@babel/core@7.24.4) '@babel/helper-plugin-utils': 7.24.0 '@babel/helper-simple-access': 7.22.5 @@ -3259,7 +3216,7 @@ packages: peerDependencies: '@babel/core': ^7.0.0-0 dependencies: - '@babel/core': 7.24.4 + '@babel/core': 7.24.4(supports-color@8.1.1) '@babel/helper-hoist-variables': 7.22.5 '@babel/helper-module-transforms': 7.23.3(@babel/core@7.24.4) '@babel/helper-plugin-utils': 7.24.0 @@ -3271,7 +3228,7 @@ packages: peerDependencies: '@babel/core': ^7.0.0-0 dependencies: - '@babel/core': 7.24.4 + '@babel/core': 7.24.4(supports-color@8.1.1) '@babel/helper-module-transforms': 7.23.3(@babel/core@7.24.4) '@babel/helper-plugin-utils': 7.24.0 @@ -3281,7 +3238,7 @@ packages: peerDependencies: '@babel/core': ^7.0.0 dependencies: - '@babel/core': 7.24.4 + '@babel/core': 7.24.4(supports-color@8.1.1) '@babel/helper-create-regexp-features-plugin': 7.22.15(@babel/core@7.24.4) '@babel/helper-plugin-utils': 7.24.0 @@ -3291,7 +3248,7 @@ packages: peerDependencies: '@babel/core': ^7.0.0-0 dependencies: - '@babel/core': 7.24.4 + '@babel/core': 7.24.4(supports-color@8.1.1) '@babel/helper-plugin-utils': 7.24.0 /@babel/plugin-transform-nullish-coalescing-operator@7.24.1(@babel/core@7.24.4): @@ -3300,7 +3257,7 @@ packages: peerDependencies: '@babel/core': ^7.0.0-0 dependencies: - '@babel/core': 7.24.4 + '@babel/core': 7.24.4(supports-color@8.1.1) '@babel/helper-plugin-utils': 7.24.0 '@babel/plugin-syntax-nullish-coalescing-operator': 7.8.3(@babel/core@7.24.4) @@ -3310,7 +3267,7 @@ packages: peerDependencies: '@babel/core': ^7.0.0-0 dependencies: - '@babel/core': 7.24.4 + '@babel/core': 7.24.4(supports-color@8.1.1) '@babel/helper-plugin-utils': 7.24.0 '@babel/plugin-syntax-numeric-separator': 7.10.4(@babel/core@7.24.4) @@ -3320,7 +3277,7 @@ packages: peerDependencies: '@babel/core': ^7.0.0-0 dependencies: - '@babel/core': 7.24.4 + '@babel/core': 7.24.4(supports-color@8.1.1) '@babel/helper-compilation-targets': 7.23.6 '@babel/helper-plugin-utils': 7.24.0 '@babel/plugin-syntax-object-rest-spread': 7.8.3(@babel/core@7.24.4) @@ -3332,7 +3289,7 @@ packages: peerDependencies: '@babel/core': ^7.0.0-0 dependencies: - '@babel/core': 7.24.4 + '@babel/core': 7.24.4(supports-color@8.1.1) '@babel/helper-plugin-utils': 7.24.0 '@babel/helper-replace-supers': 7.24.1(@babel/core@7.24.4) @@ -3342,7 +3299,7 @@ packages: peerDependencies: '@babel/core': ^7.0.0-0 dependencies: - '@babel/core': 7.24.4 + '@babel/core': 7.24.4(supports-color@8.1.1) '@babel/helper-plugin-utils': 7.24.0 '@babel/plugin-syntax-optional-catch-binding': 7.8.3(@babel/core@7.24.4) @@ -3352,7 +3309,7 @@ packages: peerDependencies: '@babel/core': ^7.0.0-0 dependencies: - '@babel/core': 7.24.4 + '@babel/core': 7.24.4(supports-color@8.1.1) '@babel/helper-plugin-utils': 7.24.0 '@babel/helper-skip-transparent-expression-wrappers': 7.22.5 '@babel/plugin-syntax-optional-chaining': 7.8.3(@babel/core@7.24.4) @@ -3363,7 +3320,7 @@ packages: peerDependencies: '@babel/core': ^7.0.0-0 dependencies: - '@babel/core': 7.24.4 + '@babel/core': 7.24.4(supports-color@8.1.1) '@babel/helper-plugin-utils': 7.24.0 /@babel/plugin-transform-private-methods@7.24.1(@babel/core@7.24.4): @@ -3372,7 +3329,7 @@ packages: peerDependencies: '@babel/core': ^7.0.0-0 dependencies: - '@babel/core': 7.24.4 + '@babel/core': 7.24.4(supports-color@8.1.1) '@babel/helper-create-class-features-plugin': 7.24.4(@babel/core@7.24.4) '@babel/helper-plugin-utils': 7.24.0 @@ -3382,7 +3339,7 @@ packages: peerDependencies: '@babel/core': ^7.0.0-0 dependencies: - '@babel/core': 7.24.4 + '@babel/core': 7.24.4(supports-color@8.1.1) '@babel/helper-annotate-as-pure': 7.22.5 '@babel/helper-create-class-features-plugin': 7.24.4(@babel/core@7.24.4) '@babel/helper-plugin-utils': 7.24.0 @@ -3394,7 +3351,7 @@ packages: peerDependencies: '@babel/core': ^7.0.0-0 dependencies: - '@babel/core': 7.24.4 + '@babel/core': 7.24.4(supports-color@8.1.1) '@babel/helper-plugin-utils': 7.24.0 /@babel/plugin-transform-regenerator@7.24.1(@babel/core@7.24.4): @@ -3403,7 +3360,7 @@ packages: peerDependencies: '@babel/core': ^7.0.0-0 dependencies: - '@babel/core': 7.24.4 + '@babel/core': 7.24.4(supports-color@8.1.1) '@babel/helper-plugin-utils': 7.24.0 regenerator-transform: 0.15.2 @@ -3413,7 +3370,7 @@ packages: peerDependencies: '@babel/core': ^7.0.0-0 dependencies: - '@babel/core': 7.24.4 + '@babel/core': 7.24.4(supports-color@8.1.1) '@babel/helper-plugin-utils': 7.24.0 /@babel/plugin-transform-runtime@7.24.3(@babel/core@7.24.4): @@ -3422,12 +3379,12 @@ packages: peerDependencies: '@babel/core': ^7.0.0-0 dependencies: - '@babel/core': 7.24.4 + '@babel/core': 7.24.4(supports-color@8.1.1) '@babel/helper-module-imports': 7.24.3 '@babel/helper-plugin-utils': 7.24.0 - babel-plugin-polyfill-corejs2: 0.4.11(@babel/core@7.24.4) - babel-plugin-polyfill-corejs3: 0.10.4(@babel/core@7.24.4) - babel-plugin-polyfill-regenerator: 0.6.2(@babel/core@7.24.4) + babel-plugin-polyfill-corejs2: 0.4.11(@babel/core@7.24.4)(supports-color@8.1.1) + babel-plugin-polyfill-corejs3: 0.10.4(@babel/core@7.24.4)(supports-color@8.1.1) + babel-plugin-polyfill-regenerator: 0.6.2(@babel/core@7.24.4)(supports-color@8.1.1) semver: 6.3.1 transitivePeerDependencies: - supports-color @@ -3438,7 +3395,7 @@ packages: peerDependencies: '@babel/core': ^7.0.0-0 dependencies: - '@babel/core': 7.24.4 + '@babel/core': 7.24.4(supports-color@8.1.1) '@babel/helper-plugin-utils': 7.24.0 /@babel/plugin-transform-spread@7.24.1(@babel/core@7.24.4): @@ -3447,7 +3404,7 @@ packages: peerDependencies: '@babel/core': ^7.0.0-0 dependencies: - '@babel/core': 7.24.4 + '@babel/core': 7.24.4(supports-color@8.1.1) '@babel/helper-plugin-utils': 7.24.0 '@babel/helper-skip-transparent-expression-wrappers': 7.22.5 @@ -3457,7 +3414,7 @@ packages: peerDependencies: '@babel/core': ^7.0.0-0 dependencies: - '@babel/core': 7.24.4 + '@babel/core': 7.24.4(supports-color@8.1.1) '@babel/helper-plugin-utils': 7.24.0 /@babel/plugin-transform-template-literals@7.24.1(@babel/core@7.24.4): @@ -3466,7 +3423,7 @@ packages: peerDependencies: '@babel/core': ^7.0.0-0 dependencies: - '@babel/core': 7.24.4 + '@babel/core': 7.24.4(supports-color@8.1.1) '@babel/helper-plugin-utils': 7.24.0 /@babel/plugin-transform-typeof-symbol@7.24.1(@babel/core@7.24.4): @@ -3475,7 +3432,7 @@ packages: peerDependencies: '@babel/core': ^7.0.0-0 dependencies: - '@babel/core': 7.24.4 + '@babel/core': 7.24.4(supports-color@8.1.1) '@babel/helper-plugin-utils': 7.24.0 /@babel/plugin-transform-typescript@7.24.4(@babel/core@7.24.4): @@ -3484,7 +3441,7 @@ packages: peerDependencies: '@babel/core': ^7.0.0-0 dependencies: - '@babel/core': 7.24.4 + '@babel/core': 7.24.4(supports-color@8.1.1) '@babel/helper-annotate-as-pure': 7.22.5 '@babel/helper-create-class-features-plugin': 7.24.4(@babel/core@7.24.4) '@babel/helper-plugin-utils': 7.24.0 @@ -3505,7 +3462,7 @@ packages: peerDependencies: '@babel/core': ^7.0.0-0 dependencies: - '@babel/core': 7.24.4 + '@babel/core': 7.24.4(supports-color@8.1.1) '@babel/helper-create-class-features-plugin': 7.24.4(@babel/core@7.24.4) '@babel/helper-plugin-utils': 7.24.0 '@babel/plugin-syntax-typescript': 7.24.1(@babel/core@7.24.4) @@ -3516,7 +3473,7 @@ packages: peerDependencies: '@babel/core': ^7.0.0-0 dependencies: - '@babel/core': 7.24.4 + '@babel/core': 7.24.4(supports-color@8.1.1) '@babel/helper-plugin-utils': 7.24.0 /@babel/plugin-transform-unicode-property-regex@7.24.1(@babel/core@7.24.4): @@ -3525,7 +3482,7 @@ packages: peerDependencies: '@babel/core': ^7.0.0-0 dependencies: - '@babel/core': 7.24.4 + '@babel/core': 7.24.4(supports-color@8.1.1) '@babel/helper-create-regexp-features-plugin': 7.22.15(@babel/core@7.24.4) '@babel/helper-plugin-utils': 7.24.0 @@ -3535,7 +3492,7 @@ packages: peerDependencies: '@babel/core': ^7.0.0-0 dependencies: - '@babel/core': 7.24.4 + '@babel/core': 7.24.4(supports-color@8.1.1) '@babel/helper-create-regexp-features-plugin': 7.22.15(@babel/core@7.24.4) '@babel/helper-plugin-utils': 7.24.0 @@ -3545,7 +3502,7 @@ packages: peerDependencies: '@babel/core': ^7.0.0 dependencies: - '@babel/core': 7.24.4 + '@babel/core': 7.24.4(supports-color@8.1.1) '@babel/helper-create-regexp-features-plugin': 7.22.15(@babel/core@7.24.4) '@babel/helper-plugin-utils': 7.24.0 @@ -3556,97 +3513,6 @@ packages: core-js: 2.6.12 regenerator-runtime: 0.13.11 - /@babel/preset-env@7.24.4(@babel/core@7.24.4): - resolution: {integrity: sha512-7Kl6cSmYkak0FK/FXjSEnLJ1N9T/WA2RkMhu17gZ/dsxKJUuTYNIylahPTzqpLyJN4WhDif8X0XK1R8Wsguo/A==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 - dependencies: - '@babel/compat-data': 7.24.4 - '@babel/core': 7.24.4 - '@babel/helper-compilation-targets': 7.23.6 - '@babel/helper-plugin-utils': 7.24.0 - '@babel/helper-validator-option': 7.23.5 - '@babel/plugin-bugfix-firefox-class-in-computed-class-key': 7.24.4(@babel/core@7.24.4) - '@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression': 7.24.1(@babel/core@7.24.4) - '@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining': 7.24.1(@babel/core@7.24.4) - '@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly': 7.24.1(@babel/core@7.24.4) - '@babel/plugin-proposal-private-property-in-object': 7.21.0-placeholder-for-preset-env.2(@babel/core@7.24.4) - '@babel/plugin-syntax-async-generators': 7.8.4(@babel/core@7.24.4) - '@babel/plugin-syntax-class-properties': 7.12.13(@babel/core@7.24.4) - '@babel/plugin-syntax-class-static-block': 7.14.5(@babel/core@7.24.4) - '@babel/plugin-syntax-dynamic-import': 7.8.3(@babel/core@7.24.4) - '@babel/plugin-syntax-export-namespace-from': 7.8.3(@babel/core@7.24.4) - '@babel/plugin-syntax-import-assertions': 7.24.1(@babel/core@7.24.4) - '@babel/plugin-syntax-import-attributes': 7.24.1(@babel/core@7.24.4) - '@babel/plugin-syntax-import-meta': 7.10.4(@babel/core@7.24.4) - '@babel/plugin-syntax-json-strings': 7.8.3(@babel/core@7.24.4) - '@babel/plugin-syntax-logical-assignment-operators': 7.10.4(@babel/core@7.24.4) - '@babel/plugin-syntax-nullish-coalescing-operator': 7.8.3(@babel/core@7.24.4) - '@babel/plugin-syntax-numeric-separator': 7.10.4(@babel/core@7.24.4) - '@babel/plugin-syntax-object-rest-spread': 7.8.3(@babel/core@7.24.4) - '@babel/plugin-syntax-optional-catch-binding': 7.8.3(@babel/core@7.24.4) - '@babel/plugin-syntax-optional-chaining': 7.8.3(@babel/core@7.24.4) - '@babel/plugin-syntax-private-property-in-object': 7.14.5(@babel/core@7.24.4) - '@babel/plugin-syntax-top-level-await': 7.14.5(@babel/core@7.24.4) - '@babel/plugin-syntax-unicode-sets-regex': 7.18.6(@babel/core@7.24.4) - '@babel/plugin-transform-arrow-functions': 7.24.1(@babel/core@7.24.4) - '@babel/plugin-transform-async-generator-functions': 7.24.3(@babel/core@7.24.4) - '@babel/plugin-transform-async-to-generator': 7.24.1(@babel/core@7.24.4) - '@babel/plugin-transform-block-scoped-functions': 7.24.1(@babel/core@7.24.4) - '@babel/plugin-transform-block-scoping': 7.24.4(@babel/core@7.24.4) - '@babel/plugin-transform-class-properties': 7.24.1(@babel/core@7.24.4) - '@babel/plugin-transform-class-static-block': 7.24.4(@babel/core@7.24.4) - '@babel/plugin-transform-classes': 7.24.1(@babel/core@7.24.4) - '@babel/plugin-transform-computed-properties': 7.24.1(@babel/core@7.24.4) - '@babel/plugin-transform-destructuring': 7.24.1(@babel/core@7.24.4) - '@babel/plugin-transform-dotall-regex': 7.24.1(@babel/core@7.24.4) - '@babel/plugin-transform-duplicate-keys': 7.24.1(@babel/core@7.24.4) - '@babel/plugin-transform-dynamic-import': 7.24.1(@babel/core@7.24.4) - '@babel/plugin-transform-exponentiation-operator': 7.24.1(@babel/core@7.24.4) - '@babel/plugin-transform-export-namespace-from': 7.24.1(@babel/core@7.24.4) - '@babel/plugin-transform-for-of': 7.24.1(@babel/core@7.24.4) - '@babel/plugin-transform-function-name': 7.24.1(@babel/core@7.24.4) - '@babel/plugin-transform-json-strings': 7.24.1(@babel/core@7.24.4) - '@babel/plugin-transform-literals': 7.24.1(@babel/core@7.24.4) - '@babel/plugin-transform-logical-assignment-operators': 7.24.1(@babel/core@7.24.4) - '@babel/plugin-transform-member-expression-literals': 7.24.1(@babel/core@7.24.4) - '@babel/plugin-transform-modules-amd': 7.24.1(@babel/core@7.24.4) - '@babel/plugin-transform-modules-commonjs': 7.24.1(@babel/core@7.24.4) - '@babel/plugin-transform-modules-systemjs': 7.24.1(@babel/core@7.24.4) - '@babel/plugin-transform-modules-umd': 7.24.1(@babel/core@7.24.4) - '@babel/plugin-transform-named-capturing-groups-regex': 7.22.5(@babel/core@7.24.4) - '@babel/plugin-transform-new-target': 7.24.1(@babel/core@7.24.4) - '@babel/plugin-transform-nullish-coalescing-operator': 7.24.1(@babel/core@7.24.4) - '@babel/plugin-transform-numeric-separator': 7.24.1(@babel/core@7.24.4) - '@babel/plugin-transform-object-rest-spread': 7.24.1(@babel/core@7.24.4) - '@babel/plugin-transform-object-super': 7.24.1(@babel/core@7.24.4) - '@babel/plugin-transform-optional-catch-binding': 7.24.1(@babel/core@7.24.4) - '@babel/plugin-transform-optional-chaining': 7.24.1(@babel/core@7.24.4) - '@babel/plugin-transform-parameters': 7.24.1(@babel/core@7.24.4) - '@babel/plugin-transform-private-methods': 7.24.1(@babel/core@7.24.4) - '@babel/plugin-transform-private-property-in-object': 7.24.1(@babel/core@7.24.4) - '@babel/plugin-transform-property-literals': 7.24.1(@babel/core@7.24.4) - '@babel/plugin-transform-regenerator': 7.24.1(@babel/core@7.24.4) - '@babel/plugin-transform-reserved-words': 7.24.1(@babel/core@7.24.4) - '@babel/plugin-transform-shorthand-properties': 7.24.1(@babel/core@7.24.4) - '@babel/plugin-transform-spread': 7.24.1(@babel/core@7.24.4) - '@babel/plugin-transform-sticky-regex': 7.24.1(@babel/core@7.24.4) - '@babel/plugin-transform-template-literals': 7.24.1(@babel/core@7.24.4) - '@babel/plugin-transform-typeof-symbol': 7.24.1(@babel/core@7.24.4) - '@babel/plugin-transform-unicode-escapes': 7.24.1(@babel/core@7.24.4) - '@babel/plugin-transform-unicode-property-regex': 7.24.1(@babel/core@7.24.4) - '@babel/plugin-transform-unicode-regex': 7.24.1(@babel/core@7.24.4) - '@babel/plugin-transform-unicode-sets-regex': 7.24.1(@babel/core@7.24.4) - '@babel/preset-modules': 0.1.6-no-external-plugins(@babel/core@7.24.4) - babel-plugin-polyfill-corejs2: 0.4.11(@babel/core@7.24.4) - babel-plugin-polyfill-corejs3: 0.10.4(@babel/core@7.24.4) - babel-plugin-polyfill-regenerator: 0.6.2(@babel/core@7.24.4) - core-js-compat: 3.37.0 - semver: 6.3.1 - transitivePeerDependencies: - - supports-color - /@babel/preset-env@7.24.4(@babel/core@7.24.4)(supports-color@8.1.1): resolution: {integrity: sha512-7Kl6cSmYkak0FK/FXjSEnLJ1N9T/WA2RkMhu17gZ/dsxKJUuTYNIylahPTzqpLyJN4WhDif8X0XK1R8Wsguo/A==} engines: {node: '>=6.9.0'} @@ -3737,14 +3603,13 @@ packages: semver: 6.3.1 transitivePeerDependencies: - supports-color - dev: true /@babel/preset-modules@0.1.6-no-external-plugins(@babel/core@7.24.4): resolution: {integrity: sha512-HrcgcIESLm9aIR842yhJ5RWan/gebQUJ6E/E5+rf0y9o6oj7w0Br+sWuL6kEQ/o/AdfvR1Je9jG18/gnpwjEyA==} peerDependencies: '@babel/core': ^7.0.0-0 || ^8.0.0-0 <8.0.0 dependencies: - '@babel/core': 7.24.4 + '@babel/core': 7.24.4(supports-color@8.1.1) '@babel/helper-plugin-utils': 7.24.0 '@babel/types': 7.24.0 esutils: 2.0.3 @@ -3789,23 +3654,6 @@ packages: - supports-color dev: true - /@babel/traverse@7.24.1: - resolution: {integrity: sha512-xuU6o9m68KeqZbQuDt2TcKSxUw/mrsvavlEqQ1leZ/B+C9tk6E4sRWy97WaXgvq5E+nU3cXMxv3WKOCanVMCmQ==} - engines: {node: '>=6.9.0'} - dependencies: - '@babel/code-frame': 7.24.2 - '@babel/generator': 7.24.4 - '@babel/helper-environment-visitor': 7.22.20 - '@babel/helper-function-name': 7.23.0 - '@babel/helper-hoist-variables': 7.22.5 - '@babel/helper-split-export-declaration': 7.22.6 - '@babel/parser': 7.24.4 - '@babel/types': 7.24.0 - debug: 4.3.4(supports-color@8.1.1) - globals: 11.12.0 - transitivePeerDependencies: - - supports-color - /@babel/traverse@7.24.1(supports-color@8.1.1): resolution: {integrity: sha512-xuU6o9m68KeqZbQuDt2TcKSxUw/mrsvavlEqQ1leZ/B+C9tk6E4sRWy97WaXgvq5E+nU3cXMxv3WKOCanVMCmQ==} engines: {node: '>=6.9.0'} @@ -3822,7 +3670,6 @@ packages: globals: 11.12.0 transitivePeerDependencies: - supports-color - dev: true /@babel/types@7.23.0: resolution: {integrity: sha512-0oIyUfKoI3mSqMvsxBdclDwxXKXAUA8v/apZbc+iSyARYou1o8ZGDxbUYyLFoW2arqS2jDGqJuZvv1d/io1axg==} @@ -4385,8 +4232,8 @@ packages: '@glint/template': optional: true dependencies: - '@babel/core': 7.24.4 - '@embroider/shared-internals': 2.6.0 + '@babel/core': 7.24.4(supports-color@8.1.1) + '@embroider/shared-internals': 2.6.0(supports-color@8.1.1) assert-never: 1.2.1 babel-import-util: 2.1.1 ember-cli-babel: 8.2.0(@babel/core@7.24.4) @@ -4418,23 +4265,6 @@ packages: - supports-color dev: true - /@embroider/shared-internals@2.6.0: - resolution: {integrity: sha512-A2BYQkhotdKOXuTaxvo9dqOIMbk+2LqFyqvfaaePkZcFJvtCkvTaD31/sSzqvRF6rdeBHjdMwU9Z2baPZ55fEQ==} - engines: {node: 12.* || 14.* || >= 16} - dependencies: - babel-import-util: 2.1.1 - debug: 4.3.4(supports-color@8.1.1) - ember-rfc176-data: 0.3.18 - fs-extra: 9.1.0 - js-string-escape: 1.0.1 - lodash: 4.17.21 - minimatch: 3.1.2 - resolve-package-path: 4.0.3 - semver: 7.6.0 - typescript-memoize: 1.1.1 - transitivePeerDependencies: - - supports-color - /@embroider/shared-internals@2.6.0(supports-color@8.1.1): resolution: {integrity: sha512-A2BYQkhotdKOXuTaxvo9dqOIMbk+2LqFyqvfaaePkZcFJvtCkvTaD31/sSzqvRF6rdeBHjdMwU9Z2baPZ55fEQ==} engines: {node: 12.* || 14.* || >= 16} @@ -4451,7 +4281,6 @@ packages: typescript-memoize: 1.1.1 transitivePeerDependencies: - supports-color - dev: true /@embroider/test-setup@4.0.0: resolution: {integrity: sha512-1S3Ebk0CEh3XDqD93AWSwQZBCk+oGv03gtkaGgdgyXGIR7jrVyDgEnEuslN/hJ0cuU8TqhiXrzHMw7bJwIGhWw==} @@ -5100,11 +4929,14 @@ packages: dependencies: '@jridgewell/gen-mapping': 0.3.5 '@jridgewell/trace-mapping': 0.3.25 - dev: true /@jridgewell/sourcemap-codec@1.4.15: resolution: {integrity: sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg==} + /@jridgewell/sourcemap-codec@1.5.0: + resolution: {integrity: sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==} + dev: true + /@jridgewell/trace-mapping@0.3.25: resolution: {integrity: sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==} dependencies: @@ -5978,18 +5810,37 @@ packages: resolution: {integrity: sha512-YF7Y52kFdFT/xVSuVdjkV5ZdX/3YtmX0QulG+x0taQOtJdHYzVU61aSSkAgVJ7NOv6qPkIYiJSgSWWN/DM5sGw==} engines: {node: '>=14.0.0'} peerDependencies: - '@babel/core': ^7.0.0 - '@types/babel__core': ^7.1.9 + '@babel/core': ^7.0.0 + '@types/babel__core': ^7.1.9 + rollup: ^4.2.0 + peerDependenciesMeta: + '@types/babel__core': + optional: true + rollup: + optional: true + dependencies: + '@babel/core': 7.24.4(supports-color@8.1.1) + '@babel/helper-module-imports': 7.24.3 + '@rollup/pluginutils': 5.1.0(rollup@4.16.4) + rollup: 4.16.4 + dev: true + + /@rollup/plugin-commonjs@28.0.0(rollup@4.16.4): + resolution: {integrity: sha512-BJcu+a+Mpq476DMXG+hevgPSl56bkUoi88dKT8t3RyUp8kGuOh+2bU8Gs7zXDlu+fyZggnJ+iOBGrb/O1SorYg==} + engines: {node: '>=16.0.0 || 14 >= 14.17'} + peerDependencies: rollup: ^4.2.0 peerDependenciesMeta: - '@types/babel__core': - optional: true rollup: optional: true dependencies: - '@babel/core': 7.24.4 - '@babel/helper-module-imports': 7.24.3 '@rollup/pluginutils': 5.1.0(rollup@4.16.4) + commondir: 1.0.1 + estree-walker: 2.0.2 + fdir: 6.4.0(picomatch@2.3.1) + is-reference: 1.2.1 + magic-string: 0.30.12 + picomatch: 2.3.1 rollup: 4.16.4 dev: true @@ -6629,16 +6480,6 @@ packages: resolution: {integrity: sha512-9BCxFwvbGg/RsZK9tjXd8s4UcwR0MWeFQ1XEKIQVVvAGJyINdrqKMcTRyLoK8Rse1GjzLV9cwjWV1olXRWEXVA==} dev: true - /@swc-node/core@1.13.0(@swc/core@1.5.0): - resolution: {integrity: sha512-lFPD4nmy4ifAOVMChFjwlpXN5KQXvegqeyuzz1KQz42q1lf+cL3Qux1/GteGuZjh8HC+Rj1RdNrHpE/MCfJSTw==} - engines: {node: '>= 10'} - peerDependencies: - '@swc/core': '>= 1.3' - '@swc/types': '>= 0.1' - dependencies: - '@swc/core': 1.5.0 - dev: true - /@swc-node/core@1.13.0(@swc/core@1.5.0)(@swc/types@0.1.6): resolution: {integrity: sha512-lFPD4nmy4ifAOVMChFjwlpXN5KQXvegqeyuzz1KQz42q1lf+cL3Qux1/GteGuZjh8HC+Rj1RdNrHpE/MCfJSTw==} engines: {node: '>= 10'} @@ -6669,25 +6510,6 @@ packages: - supports-color dev: true - /@swc-node/register@1.9.0(@swc/core@1.5.0)(typescript@5.1.6): - resolution: {integrity: sha512-i0iYInD4q5v3xQC6bKvs0QtfUxu197CU5qKALmpxEqTYs7sIhQ7KFLe3kP+eAR4gRkJTvAgjQgrokXLN2jZrOw==} - peerDependencies: - '@swc/core': '>= 1.3' - typescript: '>= 4.3' - dependencies: - '@swc-node/core': 1.13.0(@swc/core@1.5.0) - '@swc-node/sourcemap-support': 0.5.0 - '@swc/core': 1.5.0 - colorette: 2.0.20 - debug: 4.3.4(supports-color@8.1.1) - pirates: 4.0.6 - tslib: 2.6.2 - typescript: 5.1.6 - transitivePeerDependencies: - - '@swc/types' - - supports-color - dev: true - /@swc-node/sourcemap-support@0.5.0: resolution: {integrity: sha512-fbhjL5G0YvFoWwNhWleuBUfotiX+USiA9oJqu9STFw+Hb0Cgnddn+HVS/K5fI45mn92e8V+cHD2jgFjk4w2T9Q==} dependencies: @@ -6701,7 +6523,6 @@ packages: cpu: [arm64] os: [darwin] requiresBuild: true - dev: true optional: true /@swc/core-darwin-x64@1.5.0: @@ -6710,7 +6531,6 @@ packages: cpu: [x64] os: [darwin] requiresBuild: true - dev: true optional: true /@swc/core-linux-arm-gnueabihf@1.5.0: @@ -6719,7 +6539,6 @@ packages: cpu: [arm] os: [linux] requiresBuild: true - dev: true optional: true /@swc/core-linux-arm64-gnu@1.5.0: @@ -6728,7 +6547,6 @@ packages: cpu: [arm64] os: [linux] requiresBuild: true - dev: true optional: true /@swc/core-linux-arm64-musl@1.5.0: @@ -6737,7 +6555,6 @@ packages: cpu: [arm64] os: [linux] requiresBuild: true - dev: true optional: true /@swc/core-linux-x64-gnu@1.5.0: @@ -6746,7 +6563,6 @@ packages: cpu: [x64] os: [linux] requiresBuild: true - dev: true optional: true /@swc/core-linux-x64-musl@1.5.0: @@ -6755,7 +6571,6 @@ packages: cpu: [x64] os: [linux] requiresBuild: true - dev: true optional: true /@swc/core-win32-arm64-msvc@1.5.0: @@ -6764,7 +6579,6 @@ packages: cpu: [arm64] os: [win32] requiresBuild: true - dev: true optional: true /@swc/core-win32-ia32-msvc@1.5.0: @@ -6773,7 +6587,6 @@ packages: cpu: [ia32] os: [win32] requiresBuild: true - dev: true optional: true /@swc/core-win32-x64-msvc@1.5.0: @@ -6782,7 +6595,6 @@ packages: cpu: [x64] os: [win32] requiresBuild: true - dev: true optional: true /@swc/core@1.5.0: @@ -6809,17 +6621,14 @@ packages: '@swc/core-win32-arm64-msvc': 1.5.0 '@swc/core-win32-ia32-msvc': 1.5.0 '@swc/core-win32-x64-msvc': 1.5.0 - dev: true /@swc/counter@0.1.3: resolution: {integrity: sha512-e2BR4lsJkkRlKZ/qCHPw9ZaSxc0MVUd7gtbtaB7aMvHeJVYe8sOB8DBZkP2DtISHGSku9sCK6T6cnY0CtXrOCQ==} - dev: true /@swc/types@0.1.6: resolution: {integrity: sha512-/JLo/l2JsT/LRd80C3HfbmVpxOAJ11FO2RCEslFrgzLltoP9j8XIbsyDcfCt2WWyX+CM96rBoNM+IToAkFOugg==} dependencies: '@swc/counter': 0.1.3 - dev: true /@szmarczak/http-timer@1.1.2: resolution: {integrity: sha512-XIB2XbzHTN6ieIjfIMV9hlVcfPU26s2vafYWQcZHWXHOxiaRZYEDKEwdl129Zyg50+foYV2jCgtrqSA6qNuNSA==} @@ -6883,18 +6692,15 @@ packages: dependencies: '@types/eslint': 8.56.10 '@types/estree': 1.0.5 - dev: true /@types/eslint@8.56.10: resolution: {integrity: sha512-Shavhk87gCtY2fhXDctcfS3e6FdxWkCx1iUZ9eEUbh7rTqlZT0/IzOkCOVt0fCjcFuZ9FPYfuezTBImfHCDBGQ==} dependencies: '@types/estree': 1.0.5 '@types/json-schema': 7.0.15 - dev: true /@types/estree@1.0.5: resolution: {integrity: sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw==} - dev: true /@types/express-serve-static-core@4.19.0: resolution: {integrity: sha512-bGyep3JqPCRry1wq+O5n7oiBgGWmeIJXPjXXCo8EK0u8duZGSYar7cGqd3ML2JUsLGeB7fmc06KYo9fLGWqPvQ==} @@ -6979,7 +6785,6 @@ packages: resolution: {integrity: sha512-wq0cICSkRLVaf3UGLMGItu/PtdY7oaXaI/RVU+xliKVOtRna3PRY57ZDfztpDL0n11vfymMUnXv8QwYCO7L1wg==} dependencies: undici-types: 5.26.5 - dev: true /@types/node@20.12.8: resolution: {integrity: sha512-NU0rJLJnshZWdE/097cdCBbyW1h4hEg0xpovcoAQYHl8dnEyp/NAOiE45pvc+Bd1Dt+2r94v2eGFpQJ4R7g+2w==} @@ -7248,19 +7053,15 @@ packages: dependencies: '@webassemblyjs/helper-numbers': 1.11.6 '@webassemblyjs/helper-wasm-bytecode': 1.11.6 - dev: true /@webassemblyjs/floating-point-hex-parser@1.11.6: resolution: {integrity: sha512-ejAj9hfRJ2XMsNHk/v6Fu2dGS+i4UaXBXGemOfQ/JfQ6mdQg/WXtwleQRLLS4OvfDhv8rYnVwH27YJLMyYsxhw==} - dev: true /@webassemblyjs/helper-api-error@1.11.6: resolution: {integrity: sha512-o0YkoP4pVu4rN8aTJgAyj9hC2Sv5UlkzCHhxqWj8butaLvnpdc2jOwh4ewE6CX0txSfLn/UYaV/pheS2Txg//Q==} - dev: true /@webassemblyjs/helper-buffer@1.12.1: resolution: {integrity: sha512-nzJwQw99DNDKr9BVCOZcLuJJUlqkJh+kVzVl6Fmq/tI5ZtEyWT1KZMyOXltXLZJmDtvLCDgwsyrkohEtopTXCw==} - dev: true /@webassemblyjs/helper-numbers@1.11.6: resolution: {integrity: sha512-vUIhZ8LZoIWHBohiEObxVm6hwP034jwmc9kuq5GdHZH0wiLVLIPcMCdpJzG4C11cHoQ25TFIQj9kaVADVX7N3g==} @@ -7268,11 +7069,9 @@ packages: '@webassemblyjs/floating-point-hex-parser': 1.11.6 '@webassemblyjs/helper-api-error': 1.11.6 '@xtuc/long': 4.2.2 - dev: true /@webassemblyjs/helper-wasm-bytecode@1.11.6: resolution: {integrity: sha512-sFFHKwcmBprO9e7Icf0+gddyWYDViL8bpPjJJl0WHxCdETktXdmtWLGVzoHbqUcY4Be1LkNfwTmXOJUFZYSJdA==} - dev: true /@webassemblyjs/helper-wasm-section@1.12.1: resolution: {integrity: sha512-Jif4vfB6FJlUlSbgEMHUyk1j234GTNG9dBJ4XJdOySoj518Xj0oGsNi59cUQF4RRMS9ouBUxDDdyBVfPTypa5g==} @@ -7281,23 +7080,19 @@ packages: '@webassemblyjs/helper-buffer': 1.12.1 '@webassemblyjs/helper-wasm-bytecode': 1.11.6 '@webassemblyjs/wasm-gen': 1.12.1 - dev: true /@webassemblyjs/ieee754@1.11.6: resolution: {integrity: sha512-LM4p2csPNvbij6U1f19v6WR56QZ8JcHg3QIJTlSwzFcmx6WSORicYj6I63f9yU1kEUtrpG+kjkiIAkevHpDXrg==} dependencies: '@xtuc/ieee754': 1.2.0 - dev: true /@webassemblyjs/leb128@1.11.6: resolution: {integrity: sha512-m7a0FhE67DQXgouf1tbN5XQcdWoNgaAuoULHIfGFIEVKA6tu/edls6XnIlkmS6FrXAquJRPni3ZZKjw6FSPjPQ==} dependencies: '@xtuc/long': 4.2.2 - dev: true /@webassemblyjs/utf8@1.11.6: resolution: {integrity: sha512-vtXf2wTQ3+up9Zsg8sa2yWiQpzSsMyXj0qViVP6xKGCUT8p8YJ6HqI7l5eCnWx1T/FYdsv07HQs2wTFbbof/RA==} - dev: true /@webassemblyjs/wasm-edit@1.12.1: resolution: {integrity: sha512-1DuwbVvADvS5mGnXbE+c9NfA8QRcZ6iKquqjjmR10k6o+zzsRVesil54DKexiowcFCPdr/Q0qaMgB01+SQ1u6g==} @@ -7310,7 +7105,6 @@ packages: '@webassemblyjs/wasm-opt': 1.12.1 '@webassemblyjs/wasm-parser': 1.12.1 '@webassemblyjs/wast-printer': 1.12.1 - dev: true /@webassemblyjs/wasm-gen@1.12.1: resolution: {integrity: sha512-TDq4Ojh9fcohAw6OIMXqiIcTq5KUXTGRkVxbSo1hQnSy6lAM5GSdfwWeSxpAo0YzgsgF182E/U0mDNhuA0tW7w==} @@ -7320,7 +7114,6 @@ packages: '@webassemblyjs/ieee754': 1.11.6 '@webassemblyjs/leb128': 1.11.6 '@webassemblyjs/utf8': 1.11.6 - dev: true /@webassemblyjs/wasm-opt@1.12.1: resolution: {integrity: sha512-Jg99j/2gG2iaz3hijw857AVYekZe2SAskcqlWIZXjji5WStnOpVoat3gQfT/Q5tb2djnCjBtMocY/Su1GfxPBg==} @@ -7329,7 +7122,6 @@ packages: '@webassemblyjs/helper-buffer': 1.12.1 '@webassemblyjs/wasm-gen': 1.12.1 '@webassemblyjs/wasm-parser': 1.12.1 - dev: true /@webassemblyjs/wasm-parser@1.12.1: resolution: {integrity: sha512-xikIi7c2FHXysxXe3COrVUPSheuBtpcfhbpFj4gmu7KRLYOzANztwUU0IbsqvMqzuNK2+glRGWCEqZo1WCLyAQ==} @@ -7340,14 +7132,12 @@ packages: '@webassemblyjs/ieee754': 1.11.6 '@webassemblyjs/leb128': 1.11.6 '@webassemblyjs/utf8': 1.11.6 - dev: true /@webassemblyjs/wast-printer@1.12.1: resolution: {integrity: sha512-+X4WAlOisVWQMikjbcvY2e0rwPsKQ9F688lksZhBcPycBBuii3O7m8FACbDMWDojpAqvjIncrG8J0XHKyQfVeA==} dependencies: '@webassemblyjs/ast': 1.12.1 '@xtuc/long': 4.2.2 - dev: true /@xmldom/xmldom@0.8.10: resolution: {integrity: sha512-2WALfTl4xo2SkGCYRt6rDTFfk9R1czmBvUQy12gK2KuRKIpWEhcbbzy8EZXtz/jkRqHX8bFEc6FC1HjX4TUWYw==} @@ -7356,11 +7146,9 @@ packages: /@xtuc/ieee754@1.2.0: resolution: {integrity: sha512-DX8nKgqcGwsc0eJSqYt5lwP4DH5FlHnmuWWBRy7X0NcaGR0ZtuyeESgMwTYVEtxmsNGY+qit4QYT/MIYTOTPeA==} - dev: true /@xtuc/long@4.2.2: resolution: {integrity: sha512-NuHqBY1PB/D8xU6s/thBgOAiAP7HOYDQ32+BFZILJ8ivkUkAHQnWfn6WhL79Owj1qmUnoN/YPhktdIoucipkAQ==} - dev: true /@zkochan/which@2.0.3: resolution: {integrity: sha512-C1ReN7vt2/2O0fyTsx5xnbQuxBrmG5NMSbcIkPKCCfCTJgpZBsuRYzFXHj3nVq8vTfK7vxHUmzfCpSHgO7j4rg==} @@ -7400,7 +7188,6 @@ packages: acorn: ^8 dependencies: acorn: 8.11.3 - dev: true /acorn-jsx@5.3.2(acorn@8.11.3): resolution: {integrity: sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==} @@ -7425,7 +7212,6 @@ packages: resolution: {integrity: sha512-Y9rRfJG5jcKOE0CLisYbojUjIrIEE7AGMzA/Sm4BslANhbS+cDMpgBdcPT91oJ7OuJ9hYJBx59RjbhxVnrF8Xg==} engines: {node: '>=0.4.0'} hasBin: true - dev: true /agent-base@4.3.0: resolution: {integrity: sha512-salcGninV0nPrwpGNn4VTXBb1SOuXQBiqbrNXoeizJsHrsL6ERFM2Ne3JUSBWRE6aeNJI2ROP/WEEIDUiDe3cg==} @@ -7434,15 +7220,6 @@ packages: es6-promisify: 5.0.0 dev: true - /agent-base@6.0.2: - resolution: {integrity: sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==} - engines: {node: '>= 6.0.0'} - dependencies: - debug: 4.3.4(supports-color@8.1.1) - transitivePeerDependencies: - - supports-color - dev: true - /agent-base@6.0.2(supports-color@8.1.1): resolution: {integrity: sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==} engines: {node: '>= 6.0.0'} @@ -7958,20 +7735,6 @@ packages: engines: {node: '>= 12.*'} dev: true - /babel-loader@8.3.0(@babel/core@7.24.4): - resolution: {integrity: sha512-H8SvsMF+m9t15HNLMipppzkC+Y2Yq+v3SonZyU70RBL/h1gxPkH08Ot8pEE9Z4Kd+czyWJClmFS8qzIP9OZ04Q==} - engines: {node: '>= 8.9'} - peerDependencies: - '@babel/core': ^7.0.0 - webpack: '>=2' - dependencies: - '@babel/core': 7.24.4 - find-cache-dir: 3.3.2 - loader-utils: 2.0.4 - make-dir: 3.1.0 - schema-utils: 2.7.1 - dev: false - /babel-loader@8.3.0(@babel/core@7.24.4)(webpack@5.91.0): resolution: {integrity: sha512-H8SvsMF+m9t15HNLMipppzkC+Y2Yq+v3SonZyU70RBL/h1gxPkH08Ot8pEE9Z4Kd+czyWJClmFS8qzIP9OZ04Q==} engines: {node: '>= 8.9'} @@ -7985,7 +7748,6 @@ packages: make-dir: 3.1.0 schema-utils: 2.7.1 webpack: 5.91.0(@swc/core@1.5.0) - dev: true /babel-loader@9.1.3(@babel/core@7.24.4)(webpack@5.91.0): resolution: {integrity: sha512-xG3ST4DglodGf8qSwv0MdeWLhrDsw/32QMdTO5T1ZIp9gQur0HkCyFs7Awskr10JKXFXwpAhiCuYX5oGXnRGbw==} @@ -8006,7 +7768,7 @@ packages: peerDependencies: '@babel/core': ^7.0.0-beta.42 dependencies: - '@babel/core': 7.24.4 + '@babel/core': 7.24.4(supports-color@8.1.1) semver: 5.7.2 /babel-plugin-debug-macros@0.3.4(@babel/core@7.24.4): @@ -8015,7 +7777,7 @@ packages: peerDependencies: '@babel/core': ^7.0.0 dependencies: - '@babel/core': 7.24.4 + '@babel/core': 7.24.4(supports-color@8.1.1) semver: 5.7.2 /babel-plugin-debug-macros@1.0.0(@babel/core@7.24.4): @@ -8024,7 +7786,7 @@ packages: peerDependencies: '@babel/core': ^7.0.0 dependencies: - '@babel/core': 7.24.4 + '@babel/core': 7.24.4(supports-color@8.1.1) babel-import-util: 2.1.1 semver: 7.6.0 dev: true @@ -8096,18 +7858,6 @@ packages: reselect: 4.1.8 resolve: 1.22.8 - /babel-plugin-polyfill-corejs2@0.4.11(@babel/core@7.24.4): - resolution: {integrity: sha512-sMEJ27L0gRHShOh5G54uAAPaiCOygY/5ratXuiyb2G46FmlSpc9eFCzYVyDiPxfNbwzA7mYahmjQc5q+CZQ09Q==} - peerDependencies: - '@babel/core': ^7.4.0 || ^8.0.0-0 <8.0.0 - dependencies: - '@babel/compat-data': 7.24.4 - '@babel/core': 7.24.4 - '@babel/helper-define-polyfill-provider': 0.6.2(@babel/core@7.24.4) - semver: 6.3.1 - transitivePeerDependencies: - - supports-color - /babel-plugin-polyfill-corejs2@0.4.11(@babel/core@7.24.4)(supports-color@8.1.1): resolution: {integrity: sha512-sMEJ27L0gRHShOh5G54uAAPaiCOygY/5ratXuiyb2G46FmlSpc9eFCzYVyDiPxfNbwzA7mYahmjQc5q+CZQ09Q==} peerDependencies: @@ -8119,18 +7869,6 @@ packages: semver: 6.3.1 transitivePeerDependencies: - supports-color - dev: true - - /babel-plugin-polyfill-corejs3@0.10.4(@babel/core@7.24.4): - resolution: {integrity: sha512-25J6I8NGfa5YkCDogHRID3fVCadIR8/pGl1/spvCkzb6lVn6SR3ojpx9nOn9iEBcUsjY24AmdKm5khcfKdylcg==} - peerDependencies: - '@babel/core': ^7.4.0 || ^8.0.0-0 <8.0.0 - dependencies: - '@babel/core': 7.24.4 - '@babel/helper-define-polyfill-provider': 0.6.2(@babel/core@7.24.4) - core-js-compat: 3.37.0 - transitivePeerDependencies: - - supports-color /babel-plugin-polyfill-corejs3@0.10.4(@babel/core@7.24.4)(supports-color@8.1.1): resolution: {integrity: sha512-25J6I8NGfa5YkCDogHRID3fVCadIR8/pGl1/spvCkzb6lVn6SR3ojpx9nOn9iEBcUsjY24AmdKm5khcfKdylcg==} @@ -8142,17 +7880,6 @@ packages: core-js-compat: 3.37.0 transitivePeerDependencies: - supports-color - dev: true - - /babel-plugin-polyfill-regenerator@0.6.2(@babel/core@7.24.4): - resolution: {integrity: sha512-2R25rQZWP63nGwaAswvDazbPXfrM3HwVoBXK6HcqeKrSrL/JqcC/rDcf95l4r7LXLyxDXc8uQDa064GubtCABg==} - peerDependencies: - '@babel/core': ^7.4.0 || ^8.0.0-0 <8.0.0 - dependencies: - '@babel/core': 7.24.4 - '@babel/helper-define-polyfill-provider': 0.6.2(@babel/core@7.24.4) - transitivePeerDependencies: - - supports-color /babel-plugin-polyfill-regenerator@0.6.2(@babel/core@7.24.4)(supports-color@8.1.1): resolution: {integrity: sha512-2R25rQZWP63nGwaAswvDazbPXfrM3HwVoBXK6HcqeKrSrL/JqcC/rDcf95l4r7LXLyxDXc8uQDa064GubtCABg==} @@ -8163,7 +7890,6 @@ packages: '@babel/helper-define-polyfill-provider': 0.6.2(@babel/core@7.24.4)(supports-color@8.1.1) transitivePeerDependencies: - supports-color - dev: true /babel-plugin-syntax-dynamic-import@6.18.0: resolution: {integrity: sha512-MioUE+LfjCEz65Wf7Z/Rm4XCP5k2c+TbMd2Z2JKc7U9uwjBhAfNPE48KC4GTGKhppMeYVepwDBNO/nGY6NYHBA==} @@ -8430,7 +8156,7 @@ packages: resolution: {integrity: sha512-6IXBgfRt7HZ61g67ssBc6lBb3Smw3DPZ9dEYirgtvXWpRZ2A9M22nxy6opEwJDgDJzlu/bB7ToppW33OFkA1gA==} engines: {node: '>= 6'} dependencies: - '@babel/core': 7.24.4 + '@babel/core': 7.24.4(supports-color@8.1.1) '@babel/polyfill': 7.12.1 broccoli-funnel: 2.0.2 broccoli-merge-trees: 3.0.2 @@ -8451,7 +8177,7 @@ packages: peerDependencies: '@babel/core': ^7.17.9 dependencies: - '@babel/core': 7.24.4 + '@babel/core': 7.24.4(supports-color@8.1.1) broccoli-persistent-filter: 3.1.3 clone: 2.1.2 hash-for-dep: 1.5.1 @@ -8940,8 +8666,8 @@ packages: /browserstack-local@1.5.5: resolution: {integrity: sha512-jKne7yosrMcptj3hqxp36TP9k0ZW2sCqhyurX24rUL4G3eT7OLgv+CSQN8iq5dtkv5IK+g+v8fWvsiC/S9KxMg==} dependencies: - agent-base: 6.0.2 - https-proxy-agent: 5.0.1 + agent-base: 6.0.2(supports-color@8.1.1) + https-proxy-agent: 5.0.1(supports-color@8.1.1) is-running: 2.1.0 ps-tree: 1.2.0 temp-fs: 0.9.9 @@ -8969,7 +8695,6 @@ packages: /buffer-from@1.1.2: resolution: {integrity: sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==} - dev: true /buffer@5.7.1: resolution: {integrity: sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==} @@ -9234,7 +8959,6 @@ packages: /chrome-trace-event@1.0.3: resolution: {integrity: sha512-p3KULyQg4S7NIHixdwbGX+nFHkoBiA4YQmyWtjb8XngSKV124nJmRysgAeujbUVb15vh+RvFUfCPqU7rXk+hZg==} engines: {node: '>=6.0'} - dev: true /chromium-bidi@0.4.16(devtools-protocol@0.0.1147663): resolution: {integrity: sha512-7ZbXdWERxRxSwo3txsBjjmc/NLxqb1Bk30mRb0BMS4YIaiV6zvKZqL/UAH+DdqcDYayDWk2n/y8klkBDODrPvA==} @@ -9463,7 +9187,6 @@ packages: /commander@2.20.3: resolution: {integrity: sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==} - dev: true /commander@2.8.1: resolution: {integrity: sha512-+pJLBFVk+9ZZdlAOB5WuIElVPPth47hILFkmGym57aq8kwxsowvByvB0DHs1vQAhyMZzdcpTtF0VDKGkSDR4ZQ==} @@ -9866,24 +9589,6 @@ packages: engines: {node: '>=8'} dev: true - /css-loader@5.2.7: - resolution: {integrity: sha512-Q7mOvpBNBG7YrVGMxRxcBJZFL75o+cH2abNASdibkj/fffYD8qWbInZrD0S9ccI6vZclF3DsHE7njGlLtaHbhg==} - engines: {node: '>= 10.13.0'} - peerDependencies: - webpack: ^4.27.0 || ^5.0.0 - dependencies: - icss-utils: 5.1.0(postcss@8.4.38) - loader-utils: 2.0.4 - postcss: 8.4.38 - postcss-modules-extract-imports: 3.1.0(postcss@8.4.38) - postcss-modules-local-by-default: 4.0.5(postcss@8.4.38) - postcss-modules-scope: 3.2.0(postcss@8.4.38) - postcss-modules-values: 4.0.0(postcss@8.4.38) - postcss-value-parser: 4.2.0 - schema-utils: 3.3.0 - semver: 7.6.0 - dev: false - /css-loader@5.2.7(webpack@5.91.0): resolution: {integrity: sha512-Q7mOvpBNBG7YrVGMxRxcBJZFL75o+cH2abNASdibkj/fffYD8qWbInZrD0S9ccI6vZclF3DsHE7njGlLtaHbhg==} engines: {node: '>= 10.13.0'} @@ -9901,7 +9606,6 @@ packages: schema-utils: 3.3.0 semver: 7.6.0 webpack: 5.91.0(@swc/core@1.5.0) - dev: true /css-tree@1.1.3: resolution: {integrity: sha512-tRpdppF7TRazZrjJ6v3stzv93qxRcSsFmW6cX0Zm2NVKpxE1WV1HblnghVv9TreireHkqI/VDEsfolRF1p6y7Q==} @@ -9916,7 +9620,7 @@ packages: engines: {node: ^10 || ^12.20.0 || ^14.13.0 || >=15.0.0} dependencies: mdn-data: 2.0.30 - source-map-js: 1.2.0 + source-map-js: 1.2.1 dev: true /cssesc@3.0.0: @@ -10327,50 +10031,6 @@ packages: /electron-to-chromium@1.4.750: resolution: {integrity: sha512-9ItEpeu15hW5m8jKdriL+BQrgwDTXEL9pn4SkillWFu73ZNNNQ2BKKLS+ZHv2vC9UkNhosAeyfxOf/5OSeTCPA==} - /ember-auto-import@2.7.2: - resolution: {integrity: sha512-pkWIljmJClYL17YBk8FjO7NrZPQoY9v0b+FooJvaHf/xlDQIBYVP7OaDHbNuNbpj7+wAwSDAnnwxjCoLsmm4cw==} - engines: {node: 12.* || 14.* || >= 16} - dependencies: - '@babel/core': 7.24.4 - '@babel/plugin-proposal-class-properties': 7.18.6(@babel/core@7.24.4) - '@babel/plugin-proposal-decorators': 7.24.1(@babel/core@7.24.4) - '@babel/plugin-proposal-private-methods': 7.18.6(@babel/core@7.24.4) - '@babel/plugin-transform-class-static-block': 7.24.4(@babel/core@7.24.4) - '@babel/preset-env': 7.24.4(@babel/core@7.24.4) - '@embroider/macros': 1.16.0 - '@embroider/shared-internals': 2.6.0 - babel-loader: 8.3.0(@babel/core@7.24.4) - babel-plugin-ember-modules-api-polyfill: 3.5.0 - babel-plugin-ember-template-compilation: 2.2.2 - babel-plugin-htmlbars-inline-precompile: 5.3.1 - babel-plugin-syntax-dynamic-import: 6.18.0 - broccoli-debug: 0.6.5 - broccoli-funnel: 3.0.8 - broccoli-merge-trees: 4.2.0 - broccoli-plugin: 4.0.7 - broccoli-source: 3.0.1 - css-loader: 5.2.7 - debug: 4.3.4(supports-color@8.1.1) - fs-extra: 10.1.0 - fs-tree-diff: 2.0.1 - handlebars: 4.7.8 - js-string-escape: 1.0.1 - lodash: 4.17.21 - mini-css-extract-plugin: 2.9.0 - minimatch: 3.1.2 - parse5: 6.0.1 - resolve: 1.22.8 - resolve-package-path: 4.0.3 - semver: 7.6.0 - style-loader: 2.0.0 - typescript-memoize: 1.1.1 - walk-sync: 3.0.0 - transitivePeerDependencies: - - '@glint/template' - - supports-color - - webpack - dev: false - /ember-auto-import@2.7.2(webpack@5.91.0): resolution: {integrity: sha512-pkWIljmJClYL17YBk8FjO7NrZPQoY9v0b+FooJvaHf/xlDQIBYVP7OaDHbNuNbpj7+wAwSDAnnwxjCoLsmm4cw==} engines: {node: 12.* || 14.* || >= 16} @@ -10413,7 +10073,6 @@ packages: - '@glint/template' - supports-color - webpack - dev: true /ember-cache-primitive-polyfill@1.0.1(@babel/core@7.24.4): resolution: {integrity: sha512-hSPcvIKarA8wad2/b6jDd/eU+OtKmi6uP+iYQbzi5TQpjsqV6b4QdRqrLk7ClSRRKBAtdTuutx+m+X+WlEd2lw==} @@ -10468,7 +10127,7 @@ packages: resolution: {integrity: sha512-JJYeYjiz/JTn34q7F5DSOjkkZqy8qwFOOxXfE6pe9yEJqWGu4qErKxlz8I22JoVEQ/aBUO+OcKTpmctvykM9YA==} engines: {node: 6.* || 8.* || >= 10.*} dependencies: - '@babel/core': 7.24.4 + '@babel/core': 7.24.4(supports-color@8.1.1) '@babel/helper-compilation-targets': 7.23.6 '@babel/plugin-proposal-class-properties': 7.18.6(@babel/core@7.24.4) '@babel/plugin-proposal-decorators': 7.24.1(@babel/core@7.24.4) @@ -10478,7 +10137,7 @@ packages: '@babel/plugin-transform-runtime': 7.24.3(@babel/core@7.24.4) '@babel/plugin-transform-typescript': 7.24.4(@babel/core@7.24.4) '@babel/polyfill': 7.12.1 - '@babel/preset-env': 7.24.4(@babel/core@7.24.4) + '@babel/preset-env': 7.24.4(@babel/core@7.24.4)(supports-color@8.1.1) '@babel/runtime': 7.12.18 amd-name-resolver: 1.3.1 babel-plugin-debug-macros: 0.3.4(@babel/core@7.24.4) @@ -10507,7 +10166,7 @@ packages: peerDependencies: '@babel/core': ^7.12.0 dependencies: - '@babel/core': 7.24.4 + '@babel/core': 7.24.4(supports-color@8.1.1) '@babel/helper-compilation-targets': 7.23.6 '@babel/plugin-proposal-class-properties': 7.18.6(@babel/core@7.24.4) '@babel/plugin-proposal-decorators': 7.24.1(@babel/core@7.24.4) @@ -10517,7 +10176,7 @@ packages: '@babel/plugin-transform-modules-amd': 7.24.1(@babel/core@7.24.4) '@babel/plugin-transform-runtime': 7.24.3(@babel/core@7.24.4) '@babel/plugin-transform-typescript': 7.24.4(@babel/core@7.24.4) - '@babel/preset-env': 7.24.4(@babel/core@7.24.4) + '@babel/preset-env': 7.24.4(@babel/core@7.24.4)(supports-color@8.1.1) '@babel/runtime': 7.12.18 amd-name-resolver: 1.3.1 babel-plugin-debug-macros: 0.3.4(@babel/core@7.24.4) @@ -10821,7 +10480,7 @@ packages: engines: {node: '>= 14'} hasBin: true dependencies: - '@babel/core': 7.24.4 + '@babel/core': 7.24.4(supports-color@8.1.1) '@babel/plugin-transform-modules-amd': 7.24.1(@babel/core@7.24.4) amd-name-resolver: 1.3.1 babel-plugin-module-resolver: 4.1.0 @@ -11267,7 +10926,7 @@ packages: engines: {node: 8.* || 10.* || >= 12} dependencies: '@babel/parser': 7.24.4 - '@babel/traverse': 7.24.1 + '@babel/traverse': 7.24.1(supports-color@8.1.1) recast: 0.18.10 transitivePeerDependencies: - supports-color @@ -11406,7 +11065,6 @@ packages: dependencies: graceful-fs: 4.2.11 tapable: 2.2.1 - dev: true /ensure-posix-path@1.1.1: resolution: {integrity: sha512-VWU0/zXzVbeJNXvME/5EmLuEj2TauvoaTz6aFYK1Z92JCBlDlZ3Gu0tuGR42kpW1754ywTs+QB0g5TP0oj9Zaw==} @@ -11502,7 +11160,6 @@ packages: /es-module-lexer@1.5.2: resolution: {integrity: sha512-l60ETUTmLqbVbVHv1J4/qj+M8nq7AwMzEcg3kmJDt9dCNrTk+yHcYFf/Kw75pMDwd9mPcIGCG5LcS20SxYRzFA==} - dev: true /es-object-atoms@1.0.0: resolution: {integrity: sha512-MZ4iQ6JwHOBQjahnjwaC1ZtIBH+2ohjamzAO3oaHcXYup7qxjF2fixyH+Q71voWHeOkI2q/TnJao/KfXYIZWbw==} @@ -11855,7 +11512,6 @@ packages: dependencies: esrecurse: 4.3.0 estraverse: 4.3.0 - dev: true /eslint-scope@7.2.2: resolution: {integrity: sha512-dOt21O7lTMhDM+X9mB4GX+DZrZtCUJPL/wlcTqxyrx5IvO0IYtILdtrQGQp+8n5S0gwSVmOf9NQrjMOgfQZlIg==} @@ -11981,17 +11637,14 @@ packages: engines: {node: '>=4.0'} dependencies: estraverse: 5.3.0 - dev: true /estraverse@4.3.0: resolution: {integrity: sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==} engines: {node: '>=4.0'} - dev: true /estraverse@5.3.0: resolution: {integrity: sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==} engines: {node: '>=4.0'} - dev: true /estree-walker@2.0.2: resolution: {integrity: sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==} @@ -12029,7 +11682,6 @@ packages: /events@3.3.0: resolution: {integrity: sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==} engines: {node: '>=0.8.x'} - dev: true /exec-sh@0.3.6: resolution: {integrity: sha512-nQn+hI3yp+oD0huYhKwvYI32+JFeq+XkNcD1GAo3Y/MjxsfVGmrrzrnzjWiNY6f+pUCP440fThsFh5gZrRAU/w==} @@ -12326,6 +11978,17 @@ packages: pend: 1.2.0 dev: true + /fdir@6.4.0(picomatch@2.3.1): + resolution: {integrity: sha512-3oB133prH1o4j/L5lLW7uOCF1PlD+/It2L0eL/iAqWMB91RBbqTewABqxhj0ibBd90EEmWZq7ntIWzVaWcXTGQ==} + peerDependencies: + picomatch: ^3 || ^4 + peerDependenciesMeta: + picomatch: + optional: true + dependencies: + picomatch: 2.3.1 + dev: true + /figures@2.0.0: resolution: {integrity: sha512-Oa2M9atig69ZkfwiApY8F2Yy+tzMbazyvqv21R0NsSC8floSOC09BbT1ITWAdoMGQvJ/aZnR1KMwdx9tvHnTNA==} engines: {node: '>=4'} @@ -12972,7 +12635,6 @@ packages: /glob-to-regexp@0.4.1: resolution: {integrity: sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw==} - dev: true /glob@5.0.15: resolution: {integrity: sha512-c9IPMazfRITpmAAKi22dK1VKxGDX9ehhqfABDriL/lzO92xcUKEJPQHrVA/2YHSNFB4iFlykVmWvwo48nr3OxA==} @@ -13451,16 +13113,6 @@ packages: - supports-color dev: true - /https-proxy-agent@5.0.1: - resolution: {integrity: sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==} - engines: {node: '>= 6'} - dependencies: - agent-base: 6.0.2 - debug: 4.3.4(supports-color@8.1.1) - transitivePeerDependencies: - - supports-color - dev: true - /https-proxy-agent@5.0.1(supports-color@8.1.1): resolution: {integrity: sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==} engines: {node: '>= 6'} @@ -13924,6 +13576,12 @@ packages: resolution: {integrity: sha512-bCYeRA2rVibKZd+s2625gGnGF/t7DSqDs4dP7CrLA1m7jKWz6pps0LpYLJN8Q64HtmPKJ1hrN3nzPNKFEKOUiQ==} dev: true + /is-reference@1.2.1: + resolution: {integrity: sha512-U82MsXXiFIrjCK4otLT+o2NA2Cd2g5MLoOVXUZjIOhLurrRxpEXzI8O0KZHr3IjLvlAH1kTPYSuqer5T9ZVBKQ==} + dependencies: + '@types/estree': 1.0.5 + dev: true + /is-regex@1.1.4: resolution: {integrity: sha512-kvRdxDsxZjhzUX07ZnLydzS1TU/TJlTUHHY4YLL87e37oUA49DfkLqgy+VjFocowy29cKvcSiu+kIv728jTTVg==} engines: {node: '>= 0.4'} @@ -14069,7 +13727,6 @@ packages: '@types/node': 20.12.7 merge-stream: 2.0.0 supports-color: 8.1.1 - dev: true /js-string-escape@1.0.1: resolution: {integrity: sha512-Smw4xcfIQ5LVjAOuJCvN/zIodzA/BBSsluuoSykP+lUvScIi4U6RJLfwHet5cxFnCswUjISV8oAXaqaJDY3chg==} @@ -14167,7 +13824,6 @@ packages: /json-parse-even-better-errors@2.3.1: resolution: {integrity: sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==} - dev: true /json-parse-even-better-errors@3.0.1: resolution: {integrity: sha512-aatBvbL26wVUCLmbWdCpeu9iF5wOyWpagiKkInA+kfws3sWdBrTnsvN2CKcyCYyUrc7rebNBlK6+kteg7ksecg==} @@ -14351,7 +14007,6 @@ packages: /loader-runner@4.3.0: resolution: {integrity: sha512-3R/1M+yS3j5ou80Me59j7F9IMs4PXs3VqRrm0TU3AbKPxlmpoY1TNscJV/oGJXo8qCatFGTfDbY6W6ipGOYXfg==} engines: {node: '>=6.11.5'} - dev: true /loader-utils@2.0.4: resolution: {integrity: sha512-xXqpXoINfFhgua9xiqD8fPFHgkoq1mmmpE92WlDbm9rNRd/EbRb+Gqf908T2DMfuHjjJlksiK2RbHVOdD/MqSw==} @@ -14615,6 +14270,12 @@ packages: dependencies: sourcemap-codec: 1.4.8 + /magic-string@0.30.12: + resolution: {integrity: sha512-Ea8I3sQMVXr8JhN4z+H/d8zwo+tYDgHE9+5G4Wnrwhs0gaK9fXTKx0Tw5Xwsd/bCPTTZNRAdpyzvoeORe9LYpw==} + dependencies: + '@jridgewell/sourcemap-codec': 1.5.0 + dev: true + /make-dir@3.1.0: resolution: {integrity: sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==} engines: {node: '>=8'} @@ -14830,7 +14491,6 @@ packages: /mime-db@1.52.0: resolution: {integrity: sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==} engines: {node: '>= 0.6'} - dev: true /mime-types@1.0.2: resolution: {integrity: sha512-echfutj/t5SoTL4WZpqjA1DCud1XO0WQF3/GJ48YBmc4ZMhCK77QA6Z/w6VTQERLKuJ4drze3kw2TUT8xZXVNw==} @@ -14842,7 +14502,6 @@ packages: engines: {node: '>= 0.6'} dependencies: mime-db: 1.52.0 - dev: true /mime@1.2.11: resolution: {integrity: sha512-Ysa2F/nqTNGHhhm9MV8ure4+Hc+Y8AWiqUdHxsO7xu8zc92ND9f3kpALHjaP026Ft17UfxrMt95c50PLUeynBw==} @@ -14880,16 +14539,6 @@ packages: engines: {node: '>=4'} dev: true - /mini-css-extract-plugin@2.9.0: - resolution: {integrity: sha512-Zs1YsZVfemekSZG+44vBsYTLQORkPMwnlv+aehcxK/NLKC+EGhDB39/YePYYqx/sTk6NnYpuqikhSn7+JIevTA==} - engines: {node: '>= 12.13.0'} - peerDependencies: - webpack: ^5.0.0 - dependencies: - schema-utils: 4.2.0 - tapable: 2.2.1 - dev: false - /mini-css-extract-plugin@2.9.0(webpack@5.91.0): resolution: {integrity: sha512-Zs1YsZVfemekSZG+44vBsYTLQORkPMwnlv+aehcxK/NLKC+EGhDB39/YePYYqx/sTk6NnYpuqikhSn7+JIevTA==} engines: {node: '>= 12.13.0'} @@ -14899,7 +14548,6 @@ packages: schema-utils: 4.2.0 tapable: 2.2.1 webpack: 5.91.0(@swc/core@1.5.0) - dev: true /minimatch@3.1.2: resolution: {integrity: sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==} @@ -15999,7 +15647,7 @@ packages: dependencies: nanoid: 3.3.7 picocolors: 1.0.0 - source-map-js: 1.2.0 + source-map-js: 1.2.1 /prelude-ls@1.2.1: resolution: {integrity: sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==} @@ -16266,7 +15914,6 @@ packages: resolution: {integrity: sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==} dependencies: safe-buffer: 5.2.1 - dev: true /range-parser@1.2.1: resolution: {integrity: sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==} @@ -16503,7 +16150,7 @@ packages: /remove-types@1.0.0: resolution: {integrity: sha512-G7Hk1Q+UJ5DvlNAoJZObxANkBZGiGdp589rVcTW/tYqJWJ5rwfraSnKSQaETN8Epaytw8J40nS/zC7bcHGv36w==} dependencies: - '@babel/core': 7.24.4 + '@babel/core': 7.24.4(supports-color@8.1.1) '@babel/plugin-syntax-decorators': 7.24.1(@babel/core@7.24.4) '@babel/plugin-transform-typescript': 7.24.4(@babel/core@7.24.4) prettier: 2.8.8 @@ -16808,7 +16455,6 @@ packages: /safe-buffer@5.2.1: resolution: {integrity: sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==} - dev: true /safe-execa@0.1.2: resolution: {integrity: sha512-vdTshSQ2JsRCgT8eKZWNJIL26C6bVqy1SOmuCMlKHegVeo8KYRobRrefOdUq9OozSPUUiSxrylteeRmLOMFfWg==} @@ -16972,7 +16618,6 @@ packages: resolution: {integrity: sha512-Saa1xPByTTq2gdeFZYLLo+RFE35NHZkAbqZeWNd3BpzppeVisAqpDjcp8dyf6uIvEqJRd46jemmyA4iFIeVk8g==} dependencies: randombytes: 2.1.0 - dev: true /serve-static@1.15.0: resolution: {integrity: sha512-XGuRDNjXUijsUL0vl6nSD7cwURuzEgglbOaFuZM9g3kwDXOWVTck0jLzjPzGD+TazWbboZYu52/9/XPdUgne9g==} @@ -17248,8 +16893,8 @@ packages: sort-object-keys: 1.1.3 dev: true - /source-map-js@1.2.0: - resolution: {integrity: sha512-itJW8lvSA0TXEphiRoawsCksnlf8SyvmFzIhltqAHluXd88pkCd+cXJVHTDwdCr0IzwptSm035IHQktUu1QUMg==} + /source-map-js@1.2.1: + resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==} engines: {node: '>=0.10.0'} /source-map-resolve@0.5.3: @@ -17268,7 +16913,6 @@ packages: dependencies: buffer-from: 1.1.2 source-map: 0.6.1 - dev: true /source-map-url@0.3.0: resolution: {integrity: sha512-QU4fa0D6aSOmrT+7OHpUXw+jS84T0MLaQNtFs8xzLNe6Arj44Magd7WEbyVW5LNYoAPVV35aKs4azxIfVJrToQ==} @@ -17610,16 +17254,6 @@ packages: resolution: {integrity: sha512-H2N9c26eXjzL/S/K+i/RHHcFanE74dptvvjM8iwzwbVcWY/zjBbgRqF3K0DY4+OD+uTTASTBvDoxPDaPN02D7g==} dev: true - /style-loader@2.0.0: - resolution: {integrity: sha512-Z0gYUJmzZ6ZdRUqpg1r8GsaFKypE+3xAzuFeMuoHgjc9KZv3wMyCRjQIWEbhoFSq7+7yoHXySDJyyWQaPajeiQ==} - engines: {node: '>= 10.13.0'} - peerDependencies: - webpack: ^4.0.0 || ^5.0.0 - dependencies: - loader-utils: 2.0.4 - schema-utils: 3.3.0 - dev: false - /style-loader@2.0.0(webpack@5.91.0): resolution: {integrity: sha512-Z0gYUJmzZ6ZdRUqpg1r8GsaFKypE+3xAzuFeMuoHgjc9KZv3wMyCRjQIWEbhoFSq7+7yoHXySDJyyWQaPajeiQ==} engines: {node: '>= 10.13.0'} @@ -17629,7 +17263,6 @@ packages: loader-utils: 2.0.4 schema-utils: 3.3.0 webpack: 5.91.0(@swc/core@1.5.0) - dev: true /styled_string@0.0.1: resolution: {integrity: sha1-0ieCvYEpVFm8Tx3xjEutjpTdEko=} @@ -17784,7 +17417,6 @@ packages: serialize-javascript: 6.0.2 terser: 5.31.0 webpack: 5.91.0(@swc/core@1.5.0) - dev: true /terser-webpack-plugin@5.3.10(webpack@5.91.0): resolution: {integrity: sha512-BKFPWlPDndPs+NGGCr1U59t0XScL5317Y0UReNrHaw9/FwhPENlq6bfgs+4yPfyP51vqC1bQ4rp1EfXW5ZSH9w==} @@ -17819,7 +17451,6 @@ packages: acorn: 8.11.3 commander: 2.20.3 source-map-support: 0.5.21 - dev: true /testdouble@3.20.2: resolution: {integrity: sha512-790e9vJKdfddWNOaxW1/V9FcMk48cPEl3eJSj2i8Hh1fX89qArEJ6cp3DBnaECpGXc3xKJVWbc1jeNlWYWgiMg==} @@ -18740,7 +18371,6 @@ packages: dependencies: glob-to-regexp: 0.4.1 graceful-fs: 4.2.11 - dev: true /wcwidth@1.0.1: resolution: {integrity: sha512-XHPEwS0q6TaxcvG85+8EYkbiCux2XtWG2mkc47Ng2A77BQu9+DqIOJldST4HgPkuea7dvKSj5VgX3P1d4rW8Tg==} @@ -18765,7 +18395,6 @@ packages: /webpack-sources@3.2.3: resolution: {integrity: sha512-/DyMEOrDgLKKIG0fmvtz+4dUX/3Ghozwgm6iPp8KRhvn+eQf9+Q7GWxVNMk3+uCPWfdXYC4ExGBckIXdFEfH1w==} engines: {node: '>=10.13.0'} - dev: true /webpack@5.91.0: resolution: {integrity: sha512-rzVwlLeBWHJbmgTC/8TvAcu5vpJNII+MelQpylD4jNERPwpBJOE2lEcko1zJX3QJeLjTTAnQxn/OJ8bjDzVQaw==} @@ -18845,7 +18474,6 @@ packages: - '@swc/core' - esbuild - uglify-js - dev: true /websocket-driver@0.7.4: resolution: {integrity: sha512-b17KeDIQVjvb0ssuSDF2cYXSg2iztliJ4B9WdsuB6J952qCPKmnVq4DyW5motImXHDC1cBT/1UezrJVsKw5zjg==} @@ -18960,7 +18588,7 @@ packages: /workerpool@3.1.2: resolution: {integrity: sha512-WJFA0dGqIK7qj7xPTqciWBH5DlJQzoPjsANvc3Y4hNB0SScT+Emjvt0jPPkDBUjBNngX1q9hHgt1Gfwytu6pug==} dependencies: - '@babel/core': 7.24.4 + '@babel/core': 7.24.4(supports-color@8.1.1) object-assign: 4.1.1 rsvp: 4.8.5 transitivePeerDependencies: diff --git a/rollup.config.mjs b/rollup.config.mjs index dc8761a1d1f..96b6b4b31a7 100644 --- a/rollup.config.mjs +++ b/rollup.config.mjs @@ -4,6 +4,7 @@ import { createRequire } from 'node:module'; import { fileURLToPath } from 'node:url'; import glob from 'glob'; import { babel } from '@rollup/plugin-babel'; +import commonjs from '@rollup/plugin-commonjs'; import sharedBabelConfig from './babel.config.mjs'; const require = createRequire(import.meta.url); @@ -61,6 +62,9 @@ function esmConfig() { chunkFileNames: 'packages/shared-chunks/[name]-[hash].js', }, plugins: [ + commonjs({ + include: [resolve(require.resolve('source-map-js'), '..', '**')], + }), babel({ babelHelpers: 'bundled', extensions: ['.js', '.ts'], @@ -92,6 +96,7 @@ function legacyBundleConfig(input, output, { isDeveloping, isExternal }) { file: `dist/${output}`, generatedCode: 'es2015', sourcemap: true, + inlineDynamicImports: true, // We are relying on unfrozen modules because we need to add the // __esModule marker to them in our amd-compat-entrypoints. Rollup has an @@ -109,6 +114,9 @@ function legacyBundleConfig(input, output, { isDeveloping, isExternal }) { }, onLog: handleRollupWarnings, plugins: [ + commonjs({ + include: [resolve(require.resolve('source-map-js'), '..', '**')], + }), amdDefineSupport(), ...(isDeveloping ? [concatenateAMDEntrypoints()] : []), babel({ @@ -217,6 +225,7 @@ export function hiddenDependencies() { findFromProject('decorator-transforms').root, 'dist/runtime.js' ), + 'source-map-js': require.resolve('source-map-js'), }; } @@ -265,6 +274,7 @@ function entrypoint(pkg, which) { return; } let resolved = resolve(pkg.root, module); + console.log('entrypoint', module, pkg.name, pkg.root, resolved); let { dir, base } = parse(resolved); return { dir, @@ -276,8 +286,8 @@ function entrypoint(pkg, which) { function resolveTS() { return { name: 'resolve-ts', - async resolveId(source, importer) { - let result = await this.resolve(source, importer); + async resolveId(source, importer, options) { + let result = await this.resolve(source, importer, options); if (result === null) { // the rest of rollup couldn't find it let stem = resolve(dirname(importer), source); @@ -327,6 +337,7 @@ export function resolvePackages(deps, isExternal) { } if (testDependencies.includes(pkgName)) { + // these are allowed to fall through and get resolved noramlly by vite // these are allowed to fall through and get resolved noramlly by vite // within our test suite. return;