rdesktop
filip mertens 1 year ago
parent 892915b138
commit ac28db3a52
  1. 2
      apps/1test/src/electron/engine.ts
  2. 15
      apps/1test/src/electron/fsPlugin.ts
  3. 15
      apps/1test/src/electron/gitPlugin.ts
  4. 27
      apps/1test/src/electron/lib/electronBasePlugin.ts
  5. 15
      apps/1test/src/electron/xtermPlugin.ts
  6. 2
      apps/1test/src/index.ts
  7. 10
      apps/1test/src/remix/lib/electronPlugin.ts
  8. 2
      apps/1test/src/renderer.ts
  9. 9
      apps/remix-ide/contracts/.deps/remix-tests/remix_accounts.sol
  10. 225
      apps/remix-ide/contracts/.deps/remix-tests/remix_tests.sol
  11. 21
      apps/remix-ide/src/app/plugins/fsPlugin.ts
  12. 15
      apps/remixdesktop/src/engine.ts
  13. 95
      apps/remixdesktop/src/fsPlugin.ts
  14. 31
      apps/remixdesktop/src/gitPlugin.ts
  15. 25
      apps/remixdesktop/src/preload.ts
  16. 37
      apps/remixdesktop/src/xtermPlugin.ts
  17. 2
      libs/remix-ui/home-tab/src/lib/components/homeTabFeaturedPlugins.tsx
  18. 1
      libs/remix-ui/search/src/lib/context/context.tsx
  19. 14
      libs/remix-ui/workspace/src/lib/actions/index.ts
  20. 18
      package.json
  21. 134
      yarn.lock

@ -23,7 +23,7 @@ appManager.activatePlugin('xterm')
ipcMain.handle('manager:activatePlugin', async (event, plugin) => { ipcMain.handle('manager:activatePlugin', async (event, plugin) => {
console.log('manager:activatePlugin', plugin, event.sender.id) console.log('manager:activatePlugin', plugin, event.sender.id)
appManager.call(plugin, 'createClient', event.sender.id) return await appManager.call(plugin, 'createClient', event.sender.id)
}) })
ipcMain.handle('getWebContentsID', (event, message) => { ipcMain.handle('getWebContentsID', (event, message) => {

@ -14,19 +14,10 @@ const profile: Profile = {
export class FSPlugin extends ElectronBasePlugin { export class FSPlugin extends ElectronBasePlugin {
clients: FSPluginClient[] = [] clients: FSPluginClient[] = []
constructor() { constructor() {
super(profile) super(profile, clientProfile, FSPluginClient)
this.methods = [...super.methods, 'closeWatch'] this.methods = [...super.methods, 'closeWatch']
} }
async createClient(webContentsId: number): Promise<void> {
this.clients.push(new FSPluginClient(webContentsId))
}
async closeClient(webContentsId: number): Promise<void> {
console.log('closeClient', webContentsId)
}
async closeWatch(): Promise<void> { async closeWatch(): Promise<void> {
for (const client of this.clients) { for (const client of this.clients) {
await client.closeWatch() await client.closeWatch()
@ -46,8 +37,8 @@ class FSPluginClient extends ElectronBasePluginClient {
watcher: chokidar.FSWatcher watcher: chokidar.FSWatcher
workingDir: string = '/Volumes/bunsen/code/rmproject2/remix-project/apps/remix-ide/contracts/' workingDir: string = '/Volumes/bunsen/code/rmproject2/remix-project/apps/remix-ide/contracts/'
constructor(webContentsId: number) { constructor(webContentsId: number, profile: Profile) {
super(webContentsId, clientProfile) super(webContentsId, profile)
this.onload(() => { this.onload(() => {
console.log('fsPluginClient onload') console.log('fsPluginClient onload')
}) })

@ -12,18 +12,9 @@ const profile: Profile = {
export class GitPlugin extends ElectronBasePlugin { export class GitPlugin extends ElectronBasePlugin {
client: PluginClient client: PluginClient
constructor() { constructor() {
super(profile) super(profile, clientProfile, GitPluginClient)
} }
async createClient(webContentsId: number): Promise<void> {
this.clients.push(new GitPluginClient(webContentsId))
}
async closeClient(webContentsId: number): Promise<void> {
console.log('closeClient', webContentsId)
}
} }
const clientProfile: Profile = { const clientProfile: Profile = {
@ -35,8 +26,8 @@ const clientProfile: Profile = {
class GitPluginClient extends ElectronBasePluginClient { class GitPluginClient extends ElectronBasePluginClient {
constructor(webContentsId: number) { constructor(webContentsId: number, profile: Profile) {
super(webContentsId, clientProfile) super(webContentsId, profile)
this.onload(() => { this.onload(() => {
console.log('GitPluginClient onload') console.log('GitPluginClient onload')
}) })

@ -5,22 +5,34 @@ import { BrowserWindow } from "electron";
import { createElectronClient } from "./electronPluginClient"; import { createElectronClient } from "./electronPluginClient";
export interface ElectronBasePluginInterface { export interface ElectronBasePluginInterface {
createClient(windowId: number): Promise<void>; createClient(windowId: number): Promise<boolean>;
closeClient(windowId: number): Promise<void>; closeClient(windowId: number): Promise<boolean>;
} }
export abstract class ElectronBasePlugin extends Plugin implements ElectronBasePluginInterface { export abstract class ElectronBasePlugin extends Plugin implements ElectronBasePluginInterface {
clients: ElectronBasePluginClient[] = []; clients: ElectronBasePluginClient[] = [];
constructor(profile: Profile) { clientClass: any
clientProfile: Profile
constructor(profile: Profile, clientProfile: Profile, clientClass: any) {
super(profile); super(profile);
this.methods = ['createClient', 'closeClient']; this.methods = ['createClient', 'closeClient'];
this.clientClass = clientClass;
this.clientProfile = clientProfile;
} }
async createClient(windowId: number): Promise<void> { async createClient(webContentsId: number): Promise<boolean> {
console.log('createClient method not implemented'); if (this.clients.find(client => client.webContentsId === webContentsId)) return true
const client = new this.clientClass(webContentsId, this.clientProfile);
this.clients.push(client);
return new Promise((resolve, reject) => {
client.onload(() => {
resolve(true)
})
})
} }
async closeClient(windowId: number): Promise<void> { async closeClient(windowId: number): Promise<boolean> {
console.log('closeClient method not implemented'); this.clients = this.clients.filter(client => client.webContentsId !== windowId)
return true;
} }
} }
@ -29,7 +41,6 @@ export class ElectronBasePluginClient extends PluginClient {
webContentsId: number; webContentsId: number;
constructor(webcontentsid: number, profile: Profile, methods: string[] = []) { constructor(webcontentsid: number, profile: Profile, methods: string[] = []) {
super(); super();
console.log('ElectronBasePluginClient', profile);
this.methods = profile.methods; this.methods = profile.methods;
this.webContentsId = webcontentsid; this.webContentsId = webcontentsid;
BrowserWindow.getAllWindows().forEach((window) => { BrowserWindow.getAllWindows().forEach((window) => {

@ -17,16 +17,7 @@ const profile: Profile = {
export class XtermPlugin extends ElectronBasePlugin { export class XtermPlugin extends ElectronBasePlugin {
client: PluginClient client: PluginClient
constructor() { constructor() {
super(profile) super(profile, clientProfile, XtermPluginClient)
}
async createClient(webContentsId: number): Promise<void> {
console.log('createClient', webContentsId)
this.clients.push(new XtermPluginClient(webContentsId))
}
async closeClient(webContentsId: number): Promise<void> {
console.log('closeClient', webContentsId)
} }
} }
@ -41,8 +32,8 @@ const clientProfile: Profile = {
class XtermPluginClient extends ElectronBasePluginClient { class XtermPluginClient extends ElectronBasePluginClient {
terminals: pty.IPty[] = [] terminals: pty.IPty[] = []
constructor(webContentsId: number) { constructor(webContentsId: number, profile: Profile) {
super(webContentsId, clientProfile) super(webContentsId, profile)
this.onload(() => { this.onload(() => {
console.log('XtermPluginClient onload') console.log('XtermPluginClient onload')
}) })

@ -33,7 +33,7 @@ export const createWindow = (): void => {
mainWindow.loadURL(MAIN_WINDOW_WEBPACK_ENTRY); mainWindow.loadURL(MAIN_WINDOW_WEBPACK_ENTRY);
mainWindow.maximize(); mainWindow.maximize();
// Open the DevTools. // Open the DevTools.
//mainWindow.webContents.openDevTools(); mainWindow.webContents.openDevTools();
BrowserWindow.getAllWindows().forEach(window => { BrowserWindow.getAllWindows().forEach(window => {
console.log('window IDS created', window.webContents.id) console.log('window IDS created', window.webContents.id)

@ -47,9 +47,16 @@ export abstract class ElectronPlugin extends Plugin {
* @param name The name of the plugin should connect to * @param name The name of the plugin should connect to
*/ */
protected async connect(name: string) { protected async connect(name: string) {
console.log('ElectronPluginConnector connect', name)
const r = await window.electronAPI.activatePlugin(name)
console.log('ElectronPluginConnector is connected', name, r)
/*
if(await window.electronAPI.activatePlugin(name) && !this.loaded){ if(await window.electronAPI.activatePlugin(name) && !this.loaded){
console.log('ElectronPluginConnector calling handshake', name)
this.handshake() this.handshake()
} }
*/
} }
/** Close connection with the plugin */ /** Close connection with the plugin */
protected disconnect(): any | Promise<any> { protected disconnect(): any | Promise<any> {
@ -82,6 +89,7 @@ export abstract class ElectronPlugin extends Plugin {
/** Perform handshake with the client if not loaded yet */ /** Perform handshake with the client if not loaded yet */
protected async handshake() { protected async handshake() {
console.log('ElectronPluginConnector handshake', this.loaded)
if (!this.loaded) { if (!this.loaded) {
this.loaded = true this.loaded = true
let methods: string[]; let methods: string[];
@ -109,6 +117,7 @@ export abstract class ElectronPlugin extends Plugin {
protected async getMessage(message: Message) { protected async getMessage(message: Message) {
// Check for handshake request from the client // Check for handshake request from the client
if (message.action === 'request' && message.key === 'handshake') { if (message.action === 'request' && message.key === 'handshake') {
console.log('ElectronPluginConnector getMessage handshake', message)
return this.handshake() return this.handshake()
} }
@ -161,6 +170,7 @@ export abstract class ElectronPlugin extends Plugin {
// Return result from exposed method // Return result from exposed method
case 'response': { case 'response': {
const { id, payload, error } = message const { id, payload, error } = message
console.log('ElectronPluginConnector getMessage response', message, this.pendingRequest)
this.pendingRequest[id](payload, error) this.pendingRequest[id](payload, error)
delete this.pendingRequest[id] delete this.pendingRequest[id]
break break

@ -11,6 +11,8 @@ class MyAppManager extends PluginManager {
onActivation(): void { onActivation(): void {
this.on('fs', 'loaded', async () => { this.on('fs', 'loaded', async () => {
console.log('fs loaded') console.log('fs loaded')
const files = await this.call('fs', 'readdir', './')
console.log('files', files)
}) })
/* /*
this.on('fs', 'loaded', async () => { this.on('fs', 'loaded', async () => {

@ -0,0 +1,9 @@
// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.4.22 <0.9.0;
library TestsAccounts {
function getAccount(uint index) pure public returns (address) {
return address(0);
}
}

@ -0,0 +1,225 @@
// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.4.22 <0.9.0;
library Assert {
event AssertionEvent(
bool passed,
string message,
string methodName
);
event AssertionEventUint(
bool passed,
string message,
string methodName,
uint256 returned,
uint256 expected
);
event AssertionEventInt(
bool passed,
string message,
string methodName,
int256 returned,
int256 expected
);
event AssertionEventBool(
bool passed,
string message,
string methodName,
bool returned,
bool expected
);
event AssertionEventAddress(
bool passed,
string message,
string methodName,
address returned,
address expected
);
event AssertionEventBytes32(
bool passed,
string message,
string methodName,
bytes32 returned,
bytes32 expected
);
event AssertionEventString(
bool passed,
string message,
string methodName,
string returned,
string expected
);
event AssertionEventUintInt(
bool passed,
string message,
string methodName,
uint256 returned,
int256 expected
);
event AssertionEventIntUint(
bool passed,
string message,
string methodName,
int256 returned,
uint256 expected
);
function ok(bool a, string memory message) public returns (bool result) {
result = a;
emit AssertionEvent(result, message, "ok");
}
function equal(uint256 a, uint256 b, string memory message) public returns (bool result) {
result = (a == b);
emit AssertionEventUint(result, message, "equal", a, b);
}
function equal(int256 a, int256 b, string memory message) public returns (bool result) {
result = (a == b);
emit AssertionEventInt(result, message, "equal", a, b);
}
function equal(bool a, bool b, string memory message) public returns (bool result) {
result = (a == b);
emit AssertionEventBool(result, message, "equal", a, b);
}
// TODO: only for certain versions of solc
//function equal(fixed a, fixed b, string message) public returns (bool result) {
// result = (a == b);
// emit AssertionEvent(result, message);
//}
// TODO: only for certain versions of solc
//function equal(ufixed a, ufixed b, string message) public returns (bool result) {
// result = (a == b);
// emit AssertionEvent(result, message);
//}
function equal(address a, address b, string memory message) public returns (bool result) {
result = (a == b);
emit AssertionEventAddress(result, message, "equal", a, b);
}
function equal(bytes32 a, bytes32 b, string memory message) public returns (bool result) {
result = (a == b);
emit AssertionEventBytes32(result, message, "equal", a, b);
}
function equal(string memory a, string memory b, string memory message) public returns (bool result) {
result = (keccak256(abi.encodePacked(a)) == keccak256(abi.encodePacked(b)));
emit AssertionEventString(result, message, "equal", a, b);
}
function notEqual(uint256 a, uint256 b, string memory message) public returns (bool result) {
result = (a != b);
emit AssertionEventUint(result, message, "notEqual", a, b);
}
function notEqual(int256 a, int256 b, string memory message) public returns (bool result) {
result = (a != b);
emit AssertionEventInt(result, message, "notEqual", a, b);
}
function notEqual(bool a, bool b, string memory message) public returns (bool result) {
result = (a != b);
emit AssertionEventBool(result, message, "notEqual", a, b);
}
// TODO: only for certain versions of solc
//function notEqual(fixed a, fixed b, string message) public returns (bool result) {
// result = (a != b);
// emit AssertionEvent(result, message);
//}
// TODO: only for certain versions of solc
//function notEqual(ufixed a, ufixed b, string message) public returns (bool result) {
// result = (a != b);
// emit AssertionEvent(result, message);
//}
function notEqual(address a, address b, string memory message) public returns (bool result) {
result = (a != b);
emit AssertionEventAddress(result, message, "notEqual", a, b);
}
function notEqual(bytes32 a, bytes32 b, string memory message) public returns (bool result) {
result = (a != b);
emit AssertionEventBytes32(result, message, "notEqual", a, b);
}
function notEqual(string memory a, string memory b, string memory message) public returns (bool result) {
result = (keccak256(abi.encodePacked(a)) != keccak256(abi.encodePacked(b)));
emit AssertionEventString(result, message, "notEqual", a, b);
}
/*----------------- Greater than --------------------*/
function greaterThan(uint256 a, uint256 b, string memory message) public returns (bool result) {
result = (a > b);
emit AssertionEventUint(result, message, "greaterThan", a, b);
}
function greaterThan(int256 a, int256 b, string memory message) public returns (bool result) {
result = (a > b);
emit AssertionEventInt(result, message, "greaterThan", a, b);
}
// TODO: safely compare between uint and int
function greaterThan(uint256 a, int256 b, string memory message) public returns (bool result) {
if(b < int(0)) {
// int is negative uint "a" always greater
result = true;
} else {
result = (a > uint(b));
}
emit AssertionEventUintInt(result, message, "greaterThan", a, b);
}
function greaterThan(int256 a, uint256 b, string memory message) public returns (bool result) {
if(a < int(0)) {
// int is negative uint "b" always greater
result = false;
} else {
result = (uint(a) > b);
}
emit AssertionEventIntUint(result, message, "greaterThan", a, b);
}
/*----------------- Lesser than --------------------*/
function lesserThan(uint256 a, uint256 b, string memory message) public returns (bool result) {
result = (a < b);
emit AssertionEventUint(result, message, "lesserThan", a, b);
}
function lesserThan(int256 a, int256 b, string memory message) public returns (bool result) {
result = (a < b);
emit AssertionEventInt(result, message, "lesserThan", a, b);
}
// TODO: safely compare between uint and int
function lesserThan(uint256 a, int256 b, string memory message) public returns (bool result) {
if(b < int(0)) {
// int is negative int "b" always lesser
result = false;
} else {
result = (a < uint(b));
}
emit AssertionEventUintInt(result, message, "lesserThan", a, b);
}
function lesserThan(int256 a, uint256 b, string memory message) public returns (bool result) {
if(a < int(0)) {
// int is negative int "a" always lesser
result = true;
} else {
result = (uint(a) < b);
}
emit AssertionEventIntUint(result, message, "lesserThan", a, b);
}
}

@ -1,26 +1,33 @@
import { ElectronPlugin } from '@remixproject/engine-electron'; import { ElectronPlugin } from '@remixproject/engine-electron';
let workingDir = '/Volumes/bunsen/code/rmproject2/remix-project/apps/remix-ide/contracts/'
const fixPath = (path: string) => { const fixPath = (path: string) => {
const workingDir = '/Volumes/bunsen/code/rmproject2/remix-project/apps/remix-ide/contracts/' /*
// if it starts with /, it's an absolute path remove it // if it starts with /, it's an absolute path remove it
if (path.startsWith('/')) { if (path.startsWith('/')) {
path = path.slice(1) path = path.slice(1)
} }
path = workingDir + path path = workingDir + path
*/
return path return path
} }
export class fsPlugin extends ElectronPlugin { export class fsPlugin extends ElectronPlugin {
public fs: any public fs: any
constructor() { constructor() {
super({ super({
displayName: 'fs', displayName: 'fs',
name: 'fs', name: 'fs',
description: 'fs', description: 'fs',
}) })
this.methods = ['readdir', 'readFile', 'writeFile', 'mkdir', 'rmdir', 'unlink', 'rename', 'stat', 'exists'] this.methods = ['readdir', 'readFile', 'writeFile', 'mkdir', 'rmdir', 'unlink', 'rename', 'stat', 'exists', 'setWorkingDir']
this.fs = { this.fs = {
exists: async (path: string) => { exists: async (path: string) => {
@ -35,7 +42,9 @@ export class fsPlugin extends ElectronPlugin {
}, },
readdir: async (path: string) => { readdir: async (path: string) => {
path = fixPath(path) path = fixPath(path)
console.log('readdir', path)
const files = await this.call('fs', 'readdir', path) const files = await this.call('fs', 'readdir', path)
console.log('readdir', path, files)
return files return files
}, },
unlink: async (path: string) => { unlink: async (path: string) => {
@ -71,13 +80,19 @@ export class fsPlugin extends ElectronPlugin {
} }
}
}
async onActivation() { async onActivation() {
console.log('fsPluginClient onload', this.fs); console.log('fsPluginClient onload', this.fs);
(window as any).remixFileSystem = this.fs (window as any).remixFileSystem = this.fs
this.on('fs', 'workingDirChanged', (path: string) => {
console.log('change working dir', path)
workingDir = path
})
} }
} }

@ -15,12 +15,15 @@ engine.register(fsPlugin)
engine.register(gitPlugin) engine.register(gitPlugin)
engine.register(xtermPlugin) engine.register(xtermPlugin)
ipcMain.handle('manager:activatePlugin', async (event, arg) => { appManager.activatePlugin('fs')
console.log('manager:activatePlugin', arg)
if(await appManager.isActive(arg)){ ipcMain.handle('manager:activatePlugin', async (event, plugin) => {
return true console.log('manager:activatePlugin', plugin, event.sender.id)
} return await appManager.call(plugin, 'createClient', event.sender.id)
return await appManager.activatePlugin(arg) })
ipcMain.handle('getWebContentsID', (event, message) => {
return event.sender.id
}) })
app.on('before-quit', async () => { app.on('before-quit', async () => {

@ -1,76 +1,86 @@
import { PluginClient } from "@remixproject/plugin"; import { ElectronBasePlugin, ElectronBasePluginClient } from "@remixproject/plugin-electron"
import { createElectronClient } from "@remixproject/plugin-electron"
import { Plugin } from '@remixproject/engine';
import fs from 'fs/promises' import fs from 'fs/promises'
import { Stats } from "fs";
import { Profile } from "@remixproject/plugin-utils"; import { Profile } from "@remixproject/plugin-utils";
import chokidar from 'chokidar' import chokidar from 'chokidar'
import { mainWindow } from "./main"; import { dialog } from "electron";
const profile: Profile = { const profile: Profile = {
displayName: 'fs', displayName: 'fs',
name: 'fs', name: 'fs',
description: 'fs', description: 'fs'
} }
export class FSPlugin extends Plugin { export class FSPlugin extends ElectronBasePlugin {
client: FSPluginClient clients: FSPluginClient[] = []
constructor() { constructor() {
super(profile) super(profile)
this.methods = ['closeWatch'] this.methods = [...super.methods, 'closeWatch']
}
async createClient(webContentsId: number): Promise<void> {
this.clients.push(new FSPluginClient(webContentsId))
} }
onActivation(): void { async closeClient(webContentsId: number): Promise<void> {
this.client = new FSPluginClient() console.log('closeClient', webContentsId)
} }
async closeWatch(): Promise<void> { async closeWatch(): Promise<void> {
console.log('closeWatch') for (const client of this.clients) {
if (this.client) await client.closeWatch()
await this.client.closeWatch() }
} }
} }
class FSPluginClient extends PluginClient { const clientProfile: Profile = {
watcher: chokidar.FSWatcher | undefined name: 'fs',
constructor() { displayName: 'fs',
super() description: 'fs',
this.methods = ['readdir', 'readFile', 'writeFile', 'mkdir', 'rmdir', 'unlink', 'rename', 'stat', 'exists', 'watch', 'closeWatch', 'currentPath'] methods: ['readdir', 'readFile', 'writeFile', 'mkdir', 'rmdir', 'unlink', 'rename', 'stat', 'exists', 'currentPath', 'watch', 'closeWatch', 'setWorkingDir']
createElectronClient(this, profile, mainWindow) }
console.log(mainWindow)
class FSPluginClient extends ElectronBasePluginClient {
watcher: chokidar.FSWatcher
workingDir: string = '/Volumes/bunsen/code/rmproject2/remix-project/apps/remix-ide/contracts/'
constructor(webContentsId: number) {
super(webContentsId, clientProfile)
this.onload(() => { this.onload(() => {
console.log('fsPluginClient onload') console.log('fsPluginClient onload')
}) })
} }
async readdir(path: string): Promise<string[]> { async readdir(path: string): Promise<string[]> {
// call node fs.readdir
return fs.readdir(path) console.log('readdir', path)
const files = fs.readdir(this.fixPath(path))
return files
} }
async readFile(path: string): Promise<string> { async readFile(path: string): Promise<string> {
return fs.readFile(path, 'utf8') return fs.readFile(this.fixPath(path), 'utf8')
} }
async writeFile(path: string, content: string): Promise<void> { async writeFile(path: string, content: string): Promise<void> {
return fs.writeFile(path, content, 'utf8') return fs.writeFile(this.fixPath(path), content, 'utf8')
} }
async mkdir(path: string): Promise<void> { async mkdir(path: string): Promise<void> {
return fs.mkdir(path) return fs.mkdir(this.fixPath(path))
} }
async rmdir(path: string): Promise<void> { async rmdir(path: string): Promise<void> {
return fs.rmdir(path) return fs.rmdir(this.fixPath(path))
} }
async unlink(path: string): Promise<void> { async unlink(path: string): Promise<void> {
return fs.unlink(path) return fs.unlink(this.fixPath(path))
} }
async rename(oldPath: string, newPath: string): Promise<void> { async rename(oldPath: string, newPath: string): Promise<void> {
return fs.rename(oldPath, newPath) return fs.rename(this.fixPath(oldPath), this.fixPath(newPath))
} }
async stat(path: string): Promise<any> { async stat(path: string): Promise<any> {
@ -84,7 +94,7 @@ class FSPluginClient extends PluginClient {
} }
async exists(path: string): Promise<boolean> { async exists(path: string): Promise<boolean> {
return fs.access(path).then(() => true).catch(() => false) return fs.access(this.fixPath(path)).then(() => true).catch(() => false)
} }
async currentPath(): Promise<string> { async currentPath(): Promise<string> {
@ -92,19 +102,38 @@ class FSPluginClient extends PluginClient {
} }
async watch(path: string): Promise<void> { async watch(path: string): Promise<void> {
console.log('watch', path)
if (this.watcher) this.watcher.close() if (this.watcher) this.watcher.close()
this.watcher = this.watcher =
chokidar.watch(path).on('change', (path, stats) => { chokidar.watch(this.fixPath(path)).on('change', (path, stats) => {
console.log('change', path, stats) console.log('change', path, stats)
this.emit('change', path, stats) this.emit('change', path, stats)
}) })
} }
async closeWatch(): Promise<void> { async closeWatch(): Promise<void> {
console.log('closeWatch') console.log('closing Watcher', this.webContentsId)
if (this.watcher) this.watcher.close() if (this.watcher) this.watcher.close()
} }
async setWorkingDir(): Promise<void> {
const dirs = dialog.showOpenDialogSync(this.window, {
properties: ['openDirectory']
})
if (dirs && dirs.length > 0) {
this.workingDir = dirs[0]
this.emit('workingDirChanged', dirs[0])
}
}
fixPath(path: string): string {
if (path.startsWith('/')) {
path = path.slice(1)
}
path = this.workingDir + path
return path
}
} }

@ -1,9 +1,7 @@
import { Plugin } from "@remixproject/engine";
import { PluginClient } from "@remixproject/plugin"; import { PluginClient } from "@remixproject/plugin";
import { Profile } from "@remixproject/plugin-utils"; import { Profile } from "@remixproject/plugin-utils";
import { spawn } from "child_process"; import { spawn } from "child_process";
import { createElectronClient } from "@remixproject/plugin-electron" import { ElectronBasePlugin, ElectronBasePluginClient } from "@remixproject/plugin-electron"
import { mainWindow } from "./main";
const profile: Profile = { const profile: Profile = {
name: 'git', name: 'git',
@ -11,23 +9,34 @@ const profile: Profile = {
description: 'Git plugin', description: 'Git plugin',
} }
export class GitPlugin extends Plugin { export class GitPlugin extends ElectronBasePlugin {
client: PluginClient client: PluginClient
constructor() { constructor() {
super(profile) super(profile)
} }
onActivation(): void { async createClient(webContentsId: number): Promise<void> {
this.client = new GitPluginClient() this.clients.push(new GitPluginClient(webContentsId))
} }
async closeClient(webContentsId: number): Promise<void> {
console.log('closeClient', webContentsId)
} }
class GitPluginClient extends PluginClient {
constructor() { }
super()
this.methods = ['log', 'status', 'add', 'commit', 'push', 'pull', 'clone', 'checkout', 'branch', 'merge', 'reset', 'revert', 'diff', 'stash', 'apply', 'cherryPick', 'rebase', 'tag', 'fetch', 'remote', 'config', 'show', 'init', 'help', 'version'] const clientProfile: Profile = {
createElectronClient(this, profile, mainWindow) name: 'git',
displayName: 'Git',
description: 'Git plugin',
methods: ['log', 'status', 'add', 'commit', 'push', 'pull', 'clone', 'checkout', 'branch', 'merge', 'reset', 'revert', 'diff', 'stash', 'apply', 'cherryPick', 'rebase', 'tag', 'fetch', 'remote', 'config', 'show', 'init', 'help', 'version']
}
class GitPluginClient extends ElectronBasePluginClient {
constructor(webContentsId: number) {
super(webContentsId, clientProfile)
this.onload(() => { this.onload(() => {
console.log('GitPluginClient onload') console.log('GitPluginClient onload')
}) })

@ -1,6 +1,3 @@
// electron preload script
// write contextbridge to window
import { Message } from '@remixproject/plugin-utils' import { Message } from '@remixproject/plugin-utils'
import { contextBridge, ipcRenderer } from 'electron' import { contextBridge, ipcRenderer } from 'electron'
@ -11,16 +8,36 @@ console.log('preload.ts')
const exposedPLugins = ['fs', 'git', 'xterm'] const exposedPLugins = ['fs', 'git', 'xterm']
console.log('preload.ts', process)
let webContentsId: number | undefined
/*
// get the window id from the process arguments
const windowIdFromArgs = process.argv.find(arg => arg.startsWith('--window-id='))
if (windowIdFromArgs) {
[, windowId] = windowIdFromArgs.split('=')
console.log('windowId', windowId, )
}
*/
ipcRenderer.invoke('getWebContentsID').then((id: number) => {
webContentsId = id
console.log('getWebContentsID', webContentsId)
})
contextBridge.exposeInMainWorld('electronAPI', { contextBridge.exposeInMainWorld('electronAPI', {
activatePlugin: (name: string) => { activatePlugin: (name: string) => {
return ipcRenderer.invoke('manager:activatePlugin', name) return ipcRenderer.invoke('manager:activatePlugin', name)
}, },
getWindowId: () => ipcRenderer.invoke('getWindowID'),
plugins: exposedPLugins.map(name => { plugins: exposedPLugins.map(name => {
return { return {
name, name,
on: (cb:any) => ipcRenderer.on(`${name}:send`, cb), on: (cb:any) => ipcRenderer.on(`${name}:send`, cb),
send: (message: Partial<Message>) => ipcRenderer.send(`${name}:on`, message) send: (message: Partial<Message>) => {
console.log('send', message, `${name}:on:${webContentsId}`)
ipcRenderer.send(`${name}:on:${webContentsId}`, message)
}
} }
}) })
}) })

@ -1,12 +1,9 @@
import { Plugin } from "@remixproject/engine";
import { PluginClient } from "@remixproject/plugin"; import { PluginClient } from "@remixproject/plugin";
import { Profile } from "@remixproject/plugin-utils"; import { Profile } from "@remixproject/plugin-utils";
import { spawn } from "child_process"; import { ElectronBasePlugin, ElectronBasePluginClient } from "@remixproject/plugin-electron"
import { createElectronClient } from "@remixproject/plugin-electron"
import os from 'os'; import os from 'os';
import * as pty from "node-pty" import * as pty from "node-pty"
import { mainWindow } from "./main";
const profile: Profile = { const profile: Profile = {
name: 'xterm', name: 'xterm',
@ -14,31 +11,41 @@ const profile: Profile = {
description: 'xterm plugin', description: 'xterm plugin',
} }
export class XtermPlugin extends Plugin { export class XtermPlugin extends ElectronBasePlugin {
client: PluginClient client: PluginClient
constructor() { constructor() {
super(profile) super(profile)
} }
onActivation(): void { async createClient(webContentsId: number): Promise<void> {
this.client = new XtermPluginClient() console.log('createClient', webContentsId)
this.clients.push(new XtermPluginClient(webContentsId))
} }
async closeClient(webContentsId: number): Promise<void> {
console.log('closeClient', webContentsId)
} }
class XtermPluginClient extends PluginClient { }
const clientProfile: Profile = {
name: 'xterm',
displayName: 'xterm',
description: 'xterm plugin',
methods: ['createTerminal', 'close', 'keystroke']
}
class XtermPluginClient extends ElectronBasePluginClient {
terminals: pty.IPty[] = [] terminals: pty.IPty[] = []
constructor() { constructor(webContentsId: number) {
super() super(webContentsId, clientProfile)
this.methods = ['keystroke', 'createTerminal', 'close']
createElectronClient(this, profile, mainWindow)
this.onload(() => { this.onload(() => {
console.log('XtermPluginClient onload') console.log('XtermPluginClient onload')
}) })
} }
async keystroke(key: string, pid: number): Promise<void> { async keystroke(key: string, pid: number): Promise<void> {
console.log('keystroke', key)
this.terminals[pid].write(key) this.terminals[pid].write(key)
} }
@ -50,14 +57,14 @@ class XtermPluginClient extends PluginClient {
cols: 80, cols: 80,
rows: 30, rows: 30,
cwd: path || process.cwd(), cwd: path || process.cwd(),
env: process.env as { [key: string]: string }, //env: process.env
}); });
ptyProcess.onData((data: string) => { ptyProcess.onData((data: string) => {
this.sendData(data, ptyProcess.pid); this.sendData(data, ptyProcess.pid);
}) })
this.terminals[ptyProcess.pid] = ptyProcess this.terminals[ptyProcess.pid] = ptyProcess
console.log('create terminal', ptyProcess.pid)
return ptyProcess.pid return ptyProcess.pid
} }

@ -59,7 +59,7 @@ function HomeTabFeaturedPlugins ({plugin}: HomeTabFeaturedPluginsProps) {
} }
const startSolidity = async () => { const startSolidity = async () => {
await plugin.appManager.activatePlugin(['solidity', 'udapp', 'solidityStaticAnalysis', 'solidityUnitTesting']) //await plugin.appManager.activatePlugin(['solidity', 'udapp', 'solidityStaticAnalysis', 'solidityUnitTesting'])
plugin.verticalIcons.select('solidity') plugin.verticalIcons.select('solidity')
_paq.push(['trackEvent', 'hometabActivate', 'userActivate', 'solidity']) _paq.push(['trackEvent', 'hometabActivate', 'userActivate', 'solidity'])
} }

@ -311,6 +311,7 @@ export const SearchProvider = ({
} }
useEffect(() => { useEffect(() => {
return
plugin.on('filePanel', 'setWorkspace', async workspace => { plugin.on('filePanel', 'setWorkspace', async workspace => {
value.setSearchResults(null) value.setSearchResults(null)
value.clearUndo() value.clearUndo()

@ -44,6 +44,7 @@ const basicWorkspaceInit = async (workspaces: { name: string; isGitRepo: boolean
} }
export const initWorkspace = (filePanelPlugin) => async (reducerDispatch: React.Dispatch<any>) => { export const initWorkspace = (filePanelPlugin) => async (reducerDispatch: React.Dispatch<any>) => {
console.log('initWorkspace', filePanelPlugin)
if (filePanelPlugin) { if (filePanelPlugin) {
plugin = filePanelPlugin plugin = filePanelPlugin
dispatch = reducerDispatch dispatch = reducerDispatch
@ -52,8 +53,11 @@ export const initWorkspace = (filePanelPlugin) => async (reducerDispatch: React.
const localhostProvider = filePanelPlugin.fileProviders.localhost const localhostProvider = filePanelPlugin.fileProviders.localhost
const electrOnProvider = filePanelPlugin.fileProviders.browser const electrOnProvider = filePanelPlugin.fileProviders.browser
const params = queryParams.get() as UrlParametersType const params = queryParams.get() as UrlParametersType
const workspaces = await getWorkspaces() || [] let workspaces = []
if (!isElectron()){
workspaces = await getWorkspaces() || []
dispatch(setWorkspaces(workspaces)) dispatch(setWorkspaces(workspaces))
}
if (params.gist) { if (params.gist) {
await createWorkspaceTemplate('gist-sample', 'gist-template') await createWorkspaceTemplate('gist-sample', 'gist-template')
plugin.setWorkspace({ name: 'gist-sample', isLocalhost: false }) plugin.setWorkspace({ name: 'gist-sample', isLocalhost: false })
@ -114,9 +118,17 @@ export const initWorkspace = (filePanelPlugin) => async (reducerDispatch: React.
} }
} else await basicWorkspaceInit(workspaces, workspaceProvider) } else await basicWorkspaceInit(workspaces, workspaceProvider)
} else if (isElectron()) { } else if (isElectron()) {
console.log('isElectron initWorkspace')
plugin.call('notification', 'toast', `connecting to electron...`) plugin.call('notification', 'toast', `connecting to electron...`)
plugin.setWorkspace({ name: 'electron', isLocalhost: false }) plugin.setWorkspace({ name: 'electron', isLocalhost: false })
dispatch(setCurrentWorkspace({ name: 'electron', isGitRepo: false })) dispatch(setCurrentWorkspace({ name: 'electron', isGitRepo: false }))
listenOnProviderEvents(electrOnProvider)(dispatch)
dispatch(setMode('browser'))
dispatch(fsInitializationCompleted())
plugin.emit('workspaceInitializationCompleted')
return
} else if (localStorage.getItem("currentWorkspace")) { } else if (localStorage.getItem("currentWorkspace")) {
const index = workspaces.findIndex(element => element.name == localStorage.getItem("currentWorkspace")) const index = workspaces.findIndex(element => element.name == localStorage.getItem("currentWorkspace"))

@ -134,15 +134,15 @@
"@openzeppelin/contracts": "^4.7.3", "@openzeppelin/contracts": "^4.7.3",
"@openzeppelin/upgrades-core": "^1.22.0", "@openzeppelin/upgrades-core": "^1.22.0",
"@openzeppelin/wizard": "0.2.0", "@openzeppelin/wizard": "0.2.0",
"@remixproject/engine": "0.3.35", "@remixproject/engine": "0.3.36",
"@remixproject/engine-electron": " 0.3.35", "@remixproject/engine-electron": "0.3.36",
"@remixproject/engine-web": "0.3.35", "@remixproject/engine-web": "0.3.36",
"@remixproject/plugin": "0.3.35", "@remixproject/plugin": "0.3.36",
"@remixproject/plugin-api": "0.3.35", "@remixproject/plugin-api": "0.3.36",
"@remixproject/plugin-electron": "0.3.35", "@remixproject/plugin-electron": "0.3.36",
"@remixproject/plugin-utils": "0.3.35", "@remixproject/plugin-utils": "0.3.36",
"@remixproject/plugin-webview": "0.3.35", "@remixproject/plugin-webview": "0.3.36",
"@remixproject/plugin-ws": "0.3.35", "@remixproject/plugin-ws": "0.3.36",
"@types/nightwatch": "^2.3.1", "@types/nightwatch": "^2.3.1",
"@walletconnect/ethereum-provider": "^2.6.2", "@walletconnect/ethereum-provider": "^2.6.2",
"@walletconnect/sign-client": "^2.6.0", "@walletconnect/sign-client": "^2.6.0",

@ -5131,82 +5131,82 @@
resolved "https://registry.yarnpkg.com/@popperjs/core/-/core-2.10.2.tgz#0798c03351f0dea1a5a4cabddf26a55a7cbee590" resolved "https://registry.yarnpkg.com/@popperjs/core/-/core-2.10.2.tgz#0798c03351f0dea1a5a4cabddf26a55a7cbee590"
integrity sha512-IXf3XA7+XyN7CP9gGh/XB0UxVMlvARGEgGXLubFICsUMGz6Q+DU+i4gGlpOxTjKvXjkJDJC8YdqdKkDj9qZHEQ== integrity sha512-IXf3XA7+XyN7CP9gGh/XB0UxVMlvARGEgGXLubFICsUMGz6Q+DU+i4gGlpOxTjKvXjkJDJC8YdqdKkDj9qZHEQ==
"@remixproject/engine-electron@ 0.3.35": "@remixproject/engine-electron@0.3.36":
version "0.3.35" version "0.3.36"
resolved "https://registry.yarnpkg.com/@remixproject/engine-electron/-/engine-electron-0.3.35.tgz#6c5bf507e7719f9767b837c48f07f29f4bbc5ff5" resolved "https://registry.yarnpkg.com/@remixproject/engine-electron/-/engine-electron-0.3.36.tgz#24304072b102d23ea76d88dd142c28ed991aa46b"
integrity sha512-JPKzQ/Bn7QZDQzGceyZI23pZ4jhECf26Ajy/+my38zZWygoUms0J743yD4Z8ClrnXTUa7cXejOMJkwKcMDJ6kQ== integrity sha512-ueiO48ViKyYgZgZmMreXyEk62cSlAzwukSyXnxI3T4G5ykKL0G461ocK3cSYfTKoZ/g0/j7oUz25I8wXtU3boA==
dependencies: dependencies:
"@remixproject/engine" "0.3.35" "@remixproject/engine" "0.3.36"
"@remixproject/plugin-api" "0.3.35" "@remixproject/plugin-api" "0.3.36"
"@remixproject/plugin-utils" "0.3.35" "@remixproject/plugin-utils" "0.3.36"
"@remixproject/engine-web@0.3.35": "@remixproject/engine-web@0.3.36":
version "0.3.35" version "0.3.36"
resolved "https://registry.yarnpkg.com/@remixproject/engine-web/-/engine-web-0.3.35.tgz#34a017fa8675164a762dc8bcec9a2c7f8410f950" resolved "https://registry.yarnpkg.com/@remixproject/engine-web/-/engine-web-0.3.36.tgz#105328588fee0eee692f2e64aeb3faf2749ea56c"
integrity sha512-d0fBA/FjFdMY9FCp2sEs/sL7kSo29IsFY02mM4HA4byjGdXmOtFquNI/bg32IX3aDWgsnTZhgbI1LQLU0vnxwg== integrity sha512-rNYZiEFplg9A3Vfb72zVxqrUE3rVFOss8uBcAhWPt0um8vl7cZ8nRoFc5pxgnvDh+ozRo5YbWR0fqmDA4OiIUw==
dependencies: dependencies:
"@remixproject/engine" "0.3.35" "@remixproject/engine" "0.3.36"
"@remixproject/plugin-api" "0.3.35" "@remixproject/plugin-api" "0.3.36"
"@remixproject/plugin-utils" "0.3.35" "@remixproject/plugin-utils" "0.3.36"
"@remixproject/engine@0.3.35": "@remixproject/engine@0.3.36":
version "0.3.35" version "0.3.36"
resolved "https://registry.yarnpkg.com/@remixproject/engine/-/engine-0.3.35.tgz#4bbb21a7ce6ecdbd021fd231ba3daefbbedb2d6d" resolved "https://registry.yarnpkg.com/@remixproject/engine/-/engine-0.3.36.tgz#aa93210716ab959b1d39e08ea38c7357495381f3"
integrity sha512-lK1h7njE/dGLnBayE5X/zcnkjOeAu+UvDkE9PBRCdIte21M6UqmzClslCL1FjHP7j5hrr769OziQqvGajk4nFQ== integrity sha512-D5sf8WJBS5cbjqZBDWo/hN5kT6YhkZysGy0Y1QWcQj48WhOYHawl6VMExMsqVXtPZzJTWHs2N/hXuoc6MF6xfg==
dependencies: dependencies:
"@remixproject/plugin-api" "0.3.35" "@remixproject/plugin-api" "0.3.36"
"@remixproject/plugin-utils" "0.3.35" "@remixproject/plugin-utils" "0.3.36"
"@remixproject/plugin-api@0.3.35": "@remixproject/plugin-api@0.3.36":
version "0.3.35" version "0.3.36"
resolved "https://registry.yarnpkg.com/@remixproject/plugin-api/-/plugin-api-0.3.35.tgz#1f47eda88bb49c14b29614549f9f42387afba054" resolved "https://registry.yarnpkg.com/@remixproject/plugin-api/-/plugin-api-0.3.36.tgz#58a77184c17ddece59fc2b8e7dd30a92ddb164a2"
integrity sha512-lleQQ4B3QMtnjWflJHmLZYQ19jR/vGDT6ULskbj90qjss/xPp4NGqv18DnH5m8ycI2sTxcCdx9GEljFNieY3Bg== integrity sha512-T7/9FAhm499sFbOh/iuK05GRpTZwYJtXrlMUAGE9qhfo4ep62sJg0TLss1BRnfhkDBvlS10rqdF2wWTPQ9JSPA==
dependencies: dependencies:
"@remixproject/plugin-utils" "0.3.35" "@remixproject/plugin-utils" "0.3.36"
"@remixproject/plugin-electron@0.3.35": "@remixproject/plugin-electron@0.3.36":
version "0.3.35" version "0.3.36"
resolved "https://registry.yarnpkg.com/@remixproject/plugin-electron/-/plugin-electron-0.3.35.tgz#64646f8deafb6ec816d6bf156a610d8d1c8b8276" resolved "https://registry.yarnpkg.com/@remixproject/plugin-electron/-/plugin-electron-0.3.36.tgz#4b0067f3b42454f95625ed774a10751a6557b3ee"
integrity sha512-fdRm4StVdkuZj0nvRk9KxqC1jcwZ4ytjKbdPRpZMXl2rrImvxMt4VDTkz9MHTYufDF6CDprMfHN3zLlFKuelBA== integrity sha512-WM4u4zUs95adZxbC/GrMzfuCXfBde9TuoPeS2Nb9ysRQKj5eRfTIWiYrh9PxoezrEF8EYPo8gVrf9A+PGlKf3w==
dependencies: dependencies:
"@remixproject/engine" "0.3.35" "@remixproject/engine" "0.3.36"
"@remixproject/plugin" "0.3.35" "@remixproject/plugin" "0.3.36"
"@remixproject/plugin-api" "0.3.35" "@remixproject/plugin-api" "0.3.36"
"@remixproject/plugin-utils" "0.3.35" "@remixproject/plugin-utils" "0.3.36"
"@remixproject/plugin-utils@0.3.35": "@remixproject/plugin-utils@0.3.36":
version "0.3.35" version "0.3.36"
resolved "https://registry.yarnpkg.com/@remixproject/plugin-utils/-/plugin-utils-0.3.35.tgz#7d3b561c1bddea1c1c0bd74970b91052c1a42e5b" resolved "https://registry.yarnpkg.com/@remixproject/plugin-utils/-/plugin-utils-0.3.36.tgz#92478cd820e23b42a911a3e6e3f88d759f34d395"
integrity sha512-+QCbOp45+bn5ou6CLmTqDQ2AGsCiQIEXQlg3ULZNWKeo+8yvMXOHWJeLYRPV8TsSJQ6G1t3FeIVg4ZUGEPTFsw== integrity sha512-gbTzp3rHVITVn45vgb/f+huo6C/zogUCTmzqOj+D9INaQR1Fr6XtOL8erKjD0pNBbyw3lxpRsDKEpRQVE7sMbg==
dependencies: dependencies:
tslib "2.0.1" tslib "2.0.1"
"@remixproject/plugin-webview@0.3.35": "@remixproject/plugin-webview@0.3.36":
version "0.3.35" version "0.3.36"
resolved "https://registry.yarnpkg.com/@remixproject/plugin-webview/-/plugin-webview-0.3.35.tgz#d602f954d93dd3cbef8c41e43aa6c0e6da79b293" resolved "https://registry.yarnpkg.com/@remixproject/plugin-webview/-/plugin-webview-0.3.36.tgz#38997745608952cc40ce5c668d0836d0d5279b12"
integrity sha512-Ch2KjufmMjq+3Ynj7NkPP4lvlxxHmE4TY2k3bII+nn2dEa0ts0/aNCp5fCzMEVMivNzVpZIQBPEMzJhSv+ULcA== integrity sha512-9C/GQLDQzzX6g8MH9L54C+K/2FxBa4tCdlxUbPH9infTFvk/jcMjpd/CSAIBK4EfZCs1mL+1Opc8bX3VrfchpA==
dependencies: dependencies:
"@remixproject/plugin" "0.3.35" "@remixproject/plugin" "0.3.36"
"@remixproject/plugin-api" "0.3.35" "@remixproject/plugin-api" "0.3.36"
"@remixproject/plugin-utils" "0.3.35" "@remixproject/plugin-utils" "0.3.36"
axios "^0.21.1" axios "^0.21.1"
"@remixproject/plugin-ws@0.3.35": "@remixproject/plugin-ws@0.3.36":
version "0.3.35" version "0.3.36"
resolved "https://registry.yarnpkg.com/@remixproject/plugin-ws/-/plugin-ws-0.3.35.tgz#16a3f6673bb37288db531dcd063af59fefb01229" resolved "https://registry.yarnpkg.com/@remixproject/plugin-ws/-/plugin-ws-0.3.36.tgz#a80ccf5e7ae866bc53a79a7da4f36b67a0a33972"
integrity sha512-4sZnd2Fme3T+Ohr0+I4hJlHtZVF0oG2srGds80Cp0MiYmz9pJ3l2lhSe4Dl7Np14nfYrqxiwRGf6s4q2PQx6Ug== integrity sha512-+vGwHd7KAgGsPoPph8T5mGkGthJagtwsb53LopZwbwDziMEWP7IVu44TcI46qR/+oBHLm0MmH1ukCzRAkbDu+w==
dependencies: dependencies:
"@remixproject/plugin" "0.3.35" "@remixproject/plugin" "0.3.36"
"@remixproject/plugin-api" "0.3.35" "@remixproject/plugin-api" "0.3.36"
"@remixproject/plugin-utils" "0.3.35" "@remixproject/plugin-utils" "0.3.36"
"@remixproject/plugin@0.3.35": "@remixproject/plugin@0.3.36":
version "0.3.35" version "0.3.36"
resolved "https://registry.yarnpkg.com/@remixproject/plugin/-/plugin-0.3.35.tgz#6e4a8f2d400f70ae5351a9b560d3855f461389b4" resolved "https://registry.yarnpkg.com/@remixproject/plugin/-/plugin-0.3.36.tgz#393867208ace117c8dbfd08adb4d5feec1b17d4b"
integrity sha512-GGLyAv8sV0X0QhcF7ZqSfS0Fb+xhaFsWzhRZPeVs54d4a6ddOjCrR7A/vyIPGNS2vU4YSVbi8tk1Q1RuVBLAgQ== integrity sha512-+lFgfLI3vbiB6OQIkeX2Yx1EyKdIUGU1lJ7K51HwPfbGet5Y4w8qDMXBblRM1HhVe7bDYlBOzzbcil8/03Dsnw==
dependencies: dependencies:
"@remixproject/plugin-api" "0.3.35" "@remixproject/plugin-api" "0.3.36"
"@remixproject/plugin-utils" "0.3.35" "@remixproject/plugin-utils" "0.3.36"
events "3.2.0" events "3.2.0"
"@restart/context@^2.1.4": "@restart/context@^2.1.4":

Loading…
Cancel
Save