diff --git a/.gitignore b/.gitignore index f4243f680bc..25e14cb305c 100644 --- a/.gitignore +++ b/.gitignore @@ -21,6 +21,7 @@ packages/api/dist packages/api/@types packages/crdt/dist packages/desktop-electron/client-build +packages/desktop-electron/build packages/desktop-electron/.electron-symbols packages/desktop-electron/dist packages/desktop-electron/loot-core diff --git a/packages/desktop-electron/bin/update-client b/packages/desktop-electron/bin/update-client index 3560b0bcf08..778b79386e9 100755 --- a/packages/desktop-electron/bin/update-client +++ b/packages/desktop-electron/bin/update-client @@ -2,12 +2,13 @@ ROOT=`dirname $0`/.. -rm -rf ${ROOT}/client-build -cp -r ${ROOT}/../desktop-client/build ${ROOT}/client-build +rm -rf ${ROOT}/build +mkdir -p ${ROOT}/build +cp -r ${ROOT}/../desktop-client/build ${ROOT}/build/client-build # Remove the embedded backend for the browser version. Will improve # this process -rm -rf ${ROOT}/client-build/data -rm -rf ${ROOT}/client-build/*kcab.* -rm -rf ${ROOT}/client-build/*.wasm -rm -rf ${ROOT}/client-build/*.map +rm -rf ${ROOT}/build/client-build/data +rm -rf ${ROOT}/build/client-build/*kcab.* +rm -rf ${ROOT}/build/client-build/*.wasm +rm -rf ${ROOT}/build/client-build/*.map diff --git a/packages/desktop-electron/index.js b/packages/desktop-electron/index.ts similarity index 81% rename from packages/desktop-electron/index.js rename to packages/desktop-electron/index.ts index f3b2fca55ae..46b126559dc 100644 --- a/packages/desktop-electron/index.js +++ b/packages/desktop-electron/index.ts @@ -1,11 +1,8 @@ -/* eslint-disable import/order */ -// (I have no idea why the imports are like this. Not touching them.) -const isDev = require('electron-is-dev'); -const fs = require('fs'); +import fs from 'fs'; +import NodeModule from 'module'; +import path from 'path'; -require('module').globalPaths.push(__dirname + '/..'); - -const { +import { app, ipcMain, BrowserWindow, @@ -14,8 +11,28 @@ const { shell, protocol, utilityProcess, -} = require('electron'); -const promiseRetry = require('promise-retry'); + UtilityProcess, +} from 'electron'; +import isDev from 'electron-is-dev'; +import promiseRetry from 'promise-retry'; + +import about from './about'; +import getMenu from './menu'; +import updater from './updater'; +import { + get as getWindowState, + listen as listenToWindowState, +} from './window-state'; + +import './setRequireHook'; + +import './security'; + +const Module: typeof NodeModule & { globalPaths: string[] } = + // eslint-disable-next-line @typescript-eslint/no-explicit-any + NodeModule as unknown as any; + +Module.globalPaths.push(__dirname + '/..'); // This allows relative URLs to be resolved to app:// which makes // local assets load correctly @@ -25,16 +42,6 @@ protocol.registerSchemesAsPrivileged([ global.fetch = require('node-fetch'); -const about = require('./about'); -const getMenu = require('./menu'); -const updater = require('./updater'); - -require('./security'); - -const path = require('path'); - -require('./setRequireHook'); - if (!isDev || !process.env.ACTUAL_DOCUMENT_DIR) { process.env.ACTUAL_DOCUMENT_DIR = app.getPath('documents'); } @@ -43,15 +50,12 @@ if (!isDev || !process.env.ACTUAL_DATA_DIR) { process.env.ACTUAL_DATA_DIR = app.getPath('userData'); } -// eslint-disable-next-line import/extensions -const WindowState = require('./window-state.js'); - // Keep a global reference of the window object, if you don't, the window will // be closed automatically when the JavaScript object is garbage collected. -let clientWin; -let serverProcess; +let clientWin: BrowserWindow | null; +let serverProcess: UtilityProcess | null; -updater.onEvent((type, data) => { +updater.onEvent((type: string, data: Record | string) => { // Notify both the app and the about window if (clientWin) { clientWin.webContents.send(type, data); @@ -99,7 +103,7 @@ function createBackgroundProcess() { } async function createWindow() { - const windowState = await WindowState.get(); + const windowState = await getWindowState(); // Create the browser window. const win = new BrowserWindow({ @@ -113,7 +117,6 @@ async function createWindow() { nodeIntegrationInWorker: false, nodeIntegrationInSubFrames: false, contextIsolation: true, - enableRemoteModule: false, preload: __dirname + '/preload.js', }, }); @@ -123,7 +126,7 @@ async function createWindow() { win.webContents.openDevTools(); } - const unlistenToState = WindowState.listen(win, windowState); + const unlistenToState = listenToWindowState(win, windowState); if (isDev) { win.loadURL(`file://${__dirname}/loading.html`); @@ -148,9 +151,11 @@ async function createWindow() { }); win.on('focus', async () => { - const url = clientWin.webContents.getURL(); - if (url.includes('app://') || url.includes('localhost:')) { - clientWin.webContents.executeJavaScript('__actionsForMenu.focused()'); + if (clientWin) { + const url = clientWin.webContents.getURL(); + if (url.includes('app://') || url.includes('localhost:')) { + clientWin.webContents.executeJavaScript('__actionsForMenu.focused()'); + } } }); @@ -183,29 +188,28 @@ async function createWindow() { clientWin = win; } -function isExternalUrl(url) { +function isExternalUrl(url: string) { return !url.includes('localhost:') && !url.includes('app://'); } -function updateMenu(budgetId) { +function updateMenu(budgetId?: string) { const isBudgetOpen = !!budgetId; - const menu = getMenu(isDev, createWindow); + const menu = getMenu(isDev, createWindow, budgetId); const file = menu.items.filter(item => item.label === 'File')[0]; - const fileItems = file.submenu.items; + const fileItems = file.submenu?.items || []; fileItems .filter(item => item.label === 'Load Backup...') .forEach(item => { item.enabled = isBudgetOpen; - item.budgetId = budgetId; }); const tools = menu.items.filter(item => item.label === 'Tools')[0]; - tools.submenu.items.forEach(item => { + tools.submenu?.items.forEach(item => { item.enabled = isBudgetOpen; }); const edit = menu.items.filter(item => item.label === 'Edit')[0]; - const editItems = edit.submenu.items; + const editItems = edit.submenu?.items || []; editItems .filter(item => item.label === 'Undo' || item.label === 'Redo') .map(item => (item.enabled = isBudgetOpen)); @@ -312,7 +316,7 @@ ipcMain.handle( async (event, { title, defaultPath, fileContents }) => { const fileLocation = await dialog.showSaveDialog({ title, defaultPath }); - return new Promise((resolve, reject) => { + return new Promise((resolve, reject) => { if (fileLocation) { fs.writeFile(fileLocation.filePath, fileContents, error => { return reject(error); @@ -344,8 +348,9 @@ ipcMain.on('screenshot', () => { const width = 1100; // This is for the main screenshot inside the frame - clientWin.setSize(width, Math.floor(width * (427 / 623))); - // clientWin.setSize(width, Math.floor(width * (495 / 700))); + if (clientWin) { + clientWin.setSize(width, Math.floor(width * (427 / 623))); + } } }); @@ -366,14 +371,15 @@ ipcMain.on('apply-update', () => { updater.apply(); }); -ipcMain.on('update-menu', (event, budgetId) => { +ipcMain.on('update-menu', (event, budgetId?: string) => { updateMenu(budgetId); }); ipcMain.on('set-theme', theme => { const obj = { theme }; - - clientWin.webContents.executeJavaScript( - `window.__actionsForMenu && window.__actionsForMenu.saveGlobalPrefs(${obj})`, - ); + if (clientWin) { + clientWin.webContents.executeJavaScript( + `window.__actionsForMenu && window.__actionsForMenu.saveGlobalPrefs(${obj})`, + ); + } }); diff --git a/packages/desktop-electron/menu.js b/packages/desktop-electron/menu.js index fc274511851..37d290c2666 100644 --- a/packages/desktop-electron/menu.js +++ b/packages/desktop-electron/menu.js @@ -1,6 +1,6 @@ const { Menu, ipcMain, app, shell } = require('electron'); -function getMenu(isDev, createWindow) { +function getMenu(isDev, createWindow, budgetId) { const template = [ { label: 'File', @@ -9,10 +9,10 @@ function getMenu(isDev, createWindow) { label: 'Load Backup...', enabled: false, click(item, focusedWindow) { - if (focusedWindow) { + if (focusedWindow && budgetId) { if (focusedWindow.webContents.getTitle() === 'Actual') { focusedWindow.webContents.executeJavaScript( - `__actionsForMenu.replaceModal('load-backup', { budgetId: '${item.budgetId}' })`, + `__actionsForMenu.replaceModal('load-backup', { budgetId: '${budgetId}' })`, ); } } diff --git a/packages/desktop-electron/package.json b/packages/desktop-electron/package.json index ef1199bbd92..1134f506cd9 100644 --- a/packages/desktop-electron/package.json +++ b/packages/desktop-electron/package.json @@ -7,10 +7,12 @@ "scripts": { "clean": "rm -rf dist", "update-client": "bin/update-client", - "build": "electron-builder", - "watch": "cross-env ACTUAL_DOCUMENT_DIR=\"../../data\" ACTUAL_DATA_DIR=\"../../data\" electron ." + "build": "yarn build:dist && electron-builder", + "build:dist": "tsc --p tsconfig.dist.json && yarn copy-static-html", + "copy-static-html": "copyfiles --exclude 'build/**/*' **/*.html icons/**/* build", + "watch": "yarn build:dist && cross-env ACTUAL_DOCUMENT_DIR=\"../../data\" ACTUAL_DATA_DIR=\"../../data\" electron ." }, - "main": "index.js", + "main": "build/index.js", "build": { "appId": "com.actualbudget.actual", "files": [ @@ -19,7 +21,8 @@ "!**/*.js.map", "!node_modules/@jlongster/sql.js", "!node_modules/absurd-sql", - "!node_modules/better-sqlite3/{benchmark,src,bin,docs,deps,build/Release/obj,build/Release/sqlite3.a,build/Release/test_extension.node}" + "!node_modules/better-sqlite3/{benchmark,src,bin,docs,deps,build/Release/obj,build/Release/sqlite3.a,build/Release/test_extension.node}", + "build" ], "publish": { "provider": "github", @@ -63,8 +66,11 @@ "devDependencies": { "@electron/notarize": "2.2.0", "@electron/rebuild": "3.6.0", + "@types/copyfiles": "^2", + "copyfiles": "^2.4.1", "cross-env": "^7.0.3", "electron": "30.0.6", - "electron-builder": "24.13.3" + "electron-builder": "24.13.3", + "typescript": "^5.0.2" } } diff --git a/packages/desktop-electron/tsconfig.dist.json b/packages/desktop-electron/tsconfig.dist.json new file mode 100644 index 00000000000..5a0ba042c52 --- /dev/null +++ b/packages/desktop-electron/tsconfig.dist.json @@ -0,0 +1,14 @@ +{ + "extends": "../../tsconfig.json", + "compilerOptions": { + // Using ES2021 because that’s the newest version where + // the latest Node 16.x release supports all of the features + "target": "ES2021", + "module": "CommonJS", + "noEmit": false, + "declaration": true, + "outDir": "build", + }, + "include": ["."], + "exclude": ["**/node_modules/*", "build/**/*"] +} diff --git a/upcoming-release-notes/2880.md b/upcoming-release-notes/2880.md new file mode 100644 index 00000000000..d6e6b7480b3 --- /dev/null +++ b/upcoming-release-notes/2880.md @@ -0,0 +1,6 @@ +--- +category: Maintenance +authors: [MikesGlitch] +--- + +Refactoring desktop-electron package to use typescript diff --git a/yarn.lock b/yarn.lock index f2f8d291424..345c0eed3f8 100644 --- a/yarn.lock +++ b/yarn.lock @@ -5443,6 +5443,13 @@ __metadata: languageName: node linkType: hard +"@types/copyfiles@npm:^2": + version: 2.4.4 + resolution: "@types/copyfiles@npm:2.4.4" + checksum: 0513199240828feda5f6ed04c69d6a642c47e6ab66b81214716807f948ed3e865e9b3d2b69f75cbcc6fbe2154630755c47ca473b3913f0a831179366c709a8cc + languageName: node + linkType: hard + "@types/d3-array@npm:^3.0.3": version: 3.0.4 resolution: "@types/d3-array@npm:3.0.4" @@ -7999,6 +8006,24 @@ __metadata: languageName: node linkType: hard +"copyfiles@npm:^2.4.1": + version: 2.4.1 + resolution: "copyfiles@npm:2.4.1" + dependencies: + glob: "npm:^7.0.5" + minimatch: "npm:^3.0.3" + mkdirp: "npm:^1.0.4" + noms: "npm:0.0.0" + through2: "npm:^2.0.1" + untildify: "npm:^4.0.0" + yargs: "npm:^16.1.0" + bin: + copyfiles: copyfiles + copyup: copyfiles + checksum: 17070f88cbeaf62a9355341cb2521bacd48069e1ac8e7f95a3f69c848c53646f16ff0f94807a789e0f3eedc11407ec8d3980a13ab62e2add6ef81d0a5900fd85 + languageName: node + linkType: hard + "core-js-compat@npm:^3.31.0, core-js-compat@npm:^3.34.0": version: 3.36.0 resolution: "core-js-compat@npm:3.36.0" @@ -8655,6 +8680,8 @@ __metadata: dependencies: "@electron/notarize": "npm:2.2.0" "@electron/rebuild": "npm:3.6.0" + "@types/copyfiles": "npm:^2" + copyfiles: "npm:^2.4.1" cross-env: "npm:^7.0.3" electron: "npm:30.0.6" electron-builder: "npm:24.13.3" @@ -8664,6 +8691,7 @@ __metadata: loot-core: "npm:*" node-fetch: "npm:^2.7.0" promise-retry: "npm:^2.0.1" + typescript: "npm:^5.0.2" languageName: unknown linkType: soft @@ -10682,7 +10710,7 @@ __metadata: languageName: node linkType: hard -"glob@npm:^7.1.1, glob@npm:^7.1.2, glob@npm:^7.1.3, glob@npm:^7.1.4, glob@npm:^7.1.6": +"glob@npm:^7.0.5, glob@npm:^7.1.1, glob@npm:^7.1.2, glob@npm:^7.1.3, glob@npm:^7.1.4, glob@npm:^7.1.6": version: 7.2.3 resolution: "glob@npm:7.2.3" dependencies: @@ -11206,7 +11234,7 @@ __metadata: languageName: node linkType: hard -"inherits@npm:2, inherits@npm:^2.0.3, inherits@npm:^2.0.4, inherits@npm:~2.0.3, inherits@npm:~2.0.4": +"inherits@npm:2, inherits@npm:^2.0.1, inherits@npm:^2.0.3, inherits@npm:^2.0.4, inherits@npm:~2.0.1, inherits@npm:~2.0.3, inherits@npm:~2.0.4": version: 2.0.4 resolution: "inherits@npm:2.0.4" checksum: cd45e923bee15186c07fa4c89db0aace24824c482fb887b528304694b2aa6ff8a898da8657046a5dcf3e46cd6db6c61629551f9215f208d7c3f157cf9b290521 @@ -11771,6 +11799,13 @@ __metadata: languageName: node linkType: hard +"isarray@npm:0.0.1": + version: 0.0.1 + resolution: "isarray@npm:0.0.1" + checksum: 49191f1425681df4a18c2f0f93db3adb85573bcdd6a4482539d98eac9e705d8961317b01175627e860516a2fc45f8f9302db26e5a380a97a520e272e2a40a8d4 + languageName: node + linkType: hard + "isarray@npm:^2.0.5": version: 2.0.5 resolution: "isarray@npm:2.0.5" @@ -13976,7 +14011,7 @@ __metadata: languageName: node linkType: hard -"minimatch@npm:^3.0.4, minimatch@npm:^3.0.5, minimatch@npm:^3.1.1, minimatch@npm:^3.1.2": +"minimatch@npm:^3.0.3, minimatch@npm:^3.0.4, minimatch@npm:^3.0.5, minimatch@npm:^3.1.1, minimatch@npm:^3.1.2": version: 3.1.2 resolution: "minimatch@npm:3.1.2" dependencies: @@ -14401,6 +14436,16 @@ __metadata: languageName: node linkType: hard +"noms@npm:0.0.0": + version: 0.0.0 + resolution: "noms@npm:0.0.0" + dependencies: + inherits: "npm:^2.0.1" + readable-stream: "npm:~1.0.31" + checksum: a05f056dabf764c86472b6b5aad10455f3adcb6971f366cdf36a72b559b29310a940e316bca30802f2804fdd41707941366224f4cba80c4f53071512245bf200 + languageName: node + linkType: hard + "nopt@npm:^6.0.0": version: 6.0.0 resolution: "nopt@npm:6.0.0" @@ -15893,7 +15938,7 @@ __metadata: languageName: node linkType: hard -"readable-stream@npm:^2.3.0, readable-stream@npm:^2.3.5": +"readable-stream@npm:^2.3.0, readable-stream@npm:^2.3.5, readable-stream@npm:~2.3.6": version: 2.3.8 resolution: "readable-stream@npm:2.3.8" dependencies: @@ -15919,6 +15964,18 @@ __metadata: languageName: node linkType: hard +"readable-stream@npm:~1.0.31": + version: 1.0.34 + resolution: "readable-stream@npm:1.0.34" + dependencies: + core-util-is: "npm:~1.0.0" + inherits: "npm:~2.0.1" + isarray: "npm:0.0.1" + string_decoder: "npm:~0.10.x" + checksum: 20537fca5a8ffd4af0f483be1cce0e981ed8cbb1087e0c762e2e92ae77f1005627272cebed8422f28047b465056aa1961fefd24baf532ca6a3616afea6811ae0 + languageName: node + linkType: hard + "readdirp@npm:~3.6.0": version: 3.6.0 resolution: "readdirp@npm:3.6.0" @@ -17155,6 +17212,13 @@ __metadata: languageName: node linkType: hard +"string_decoder@npm:~0.10.x": + version: 0.10.31 + resolution: "string_decoder@npm:0.10.31" + checksum: cc43e6b1340d4c7843da0e37d4c87a4084c2342fc99dcf6563c3ec273bb082f0cbd4ebf25d5da19b04fb16400d393885fda830be5128e1c416c73b5a6165f175 + languageName: node + linkType: hard + "string_decoder@npm:~1.1.1": version: 1.1.1 resolution: "string_decoder@npm:1.1.1" @@ -17603,6 +17667,16 @@ __metadata: languageName: node linkType: hard +"through2@npm:^2.0.1": + version: 2.0.5 + resolution: "through2@npm:2.0.5" + dependencies: + readable-stream: "npm:~2.3.6" + xtend: "npm:~4.0.1" + checksum: cd71f7dcdc7a8204fea003a14a433ef99384b7d4e31f5497e1f9f622b3cf3be3691f908455f98723bdc80922a53af7fa10c3b7abbe51c6fd3d536dbc7850e2c4 + languageName: node + linkType: hard + "through@npm:^2.3.8": version: 2.3.8 resolution: "through@npm:2.3.8" @@ -18335,6 +18409,13 @@ __metadata: languageName: node linkType: hard +"untildify@npm:^4.0.0": + version: 4.0.0 + resolution: "untildify@npm:4.0.0" + checksum: 39ced9c418a74f73f0a56e1ba4634b4d959422dff61f4c72a8e39f60b99380c1b45ed776fbaa0a4101b157e4310d873ad7d114e8534ca02609b4916bb4187fb9 + languageName: node + linkType: hard + "upath@npm:^1.2.0": version: 1.2.0 resolution: "upath@npm:1.2.0" @@ -19355,7 +19436,7 @@ __metadata: languageName: node linkType: hard -"xtend@npm:^4.0.0": +"xtend@npm:^4.0.0, xtend@npm:~4.0.1": version: 4.0.2 resolution: "xtend@npm:4.0.2" checksum: ac5dfa738b21f6e7f0dd6e65e1b3155036d68104e67e5d5d1bde74892e327d7e5636a076f625599dc394330a731861e87343ff184b0047fef1360a7ec0a5a36a @@ -19427,7 +19508,7 @@ __metadata: languageName: node linkType: hard -"yargs@npm:^16.2.0": +"yargs@npm:^16.1.0, yargs@npm:^16.2.0": version: 16.2.0 resolution: "yargs@npm:16.2.0" dependencies: