mirror of
https://github.com/arduino/arduino-ide.git
synced 2025-07-23 11:16:42 +00:00
fix: invalid custom board option handling in FQBN
Closes arduino/arduino-ide#1588 Signed-off-by: Akos Kitta <a.kitta@arduino.cc>
This commit is contained in:
parent
2a325a5b74
commit
a088ba99f5
@ -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",
|
||||
|
@ -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<void> {
|
||||
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<BoardsDataStore.Data> {
|
||||
@ -201,48 +229,63 @@ export class BoardsDataStore
|
||||
fqbn: string;
|
||||
selectedProgrammer: Programmer;
|
||||
}): Promise<boolean> {
|
||||
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<boolean> {
|
||||
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<boolean> {
|
||||
const { fqbn, optionsToUpdate } = params;
|
||||
if (!optionsToUpdate.length) {
|
||||
return false;
|
||||
}
|
||||
let updated = false;
|
||||
for (const value of configOption.values) {
|
||||
const mutable: Mutable<ConfigValue> = 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<ConfigValue>)
|
||||
.forEach((value) => (value.selected = false));
|
||||
const mutableConfigValue: Mutable<ConfigValue> =
|
||||
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;
|
||||
}
|
||||
|
||||
|
@ -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<BoardIdentifier> = 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<void>();
|
||||
|
||||
private readonly boardsConfigDidChangeEmitter =
|
||||
new Emitter<BoardsConfigChangeEvent>();
|
||||
new Emitter<BoardsConfigChangeEventUI>();
|
||||
readonly onBoardsConfigDidChange = this.boardsConfigDidChangeEmitter.event;
|
||||
|
||||
private readonly boardListDidChangeEmitter = new Emitter<BoardListUI>();
|
||||
@ -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();
|
||||
|
@ -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,
|
||||
};
|
||||
|
@ -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(
|
||||
|
@ -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'),
|
||||
]);
|
||||
|
@ -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.
|
||||
|
@ -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];
|
||||
}
|
||||
}
|
||||
|
@ -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,
|
||||
|
@ -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,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;
|
||||
expect(didChangeCounter).to.be.equal(1);
|
||||
@ -430,6 +442,220 @@ describe('boards-data-store', function () {
|
||||
});
|
||||
});
|
||||
|
||||
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);
|
||||
|
||||
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,
|
||||
],
|
||||
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],
|
||||
});
|
||||
});
|
||||
|
||||
it('should not select a config option if the option is absent', async () => {
|
||||
const fqbn = 'a:b:c';
|
||||
let data = await boardsDataStore.getData(fqbn);
|
||||
@ -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);
|
||||
|
176
yarn.lock
176
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"
|
||||
|
Loading…
x
Reference in New Issue
Block a user