diff --git a/.env.template b/.env.template index 997e3b808..08c1891db 100644 --- a/.env.template +++ b/.env.template @@ -1,2 +1,5 @@ # GitHub Personal Access Token for GraphQL Codegen -GITHUB_TOKEN=some-pat \ No newline at end of file +GITHUB_TOKEN=some-pat + +# Custom OAuth Client ID for GitHub Device Flow +#OAUTH_CLIENT_ID=some-client-id \ No newline at end of file diff --git a/.github/actions/setup-node/action.yml b/.github/actions/setup-node/action.yml index 153232b8a..8a097edd1 100644 --- a/.github/actions/setup-node/action.yml +++ b/.github/actions/setup-node/action.yml @@ -4,18 +4,28 @@ name: Setup Node, pnpm and install dependencies description: Setup pnpm and Node.js with caching +inputs: + run-install: + description: 'Whether to run `pnpm install`' + required: false + default: 'true' + runs: using: composite steps: - name: Setup pnpm uses: pnpm/action-setup@fc06bc1257f339d1d5d8b3a19a8cae5388b55320 # v5.0.0 + with: + run_install: false - name: Setup Node uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0 with: node-version-file: .nvmrc cache: pnpm + cache-dependency-path: pnpm-lock.yaml - name: Install dependencies + if: ${{ inputs.run-install == 'true' }} run: pnpm install --frozen-lockfile shell: bash diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index 3661f8b72..157fafa74 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -37,6 +37,7 @@ jobs: release: name: Publish ${{ matrix.platform }} (electron-builder) + needs: prepare strategy: matrix: include: diff --git a/.github/workflows/renovate.yml b/.github/workflows/renovate.yml index 52cf2e67d..7bcd4ea55 100644 --- a/.github/workflows/renovate.yml +++ b/.github/workflows/renovate.yml @@ -21,16 +21,9 @@ jobs: - name: Checkout uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - - name: Setup pnpm - uses: pnpm/action-setup@fc06bc1257f339d1d5d8b3a19a8cae5388b55320 # v5.0.0 + - uses: ./.github/actions/setup-node with: - run_install: false - - - name: Setup Node - uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0 - with: - node-version-file: .nvmrc - cache: pnpm + run-install: "false" - name: Install Renovate run: pnpm install --global renovate diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 081f3e7f9..41b73e3d8 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -15,7 +15,8 @@ jobs: - name: Checkout uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - - uses: ./.github/actions/setup-node + - name: Setup Node.js + uses: ./.github/actions/setup-node - name: Check TypeScript run: pnpm tsc --noEmit diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 86eb2d09d..456d6b8f6 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -25,11 +25,6 @@ Copy the `.env.template` to `.env` and add update `GITHUB_TOKEN` with a GitHub P GITHUB_TOKEN= ``` -Build static resources (tray icons, twemojis, etc). You only need to rebuild if you change static assets: - ```shell - pnpm build - ``` - Start development mode (includes GraphQL codegen and hot module reload): ```shell pnpm dev diff --git a/package.json b/package.json index b072975fb..f07bc75b7 100644 --- a/package.json +++ b/package.json @@ -125,7 +125,6 @@ "vite-plugin-checker": "0.12.0", "vite-plugin-electron": "0.29.1", "vite-plugin-electron-renderer": "0.14.6", - "vite-plugin-static-copy": "3.3.0", "vitest": "4.1.0", "zustand": "5.0.12" }, diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index dc2979e67..3af19a422 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -183,9 +183,6 @@ importers: vite-plugin-electron-renderer: specifier: 0.14.6 version: 0.14.6 - vite-plugin-static-copy: - specifier: 3.3.0 - version: 3.3.0(vite@8.0.2(@types/node@24.12.0)(jiti@2.6.1)(yaml@2.8.2)) vitest: specifier: 4.1.0 version: 4.1.0(@types/node@24.12.0)(@vitest/ui@4.1.0)(happy-dom@20.8.4)(vite@8.0.2(@types/node@24.12.0)(jiti@2.6.1)(yaml@2.8.2)) @@ -1528,10 +1525,6 @@ packages: resolution: {integrity: sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==} engines: {node: '>=12'} - anymatch@3.1.3: - resolution: {integrity: sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==} - engines: {node: '>= 8'} - app-builder-bin@5.0.0-alpha.12: resolution: {integrity: sha512-j87o0j6LqPL3QRr8yid6c+Tt5gC7xNfYo6uQIQkorAC6MpeayVMZrEDzKmJJ/Hlv7EnOQpaRm53k6ktDYZyB6w==} @@ -1607,10 +1600,6 @@ packages: before-after-hook@4.0.0: resolution: {integrity: sha512-q6tR3RPqIB1pMiTRMFcZwuG5T8vwp+vUvEG0vuI6B+Rikh5BfPp2fQ82c925FOs+b0lcFQ8CFrL+KbilfZFhOQ==} - binary-extensions@2.3.0: - resolution: {integrity: sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==} - engines: {node: '>=8'} - bl@4.1.0: resolution: {integrity: sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==} @@ -1718,10 +1707,6 @@ packages: chardet@2.1.1: resolution: {integrity: sha512-PsezH1rqdV9VvyNhxxOW32/d75r01NY7TQCmOqomRo15ZSOKbpTFVsfjghxo6JloQUCGnH4k1LGu0R4yCLlWQQ==} - chokidar@3.6.0: - resolution: {integrity: sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==} - engines: {node: '>= 8.10.0'} - chokidar@4.0.3: resolution: {integrity: sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==} engines: {node: '>= 14.16.0'} @@ -2537,10 +2522,6 @@ packages: is-arrayish@0.2.1: resolution: {integrity: sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==} - is-binary-path@2.1.0: - resolution: {integrity: sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==} - engines: {node: '>=8'} - is-ci@1.2.1: resolution: {integrity: sha512-s6tfsaQaQi3JNciBH6shVqEDvhGut0SUXr31ag8Pd8BBbVVlcGfWhpPmEOoM6RJ5TFhbypvf5yyRw/VXW1IiWg==} hasBin: true @@ -3097,10 +3078,6 @@ packages: resolution: {integrity: sha512-3pKJwH184Xo/lnH6oyP1q2pMd7HcypqqmRs91/6/i2CGtWwIKGCkOOMTm/zXbgTEWHw1uNpNi/igc3ePOYHb6w==} engines: {node: '>=0.10.0'} - normalize-path@3.0.0: - resolution: {integrity: sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==} - engines: {node: '>=0.10.0'} - normalize-url@6.1.0: resolution: {integrity: sha512-DlL+XwOy3NxAQ8xuC0okPgK46iuVNAK01YN7RueYBqqFeGsBjV9XmCAzAdgt+667bCl5kPh9EqKKDwnaPG1I7A==} engines: {node: '>=10'} @@ -3370,10 +3347,6 @@ packages: resolution: {integrity: sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==} engines: {node: '>= 6'} - readdirp@3.6.0: - resolution: {integrity: sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==} - engines: {node: '>=8.10.0'} - readdirp@4.1.2: resolution: {integrity: sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==} engines: {node: '>= 14.18.0'} @@ -3913,12 +3886,6 @@ packages: vite-plugin-electron-renderer: optional: true - vite-plugin-static-copy@3.3.0: - resolution: {integrity: sha512-XiAtZcev7nppxNFgKoD55rfL+ukVp/RtrnTJONRwRuzv/B2FK2h2ZRCYjvxhwBV/Oarse83SiyXBSxMTfeEM0Q==} - engines: {node: ^18.0.0 || >=20.0.0} - peerDependencies: - vite: ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0 - vite@8.0.2: resolution: {integrity: sha512-1gFhNi+bHhRE/qKZOJXACm6tX4bA3Isy9KuKF15AgSRuRazNBOJfdDemPBU16/mpMxApDPrWvZ08DcLPEoRnuA==} engines: {node: ^20.19.0 || >=22.12.0} @@ -5676,11 +5643,6 @@ snapshots: ansi-styles@6.2.3: {} - anymatch@3.1.3: - dependencies: - normalize-path: 3.0.0 - picomatch: 2.3.1 - app-builder-bin@5.0.0-alpha.12: {} app-builder-lib@26.8.1(dmg-builder@26.8.1)(electron-builder-squirrel-windows@26.8.1): @@ -5770,8 +5732,6 @@ snapshots: before-after-hook@4.0.0: {} - binary-extensions@2.3.0: {} - bl@4.1.0: dependencies: buffer: 5.7.1 @@ -5949,18 +5909,6 @@ snapshots: chardet@2.1.1: {} - chokidar@3.6.0: - dependencies: - anymatch: 3.1.3 - braces: 3.0.3 - glob-parent: 5.1.2 - is-binary-path: 2.1.0 - is-glob: 4.0.3 - normalize-path: 3.0.0 - readdirp: 3.6.0 - optionalDependencies: - fsevents: 2.3.3 - chokidar@4.0.3: dependencies: readdirp: 4.1.2 @@ -6853,10 +6801,6 @@ snapshots: is-arrayish@0.2.1: {} - is-binary-path@2.1.0: - dependencies: - binary-extensions: 2.3.0 - is-ci@1.2.1: dependencies: ci-info: 1.6.0 @@ -7341,8 +7285,6 @@ snapshots: dependencies: remove-trailing-separator: 1.1.0 - normalize-path@3.0.0: {} - normalize-url@6.1.0: {} npm-run-path@2.0.2: @@ -7614,10 +7556,6 @@ snapshots: string_decoder: 1.3.0 util-deprecate: 1.0.2 - readdirp@3.6.0: - dependencies: - picomatch: 2.3.1 - readdirp@4.1.2: {} redent@3.0.0: @@ -8120,14 +8058,6 @@ snapshots: optionalDependencies: vite-plugin-electron-renderer: 0.14.6 - vite-plugin-static-copy@3.3.0(vite@8.0.2(@types/node@24.12.0)(jiti@2.6.1)(yaml@2.8.2)): - dependencies: - chokidar: 3.6.0 - p-map: 7.0.4 - picocolors: 1.1.1 - tinyglobby: 0.2.15 - vite: 8.0.2(@types/node@24.12.0)(jiti@2.6.1)(yaml@2.8.2) - vite@8.0.2(@types/node@24.12.0)(jiti@2.6.1)(yaml@2.8.2): dependencies: lightningcss: 1.32.0 diff --git a/vite.config.ts b/vite.config.ts index 70d7b7c69..cfb6d367d 100644 --- a/vite.config.ts +++ b/vite.config.ts @@ -1,3 +1,5 @@ +import fs from 'node:fs'; +import path from 'node:path'; import { fileURLToPath } from 'node:url'; import twemoji from '@discordapp/twemoji'; @@ -7,15 +9,15 @@ import type { Plugin } from 'vite'; import { defineConfig } from 'vite'; import checker from 'vite-plugin-checker'; import electron from 'vite-plugin-electron/simple'; -import { viteStaticCopy } from 'vite-plugin-static-copy'; import { Constants } from './src/renderer/constants'; /** - * Helper to generate Twemoji static copy targets. - * Extracts all emojis from Constants.EMOJIS and maps them to their SVG filenames. + * Vite plugin that copies static assets to disk on startup. + * Runs in both dev and build modes so the Electron main process can resolve + * asset file URLs without requiring a prior `pnpm build`. */ -const getTwemojiCopyTargets = () => { +const copyStaticAssetsPlugin = (): Plugin => { const flatten = (obj: object): string[] => Object.values(obj).flatMap((v) => Array.isArray(v) ? v : flatten(v as object), @@ -27,15 +29,59 @@ const getTwemojiCopyTargets = () => { .split('/') .pop(); - const allEmojis = flatten(Constants.EMOJIS); - const emojiFilenames = allEmojis.map((emoji) => - extractSvgFilename(twemoji.parse(emoji, { folder: 'svg', ext: '.svg' })), - ); + let isBuild = false; - return emojiFilenames.map((filename) => ({ - src: `../../node_modules/@discordapp/twemoji/dist/svg/${filename}`, - dest: 'assets/images/twemoji', - })); + const copyAssets = () => { + // Copy the root assets/ directory (images, sounds, etc.) into build/ + fs.cpSync( + fileURLToPath(new URL('assets', import.meta.url)), + fileURLToPath(new URL('build/assets', import.meta.url)), + { recursive: true }, + ); + + // Copy individual Twemoji SVGs needed by the app into build/assets/images/twemoji/ + const twemojiSrcDir = fileURLToPath( + new URL('node_modules/@discordapp/twemoji/dist/svg', import.meta.url), + ); + const twemojiDestDir = fileURLToPath( + new URL('build/assets/images/twemoji', import.meta.url), + ); + + fs.mkdirSync(twemojiDestDir, { recursive: true }); + + const allEmojis = flatten(Constants.EMOJIS); + for (const emoji of allEmojis) { + const filename = extractSvgFilename( + twemoji.parse(emoji, { folder: 'svg', ext: '.svg' }), + ); + if (filename) { + fs.copyFileSync( + path.join(twemojiSrcDir, filename), + path.join(twemojiDestDir, filename), + ); + } + } + }; + + return { + name: 'copy-static-assets', + configResolved(config) { + isBuild = config.command === 'build'; + }, + // In serve/dev mode, copy before the build starts (emptyOutDir doesn't run). + // In build mode, copy after all output is written — buildStart runs before + // Vite's emptyOutDir wipe, so assets copied there would be deleted. + buildStart() { + if (!isBuild) { + copyAssets(); + } + }, + closeBundle() { + if (isBuild) { + copyAssets(); + } + }, + }; }; /** @@ -90,6 +136,9 @@ export default defineConfig(({ command }) => { main: { entry: fileURLToPath(new URL('src/main/index.ts', import.meta.url)), vite: { + // The outer Vite config sets root:'src/renderer', so we must + // explicitly tell the main-process sub-build where to find .env files. + envDir: fileURLToPath(new URL('.', import.meta.url)), build: { outDir: fileURLToPath(new URL('build', import.meta.url)), rollupOptions: { @@ -118,15 +167,7 @@ export default defineConfig(({ command }) => { }, }, }), - viteStaticCopy({ - targets: [ - ...getTwemojiCopyTargets(), - { - src: '../../assets', - dest: '.', - }, - ], - }), + copyStaticAssetsPlugin(), ], root: 'src/renderer', publicDir: false as const,