diff --git a/arduino-ide-extension/package.json b/arduino-ide-extension/package.json index 89cef04fe..8963b4f5a 100644 --- a/arduino-ide-extension/package.json +++ b/arduino-ide-extension/package.json @@ -72,6 +72,7 @@ "fast-json-stable-stringify": "^2.1.0", "fast-safe-stringify": "^2.1.1", "filename-reserved-regex": "^2.0.0", + "fqbn": "^1.0.5", "glob": "^7.1.6", "google-protobuf": "^3.20.1", "hash.js": "^1.1.7", diff --git a/arduino-ide-extension/src/browser/boards/boards-data-store.ts b/arduino-ide-extension/src/browser/boards/boards-data-store.ts index e78f5b74b..1c23fb38f 100644 --- a/arduino-ide-extension/src/browser/boards/boards-data-store.ts +++ b/arduino-ide-extension/src/browser/boards/boards-data-store.ts @@ -12,6 +12,7 @@ import { ILogger } from '@theia/core/lib/common/logger'; import { deepClone, deepFreeze } from '@theia/core/lib/common/objects'; import type { Mutable } from '@theia/core/lib/common/types'; import { inject, injectable, named } from '@theia/core/shared/inversify'; +import { FQBN } from 'fqbn'; import { BoardDetails, BoardsService, @@ -20,6 +21,7 @@ import { Programmer, isBoardIdentifierChangeEvent, isProgrammer, + sanitizeFqbn, } from '../../common/protocol'; import { notEmpty } from '../../common/utils'; import type { @@ -29,6 +31,14 @@ import type { import { NotificationCenter } from '../notification-center'; import { BoardsServiceProvider } from './boards-service-provider'; +export interface SelectConfigOptionParams { + readonly fqbn: string; + readonly optionsToUpdate: readonly Readonly<{ + option: string; + selectedValue: string; + }>[]; +} + @injectable() export class BoardsDataStore implements @@ -64,7 +74,12 @@ export class BoardsDataStore this.toDispose.pushAll([ this.boardsServiceProvider.onBoardsConfigDidChange((event) => { if (isBoardIdentifierChangeEvent(event)) { - this.updateSelectedBoardData(event.selectedBoard?.fqbn); + this.updateSelectedBoardData( + event.selectedBoard?.fqbn, + // If the change event comes from toolbar and the FQBN contains custom board options, change the currently selected options + // https://github.com/arduino/arduino-ide/issues/1588 + event.reason === 'toolbar' + ); } }), this.notificationCenter.onPlatformDidInstall(async ({ item }) => { @@ -116,7 +131,7 @@ export class BoardsDataStore if (!fqbn) { return undefined; } else { - const data = await this.getData(fqbn); + const data = await this.getData(sanitizeFqbn(fqbn)); if (data === BoardsDataStore.Data.EMPTY) { return undefined; } @@ -125,9 +140,22 @@ export class BoardsDataStore } private async updateSelectedBoardData( - fqbn: string | undefined + fqbn: string | undefined, + updateConfigOptions = false ): Promise { this._selectedBoardData = await this.getSelectedBoardData(fqbn); + if (fqbn && updateConfigOptions) { + const { options } = new FQBN(fqbn); + if (options) { + const optionsToUpdate = Object.entries(options).map(([key, value]) => ({ + option: key, + selectedValue: value, + })); + const params = { fqbn, optionsToUpdate }; + await this.selectConfigOption(params); + this._selectedBoardData = await this.getSelectedBoardData(fqbn); // reload the updated data + } + } } onStop(): void { @@ -168,7 +196,7 @@ export class BoardsDataStore return undefined; } const { configOptions } = await this.getData(fqbn); - return ConfigOption.decorate(fqbn, configOptions); + return new FQBN(fqbn).withConfigOptions(...configOptions).toString(); } async getData(fqbn: string | undefined): Promise { @@ -201,48 +229,63 @@ export class BoardsDataStore fqbn: string; selectedProgrammer: Programmer; }): Promise { - const storedData = deepClone(await this.getData(fqbn)); + const sanitizedFQBN = sanitizeFqbn(fqbn); + const storedData = deepClone(await this.getData(sanitizedFQBN)); const { programmers } = storedData; if (!programmers.find((p) => Programmer.equals(selectedProgrammer, p))) { return false; } - const data = { ...storedData, selectedProgrammer }; - await this.setData({ fqbn, data }); - this.fireChanged({ fqbn, data }); + const change: BoardsDataStoreChange = { + fqbn: sanitizedFQBN, + data: { ...storedData, selectedProgrammer }, + }; + await this.setData(change); + this.fireChanged(change); return true; } - async selectConfigOption({ - fqbn, - option, - selectedValue, - }: { - fqbn: string; - option: string; - selectedValue: string; - }): Promise { - const data = deepClone(await this.getData(fqbn)); - const { configOptions } = data; - const configOption = configOptions.find((c) => c.option === option); - if (!configOption) { + async selectConfigOption(params: SelectConfigOptionParams): Promise { + const { fqbn, optionsToUpdate } = params; + if (!optionsToUpdate.length) { return false; } - let updated = false; - for (const value of configOption.values) { - const mutable: Mutable = value; - if (mutable.value === selectedValue) { - mutable.selected = true; - updated = true; - } else { - mutable.selected = false; + + const sanitizedFQBN = sanitizeFqbn(fqbn); + const mutableData = deepClone(await this.getData(sanitizedFQBN)); + let didChange = false; + + for (const { option, selectedValue } of optionsToUpdate) { + const { configOptions } = mutableData; + const configOption = configOptions.find((c) => c.option === option); + if (configOption) { + const configOptionValueIndex = configOption.values.findIndex( + (configOptionValue) => configOptionValue.value === selectedValue + ); + if (configOptionValueIndex >= 0) { + // unselect all + configOption.values + .map((value) => value as Mutable) + .forEach((value) => (value.selected = false)); + const mutableConfigValue: Mutable = + configOption.values[configOptionValueIndex]; + // make the new value `selected` + mutableConfigValue.selected = true; + didChange = true; + } } } - if (!updated) { + + if (!didChange) { return false; } - await this.setData({ fqbn, data }); - this.fireChanged({ fqbn, data }); + + const change: BoardsDataStoreChange = { + fqbn: sanitizedFQBN, + data: mutableData, + }; + await this.setData(change); + this.fireChanged(change); return true; } diff --git a/arduino-ide-extension/src/browser/boards/boards-service-provider.ts b/arduino-ide-extension/src/browser/boards/boards-service-provider.ts index f1182adbf..29bccb242 100644 --- a/arduino-ide-extension/src/browser/boards/boards-service-provider.ts +++ b/arduino-ide-extension/src/browser/boards/boards-service-provider.ts @@ -12,6 +12,7 @@ import { Emitter } from '@theia/core/lib/common/event'; import { ILogger } from '@theia/core/lib/common/logger'; import { MessageService } from '@theia/core/lib/common/message-service'; import { nls } from '@theia/core/lib/common/nls'; +import { deepClone } from '@theia/core/lib/common/objects'; import { Deferred } from '@theia/core/lib/common/promise-util'; import type { Mutable } from '@theia/core/lib/common/types'; import { inject, injectable, optional } from '@theia/core/shared/inversify'; @@ -21,31 +22,32 @@ import { } from '@theia/output/lib/browser/output-channel'; import { BoardIdentifier, - boardIdentifierEquals, + BoardUserField, + BoardWithPackage, BoardsConfig, BoardsConfigChangeEvent, BoardsPackage, BoardsService, - BoardUserField, - BoardWithPackage, DetectedPorts, + Port, + PortIdentifier, + boardIdentifierEquals, emptyBoardsConfig, isBoardIdentifier, isBoardIdentifierChangeEvent, isPortIdentifier, isPortIdentifierChangeEvent, - Port, - PortIdentifier, portIdentifierEquals, + sanitizeFqbn, serializePlatformIdentifier, } from '../../common/protocol'; import { BoardList, BoardListHistory, - createBoardList, EditBoardsConfigActionParams, - isBoardListHistory, SelectBoardsConfigActionParams, + createBoardList, + isBoardListHistory, } from '../../common/protocol/board-list'; import type { Defined } from '../../common/types'; import type { @@ -104,6 +106,21 @@ type BoardListHistoryUpdateResult = type BoardToSelect = BoardIdentifier | undefined | 'ignore-board'; type PortToSelect = PortIdentifier | undefined | 'ignore-port'; +function sanitizeBoardToSelectFQBN(board: BoardToSelect): BoardToSelect { + if (isBoardIdentifier(board)) { + return sanitizeBoardIdentifierFQBN(board); + } + return board; +} +function sanitizeBoardIdentifierFQBN(board: BoardIdentifier): BoardIdentifier { + if (board.fqbn) { + const copy: Mutable = deepClone(board); + copy.fqbn = sanitizeFqbn(board.fqbn); + return copy; + } + return board; +} + interface UpdateBoardListHistoryParams { readonly portToSelect: PortToSelect; readonly boardToSelect: BoardToSelect; @@ -136,6 +153,9 @@ export interface BoardListUIActions { } export type BoardListUI = BoardList & BoardListUIActions; +export type BoardsConfigChangeEventUI = BoardsConfigChangeEvent & + Readonly<{ reason?: UpdateBoardsConfigReason }>; + @injectable() export class BoardListDumper implements Disposable { @inject(OutputChannelManager) @@ -190,7 +210,7 @@ export class BoardsServiceProvider private _ready = new Deferred(); private readonly boardsConfigDidChangeEmitter = - new Emitter(); + new Emitter(); readonly onBoardsConfigDidChange = this.boardsConfigDidChangeEmitter.event; private readonly boardListDidChangeEmitter = new Emitter(); @@ -353,7 +373,8 @@ export class BoardsServiceProvider portToSelect !== 'ignore-port' && !portIdentifierEquals(portToSelect, previousSelectedPort); const boardDidChangeEvent = boardDidChange - ? { selectedBoard: boardToSelect, previousSelectedBoard } + ? // The change event must always contain any custom board options. Hence the board to select is not sanitized. + { selectedBoard: boardToSelect, previousSelectedBoard } : undefined; const portDidChangeEvent = portDidChange ? { selectedPort: portToSelect, previousSelectedPort } @@ -374,16 +395,31 @@ export class BoardsServiceProvider return false; } - this.maybeUpdateBoardListHistory({ portToSelect, boardToSelect }); - this.maybeUpdateBoardsData({ boardToSelect, reason }); + // unlike for the board change event, every persistent state must not contain custom board config options in the FQBN + const sanitizedBoardToSelect = sanitizeBoardToSelectFQBN(boardToSelect); + + this.maybeUpdateBoardListHistory({ + portToSelect, + boardToSelect: sanitizedBoardToSelect, + }); + this.maybeUpdateBoardsData({ + boardToSelect: sanitizedBoardToSelect, + reason, + }); if (isBoardIdentifierChangeEvent(event)) { - this._boardsConfig.selectedBoard = event.selectedBoard; + this._boardsConfig.selectedBoard = event.selectedBoard + ? sanitizeBoardIdentifierFQBN(event.selectedBoard) + : event.selectedBoard; } if (isPortIdentifierChangeEvent(event)) { this._boardsConfig.selectedPort = event.selectedPort; } + if (reason) { + event = Object.assign(event, { reason }); + } + this.boardsConfigDidChangeEmitter.fire(event); this.refreshBoardList(); this.saveState(); diff --git a/arduino-ide-extension/src/browser/contributions/boards-data-menu-updater.ts b/arduino-ide-extension/src/browser/contributions/boards-data-menu-updater.ts index ea085f5ba..382e0f2ef 100644 --- a/arduino-ide-extension/src/browser/contributions/boards-data-menu-updater.ts +++ b/arduino-ide-extension/src/browser/contributions/boards-data-menu-updater.ts @@ -87,8 +87,7 @@ export class BoardsDataMenuUpdater extends Contribution { execute: () => this.boardsDataStore.selectConfigOption({ fqbn, - option, - selectedValue: value.value, + optionsToUpdate: [{ option, selectedValue: value.value }], }), isToggled: () => value.selected, }; diff --git a/arduino-ide-extension/src/browser/contributions/ino-language.ts b/arduino-ide-extension/src/browser/contributions/ino-language.ts index ca3019e1b..4f336ef3d 100644 --- a/arduino-ide-extension/src/browser/contributions/ino-language.ts +++ b/arduino-ide-extension/src/browser/contributions/ino-language.ts @@ -9,7 +9,6 @@ import { BoardIdentifier, BoardsService, ExecutableService, - assertSanitizedFqbn, isBoardIdentifierChangeEvent, sanitizeFqbn, } from '../../common/protocol'; @@ -159,14 +158,11 @@ export class InoLanguage extends SketchContribution { this.notificationCenter.onDidReinitialize(() => forceRestart()), this.boardDataStore.onDidChange((event) => { if (this.languageServerFqbn) { - const sanitizedFqbn = sanitizeFqbn(this.languageServerFqbn); - if (!sanitizeFqbn) { - throw new Error( - `Failed to sanitize the FQBN of the running language server. FQBN with the board settings was: ${this.languageServerFqbn}` - ); - } + const sanitizedFQBN = sanitizeFqbn(this.languageServerFqbn); + // The incoming FQBNs might contain custom boards configs, sanitize them before the comparison. + // https://github.com/arduino/arduino-ide/pull/2113#pullrequestreview-1499998328 const matchingChange = event.changes.find( - (change) => change.fqbn === sanitizedFqbn + (change) => sanitizedFQBN === sanitizeFqbn(change.fqbn) ); const { boardsConfig } = this.boardsServiceProvider; if ( @@ -228,7 +224,6 @@ export class InoLanguage extends SketchContribution { } return; } - assertSanitizedFqbn(fqbn); const fqbnWithConfig = await this.boardDataStore.appendConfigToFqbn(fqbn); if (!fqbnWithConfig) { throw new Error( diff --git a/arduino-ide-extension/src/browser/contributions/upload-sketch.ts b/arduino-ide-extension/src/browser/contributions/upload-sketch.ts index d6745d232..9cee46abb 100644 --- a/arduino-ide-extension/src/browser/contributions/upload-sketch.ts +++ b/arduino-ide-extension/src/browser/contributions/upload-sketch.ts @@ -1,7 +1,8 @@ import { Emitter } from '@theia/core/lib/common/event'; import { nls } from '@theia/core/lib/common/nls'; import { inject, injectable } from '@theia/core/shared/inversify'; -import { CoreService, sanitizeFqbn } from '../../common/protocol'; +import { FQBN } from 'fqbn'; +import { CoreService } from '../../common/protocol'; import { ArduinoMenus } from '../menu/arduino-menus'; import { CurrentSketch } from '../sketches-service-client-impl'; import { ArduinoToolbar } from '../toolbar/arduino-toolbar'; @@ -173,7 +174,11 @@ export class UploadSketch extends CoreServiceContribution { const [fqbn, { selectedProgrammer: programmer }, verify, verbose] = await Promise.all([ verifyOptions.fqbn, // already decorated FQBN - this.boardsDataStore.getData(sanitizeFqbn(verifyOptions.fqbn)), + this.boardsDataStore.getData( + verifyOptions.fqbn + ? new FQBN(verifyOptions.fqbn).toString(true) + : undefined + ), this.preferences.get('arduino.upload.verify'), this.preferences.get('arduino.upload.verbose'), ]); diff --git a/arduino-ide-extension/src/common/protocol/boards-service.ts b/arduino-ide-extension/src/common/protocol/boards-service.ts index 1ae7c1811..d76406216 100644 --- a/arduino-ide-extension/src/common/protocol/boards-service.ts +++ b/arduino-ide-extension/src/common/protocol/boards-service.ts @@ -1,4 +1,5 @@ import { nls } from '@theia/core/lib/common/nls'; +import { FQBN } from 'fqbn'; import type { MaybePromise } from '@theia/core/lib/common/types'; import type URI from '@theia/core/lib/common/uri'; import { @@ -367,40 +368,6 @@ export interface ConfigOption { readonly values: ConfigValue[]; } export namespace ConfigOption { - /** - * Appends the configuration options to the `fqbn` argument. - * Throws an error if the `fqbn` does not have the `segment(':'segment)*` format. - * The provided output format is always segment(':'segment)*(':'option'='value(','option'='value)*)? - */ - export function decorate( - fqbn: string, - configOptions: ConfigOption[] - ): string { - if (!configOptions.length) { - return fqbn; - } - - const toValue = (values: ConfigValue[]) => { - const selectedValue = values.find(({ selected }) => selected); - if (!selectedValue) { - console.warn( - `None of the config values was selected. Values were: ${JSON.stringify( - values - )}` - ); - return undefined; - } - return selectedValue.value; - }; - const options = configOptions - .map(({ option, values }) => [option, toValue(values)]) - .filter(([, value]) => !!value) - .map(([option, value]) => `${option}=${value}`) - .join(','); - - return `${fqbn}:${options}`; - } - export class ConfigOptionError extends Error { constructor(message: string) { super(message); @@ -574,28 +541,13 @@ export namespace Board { } } -/** - * Throws an error if the `fqbn` argument is not sanitized. A sanitized FQBN has the `VENDOR:ARCHITECTURE:BOARD_ID` construct. - */ -export function assertSanitizedFqbn(fqbn: string): void { - if (fqbn.split(':').length !== 3) { - throw new Error( - `Expected a sanitized FQBN with three segments in the following format: 'VENDOR:ARCHITECTURE:BOARD_ID'. Got ${fqbn} instead.` - ); - } -} - /** * Converts the `VENDOR:ARCHITECTURE:BOARD_ID[:MENU_ID=OPTION_ID[,MENU2_ID=OPTION_ID ...]]` FQBN to * `VENDOR:ARCHITECTURE:BOARD_ID` format. * See the details of the `{build.fqbn}` entry in the [specs](https://arduino.github.io/arduino-cli/latest/platform-specification/#global-predefined-properties). */ -export function sanitizeFqbn(fqbn: string | undefined): string | undefined { - if (!fqbn) { - return undefined; - } - const [vendor, arch, id] = fqbn.split(':'); - return `${vendor}:${arch}:${id}`; +export function sanitizeFqbn(fqbn: string): string { + return new FQBN(fqbn).sanitize().toString(); } export type PlatformIdentifier = Readonly<{ vendorId: string; arch: string }>; @@ -752,8 +704,8 @@ export function boardIdentifierEquals( return false; // TODO: This a strict now. Maybe compare name in the future. } if (left.fqbn && right.fqbn) { - const leftFqbn = options.looseFqbn ? sanitizeFqbn(left.fqbn) : left.fqbn; - const rightFqbn = options.looseFqbn ? sanitizeFqbn(right.fqbn) : right.fqbn; + const leftFqbn = new FQBN(left.fqbn).toString(options.looseFqbn); + const rightFqbn = new FQBN(right.fqbn).toString(options.looseFqbn); return leftFqbn === rightFqbn; } // No more Genuino hack. diff --git a/arduino-ide-extension/src/node/board-discovery.ts b/arduino-ide-extension/src/node/board-discovery.ts index 628ed97ec..44a84ec38 100644 --- a/arduino-ide-extension/src/node/board-discovery.ts +++ b/arduino-ide-extension/src/node/board-discovery.ts @@ -267,24 +267,12 @@ export class BoardDiscovery const { port, boards } = detectedPort; const key = Port.keyOf(port); if (eventType === EventType.Add) { - const alreadyDetectedPort = newState[key]; - if (alreadyDetectedPort) { - console.warn( - `Detected a new port that has been already discovered. The old value will be overridden. Old value: ${JSON.stringify( - alreadyDetectedPort - )}, new value: ${JSON.stringify(detectedPort)}` - ); - } + // Note that, the serial discovery might detect port details (such as addressLabel) in chunks. + // For example, first, the Teensy 4.1 port is detected with the `[no_device] Triple Serial` address label, + // Then, when more refinements are available, the same port is detected with `/dev/cu.usbmodem127902301 Triple Serial` address label. + // In such cases, an `add` event is received from the CLI, and the the detected port is overridden in the state. newState[key] = { port, boards }; } else if (eventType === EventType.Remove) { - const alreadyDetectedPort = newState[key]; - if (!alreadyDetectedPort) { - console.warn( - `Detected a port removal but it has not been discovered. This is most likely a bug! Detected port was: ${JSON.stringify( - detectedPort - )}` - ); - } delete newState[key]; } } diff --git a/arduino-ide-extension/src/test/browser/board-service-provider.test.ts b/arduino-ide-extension/src/test/browser/board-service-provider.test.ts index 54d3aa8ba..dd733a2c6 100644 --- a/arduino-ide-extension/src/test/browser/board-service-provider.test.ts +++ b/arduino-ide-extension/src/test/browser/board-service-provider.test.ts @@ -170,6 +170,36 @@ describe('board-service-provider', () => { expect(events).deep.equals([expectedEvent]); }); + it('should ignore custom board configs from the FQBN', () => { + boardsServiceProvider['_boardsConfig'] = { + selectedBoard: uno, + selectedPort: unoSerialPort, + }; + const events: BoardsConfigChangeEvent[] = []; + toDisposeAfterEach.push( + boardsServiceProvider.onBoardsConfigDidChange((event) => + events.push(event) + ) + ); + const mkr1000WithCustomOptions = { + ...mkr1000, + fqbn: `${mkr1000.fqbn}:c1=v1`, + }; + const didUpdate = boardsServiceProvider.updateConfig( + mkr1000WithCustomOptions + ); + expect(didUpdate).to.be.true; + const expectedEvent: BoardIdentifierChangeEvent = { + previousSelectedBoard: uno, + selectedBoard: mkr1000WithCustomOptions, // the even has the custom board options + }; + expect(events).deep.equals([expectedEvent]); + // the persisted state does not have the config options property + expect(boardsServiceProvider.boardsConfig.selectedBoard?.fqbn).to.equal( + mkr1000.fqbn + ); + }); + it('should not update the board if did not change (board identifier)', () => { boardsServiceProvider['_boardsConfig'] = { selectedBoard: uno, diff --git a/arduino-ide-extension/src/test/browser/boards-data-store.test.ts b/arduino-ide-extension/src/test/browser/boards-data-store.test.ts index 791c74e72..2ed808ad1 100644 --- a/arduino-ide-extension/src/test/browser/boards-data-store.test.ts +++ b/arduino-ide-extension/src/test/browser/boards-data-store.test.ts @@ -15,11 +15,14 @@ import { DisposableCollection, } from '@theia/core/lib/common/disposable'; import { MessageService } from '@theia/core/lib/common/message-service'; -import { wait } from '@theia/core/lib/common/promise-util'; +import { wait, waitForEvent } from '@theia/core/lib/common/promise-util'; import { Container, ContainerModule } from '@theia/core/shared/inversify'; import { expect } from 'chai'; import { BoardsDataStore } from '../../browser/boards/boards-data-store'; -import { BoardsServiceProvider } from '../../browser/boards/boards-service-provider'; +import { + BoardsServiceProvider, + UpdateBoardsConfigParams, +} from '../../browser/boards/boards-service-provider'; import { NotificationCenter } from '../../browser/notification-center'; import { BoardDetails, @@ -30,6 +33,7 @@ import { } from '../../common/protocol/boards-service'; import { NotificationServiceServer } from '../../common/protocol/notification-service'; import { bindBrowser } from './browser-test-bindings'; +import { unoSerialPort } from '../common/fixtures'; disableJSDOM(); @@ -256,8 +260,12 @@ describe('boards-data-store', function () { const result = await boardsDataStore.selectConfigOption({ fqbn, - option: configOption1.option, - selectedValue: configOption1.values[1].value, + optionsToUpdate: [ + { + option: configOption1.option, + selectedValue: configOption1.values[1].value, + }, + ], }); expect(result).to.be.ok; @@ -409,8 +417,129 @@ describe('boards-data-store', function () { ); const result = await boardsDataStore.selectConfigOption({ fqbn, - option: configOption1.option, - selectedValue: configOption1.values[1].value, + optionsToUpdate: [ + { + option: configOption1.option, + selectedValue: configOption1.values[1].value, + }, + ], + }); + expect(result).to.be.ok; + expect(didChangeCounter).to.be.equal(1); + + data = await boardsDataStore.getData(fqbn); + expect(data).to.be.deep.equal({ + configOptions: [ + { + ...configOption1, + values: [ + { label: 'C1V1', selected: false, value: 'v1' }, + { label: 'C1V2', selected: true, value: 'v2' }, + ], + }, + ], + programmers: [edbg, jlink], + }); + }); + + it('should select multiple config options', async () => { + // reconfigure the board details mock for this test case to have multiple config options + toDisposeAfterEach.push( + mockBoardDetails([ + { + fqbn, + ...baseDetails, + configOptions: [configOption1, configOption2], + }, + ]) + ); + + let data = await boardsDataStore.getData(fqbn); + expect(data).to.be.deep.equal({ + configOptions: [configOption1, configOption2], + programmers: [edbg, jlink], + }); + + let didChangeCounter = 0; + toDisposeAfterEach.push( + boardsDataStore.onDidChange(() => didChangeCounter++) + ); + const result = await boardsDataStore.selectConfigOption({ + fqbn, + optionsToUpdate: [ + { + option: configOption1.option, + selectedValue: configOption1.values[1].value, + }, + { + option: configOption2.option, + selectedValue: configOption2.values[1].value, + }, + ], + }); + expect(result).to.be.ok; + expect(didChangeCounter).to.be.equal(1); + + data = await boardsDataStore.getData(fqbn); + expect(data).to.be.deep.equal({ + configOptions: [ + { + ...configOption1, + values: [ + { label: 'C1V1', selected: false, value: 'v1' }, + { label: 'C1V2', selected: true, value: 'v2' }, + ], + }, + { + ...configOption2, + values: [ + { label: 'C2V1', selected: false, value: 'v1' }, + { label: 'C2V2', selected: true, value: 'v2' }, + ], + }, + ], + programmers: [edbg, jlink], + }); + }); + + it('should emit a did change event when updating with multiple config options and at least one of them is known (valid option + valid value)', async () => { + // reconfigure the board details mock for this test case to have multiple config options + toDisposeAfterEach.push( + mockBoardDetails([ + { + fqbn, + ...baseDetails, + configOptions: [configOption1, configOption2], + }, + ]) + ); + + let data = await boardsDataStore.getData(fqbn); + expect(data).to.be.deep.equal({ + configOptions: [configOption1, configOption2], + programmers: [edbg, jlink], + }); + + let didChangeCounter = 0; + toDisposeAfterEach.push( + boardsDataStore.onDidChange(() => didChangeCounter++) + ); + const result = await boardsDataStore.selectConfigOption({ + fqbn, + optionsToUpdate: [ + { + option: 'an unknown option', + selectedValue: configOption1.values[1].value, + }, + { + option: configOption1.option, + selectedValue: configOption1.values[1].value, + }, + { + option: configOption2.option, + selectedValue: 'an unknown value', + }, + ], }); expect(result).to.be.ok; expect(didChangeCounter).to.be.equal(1); @@ -425,6 +554,103 @@ describe('boards-data-store', function () { { label: 'C1V2', selected: true, value: 'v2' }, ], }, + configOption2, + ], + programmers: [edbg, jlink], + }); + }); + + it('should not emit a did change event when updating with multiple config options and all of the are unknown', async () => { + let data = await boardsDataStore.getData(fqbn); + expect(data).to.be.deep.equal({ + configOptions: [configOption1], + programmers: [edbg, jlink], + }); + + let didChangeCounter = 0; + toDisposeAfterEach.push( + boardsDataStore.onDidChange(() => didChangeCounter++) + ); + const result = await boardsDataStore.selectConfigOption({ + fqbn, + optionsToUpdate: [ + { + option: 'an unknown option', + selectedValue: configOption1.values[1].value, + }, + { + option: configOption1.option, + selectedValue: 'an unknown value', + }, + ], + }); + expect(result).to.be.not.ok; + expect(didChangeCounter).to.be.equal(0); + + data = await boardsDataStore.getData(fqbn); + expect(data).to.be.deep.equal({ + configOptions: [configOption1], + programmers: [edbg, jlink], + }); + }); + + it("should automatically update the selected config options if the boards config change 'reason' is the 'toolbar' and the (CLI) detected FQBN has config options", async () => { + // reconfigure the board details mock for this test case to have multiple config options + toDisposeAfterEach.push( + mockBoardDetails([ + { + fqbn, + ...baseDetails, + configOptions: [configOption1, configOption2], + }, + ]) + ); + + let data = await boardsDataStore.getData(fqbn); + expect(data).to.be.deep.equal({ + configOptions: [configOption1, configOption2], + programmers: [edbg, jlink], + }); + + let didChangeCounter = 0; + toDisposeAfterEach.push( + boardsDataStore.onDidChange(() => didChangeCounter++) + ); + + const boardsConfig = { + selectedPort: unoSerialPort, // the port value does not matter here, but the change must come from a toolbar as a boards config: with port+board, + selectedBoard: { + fqbn: `${board.fqbn}:${configOption1.option}=${configOption1.values[1].value},${configOption2.option}=${configOption2.values[1].value}`, + name: board.name, + }, + }; + const params: UpdateBoardsConfigParams = { + ...boardsConfig, + reason: 'toolbar', + }; + const updated = boardsServiceProvider.updateConfig(params); + expect(updated).to.be.ok; + + await waitForEvent(boardsDataStore.onDidChange, 100); + + expect(didChangeCounter).to.be.equal(1); + data = await boardsDataStore.getData(fqbn); + expect(data).to.be.deep.equal({ + configOptions: [ + { + ...configOption1, + values: [ + { label: 'C1V1', selected: false, value: 'v1' }, + { label: 'C1V2', selected: true, value: 'v2' }, + ], + }, + { + ...configOption2, + values: [ + { label: 'C2V1', selected: false, value: 'v1' }, + { label: 'C2V2', selected: true, value: 'v2' }, + ], + }, ], programmers: [edbg, jlink], }); @@ -444,8 +670,9 @@ describe('boards-data-store', function () { ); const result = await boardsDataStore.selectConfigOption({ fqbn, - option: 'missing', - selectedValue: configOption1.values[1].value, + optionsToUpdate: [ + { option: 'missing', selectedValue: configOption1.values[1].value }, + ], }); expect(result).to.be.not.ok; expect(didChangeCounter).to.be.equal(0); @@ -470,8 +697,9 @@ describe('boards-data-store', function () { ); const result = await boardsDataStore.selectConfigOption({ fqbn, - option: configOption1.option, - selectedValue: 'missing', + optionsToUpdate: [ + { option: configOption1.option, selectedValue: 'missing' }, + ], }); expect(result).to.be.not.ok; expect(didChangeCounter).to.be.equal(0); diff --git a/yarn.lock b/yarn.lock index fe12ae342..71a0d2acb 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3885,6 +3885,14 @@ ardunno-cli@^0.1.2: nice-grpc-common "^2.0.2" protobufjs "^7.2.3" +ardunno-cli@^0.1.7: + version "0.1.8" + resolved "https://registry.yarnpkg.com/ardunno-cli/-/ardunno-cli-0.1.8.tgz#c70b11b2ee0256227689079d01b828328bb1bfb6" + integrity sha512-DfyI98EFHdpc26nPYq2IXK6ZNypwBY0Fg+CAjYeGI/mjgQ1O9QUjNgz6NADwr+pcQ/ikhvLc88Ud9qR08CFTyg== + dependencies: + nice-grpc-common "^2.0.2" + protobufjs "^7.2.3" + are-we-there-yet@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/are-we-there-yet/-/are-we-there-yet-2.0.0.tgz#372e0e7bd279d8e94c653aaa1f67200884bf3e1c" @@ -4126,6 +4134,11 @@ available-typed-arrays@^1.0.5: resolved "https://registry.yarnpkg.com/available-typed-arrays/-/available-typed-arrays-1.0.5.tgz#92f95616501069d07d10edb2fc37d3e1c65123b7" integrity sha512-DMD0KiN46eipeziST1LPP/STfDU0sufISXmjSgvVsoU2tqxctQeASejWcfNtxYKqETM1UxQ8sp2OrSBWpHY6sw== +available-typed-arrays@^1.0.6: + version "1.0.6" + resolved "https://registry.yarnpkg.com/available-typed-arrays/-/available-typed-arrays-1.0.6.tgz#ac812d8ce5a6b976d738e1c45f08d0b00bc7d725" + integrity sha512-j1QzY8iPNPG4o4xmO3ptzpRxTciqD3MgEHtifP/YnJpIo58Xu+ne4BejlbkuaLfXn/nz6HFiw29bLpj2PNMdGg== + axios@^1.0.0, axios@^1.6.2, axios@^1.6.7: version "1.6.7" resolved "https://registry.yarnpkg.com/axios/-/axios-1.6.7.tgz#7b48c2e27c96f9c68a2f8f31e2ab19f59b06b0a7" @@ -4557,6 +4570,16 @@ call-bind@^1.0.0, call-bind@^1.0.2: function-bind "^1.1.1" get-intrinsic "^1.0.2" +call-bind@^1.0.5: + version "1.0.6" + resolved "https://registry.yarnpkg.com/call-bind/-/call-bind-1.0.6.tgz#6c46675fc7a5e9de82d75a233d586c8b7ac0d931" + integrity sha512-Mj50FLHtlsoVfRfnHaZvyrooHcrlceNZdL/QBvJJVd9Ta55qCQK0gs4ss2oZDeV9zFCs6ewzYgVE5yfVmfFpVg== + dependencies: + es-errors "^1.3.0" + function-bind "^1.1.2" + get-intrinsic "^1.2.3" + set-function-length "^1.2.0" + callsites@^3.0.0: version "3.1.0" resolved "https://registry.yarnpkg.com/callsites/-/callsites-3.1.0.tgz#b3630abd8943432f54b3f0519238e33cd7df2f73" @@ -5513,6 +5536,30 @@ deep-eql@^4.1.3: dependencies: type-detect "^4.0.0" +deep-equal@^2.2.3: + version "2.2.3" + resolved "https://registry.yarnpkg.com/deep-equal/-/deep-equal-2.2.3.tgz#af89dafb23a396c7da3e862abc0be27cf51d56e1" + integrity sha512-ZIwpnevOurS8bpT4192sqAowWM76JDKSHYzMLty3BZGSswgq6pBaH3DhCSW5xVAZICZyKdOBPjwww5wfgT/6PA== + dependencies: + array-buffer-byte-length "^1.0.0" + call-bind "^1.0.5" + es-get-iterator "^1.1.3" + get-intrinsic "^1.2.2" + is-arguments "^1.1.1" + is-array-buffer "^3.0.2" + is-date-object "^1.0.5" + is-regex "^1.1.4" + is-shared-array-buffer "^1.0.2" + isarray "^2.0.5" + object-is "^1.1.5" + object-keys "^1.1.1" + object.assign "^4.1.4" + regexp.prototype.flags "^1.5.1" + side-channel "^1.0.4" + which-boxed-primitive "^1.0.2" + which-collection "^1.0.1" + which-typed-array "^1.1.13" + deep-extend@^0.6.0: version "0.6.0" resolved "https://registry.yarnpkg.com/deep-extend/-/deep-extend-0.6.0.tgz#c4fa7c95404a17a9c3e8ca7e1537312b736330ac" @@ -5554,6 +5601,16 @@ define-data-property@^1.0.1: gopd "^1.0.1" has-property-descriptors "^1.0.0" +define-data-property@^1.1.2: + version "1.1.2" + resolved "https://registry.yarnpkg.com/define-data-property/-/define-data-property-1.1.2.tgz#f3c33b4f0102360cd7c0f5f28700f5678510b63a" + integrity sha512-SRtsSqsDbgpJBbW3pABMCOt6rQyeM8s8RiyeSN8jYG8sYmt/kGJejbydttUsnDs1tadr19tvhT4ShwMyoqAm4g== + dependencies: + es-errors "^1.3.0" + get-intrinsic "^1.2.2" + gopd "^1.0.1" + has-property-descriptors "^1.0.1" + define-lazy-prop@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/define-lazy-prop/-/define-lazy-prop-2.0.0.tgz#3f7ae421129bcaaac9bc74905c98a0009ec9ee7f" @@ -6102,6 +6159,26 @@ es-abstract@^1.22.1: unbox-primitive "^1.0.2" which-typed-array "^1.1.11" +es-errors@^1.3.0: + version "1.3.0" + resolved "https://registry.yarnpkg.com/es-errors/-/es-errors-1.3.0.tgz#05f75a25dab98e4fb1dcd5e1472c0546d5057c8f" + integrity sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw== + +es-get-iterator@^1.1.3: + version "1.1.3" + resolved "https://registry.yarnpkg.com/es-get-iterator/-/es-get-iterator-1.1.3.tgz#3ef87523c5d464d41084b2c3c9c214f1199763d6" + integrity sha512-sPZmqHBe6JIiTfN5q2pEi//TwxmAFHwj/XEuYjTuse78i8KxaqMTTzxPoFKuzRpDpTJ+0NAbpfenkmH2rePtuw== + dependencies: + call-bind "^1.0.2" + get-intrinsic "^1.1.3" + has-symbols "^1.0.3" + is-arguments "^1.1.1" + is-map "^2.0.2" + is-set "^2.0.2" + is-string "^1.0.7" + isarray "^2.0.5" + stop-iteration-iterator "^1.0.0" + es-iterator-helpers@^1.0.12: version "1.0.15" resolved "https://registry.yarnpkg.com/es-iterator-helpers/-/es-iterator-helpers-1.0.15.tgz#bd81d275ac766431d19305923707c3efd9f1ae40" @@ -6881,6 +6958,15 @@ forwarded@0.2.0: resolved "https://registry.yarnpkg.com/forwarded/-/forwarded-0.2.0.tgz#2269936428aad4c15c7ebe9779a84bf0b2a81811" integrity sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow== +fqbn@^1.0.5: + version "1.0.5" + resolved "https://registry.yarnpkg.com/fqbn/-/fqbn-1.0.5.tgz#4a4ea6babadeffc12c4637d5a4f5ef57c7ef317b" + integrity sha512-ImcK5biXDRSQHsvC8XXhEZH/YPmW7lRrmTABv6m5D7HQz3Xzi5foHZxTxmeXekcrRkZOfIrDWWtpk2wtUJgPPA== + dependencies: + ardunno-cli "^0.1.7" + clone "^2.1.2" + deep-equal "^2.2.3" + fresh@0.5.2: version "0.5.2" resolved "https://registry.yarnpkg.com/fresh/-/fresh-0.5.2.tgz#3d8cadd90d976569fa835ab1f8e4b23a105605a7" @@ -6981,6 +7067,11 @@ function-bind@^1.1.1: resolved "https://registry.yarnpkg.com/function-bind/-/function-bind-1.1.1.tgz#a56899d3ea3c9bab874bb9773b7c5ede92f4895d" integrity sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A== +function-bind@^1.1.2: + version "1.1.2" + resolved "https://registry.yarnpkg.com/function-bind/-/function-bind-1.1.2.tgz#2c02d864d97f3ea6c8830c464cbd11ab6eab7a1c" + integrity sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA== + function.prototype.name@^1.1.5, function.prototype.name@^1.1.6: version "1.1.6" resolved "https://registry.yarnpkg.com/function.prototype.name/-/function.prototype.name-1.1.6.tgz#cdf315b7d90ee77a4c6ee216c3c3362da07533fd" @@ -7069,6 +7160,17 @@ get-intrinsic@^1.0.2, get-intrinsic@^1.1.1, get-intrinsic@^1.1.3, get-intrinsic@ has-proto "^1.0.1" has-symbols "^1.0.3" +get-intrinsic@^1.2.2, get-intrinsic@^1.2.3: + version "1.2.4" + resolved "https://registry.yarnpkg.com/get-intrinsic/-/get-intrinsic-1.2.4.tgz#e385f5a4b5227d449c3eabbad05494ef0abbeadd" + integrity sha512-5uYhsJH8VJBTv7oslg4BznJYhDoRI6waYCxMmCdnTrcCrHA/fCFKoTFz2JKKE0HdDFUF7/oQuhzumXJK7paBRQ== + dependencies: + es-errors "^1.3.0" + function-bind "^1.1.2" + has-proto "^1.0.1" + has-symbols "^1.0.3" + hasown "^2.0.0" + get-own-enumerable-property-symbols@^3.0.0: version "3.0.2" resolved "https://registry.yarnpkg.com/get-own-enumerable-property-symbols/-/get-own-enumerable-property-symbols-3.0.2.tgz#b5fde77f22cbe35f390b4e089922c50bce6ef664" @@ -7443,6 +7545,13 @@ has-property-descriptors@^1.0.0: dependencies: get-intrinsic "^1.1.1" +has-property-descriptors@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/has-property-descriptors/-/has-property-descriptors-1.0.1.tgz#52ba30b6c5ec87fd89fa574bc1c39125c6f65340" + integrity sha512-VsX8eaIewvas0xnvinAe9bw4WfIeODpGYikiWYLH+dma0Jw6KHYqWiWfhQlgOVK8D6PvjubK5Uc4P0iIhIcNVg== + dependencies: + get-intrinsic "^1.2.2" + has-proto@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/has-proto/-/has-proto-1.0.1.tgz#1885c1305538958aff469fef37937c22795408e0" @@ -7460,6 +7569,13 @@ has-tostringtag@^1.0.0: dependencies: has-symbols "^1.0.2" +has-tostringtag@^1.0.1: + version "1.0.2" + resolved "https://registry.yarnpkg.com/has-tostringtag/-/has-tostringtag-1.0.2.tgz#2cdc42d40bef2e5b4eeab7c01a73c54ce7ab5abc" + integrity sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw== + dependencies: + has-symbols "^1.0.3" + has-unicode@2.0.1, has-unicode@^2.0.0, has-unicode@^2.0.1: version "2.0.1" resolved "https://registry.yarnpkg.com/has-unicode/-/has-unicode-2.0.1.tgz#e0e6fe6a28cf51138855e086d1691e771de2a8b9" @@ -7478,6 +7594,13 @@ hash.js@^1.1.7: inherits "^2.0.3" minimalistic-assert "^1.0.1" +hasown@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/hasown/-/hasown-2.0.0.tgz#f4c513d454a57b7c7e1650778de226b11700546c" + integrity sha512-vUptKVTpIJhcczKBbgnS+RtcuYMB8+oNzPK2/Hp3hanz8JmpATdmmgLgSaadVREkDm+e2giHwY3ZRkyjSIDDFA== + dependencies: + function-bind "^1.1.2" + hast-util-whitespace@^2.0.0: version "2.0.1" resolved "https://registry.yarnpkg.com/hast-util-whitespace/-/hast-util-whitespace-2.0.1.tgz#0ec64e257e6fc216c7d14c8a1b74d27d650b4557" @@ -7814,6 +7937,15 @@ inspect-with-kind@^1.0.5: dependencies: kind-of "^6.0.2" +internal-slot@^1.0.4: + version "1.0.7" + resolved "https://registry.yarnpkg.com/internal-slot/-/internal-slot-1.0.7.tgz#c06dcca3ed874249881007b0a5523b172a190802" + integrity sha512-NGnrKwXzSms2qUUih/ILZ5JBqNTSa1+ZmP6flaIp6KmSElgE9qdndzS3cqjrDovwFdmwsGsLdeFgB6suw+1e9g== + dependencies: + es-errors "^1.3.0" + hasown "^2.0.0" + side-channel "^1.0.4" + internal-slot@^1.0.5: version "1.0.5" resolved "https://registry.yarnpkg.com/internal-slot/-/internal-slot-1.0.5.tgz#f2a2ee21f668f8627a4667f309dc0f4fb6674986" @@ -7848,7 +7980,7 @@ ipaddr.js@1.9.1: resolved "https://registry.yarnpkg.com/ipaddr.js/-/ipaddr.js-1.9.1.tgz#bff38543eeb8984825079ff3a2a8e6cbd46781b3" integrity sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g== -is-arguments@^1.0.4: +is-arguments@^1.0.4, is-arguments@^1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/is-arguments/-/is-arguments-1.1.1.tgz#15b3f88fda01f2a97fec84ca761a560f123efa9b" integrity sha512-8Q7EARjzEnKpt/PCD7e1cgUS0a6X8u5tdSiMqXhojOdoV9TsMsiO+9VLC5vAmO8N7/GmXn7yjR8qnA6bVAEzfA== @@ -8002,7 +8134,7 @@ is-lambda@^1.0.1: resolved "https://registry.yarnpkg.com/is-lambda/-/is-lambda-1.0.1.tgz#3d9877899e6a53efc0160504cde15f82e6f061d5" integrity sha512-z7CMFGNrENq5iFB9Bqo64Xk6Y9sg+epq1myIcdHaGnbMTYOxvzsEtdYqQUylB7LxfkvgrrjP32T6Ywciio9UIQ== -is-map@^2.0.1: +is-map@^2.0.1, is-map@^2.0.2: version "2.0.2" resolved "https://registry.yarnpkg.com/is-map/-/is-map-2.0.2.tgz#00922db8c9bf73e81b7a335827bc2a43f2b91127" integrity sha512-cOZFQQozTha1f4MxLFzlgKYPTyj26picdZTx82hbc/Xf4K/tZOOXSCkMvU4pKioRXGDLJRn0GM7Upe7kR721yg== @@ -8099,7 +8231,7 @@ is-regexp@^1.0.0: resolved "https://registry.yarnpkg.com/is-regexp/-/is-regexp-1.0.0.tgz#fd2d883545c46bac5a633e7b9a09e87fa2cb5069" integrity sha512-7zjFAPO4/gwyQAAgRRmqeEeyIICSdmCqa3tsVHMdBzaXXRiqopZL4Cyghg/XulGWrtABTpbnYYzzIRffLkP4oA== -is-set@^2.0.1: +is-set@^2.0.1, is-set@^2.0.2: version "2.0.2" resolved "https://registry.yarnpkg.com/is-set/-/is-set-2.0.2.tgz#90755fa4c2562dc1c5d4024760d6119b94ca18ec" integrity sha512-+2cnTEZeY5z/iXGbLhPrOAaK/Mau5k5eXq9j14CpRTftq0pAJu2MwVRSZhyZWBzx3o6X795Lz6Bpb6R0GKf37g== @@ -10207,6 +10339,14 @@ object-inspect@^1.12.3, object-inspect@^1.9.0: resolved "https://registry.yarnpkg.com/object-inspect/-/object-inspect-1.12.3.tgz#ba62dffd67ee256c8c086dfae69e016cd1f198b9" integrity sha512-geUvdk7c+eizMNUDkRpW1wJwgfOiOeHbxBR/hLXK1aT6zmVSO0jsQcs7fj6MGw89jC/cjGfLcNOrtMYtGqm81g== +object-is@^1.1.5: + version "1.1.5" + resolved "https://registry.yarnpkg.com/object-is/-/object-is-1.1.5.tgz#b9deeaa5fc7f1846a0faecdceec138e5778f53ac" + integrity sha512-3cyDsyHgtmi7I7DfSSI2LDp6SK2lwvtbg0p0R1e0RvTqF5ceGx+K2dfSjm1bKDMVCFEDAQvy+o8c6a7VujOddw== + dependencies: + call-bind "^1.0.2" + define-properties "^1.1.3" + object-keys@^1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/object-keys/-/object-keys-1.1.1.tgz#1c47f272df277f3b1daf061677d9c82e2322c60e" @@ -11914,6 +12054,18 @@ set-blocking@^2.0.0, set-blocking@~2.0.0: resolved "https://registry.yarnpkg.com/set-blocking/-/set-blocking-2.0.0.tgz#045f9782d011ae9a6803ddd382b24392b3d890f7" integrity sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw== +set-function-length@^1.2.0: + version "1.2.1" + resolved "https://registry.yarnpkg.com/set-function-length/-/set-function-length-1.2.1.tgz#47cc5945f2c771e2cf261c6737cf9684a2a5e425" + integrity sha512-j4t6ccc+VsKwYHso+kElc5neZpjtq9EnRICFZtWyBsLojhmeF/ZBd/elqm22WJh/BziDe/SBiOeAt0m2mfLD0g== + dependencies: + define-data-property "^1.1.2" + es-errors "^1.3.0" + function-bind "^1.1.2" + get-intrinsic "^1.2.3" + gopd "^1.0.1" + has-property-descriptors "^1.0.1" + set-function-name@^2.0.0, set-function-name@^2.0.1: version "2.0.1" resolved "https://registry.yarnpkg.com/set-function-name/-/set-function-name-2.0.1.tgz#12ce38b7954310b9f61faa12701620a0c882793a" @@ -12273,6 +12425,13 @@ statuses@2.0.1: resolved "https://registry.yarnpkg.com/statuses/-/statuses-2.0.1.tgz#55cb000ccf1d48728bd23c685a063998cf1a1b63" integrity sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ== +stop-iteration-iterator@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/stop-iteration-iterator/-/stop-iteration-iterator-1.0.0.tgz#6a60be0b4ee757d1ed5254858ec66b10c49285e4" + integrity sha512-iCGQj+0l0HOdZ2AEeBADlsRC+vsnDsZsbdSiH1yNSjcfKM7fdpCMfqAL/dwF5BLiw/XhRft/Wax6zQbhq2BcjQ== + dependencies: + internal-slot "^1.0.4" + stream-combiner@~0.0.4: version "0.0.4" resolved "https://registry.yarnpkg.com/stream-combiner/-/stream-combiner-0.0.4.tgz#4d5e433c185261dde623ca3f44c586bcf5c4ad14" @@ -13711,6 +13870,17 @@ which-typed-array@^1.1.11, which-typed-array@^1.1.2, which-typed-array@^1.1.9: gopd "^1.0.1" has-tostringtag "^1.0.0" +which-typed-array@^1.1.13: + version "1.1.14" + resolved "https://registry.yarnpkg.com/which-typed-array/-/which-typed-array-1.1.14.tgz#1f78a111aee1e131ca66164d8bdc3ab062c95a06" + integrity sha512-VnXFiIW8yNn9kIHN88xvZ4yOWchftKDsRJ8fEPacX/wl1lOvBrhsJ/OeJCXq7B0AaijRuqgzSKalJoPk+D8MPg== + dependencies: + available-typed-arrays "^1.0.6" + call-bind "^1.0.5" + for-each "^0.3.3" + gopd "^1.0.1" + has-tostringtag "^1.0.1" + which@^1.2.9: version "1.3.1" resolved "https://registry.yarnpkg.com/which/-/which-1.3.1.tgz#a45043d54f5805316da8d62f9f50918d3da70b0a"