diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 86eb2d09d..a4dd10767 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -35,6 +35,11 @@ Start development mode (includes GraphQL codegen and hot module reload): pnpm dev ``` +> [!NOTE] +> This will also install React Developer Tools extension automatically so you can inspect the renderer process. +> Make sure you force a reload when first opening the `Developer Tools`. + + ### Tests There are two main checks: diff --git a/package.json b/package.json index 4a88bc3e9..72e992ca6 100644 --- a/package.json +++ b/package.json @@ -110,6 +110,7 @@ "dotenv": "17.3.1", "electron": "40.6.0", "electron-builder": "26.8.1", + "electron-devtools-installer": "4.0.0", "final-form": "5.0.0", "graphql": "16.12.0", "happy-dom": "20.6.2", @@ -134,6 +135,7 @@ "@swc/core", "@tailwindcss/oxide", "electron", + "electron-winstaller", "esbuild", "unrs-resolver" ] @@ -142,4 +144,4 @@ "*": "biome check --no-errors-on-unmatched", "*.{js,ts,tsx}": "pnpm test --changed --passWithNoTests --update" } -} +} \ No newline at end of file diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 390b6c064..b0027b89c 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -138,6 +138,9 @@ importers: electron-builder: specifier: 26.8.1 version: 26.8.1(electron-builder-squirrel-windows@26.7.0) + electron-devtools-installer: + specifier: 4.0.0 + version: 4.0.0 final-form: specifier: 5.0.0 version: 5.0.0 @@ -2276,6 +2279,9 @@ packages: engines: {node: '>=14.0.0'} hasBin: true + electron-devtools-installer@4.0.0: + resolution: {integrity: sha512-9Tntu/jtfSn0n6N/ZI6IdvRqXpDyLQiDuuIbsBI+dL+1Ef7C8J2JwByw58P3TJiNeuqyV3ZkphpNWuZK5iSY2w==} + electron-log@5.4.3: resolution: {integrity: sha512-sOUsM3LjZdugatazSQ/XTyNcw8dfvH1SYhXWiJyfYodAAKOZdHs0txPiLDXFzOZbhXgAgshQkshH2ccq0feyLQ==} engines: {node: '>= 14'} @@ -2685,6 +2691,9 @@ packages: resolution: {integrity: sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==} engines: {node: '>= 4'} + immediate@3.0.6: + resolution: {integrity: sha512-XXOFtyqDjNDAQxVfYxuF7g9Il/IbWmmlQg2MYKOH8ExIT1qg6xc4zyS3HaEEATgs1btfzxq15ciUiY7gjSXRGQ==} + immutable@3.7.6: resolution: {integrity: sha512-AizQPcaofEtO11RZhPPHBOJRdo/20MKQF9mBLnVkBoyHi1/zXK8fzVdnEpSV9gxqtnh6Qomfp3F0xT5qP/vThw==} engines: {node: '>=0.8.0'} @@ -2776,6 +2785,9 @@ packages: resolution: {integrity: sha512-eXK1UInq2bPmjyX6e3VHIzMLobc4J94i4AWn+Hpq3OU5KkrRC96OAcR3PRJ/pGu6m8TRnBHP9dkXQVsT/COVIA==} engines: {node: '>=0.10.0'} + isarray@1.0.0: + resolution: {integrity: sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==} + isbinaryfile@4.0.10: resolution: {integrity: sha512-iHrqe5shvBUcFbmZq9zOQHBoeOhZJu6RQGrDpBgenUm/Am+F3JM2MgQj+rK3Z601fzrL5gLZWtAPH2OBaSVcyw==} engines: {node: '>= 8.0.0'} @@ -2874,12 +2886,18 @@ packages: jsonfile@6.2.0: resolution: {integrity: sha512-FGuPw30AdOIUTRMC2OMRtQV+jkVj2cfPqSeWXv1NEAJ1qZ5zb1X6z1mFhbfOB/iy3ssJCD+3KuZ8r8C3uVFlAg==} + jszip@3.10.1: + resolution: {integrity: sha512-xXDvecyTpGLrqFrvkrUSoxxfJI5AH7U8zxxtVclpsUtMCq4JQ290LY8AW5c7Ggnr/Y/oK+bQMbqK2qmtk3pN4g==} + keyv@4.5.4: resolution: {integrity: sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==} lazy-val@1.0.5: resolution: {integrity: sha512-0/BnGCCfyUMkBpeDgWihanIAF9JmZhHBgUhEqzvf+adhNGLoP6TaiI5oF8oyb3I45P+PcnrqihSf01M0l0G5+Q==} + lie@3.3.0: + resolution: {integrity: sha512-UaiMJzeWRlEujzAuw5LokY1L5ecNQYZKfmyZ9L7wDHb/p5etKaxXhohBcrw0EYby+G/NA52vRSN4N39dxHAIwQ==} + lightningcss-android-arm64@1.30.2: resolution: {integrity: sha512-BH9sEdOCahSgmkVhBLeU7Hc9DWeZ1Eb6wNS6Da8igvUwAe0sqROHddIlvU06q3WyXVEOYDZ6ykBZQnjTbmo4+A==} engines: {node: '>= 12.0.0'} @@ -3285,6 +3303,9 @@ packages: package-json-from-dist@1.0.1: resolution: {integrity: sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==} + pako@1.0.11: + resolution: {integrity: sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw==} + param-case@3.0.4: resolution: {integrity: sha512-RXlj7zCYokReqWpOPH9oYivUzLYZ5vAPIfEmCTNViosC78F8F0H9y7T7gG2M39ymgutxF5gcFEsyZQSph9Bp3A==} @@ -3380,6 +3401,9 @@ packages: resolution: {integrity: sha512-Azwzvl90HaF0aCz1JrDdXQykFakSSNPaPoiZ9fm5qJIMHioDZEi7OAdRwSm6rSoPtY3Qutnm3L7ogmg3dc+wbQ==} engines: {node: ^18.17.0 || >=20.5.0} + process-nextick-args@2.0.1: + resolution: {integrity: sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==} + progress@2.0.3: resolution: {integrity: sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==} engines: {node: '>=0.4.0'} @@ -3458,6 +3482,9 @@ packages: resolution: {integrity: sha512-BNg9EN3DD3GsDXX7Aa8O4p92sryjkmzYYgmgTAc6CA4uGLEDzFfxOxugu21akOxpcXHiEgsYkC6nPsQvLLLmEg==} hasBin: true + readable-stream@2.3.8: + resolution: {integrity: sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==} + readable-stream@3.6.2: resolution: {integrity: sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==} engines: {node: '>= 6'} @@ -3552,6 +3579,9 @@ packages: rxjs@7.8.2: resolution: {integrity: sha512-dhKf903U/PQZY6boNNtAGdWbG85WAbjT/1xYoZIC7FAY0yWapOBQVsVrDl58W86//e1VpMNBtRV4MaXfdMySFA==} + safe-buffer@5.1.2: + resolution: {integrity: sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==} + safe-buffer@5.2.1: resolution: {integrity: sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==} @@ -3707,6 +3737,9 @@ packages: resolution: {integrity: sha512-KpqHIdDL9KwYk22wEOg/VIqYbrnLeSApsKT/bSj6Ez7pn3CftUiLAv2Lccpq1ALcpLV9UX1Ppn92npZWu2w/aw==} engines: {node: '>=20'} + string_decoder@1.1.1: + resolution: {integrity: sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==} + string_decoder@1.3.0: resolution: {integrity: sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==} @@ -3878,6 +3911,9 @@ packages: resolution: {integrity: sha512-6bc58dPYhCMHHuwxldQxO3RRNZ4eCogZ/st++0+fcC1nr0jiGUtAdBJ2qzmLQWSxbtz42pWt4QQMiZ9HvZf5cg==} engines: {node: '>=0.10.0'} + unzip-crx-3@0.2.0: + resolution: {integrity: sha512-0+JiUq/z7faJ6oifVB5nSwt589v1KCduqIJupNVDoWSXZtWDmjDGO3RAEOvwJ07w90aoXoP4enKsR7ecMrJtWQ==} + update-browserslist-db@1.2.3: resolution: {integrity: sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w==} hasBin: true @@ -4112,6 +4148,9 @@ packages: resolution: {integrity: sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==} engines: {node: '>=10'} + yaku@0.16.7: + resolution: {integrity: sha512-Syu3IB3rZvKvYk7yTiyl1bo/jiEFaaStrgv1V2TIJTqYPStSMQVO8EQjg/z+DRzLq/4LIIharNT3iH1hylEIRw==} + yallist@3.1.1: resolution: {integrity: sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==} @@ -6330,8 +6369,7 @@ snapshots: cookie@1.1.1: {} - core-util-is@1.0.2: - optional: true + core-util-is@1.0.2: {} cosmiconfig@8.3.6(typescript@5.9.3): dependencies: @@ -6520,6 +6558,10 @@ snapshots: - electron-builder-squirrel-windows - supports-color + electron-devtools-installer@4.0.0: + dependencies: + unzip-crx-3: 0.2.0 + electron-log@5.4.3: {} electron-positioner@4.1.0: {} @@ -7042,6 +7084,8 @@ snapshots: ignore@5.3.2: {} + immediate@3.0.6: {} + immutable@3.7.6: {} import-fresh@3.3.1: @@ -7115,6 +7159,8 @@ snapshots: is-windows@1.0.2: {} + isarray@1.0.0: {} + isbinaryfile@4.0.10: {} isbinaryfile@5.0.7: {} @@ -7204,12 +7250,23 @@ snapshots: optionalDependencies: graceful-fs: 4.2.11 + jszip@3.10.1: + dependencies: + lie: 3.3.0 + pako: 1.0.11 + readable-stream: 2.3.8 + setimmediate: 1.0.5 + keyv@4.5.4: dependencies: json-buffer: 3.0.1 lazy-val@1.0.5: {} + lie@3.3.0: + dependencies: + immediate: 3.0.6 + lightningcss-android-arm64@1.30.2: optional: true @@ -7573,6 +7630,8 @@ snapshots: package-json-from-dist@1.0.1: {} + pako@1.0.11: {} + param-case@3.0.4: dependencies: dot-case: 3.0.4 @@ -7666,6 +7725,8 @@ snapshots: proc-log@5.0.0: {} + process-nextick-args@2.0.1: {} + progress@2.0.3: {} promise-retry@2.0.1: @@ -7735,6 +7796,16 @@ snapshots: transitivePeerDependencies: - supports-color + readable-stream@2.3.8: + dependencies: + core-util-is: 1.0.2 + inherits: 2.0.4 + isarray: 1.0.0 + process-nextick-args: 2.0.1 + safe-buffer: 5.1.2 + string_decoder: 1.1.1 + util-deprecate: 1.0.2 + readable-stream@3.6.2: dependencies: inherits: 2.0.4 @@ -7856,6 +7927,8 @@ snapshots: dependencies: tslib: 2.8.1 + safe-buffer@5.1.2: {} + safe-buffer@5.2.1: {} safer-buffer@2.1.2: {} @@ -8003,6 +8076,10 @@ snapshots: get-east-asian-width: 1.4.0 strip-ansi: 7.1.2 + string_decoder@1.1.1: + dependencies: + safe-buffer: 5.1.2 + string_decoder@1.3.0: dependencies: safe-buffer: 5.2.1 @@ -8155,6 +8232,12 @@ snapshots: dependencies: normalize-path: 2.1.1 + unzip-crx-3@0.2.0: + dependencies: + jszip: 3.10.1 + mkdirp: 0.5.6 + yaku: 0.16.7 + update-browserslist-db@1.2.3(browserslist@4.28.1): dependencies: browserslist: 4.28.1 @@ -8333,6 +8416,8 @@ snapshots: y18n@5.0.8: {} + yaku@0.16.7: {} + yallist@3.1.1: {} yallist@4.0.0: {} diff --git a/src/main/devtools.ts b/src/main/devtools.ts new file mode 100644 index 000000000..f17316aac --- /dev/null +++ b/src/main/devtools.ts @@ -0,0 +1,62 @@ +import { session } from 'electron'; +import installExtension, { + REACT_DEVELOPER_TOOLS, +} from 'electron-devtools-installer'; + +import { logInfo, logWarn } from '../shared/logger'; + +import { isDevMode } from './utils'; + +let installTask: Promise | null = null; + +export async function installReactDevtools() { + if (!isDevMode) { + return; + } + + if (installTask) { + await installTask; + return; + } + + installTask = (async () => { + try { + const result = await installExtension(REACT_DEVELOPER_TOOLS, { + loadExtensionOptions: { + allowFileAccess: true, + }, + forceDownload: false, + }); + + logInfo('devtools', `Installed ${result.name} v${result.version}`); + + // Verify the extension is loaded + const extensions = session.defaultSession.extensions.getAllExtensions(); + const installedReactDevTools = extensions.find((ext) => + ext.name.includes('React Developer Tools'), + ); + + if (installedReactDevTools) { + logInfo( + 'devtools', + `React Developer Tools verified: ${installedReactDevTools.name} v${installedReactDevTools.version}`, + ); + } else { + logWarn( + 'devtools', + 'React Developer Tools not found after installation', + ); + } + } catch (error) { + logWarn( + 'devtools', + 'Failed to install React DevTools via installer', + error, + ); + } finally { + installTask = null; + } + })(); + + await installTask; +} diff --git a/src/main/first-run.ts b/src/main/first-run.ts index 224d79c8b..c799578ae 100644 --- a/src/main/first-run.ts +++ b/src/main/first-run.ts @@ -7,6 +7,8 @@ import { APPLICATION } from '../shared/constants'; import { logError } from '../shared/logger'; import { isMacOS } from '../shared/platform'; +import { isDevMode } from './utils'; + export async function onFirstRunMaybe() { if (isFirstRun()) { await promptMoveToApplicationsFolder(); @@ -21,7 +23,6 @@ async function promptMoveToApplicationsFolder() { return; } - const isDevMode = !!process.defaultApp; if (isDevMode || app.isInApplicationsFolder()) { return; } diff --git a/src/main/index.ts b/src/main/index.ts index a7eff7f68..309a02a74 100644 --- a/src/main/index.ts +++ b/src/main/index.ts @@ -21,6 +21,7 @@ import { } from '../shared/events'; import { logInfo, logWarn } from '../shared/logger'; +import { installReactDevtools } from './devtools'; import { handleMainEvent, onMainEvent, sendRendererEvent } from './events'; import { onFirstRunMaybe } from './first-run'; import { TrayIcons } from './icons'; @@ -89,6 +90,7 @@ let shouldUseUnreadActiveIcon = true; app.whenReady().then(async () => { preventSecondInstance(); + await installReactDevtools(); await onFirstRunMaybe(); appUpdater.start();