parent
0483d13e56
commit
1d97df570c
@ -1,16 +0,0 @@ |
|||||||
{ |
|
||||||
"env": { |
|
||||||
"browser": true, |
|
||||||
"es6": true, |
|
||||||
"node": true |
|
||||||
}, |
|
||||||
"extends": [ |
|
||||||
"eslint:recommended", |
|
||||||
"plugin:@typescript-eslint/eslint-recommended", |
|
||||||
"plugin:@typescript-eslint/recommended", |
|
||||||
"plugin:import/recommended", |
|
||||||
"plugin:import/electron", |
|
||||||
"plugin:import/typescript" |
|
||||||
], |
|
||||||
"parser": "@typescript-eslint/parser" |
|
||||||
} |
|
@ -1,92 +0,0 @@ |
|||||||
# Logs |
|
||||||
logs |
|
||||||
*.log |
|
||||||
npm-debug.log* |
|
||||||
yarn-debug.log* |
|
||||||
yarn-error.log* |
|
||||||
lerna-debug.log* |
|
||||||
|
|
||||||
# Diagnostic reports (https://nodejs.org/api/report.html) |
|
||||||
report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json |
|
||||||
|
|
||||||
# Runtime data |
|
||||||
pids |
|
||||||
*.pid |
|
||||||
*.seed |
|
||||||
*.pid.lock |
|
||||||
.DS_Store |
|
||||||
|
|
||||||
# Directory for instrumented libs generated by jscoverage/JSCover |
|
||||||
lib-cov |
|
||||||
|
|
||||||
# Coverage directory used by tools like istanbul |
|
||||||
coverage |
|
||||||
*.lcov |
|
||||||
|
|
||||||
# nyc test coverage |
|
||||||
.nyc_output |
|
||||||
|
|
||||||
# node-waf configuration |
|
||||||
.lock-wscript |
|
||||||
|
|
||||||
# Compiled binary addons (https://nodejs.org/api/addons.html) |
|
||||||
build/Release |
|
||||||
|
|
||||||
# Dependency directories |
|
||||||
node_modules/ |
|
||||||
jspm_packages/ |
|
||||||
|
|
||||||
# TypeScript v1 declaration files |
|
||||||
typings/ |
|
||||||
|
|
||||||
# TypeScript cache |
|
||||||
*.tsbuildinfo |
|
||||||
|
|
||||||
# Optional npm cache directory |
|
||||||
.npm |
|
||||||
|
|
||||||
# Optional eslint cache |
|
||||||
.eslintcache |
|
||||||
|
|
||||||
# Optional REPL history |
|
||||||
.node_repl_history |
|
||||||
|
|
||||||
# Output of 'npm pack' |
|
||||||
*.tgz |
|
||||||
|
|
||||||
# Yarn Integrity file |
|
||||||
.yarn-integrity |
|
||||||
|
|
||||||
# dotenv environment variables file |
|
||||||
.env |
|
||||||
.env.test |
|
||||||
|
|
||||||
# parcel-bundler cache (https://parceljs.org/) |
|
||||||
.cache |
|
||||||
|
|
||||||
# next.js build output |
|
||||||
.next |
|
||||||
|
|
||||||
# nuxt.js build output |
|
||||||
.nuxt |
|
||||||
|
|
||||||
# vuepress build output |
|
||||||
.vuepress/dist |
|
||||||
|
|
||||||
# Serverless directories |
|
||||||
.serverless/ |
|
||||||
|
|
||||||
# FuseBox cache |
|
||||||
.fusebox/ |
|
||||||
|
|
||||||
# DynamoDB Local files |
|
||||||
.dynamodb/ |
|
||||||
|
|
||||||
# Webpack |
|
||||||
.webpack/ |
|
||||||
|
|
||||||
# Vite |
|
||||||
.vite/ |
|
||||||
|
|
||||||
# Electron-Forge |
|
||||||
out/ |
|
@ -1,35 +0,0 @@ |
|||||||
import type { ForgeConfig } from '@electron-forge/shared-types'; |
|
||||||
import { MakerSquirrel } from '@electron-forge/maker-squirrel'; |
|
||||||
import { MakerZIP } from '@electron-forge/maker-zip'; |
|
||||||
import { MakerDeb } from '@electron-forge/maker-deb'; |
|
||||||
import { MakerRpm } from '@electron-forge/maker-rpm'; |
|
||||||
import { WebpackPlugin } from '@electron-forge/plugin-webpack'; |
|
||||||
|
|
||||||
import { mainConfig } from './webpack.main.config'; |
|
||||||
import { rendererConfig } from './webpack.renderer.config'; |
|
||||||
|
|
||||||
const config: ForgeConfig = { |
|
||||||
packagerConfig: {}, |
|
||||||
rebuildConfig: {}, |
|
||||||
makers: [new MakerSquirrel({}), new MakerZIP({}, ['darwin']), new MakerRpm({}), new MakerDeb({})], |
|
||||||
plugins: [ |
|
||||||
new WebpackPlugin({ |
|
||||||
mainConfig, |
|
||||||
renderer: { |
|
||||||
config: rendererConfig, |
|
||||||
entryPoints: [ |
|
||||||
{ |
|
||||||
html: './src/index.html', |
|
||||||
js: './src/renderer.ts', |
|
||||||
name: 'main_window', |
|
||||||
preload: { |
|
||||||
js: './src/preload.ts', |
|
||||||
}, |
|
||||||
}, |
|
||||||
], |
|
||||||
}, |
|
||||||
}), |
|
||||||
], |
|
||||||
}; |
|
||||||
|
|
||||||
export default config; |
|
@ -1,55 +0,0 @@ |
|||||||
{ |
|
||||||
"name": "1test", |
|
||||||
"productName": "1test", |
|
||||||
"version": "1.0.0", |
|
||||||
"description": "My Electron application description", |
|
||||||
"main": ".webpack/main", |
|
||||||
"scripts": { |
|
||||||
"start": "electron-forge start", |
|
||||||
"package": "electron-forge package", |
|
||||||
"make": "electron-forge make", |
|
||||||
"publish": "electron-forge publish", |
|
||||||
"lint": "eslint --ext .ts,.tsx ." |
|
||||||
}, |
|
||||||
"keywords": [], |
|
||||||
"author": { |
|
||||||
"name": "filip mertens", |
|
||||||
"email": "filip.mertens@ethereum.org" |
|
||||||
}, |
|
||||||
"license": "MIT", |
|
||||||
"devDependencies": { |
|
||||||
"@electron-forge/cli": "^6.1.1", |
|
||||||
"@electron-forge/maker-deb": "^6.1.1", |
|
||||||
"@electron-forge/maker-rpm": "^6.1.1", |
|
||||||
"@electron-forge/maker-squirrel": "^6.1.1", |
|
||||||
"@electron-forge/maker-zip": "^6.1.1", |
|
||||||
"@electron-forge/plugin-webpack": "^6.1.1", |
|
||||||
"@types/react": "^18.2.8", |
|
||||||
"@types/react-dom": "^18.2.4", |
|
||||||
"@typescript-eslint/eslint-plugin": "^5.0.0", |
|
||||||
"@typescript-eslint/parser": "^5.0.0", |
|
||||||
"@vercel/webpack-asset-relocator-loader": "1.7.3", |
|
||||||
"css-loader": "^6.0.0", |
|
||||||
"electron": "25.0.0", |
|
||||||
"eslint": "^8.0.1", |
|
||||||
"eslint-plugin-import": "^2.25.0", |
|
||||||
"fork-ts-checker-webpack-plugin": "^7.2.13", |
|
||||||
"node-loader": "^2.0.0", |
|
||||||
"style-loader": "^3.0.0", |
|
||||||
"ts-loader": "^9.2.2", |
|
||||||
"ts-node": "^10.0.0", |
|
||||||
"typescript": "~4.5.4" |
|
||||||
}, |
|
||||||
"dependencies": { |
|
||||||
"chokidar": "^3.5.3", |
|
||||||
"electron-squirrel-startup": "^1.0.0", |
|
||||||
"fix-path": "^4.0.0", |
|
||||||
"isomorphic-git": "^1.24.0", |
|
||||||
"node-pty": "0.10.1", |
|
||||||
"react": "^18.2.0", |
|
||||||
"react-dom": "^18.2.0", |
|
||||||
"xterm": "^5.1.0", |
|
||||||
"xterm-addon-fit": "^0.7.0", |
|
||||||
"xterm-for-react": "^1.0.4" |
|
||||||
} |
|
||||||
} |
|
@ -1,14 +0,0 @@ |
|||||||
import * as ReactDOM from 'react-dom'; |
|
||||||
import { RemixUiXterminals } from './remix/ui/remix-ui-xterminals'; |
|
||||||
import { RemixUIFileDialog } from './remix/ui/remix-ui-filedialog'; |
|
||||||
import { xterm, filePanel } from './renderer'; |
|
||||||
|
|
||||||
import { createRoot } from 'react-dom/client'; |
|
||||||
const container = document.getElementById('app'); |
|
||||||
const root = createRoot(container); // createRoot(container!) if you use TypeScript
|
|
||||||
root.render( |
|
||||||
<> |
|
||||||
<RemixUiXterminals plugin={xterm} /> |
|
||||||
<RemixUIFileDialog plugin={filePanel} /> |
|
||||||
</> |
|
||||||
) |
|
@ -1,39 +0,0 @@ |
|||||||
import { Engine, PluginManager } from '@remixproject/engine'; |
|
||||||
import { BrowserWindow, ipcMain } from 'electron'; |
|
||||||
import { FSPlugin } from './fsPlugin'; |
|
||||||
import { GitPlugin } from './gitPlugin'; |
|
||||||
import { app } from 'electron'; |
|
||||||
import { XtermPlugin } from './xtermPlugin'; |
|
||||||
|
|
||||||
|
|
||||||
const engine = new Engine() |
|
||||||
const appManager = new PluginManager() |
|
||||||
const fsPlugin = new FSPlugin() |
|
||||||
const gitPlugin = new GitPlugin() |
|
||||||
const xtermPlugin = new XtermPlugin() |
|
||||||
|
|
||||||
engine.register(appManager) |
|
||||||
engine.register(fsPlugin) |
|
||||||
engine.register(gitPlugin) |
|
||||||
engine.register(xtermPlugin)
|
|
||||||
|
|
||||||
appManager.activatePlugin('fs') |
|
||||||
appManager.activatePlugin('git') |
|
||||||
appManager.activatePlugin('xterm') |
|
||||||
|
|
||||||
ipcMain.handle('manager:activatePlugin', async (event, plugin) => { |
|
||||||
console.log('manager:activatePlugin', plugin, event.sender.id) |
|
||||||
return await appManager.call(plugin, 'createClient', event.sender.id) |
|
||||||
}) |
|
||||||
|
|
||||||
ipcMain.handle('getWebContentsID', (event, message) => { |
|
||||||
return event.sender.id |
|
||||||
}) |
|
||||||
|
|
||||||
|
|
||||||
app.on('before-quit', async () => { |
|
||||||
console.log('before-quit') |
|
||||||
await appManager.call('fs', 'closeWatch') |
|
||||||
app.quit() |
|
||||||
}) |
|
||||||
|
|
@ -1,134 +0,0 @@ |
|||||||
import fs from 'fs/promises' |
|
||||||
import { Stats } from "fs"; |
|
||||||
import { Profile } from "@remixproject/plugin-utils"; |
|
||||||
import chokidar from 'chokidar' |
|
||||||
import { ElectronBasePlugin, ElectronBasePluginClient, ElectronBasePluginInterface } from "./lib/electronBasePlugin"; |
|
||||||
import { dialog } from 'electron'; |
|
||||||
|
|
||||||
const profile: Profile = { |
|
||||||
displayName: 'fs', |
|
||||||
name: 'fs', |
|
||||||
description: 'fs' |
|
||||||
} |
|
||||||
|
|
||||||
export class FSPlugin extends ElectronBasePlugin { |
|
||||||
clients: FSPluginClient[] = [] |
|
||||||
constructor() { |
|
||||||
super(profile, clientProfile, FSPluginClient) |
|
||||||
this.methods = [...super.methods, 'closeWatch'] |
|
||||||
} |
|
||||||
|
|
||||||
async closeWatch(): Promise<void> { |
|
||||||
for (const client of this.clients) { |
|
||||||
await client.closeWatch() |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
} |
|
||||||
|
|
||||||
const clientProfile: Profile = { |
|
||||||
name: 'fs', |
|
||||||
displayName: 'fs', |
|
||||||
description: 'fs', |
|
||||||
methods: ['readdir', 'readFile', 'writeFile', 'mkdir', 'rmdir', 'unlink', 'rename', 'stat', 'exists', 'currentPath', 'watch', 'closeWatch', 'setWorkingDir'] |
|
||||||
} |
|
||||||
|
|
||||||
class FSPluginClient extends ElectronBasePluginClient { |
|
||||||
watcher: chokidar.FSWatcher |
|
||||||
workingDir: string = '/Volumes/bunsen/code/empty/' |
|
||||||
|
|
||||||
constructor(webContentsId: number, profile: Profile) { |
|
||||||
super(webContentsId, profile) |
|
||||||
this.onload(() => { |
|
||||||
console.log('fsPluginClient onload') |
|
||||||
}) |
|
||||||
} |
|
||||||
|
|
||||||
async readdir(path: string): Promise<string[]> { |
|
||||||
// call node fs.readdir
|
|
||||||
return fs.readdir(this.fixPath(path)) |
|
||||||
} |
|
||||||
|
|
||||||
async readFile(path: string): Promise<string> { |
|
||||||
return fs.readFile(this.fixPath(path), 'utf8') |
|
||||||
} |
|
||||||
|
|
||||||
async writeFile(path: string, content: string): Promise<void> { |
|
||||||
return fs.writeFile(this.fixPath(path), content, 'utf8') |
|
||||||
} |
|
||||||
|
|
||||||
async mkdir(path: string): Promise<void> { |
|
||||||
return fs.mkdir(this.fixPath(path)) |
|
||||||
} |
|
||||||
|
|
||||||
async rmdir(path: string): Promise<void> { |
|
||||||
return fs.rmdir(this.fixPath(path)) |
|
||||||
} |
|
||||||
|
|
||||||
async unlink(path: string): Promise<void> { |
|
||||||
return fs.unlink(this.fixPath(path)) |
|
||||||
} |
|
||||||
|
|
||||||
async rename(oldPath: string, newPath: string): Promise<void> { |
|
||||||
return fs.rename(this.fixPath(oldPath), this.fixPath(newPath)) |
|
||||||
} |
|
||||||
|
|
||||||
async stat(path: string): Promise<any> { |
|
||||||
const stat = await fs.stat(path) |
|
||||||
//console.log('stat', path, stat)
|
|
||||||
const isDirectory = stat.isDirectory() |
|
||||||
return { |
|
||||||
...stat, |
|
||||||
isDirectoryValue: isDirectory |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
async lstat(path: string): Promise<any> { |
|
||||||
const lstat = await fs.lstat(this.fixPath(path)) |
|
||||||
return lstat |
|
||||||
} |
|
||||||
|
|
||||||
|
|
||||||
async exists(path: string): Promise<boolean> { |
|
||||||
return fs.access(this.fixPath(path)).then(() => true).catch(() => false) |
|
||||||
} |
|
||||||
|
|
||||||
async currentPath(): Promise<string> { |
|
||||||
return process.cwd() |
|
||||||
} |
|
||||||
|
|
||||||
async watch(path: string): Promise<void> { |
|
||||||
if (this.watcher) this.watcher.close() |
|
||||||
this.watcher = |
|
||||||
chokidar.watch(this.fixPath(path)).on('change', (path, stats) => { |
|
||||||
console.log('change', path, stats) |
|
||||||
this.emit('change', path, stats) |
|
||||||
}) |
|
||||||
} |
|
||||||
|
|
||||||
async closeWatch(): Promise<void> { |
|
||||||
console.log('closing Watcher', this.webContentsId) |
|
||||||
if (this.watcher) this.watcher.close() |
|
||||||
} |
|
||||||
|
|
||||||
async setWorkingDir(): Promise<void> { |
|
||||||
const dirs = dialog.showOpenDialogSync(this.window, { |
|
||||||
properties: ['openDirectory'] |
|
||||||
}) |
|
||||||
if (dirs && dirs.length > 0) |
|
||||||
this.workingDir = dirs[0] |
|
||||||
|
|
||||||
this.emit('workingDirChanged', dirs[0]) |
|
||||||
} |
|
||||||
|
|
||||||
fixPath(path: string): string { |
|
||||||
|
|
||||||
if (path.startsWith('/')) { |
|
||||||
path = path.slice(1) |
|
||||||
} |
|
||||||
path = this.workingDir + path |
|
||||||
return path |
|
||||||
} |
|
||||||
|
|
||||||
|
|
||||||
} |
|
@ -1,53 +0,0 @@ |
|||||||
import { PluginClient } from "@remixproject/plugin"; |
|
||||||
import { Profile } from "@remixproject/plugin-utils"; |
|
||||||
import { spawn } from "child_process"; |
|
||||||
import { ElectronBasePlugin, ElectronBasePluginClient } from "./lib/electronBasePlugin"; |
|
||||||
|
|
||||||
const profile: Profile = { |
|
||||||
name: 'git', |
|
||||||
displayName: 'Git', |
|
||||||
description: 'Git plugin', |
|
||||||
} |
|
||||||
|
|
||||||
export class GitPlugin extends ElectronBasePlugin { |
|
||||||
client: PluginClient |
|
||||||
constructor() { |
|
||||||
super(profile, clientProfile, GitPluginClient) |
|
||||||
} |
|
||||||
|
|
||||||
} |
|
||||||
|
|
||||||
const clientProfile: Profile = { |
|
||||||
name: 'git', |
|
||||||
displayName: 'Git', |
|
||||||
description: 'Git plugin', |
|
||||||
methods: ['log', 'status', 'add', 'commit', 'push', 'pull', 'clone', 'checkout', 'branch', 'merge', 'reset', 'revert', 'diff', 'stash', 'apply', 'cherryPick', 'rebase', 'tag', 'fetch', 'remote', 'config', 'show', 'init', 'help', 'version'] |
|
||||||
} |
|
||||||
|
|
||||||
class GitPluginClient extends ElectronBasePluginClient { |
|
||||||
|
|
||||||
constructor(webContentsId: number, profile: Profile) { |
|
||||||
super(webContentsId, profile) |
|
||||||
this.onload(() => { |
|
||||||
console.log('GitPluginClient onload') |
|
||||||
}) |
|
||||||
} |
|
||||||
|
|
||||||
async log(path: string): Promise<string> { |
|
||||||
const log = spawn('git', ['log'], { |
|
||||||
cwd: path, |
|
||||||
env: { |
|
||||||
NODE_ENV: 'production', |
|
||||||
PATH: process.env.PATH, |
|
||||||
}, |
|
||||||
}) |
|
||||||
|
|
||||||
return new Promise((resolve, reject) => { |
|
||||||
log.stdout.on('data', (data) => { |
|
||||||
resolve(data.toString()) |
|
||||||
}) |
|
||||||
}) |
|
||||||
} |
|
||||||
|
|
||||||
|
|
||||||
} |
|
@ -1,54 +0,0 @@ |
|||||||
import { Plugin } from "@remixproject/engine"; |
|
||||||
import { PluginClient } from "@remixproject/plugin"; |
|
||||||
import { Profile } from "@remixproject/plugin-utils"; |
|
||||||
import { BrowserWindow } from "electron"; |
|
||||||
import { createElectronClient } from "./electronPluginClient"; |
|
||||||
|
|
||||||
export interface ElectronBasePluginInterface { |
|
||||||
createClient(windowId: number): Promise<boolean>; |
|
||||||
closeClient(windowId: number): Promise<boolean>; |
|
||||||
} |
|
||||||
|
|
||||||
export abstract class ElectronBasePlugin extends Plugin implements ElectronBasePluginInterface { |
|
||||||
clients: ElectronBasePluginClient[] = []; |
|
||||||
clientClass: any |
|
||||||
clientProfile: Profile |
|
||||||
constructor(profile: Profile, clientProfile: Profile, clientClass: any) { |
|
||||||
super(profile); |
|
||||||
this.methods = ['createClient', 'closeClient']; |
|
||||||
this.clientClass = clientClass; |
|
||||||
this.clientProfile = clientProfile; |
|
||||||
} |
|
||||||
|
|
||||||
async createClient(webContentsId: number): Promise<boolean> { |
|
||||||
if (this.clients.find(client => client.webContentsId === webContentsId)) return true |
|
||||||
const client = new this.clientClass(webContentsId, this.clientProfile); |
|
||||||
this.clients.push(client); |
|
||||||
return new Promise((resolve, reject) => { |
|
||||||
client.onload(() => { |
|
||||||
resolve(true) |
|
||||||
}) |
|
||||||
}) |
|
||||||
} |
|
||||||
async closeClient(windowId: number): Promise<boolean> { |
|
||||||
this.clients = this.clients.filter(client => client.webContentsId !== windowId) |
|
||||||
return true; |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
export class ElectronBasePluginClient extends PluginClient { |
|
||||||
window: Electron.BrowserWindow; |
|
||||||
webContentsId: number; |
|
||||||
constructor(webcontentsid: number, profile: Profile, methods: string[] = []) { |
|
||||||
super(); |
|
||||||
this.methods = profile.methods; |
|
||||||
this.webContentsId = webcontentsid; |
|
||||||
BrowserWindow.getAllWindows().forEach((window) => { |
|
||||||
if (window.webContents.id === webcontentsid) { |
|
||||||
this.window = window; |
|
||||||
} |
|
||||||
}); |
|
||||||
createElectronClient(this, profile, this.window); |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
@ -1,34 +0,0 @@ |
|||||||
import { ClientConnector, connectClient, applyApi, Client, PluginClient } from '@remixproject/plugin' |
|
||||||
import type { Message, Api, ApiMap, Profile } from '@remixproject/plugin-utils' |
|
||||||
import { IRemixApi } from '@remixproject/plugin-api' |
|
||||||
import { ipcMain } from 'electron' |
|
||||||
|
|
||||||
export class ElectronPluginClientConnector implements ClientConnector { |
|
||||||
|
|
||||||
constructor(public profile: Profile, public browserWindow: Electron.BrowserWindow) {
|
|
||||||
} |
|
||||||
|
|
||||||
/** Send a message to the engine */ |
|
||||||
send(message: Partial<Message>) { |
|
||||||
this.browserWindow.webContents.send(this.profile.name + ':send', message) |
|
||||||
} |
|
||||||
|
|
||||||
/** Listen to message from the engine */ |
|
||||||
on(cb: (message: Partial<Message>) => void) { |
|
||||||
ipcMain.on(this.profile.name + ':on:' + this.browserWindow.webContents.id, (event, message) => { |
|
||||||
cb(message) |
|
||||||
}) |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
export const createElectronClient = < |
|
||||||
P extends Api, |
|
||||||
App extends ApiMap = Readonly<IRemixApi> |
|
||||||
>(client: PluginClient<P, App> = new PluginClient(), profile: Profile |
|
||||||
, window: Electron.BrowserWindow |
|
||||||
): Client<P, App> => { |
|
||||||
const c = client as any |
|
||||||
connectClient(new ElectronPluginClientConnector(profile, window), c) |
|
||||||
applyApi(c) |
|
||||||
return c |
|
||||||
} |
|
@ -1,18 +0,0 @@ |
|||||||
import {app, Menu, BrowserWindow} from 'electron'; |
|
||||||
import { createWindow } from '../../'; |
|
||||||
|
|
||||||
const commands: Record<string, (focusedWindow?: BrowserWindow) => void> = { |
|
||||||
'window:new': () => { |
|
||||||
// If window is created on the same tick, it will consume event too
|
|
||||||
setTimeout(createWindow, 0); |
|
||||||
}, |
|
||||||
|
|
||||||
}; |
|
||||||
|
|
||||||
|
|
||||||
export const execCommand = (command: string, focusedWindow?: BrowserWindow) => { |
|
||||||
const fn = commands[command]; |
|
||||||
if (fn) { |
|
||||||
fn(focusedWindow); |
|
||||||
} |
|
||||||
}; |
|
@ -1,36 +0,0 @@ |
|||||||
import {BrowserWindow, MenuItemConstructorOptions} from 'electron'; |
|
||||||
|
|
||||||
export default ( |
|
||||||
commandKeys: Record<string, string>, |
|
||||||
execCommand: (command: string, focusedWindow?: BrowserWindow) => void |
|
||||||
): MenuItemConstructorOptions => { |
|
||||||
const isMac = process.platform === 'darwin'; |
|
||||||
|
|
||||||
return { |
|
||||||
label: isMac ? 'Shell' : 'File', |
|
||||||
submenu: [ |
|
||||||
{ |
|
||||||
label: 'New Window', |
|
||||||
accelerator: commandKeys['window:new'], |
|
||||||
click(item, focusedWindow) { |
|
||||||
execCommand('window:new', focusedWindow); |
|
||||||
} |
|
||||||
}, |
|
||||||
{ |
|
||||||
type: 'separator' |
|
||||||
}, |
|
||||||
{ |
|
||||||
label: 'Close', |
|
||||||
accelerator: commandKeys['pane:close'], |
|
||||||
click(item, focusedWindow) { |
|
||||||
execCommand('pane:close', focusedWindow); |
|
||||||
} |
|
||||||
}, |
|
||||||
{ |
|
||||||
label: isMac ? 'Close Window' : 'Quit', |
|
||||||
role: 'close', |
|
||||||
accelerator: commandKeys['window:close'] |
|
||||||
} |
|
||||||
] |
|
||||||
}; |
|
||||||
}; |
|
@ -1,75 +0,0 @@ |
|||||||
import { Plugin } from "@remixproject/engine"; |
|
||||||
import { PluginClient } from "@remixproject/plugin"; |
|
||||||
import { Profile } from "@remixproject/plugin-utils"; |
|
||||||
import { spawn } from "child_process"; |
|
||||||
import { createElectronClient } from "./lib/electronPluginClient"; |
|
||||||
|
|
||||||
import os from 'os'; |
|
||||||
import * as pty from "node-pty" |
|
||||||
import { ElectronBasePlugin, ElectronBasePluginClient } from "./lib/electronBasePlugin"; |
|
||||||
|
|
||||||
const profile: Profile = { |
|
||||||
name: 'xterm', |
|
||||||
displayName: 'xterm', |
|
||||||
description: 'xterm plugin', |
|
||||||
} |
|
||||||
|
|
||||||
export class XtermPlugin extends ElectronBasePlugin { |
|
||||||
client: PluginClient |
|
||||||
constructor() { |
|
||||||
super(profile, clientProfile, XtermPluginClient) |
|
||||||
} |
|
||||||
|
|
||||||
} |
|
||||||
|
|
||||||
const clientProfile: Profile = { |
|
||||||
name: 'xterm', |
|
||||||
displayName: 'xterm', |
|
||||||
description: 'xterm plugin', |
|
||||||
methods: ['createTerminal', 'close', 'keystroke'] |
|
||||||
} |
|
||||||
|
|
||||||
class XtermPluginClient extends ElectronBasePluginClient { |
|
||||||
|
|
||||||
terminals: pty.IPty[] = [] |
|
||||||
constructor(webContentsId: number, profile: Profile) { |
|
||||||
super(webContentsId, profile) |
|
||||||
this.onload(() => { |
|
||||||
console.log('XtermPluginClient onload') |
|
||||||
}) |
|
||||||
} |
|
||||||
|
|
||||||
async keystroke(key: string, pid: number): Promise<void> { |
|
||||||
this.terminals[pid].write(key) |
|
||||||
} |
|
||||||
|
|
||||||
async createTerminal(path?: string): Promise<number> { |
|
||||||
const shell = os.platform() === 'win32' ? 'powershell.exe' : 'bash'; |
|
||||||
|
|
||||||
const ptyProcess = pty.spawn(shell, [], { |
|
||||||
name: 'xterm-color', |
|
||||||
cols: 80, |
|
||||||
rows: 30, |
|
||||||
cwd: path || process.cwd(), |
|
||||||
env: process.env |
|
||||||
}); |
|
||||||
|
|
||||||
ptyProcess.onData((data: string) => { |
|
||||||
this.sendData(data, ptyProcess.pid); |
|
||||||
}) |
|
||||||
this.terminals[ptyProcess.pid] = ptyProcess |
|
||||||
|
|
||||||
return ptyProcess.pid |
|
||||||
} |
|
||||||
|
|
||||||
async close(pid: number): Promise<void> { |
|
||||||
this.terminals[pid].kill() |
|
||||||
delete this.terminals[pid] |
|
||||||
this.emit('close', pid) |
|
||||||
} |
|
||||||
|
|
||||||
async sendData(data: string, pid: number) { |
|
||||||
this.emit('data', data, pid) |
|
||||||
} |
|
||||||
|
|
||||||
} |
|
@ -1,15 +0,0 @@ |
|||||||
export interface IElectronAPI { |
|
||||||
activatePlugin: (name: string) => Promise<boolean> |
|
||||||
plugins: { |
|
||||||
name: string |
|
||||||
on: (cb: any) => void |
|
||||||
send: (message: Partial<Message>) => void |
|
||||||
}[] |
|
||||||
getWindowId: () => string | undefined |
|
||||||
} |
|
||||||
|
|
||||||
declare global { |
|
||||||
interface Window { |
|
||||||
electronAPI: IElectronAPI |
|
||||||
} |
|
||||||
} |
|
@ -1,7 +0,0 @@ |
|||||||
body { |
|
||||||
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, |
|
||||||
Arial, sans-serif; |
|
||||||
margin: auto; |
|
||||||
max-width: 38rem; |
|
||||||
padding: 2rem; |
|
||||||
} |
|
@ -1,11 +0,0 @@ |
|||||||
<!DOCTYPE html> |
|
||||||
<html> |
|
||||||
<head> |
|
||||||
<meta charset="UTF-8" /> |
|
||||||
<title>Hello Terminal!</title> |
|
||||||
|
|
||||||
</head> |
|
||||||
<body> |
|
||||||
<div id="app"></div> |
|
||||||
</body> |
|
||||||
</html> |
|
@ -1,302 +0,0 @@ |
|||||||
import { app, BrowserWindow, Menu } from 'electron'; |
|
||||||
import path from 'path'; |
|
||||||
import fixPath from 'fix-path'; |
|
||||||
import { add } from 'lodash'; |
|
||||||
// This allows TypeScript to pick up the magic constants that's auto-generated by Forge's Webpack
|
|
||||||
// plugin that tells the Electron app where to look for the Webpack-bundled app code (depending on
|
|
||||||
// whether you're running in development or production).
|
|
||||||
declare const MAIN_WINDOW_WEBPACK_ENTRY: string; |
|
||||||
declare const MAIN_WINDOW_PRELOAD_WEBPACK_ENTRY: string; |
|
||||||
|
|
||||||
fixPath(); |
|
||||||
|
|
||||||
// Handle creating/removing shortcuts on Windows when installing/uninstalling.
|
|
||||||
if (require('electron-squirrel-startup')) { |
|
||||||
app.quit(); |
|
||||||
} |
|
||||||
export let mainWindow: BrowserWindow; |
|
||||||
export const createWindow = (): void => { |
|
||||||
// generate unique id for this window
|
|
||||||
const id = Date.now().toString(); |
|
||||||
|
|
||||||
// Create the browser window.
|
|
||||||
mainWindow = new BrowserWindow({ |
|
||||||
height: 800, |
|
||||||
width: 1024, |
|
||||||
webPreferences: { |
|
||||||
preload: MAIN_WINDOW_PRELOAD_WEBPACK_ENTRY, |
|
||||||
additionalArguments: [`--window-id=${id}`], |
|
||||||
}, |
|
||||||
}); |
|
||||||
|
|
||||||
// and load the index.html of the app.
|
|
||||||
mainWindow.loadURL(MAIN_WINDOW_WEBPACK_ENTRY); |
|
||||||
mainWindow.maximize(); |
|
||||||
// Open the DevTools.
|
|
||||||
mainWindow.webContents.openDevTools(); |
|
||||||
|
|
||||||
BrowserWindow.getAllWindows().forEach(window => { |
|
||||||
console.log('window IDS created', window.webContents.id) |
|
||||||
}) |
|
||||||
require('./electron/engine') |
|
||||||
}; |
|
||||||
|
|
||||||
// This method will be called when Electron has finished
|
|
||||||
// initialization and is ready to create browser windows.
|
|
||||||
// Some APIs can only be used after this event occurs.
|
|
||||||
//app.on('ready', createWindow);
|
|
||||||
|
|
||||||
// when a window is closed event
|
|
||||||
app.on('web-contents-created', (event, contents) => { |
|
||||||
console.log('web-contents-created', contents.id) |
|
||||||
}) |
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// Quit when all windows are closed, except on macOS. There, it's common
|
|
||||||
// for applications and their menu bar to stay active until the user quits
|
|
||||||
// explicitly with Cmd + Q.
|
|
||||||
app.on('window-all-closed', () => { |
|
||||||
if (process.platform !== 'darwin') { |
|
||||||
app.quit(); |
|
||||||
} |
|
||||||
}); |
|
||||||
|
|
||||||
app.on('activate', () => { |
|
||||||
// On OS X it's common to re-create a window in the app when the
|
|
||||||
// dock icon is clicked and there are no other windows open.
|
|
||||||
if (BrowserWindow.getAllWindows().length === 0) { |
|
||||||
createWindow(); |
|
||||||
} |
|
||||||
}); |
|
||||||
|
|
||||||
// In this file you can include the rest of your app's specific main process
|
|
||||||
// code. You can also put them in separate files and import them here
|
|
||||||
|
|
||||||
|
|
||||||
const isMac = process.platform === 'darwin' |
|
||||||
|
|
||||||
import shellMenu from './electron/menus/shell'; |
|
||||||
import { execCommand } from './electron/menus/commands'; |
|
||||||
|
|
||||||
const commandKeys: Record<string, string> = { |
|
||||||
'tab:new': 'CmdOrCtrl+T', |
|
||||||
'window:new': 'CmdOrCtrl+N', |
|
||||||
'pane:splitDown': 'CmdOrCtrl+D', |
|
||||||
'pane:splitRight': 'CmdOrCtrl+E', |
|
||||||
'pane:close': 'CmdOrCtrl+W', |
|
||||||
'window:close': 'CmdOrCtrl+Q', |
|
||||||
}; |
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
const menu = [shellMenu(commandKeys, execCommand)] |
|
||||||
Menu.setApplicationMenu(Menu.buildFromTemplate(menu)) |
|
||||||
|
|
||||||
import fs from 'fs/promises' |
|
||||||
import { readlink, stat } from 'fs'; |
|
||||||
//const menu = Menu.buildFromTemplate(shellMenu([], undefined))
|
|
||||||
//Menu.setApplicationMenu(menu)
|
|
||||||
|
|
||||||
const myFS = { |
|
||||||
promises: { |
|
||||||
readdir: async (path: string, options: any): Promise<string[]> => { |
|
||||||
// call node fs.readdir
|
|
||||||
//console.log('myFS.readdir', path, options)
|
|
||||||
const file = await fs.readdir(path, { |
|
||||||
encoding: 'utf8', |
|
||||||
}) |
|
||||||
//console.log('myFS.readdir', file)
|
|
||||||
return file |
|
||||||
}, |
|
||||||
|
|
||||||
readFile: async (path: string, options: any): Promise<string> => { |
|
||||||
//console.log('myFS.readFile', path, options)
|
|
||||||
const file = await (fs as any).readFile(path, options) |
|
||||||
//console.log('myFS.readFile', file)
|
|
||||||
return file |
|
||||||
|
|
||||||
|
|
||||||
}, |
|
||||||
|
|
||||||
async writeFile(path: string, content: string): Promise<void> { |
|
||||||
return fs.writeFile(path, content, 'utf8') |
|
||||||
}, |
|
||||||
|
|
||||||
async mkdir(path: string): Promise<void> { |
|
||||||
return fs.mkdir(path) |
|
||||||
}, |
|
||||||
|
|
||||||
async rmdir(path: string): Promise<void> { |
|
||||||
return fs.rmdir(path) |
|
||||||
}, |
|
||||||
|
|
||||||
async unlink(path: string): Promise<void> { |
|
||||||
return fs.unlink(path) |
|
||||||
}, |
|
||||||
|
|
||||||
async rename(oldPath: string, newPath: string): Promise<void> { |
|
||||||
return fs.rename(oldPath, newPath) |
|
||||||
}, |
|
||||||
|
|
||||||
async stat(path: string): Promise<any> { |
|
||||||
//console.log('myFS.stat', path)
|
|
||||||
const stat = await fs.stat(path) |
|
||||||
//console.log('myFS.stat', stat)
|
|
||||||
return stat |
|
||||||
}, |
|
||||||
|
|
||||||
async lstat(path: string): Promise<any> { |
|
||||||
const lstat = await fs.lstat(path) |
|
||||||
//console.log('myFS.stat', path, lstat)
|
|
||||||
return lstat |
|
||||||
}, |
|
||||||
|
|
||||||
readlink: async (path: string): Promise<string> => { |
|
||||||
return fs.readlink(path) |
|
||||||
}, |
|
||||||
symlink: async (target: string, path: string): Promise<void> => { |
|
||||||
return fs.symlink(target, path) |
|
||||||
} |
|
||||||
|
|
||||||
|
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
console.log('myFS', myFS) |
|
||||||
import git, { CommitObject, ReadCommitResult } from 'isomorphic-git' |
|
||||||
async function checkGit() { |
|
||||||
|
|
||||||
|
|
||||||
const files = await git.statusMatrix({ fs: myFS, dir: '/Volumes/bunsen/code/rmproject2/remix-project', filepaths: ['apps/1test/src/index.ts'] }); |
|
||||||
console.log('GIT', files) |
|
||||||
} |
|
||||||
|
|
||||||
|
|
||||||
//checkGit()
|
|
||||||
|
|
||||||
/* |
|
||||||
setInterval(() => { |
|
||||||
|
|
||||||
const startTime = Date.now() |
|
||||||
checkGit() |
|
||||||
.then(() => { |
|
||||||
console.log('checkGit', Date.now() - startTime) |
|
||||||
}) |
|
||||||
|
|
||||||
}, 3000) |
|
||||||
|
|
||||||
*/ |
|
||||||
/* |
|
||||||
git.add({ fs: myFS, dir: '/Volumes/bunsen/code/rmproject2/remix-project', filepath: 'test.txt' }).then(() => { |
|
||||||
console.log('git add') |
|
||||||
}).catch((e: any) => { |
|
||||||
console.log('git add error', e) |
|
||||||
}) |
|
||||||
|
|
||||||
git.log({ fs: myFS, dir: '/Volumes/bunsen/code/rmproject2/remix-project', depth:10 }).then((log: any) => { |
|
||||||
console.log('git log', log) |
|
||||||
}) |
|
||||||
*/ |
|
||||||
|
|
||||||
// run a shell command
|
|
||||||
import { exec } from 'child_process'; |
|
||||||
import { promisify } from 'util'; |
|
||||||
const execAsync = promisify(exec); |
|
||||||
|
|
||||||
const statusTransFormMatrix = (status: string) => { |
|
||||||
switch (status) { |
|
||||||
case '??': |
|
||||||
return [0, 2, 0] |
|
||||||
case 'A ': |
|
||||||
return [0, 2, 2] |
|
||||||
case 'M ': |
|
||||||
return [1, 2, 2] |
|
||||||
case 'MM': |
|
||||||
return [1, 2, 3] |
|
||||||
case ' M': |
|
||||||
return [1, 0, 1] |
|
||||||
case ' D': |
|
||||||
return [0, 2, 0] |
|
||||||
case 'D ': |
|
||||||
return [1, 0, 0] |
|
||||||
case 'AM': |
|
||||||
return [0, 2, 3] |
|
||||||
default: |
|
||||||
return [-1, -1, -1] |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
execAsync('git status --porcelain -uall', { cwd: '/Volumes/bunsen/code/rmproject2/remix-project' }).then(async (result: any) => { |
|
||||||
//console.log('git status --porcelain -uall', result.stdout)
|
|
||||||
// parse the result.stdout
|
|
||||||
const lines = result.stdout.split('\n') |
|
||||||
const files: any = [] |
|
||||||
const fileNames: any = [] |
|
||||||
//console.log('lines', lines)
|
|
||||||
lines.forEach((line: string) => { |
|
||||||
// get the first two characters of the line
|
|
||||||
const status = line.slice(0, 2) |
|
||||||
|
|
||||||
const file = line.split(' ').pop() |
|
||||||
|
|
||||||
//console.log('line', line)
|
|
||||||
if (status && file) { |
|
||||||
fileNames.push(file) |
|
||||||
files.push([ |
|
||||||
file, |
|
||||||
...statusTransFormMatrix(status) |
|
||||||
]) |
|
||||||
} |
|
||||||
} |
|
||||||
) |
|
||||||
// sort files by first column
|
|
||||||
files.sort((a: any, b: any) => { |
|
||||||
if (a[0] < b[0]) { |
|
||||||
return -1 |
|
||||||
} |
|
||||||
if (a[0] > b[0]) { |
|
||||||
return 1 |
|
||||||
} |
|
||||||
return 0 |
|
||||||
}) |
|
||||||
|
|
||||||
//console.log('files', files, files.length)
|
|
||||||
const iso = await git.statusMatrix({ fs: myFS, dir: '/Volumes/bunsen/code/rmproject2/remix-project', filepaths: fileNames }); |
|
||||||
//console.log('GIT', iso, iso.length)
|
|
||||||
}) |
|
||||||
|
|
||||||
git.log({ fs: myFS, dir: '/Volumes/bunsen/code/rmproject2/remix-project', depth:3 }).then((log: ReadCommitResult[]) => { |
|
||||||
log.forEach((commit: ReadCommitResult) => { |
|
||||||
console.log('commit', commit.commit.parent) |
|
||||||
}) |
|
||||||
}) |
|
||||||
|
|
||||||
// exec git log --pretty=format:"%h - %an, %ar : %s" -n 10
|
|
||||||
execAsync(`git log --pretty=format:'{ "oid":"%H", "message":"%s", "author":"%an", "email": "%ae", "timestamp":"%at", "tree": "%T", "committer": "%cn", "committer-email": "%ce", "committer-timestamp": "%ct", "parent": "%P" }' -n 3`, { cwd: '/Volumes/bunsen/code/rmproject2/remix-project' }).then(async (result: any) =>{ |
|
||||||
//console.log('git log', result.stdout)
|
|
||||||
const lines = result.stdout.split('\n') |
|
||||||
const commits: ReadCommitResult[] = [] |
|
||||||
lines.forEach((line: string) => { |
|
||||||
console.log('line', line) |
|
||||||
const data = JSON.parse(line) |
|
||||||
let commit:ReadCommitResult = {} as ReadCommitResult |
|
||||||
commit.oid = data.oid |
|
||||||
commit.commit = {} as CommitObject |
|
||||||
commit.commit.message = data.message |
|
||||||
commit.commit.tree = data.tree |
|
||||||
commit.commit.committer = {} as any |
|
||||||
commit.commit.committer.name = data.committer |
|
||||||
commit.commit.committer.email = data['committer-email'] |
|
||||||
commit.commit.committer.timestamp = data['committer-timestamp'] |
|
||||||
commit.commit.author = {} as any |
|
||||||
commit.commit.author.name = data.author |
|
||||||
commit.commit.author.email = data.email |
|
||||||
commit.commit.author.timestamp = data.timestamp |
|
||||||
commit.commit.parent = [data.parent] |
|
||||||
console.log('commit', commit) |
|
||||||
commits.push(commit) |
|
||||||
}) |
|
||||||
}) |
|
@ -1,43 +0,0 @@ |
|||||||
|
|
||||||
import { Message } from '@remixproject/plugin-utils' |
|
||||||
import { contextBridge, ipcRenderer } from 'electron' |
|
||||||
|
|
||||||
console.log('preload.ts') |
|
||||||
|
|
||||||
/* preload script needs statically defined API for each plugin */ |
|
||||||
|
|
||||||
const exposedPLugins = ['fs', 'git', 'xterm'] |
|
||||||
|
|
||||||
console.log('preload.ts', process) |
|
||||||
let webContentsId: number | undefined |
|
||||||
/* |
|
||||||
// get the window id from the process arguments
|
|
||||||
const windowIdFromArgs = process.argv.find(arg => arg.startsWith('--window-id=')) |
|
||||||
if (windowIdFromArgs) { |
|
||||||
[, windowId] = windowIdFromArgs.split('=') |
|
||||||
console.log('windowId', windowId, ) |
|
||||||
} |
|
||||||
*/ |
|
||||||
ipcRenderer.invoke('getWebContentsID').then((id: number) => { |
|
||||||
webContentsId = id |
|
||||||
console.log('getWebContentsID', webContentsId) |
|
||||||
}) |
|
||||||
|
|
||||||
contextBridge.exposeInMainWorld('electronAPI', { |
|
||||||
activatePlugin: (name: string) => { |
|
||||||
return ipcRenderer.invoke('manager:activatePlugin', name) |
|
||||||
}, |
|
||||||
|
|
||||||
getWindowId: () => ipcRenderer.invoke('getWindowID'), |
|
||||||
|
|
||||||
plugins: exposedPLugins.map(name => { |
|
||||||
return { |
|
||||||
name, |
|
||||||
on: (cb:any) => ipcRenderer.on(`${name}:send`, cb), |
|
||||||
send: (message: Partial<Message>) => { |
|
||||||
console.log('send', message, `${name}:on:${webContentsId}`) |
|
||||||
ipcRenderer.send(`${name}:on:${webContentsId}`, message) |
|
||||||
} |
|
||||||
} |
|
||||||
}) |
|
||||||
}) |
|
@ -1,97 +0,0 @@ |
|||||||
import { ElectronPlugin } from './lib/electronPlugin'; |
|
||||||
|
|
||||||
let workingDir = '/Volumes/bunsen/code/empty/' |
|
||||||
|
|
||||||
const fixPath = (path: string) => { |
|
||||||
/* |
|
||||||
// if it starts with /, it's an absolute path remove it
|
|
||||||
if (path.startsWith('/')) { |
|
||||||
path = path.slice(1) |
|
||||||
} |
|
||||||
|
|
||||||
path = workingDir + path |
|
||||||
*/ |
|
||||||
|
|
||||||
|
|
||||||
return path |
|
||||||
} |
|
||||||
|
|
||||||
export class fsPlugin extends ElectronPlugin { |
|
||||||
public fs: any |
|
||||||
|
|
||||||
constructor() { |
|
||||||
super({ |
|
||||||
displayName: 'fs', |
|
||||||
name: 'fs', |
|
||||||
description: 'fs', |
|
||||||
}) |
|
||||||
this.methods = ['readdir', 'readFile', 'writeFile', 'mkdir', 'rmdir', 'unlink', 'rename', 'stat', 'exists', 'setWorkingDir'] |
|
||||||
|
|
||||||
|
|
||||||
this.fs = { |
|
||||||
|
|
||||||
exists: async (path: string) => { |
|
||||||
path = fixPath(path) |
|
||||||
const exists = await this.call('fs', 'exists', path) |
|
||||||
return exists |
|
||||||
}, |
|
||||||
rmdir: async (path: string) => { |
|
||||||
path = fixPath(path) |
|
||||||
return await this.call('fs', 'rmdir', path) |
|
||||||
|
|
||||||
}, |
|
||||||
readdir: async (path: string) => { |
|
||||||
path = fixPath(path) |
|
||||||
console.log('readdir', path) |
|
||||||
const files = await this.call('fs', 'readdir', path) |
|
||||||
return files |
|
||||||
}, |
|
||||||
unlink: async (path: string) => { |
|
||||||
path = fixPath(path) |
|
||||||
return await this.call('fs', 'unlink', path) |
|
||||||
}, |
|
||||||
mkdir: async (path: string) => { |
|
||||||
path = fixPath(path) |
|
||||||
return await this.call('fs', 'mkdir', path) |
|
||||||
}, |
|
||||||
readFile: async (path: string) => { |
|
||||||
path = fixPath(path) |
|
||||||
return await this.call('fs', 'readFile', path) |
|
||||||
} |
|
||||||
, |
|
||||||
rename: async (from: string, to: string) => { |
|
||||||
return await this.call('fs', 'rename', from, to) |
|
||||||
}, |
|
||||||
writeFile: async (path: string, content: string) => { |
|
||||||
path = fixPath(path) |
|
||||||
return await this.call('fs', 'writeFile', path, content) |
|
||||||
} |
|
||||||
, |
|
||||||
stat: async (path: string) => { |
|
||||||
path = fixPath(path) |
|
||||||
const stat = await this.call('fs', 'stat', path) |
|
||||||
stat.isDirectory = () => stat.isDirectoryValue |
|
||||||
stat.isFile = () => !stat.isDirectoryValue |
|
||||||
//console.log('stat', path, stat)
|
|
||||||
return stat |
|
||||||
} |
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
} |
|
||||||
|
|
||||||
|
|
||||||
} |
|
||||||
|
|
||||||
|
|
||||||
async onActivation() { |
|
||||||
console.log('fsPluginClient onload', this.fs); |
|
||||||
(window as any).remixFileSystem = this.fs |
|
||||||
|
|
||||||
this.on('fs', 'workingDirChanged', (path: string) => { |
|
||||||
console.log('change working dir', path) |
|
||||||
workingDir = path |
|
||||||
}) |
|
||||||
} |
|
||||||
|
|
||||||
} |
|
@ -1,14 +0,0 @@ |
|||||||
import { ElectronPlugin } from './lib/electronPlugin'; |
|
||||||
|
|
||||||
export class gitPlugin extends ElectronPlugin { |
|
||||||
constructor(){ |
|
||||||
super({ |
|
||||||
displayName: 'git', |
|
||||||
name: 'git', |
|
||||||
description: 'git', |
|
||||||
}) |
|
||||||
this.methods = ['log', 'status', 'add', 'commit', 'push', 'pull', 'clone', 'checkout', 'branch', 'merge', 'reset', 'revert', 'diff', 'stash', 'apply', 'cherryPick', 'rebase', 'tag', 'fetch', 'remote', 'config', 'show', 'init', 'help', 'version'] |
|
||||||
} |
|
||||||
|
|
||||||
|
|
||||||
} |
|
@ -1,176 +0,0 @@ |
|||||||
import type { Profile, Message } from '@remixproject/plugin-utils' |
|
||||||
import { Plugin } from '@remixproject/engine'; |
|
||||||
|
|
||||||
export abstract class ElectronPlugin extends Plugin { |
|
||||||
protected loaded: boolean |
|
||||||
protected id = 0 |
|
||||||
protected pendingRequest: Record<number, (result: any, error: Error | string) => void> = {} |
|
||||||
protected api: { |
|
||||||
send: (message: Partial<Message>) => void |
|
||||||
on: (cb: (event: any, message: any) => void) => void |
|
||||||
} |
|
||||||
|
|
||||||
profile: Profile |
|
||||||
constructor(profile: Profile) { |
|
||||||
super(profile) |
|
||||||
this.loaded = false |
|
||||||
|
|
||||||
if(!window.electronAPI) throw new Error('ElectronPluginConnector requires window.api') |
|
||||||
if(!window.electronAPI.plugins) throw new Error('ElectronPluginConnector requires window.api.plugins') |
|
||||||
|
|
||||||
window.electronAPI.plugins.find((plugin: any) => { |
|
||||||
if(plugin.name === profile.name){ |
|
||||||
this.api = plugin |
|
||||||
return true |
|
||||||
} |
|
||||||
}) |
|
||||||
|
|
||||||
if(!this.api) throw new Error(`ElectronPluginConnector requires window.api.plugins.${profile.name} to be defined in preload.ts`) |
|
||||||
|
|
||||||
this.api.on((event: any, message: any) => { |
|
||||||
this.getMessage(message) |
|
||||||
}) |
|
||||||
|
|
||||||
|
|
||||||
} |
|
||||||
|
|
||||||
/** |
|
||||||
* Send a message to the external plugin |
|
||||||
* @param message the message passed to the plugin |
|
||||||
*/ |
|
||||||
protected send(message: Partial<Message>): void { |
|
||||||
if(this.loaded) |
|
||||||
this.api.send(message) |
|
||||||
} |
|
||||||
/** |
|
||||||
* Open connection with the plugin |
|
||||||
* @param name The name of the plugin should connect to |
|
||||||
*/ |
|
||||||
protected async connect(name: string) { |
|
||||||
const connected = await window.electronAPI.activatePlugin(name) |
|
||||||
if(connected && !this.loaded){ |
|
||||||
this.handshake() |
|
||||||
} |
|
||||||
|
|
||||||
} |
|
||||||
/** Close connection with the plugin */ |
|
||||||
protected disconnect(): any | Promise<any> { |
|
||||||
// TODO: Disconnect from the plugin
|
|
||||||
} |
|
||||||
|
|
||||||
async activate() { |
|
||||||
await this.connect(this.profile.name) |
|
||||||
return super.activate() |
|
||||||
} |
|
||||||
|
|
||||||
async deactivate() { |
|
||||||
this.loaded = false |
|
||||||
await this.disconnect() |
|
||||||
return super.deactivate() |
|
||||||
} |
|
||||||
|
|
||||||
/** Call a method from this plugin */ |
|
||||||
protected callPluginMethod(key: string, payload: any[] = []): Promise<any> { |
|
||||||
const action = 'request' |
|
||||||
const id = this.id++ |
|
||||||
const requestInfo = this.currentRequest |
|
||||||
const name = this.name |
|
||||||
const promise = new Promise((res, rej) => { |
|
||||||
this.pendingRequest[id] = (result: any[], error: Error | string) => error ? rej (error) : res(result) |
|
||||||
}) |
|
||||||
this.send({ id, action, key, payload, requestInfo, name }) |
|
||||||
return promise |
|
||||||
} |
|
||||||
|
|
||||||
/** Perform handshake with the client if not loaded yet */ |
|
||||||
protected async handshake() { |
|
||||||
if (!this.loaded) { |
|
||||||
this.loaded = true |
|
||||||
let methods: string[]; |
|
||||||
try { |
|
||||||
methods = await this.callPluginMethod('handshake', [this.profile.name]) |
|
||||||
} catch (err) { |
|
||||||
this.loaded = false |
|
||||||
throw err; |
|
||||||
} |
|
||||||
this.emit('loaded', this.name) |
|
||||||
if (methods) { |
|
||||||
this.profile.methods = methods |
|
||||||
this.call('manager', 'updateProfile', this.profile) |
|
||||||
} |
|
||||||
} else { |
|
||||||
// If there is a broken connection we want send back the handshake to the plugin client
|
|
||||||
return this.callPluginMethod('handshake', [this.profile.name]) |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
/** |
|
||||||
* React when a message comes from client |
|
||||||
* @param message The message sent by the client |
|
||||||
*/ |
|
||||||
protected async getMessage(message: Message) { |
|
||||||
// Check for handshake request from the client
|
|
||||||
if (message.action === 'request' && message.key === 'handshake') { |
|
||||||
console.log('ElectronPluginConnector getMessage handshake', message) |
|
||||||
return this.handshake() |
|
||||||
} |
|
||||||
|
|
||||||
switch (message.action) { |
|
||||||
// Start listening on an event
|
|
||||||
case 'on': |
|
||||||
case 'listen': { |
|
||||||
const { name, key } = message |
|
||||||
const action = 'notification' |
|
||||||
this.on(name, key, (...payload: any[]) => this.send({ action, name, key, payload })) |
|
||||||
break |
|
||||||
} |
|
||||||
case 'off': { |
|
||||||
const { name, key } = message |
|
||||||
this.off(name, key) |
|
||||||
break |
|
||||||
} |
|
||||||
case 'once': { |
|
||||||
const { name, key } = message |
|
||||||
const action = 'notification' |
|
||||||
this.once(name, key, (...payload: any) => this.send({ action, name, key, payload })) |
|
||||||
break |
|
||||||
} |
|
||||||
// Emit an event
|
|
||||||
case 'emit': |
|
||||||
case 'notification': { |
|
||||||
if (!message.payload) break |
|
||||||
this.emit(message.key, ...message.payload) |
|
||||||
break |
|
||||||
} |
|
||||||
// Call a method
|
|
||||||
case 'call': |
|
||||||
case 'request': { |
|
||||||
const action = 'response' |
|
||||||
try { |
|
||||||
const payload = await this.call(message.name, message.key, ...message.payload) |
|
||||||
const error: any = undefined |
|
||||||
this.send({ ...message, action, payload, error }) |
|
||||||
} catch (err) { |
|
||||||
const payload: any = undefined |
|
||||||
const error = err.message || err |
|
||||||
this.send({ ...message, action, payload, error }) |
|
||||||
} |
|
||||||
break |
|
||||||
} |
|
||||||
case 'cancel': { |
|
||||||
const payload = this.cancel(message.name, message.key) |
|
||||||
break; |
|
||||||
} |
|
||||||
// Return result from exposed method
|
|
||||||
case 'response': { |
|
||||||
const { id, payload, error } = message |
|
||||||
this.pendingRequest[id](payload, error) |
|
||||||
delete this.pendingRequest[id] |
|
||||||
break |
|
||||||
} |
|
||||||
default: { |
|
||||||
throw new Error('Message should be a notification, request or response') |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
@ -1,40 +0,0 @@ |
|||||||
import { Plugin } from "@remixproject/engine" |
|
||||||
import { useEffect, useState } from "react" |
|
||||||
import { ElectronPlugin } from "../lib/electronPlugin" |
|
||||||
|
|
||||||
interface RemixUIFileDialogInterface { |
|
||||||
plugin: Plugin |
|
||||||
} |
|
||||||
|
|
||||||
export const RemixUIFileDialog = (props: RemixUIFileDialogInterface) => { |
|
||||||
const { plugin } = props |
|
||||||
const [files, setFiles] = useState<string[]>([]) |
|
||||||
const [workingDir, setWorkingDir] = useState<string>('') |
|
||||||
|
|
||||||
useEffect(() => { |
|
||||||
plugin.on('fs', 'workingDirChanged', async (path: string) => { |
|
||||||
console.log('workingDirChanged') |
|
||||||
setWorkingDir(path) |
|
||||||
await readdir() |
|
||||||
}) |
|
||||||
}, []) |
|
||||||
|
|
||||||
const readdir = async () => { |
|
||||||
const files = await plugin.call('fs', 'readdir', '/') |
|
||||||
console.log('files', files) |
|
||||||
setFiles(files) |
|
||||||
} |
|
||||||
|
|
||||||
return ( |
|
||||||
<> |
|
||||||
<h1>RemixUIFileDialog</h1> |
|
||||||
<button onClick={() => plugin.call('fs', 'setWorkingDir')}>open</button> |
|
||||||
<button onClick={async () => await readdir()}>read</button> |
|
||||||
<hr></hr> |
|
||||||
{workingDir} |
|
||||||
<hr></hr> |
|
||||||
|
|
||||||
{files.map(file => <div key={file}>{file}</div>)} |
|
||||||
</> |
|
||||||
) |
|
||||||
} |
|
@ -1,44 +0,0 @@ |
|||||||
import React, { useState, useEffect, forwardRef } from 'react' // eslint-disable-line
|
|
||||||
import { ElectronPlugin } from '../lib/electronPlugin' |
|
||||||
import { XTerm } from 'xterm-for-react' |
|
||||||
|
|
||||||
|
|
||||||
export interface RemixUiXtermProps { |
|
||||||
plugin: ElectronPlugin |
|
||||||
pid: number |
|
||||||
send: (data: string, pid: number) => void |
|
||||||
timeStamp: number |
|
||||||
setTerminalRef: (pid: number, ref: any) => void |
|
||||||
} |
|
||||||
|
|
||||||
const RemixUiXterm = (props: RemixUiXtermProps) => { |
|
||||||
const { plugin, pid, send, timeStamp } = props |
|
||||||
const xtermRef = React.useRef(null) |
|
||||||
|
|
||||||
useEffect(() => { |
|
||||||
console.log('remix-ui-xterm ref', xtermRef.current) |
|
||||||
props.setTerminalRef(pid, xtermRef.current) |
|
||||||
}, [xtermRef.current]) |
|
||||||
|
|
||||||
const onKey = (event: { key: string; domEvent: KeyboardEvent }) => { |
|
||||||
send(event.key, pid) |
|
||||||
} |
|
||||||
|
|
||||||
const onData = (data: string) => { |
|
||||||
console.log('onData', data) |
|
||||||
} |
|
||||||
|
|
||||||
const closeTerminal = () => { |
|
||||||
plugin.call('xterm', 'close', pid) |
|
||||||
} |
|
||||||
|
|
||||||
return ( |
|
||||||
<> |
|
||||||
<XTerm ref={xtermRef} onData={onData} onKey={onKey}></XTerm> |
|
||||||
<button onClick={closeTerminal}>close</button> |
|
||||||
</> |
|
||||||
) |
|
||||||
|
|
||||||
} |
|
||||||
|
|
||||||
export default RemixUiXterm |
|
@ -1,95 +0,0 @@ |
|||||||
import React, { useState, useEffect } from 'react' // eslint-disable-line
|
|
||||||
import { ElectronPlugin } from '../lib/electronPlugin' |
|
||||||
import RemixUiXterm from './remix-ui-xterm' |
|
||||||
|
|
||||||
export interface RemixUiXterminalsProps { |
|
||||||
plugin: ElectronPlugin |
|
||||||
} |
|
||||||
|
|
||||||
export interface xtermState { |
|
||||||
pid: number |
|
||||||
queue: string |
|
||||||
timeStamp: number |
|
||||||
ref: any |
|
||||||
} |
|
||||||
|
|
||||||
export const RemixUiXterminals = (props: RemixUiXterminalsProps) => { |
|
||||||
const [terminals, setTerminals] = useState<xtermState[]>([]) |
|
||||||
const [workingDir, setWorkingDir] = useState<string>('') |
|
||||||
const { plugin } = props |
|
||||||
|
|
||||||
useEffect(() => { |
|
||||||
plugin.on('xterm', 'loaded', async () => { |
|
||||||
}) |
|
||||||
plugin.on('xterm', 'data', async (data: string, pid: number) => { |
|
||||||
writeToTerminal(data, pid) |
|
||||||
}) |
|
||||||
|
|
||||||
plugin.on('xterm', 'close', async (pid: number) => { |
|
||||||
setTerminals(prevState => { |
|
||||||
return prevState.filter(xtermState => xtermState.pid !== pid) |
|
||||||
}) |
|
||||||
}) |
|
||||||
|
|
||||||
plugin.on('fs', 'workingDirChanged', (path: string) => { |
|
||||||
setWorkingDir(path) |
|
||||||
}) |
|
||||||
}, []) |
|
||||||
|
|
||||||
const writeToTerminal = (data: string, pid: number) => { |
|
||||||
setTerminals(prevState => { |
|
||||||
const terminal = prevState.find(xtermState => xtermState.pid === pid) |
|
||||||
if (terminal.ref && terminal.ref.terminal) { |
|
||||||
terminal.ref.terminal.write(data) |
|
||||||
}else { |
|
||||||
terminal.queue += data |
|
||||||
} |
|
||||||
return [...prevState] |
|
||||||
}) |
|
||||||
} |
|
||||||
|
|
||||||
const send = (data: string, pid: number) => { |
|
||||||
plugin.call('xterm', 'keystroke', data, pid) |
|
||||||
} |
|
||||||
|
|
||||||
const createTerminal = async () => { |
|
||||||
const pid = await plugin.call('xterm', 'createTerminal', workingDir) |
|
||||||
|
|
||||||
setTerminals(prevState => { |
|
||||||
return [...prevState, { |
|
||||||
pid: pid, |
|
||||||
queue: '', |
|
||||||
timeStamp: Date.now(), |
|
||||||
ref: null |
|
||||||
}] |
|
||||||
}) |
|
||||||
} |
|
||||||
|
|
||||||
const setTerminalRef = (pid: number, ref: any) => { |
|
||||||
setTerminals(prevState => { |
|
||||||
const terminal = prevState.find(xtermState => xtermState.pid === pid) |
|
||||||
terminal.ref = ref |
|
||||||
if(terminal.queue) { |
|
||||||
ref.terminal.write(terminal.queue) |
|
||||||
terminal.queue = '' |
|
||||||
} |
|
||||||
return [...prevState] |
|
||||||
}) |
|
||||||
} |
|
||||||
|
|
||||||
|
|
||||||
return (<> |
|
||||||
<button onClick={() => { |
|
||||||
createTerminal() |
|
||||||
}}>create terminal</button> |
|
||||||
|
|
||||||
{terminals.map((xtermState) => { |
|
||||||
return ( |
|
||||||
<div key={xtermState.pid} data-id={`remixUIXT${xtermState.pid}`}>{xtermState.pid} |
|
||||||
<RemixUiXterm setTerminalRef={setTerminalRef} timeStamp={xtermState.timeStamp} send={send} pid={xtermState.pid} plugin={plugin}></RemixUiXterm> |
|
||||||
</div> |
|
||||||
) |
|
||||||
})} |
|
||||||
</>) |
|
||||||
} |
|
||||||
|
|
@ -1,11 +0,0 @@ |
|||||||
import { ElectronPlugin } from './lib/electronPlugin'; |
|
||||||
|
|
||||||
export class xtermPlugin extends ElectronPlugin { |
|
||||||
constructor(){ |
|
||||||
super({ |
|
||||||
displayName: 'xterm', |
|
||||||
name: 'xterm', |
|
||||||
description: 'xterm', |
|
||||||
}) |
|
||||||
} |
|
||||||
} |
|
@ -1,111 +0,0 @@ |
|||||||
|
|
||||||
import { Engine, Plugin, PluginManager } from '@remixproject/engine'; |
|
||||||
import { fsPlugin } from './remix/fsPlugin'; |
|
||||||
import { gitPlugin } from './remix/gitPlugin'; |
|
||||||
import { Terminal } from 'xterm'; |
|
||||||
import 'xterm/css/xterm.css'; |
|
||||||
import { FitAddon } from 'xterm-addon-fit'; |
|
||||||
import { xtermPlugin } from './remix/xtermPlugin'; |
|
||||||
|
|
||||||
class MyAppManager extends PluginManager { |
|
||||||
onActivation(): void { |
|
||||||
this.on('fs', 'loaded', async () => { |
|
||||||
console.log('fs loaded') |
|
||||||
const files = await this.call('fs', 'readdir', './') |
|
||||||
console.log('files', files) |
|
||||||
}) |
|
||||||
/* |
|
||||||
this.on('fs', 'loaded', async () => { |
|
||||||
const files = await this.call('fs', 'readdir', './') |
|
||||||
console.log('files', files) |
|
||||||
let exists = await this.call('fs', 'exists', '/Volumes/bunsen/code/rmproject2/remix-project/') |
|
||||||
console.log('exists', exists) |
|
||||||
exists = await this.call('fs', 'exists', './notexists') |
|
||||||
console.log('exists', exists) |
|
||||||
// stat test
|
|
||||||
const stat = await this.call('fs', 'stat', '/Volumes/bunsen/code/rmproject2/remix-project/') |
|
||||||
console.log('stat', stat) |
|
||||||
// read file test
|
|
||||||
const content = await this.call('fs', 'readFile', './src/index.html') |
|
||||||
console.log('content', content) |
|
||||||
|
|
||||||
await this.call('fs', 'watch', '/Volumes/bunsen/code/rmproject2/remix-project/') |
|
||||||
|
|
||||||
this.on('fs', 'change', (path: string, stats: any) => { |
|
||||||
console.log('change', path, stats) |
|
||||||
}) |
|
||||||
}) |
|
||||||
|
|
||||||
this.on('git', 'loaded', async () => { |
|
||||||
//const log = await this.call('git', 'log', '/Volumes/bunsen/code/rmproject2/remix-project/')
|
|
||||||
//console.log('log', log)
|
|
||||||
}) |
|
||||||
*/ |
|
||||||
this.on('xterm', 'loaded', async () => { |
|
||||||
console.log('xterm loaded') |
|
||||||
/* |
|
||||||
const term = new Terminal(); |
|
||||||
const fitAddon = new FitAddon(); |
|
||||||
term.loadAddon(fitAddon); |
|
||||||
term.open(document.getElementById('terminal')); |
|
||||||
fitAddon.fit(); |
|
||||||
|
|
||||||
|
|
||||||
const pid = await this.call('xterm', 'createTerminal') |
|
||||||
console.log('pid', pid) |
|
||||||
this.on('xterm', 'data', (data: string, pid: number) => { |
|
||||||
console.log('data', data) |
|
||||||
term.write(data) |
|
||||||
} |
|
||||||
) |
|
||||||
|
|
||||||
term.onData((data) => { |
|
||||||
console.log('term.onData', data) |
|
||||||
this.call('xterm', 'keystroke', data, pid) |
|
||||||
} |
|
||||||
); |
|
||||||
*/ |
|
||||||
}) |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
|
|
||||||
const engine = new Engine() |
|
||||||
const appManager = new MyAppManager() |
|
||||||
export const fs = new fsPlugin() |
|
||||||
const git = new gitPlugin() |
|
||||||
export const xterm = new xtermPlugin() |
|
||||||
|
|
||||||
export class filePanelPlugin extends Plugin { |
|
||||||
constructor() { |
|
||||||
super({ |
|
||||||
displayName: 'filePanel', |
|
||||||
name: 'filePanel', |
|
||||||
}) |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
export const filePanel = new filePanelPlugin() |
|
||||||
|
|
||||||
engine.register(appManager) |
|
||||||
engine.register(fs) |
|
||||||
engine.register(git) |
|
||||||
engine.register(xterm) |
|
||||||
engine.register(filePanel) |
|
||||||
|
|
||||||
appManager.activatePlugin('fs') |
|
||||||
appManager.activatePlugin('git') |
|
||||||
appManager.activatePlugin('xterm') |
|
||||||
appManager.activatePlugin('filePanel') |
|
||||||
|
|
||||||
|
|
||||||
setTimeout(async () => { |
|
||||||
const files = await appManager.call('fs', 'readdir', './src') |
|
||||||
console.log('files', files) |
|
||||||
}, 1000) |
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
import './app' |
|
||||||
import { ElectronPlugin } from './remix/lib/electronPlugin'; |
|
||||||
|
|
@ -1,20 +0,0 @@ |
|||||||
{ |
|
||||||
"compilerOptions": { |
|
||||||
"target": "ES6", |
|
||||||
"allowJs": true, |
|
||||||
"module": "commonjs", |
|
||||||
"jsx": "react-jsx", |
|
||||||
"skipLibCheck": true, |
|
||||||
"esModuleInterop": true, |
|
||||||
"noImplicitAny": true, |
|
||||||
"sourceMap": true, |
|
||||||
"baseUrl": ".", |
|
||||||
"outDir": "dist", |
|
||||||
"moduleResolution": "node", |
|
||||||
"resolveJsonModule": true, |
|
||||||
"paths": { |
|
||||||
"*": ["node_modules/*"] |
|
||||||
} |
|
||||||
}, |
|
||||||
"include": ["src/**/*"] |
|
||||||
} |
|
@ -1,18 +0,0 @@ |
|||||||
import type { Configuration } from 'webpack'; |
|
||||||
|
|
||||||
import { rules } from './webpack.rules'; |
|
||||||
|
|
||||||
export const mainConfig: Configuration = { |
|
||||||
/** |
|
||||||
* This is the main entry point for your application, it's the first file |
|
||||||
* that runs in the main process. |
|
||||||
*/ |
|
||||||
entry: './src/index.ts', |
|
||||||
// Put your normal webpack config below here
|
|
||||||
module: { |
|
||||||
rules, |
|
||||||
}, |
|
||||||
resolve: { |
|
||||||
extensions: ['.js', '.ts', '.jsx', '.tsx', '.css', '.json'], |
|
||||||
}, |
|
||||||
}; |
|
@ -1,10 +0,0 @@ |
|||||||
import type IForkTsCheckerWebpackPlugin from 'fork-ts-checker-webpack-plugin'; |
|
||||||
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
|
||||||
const ForkTsCheckerWebpackPlugin: typeof IForkTsCheckerWebpackPlugin = require('fork-ts-checker-webpack-plugin'); |
|
||||||
|
|
||||||
export const plugins = [ |
|
||||||
new ForkTsCheckerWebpackPlugin({ |
|
||||||
logger: 'webpack-infrastructure', |
|
||||||
}), |
|
||||||
]; |
|
@ -1,19 +0,0 @@ |
|||||||
import type { Configuration } from 'webpack'; |
|
||||||
|
|
||||||
import { rules } from './webpack.rules'; |
|
||||||
import { plugins } from './webpack.plugins'; |
|
||||||
|
|
||||||
rules.push({ |
|
||||||
test: /\.css$/, |
|
||||||
use: [{ loader: 'style-loader' }, { loader: 'css-loader' }], |
|
||||||
}); |
|
||||||
|
|
||||||
export const rendererConfig: Configuration = { |
|
||||||
module: { |
|
||||||
rules, |
|
||||||
}, |
|
||||||
plugins, |
|
||||||
resolve: { |
|
||||||
extensions: ['.js', '.ts', '.jsx', '.tsx', '.css'], |
|
||||||
}, |
|
||||||
}; |
|
@ -1,31 +0,0 @@ |
|||||||
import type { ModuleOptions } from 'webpack'; |
|
||||||
|
|
||||||
export const rules: Required<ModuleOptions>['rules'] = [ |
|
||||||
// Add support for native node modules
|
|
||||||
{ |
|
||||||
// We're specifying native_modules in the test because the asset relocator loader generates a
|
|
||||||
// "fake" .node file which is really a cjs file.
|
|
||||||
test: /native_modules[/\\].+\.node$/, |
|
||||||
use: 'node-loader', |
|
||||||
}, |
|
||||||
{ |
|
||||||
test: /[/\\]node_modules[/\\].+\.(m?js|node)$/, |
|
||||||
parser: { amd: false }, |
|
||||||
use: { |
|
||||||
loader: '@vercel/webpack-asset-relocator-loader', |
|
||||||
options: { |
|
||||||
outputAssetBase: 'native_modules', |
|
||||||
}, |
|
||||||
}, |
|
||||||
}, |
|
||||||
{ |
|
||||||
test: /\.tsx?$/, |
|
||||||
exclude: /(node_modules|\.webpack)/, |
|
||||||
use: { |
|
||||||
loader: 'ts-loader', |
|
||||||
options: { |
|
||||||
transpileOnly: true, |
|
||||||
}, |
|
||||||
}, |
|
||||||
}, |
|
||||||
]; |
|
File diff suppressed because it is too large
Load Diff
Loading…
Reference in new issue