editor: add basic intellisense

This adds basic intellisense for code completion and function signatures
using the Python `jedi` package running in a Pyodide environment.
This commit is contained in:
David Lechner
2022-06-24 19:15:32 -05:00
parent 9f0e5db307
commit 096eea7077
15 changed files with 693 additions and 15 deletions
+5
View File
@@ -2,6 +2,11 @@
# Changelog # Changelog
## [Unreleased]
### Added
- Added basic intellisense to the code editor.
## [2.0.0-beta.1] - 2022-06-03 ## [2.0.0-beta.1] - 2022-06-03
### Added ### Added
+3 -1
View File
@@ -20,7 +20,9 @@ module.exports = babelJest.createTransformer({
['@babel/plugin-transform-typescript', { ['@babel/plugin-transform-typescript', {
allowDeclareFields: true allowDeclareFields: true
}], }],
'@shopify/react-i18n/babel'], '@shopify/react-i18n/babel',
"babel-plugin-transform-import-meta",
],
presets: [ presets: [
[ [
require.resolve('babel-preset-react-app'), require.resolve('babel-preset-react-app'),
+2
View File
@@ -40,6 +40,8 @@ module.exports = function (proxy, allowedHost) {
'Access-Control-Allow-Origin': '*', 'Access-Control-Allow-Origin': '*',
'Access-Control-Allow-Methods': '*', 'Access-Control-Allow-Methods': '*',
'Access-Control-Allow-Headers': '*', 'Access-Control-Allow-Headers': '*',
'Cross-Origin-Opener-Policy': 'same-origin',
'Cross-Origin-Embedder-Policy': 'require-corp',
}, },
// Enable gzip compression of generated files. // Enable gzip compression of generated files.
compress: true, compress: true,
+2
View File
@@ -38,6 +38,7 @@
"babel-loader": "^8.2.3", "babel-loader": "^8.2.3",
"babel-plugin-macros": "^3.0.1", "babel-plugin-macros": "^3.0.1",
"babel-plugin-named-asset-import": "^0.3.8", "babel-plugin-named-asset-import": "^0.3.8",
"babel-plugin-transform-import-meta": "^2.2.0",
"babel-preset-react-app": "^10.0.1", "babel-preset-react-app": "^10.0.1",
"bfj": "^7.0.2", "bfj": "^7.0.2",
"browser-fs-access": "^0.30.1", "browser-fs-access": "^0.30.1",
@@ -78,6 +79,7 @@
"postcss-preset-env": "^7.7.2", "postcss-preset-env": "^7.7.2",
"prompts": "^2.4.2", "prompts": "^2.4.2",
"prop-types": "^15.8.1", "prop-types": "^15.8.1",
"pyodide": "0.20.1-alpha.2",
"react": "^16.13.1", "react": "^16.13.1",
"react-app-polyfill": "^3.0.0", "react-app-polyfill": "^3.0.0",
"react-aria": "^3.17.0", "react-aria": "^3.17.0",
+5
View File
@@ -3,6 +3,11 @@
import { createAction } from '../actions'; import { createAction } from '../actions';
import { UUID } from '../fileStorage'; import { UUID } from '../fileStorage';
export {
didFailToInit as editorCompletionDidFailToInit,
didInit as editorCompletionDidInit,
init as editorCompletionInit,
} from './redux/codeCompletion';
/** Action that indicates that a code editor was created. */ /** Action that indicates that a code editor was created. */
export const editorDidCreate = createAction(() => ({ export const editorDidCreate = createAction(() => ({
+2
View File
@@ -9,6 +9,7 @@ import {
editorDidCreate, editorDidCreate,
editorDidOpenFile, editorDidOpenFile,
} from './actions'; } from './actions';
import codeCompletion from './redux/codeCompletion';
/** Indicates that the code editor is ready for use. */ /** Indicates that the code editor is ready for use. */
const isReady: Reducer<boolean> = (state = false, action) => { const isReady: Reducer<boolean> = (state = false, action) => {
@@ -46,6 +47,7 @@ const openFileUuids: Reducer<readonly UUID[]> = (state = [], action) => {
}; };
export default combineReducers({ export default combineReducers({
codeCompletion,
isReady, isReady,
activeFileUuid, activeFileUuid,
openFileUuids, openFileUuids,
+38
View File
@@ -0,0 +1,38 @@
// SPDX-License-Identifier: MIT
// Copyright (c) 2022 The Pybricks Authors
import { createSlice } from '@reduxjs/toolkit';
export enum CompletionEngineStatus {
Unknown,
Loading,
Ready,
Failed,
}
type State = {
status: CompletionEngineStatus;
};
const initialState: State = {
status: CompletionEngineStatus.Unknown,
};
const slice = createSlice({
name: 'codeCompletion',
initialState,
reducers: {
init(state) {
state.status = CompletionEngineStatus.Loading;
},
didInit(state) {
state.status = CompletionEngineStatus.Ready;
},
didFailToInit(state) {
state.status = CompletionEngineStatus.Failed;
},
},
});
export const { init, didInit, didFailToInit } = slice.actions;
export default slice.reducer;
+289
View File
@@ -6,6 +6,7 @@ import { EventChannel, buffers, eventChannel } from 'redux-saga';
import { import {
SagaGenerator, SagaGenerator,
call, call,
cancelled,
delay, delay,
fork, fork,
getContext, getContext,
@@ -26,11 +27,27 @@ import {
fileStorageStoreTextFileValue, fileStorageStoreTextFileValue,
fileStorageStoreTextFileViewState, fileStorageStoreTextFileViewState,
} from '../fileStorage/actions'; } from '../fileStorage/actions';
import {
pythonMessageComplete,
pythonMessageDidComplete,
pythonMessageDidFailToComplete,
pythonMessageDidFailToGetSignature,
pythonMessageDidFailToInit,
pythonMessageDidGetSignature,
pythonMessageDidInit,
pythonMessageGetSignature,
pythonMessageInit,
pythonMessageSetInterruptBuffer,
} from '../pybricksMicropython/python-message';
import { RootState } from '../reducers'; import { RootState } from '../reducers';
import { acquireLock, defined, ensureError } from '../utils'; import { acquireLock, defined, ensureError } from '../utils';
import { createCountFunc } from '../utils/iter';
import { import {
editorActivateFile, editorActivateFile,
editorCloseFile, editorCloseFile,
editorCompletionDidFailToInit,
editorCompletionDidInit,
editorCompletionInit,
editorDidActivateFile, editorDidActivateFile,
editorDidCloseFile, editorDidCloseFile,
editorDidCreate, editorDidCreate,
@@ -373,6 +390,278 @@ function* monitorEditors(): Generator {
} }
} }
/**
* Runs a web worker with Pyodide so that we can use Jedi for intellisense.
*/
function* runJedi(): Generator {
const defer = new Array<() => void>();
try {
console.debug('creating code completion worker');
// start the web worker and set up communication channels
const worker = new Worker(
new URL('../pybricksMicropython/python-worker.ts', import.meta.url),
);
defer.push(() => worker.terminate());
const messageChannel = eventChannel<MessageEvent>((emit) => {
worker.addEventListener('message', emit);
return () => worker.removeEventListener('message', emit);
}, buffers.expanding());
defer.push(() => messageChannel.close());
const errorChannel = eventChannel<ErrorEvent>((emit) => {
worker.addEventListener('error', emit);
return () => worker.removeEventListener('error', emit);
}, buffers.expanding());
defer.push(() => errorChannel.close());
// wait for the Python runtime to start and get in a ready state
worker.postMessage(pythonMessageInit());
yield* put(editorCompletionInit());
for (;;) {
const { messageEvent, errorEvent } = yield* race({
messageEvent: take(messageChannel),
errorEvent: take(errorChannel),
});
if (errorEvent) {
yield* put(editorCompletionDidFailToInit());
throw errorEvent.error;
}
defined(messageEvent);
if (pythonMessageDidFailToInit.matches(messageEvent.data)) {
yield* put(editorCompletionDidFailToInit());
throw messageEvent.data.error;
}
if (pythonMessageDidInit.matches(messageEvent.data)) {
break;
}
}
console.debug('code completion engine is ready');
yield* put(editorCompletionDidInit());
// configure interrupts
// https://pyodide.org/en/stable/usage/keyboard-interrupts.html
// HACK: Using WebAssembly.Memory instead of SharedArrayBuffer to avoid exception.
// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/SharedArrayBuffer/Planned_changes#api_changes
const interrupt = new Uint8Array(
new WebAssembly.Memory({ initial: 1, maximum: 1, shared: true }).buffer,
);
const setInterrupt = () => {
interrupt[0] = 2; //2 === SIGINT
};
const clearInterrupt = () => {
interrupt[0] = 0;
};
// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/SharedArrayBuffer#security_requirements
if (crossOriginIsolated) {
worker.postMessage(pythonMessageSetInterruptBuffer(interrupt));
} else {
console.warn(
'required headers missing for SharedArrayBuffer, cancellation will not work',
);
}
// register intellisense hooks with editor
const nextId = createCountFunc();
const completionItemProviderChan = eventChannel<{
model: monaco.editor.ITextModel;
position: monaco.Position;
context: monaco.languages.CompletionContext;
token: monaco.CancellationToken;
resolve: (value: monaco.languages.CompletionList | null) => void;
}>((emit) => {
const subscription = monaco.languages.registerCompletionItemProvider(
pybricksMicroPythonId,
{
triggerCharacters: ['.', ' '],
provideCompletionItems(
model,
position,
context,
token,
): Promise<monaco.languages.CompletionList | null> {
return new Promise((resolve) => {
emit({ model, position, context, token, resolve });
});
},
},
);
return () => subscription.dispose();
}, buffers.expanding());
defer.push(() => completionItemProviderChan.close());
const signatureProviderChan = eventChannel<{
model: monaco.editor.ITextModel;
position: monaco.Position;
token: monaco.CancellationToken;
context: monaco.languages.SignatureHelpContext;
resolve: (value: monaco.languages.SignatureHelpResult | null) => void;
}>((emit) => {
const subscription = monaco.languages.registerSignatureHelpProvider(
pybricksMicroPythonId,
{
signatureHelpTriggerCharacters: ['('],
signatureHelpRetriggerCharacters: [','],
provideSignatureHelp(model, position, token, context) {
return new Promise((resolve) => {
emit({ model, position, token, context, resolve });
});
},
},
);
return () => subscription.dispose();
}, buffers.expanding());
defer.push(() => signatureProviderChan.close());
// Serialize requests from editor. Due to the way cancellation works, we
// can only have one pending message from the web worker at a time.
for (;;) {
const { complete, getSignature } = yield* race({
complete: take(completionItemProviderChan),
getSignature: take(signatureProviderChan),
});
if (complete) {
// for debugging
const id = nextId();
console.debug(`${id}: requested completion item`);
const subscription = complete.token.onCancellationRequested(() => {
console.debug(`${id}: requested cancelation`);
setInterrupt();
});
try {
clearInterrupt();
worker.postMessage(
pythonMessageComplete(
complete.model.getValue(),
complete.position.lineNumber,
complete.position.column,
),
);
for (;;) {
const msg = yield* take(messageChannel);
if (pythonMessageDidFailToComplete.matches(msg.data)) {
if (
msg.data.error instanceof DOMException &&
msg.data.error.name === 'AbortError'
) {
console.log(`${id} canceled`);
} else {
console.error(msg.data.error);
}
complete.resolve(null);
break;
}
if (pythonMessageDidComplete.matches(msg.data)) {
const list = JSON.parse(msg.data.completionListJson);
console.debug(list);
complete.resolve({ suggestions: list });
console.debug(`${id}: resolved: ${msg.data.type}`);
break;
}
}
} finally {
subscription.dispose();
}
} else if (getSignature) {
// for debugging
const id = nextId();
console.debug(`${id}: requested signatures`);
const subscription = getSignature.token.onCancellationRequested(() => {
console.debug(`${id}: requested cancelation`);
setInterrupt();
});
try {
clearInterrupt();
worker.postMessage(
pythonMessageGetSignature(
getSignature.model.getValue(),
getSignature.position.lineNumber,
getSignature.position.column,
),
);
for (;;) {
const msg = yield* take(messageChannel);
if (pythonMessageDidFailToGetSignature.matches(msg.data)) {
if (
msg.data.error instanceof DOMException &&
msg.data.error.name === 'AbortError'
) {
console.log(`${id} canceled`);
} else {
console.error(msg.data.error);
}
getSignature.resolve(null);
break;
}
if (pythonMessageDidGetSignature.matches(msg.data)) {
const signatures = JSON.parse(msg.data.signatureHelpJson);
console.debug(signatures);
getSignature.resolve({
value: signatures,
dispose: () => undefined,
});
console.debug(`${id}: resolved: ${msg.data.type}`);
break;
}
}
} finally {
subscription.dispose();
}
}
}
} catch (err) {
const isCancelled = yield* cancelled();
if (!isCancelled) {
console.error(err);
}
} finally {
defer.forEach((item) => item());
}
}
export default function* (): Generator { export default function* (): Generator {
yield* fork(monitorEditors); yield* fork(monitorEditors);
yield* fork(runJedi);
} }
+101
View File
@@ -0,0 +1,101 @@
// SPDX-License-Identifier: MIT
// Copyright (c) 2022 The Pybricks Authors
import { createAction } from '../actions';
// NB: although we are using the same action creator as we do for redux, these
// actions are not used by redux but rather are to sent between workers.
/**
* Message sent from main to work to request initialization of Pyodide.
*/
export const pythonMessageInit = createAction(() => ({
type: 'python.message.init',
}));
/**
* Message sent from worker to main that indicates {@link pythonMessageInit}
* succeeded.
*/
export const pythonMessageDidInit = createAction(() => ({
type: 'python.message.didInit',
}));
/**
* Message sent from worker to main that indicates {@link pythonMessageInit}
* failed.
*/
export const pythonMessageDidFailToInit = createAction((error: Error) => ({
type: 'python.message.didFailToInit',
error,
}));
/**
* Message sent from main to worker to set the shared interrupt buffer.
*/
export const pythonMessageSetInterruptBuffer = createAction((buffer: Uint8Array) => ({
type: 'python.message.setInterruptBuffer',
buffer,
}));
/**
* Message sent from main to worker to request code completion.
*/
export const pythonMessageComplete = createAction(
(code: string, lineNumber: number, column: number) => ({
type: 'python.message.complete',
code,
lineNumber,
column,
}),
);
/**
* Message sent from worker to main that indicates {@link pythonMessageComplete}
* succeeded.
*/
export const pythonMessageDidComplete = createAction((completionListJson: string) => ({
type: 'python.message.didComplete',
completionListJson,
}));
/**
* Message sent from worker to main that indicates {@link pythonMessageComplete}
* failed.
*/
export const pythonMessageDidFailToComplete = createAction((error: Error) => ({
type: 'python.message.didFailToComplete',
error,
}));
/**
* Message sent from main to worker to request function signature.
*/
export const pythonMessageGetSignature = createAction(
(code: string, lineNumber: number, column: number) => ({
type: 'python.message.getSignature',
code,
lineNumber,
column,
}),
);
/**
* Message sent from worker to main that indicates {@link pythonMessageGetSignature}
* succeeded.
*/
export const pythonMessageDidGetSignature = createAction(
(signatureHelpJson: string) => ({
type: 'python.message.didGetSignature',
signatureHelpJson,
}),
);
/**
* Message sent from worker to main that indicates {@link pythonMessageGetSignature}
* failed.
*/
export const pythonMessageDidFailToGetSignature = createAction((error: Error) => ({
type: 'python.message.didFailToGetSignature',
error,
}));
+115
View File
@@ -0,0 +1,115 @@
// SPDX-License-Identifier: MIT
// Copyright (c) 2022 The Pybricks Authors
// This file runs as a web worker.
// NB: We need to be very careful about imports here since many libraries for
// web aren't compatible with web workers!
import type { loadPyodide as loadPyodideFunc } from 'pyodide';
import { ensureError } from '../utils';
import {
pythonMessageComplete,
pythonMessageDidComplete,
pythonMessageDidFailToComplete,
pythonMessageDidFailToGetSignature,
pythonMessageDidFailToInit,
pythonMessageDidGetSignature,
pythonMessageDidInit,
pythonMessageGetSignature,
pythonMessageInit,
pythonMessageSetInterruptBuffer,
} from './python-message';
importScripts('https://cdn.jsdelivr.net/pyodide/v0.20.0/full/pyodide.js');
declare const loadPyodide: typeof loadPyodideFunc;
/**
* Wrapper around {@link ensureError} that also converts KeyboardInterrupt to
* AbortError.
* @param err The value from the catch clause.
* @returns The fixed up error.
*/
function fixUpError(err: unknown): Error {
const error = ensureError(err);
if (
error.constructor.name === 'PythonError' &&
error.message.match(/KeyboardInterrupt/)
) {
return new DOMException('cancelled', 'AbortError');
}
return error;
}
const setUpPythonEnvironment = `
import jedi
import micropip
print('loading pybricks...')
await micropip.install('pybricks-jedi')
print('loaded pybricks.')
import pybricks_jedi
print('preloading...')
pybricks_jedi.initialize()
print('preloading done.')
`;
async function init(): Promise<void> {
console.log('starting Pyodide...');
const pyodide = await loadPyodide();
await pyodide.loadPackage(['micropip', 'jedi']);
await pyodide.runPythonAsync(setUpPythonEnvironment);
const complete = pyodide.runPython('pybricks_jedi.complete');
const getSignatures = pyodide.runPython('pybricks_jedi.get_signatures');
self.addEventListener('message', async (e) => {
if (pythonMessageSetInterruptBuffer.matches(e.data)) {
pyodide.setInterruptBuffer(e.data.buffer);
return;
}
if (pythonMessageComplete.matches(e.data)) {
console.debug('worker received complete message');
try {
const { code, lineNumber, column } = e.data;
const list = complete(code, lineNumber, column);
self.postMessage(pythonMessageDidComplete(list));
} catch (err) {
self.postMessage(pythonMessageDidFailToComplete(fixUpError(err)));
}
return;
}
if (pythonMessageGetSignature.matches(e.data)) {
console.debug('worker received getSignatures message');
try {
const { code, lineNumber, column } = e.data;
const list = getSignatures(code, lineNumber, column);
self.postMessage(pythonMessageDidGetSignature(list));
} catch (err) {
self.postMessage(pythonMessageDidFailToGetSignature(fixUpError(err)));
}
return;
}
});
console.log('Pyodide is ready.');
}
self.addEventListener('message', async (e) => {
if (pythonMessageInit.matches(e.data)) {
try {
await init();
postMessage(pythonMessageDidInit());
} catch (err) {
postMessage(pythonMessageDidFailToInit(ensureError(err)));
}
}
});
+63 -8
View File
@@ -1,10 +1,18 @@
// SPDX-License-Identifier: MIT // SPDX-License-Identifier: MIT
// Copyright (c) 2020-2022 The Pybricks Authors // Copyright (c) 2020-2022 The Pybricks Authors
import { Button, Intent, ProgressBar } from '@blueprintjs/core'; import {
Button,
Icon,
IconSize,
Intent,
ProgressBar,
Spinner,
} from '@blueprintjs/core';
import { Classes as Classes2, Popover2, Popover2Props } from '@blueprintjs/popover2'; import { Classes as Classes2, Popover2, Popover2Props } from '@blueprintjs/popover2';
import React from 'react'; import React, { useMemo } from 'react';
import { BleConnectionState } from '../ble/reducers'; import { BleConnectionState } from '../ble/reducers';
import { CompletionEngineStatus } from '../editor/redux/codeCompletion';
import { useSelector } from '../reducers'; import { useSelector } from '../reducers';
import { I18nId, useI18n } from './i18n'; import { I18nId, useI18n } from './i18n';
@@ -15,6 +23,48 @@ const commonPopoverProps: Partial<Popover2Props> = {
placement: 'top', placement: 'top',
}; };
const CompletionEngineIndicator: React.VoidFunctionComponent = () => {
const { status } = useSelector((s) => s.editor.codeCompletion);
const i18n = useI18n();
const icon = useMemo(() => {
switch (status) {
case CompletionEngineStatus.Loading:
return <Spinner size={IconSize.STANDARD} />;
case CompletionEngineStatus.Ready:
return <Icon icon="tick-circle" />;
case CompletionEngineStatus.Failed:
return <Icon icon="error" />;
default:
return <Icon icon="disable" />;
}
}, [status]);
const message = useMemo(() => {
switch (status) {
case CompletionEngineStatus.Loading:
return i18n.translate(I18nId.CompletionEngineStatusMessageLoading);
case CompletionEngineStatus.Ready:
return i18n.translate(I18nId.CompletionEngineStatusMessageReady);
case CompletionEngineStatus.Failed:
return i18n.translate(I18nId.CompletionEngineStatusMessageFailed);
default:
return i18n.translate(I18nId.CompletionEngineStatusMessageUnknown);
}
}, [status, i18n]);
return (
<Popover2 {...commonPopoverProps} content={message}>
<div
aria-label={i18n.translate(I18nId.CompletionEngineStatusLabel)}
style={{ cursor: 'pointer' }}
>
{icon}
</div>
</Popover2>
);
};
const HubInfoButton: React.VoidFunctionComponent = () => { const HubInfoButton: React.VoidFunctionComponent = () => {
const i18n = useI18n(); const i18n = useI18n();
const deviceName = useSelector((s) => s.ble.deviceName); const deviceName = useSelector((s) => s.ble.deviceName);
@@ -99,12 +149,17 @@ const StatusBar: React.VFC = (_props) => {
return ( return (
<div className="pb-status-bar" role="status" aria-live="off"> <div className="pb-status-bar" role="status" aria-live="off">
{connection === BleConnectionState.Connected && ( <div className="pb-status-bar-group">
<> <CompletionEngineIndicator />
<HubInfoButton /> </div>
<BatteryIndicator /> <div className="pb-status-bar-group">
</> {connection === BleConnectionState.Connected && (
)} <>
<HubInfoButton />
<BatteryIndicator />
</>
)}
</div>
</div> </div>
); );
}; };
+5
View File
@@ -12,6 +12,11 @@ export function useI18n(): I18n {
} }
export enum I18nId { export enum I18nId {
CompletionEngineStatusLabel = 'completionEngineStatus.label',
CompletionEngineStatusMessageLoading = 'completionEngineStatus.message.loading',
CompletionEngineStatusMessageReady = 'completionEngineStatus.message.ready',
CompletionEngineStatusMessageFailed = 'completionEngineStatus.message.failed',
CompletionEngineStatusMessageUnknown = 'completionEngineStatus.message.unknown',
BatteryTitle = 'battery.title', BatteryTitle = 'battery.title',
BatteryLow = 'battery.low', BatteryLow = 'battery.low',
BatteryOk = 'battery.ok', BatteryOk = 'battery.ok',
+5 -4
View File
@@ -15,11 +15,12 @@
padding-inline: 5px; padding-inline: 5px;
display: flex; display: flex;
align-items: center; align-items: center;
} justify-content: space-between;
// make status bar items right-aligned &-group {
.pb-status-bar :first-child { display: flex;
margin-left: auto; align-items: center;
}
} }
.pb-battery-indicator { .pb-battery-indicator {
+9
View File
@@ -1,4 +1,13 @@
{ {
"completionEngineStatus": {
"label": "Code completion engine status indicator",
"message": {
"loading": "Code completion engine is starting up and will be ready soon.",
"ready": "Code completion engine is ready to use.",
"failed": "Code completion engine has stopped working.",
"unknown": "Code completion engine has not started yet."
}
},
"hubInfo": { "hubInfo": {
"title": "Connected hub", "title": "Connected hub",
"connectedTo": "Connected to:", "connectedTo": "Connected to:",
+49 -2
View File
@@ -1451,7 +1451,7 @@ __metadata:
languageName: node languageName: node
linkType: hard linkType: hard
"@babel/template@npm:^7.0.0, @babel/template@npm:^7.16.7, @babel/template@npm:^7.3.3": "@babel/template@npm:^7.0.0, @babel/template@npm:^7.16.7, @babel/template@npm:^7.3.3, @babel/template@npm:^7.4.4":
version: 7.16.7 version: 7.16.7
resolution: "@babel/template@npm:7.16.7" resolution: "@babel/template@npm:7.16.7"
dependencies: dependencies:
@@ -2319,6 +2319,7 @@ __metadata:
babel-loader: ^8.2.3 babel-loader: ^8.2.3
babel-plugin-macros: ^3.0.1 babel-plugin-macros: ^3.0.1
babel-plugin-named-asset-import: ^0.3.8 babel-plugin-named-asset-import: ^0.3.8
babel-plugin-transform-import-meta: ^2.2.0
babel-preset-react-app: ^10.0.1 babel-preset-react-app: ^10.0.1
bfj: ^7.0.2 bfj: ^7.0.2
browser-fs-access: ^0.30.1 browser-fs-access: ^0.30.1
@@ -2370,6 +2371,7 @@ __metadata:
prettier: ^2.7.1 prettier: ^2.7.1
prompts: ^2.4.2 prompts: ^2.4.2
prop-types: ^15.8.1 prop-types: ^15.8.1
pyodide: 0.20.1-alpha.2
react: ^16.13.1 react: ^16.13.1
react-app-polyfill: ^3.0.0 react-app-polyfill: ^3.0.0
react-aria: ^3.17.0 react-aria: ^3.17.0
@@ -5662,6 +5664,18 @@ __metadata:
languageName: node languageName: node
linkType: hard linkType: hard
"babel-plugin-transform-import-meta@npm:^2.2.0":
version: 2.2.0
resolution: "babel-plugin-transform-import-meta@npm:2.2.0"
dependencies:
"@babel/template": ^7.4.4
tslib: ^2.4.0
peerDependencies:
"@babel/core": ^7.10.0
checksum: 305b0750f957e4df3ce08e0b24359dc55fb789c96477e944d113d259a8292e7bb8d665944e04b5a690c82a8dc23f54b711a85917d80988dffe1e046e1db940ee
languageName: node
linkType: hard
"babel-plugin-transform-react-remove-prop-types@npm:^0.4.24": "babel-plugin-transform-react-remove-prop-types@npm:^0.4.24":
version: 0.4.24 version: 0.4.24
resolution: "babel-plugin-transform-react-remove-prop-types@npm:0.4.24" resolution: "babel-plugin-transform-react-remove-prop-types@npm:0.4.24"
@@ -5734,6 +5748,13 @@ __metadata:
languageName: node languageName: node
linkType: hard linkType: hard
"base-64@npm:^1.0.0":
version: 1.0.0
resolution: "base-64@npm:1.0.0"
checksum: d10b64a1fc9b2c5a5f39f1ce1e6c9d1c5b249222bbfa3a0604c592d90623caf74419983feadd8a170f27dc0c3389704f72faafa3e645aeb56bfc030c93ff074a
languageName: node
linkType: hard
"base64-arraybuffer-es6@npm:^0.7.0": "base64-arraybuffer-es6@npm:^0.7.0":
version: 0.7.0 version: 0.7.0
resolution: "base64-arraybuffer-es6@npm:0.7.0" resolution: "base64-arraybuffer-es6@npm:0.7.0"
@@ -10785,7 +10806,7 @@ __metadata:
languageName: node languageName: node
linkType: hard linkType: hard
"node-fetch@npm:^2.6.5": "node-fetch@npm:^2.6.1, node-fetch@npm:^2.6.5":
version: 2.6.7 version: 2.6.7
resolution: "node-fetch@npm:2.6.7" resolution: "node-fetch@npm:2.6.7"
dependencies: dependencies:
@@ -12363,6 +12384,17 @@ __metadata:
languageName: node languageName: node
linkType: hard linkType: hard
"pyodide@npm:0.20.1-alpha.2":
version: 0.20.1-alpha.2
resolution: "pyodide@npm:0.20.1-alpha.2"
dependencies:
base-64: ^1.0.0
node-fetch: ^2.6.1
ws: ^8.5.0
checksum: aeadfd5822660b435561c7f010f1a029b28bfa6080a781a7fbb2fe95b02ca2ea95f234d3c79a5f750c71a6eb3b10872731814805b82be5cbb1a2da2a6386e65f
languageName: node
linkType: hard
"qs@npm:6.10.3": "qs@npm:6.10.3":
version: 6.10.3 version: 6.10.3
resolution: "qs@npm:6.10.3" resolution: "qs@npm:6.10.3"
@@ -15345,6 +15377,21 @@ __metadata:
languageName: node languageName: node
linkType: hard linkType: hard
"ws@npm:^8.5.0":
version: 8.8.0
resolution: "ws@npm:8.8.0"
peerDependencies:
bufferutil: ^4.0.1
utf-8-validate: ^5.0.2
peerDependenciesMeta:
bufferutil:
optional: true
utf-8-validate:
optional: true
checksum: 6ceed1ca1cb800ef60c7fc8346c7d5d73d73be754228eb958765abf5d714519338efa20ffe674167039486eb3a813aae5a497f8d319e16b4d96216a31df5bd95
languageName: node
linkType: hard
"xml-name-validator@npm:^4.0.0": "xml-name-validator@npm:^4.0.0":
version: 4.0.0 version: 4.0.0
resolution: "xml-name-validator@npm:4.0.0" resolution: "xml-name-validator@npm:4.0.0"