diff --git a/apps/remix-ide-e2e/src/tests/remixd.test.ts b/apps/remix-ide-e2e/src/tests/remixd.test.ts
index 333bb49ff7..c45c95a660 100644
--- a/apps/remix-ide-e2e/src/tests/remixd.test.ts
+++ b/apps/remix-ide-e2e/src/tests/remixd.test.ts
@@ -69,6 +69,17 @@ module.exports = {
.clickLaunchIcon('solidity')
.setSolidityCompilerVersion('soljson-v0.6.2+commit.bacdbe57.js') // open-zeppelin moved to pragma ^0.6.0
.testContracts('test_import_node_modules_with_github_import.sol', sources[4]['browser/test_import_node_modules_with_github_import.sol'], ['ERC20', 'test11'])
+ },
+
+ 'Run git status': function (browser) {
+ browser
+ .executeScript('git status')
+ .pause(3000)
+ .journalLastChildIncludes('On branch ')
+ },
+
+ 'Close Remixd': function (browser) {
+ browser
.clickLaunchIcon('pluginManager')
.scrollAndClick('#pluginManager article[id="remixPluginManagerListItem_remixd"] button')
.end()
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..a935068679
--- /dev/null
+++ b/apps/remix-ide/src/app/files/git-handle.js
@@ -0,0 +1,18 @@
+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: ['execute'],
+ description: 'Using Remixd daemon, allow to access git API',
+ kind: 'other',
+ version: packageJson.version
+}
+
+export class GitHandle extends WebsocketPlugin {
+ constructor () {
+ super(profile)
+ }
+}
diff --git a/apps/remix-ide/src/app/files/remixd-handle.js b/apps/remix-ide/src/app/files/remixd-handle.js
index 1bfcb2cd17..311883e020 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.call('manager', 'deactivatePlugin', 'git')
this.locahostProvider.close((error) => {
if (error) console.log(error)
})
@@ -51,7 +52,8 @@ export class RemixdHandle extends WebsocketPlugin {
}
async canceled () {
- this.appManager.ensureDeactivated('remixd')
+ this.call('manager', 'deactivatePlugin', 'remixd')
+ this.call('manager', 'deactivatePlugin', 'git')
}
/**
@@ -82,6 +84,7 @@ export class RemixdHandle extends WebsocketPlugin {
}
}, 3000)
this.locahostProvider.init(_ => this.fileSystemExplorer.ensureRoot())
+ this.call('manager', 'activatePlugin', '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..25f3e3e546 100644
--- a/apps/remix-ide/src/app/panels/terminal.js
+++ b/apps/remix-ide/src/app/panels/terminal.js
@@ -485,7 +485,8 @@ class Terminal extends Plugin {
return self._view.el
function wrapScript (script) {
- if (script.startsWith('remix.')) return script
+ const isKnownScript = ['remix.', 'git'].some(prefix => script.trim().startsWith(prefix))
+ if (isKnownScript) return script
return `
try {
const ret = ${script};
@@ -746,10 +747,16 @@ class Terminal extends Plugin {
}
}
try {
- await this.call('scriptRunner', 'execute', script)
+ let result
+ if (script.trim().startsWith('git')) {
+ result = await this.call('git', 'execute', script)
+ } else {
+ result = await this.call('scriptRunner', 'execute', script)
+ }
+ if (result) self.commands.html(yo`
${result}
`)
done()
} catch (error) {
- done(error.message)
+ done(error.message || error)
}
}
}
diff --git a/apps/remix-ide/src/app/ui/auto-complete-popup.js b/apps/remix-ide/src/app/ui/auto-complete-popup.js
index 152efcc457..36e4fa9697 100644
--- a/apps/remix-ide/src/app/ui/auto-complete-popup.js
+++ b/apps/remix-ide/src/app/ui/auto-complete-popup.js
@@ -31,7 +31,6 @@ class AutoCompletePopup {
self._elementsToShow = 4
self._selectedElement = 0
this.extraCommands = []
- this.extendAutocompletion()
}
render () {
diff --git a/apps/remix-ide/src/remixAppManager.js b/apps/remix-ide/src/remixAppManager.js
index 432acd0154..c6156355a0 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)
}
@@ -34,7 +34,7 @@ export class RemixAppManager extends PluginManager {
async canDeactivatePlugin (from, to) {
if (requiredModules.includes(to.name)) return false
- return from.name === 'manager'
+ return isNative(from.name)
}
async canCall (from, to, method, message) {
@@ -70,11 +70,6 @@ export class RemixAppManager extends PluginManager {
this.event.emit('deactivate', plugin)
}
- async ensureDeactivated (apiName) {
- await this.deactivatePlugin(apiName)
- this.event.emit('ensureDeactivated', apiName)
- }
-
isRequired (name) {
return requiredModules.includes(name)
}
diff --git a/libs/remixd/src/bin/remixd.ts b/libs/remixd/src/bin/remixd.ts
index 4c4b72631d..a973c13ead 100644
--- a/libs/remixd/src/bin/remixd.ts
+++ b/libs/remixd/src/bin/remixd.ts
@@ -8,6 +8,23 @@ import * as fs from 'fs-extra'
import * as path from 'path'
import * as program from 'commander'
+const services = {
+ git: () => new servicesList.GitClient(),
+ folder: () => new servicesList.Sharedfolder()
+}
+
+const ports = {
+ git: 65521,
+ folder: 65520
+}
+
+const killCallBack: Array
= []
+function startService (service: S, callback: (ws: WS, sharedFolderClient: servicesList.Sharedfolder) => void) {
+ const socket = new WebSocket(ports[service], { remixIdeUrl: program.remixIde }, () => services[service]())
+ socket.start(callback)
+ killCallBack.push(socket.close.bind(socket))
+}
+
(async () => {
program
.usage('-s ')
@@ -19,7 +36,6 @@ import * as program from 'commander'
console.log('\nExample:\n\n remixd -s ./ --remix-ide http://localhost:8080')
}).parse(process.argv)
// eslint-disable-next-line
- const killCallBack: Array = []
if (!program.remixIde) {
console.log('\x1b[33m%s\x1b[0m', '[WARN] You can only connect to remixd from one of the supported origins.')
@@ -38,15 +54,16 @@ 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)
-
- websocketHandler.start((ws: WS) => {
+ startService('folder', (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))
+
+ startService('git', (ws: WS, sharedFolderClient: servicesList.Sharedfolder) => {
+ sharedFolderClient.setWebSocket(ws)
+ sharedFolderClient.sharedFolder(program.sharedFolder, program.readOnly || false)
+ })
} 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..868a6c575e
--- /dev/null
+++ b/libs/remixd/src/services/gitClient.ts
@@ -0,0 +1,50 @@
+import * as WS from 'ws' // eslint-disable-line
+import { PluginClient } from '@remixproject/plugin'
+const { spawn } = require('child_process')
+
+export class GitClient extends PluginClient {
+ methods: ['execute']
+ 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
+ }
+
+ execute (cmd: string) {
+ assertCommand(cmd)
+ const options = { cwd: this.currentSharedFolder, shell: true }
+ const child = spawn(cmd, options)
+ let result = ''
+ let error = ''
+ return new Promise((resolve, reject) => {
+ child.stdout.on('data', (data) => {
+ result += data.toString()
+ })
+ child.stderr.on('data', (err) => {
+ error += err.toString()
+ })
+ child.on('close', () => {
+ if (error) reject(error)
+ else resolve(result)
+ })
+ })
+ }
+}
+
+/**
+ * Validate that command can be run by service
+ * @param cmd
+ */
+function assertCommand (cmd) {
+ const regex = '^git\\s[^&|;]*$'
+ 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..dd9cc4f511 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,25 +17,25 @@ 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,
verifyClient: (info, done) => {
if (!originIsAllowed(info.origin, this)) {
done(false)
- console.log((new Date()) + ' Connection from origin ' + info.origin + ' rejected.')
+ console.log(`${new Date()} connection from origin ${info.origin}`)
return
}
done(true)
}
})
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)
})
}