conflict resolve

pull/5100/head
STetsing 2 months ago
parent 3f987b244b
commit 6dd040617f
  1. 14
      apps/remixdesktop/aftersign.js
  2. 3
      apps/remixdesktop/insiders.json
  3. 2
      apps/remixdesktop/notarizedmg.sh
  4. 47
      apps/remixdesktop/package.json
  5. 9
      apps/remixdesktop/run_ci_test.sh
  6. 1
      apps/remixdesktop/src/lib/remixd.ts
  7. 2
      apps/remixdesktop/src/lib/utils.ts
  8. 4
      apps/remixdesktop/src/plugins/appUpdater.ts
  9. 2
      apps/remixdesktop/src/plugins/compilerLoader.ts
  10. 34
      apps/remixdesktop/src/plugins/fsPlugin.ts
  11. 273
      apps/remixdesktop/src/plugins/isoGitPlugin.ts
  12. 173
      apps/remixdesktop/src/plugins/slitherPlugin.ts
  13. 43
      apps/remixdesktop/src/plugins/xtermPlugin.ts
  14. 84
      apps/remixdesktop/src/tools/git.ts
  15. 13
      apps/remixdesktop/test/nighwatch.app.ts
  16. 5
      apps/remixdesktop/test/tests/app/gist.test.ts
  17. 6
      apps/remixdesktop/test/tests/app/offline.test.ts

@ -1,6 +1,18 @@
const { notarize } = require('@electron/notarize')
const fs = require('fs')
const { exec } = require('child_process') // Import the exec function
// read the environment variables from process
console.log(process.env.DO_NOT_NOTARIZE)
if (process.env.DO_NOT_NOTARIZE) {
console.log('NOTARIZING DISABLED')
exports.default = async function notarizing(context) {
return []
}
} else {
exports.default = async function notarizing(context) {
const { electronPlatformName, appOutDir } = context // Provided by electron-builder
@ -77,6 +89,7 @@ exports.default = async function notarizing(context) {
} catch (error) {
console.error('Error during notarization:', error)
throw new Error('Error during notarization', error)
}
}
@ -88,3 +101,4 @@ exports.default = async function notarizing(context) {
return []
}
}
}

@ -5,7 +5,8 @@
"generateUpdatesFilesForAllChannels": false,
"icon": "assets",
"files": [
"build/**/*"
"build/**/*",
"node_modules/node-pty/**/*"
],
"afterSign": "aftersign.js",
"afterAllArtifactBuild": "afterbuild.js",

@ -11,7 +11,7 @@ echo $DMG_PATHS
xcrun notarytool store-credentials "notarytool-password" \
--apple-id ${APPLE_ID} \
--team-id ${APPLE_TEAM_ID} \
--password ${APPLE_ID_PASSWORD}
--password ${APPLE_ID_PASSWORD} || exit 1
# Use jq to parse the DMGs array and read each line
while IFS= read -r DMG_PATH; do

@ -1,7 +1,7 @@
{
"name": "remixdesktop",
"version": "1.0.5-insiders",
"main": "build/apps/remixdesktop/src/main.js",
"version": "1.0.8-insiders",
"main": "build/main.js",
"license": "MIT",
"type": "commonjs",
"description": "Remix IDE Desktop",
@ -13,6 +13,9 @@
"name": "Remix Team",
"email": "remix@ethereum.org"
},
"engines": {
"node": "20.2.0"
},
"bugs": {
"url": "https://github.com/ethereum/remix-project/issues"
},
@ -22,15 +25,17 @@
"category": "public.app-category.productivity"
},
"scripts": {
"start:dev": "tsc && cp -R node_modules/yarn build/tools/ && cross-env NODE_ENV=development electron --inspect=5858 .",
"start:production": "tsc && && cp -R node_modules/yarn build/tools/ && cross-env NODE_ENV=production electron .",
"dist": "tsc && cp -R node_modules/yarn build/tools/ && electron-builder -p never",
"start:dev": "yarn webpack --config webpack.config.js && electron --inspect=5858 .",
"start:production": "cross-env NODE_ENV=production yarn webpack --config webpack.config.js && electron .",
"dist": "cross-env NODE_ENV=production yarn webpack --config webpack.config.js && electron-builder -p never",
"tscbuild": "tsc && cp -R node_modules/yarn build/tools/ && electron-builder -p never",
"esbuild": "cross-env NODE_ENV=production node esbuild.js && electron-builder -p never",
"installRipGrepMacOXx64": "rm -rf node_modules/@vscode/ripgrep/bin && npm_config_arch=x64 node node_modules/@vscode/ripgrep/lib/postinstall.js",
"installRipGrepMacOXarm64": "rm -rf node_modules/@vscode/ripgrep/bin && npm_config_arch=arm64 node node_modules/@vscode/ripgrep/lib/postinstall.js",
"postinstall": "electron-builder install-app-deps",
"test": "yarn run build:e2e && nightwatch --config build-e2e/remixdesktop/test/nighwatch.app.js",
"test:isogit": "yarn run test --useIsoGit --test build-e2e/remixdesktop/test/tests/app/git.test.js",
"test:offline": "yarn run test --useOffline --test build-e2e/remixdesktop/test/tests/app/offline.test.js",
"test:isogit": "yarn run test --use-isogit",
"test:offline": "yarn run test --use-offline --test build-e2e/remixdesktop/test/tests/app/offline.test.js",
"build:e2e": "tsc -p tsconfig.e2e.json"
},
"devDependencies": {
@ -38,18 +43,24 @@
"@types/byline": "^4.2.35",
"@types/express": "^4.17.21",
"@types/nightwatch": "^2.3.23",
"@xenova/transformers": "^2.17.2",
"chromedriver": "116",
"cross-env": "^7.0.3",
"deep-equal": "^2.2.3",
"electron": "^26.0.0",
"electron-builder": "24.9.1",
"electron-devtools-installer": "^3.2.0",
"esbuild": "^0.23.1",
"nightwatch": "2.3",
"node-fetch": "2.6.1",
"onnxruntime-web": "^1.18.0",
"node-loader": "^2.0.0",
"selenium-standalone": "^9.3.1",
"tree-kill": "^1.2.2",
"ts-loader": "^9.5.1",
"tsconfig-paths-webpack-plugin": "^4.1.0",
"typescript": "^5.1.3",
"webpack": "^5.92.1",
"webpack-cli": "^5.1.4",
"webpack-merge": "^6.0.1",
"webpack-node-externals": "^3.0.0",
"yarn": "^1.22.21"
},
"dependencies": {
@ -61,25 +72,17 @@
"@remixproject/plugin-electron": "0.3.43",
"@vscode/ripgrep": "^1.15.6",
"add": "^2.0.6",
"axios": "^1.6.8",
"axios": "^1.7.4",
"byline": "^5.0.0",
"chokidar": "^3.5.3",
"create-require": "^1.1.1",
"electron-updater": "^6.1.8",
"esm": "^3.2.25",
"express": "^4.19.2",
"i": "^0.3.7",
"express": "^4.20.0",
"isomorphic-git": "^1.24.2",
"matomo-tracker": "^2.2.4",
"node-pty": "^0.10.1",
"npm": "^10.8.1",
"node-pty": "^1.0.0",
"octokit": "^3.1.2",
"semver": "^7.5.4"
},
"imports": {
"#transformers": {
"require": "@xenova/transformers"
}
},
"optionalDependencies": {
"@remix-project/remix-ws-templates": "^1.0.27"
}

@ -4,13 +4,12 @@ TEST_EXITCODE=0
yarn run build:e2e && node ./splice_tests.js
TESTFILES=$(node ./splice_tests.js | circleci tests split --split-by=timings)
for TESTFILE in $TESTFILES; do
yarn run test --test ./build-e2e/remixdesktop/test/tests/app/${TESTFILE} || TEST_EXITCODE=1
yarn run test --test ./build-e2e/remixdesktop/test/tests/app/${TESTFILE} || yarn run test --test ./build-e2e/remixdesktop/test/tests/app/${TESTFILE} || TEST_EXITCODE=1
done
if [ "$CIRCLE_NODE_INDEX" -eq 0 ]; then
yarn test:isogit
elif [ "$CIRCLE_NODE_INDEX" -eq 1 ]; then
yarn test:offline
if [ "$CIRCLE_NODE_INDEX" -eq 1 ]; then
yarn test:offline || TEST_EXITCODE=1
fi
echo "$TEST_EXITCODE"

@ -28,6 +28,7 @@ export class ElectronBasePluginRemixdClient extends ElectronBasePluginClient {
this.onload(async () => {
this.on('fs' as any, 'workingDirChanged', async (path: string) => {
console.log('workingDirChanged base remixd', path)
this.currentSharedFolder = path
})
this.currentSharedFolder = await this.call('fs' as any, 'getWorkingDir')

@ -19,6 +19,6 @@ function normalizePath (path) {
return path
}
export { absolutePath }
export { absolutePath, normalizePath }

@ -14,7 +14,6 @@ const profile = {
export class AppUpdaterPlugin extends ElectronBasePlugin {
clients: AppUpdaterPluginClient[] = []
constructor() {
console.log('AppUpdaterPlugin')
super(profile, clientProfile, AppUpdaterPluginClient)
this.methods = [...super.methods]
@ -78,14 +77,11 @@ const clientProfile: Profile = {
class AppUpdaterPluginClient extends ElectronBasePluginClient {
constructor(webContentsId: number, profile: Profile) {
console.log('AppUpdaterPluginClient')
super(webContentsId, profile)
}
async onActivation(): Promise<void> {
console.log('onActivation', 'appUpdaterPluginClient')
this.onload(async () => {
console.log('onload', 'appUpdaterPluginClient')
this.emit('loaded')
if(isE2E) return
await this.checkForUpdates()

@ -12,7 +12,7 @@ export const baseURLWasm = 'https://binaries.soliditylang.org/wasm'
const appExpress = express()
// used in e2e tests
const useOffline = process.argv.includes('--useOffline');
const useOffline = process.argv.includes('--use-offline');
console.log('cacheDir', cacheDir)
appExpress.use(express.static(cacheDir))

@ -32,6 +32,14 @@ const getBaseName = (pathName: string): string => {
return path.basename(pathName)
}
function onlyUnique(value: recentFolder, index: number, self: recentFolder[]) {
return self.findIndex((rc, index) => rc.path === value.path) === index
}
const deplucateFolderList = (list: recentFolder[]): recentFolder[] => {
return list.filter(onlyUnique)
}
export class FSPlugin extends ElectronBasePlugin {
clients: FSPluginClient[] = []
constructor() {
@ -40,28 +48,28 @@ export class FSPlugin extends ElectronBasePlugin {
}
async onActivation(): Promise<void> {
const config = await this.call('electronconfig' as any, 'readConfig')
const config = await this.call('electronconfig', 'readConfig')
const openedFolders = (config && config.openedFolders) || []
const recentFolders = (config && config.recentFolders) || []
const recentFolders: recentFolder[] = (config && config.recentFolders) || []
this.call('electronconfig', 'writeConfig', {...config,
recentFolders: recentFolders,
recentFolders: deplucateFolderList(recentFolders),
openedFolders: openedFolders})
const foldersToDelete: string[] = []
if (openedFolders && openedFolders.length) {
for (const folder of openedFolders) {
if (recentFolders && recentFolders.length) {
for (const folder of recentFolders) {
try {
const stat = await fs.stat(folder)
const stat = await fs.stat(folder.path);
if (stat.isDirectory()) {
// do nothing
}
} catch (e) {
console.log('error opening folder', folder, e)
foldersToDelete.push(folder)
foldersToDelete.push(folder.path)
}
}
if (foldersToDelete.length) {
const newFolders = openedFolders.filter((f: string) => !foldersToDelete.includes(f))
this.call('electronconfig', 'writeConfig', {recentFolders: newFolders})
const newFolders = recentFolders.filter((f: recentFolder) => !foldersToDelete.includes(f.path))
this.call('electronconfig', 'writeConfig', {recentFolders: deplucateFolderList(newFolders)})
}
}
createWindow()
@ -85,6 +93,13 @@ export class FSPlugin extends ElectronBasePlugin {
client.openFolder(path)
}
}
openFolderInSameWindow(webContentsId: any, path?: string): void {
const client = this.clients.find((c) => c.webContentsId === webContentsId)
if (client) {
client.openFolderInSameWindow(path)
}
}
}
const clientProfile: Profile = {
@ -346,6 +361,7 @@ class FSPluginClient extends ElectronBasePluginClient {
path,
timestamp,
})
config.recentFolders = deplucateFolderList(config.recentFolders)
writeConfig(config)
}

@ -1,12 +1,11 @@
import { PluginClient } from "@remixproject/plugin";
import { Profile } from "@remixproject/plugin-utils";
import { ElectronBasePlugin, ElectronBasePluginClient } from "@remixproject/plugin-electron"
import {Profile} from '@remixproject/plugin-utils'
import {ElectronBasePlugin, ElectronBasePluginClient} from '@remixproject/plugin-electron'
import fs from 'fs/promises'
import git from 'isomorphic-git'
import { dialog } from "electron";
import http from 'isomorphic-git/http/web'
import { gitProxy } from "../tools/git";
import { remote } from "../types";
import {gitProxy} from '../tools/git'
import {isoGit} from '@remix-git'
import {branchDifference, branchInputType, checkoutInputType, cloneInputType, commitChange, commitInputType, compareBranchesInput, currentBranchInput, fetchInputType, initInputType, logInputType, pullInputType, pushInputType, remote, resolveRefInput, statusInput} from '@remix-api'
const profile: Profile = {
name: 'isogit',
@ -14,7 +13,7 @@ const profile: Profile = {
description: 'isogit plugin',
}
// used in e2e tests
const useIsoGit = process.argv.includes('--useIsoGit');
const useIsoGit = process.argv.includes('--use-isogit')
export class IsoGitPlugin extends ElectronBasePlugin {
clients: IsoGitPluginClient[] = []
constructor() {
@ -22,33 +21,18 @@ export class IsoGitPlugin extends ElectronBasePlugin {
}
startClone(webContentsId: any): void {
const client = this.clients.find(c => c.webContentsId === webContentsId)
const client = this.clients.find((c) => c.webContentsId === webContentsId)
if (client) {
client.startClone()
}
}
}
const parseInput = (input: any) => {
return {
corsProxy: 'https://corsproxy.remixproject.org/',
http,
onAuth: (url: any) => {
url
const auth = {
username: input.token,
password: ''
}
return auth
}
}
}
const clientProfile: Profile = {
name: 'isogit',
displayName: 'isogit',
description: 'isogit plugin',
methods: ['init', 'localStorageUsed', 'version', 'addremote', 'delremote', 'remotes', 'fetch', 'clone', 'export', 'import', 'status', 'log', 'commit', 'add', 'remove', 'reset', 'rm', 'lsfiles', 'readblob', 'resolveref', 'branches', 'branch', 'checkout', 'currentbranch', 'push', 'pin', 'pull', 'pinList', 'unPin', 'setIpfsConfig', 'zip', 'setItem', 'getItem', 'openFolder']
methods: ['init', 'localStorageUsed', 'version', 'addremote', 'delremote', 'remotes', 'fetch', 'clone', 'export', 'import', 'status', 'log', 'commit', 'add', 'remove', 'rm', 'readblob', 'resolveref', 'branches', 'branch', 'checkout', 'currentbranch', 'push', 'pin', 'pull', 'pinList', 'unPin', 'setIpfsConfig', 'zip', 'setItem', 'getItem', 'openFolder', 'getCommitChanges', 'compareBranches', 'startClone', 'updateSubmodules'],
}
class IsoGitPluginClient extends ElectronBasePluginClient {
@ -59,15 +43,15 @@ class IsoGitPluginClient extends ElectronBasePluginClient {
this.onload(async () => {
this.on('fs' as any, 'workingDirChanged', async (path: string) => {
this.workingDir = path
this.gitIsInstalled = await gitProxy.version() ? true : false
this.gitIsInstalled = (await gitProxy.version()) && !useIsoGit ? true : false
})
this.workingDir = await this.call('fs' as any, 'getWorkingDir')
this.gitIsInstalled = await gitProxy.version() && !useIsoGit ? true : false
this.gitIsInstalled = (await gitProxy.version()) && !useIsoGit ? true : false
})
}
async version() {
return gitProxy.version()
return this.gitIsInstalled ? gitProxy.version() : 'built-in'
}
async getGitConfig() {
@ -77,13 +61,12 @@ class IsoGitPluginClient extends ElectronBasePluginClient {
}
}
async status(cmd: any) {
async status(cmd: statusInput) {
if (!this.workingDir || this.workingDir === '') {
throw new Error('No working directory')
}
if (this.workingDir === '') {
return []
}
@ -94,30 +77,25 @@ class IsoGitPluginClient extends ElectronBasePluginClient {
}
const status = await git.statusMatrix({
...await this.getGitConfig(),
...cmd
...(await this.getGitConfig()),
...cmd,
})
//console.log('STATUS', status, await this.getGitConfig())
return status
}
async log(cmd: any) {
async log(cmd: logInputType) {
/* we will use isomorphic git for now
if(this.gitIsInstalled){
const log = await gitProxy.log(this.workingDir, cmd.ref)
console.log('LOG', log)
return log
}
*/
const token = await this.call('config' as any, 'getAppParameter', 'settings/gist-access-token')
if (this.workingDir === '') {
return []
}
const log = await git.log({
...await this.getGitConfig(),
...cmd
...(await this.getGitConfig()),
...cmd,
depth: cmd.depth || 10,
})
return log
@ -129,62 +107,45 @@ class IsoGitPluginClient extends ElectronBasePluginClient {
}
const add = await git.add({
...await this.getGitConfig(),
...cmd
...(await this.getGitConfig()),
...cmd,
})
return add
}
async rm(cmd: any) {
if (!this.workingDir || this.workingDir === '') {
throw new Error('No working directory')
}
const rm = await git.remove({
...await this.getGitConfig(),
...cmd
...(await this.getGitConfig()),
...cmd,
})
return rm
}
async reset(cmd: any) {
if (!this.workingDir || this.workingDir === '') {
throw new Error('No working directory')
}
const reset = await git.resetIndex({
...await this.getGitConfig(),
...cmd
})
return reset
}
async commit(cmd: any) {
async commit(cmd: commitInputType) {
if (!this.workingDir || this.workingDir === '') {
throw new Error('No working directory')
}
if (this.gitIsInstalled) {
const status = await gitProxy.commit(this.workingDir, cmd.message)
const status = await gitProxy.commit(this.workingDir, cmd)
return status
}
const commit = await git.commit({
...await this.getGitConfig(),
...cmd
...(await this.getGitConfig()),
...cmd,
})
return commit
}
async init(input: any) {
async init(input: initInputType) {
if (!this.workingDir || this.workingDir === '') {
throw new Error('No working directory')
}
@ -193,158 +154,119 @@ class IsoGitPluginClient extends ElectronBasePluginClient {
return status
}
await git.init({
...await this.getGitConfig(),
defaultBranch: (input && input.branch) || 'main'
...(await this.getGitConfig()),
defaultBranch: (input && input.defaultBranch) || 'main',
})
}
async branch(cmd: any) {
async branch(cmd: branchInputType) {
if (!this.workingDir || this.workingDir === '') {
return null
}
const branch = await git.branch({
...await this.getGitConfig(),
...cmd
...(await this.getGitConfig()),
...cmd,
})
return branch
}
async lsfiles(cmd: any) {
if (!this.workingDir || this.workingDir === '') {
return []
}
const lsfiles = await git.listFiles({
...await this.getGitConfig(),
...cmd
})
return lsfiles
}
async resolveref(cmd: resolveRefInput) {
async resolveref(cmd: any) {
if (!this.workingDir || this.workingDir === '') {
return null
}
const resolveref = await git.resolveRef({
...await this.getGitConfig(),
...cmd
...(await this.getGitConfig()),
...cmd,
})
return resolveref
}
async readblob(cmd: any) {
if (!this.workingDir || this.workingDir === '') {
throw new Error('No working directory')
}
const readblob = await git.readBlob({
...await this.getGitConfig(),
...cmd
...(await this.getGitConfig()),
...cmd,
})
return readblob
}
async checkout(cmd: any) {
async checkout(cmd: checkoutInputType) {
if (!this.workingDir || this.workingDir === '') {
throw new Error('No working directory')
}
if (this.gitIsInstalled) {
return await gitProxy.checkout(this.workingDir, cmd)
} else {
const checkout = await git.checkout({
...await this.getGitConfig(),
...cmd
...(await this.getGitConfig()),
...cmd,
})
return checkout
}
}
async push(cmd: any) {
async push(input: pushInputType) {
if (!this.workingDir || this.workingDir === '') {
throw new Error('No working directory')
}
if (this.gitIsInstalled) {
await gitProxy.push(this.workingDir, cmd.remote, cmd.ref, cmd.remoteRef, cmd.force)
return await gitProxy.push(this.workingDir, input)
} else {
const push = await git.push({
...await this.getGitConfig(),
...cmd,
...parseInput(cmd.input)
})
const push = await isoGit.push(input, await this.getGitConfig(), this)
return push
}
}
async pull(cmd: any) {
async pull(input: pullInputType) {
if (!this.workingDir || this.workingDir === '') {
throw new Error('No working directory')
}
if (this.gitIsInstalled) {
await gitProxy.pull(this.workingDir, cmd.remote, cmd.ref, cmd.remoteRef)
return await gitProxy.pull(this.workingDir, input)
} else {
const pull = await git.pull({
...await this.getGitConfig(),
...cmd,
...parseInput(cmd.input)
})
const pull = await isoGit.pull(input, await this.getGitConfig(), this)
return pull
}
}
async fetch(cmd: any) {
async fetch(input: fetchInputType) {
if (!this.workingDir || this.workingDir === '') {
throw new Error('No working directory')
}
if (this.gitIsInstalled) {
await gitProxy.fetch(this.workingDir, cmd.remote, cmd.remoteRef)
await gitProxy.fetch(this.workingDir, input)
} else {
const fetch = await git.fetch({
...await this.getGitConfig(),
...cmd,
...parseInput(cmd.input)
})
const fetch = await isoGit.fetch(input, await this.getGitConfig(), this)
return fetch
}
}
async clone(cmd: any) {
async clone(cmd: cloneInputType) {
if (this.gitIsInstalled) {
try {
await gitProxy.clone(cmd.url, cmd.dir)
this.call('terminal' as any, 'log', 'Cloning using git... please wait.')
await gitProxy.clone(cmd)
} catch (e) {
throw e
}
} else {
try {
this.call('terminal' as any, 'log', 'Cloning using builtin git... please wait.')
const clone = await git.clone({
...await this.getGitConfig(),
...cmd,
...parseInput(cmd.input),
dir: cmd.dir || this.workingDir
})
const clone = await isoGit.clone(cmd, await this.getGitConfig(), this)
return clone
} catch (e) {
console.log('CLONE ERROR', e)
@ -353,85 +275,74 @@ class IsoGitPluginClient extends ElectronBasePluginClient {
}
}
async addremote(cmd: any) {
async addremote(input: remote) {
const addremote = await git.addRemote({
...await this.getGitConfig(),
...cmd
...(await this.getGitConfig()),
url: input.url,
remote: input.name,
})
return addremote
}
async delremote(cmd: any) {
async delremote(input: remote) {
const delremote = await git.deleteRemote({
...await this.getGitConfig(),
...cmd
...(await this.getGitConfig()),
remote: input.name,
})
return delremote
}
async remotes() {
remotes = async () => {
if (!this.workingDir || this.workingDir === '') {
return []
}
let remotes: remote[] = []
remotes = (await git.listRemotes({ ...await this.getGitConfig() })).map((remote) => { return { name: remote.remote, url: remote.url } }
)
return remotes
const defaultConfig = await this.getGitConfig()
return await isoGit.remotes(defaultConfig)
}
async currentbranch() {
async currentbranch(input: currentBranchInput) {
if (!this.workingDir || this.workingDir === '') {
return ''
}
try {
const defaultConfig = await this.getGitConfig()
const name = await git.currentBranch(defaultConfig)
return name
} catch (e) {
return ''
}
return await isoGit.currentbranch(input, defaultConfig)
}
async branches(config: any) {
async branches() {
if (!this.workingDir || this.workingDir === '') {
return []
}
try {
let cmd: any = { ...await this.getGitConfig() }
const remotes = await this.remotes()
let branches = []
branches = (await git.listBranches(cmd)).map((branch) => { return { remote: undefined, name: branch } })
for (const remote of remotes) {
cmd = {
...cmd,
remote: remote.name
}
const remotebranches = (await git.listBranches(cmd)).map((branch) => { return { remote: remote.name, name: branch } })
branches = [...branches, ...remotebranches]
}
return branches
} catch (e) {
return []
}
const defaultConfig = await this.getGitConfig()
return await isoGit.branches(defaultConfig)
}
async startClone() {
this.call('filePanel' as any, 'clone')
}
async getCommitChanges(commitHash1: string, commitHash2: string): Promise<commitChange[]> {
return await isoGit.getCommitChanges(commitHash1, commitHash2, await this.getGitConfig())
}
async compareBranches({branch, remote}: compareBranchesInput): Promise<branchDifference> {
return await isoGit.compareBranches({branch, remote}, await this.getGitConfig())
}
async updateSubmodules(input) {
if (this.gitIsInstalled) {
try {
return await gitProxy.updateSubmodules(this.workingDir)
} catch (e) {
throw e
}
} else {
this.call('terminal', 'log', {type: 'error', value: 'Please install git into your OS to use this functionality...'})
}
}
}

@ -2,7 +2,22 @@ import { Profile } from "@remixproject/plugin-utils";
import { ElectronBasePlugin, ElectronBasePluginClient } from "@remixproject/plugin-electron"
import { ElectronBasePluginRemixdClient } from "../lib/remixd"
import { SlitherClientMixin } from "../lib/slither";
import * as utils from '../lib/utils'
import { existsSync, readdirSync, readFileSync, unlinkSync } from "fs-extra";
export interface OutputStandard {
description: string
title: string
confidence: string
severity: string
sourceMap: any
category?: string
reference?: string
example?: any
[key: string]: any
}
const { spawn, execSync } = require('child_process') // eslint-disable-line
const profile: Profile = {
name: 'slither',
displayName: 'electron slither',
@ -12,7 +27,7 @@ const profile: Profile = {
export class SlitherPlugin extends ElectronBasePlugin {
clients: any[]
constructor() {
super(profile, clientProfile, SlitherClientMixin(SlitherPluginClient))
super(profile, clientProfile, SlitherPluginClient)
this.methods = [...super.methods]
}
}
@ -24,10 +39,158 @@ const clientProfile: Profile = {
methods: ['analyse']
}
class SlitherPluginClient extends ElectronBasePluginRemixdClient {
constructor(webContentsId: number, profile: Profile) {
super(webContentsId, profile);
mapNpmDepsDir(list) {
const remixNpmDepsPath = utils.absolutePath('.deps/npm', this.currentSharedFolder)
const localNpmDepsPath = utils.absolutePath('node_modules', this.currentSharedFolder)
const npmDepsExists = existsSync(remixNpmDepsPath)
const nodeModulesExists = existsSync(localNpmDepsPath)
let isLocalDep = false
let isRemixDep = false
let allowPathString = ''
let remapString = ''
for (const e of list) {
const importPath = e.replace(/import ['"]/g, '').trim()
const packageName = importPath.split('/')[0]
if (nodeModulesExists && readdirSync(localNpmDepsPath).includes(packageName)) {
isLocalDep = true
remapString += `${packageName}=./node_modules/${packageName} `
} else if (npmDepsExists && readdirSync(remixNpmDepsPath).includes(packageName)) {
isRemixDep = true
remapString += `${packageName}=./.deps/npm/${packageName} `
}
}
if (isLocalDep) allowPathString += './node_modules,'
if (isRemixDep) allowPathString += './.deps/npm,'
return { remapString, allowPathString }
}
transform(detectors: Record<string, any>[]): OutputStandard[] {
const standardReport: OutputStandard[] = []
for (const e of detectors) {
const obj = {} as OutputStandard
obj.description = e.description
obj.title = e.check
obj.confidence = e.confidence
obj.severity = e.impact
obj.sourceMap = e.elements.map((element) => {
delete element.source_mapping.filename_used
delete element.source_mapping.filename_absolute
return element
})
standardReport.push(obj)
}
return standardReport
}
analyse(filePath: string, compilerConfig: Record<string, any>) {
return new Promise((resolve, reject) => {
const options = { cwd: this.currentSharedFolder, shell: true }
const { currentVersion, optimize, evmVersion } = compilerConfig
if (currentVersion && currentVersion.includes('+commit')) {
// Get compiler version with commit id e.g: 0.8.2+commit.661d110
const versionString: string = currentVersion.substring(0, currentVersion.indexOf('+commit') + 16)
this.log(`[Slither Analysis]: Compiler version is ${versionString}`)
let solcOutput: Buffer
// Check solc current installed version
try {
solcOutput = execSync('solc --version', options)
} catch (err) {
this.error(err)
reject(new Error('Error in running solc command'))
}
if (!solcOutput.toString().includes(versionString)) {
this.log('[Slither Analysis]: Compiler version is different from installed solc version')
// Get compiler version without commit id e.g: 0.8.2
const version: string = versionString.substring(0, versionString.indexOf('+commit'))
// List solc versions installed using solc-select
try {
const solcSelectInstalledVersions: Buffer = execSync('solc-select versions', options)
// Check if required version is already installed
if (!solcSelectInstalledVersions.toString().includes(version)) {
this.log(`[Slither Analysis]: Installing ${version} using solc-select`)
// Install required version
execSync(`solc-select install ${version}`, options)
}
this.log(`[Slither Analysis]: Setting ${version} as current solc version using solc-select`)
// Set solc current version as required version
execSync(`solc-select use ${version}`, options)
} catch (err) {
this.error(err)
reject(new Error('Error in running solc-select command'))
}
} else this.log('[Slither Analysis]: Compiler version is same as installed solc version')
}
// Allow paths and set solc remapping for import URLs
const fileContent = readFileSync(utils.absolutePath(filePath, this.currentSharedFolder), 'utf8')
const importsArr = fileContent.match(/import ['"][^.|..](.+?)['"];/g)
let remaps = ''
if (importsArr?.length) {
const { remapString } = this.mapNpmDepsDir(importsArr)
remaps = remapString.trim()
}
const optimizeOption: string = optimize ? '--optimize' : ''
const evmOption: string = evmVersion ? `--evm-version ${evmVersion}` : ''
let solcArgs = ''
if (optimizeOption) {
solcArgs += optimizeOption + ' '
}
if (evmOption) {
if (!solcArgs.endsWith(' ')) solcArgs += ' '
solcArgs += evmOption
}
if (solcArgs) {
solcArgs = `--solc-args "${solcArgs.trimStart()}"`
}
const solcRemaps = remaps ? `--solc-remaps "${remaps}"` : ''
const outputFile = 'remix-slither-report.json'
try {
// We don't keep the previous analysis
const outputFilePath = utils.absolutePath(outputFile, this.currentSharedFolder)
if (existsSync(outputFilePath)) unlinkSync(outputFilePath)
} catch (e) {
this.error('unable to remove the output file')
this.error(e.message)
}
const cmd = `slither ${filePath} ${solcArgs} ${solcRemaps} --json ${outputFile}`
this.log('[Slither Analysis]: Running Slither...')
// Added `stdio: 'ignore'` as for contract with NPM imports analysis which is exported in 'stderr'
// get too big and hangs the process. We process analysis from the report file only
const child = spawn(cmd, { cwd: this.currentSharedFolder, shell: true, stdio: 'ignore' })
const response = {}
child.on('close', () => {
const outputFileAbsPath: string = utils.absolutePath(outputFile, this.currentSharedFolder)
// Check if slither report file exists
if (existsSync(outputFileAbsPath)) {
let report = readFileSync(outputFileAbsPath, 'utf8')
report = JSON.parse(report)
if (report['success']) {
response['status'] = true
if (!report['results'] || !report['results'].detectors || !report['results'].detectors.length) {
response['count'] = 0
} else {
const { detectors } = report['results']
response['count'] = detectors.length
response['data'] = this.transform(detectors)
}
resolve(response)
} else {
this.log(report['error'])
reject(new Error('Error in running Slither Analysis.'))
}
} else {
this.error('Error in generating Slither Analysis Report. Make sure Slither is properly installed.')
reject(new Error('Error in generating Slither Analysis Report. Make sure Slither is properly installed.'))
}
})
})
}
}

@ -1,16 +1,13 @@
import {PluginClient} from '@remixproject/plugin'
import {Profile} from '@remixproject/plugin-utils'
import {
ElectronBasePlugin,
ElectronBasePluginClient,
} from '@remixproject/plugin-electron'
import {ElectronBasePlugin, ElectronBasePluginClient} from '@remixproject/plugin-electron'
import os from 'os'
import * as pty from 'node-pty'
import process from 'node:process'
import {userInfo} from 'node:os'
import {findExecutable} from '../utils/findExecutable'
import { spawnSync } from 'child_process'
import {exec, spawnSync} from 'child_process'
import {stripAnsi} from '../lib'
import {DataBatcher} from '../lib/databatcher'
@ -38,10 +35,7 @@ export const detectDefaultShell = () => {
// Stores default shell when imported.
const defaultShell = detectDefaultShell()
const getShellEnvArgs = [
'-ilc',
'echo -n "_SHELL_ENV_DELIMITER_"; env; echo -n "_SHELL_ENV_DELIMITER_"; exit',
]
const getShellEnvArgs = ['-ilc', 'echo -n "_SHELL_ENV_DELIMITER_"; env; echo -n "_SHELL_ENV_DELIMITER_"; exit']
const getShellEnvEnv = {
// Disables Oh My Zsh auto-update thing that can block the process.
@ -150,7 +144,6 @@ class XtermPluginClient extends ElectronBasePluginClient {
const start_time = Date.now()
console.log('createTerminal', path, shell || defaultShell)
const env = this.parsedEnv || process.env
const ptyProcess = pty.spawn(shell || defaultShell, [], {
@ -160,11 +153,10 @@ class XtermPluginClient extends ElectronBasePluginClient {
cwd: path || this.workingDir || process.cwd(),
env: env,
encoding: 'utf8',
});
})
const dataBatcher = new DataBatcher(ptyProcess.pid)
this.dataBatchers[ptyProcess.pid] = dataBatcher
ptyProcess.onData((data: string) => {
//console.log('data', data)
dataBatcher.write(Buffer.from(data))
})
ptyProcess.onExit(() => {
@ -181,19 +173,40 @@ class XtermPluginClient extends ElectronBasePluginClient {
}
async closeTerminal(pid: number): Promise<void> {
console.log('closeTerminal', pid)
try {
if (this.terminals) {
if (this.dataBatchers[pid]) delete this.dataBatchers[pid]
if (this.terminals[pid]) {
try {
this.terminals[pid].kill()
if (os.platform() === 'win32') {
// For Windows, use taskkill to terminate the process
exec(`taskkill /PID ${pid} /T /F`, (error, stdout, stderr) => {
if (error) {
console.error(`Error killing process: ${error}`);
}else{
console.log(`stdout: ${stdout}`);
console.error(`stderr: ${stderr}`);
}
});
} else {
this.terminals[pid].kill();
}
} catch (err) {
console.error(err)
// ignore
}
delete this.terminals[pid]
}
if (this.dataBatchers[pid])
delete this.dataBatchers[pid]
}
this.emit('close', pid)
} catch (err) {
console.error(err)
}
}
async resize({cols, rows}: {cols: number; rows: number}, pid: number) {

@ -1,6 +1,7 @@
import { exec } from 'child_process';
import { CommitObject, ReadCommitResult } from 'isomorphic-git';
import { promisify } from 'util';
import { cloneInputType, commitInputType, fetchInputType, pullInputType, pushInputType, checkoutInputType } from "@remix-api";
const execAsync = promisify(exec);
const statusTransFormMatrix = (status: string) => {
@ -9,12 +10,16 @@ const statusTransFormMatrix = (status: string) => {
return [0, 2, 0]
case 'A ':
return [0, 2, 2]
case 'R ':
return [0, 2, 2]
case 'M ':
return [1, 2, 2]
case 'MM':
return [1, 2, 3]
case ' M':
return [1, 2, 0]
return [1, 2, 1]
case 'AD':
return [0, 0, 3]
case ' D':
return [1, 0, 1]
case 'D ':
@ -37,34 +42,87 @@ export const gitProxy = {
}
},
async defaultRemoteName(path: string) {
try {
const { stdout } = await execAsync('git remote', { cwd: path });
const remotes = stdout.trim().split('\n');
return remotes[0];
} catch (error) {
throw new Error(`Failed to get the default remote name: ${error.message}`);
}
},
clone: async (url: string, path: string) => {
const { stdout, stderr } = await execAsync(`git clone ${url} "${path}"`);
clone: async (input: cloneInputType) => {
const { stdout, stderr } = await execAsync(`git clone ${input.url} "${input.dir}"`);
},
async push(path: string, remote: string, src: string, branch: string, force: boolean = false) {
const { stdout, stderr } = await execAsync(`git push ${force ? ' -f' : ''} ${remote} ${src}:${branch}`, { cwd: path });
async push(path: string, input: pushInputType) {
if(!input.remote || !input.remote.name) {
input.remote = { name: await gitProxy.defaultRemoteName(path), url: '' }
}
let remoteRefString = ''
if(input.remoteRef && !input.remoteRef.name) {
remoteRefString = `:${input.remoteRef.name}`
}
const { stdout, stderr } = await execAsync(`git push ${input.force ? ' -f' : ''} ${input.remote.name}${remoteRefString} ${input.ref.name}`, { cwd: path });
},
async pull(path: string, remote: string, src: string, branch: string) {
const { stdout, stderr } = await execAsync(`git pull ${remote} ${src}:${branch}`, { cwd: path });
async pull(path: string, input: pullInputType) {
if(!input.remote || !input.remote.name) {
input.remote = { name: await gitProxy.defaultRemoteName(path), url: '' }
}
let remoteRefString = ''
if(input.remoteRef && !input.remoteRef.name) {
remoteRefString = `:${input.remoteRef.name}`
}
const { stdout, stderr } = await execAsync(`git pull ${input.remote.name} ${input.ref.name}${remoteRefString}`, { cwd: path });
},
async fetch(path: string, remote: string, branch: string) {
const { stdout, stderr } = await execAsync(`git fetch ${remote} ${branch}`, { cwd: path });
async fetch(path: string, input: fetchInputType) {
if(!input.remote || !input.remote.name) {
input.remote = { name: await gitProxy.defaultRemoteName(path), url: '' }
}
try {
const { stdout, stderr } = await execAsync(`git fetch ${input.remote.name} ${(input.ref && input.ref.name) ? input.ref.name : ''}`, { cwd: path });
if (stdout) {
console.log('stdout:', stdout);
}
if (stderr) {
console.error('stderr:', stderr);
}
} catch (error) {
console.error('Error during fetch:', error);
}
},
async checkout(path: string, input: checkoutInputType) {
let force = input.force ? ' -f' : '';
const { stdout, stderr } = await execAsync(`git checkout ${force} ${input.ref}`, { cwd: path });
},
async commit(path: string, message: string) {
async commit(path: string, input: commitInputType) {
await execAsync(`git commit -m '${message}'`, { cwd: path });
await execAsync(`git commit -m '${input.message}'`, { cwd: path });
const { stdout, stderr } = await execAsync(`git rev-parse HEAD`, { cwd: path });
console.log('stdout commit:', stdout);
return stdout;
},
async init(path: string) {
await execAsync(`git init`, { cwd: path });
await execAsync(`git init --initial-branch=main`, { cwd: path });
},
async updateSubmodules(path: string) {
const { stdout, stderr } = await execAsync(`git submodule update --init --recursive`, { cwd: path });
if (stdout) {
console.log('stdout:', stdout);
}
if (stderr) {
console.error('stderr:', stderr);
}
},
@ -103,7 +161,7 @@ export const gitProxy = {
return 0
})
console.log('files', files)
return files
},

@ -3,8 +3,8 @@ import fs from 'fs';
const useIsoGit = process.argv.includes('--useIsoGit');
const useOffline = process.argv.includes('--useOffline');
const useIsoGit = process.argv.includes('--use-isogit');
const useOffline = process.argv.includes('--use-offline');
// Function to read JSON file synchronously
function readJSONFileSync(filename: string): any {
@ -71,12 +71,12 @@ module.exports = {
// Check if running on CircleCI or locally
let args = process.env.CIRCLECI ? ["--e2e"] : ["--e2e-local"];
if(useIsoGit) args = [...args, '--useIsoGit'];
if(useOffline) args = [...args, '--useOffline'];
if(useIsoGit) args = [...args, '--use-isogit'];
if(useOffline) args = [...args, '--use-offline'];
// Set display size
const windowSize = "--window-size=1920,1080";
args = [...args, windowSize];
const windowSize = "--window-size=1000,1000";
args = [...args];
switch (type) {
case 'Windows_NT':
@ -92,6 +92,7 @@ module.exports = {
break;
}
console.log('binaryPath', binaryPath);
return {
binary: binaryPath,
args: args

@ -5,7 +5,9 @@ const tests = {
before: function (browser: NightwatchBrowser, done: VoidFunction) {
done()
},
'start gist': !function (browser: NightwatchBrowser) {
'start gist': function (browser: NightwatchBrowser) {
browser.end()
/*
browser
.waitForElementVisible('*[data-id="remixIdeIconPanel"]', 10000)
.waitForElementVisible('*[data-id="landingPageImportFromGist"]')
@ -25,6 +27,7 @@ const tests = {
.click('[data-id="treeViewLitreeViewItemcontracts"]')
.openFile('contracts/3_Ballot.sol')
.end()
*/
}
}

@ -11,9 +11,11 @@ module.exports = {
.waitForElementVisible('*[data-id="remixIdeIconPanel"]', 10000)
.waitForElementVisible('button[data-id="landingPageImportFromTemplate"]')
.click('button[data-id="landingPageImportFromTemplate"]')
.waitForElementPresent('*[data-id="create-remixDefault"]')
.scrollAndClick('*[data-id="create-remixDefault"]')
.waitForElementVisible('*[data-id="modalDialogCustomPromptTextCreate"]')
.waitForElementPresent('[data-id="fileSystemModalDialogModalFooter-react"] .modal-ok')
.click('[data-id="fileSystemModalDialogModalFooter-react"] .modal-ok')
.waitForElementPresent('[data-id="TemplatesSelectionModalDialogContainer-react"] .modal-ok')
.click('[data-id="TemplatesSelectionModalDialogContainer-react"] .modal-ok')
.pause(3000)
.windowHandles(function (result) {
console.log(result.value)

Loading…
Cancel
Save