From c76772137be1a98211aed0b87eb34d30e24d8752 Mon Sep 17 00:00:00 2001 From: yann300 Date: Thu, 26 Nov 2020 13:46:29 +0100 Subject: [PATCH] add git service in remixd & use it from the terminal --- apps/remix-ide-e2e/src/tests/remixd.test.ts | 5 ++ apps/remix-ide/src/app.js | 3 +- apps/remix-ide/src/app/files/git-handle.js | 27 ++++++++ apps/remix-ide/src/app/files/remixd-handle.js | 3 + apps/remix-ide/src/app/panels/file-panel.js | 2 + .../src/app/panels/styles/terminal-styles.js | 3 + apps/remix-ide/src/app/panels/terminal.js | 11 +++- apps/remix-ide/src/remixAppManager.js | 2 +- libs/remixd/src/bin/remixd.ts | 15 ++++- libs/remixd/src/serviceList.ts | 1 + libs/remixd/src/services/gitClient.ts | 62 +++++++++++++++++++ libs/remixd/src/types/index.ts | 4 +- libs/remixd/src/websocket.ts | 16 ++--- 13 files changed, 136 insertions(+), 18 deletions(-) create mode 100644 apps/remix-ide/src/app/files/git-handle.js create mode 100644 libs/remixd/src/services/gitClient.ts 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`
diff --git a/apps/remix-ide/src/app/panels/styles/terminal-styles.js b/apps/remix-ide/src/app/panels/styles/terminal-styles.js index 25d2d598ac..7564e4f7ea 100644 --- a/apps/remix-ide/src/app/panels/styles/terminal-styles.js +++ b/apps/remix-ide/src/app/panels/styles/terminal-styles.js @@ -54,6 +54,9 @@ var css = csjs` padding : 1ch; margin-top : 2ch; } + .block > pre { + max-height : 200px; + } .cli { line-height : 1.7em; font-family : monospace; diff --git a/apps/remix-ide/src/app/panels/terminal.js b/apps/remix-ide/src/app/panels/terminal.js index 632e45c3e2..aaf8ec2f7e 100644 --- a/apps/remix-ide/src/app/panels/terminal.js +++ b/apps/remix-ide/src/app/panels/terminal.js @@ -485,7 +485,7 @@ class Terminal extends Plugin { return self._view.el function wrapScript (script) { - if (script.startsWith('remix.')) return script + if (script.trim().startsWith('remix.') || script.trim().startsWith('git')) return script return ` try { const ret = ${script}; @@ -746,10 +746,15 @@ class Terminal extends Plugin { } } try { - await this.call('scriptRunner', 'execute', script) + if (script.trim().indexOf('git') === 0) { + const result = await this.call('git', 'command', script) + self.commands.html(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 +export type ServiceClient = InstanceType export type WebsocketOpt = { remixIdeUrl: string diff --git a/libs/remixd/src/websocket.ts b/libs/remixd/src/websocket.ts index 6c89b380c1..2aef0744c8 100644 --- a/libs/remixd/src/websocket.ts +++ b/libs/remixd/src/websocket.ts @@ -1,15 +1,15 @@ import * as WS from 'ws' import * as http from 'http' -import { WebsocketOpt, SharedFolderClient } from './types' // eslint-disable-line +import { WebsocketOpt, ServiceClient } from './types' // eslint-disable-line import { getDomain } from './utils' import { createClient } from '@remixproject/plugin-ws' export default class WebSocket { server: http.Server wsServer: WS.Server - constructor (public port: number, public opt: WebsocketOpt, public sharedFolder: SharedFolderClient) {} //eslint-disable-line + constructor (public port: number, public opt: WebsocketOpt, public getclient: () => ServiceClient) {} //eslint-disable-line - start (callback?: (ws: WS) => void): void { + start (callback?: (ws: WS, client: ServiceClient) => void): void { this.server = http.createServer((request, response) => { console.log((new Date()) + ' Received request for ' + request.url) response.writeHead(404) @@ -17,8 +17,8 @@ export default class WebSocket { }) const loopback = '127.0.0.1' - this.server.listen(this.port, loopback, function () { - console.log((new Date()) + ' remixd is listening on ' + loopback + ':65520') + this.server.listen(this.port, loopback, () => { + console.log((new Date()) + ' remixd is listening on ' + loopback + ':' + this.port + '') }) this.wsServer = new WS.Server({ server: this.server, @@ -32,10 +32,10 @@ export default class WebSocket { } }) this.wsServer.on('connection', (ws) => { - const { sharedFolder } = this + const client = this.getclient() - createClient(ws, sharedFolder as any) - if (callback) callback(ws) + createClient(ws, client as any) + if (callback) callback(ws, client) }) }