Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions apps/remix-ide-e2e/src/tests/remixd.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand Down
3 changes: 2 additions & 1 deletion apps/remix-ide/src/app.js
Original file line number Diff line number Diff line change
Expand Up @@ -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
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

as a Plugin I would recommend putting Git at the root of the project and access its methods only through remix-plugin API

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

will plan this changes later on. This will anyway go in during the react refactoring.

])

try {
Expand Down
18 changes: 18 additions & 0 deletions apps/remix-ide/src/app/files/git-handle.js
Original file line number Diff line number Diff line change
@@ -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)
}
}
5 changes: 4 additions & 1 deletion apps/remix-ide/src/app/files/remixd-handle.js
Original file line number Diff line number Diff line change
Expand Up @@ -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)
})
Expand All @@ -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')
}

/**
Expand Down Expand Up @@ -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()) {
Expand Down
2 changes: 2 additions & 0 deletions apps/remix-ide/src/app/panels/file-panel.js
Original file line number Diff line number Diff line change
Expand Up @@ -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')

Expand Down Expand Up @@ -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()
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would recommend to put GitHandle at the root of the project and not in the FilePanel property


const explorers = yo`
<div>
Expand Down
3 changes: 3 additions & 0 deletions apps/remix-ide/src/app/panels/styles/terminal-styles.js
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
13 changes: 10 additions & 3 deletions apps/remix-ide/src/app/panels/terminal.js
Original file line number Diff line number Diff line change
Expand Up @@ -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};
Expand Down Expand Up @@ -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`<pre>${result}</pre>`)
done()
} catch (error) {
done(error.message)
done(error.message || error)
}
}
}
Expand Down
1 change: 0 additions & 1 deletion apps/remix-ide/src/app/ui/auto-complete-popup.js
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,6 @@ class AutoCompletePopup {
self._elementsToShow = 4
self._selectedElement = 0
this.extraCommands = []
this.extendAutocompletion()
}

render () {
Expand Down
9 changes: 2 additions & 7 deletions apps/remix-ide/src/remixAppManager.js
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}

Expand All @@ -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) {
Expand Down Expand Up @@ -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)
}
Expand Down
29 changes: 23 additions & 6 deletions libs/remixd/src/bin/remixd.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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> = []
function startService<S extends 'git' | 'folder'> (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 <shared folder>')
Expand All @@ -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<Function> = []

if (!program.remixIde) {
console.log('\x1b[33m%s\x1b[0m', '[WARN] You can only connect to remixd from one of the supported origins.')
Expand All @@ -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)
}
Expand Down
1 change: 1 addition & 0 deletions libs/remixd/src/serviceList.ts
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
export { RemixdClient as Sharedfolder } from './services/remixdClient'
export { GitClient } from './services/gitClient'
50 changes: 50 additions & 0 deletions libs/remixd/src/services/gitClient.ts
Original file line number Diff line number Diff line change
@@ -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) => {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would recommend to put the Promise closer to its used :

assertCommand(cmd, gitRegex)
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 += data.toString())
  child.on('close', (exitCode) => exitCode !== 0 ? reject(error) : resolve(result))
})

You don't need to try / catch here everything that throw will be handled by the caller

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!')
}
}
4 changes: 2 additions & 2 deletions libs/remixd/src/types/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<typeof ServiceList[ServiceListKeys]>
export type ServiceClient = InstanceType<typeof ServiceList[ServiceListKeys]>

export type WebsocketOpt = {
remixIdeUrl: string
Expand Down
18 changes: 9 additions & 9 deletions libs/remixd/src/websocket.ts
Original file line number Diff line number Diff line change
@@ -1,41 +1,41 @@
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
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

any reason why you need to disable line ? I'm not sure you need to do that anymore.

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)
response.end()
})
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)
})
}

Expand Down