diff --git a/apps/remix-ide-e2e/src/tests/remixd.test.ts b/apps/remix-ide-e2e/src/tests/remixd.test.ts index 333bb49ff7..6aef785590 100644 --- a/apps/remix-ide-e2e/src/tests/remixd.test.ts +++ b/apps/remix-ide-e2e/src/tests/remixd.test.ts @@ -71,6 +71,11 @@ module.exports = { .testContracts('test_import_node_modules_with_github_import.sol', sources[4]['browser/test_import_node_modules_with_github_import.sol'], ['ERC20', 'test11']) .clickLaunchIcon('pluginManager') .scrollAndClick('#pluginManager article[id="remixPluginManagerListItem_remixd"] button') + }, + + 'Run git status': function (browser) { + browser.executeScript('git status') + .journalLastChildIncludes('On branch master') .end() }, tearDown: sauce diff --git a/apps/remix-ide/src/app.js b/apps/remix-ide/src/app.js index 2bf1eee928..17878c51fd 100644 --- a/apps/remix-ide/src/app.js +++ b/apps/remix-ide/src/app.js @@ -390,7 +390,8 @@ Please make a backup of your contracts and start using http://remix.ethereum.org debug, analysis, test, - filePanel.remixdHandle + filePanel.remixdHandle, + filePanel.gitHandle ]) try { diff --git a/apps/remix-ide/src/app/files/git-handle.js b/apps/remix-ide/src/app/files/git-handle.js new file mode 100644 index 0000000000..00ba104525 --- /dev/null +++ b/apps/remix-ide/src/app/files/git-handle.js @@ -0,0 +1,27 @@ +import { WebsocketPlugin } from '@remixproject/engine-web' +import * as packageJson from '../../../../../package.json' + +const profile = { + name: 'git', + displayName: 'Git', + url: 'ws://127.0.0.1:65521', + methods: ['command'], + events: [], + description: 'Using Remixd daemon, allow to access git API', + kind: 'other', + version: packageJson.version +} + +export class GitHandle extends WebsocketPlugin { + constructor () { + super(profile) + } + + deactivate () { + super.deactivate() + } + + activate () { + super.activate() + } +} diff --git a/apps/remix-ide/src/app/files/remixd-handle.js b/apps/remix-ide/src/app/files/remixd-handle.js index 1bfcb2cd17..2b1050487d 100644 --- a/apps/remix-ide/src/app/files/remixd-handle.js +++ b/apps/remix-ide/src/app/files/remixd-handle.js @@ -40,6 +40,7 @@ export class RemixdHandle extends WebsocketPlugin { deactivate () { this.fileSystemExplorer.hide() if (super.socket) super.deactivate() + this.appManager.ensureDeactivated('git') this.locahostProvider.close((error) => { if (error) console.log(error) }) @@ -52,6 +53,7 @@ export class RemixdHandle extends WebsocketPlugin { async canceled () { this.appManager.ensureDeactivated('remixd') + this.appManager.ensureDeactivated('git') } /** @@ -82,6 +84,7 @@ export class RemixdHandle extends WebsocketPlugin { } }, 3000) this.locahostProvider.init(_ => this.fileSystemExplorer.ensureRoot()) + this.appManager.ensureActivated('git') } } if (this.locahostProvider.isConnected()) { diff --git a/apps/remix-ide/src/app/panels/file-panel.js b/apps/remix-ide/src/app/panels/file-panel.js index 65e1664b7e..e210ca8ef0 100644 --- a/apps/remix-ide/src/app/panels/file-panel.js +++ b/apps/remix-ide/src/app/panels/file-panel.js @@ -5,6 +5,7 @@ var yo = require('yo-yo') var EventManager = require('../../lib/events') var FileExplorer = require('../files/file-explorer') var { RemixdHandle } = require('../files/remixd-handle.js') +var { GitHandle } = require('../files/git-handle.js') var globalRegistry = require('../../global/registry') var css = require('./styles/file-panel-styles') @@ -60,6 +61,7 @@ module.exports = class Filepanel extends ViewPlugin { var fileSystemExplorer = createProvider('localhost') self.remixdHandle = new RemixdHandle(fileSystemExplorer, self._deps.fileProviders.localhost, appManager) + self.gitHandle = new GitHandle() const explorers = yo`
${result}`) + } else { + await this.call('scriptRunner', 'execute', script) + } done() } catch (error) { - done(error.message) + done(error.message || error) } } } diff --git a/apps/remix-ide/src/remixAppManager.js b/apps/remix-ide/src/remixAppManager.js index 432acd0154..a4536e4724 100644 --- a/apps/remix-ide/src/remixAppManager.js +++ b/apps/remix-ide/src/remixAppManager.js @@ -11,7 +11,7 @@ const requiredModules = [ // services + layout views + system views 'terminal', 'settings', 'pluginManager'] export function isNative (name) { - const nativePlugins = ['vyper', 'workshops', 'debugger'] + const nativePlugins = ['vyper', 'workshops', 'debugger', 'remixd'] return nativePlugins.includes(name) || requiredModules.includes(name) } diff --git a/libs/remixd/src/bin/remixd.ts b/libs/remixd/src/bin/remixd.ts index 4c4b72631d..1c922d9e2e 100644 --- a/libs/remixd/src/bin/remixd.ts +++ b/libs/remixd/src/bin/remixd.ts @@ -38,15 +38,24 @@ import * as program from 'commander' console.log('\x1b[33m%s\x1b[0m', '[WARN] Any application that runs on your computer can potentially read from and write to all files in the directory.') console.log('\x1b[33m%s\x1b[0m', '[WARN] Symbolic links are not forwarded to Remix IDE\n') try { - const sharedFolderClient = new servicesList.Sharedfolder() - const websocketHandler = new WebSocket(65520, { remixIdeUrl: program.remixIde }, sharedFolderClient) + // shared folder + const websocketHandler = new WebSocket(65520, { remixIdeUrl: program.remixIde }, () => new servicesList.Sharedfolder()) - websocketHandler.start((ws: WS) => { + websocketHandler.start((ws: WS, sharedFolderClient: servicesList.Sharedfolder) => { sharedFolderClient.setWebSocket(ws) sharedFolderClient.setupNotifications(program.sharedFolder) sharedFolderClient.sharedFolder(program.sharedFolder, program.readOnly || false) }) killCallBack.push(websocketHandler.close.bind(websocketHandler)) + + // git + const websocketHandlerForGit = new WebSocket(65521, { remixIdeUrl: program.remixIde }, () => new servicesList.GitClient()) + + websocketHandlerForGit.start((ws: WS, gitClient: servicesList.GitClient) => { + gitClient.setWebSocket(ws) + gitClient.sharedFolder(program.sharedFolder, program.readOnly || false) + }) + killCallBack.push(websocketHandlerForGit.close.bind(websocketHandlerForGit)) } catch (error) { throw new Error(error) } diff --git a/libs/remixd/src/serviceList.ts b/libs/remixd/src/serviceList.ts index cb22bd880a..5db445ee66 100644 --- a/libs/remixd/src/serviceList.ts +++ b/libs/remixd/src/serviceList.ts @@ -1 +1,2 @@ export { RemixdClient as Sharedfolder } from './services/remixdClient' +export { GitClient } from './services/gitClient' diff --git a/libs/remixd/src/services/gitClient.ts b/libs/remixd/src/services/gitClient.ts new file mode 100644 index 0000000000..3bfbeace2a --- /dev/null +++ b/libs/remixd/src/services/gitClient.ts @@ -0,0 +1,62 @@ +import * as WS from 'ws' // eslint-disable-line +import { PluginClient } from '@remixproject/plugin' +const { spawn } = require('child_process') +const gitRegex = '^git\\s[^&|;]*$' + +export class GitClient extends PluginClient { + methods: ['command'] + websocket: WS + currentSharedFolder: string + readOnly: boolean + + setWebSocket (websocket: WS): void { + this.websocket = websocket + } + + sharedFolder (currentSharedFolder: string, readOnly: boolean): void { + this.currentSharedFolder = currentSharedFolder + this.readOnly = readOnly + } + + command (cmd: string) { + return new Promise((resolve, reject) => { + try { + try { + validateCommand(cmd, gitRegex) + } catch (e) { + return reject(e) + } + const options = { cwd: this.currentSharedFolder, shell: true } + const child = spawn(cmd, options) + let result = '' + let error = '' + child.stdout.on('data', (data) => { + result += data.toString() + }) + child.stderr.on('data', (err) => { + error += err.toString() + }) + child.on('close', (exitCode) => { + if (exitCode !== 0) { + reject(error) + } else { + resolve(result + error) + } + }) + } catch (e) { + reject(e) + } + }) + } +} + +/** + * Validate that command can be run by service + * @param cmd + * @param regex + */ +function validateCommand (cmd, regex) { + if (!RegExp(regex).test(cmd)) { // git then space and then everything else + throw new Error('Invalid command for service!') + } +} diff --git a/libs/remixd/src/types/index.ts b/libs/remixd/src/types/index.ts index 71587b5423..cde06fcf07 100644 --- a/libs/remixd/src/types/index.ts +++ b/libs/remixd/src/types/index.ts @@ -3,9 +3,9 @@ import * as Websocket from 'ws' type ServiceListKeys = keyof typeof ServiceList; -export type SharedFolder = typeof ServiceList[ServiceListKeys] +export type Service = typeof ServiceList[ServiceListKeys] -export type SharedFolderClient = InstanceType