Merge branch 'desktopautoupdate' into signmacosdesktop

signmacosdesktop
filip mertens 11 months ago
commit b8bb0df66f
  1. 58
      .circleci/config.yml
  2. 1
      .gitignore
  3. 192
      apps/remix-ide/ci/update_desktop_release_assets.ts
  4. 7
      apps/remix-ide/src/app.js
  5. 55
      apps/remix-ide/src/app/plugins/electron/appUpdaterPlugin.ts
  6. 23
      apps/remixdesktop/package.json
  7. 3
      apps/remixdesktop/src/engine.ts
  8. 117
      apps/remixdesktop/src/plugins/appUpdater.ts
  9. 2
      apps/remixdesktop/src/plugins/compilerLoader.ts
  10. 2
      apps/remixdesktop/src/preload.ts
  11. 978
      apps/remixdesktop/yarn.lock
  12. 1
      package.json
  13. 16
      yarn.lock

@ -125,15 +125,24 @@ jobs:
yarn add node-pty
yarn --ignore-optional
yarn add @remix-project/remix-ws-templates
PUBLISH_FOR_PULL_REQUEST='true' yarn dist
yarn dist
rm -rf release/*-unpacked
- save_cache:
key: remixdesktop-linux-deps-{{ checksum "apps/remixdesktop/yarn.lock" }}
paths:
- apps/remixdesktop/node_modules
- run:
name: "remove unnecessary files"
command: |
rm -rf ~/remix-project/apps/remixdesktop/release/.icon*
rm -rf ~/remix-project/apps/remixdesktop/release/builder*
- store_artifacts:
path: apps/remixdesktop/release/
destination: remixdesktop-linux
- persist_to_workspace:
root: apps/remixdesktop
paths:
- "release"
build-remixdesktop-windows:
executor:
@ -169,7 +178,7 @@ jobs:
cp -r dist/apps/remix-ide apps/remixdesktop/build
cd apps/remixdesktop/
yarn
PUBLISH_FOR_PULL_REQUEST='true' yarn dist
yarn dist
rm -rf release/*-unpacked
- save_cache:
key: remixdesktop-windows-deps-{{ checksum "apps/remixdesktop/yarn.lock" }}
@ -243,14 +252,24 @@ jobs:
echo 'Verification failed'
exit 1
}
- run:
name: "remove unnecessary files"
shell: bash.exe
command: |
rm -rf ~/remix-project/release/.icon*
rm -rf ~/remix-project/release/builder*
- store_artifacts:
path: ~/remix-project/release/
destination: remixdesktop-windows
- persist_to_workspace:
root: ~/remix-project/
paths:
- "release"
environment:
SM_CLIENT_CERT_FILE: 'C:\Certificate_pkcs12.p12'
Signtool: 'C:\Program Files (x86)\Windows Kits\10\App Certification Kit\signtool.exe'
SSM: 'C:\Program Files\DigiCert\DigiCert One Signing Manager Tools'
RemixSetupExe: 'C:\Users\circleci\remix-project\release\Remix IDE.exe'
RemixSetupExe: 'C:\Users\circleci\remix-project\release\Remix-Desktop.exe'
build-remixdesktop-mac:
macos:
@ -311,10 +330,38 @@ jobs:
brew install jq
cd apps/remixdesktop
zsh notarizedmg.sh
- run:
name: "remove unnecessary files"
command: |
rm -rf ~/remix-project/apps/remixdesktop/release/.icon*
rm -rf ~/remix-project/apps/remixdesktop/release/builder*
rm -rf ~/remix-project/apps/remixdesktop/release/*.blockmap
rm -rf ~/remix-project/apps/remixdesktop/release/_.*
- store_artifacts:
path: apps/remixdesktop/release/
destination: remixdesktop-mac
- persist_to_workspace:
root: apps/remixdesktop
paths:
- "release"
uploadartifacts:
docker:
- image: cimg/node:20.0.0-browsers
resource_class:
xlarge
working_directory: ~/remix-project
steps:
- checkout
- attach_workspace:
at: .
- restore_cache:
keys:
- v1-deps-{{ checksum "yarn.lock" }}
- run: yarn
- run:
name: "Upload Artifacts"
command: npx ts-node apps/remix-ide/ci/update_desktop_release_assets.ts
lint:
docker:
- image: cimg/node:20.0.0-browsers
@ -559,6 +606,11 @@ workflows:
- build-remixdesktop-linux:
requires:
- build-desktop
- uploadartifacts:
requires:
- build-remixdesktop-mac
- sign-remixdesktop-windows
- build-remixdesktop-linux
- build-plugin:
matrix:
parameters:

1
.gitignore vendored

@ -15,6 +15,7 @@ soljson.js
*_group*.*.ts
*_group*.ts
stats.json
release
# compiled output

@ -0,0 +1,192 @@
import {Octokit} from 'octokit'
import * as fs from 'fs'
import * as path from 'path'
import YAML from 'yaml'
import crypto from 'crypto'
const owner = 'bunsenstraat'
const repo = 'remix-desktop'
const headers = {
'X-GitHub-Api-Version': '2022-11-28',
}
const octokit = new Octokit({
auth: process.env.GH_TOKEN,
})
async function getAllReleases() {
const releases = await octokit.request('GET /repos/{owner}/{repo}/releases', {
owner: owner,
repo: repo,
headers: headers,
})
return releases.data
}
async function uploadReleaseAsset(release, name, file) {
const upload_url = release.upload_url
console.log(`Uploading ${name} to ${upload_url}`)
octokit.request({
method: "POST",
url: upload_url,
headers: {
"content-type": "text/plain",
},
data: fs.readFileSync(file),
name,
label: name
});
}
async function getVersionFromPackageJson() {
// ignore ts error
// eslint-disable-next-line @typescript-eslint/no-var-requires
const packageJson = require(__dirname + '/../../../apps/remixdesktop/package.json')
return packageJson.version
}
async function readReleaseFilesFromLocalDirectory() {
const directoryPath = path.join(__dirname, '../../../release')
const files = fs.readdirSync(directoryPath)
return files
}
async function removeAsset(asset) {
await octokit.request('DELETE /repos/{owner}/{repo}/releases/assets/{asset_id}', {
owner: owner,
repo: repo,
asset_id: asset.id,
headers: headers,
})
}
async function hashFile(file): Promise<string> {
return new Promise((resolve, reject) => {
const hash = crypto.createHash('sha512').setEncoding('base64');
// hash.on('error', reject).setEncoding(encoding);
fs.createReadStream(
file,
Object.assign({}, {}, {
highWaterMark: 1024 * 1024,
/* better to use more memory but hash faster */
})
)
.on('error', reject)
.on('end', () => {
hash.end();
console.log('hash done');
console.log(hash.read());
resolve(hash.digest('base64'));
})
.pipe(
hash,
{
end: false,
}
);
});
}
async function main() {
const allReleases = await getAllReleases()
const version = await getVersionFromPackageJson()
console.log(`preparing release version: ${version}`)
let release
allReleases.find((r) => {
if (r.tag_name === `v${version}`) {
release = r
}
})
if (!release) {
console.log('No release found.')
// create release
console.log(`Creating release ${version}`)
const r = await octokit.request('POST /repos/{owner}/{repo}/releases', {
owner: owner,
repo: repo,
tag_name: `v${version}`,
name: `${version}`,
draft: true,
headers: headers,
})
release = r.data
}
let ymlFiles = await readReleaseFilesFromLocalDirectory()
ymlFiles = ymlFiles.filter((file) => file.endsWith('.yml') && file.startsWith('latest'))
console.log(`Found ${ymlFiles.length} yml files to upload`)
// read and parse yml latest files
// the yml files contain the sha512 hash and file size of the executable
// we need to recalculate the hash and file size of the executable
// and update the yml files
// this is because the executable is resigned after the yml files are created
for (const file of ymlFiles) {
const content = fs.readFileSync(path.join(__dirname, '../../../release', file), 'utf8')
const parsed = YAML.parse(content)
const hashes:{
url: string,
sha512: string,
size: number
}[] = []
if(parsed.files) {
console.log(`Found`, parsed.files)
for (const f of parsed.files) {
const executable = f.url
const exists = fs.existsSync(path.join(__dirname, '../../../release', executable))
if (!exists) {
console.log(`File ${executable} does not exist on local fs. Skipping...`)
continue
}else{
console.log(`File ${executable} exists on local fs. Recalculating hash...`)
// calculate sha512 hash of executable
const hash:string = await hashFile(path.join(__dirname, '../../../release', executable))
console.log(hash)
// calculate file size of executable
const stats = fs.statSync(path.join(__dirname, '../../../release', executable))
const fileSizeInBytes = stats.size
console.log(fileSizeInBytes)
hashes.push({
url: executable,
sha512: hash,
size: fileSizeInBytes
})
if(parsed.path === executable) {
parsed.sha512 = hash
parsed.size = fileSizeInBytes
}
}
}
}
console.log(hashes)
parsed.files = hashes
const newYml = YAML.stringify(parsed)
fs.writeFileSync(path.join(__dirname, '../../../release', file), newYml)
}
let files = await readReleaseFilesFromLocalDirectory()
files = files.filter((file) => file.endsWith('.zip') || file.endsWith('.dmg') || file.endsWith('.exe') || file.endsWith('.AppImage') || file.endsWith('.snap') || file.endsWith('.deb') || file.startsWith('latest'))
console.log(`Found ${files.length} files to upload`)
console.log(files)
if (!release.draft) {
console.log(`Release ${version} is not a draft. Aborting...`)
return
}
// upload files
for (const file of files) {
// check if it is already uploaded
const asset = release.assets.find((a) => a.label === file)
if (asset) {
console.log(`Asset ${file} already uploaded... replacing it`)
// remove it first
await removeAsset(asset)
}
await uploadReleaseAsset(release, file, path.join(__dirname, '../../../release', file))
}
}
main()

@ -57,7 +57,8 @@ import { electronTemplates } from './app/plugins/electron/templatesPlugin'
import { xtermPlugin } from './app/plugins/electron/xtermPlugin'
import { ripgrepPlugin } from './app/plugins/electron/ripgrepPlugin'
import { compilerLoaderPlugin, compilerLoaderPluginDesktop } from './app/plugins/electron/compilerLoaderPlugin'
import { appUpdaterPlugin } from './app/plugins/electron/appUpdaterPlugin'
import {OpenAIGpt} from './app/plugins/openaigpt'
const isElectron = require('is-electron')
@ -377,6 +378,8 @@ class AppComponent {
this.engine.register([xterm])
const ripgrep = new ripgrepPlugin()
this.engine.register([ripgrep])
const appUpdater = new appUpdaterPlugin()
this.engine.register([appUpdater])
}
const compilerloader = isElectron()? new compilerLoaderPluginDesktop(): new compilerLoaderPlugin()
@ -500,7 +503,7 @@ class AppComponent {
await this.appManager.activatePlugin(['solidity-script', 'remix-templates'])
if (isElectron()){
await this.appManager.activatePlugin(['isogit', 'electronconfig', 'electronTemplates', 'xterm', 'ripgrep'])
await this.appManager.activatePlugin(['isogit', 'electronconfig', 'electronTemplates', 'xterm', 'ripgrep', 'appUpdater'])
}
this.appManager.on(

@ -0,0 +1,55 @@
import { ElectronPlugin } from '@remixproject/engine-electron'
const profile = {
displayName: 'appUpdater',
name: 'appUpdater',
description: 'appUpdater',
}
export class appUpdaterPlugin extends ElectronPlugin {
constructor() {
console.log('appUpdaterPlugin')
super(profile)
}
onActivation(): void {
this.on('appUpdater', 'askForUpdate', () => {
console.log('askForUpdate')
const upgradeModal = {
id: 'confirmUpdate',
title: 'An update is available',
message: `A new version of Remix Desktop is available. Do you want to update?`,
modalType: 'modal',
okLabel: 'Yes',
cancelLabel: 'No',
okFn: () => {
this.call('appUpdater', 'download')
},
cancelFn: () => {
},
hideFn: () => null
}
this.call('notification', 'modal', upgradeModal)
})
this.on('appUpdater', 'downloadReady', () => {
console.log('downloadReady')
const upgradeModal = {
id: 'confirmInstall',
title: 'An update is ready to install',
message: `A new version of Remix Desktop is ready to install. Do you want to install it now? This will close Remix Desktop.`,
modalType: 'modal',
okLabel: 'Yes',
cancelLabel: 'No',
okFn: () => {
this.call('appUpdater', 'install')
},
cancelFn: () => {
},
hideFn: () => null
}
this.call('notification', 'modal', upgradeModal)
})
}
}

@ -24,7 +24,7 @@
"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",
"dist": "tsc && cp -R node_modules/yarn build/tools/ && 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"
@ -35,8 +35,8 @@
"@types/byline": "^4.2.35",
"@types/express": "^4.17.21",
"cross-env": "^7.0.3",
"electron": "^25.0.1",
"electron-builder": "^23.6.0",
"electron": "^26.0.0",
"electron-builder": "^24.9.1",
"electron-devtools-installer": "^3.2.0",
"typescript": "^5.1.3",
"yarn": "^1.22.21"
@ -53,6 +53,7 @@
"axios": "^1.6.1",
"byline": "^5.0.0",
"chokidar": "^3.5.3",
"electron-updater": "^6.1.8",
"express": "^4.18.2",
"isomorphic-git": "^1.24.2",
"node-pty": "^0.10.1",
@ -62,7 +63,7 @@
"@remix-project/remix-ws-templates": "^1.0.27"
},
"build": {
"productName": "Remix IDE",
"productName": "Remix-Desktop",
"appId": "org.ethereum.remix-ide",
"asar": true,
"generateUpdatesFilesForAllChannels": true,
@ -89,6 +90,13 @@
"x64",
"arm64"
]
},
{
"target": "zip",
"arch": [
"x64",
"arm64"
]
}
],
"icon": "assets/icon.png",
@ -106,7 +114,7 @@
"createDesktopShortcut": "always",
"allowToChangeInstallationDirectory": true,
"oneClick": false,
"shortcutName": "Remix IDE",
"shortcutName": "Remix Desktop",
"differentialPackage": false
},
"win": {
@ -116,9 +124,12 @@
"icon": "assets/icon.png",
"artifactName": "${productName}.${ext}"
},
"deb": {},
"linux": {
"target": [
"deb"
"deb",
"snap",
"AppImage"
],
"category": "WebBrowser",
"icon": "assets"

@ -9,6 +9,7 @@ import { ConfigPlugin } from './plugins/configPlugin';
import { TemplatesPlugin } from './plugins/templates';
import { RipgrepPlugin } from './plugins/ripgrepPlugin';
import { CompilerLoaderPlugin } from './plugins/compilerLoader';
import { AppUpdaterPlugin } from './plugins/appUpdater';
const engine = new Engine()
const appManager = new PluginManager()
@ -19,6 +20,7 @@ const configPlugin = new ConfigPlugin()
const templatesPlugin = new TemplatesPlugin()
const ripgrepPlugin = new RipgrepPlugin()
const compilerLoaderPlugin = new CompilerLoaderPlugin()
const appUpdaterPlugin = new AppUpdaterPlugin()
engine.register(appManager)
engine.register(fsPlugin)
@ -28,6 +30,7 @@ engine.register(configPlugin)
engine.register(templatesPlugin)
engine.register(ripgrepPlugin)
engine.register(compilerLoaderPlugin)
engine.register(appUpdaterPlugin)
appManager.activatePlugin('electronconfig')
appManager.activatePlugin('fs')

@ -0,0 +1,117 @@
import { ElectronBasePlugin, ElectronBasePluginClient } from "@remixproject/plugin-electron"
import { Profile } from "@remixproject/plugin-utils"
import { autoUpdater } from "electron-updater"
import { app } from 'electron';
const profile = {
displayName: 'appUpdater',
name: 'appUpdater',
description: 'appUpdater',
}
export class AppUpdaterPlugin extends ElectronBasePlugin {
clients: AppUpdaterPluginClient[] = []
constructor() {
console.log('AppUpdaterPlugin')
super(profile, clientProfile, AppUpdaterPluginClient)
this.methods = [...super.methods]
autoUpdater.autoDownload = false
autoUpdater.disableDifferentialDownload = true
autoUpdater.on('checking-for-update', () => {
console.log('Checking for update...');
this.sendToLog('Checking for update...')
})
autoUpdater.on('update-available', (info: any) => {
console.log('Update available.', info);
this.sendToLog('Update available.')
for (const client of this.clients) {
client.askForUpdate()
}
})
autoUpdater.on('update-not-available', () => {
console.log('Update not available.');
this.sendToLog('App is already up to date.')
})
autoUpdater.on('error', (err) => {
console.log('Error in auto-updater. ' + err);
this.sendToLog('Cannot find updates...')
})
autoUpdater.on('download-progress', (progressObj) => {
let log_message = "Download speed: " + progressObj.bytesPerSecond;
log_message = log_message + ' - Downloaded ' + progressObj.percent + '%';
log_message = log_message + ' (' + progressObj.transferred + "/" + progressObj.total + ')';
console.log(log_message);
this.sendToLog(log_message)
})
autoUpdater.on('update-downloaded', (info) => {
console.log('Update downloaded');
this.sendToLog('Update downloaded')
for(const client of this.clients) {
client.downloadReady()
}
})
}
async sendToLog(message: string): Promise<void> {
for (const client of this.clients) {
client.call('terminal', 'log', {
type: 'log',
value: message,
})
}
}
}
const clientProfile: Profile = {
name: 'appUpdater',
displayName: 'appUpdater',
description: 'appUpdater',
methods: ['checkForUpdates', 'download', 'install'],
}
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')
await this.checkForUpdates()
})
}
async askForUpdate(): Promise<void> {
this.emit('askForUpdate')
}
async downloadReady(): Promise<void> {
this.emit('downloadReady')
}
async download(): Promise<void> {
autoUpdater.downloadUpdate()
}
async install(): Promise<void> {
autoUpdater.quitAndInstall()
app.exit()
}
async checkForUpdates(): Promise<void> {
console.log('checkForUpdates')
this.call('terminal', 'log', {
type: 'log',
value: 'Remix Desktop version: ' + autoUpdater.currentVersion,
})
autoUpdater.checkForUpdates()
}
}

@ -66,9 +66,7 @@ class CompilerLoaderPluginClient extends ElectronBasePluginClient {
}
async onActivation(): Promise<void> {
console.log('onActivation', 'CompilerLoaderPluginClient')
this.onload(() => {
console.log('onload', 'CompilerLoaderPluginClient')
this.emit('loaded')
})
}

@ -6,7 +6,7 @@ console.log('preload.ts', new Date().toLocaleTimeString())
/* preload script needs statically defined API for each plugin */
const exposedPLugins = ['fs', 'git', 'xterm', 'isogit', 'electronconfig', 'electronTemplates', 'ripgrep', 'compilerloader']
const exposedPLugins = ['fs', 'git', 'xterm', 'isogit', 'electronconfig', 'electronTemplates', 'ripgrep', 'compilerloader', 'appUpdater']
let webContentsId: number | undefined

File diff suppressed because it is too large Load Diff

@ -332,7 +332,6 @@
"css-minimizer-webpack-plugin": "^4.2.2",
"csslint": "^1.0.2",
"dotenv": "^8.2.0",
"electron": "^24.4.0",
"eslint": "^8.26.0",
"eslint-config-standard": "^14.1.1",
"eslint-plugin-import": "2.26.0",

@ -6449,11 +6449,6 @@
dependencies:
undici-types "~5.26.4"
"@types/node@^18.11.18":
version "18.16.16"
resolved "https://registry.yarnpkg.com/@types/node/-/node-18.16.16.tgz#3b64862856c7874ccf7439e6bab872d245c86d8e"
integrity sha512-NpaM49IGQQAUlBhHMF82QH80J08os4ZmyF9MkpCzWAGuOHqE4gTEbhzd7L3l5LmWuZ6E0OiC1FweQ4tsiW35+g==
"@types/normalize-package-data@^2.4.0":
version "2.4.1"
resolved "https://registry.yarnpkg.com/@types/normalize-package-data/-/normalize-package-data-2.4.1.tgz#d3357479a0fdfdd5907fe67e17e0a85c906e1301"
@ -12915,15 +12910,6 @@ electron-to-chromium@^1.4.251:
resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.4.264.tgz#2f68a062c38b7a04bf57f3e6954b868672fbdcd3"
integrity sha512-AZ6ZRkucHOQT8wke50MktxtmcWZr67kE17X/nAXFf62NIdMdgY6xfsaJD5Szoy84lnkuPWH+4tTNE3s2+bPCiw==
electron@^24.4.0:
version "24.4.0"
resolved "https://registry.yarnpkg.com/electron/-/electron-24.4.0.tgz#48d05561dab6a5835ec1a3a96852797f69824eea"
integrity sha512-A9YzLHRUA+HfYVf2daNv0jPXNCWShgcgcTaGntrZRGynQLEhDTbti9Lfmq2tjRKoEgXZ7qj+aJFw+tJZsT/Cfw==
dependencies:
"@electron/get" "^2.0.0"
"@types/node" "^18.11.18"
extract-zip "^2.0.1"
elliptic@6.5.4, elliptic@^6.5.2, elliptic@^6.5.3, elliptic@^6.5.4:
version "6.5.4"
resolved "https://registry.yarnpkg.com/elliptic/-/elliptic-6.5.4.tgz#da37cebd31e79a1367e941b592ed1fbebd58abbb"
@ -14233,7 +14219,7 @@ extglob@^2.0.4:
snapdragon "^0.8.1"
to-regex "^3.0.1"
extract-zip@2.0.1, extract-zip@^2.0.0, extract-zip@^2.0.1:
extract-zip@2.0.1, extract-zip@^2.0.0:
version "2.0.1"
resolved "https://registry.yarnpkg.com/extract-zip/-/extract-zip-2.0.1.tgz#663dca56fe46df890d5f131ef4a06d22bb8ba13a"
integrity sha512-GDhU9ntwuKyGXdZBUgTIe+vXnWj0fppUEtMDL0+idd5Sta8TGpHssn/eusA9mrPr9qNDym6SxAYZjNvCn/9RBg==

Loading…
Cancel
Save