refactor: migrate from electron-rebuild to electron-forge

This commit is contained in:
plebeius
2026-01-30 12:28:41 +08:00
parent 7b4d7b6b9a
commit 815f618320
15 changed files with 1867 additions and 825 deletions
+105 -136
View File
@@ -25,8 +25,8 @@ jobs:
uses: actions/cache@v4 uses: actions/cache@v4
with: with:
path: ~/.cache/electron path: ~/.cache/electron
key: ${{ runner.os }}-electron-${{ hashFiles('**/yarn.lock') }} key: ${{ runner.os }}-electron-v2-${{ hashFiles('**/yarn.lock') }}
restore-keys: ${{ runner.os }}-electron- restore-keys: ${{ runner.os }}-electron-v2-
- name: Install dependencies - name: Install dependencies
run: | run: |
@@ -37,37 +37,45 @@ jobs:
[ "$i" = "3" ] && exit 1 [ "$i" = "3" ] && exit 1
done done
- name: Download IPFS
run: node electron/download-ipfs && chmod +x bin/linux/ipfs
- name: Build React App - name: Build React App
run: yarn build run: yarn build
env: env:
CI: '' CI: ''
- name: Build Electron App (Linux) - name: Build Preload Script
run: yarn electron:build:linux run: yarn build:preload
- name: Smoke Test - name: Verify build outputs
run: | run: |
echo "Testing AppImage startup..." echo "=== Checking build/ directory ==="
APPIMAGE=$(find dist -name "*.AppImage" | head -n 1) ls -la build/ || exit 1
echo "Found AppImage: $APPIMAGE" echo "=== Checking build/electron/preload.cjs ==="
chmod +x "$APPIMAGE" ls -la build/electron/preload.cjs || exit 1
# Use --appimage-extract-and-run to avoid FUSE requirement in CI echo "=== Checking build/index.html ==="
# Run with timeout and expect it to start (will be killed after timeout) ls -la build/index.html || exit 1
timeout 10s "$APPIMAGE" --appimage-extract-and-run --no-sandbox &
APP_PID=$! - name: Verify forge config
sleep 5 run: |
# Check if process is still running (means it started successfully) echo "=== forge.config.js exists ==="
if kill -0 $APP_PID 2>/dev/null; then ls -la forge.config.js
echo "✓ App started successfully" echo "=== Validating forge config can be loaded ==="
kill $APP_PID 2>/dev/null || true node -e "import('./forge.config.js').then(c => console.log('Config loaded, makers:', c.default.makers?.length || 0)).catch(e => { console.error(e); process.exit(1); })"
exit 0
else - name: Package Electron App
echo "✗ App failed to start" timeout-minutes: 30
exit 1 run: |
fi set -o pipefail
yarn electron-forge package 2>&1 | tee forge-output.log
echo "=== Checking out/ directory ==="
ls -la out/
- name: Verify Executable
run: |
ls -la out/
EXE=$(node scripts/find-forge-executable.js)
echo "Found executable: $EXE"
# Verify the file exists and is executable
test -f "$EXE" && test -x "$EXE" && echo "✓ Executable is valid"
test-mac-intel: test-mac-intel:
name: Test Mac (Intel) name: Test Mac (Intel)
@@ -84,16 +92,14 @@ jobs:
cache: 'yarn' cache: 'yarn'
- name: Install setuptools for native modules - name: Install setuptools for native modules
run: | run: pip3 install --break-system-packages setuptools || pip3 install --user setuptools || true
# Use pip with --break-system-packages for CI environment
pip3 install --break-system-packages setuptools || pip3 install --user setuptools || true
- name: Cache electron binaries - name: Cache electron binaries
uses: actions/cache@v4 uses: actions/cache@v4
with: with:
path: ~/Library/Caches/electron path: ~/Library/Caches/electron
key: ${{ runner.os }}-electron-${{ hashFiles('**/yarn.lock') }} key: ${{ runner.os }}-electron-v2-${{ hashFiles('**/yarn.lock') }}
restore-keys: ${{ runner.os }}-electron- restore-keys: ${{ runner.os }}-electron-v2-
- name: Install dependencies - name: Install dependencies
run: | run: |
@@ -104,45 +110,33 @@ jobs:
[ "$i" = "3" ] && exit 1 [ "$i" = "3" ] && exit 1
done done
- name: Download IPFS
run: node electron/download-ipfs && chmod +x bin/mac/ipfs
- name: Build React App - name: Build React App
run: yarn build run: yarn build
env: env:
CI: '' CI: ''
- name: Build Electron App - name: Build Preload Script
# Use --dir to build only the .app bundle without DMG run: yarn build:preload
# This avoids flaky hdiutil "Resource busy" errors in CI
run: yarn build && yarn build:preload && yarn electron-builder build --publish never -m --dir
- name: Smoke Test - name: Verify build outputs
run: | run: |
if [ -d "dist/mac/5chan.app" ]; then ls -la build/ || exit 1
echo "Testing dist/mac/5chan.app..." ls -la build/electron/preload.cjs || exit 1
# Run the app in background - it will start IPFS which takes time ls -la build/index.html || exit 1
# We just verify it launches without crashing
./dist/mac/5chan.app/Contents/MacOS/5chan & - name: Package Electron App
APP_PID=$! timeout-minutes: 30
sleep 10 run: |
# Check if process is still running (means it started successfully) set -o pipefail
if kill -0 $APP_PID 2>/dev/null; then yarn electron-forge package 2>&1 | tee forge-output.log
echo "✓ App started successfully" ls -la out/
kill $APP_PID 2>/dev/null || true
# Also kill any child processes (IPFS) - name: Verify Executable
pkill -P $APP_PID 2>/dev/null || true run: |
pkill -f ipfs 2>/dev/null || true ls -la out/
exit 0 EXE=$(node scripts/find-forge-executable.js)
else echo "Found executable: $EXE"
echo "✗ App failed to start or crashed" test -f "$EXE" && test -x "$EXE" && echo "✓ Executable is valid"
exit 1
fi
else
echo "Could not find dist/mac/5chan.app to test"
ls -R dist
exit 1
fi
test-mac-arm: test-mac-arm:
name: Test Mac (Apple Silicon) name: Test Mac (Apple Silicon)
@@ -159,16 +153,14 @@ jobs:
cache: 'yarn' cache: 'yarn'
- name: Install setuptools for native modules - name: Install setuptools for native modules
run: | run: pip3 install --break-system-packages setuptools || pip3 install --user setuptools || true
# Use pip with --break-system-packages for CI environment
pip3 install --break-system-packages setuptools || pip3 install --user setuptools || true
- name: Cache electron binaries - name: Cache electron binaries
uses: actions/cache@v4 uses: actions/cache@v4
with: with:
path: ~/Library/Caches/electron path: ~/Library/Caches/electron
key: ${{ runner.os }}-electron-${{ hashFiles('**/yarn.lock') }} key: ${{ runner.os }}-electron-v2-${{ hashFiles('**/yarn.lock') }}
restore-keys: ${{ runner.os }}-electron- restore-keys: ${{ runner.os }}-electron-v2-
- name: Install dependencies - name: Install dependencies
run: | run: |
@@ -179,50 +171,33 @@ jobs:
[ "$i" = "3" ] && exit 1 [ "$i" = "3" ] && exit 1
done done
- name: Download IPFS
run: node electron/download-ipfs && chmod +x bin/mac/ipfs
- name: Build React App - name: Build React App
run: yarn build run: yarn build
env: env:
CI: '' CI: ''
- name: Build Electron App - name: Build Preload Script
# On M1 runner, this should produce arm64 build run: yarn build:preload
# Use --dir to build only the .app bundle without DMG
# This avoids flaky hdiutil "Resource busy" errors in CI
run: yarn build && yarn build:preload && yarn electron-builder build --publish never -m --dir
- name: Smoke Test - name: Verify build outputs
run: | run: |
if [ -d "dist/mac-arm64/5chan.app" ]; then ls -la build/ || exit 1
APP_PATH="dist/mac-arm64/5chan.app" ls -la build/electron/preload.cjs || exit 1
elif [ -d "dist/mac/5chan.app" ]; then ls -la build/index.html || exit 1
APP_PATH="dist/mac/5chan.app"
else
echo "Could not find 5chan.app to test"
ls -R dist
exit 1
fi
echo "Testing $APP_PATH..." - name: Package Electron App
# Run the app in background - it will start IPFS which takes time timeout-minutes: 30
# We just verify it launches without crashing run: |
"./$APP_PATH/Contents/MacOS/5chan" & set -o pipefail
APP_PID=$! yarn electron-forge package 2>&1 | tee forge-output.log
sleep 10 ls -la out/
# Check if process is still running (means it started successfully)
if kill -0 $APP_PID 2>/dev/null; then - name: Verify Executable
echo "✓ App started successfully" run: |
kill $APP_PID 2>/dev/null || true ls -la out/
# Also kill any child processes (IPFS) EXE=$(node scripts/find-forge-executable.js)
pkill -P $APP_PID 2>/dev/null || true echo "Found executable: $EXE"
pkill -f ipfs 2>/dev/null || true test -f "$EXE" && test -x "$EXE" && echo "✓ Executable is valid"
exit 0
else
echo "✗ App failed to start or crashed"
exit 1
fi
test-windows: test-windows:
name: Test Windows name: Test Windows
@@ -242,8 +217,8 @@ jobs:
uses: actions/cache@v4 uses: actions/cache@v4
with: with:
path: ~/AppData/Local/electron/Cache path: ~/AppData/Local/electron/Cache
key: ${{ runner.os }}-electron-${{ hashFiles('**/yarn.lock') }} key: ${{ runner.os }}-electron-v2-${{ hashFiles('**/yarn.lock') }}
restore-keys: ${{ runner.os }}-electron- restore-keys: ${{ runner.os }}-electron-v2-
- name: Install dependencies - name: Install dependencies
shell: bash shell: bash
@@ -255,41 +230,35 @@ jobs:
[ "$i" = "3" ] && exit 1 [ "$i" = "3" ] && exit 1
done done
- name: Download IPFS
run: node electron/download-ipfs
- name: Build React App - name: Build React App
shell: bash
run: yarn build run: yarn build
env: env:
CI: '' CI: ''
- name: Build Electron App - name: Build Preload Script
run: yarn electron:build:windows shell: bash
timeout-minutes: 30 run: yarn build:preload
- name: Smoke Test - name: Verify build outputs
shell: bash shell: bash
run: | run: |
# Try to find the unpacked executable first as it's easiest to run ls -la build/ || exit 1
if [ -d "dist/win-unpacked" ]; then ls -la build/electron/preload.cjs || exit 1
echo "Testing unpacked exe..." ls -la build/index.html || exit 1
# Run with timeout - app starts IPFS so won't exit on its own
timeout 15s ./dist/win-unpacked/5chan.exe & - name: Package Electron App
APP_PID=$! shell: bash
sleep 8 timeout-minutes: 30
# Check if process started successfully run: |
if kill -0 $APP_PID 2>/dev/null; then set -o pipefail
echo "✓ App started successfully" yarn electron-forge package 2>&1 | tee forge-output.log
taskkill //F //PID $APP_PID 2>/dev/null || true ls -la out/
# Kill any IPFS processes
taskkill //F //IM ipfs.exe 2>/dev/null || true - name: Verify Executable
exit 0 shell: bash
else run: |
echo "✗ App failed to start" ls -la out/
exit 1 EXE=$(node scripts/find-forge-executable.js)
fi echo "Found executable: $EXE"
else test -f "$EXE" && test -x "$EXE" && echo "✓ Executable is valid"
echo "No unpacked directory found"
ls -R dist
exit 1
fi
+3
View File
@@ -100,6 +100,9 @@ dist
# Electron Forge output # Electron Forge output
squashfs-root squashfs-root
# Vite PWA dev output
dev-dist
# Gatsby files # Gatsby files
.cache/ .cache/
# Comment in the public line in if your project uses Gatsby and not Next.js # Comment in the public line in if your project uses Gatsby and not Next.js
-41
View File
@@ -1,41 +0,0 @@
const fs = require('fs-extra');
const path = require('path');
const { execSync } = require('child_process');
const packageJson = require('../package.json');
const rootPath = path.resolve(__dirname, '..');
const distFolderPath = path.resolve(rootPath, 'dist');
function addPortableToPortableExecutableFileName() {
const files = fs.readdirSync(distFolderPath);
for (const file of files) {
if (file.endsWith('.exe') && !file.match('Setup')) {
const filePath = path.resolve(distFolderPath, file);
const renamedFilePath = path.resolve(distFolderPath, file.replace('5chan', '5chan Portable'));
fs.moveSync(filePath, renamedFilePath);
}
}
}
function createHtmlArchive() {
if (process.platform !== 'linux') {
return;
}
const zipBinPath = path.resolve(rootPath, 'node_modules', '7zip-bin', 'linux', 'x64', '7za');
const fivechanHtmlFolderName = `5chan-html-${packageJson.version}`;
const outputFile = path.resolve(distFolderPath, `${fivechanHtmlFolderName}.zip`);
// Vite outputs to 'dist', not 'build' (CRA default)
const inputFolder = path.resolve(rootPath, 'dist');
try {
// Exclude Electron builder artifacts from HTML archive
const excludes = '-xr!*.AppImage -xr!*.exe -xr!*.dmg -xr!*.blockmap -xr!*.yml -xr!*.yaml -xr!win-unpacked -xr!mac -xr!mac-arm64 -xr!linux-unpacked -xr!builder-*';
execSync(`${zipBinPath} a ${outputFile} ${inputFolder} ${excludes}`);
execSync(`${zipBinPath} rn -r ${outputFile} dist ${fivechanHtmlFolderName}`);
} catch (e) {
console.error('electron build createHtmlArchive error:', e);
}
}
module.exports = async function afterAllArtifactBuild(buildResult) {
addPortableToPortableExecutableFileName();
createHtmlArchive();
};
+38 -45
View File
@@ -19,9 +19,26 @@ const ipfsClientLinuxPath = path.join(ipfsClientsPath, 'linux');
// official kubo download links https://docs.ipfs.tech/install/command-line/#install-official-binary-distributions // official kubo download links https://docs.ipfs.tech/install/command-line/#install-official-binary-distributions
const ipfsClientVersion = '0.32.1'; const ipfsClientVersion = '0.32.1';
const ipfsClientWindowsUrl = `https://dist.ipfs.io/kubo/v${ipfsClientVersion}/kubo_v${ipfsClientVersion}_windows-amd64.zip`;
const ipfsClientMacUrl = `https://dist.ipfs.io/kubo/v${ipfsClientVersion}/kubo_v${ipfsClientVersion}_darwin-amd64.tar.gz`; // Resolve desired build arch: allow overriding via env (so cross-arch builds pick correct binary)
const ipfsClientLinuxUrl = `https://dist.ipfs.io/kubo/v${ipfsClientVersion}/kubo_v${ipfsClientVersion}_linux-amd64.tar.gz`; const resolveBuildArch = () => {
const envArch = process.env.BUILD_ARCH;
if (envArch === 'arm64' || envArch === 'x64') return envArch;
// fallback to host arch
if (process.arch === 'arm64') return 'arm64';
return 'x64';
};
const toKuboArch = (arch) => (arch === 'arm64' ? 'arm64' : 'amd64');
const getKuboUrl = (platform) => {
const arch = toKuboArch(resolveBuildArch());
if (platform === 'win32') return `https://dist.ipfs.io/kubo/v${ipfsClientVersion}/kubo_v${ipfsClientVersion}_windows-${arch}.zip`;
if (platform === 'darwin') return `https://dist.ipfs.io/kubo/v${ipfsClientVersion}/kubo_v${ipfsClientVersion}_darwin-${arch}.tar.gz`;
if (platform === 'linux') return `https://dist.ipfs.io/kubo/v${ipfsClientVersion}/kubo_v${ipfsClientVersion}_linux-${arch}.tar.gz`;
// default to linux
return `https://dist.ipfs.io/kubo/v${ipfsClientVersion}/kubo_v${ipfsClientVersion}_linux-${arch}.tar.gz`;
};
const downloadWithProgress = (url) => const downloadWithProgress = (url) =>
new Promise((resolve, reject) => { new Promise((resolve, reject) => {
@@ -79,25 +96,6 @@ const downloadWithRetry = async (url, retries = 3) => {
} }
}; };
// plebbit kubo downloads dont need to be extracted
const download = async (url, destinationPath) => {
let binName = 'ipfs';
if (destinationPath.endsWith('win')) {
binName += '.exe';
}
const binPath = path.join(destinationPath, binName);
// already downloaded, don't download again
if (fs.pathExistsSync(binPath)) {
return;
}
const split = url.split('/');
const fileName = split[split.length - 1];
const downloadPath = path.join(destinationPath, fileName);
const file = await downloadWithRetry(url);
fs.ensureDirSync(destinationPath);
await fs.writeFile(binPath, file);
};
// official kubo downloads need to be extracted // official kubo downloads need to be extracted
const downloadAndExtract = async (url, destinationPath) => { const downloadAndExtract = async (url, destinationPath) => {
let binName = 'ipfs'; let binName = 'ipfs';
@@ -111,14 +109,14 @@ const downloadAndExtract = async (url, destinationPath) => {
console.log(`Downloading IPFS client from ${url} to ${destinationPath}`); console.log(`Downloading IPFS client from ${url} to ${destinationPath}`);
const split = url.split('/'); const split = url.split('/');
const fileName = split[split.length - 1]; const fileName = split[split.length - 1];
const downloadPath = path.join(destinationPath, fileName); const archivePath = path.join(destinationPath, fileName);
const file = await downloadWithRetry(url); const file = await downloadWithRetry(url);
fs.ensureDirSync(destinationPath); fs.ensureDirSync(destinationPath);
await fs.writeFile(downloadPath, file); await fs.writeFile(archivePath, file);
console.log(`Downloaded archive to ${downloadPath}`); console.log(`Downloaded archive to ${archivePath}`);
console.log(`Extracting ${downloadPath} to ${destinationPath}`); console.log(`Extracting ${archivePath} to ${destinationPath}`);
try { try {
await decompress(downloadPath, destinationPath); await decompress(archivePath, destinationPath);
console.log('Decompression complete'); console.log('Decompression complete');
} catch (err) { } catch (err) {
console.error('Error during decompression:', err); console.error('Error during decompression:', err);
@@ -130,31 +128,26 @@ const downloadAndExtract = async (url, destinationPath) => {
fs.moveSync(extractedBinPath, binPath); fs.moveSync(extractedBinPath, binPath);
console.log('Binary moved'); console.log('Binary moved');
console.log('Cleaning up temporary files'); console.log('Cleaning up temporary files');
fs.removeSync(downloadPath); fs.removeSync(archivePath);
console.log('Cleanup complete'); console.log('Cleanup complete');
}; };
export const downloadIpfsClients = async () => { export const downloadIpfsClients = async () => {
const platform = process.platform; const platform = process.platform;
console.log(`Starting IPFS client download for platform: ${platform}`); console.log(`Starting IPFS client download for platform: ${platform}, targetArch: ${resolveBuildArch()}`);
switch (platform) { const url = getKuboUrl(platform);
case 'win32': if (platform === 'win32') {
await downloadAndExtract(ipfsClientWindowsUrl, ipfsClientWindowsPath); await downloadAndExtract(url, ipfsClientWindowsPath);
break; } else if (platform === 'darwin') {
case 'darwin': await downloadAndExtract(url, ipfsClientMacPath);
await downloadAndExtract(ipfsClientMacUrl, ipfsClientMacPath); } else if (platform === 'linux') {
break; await downloadAndExtract(url, ipfsClientLinuxPath);
case 'linux': } else {
await downloadAndExtract(ipfsClientLinuxUrl, ipfsClientLinuxPath); console.warn(`Unknown platform: ${platform}, defaulting to linux path`);
break; await downloadAndExtract(url, ipfsClientLinuxPath);
default:
console.warn(`Unknown platform: ${platform}, downloading all IPFS clients`);
await downloadAndExtract(ipfsClientWindowsUrl, ipfsClientWindowsPath);
await downloadAndExtract(ipfsClientMacUrl, ipfsClientMacPath);
await downloadAndExtract(ipfsClientLinuxUrl, ipfsClientLinuxPath);
} }
}; };
export default async (context) => { export default async (_context) => {
await downloadIpfsClients(); await downloadIpfsClients();
}; };
+15 -18
View File
@@ -11,7 +11,7 @@ fi
node electron/download-ipfs || { echo "Error: failed script 'node electron/download-ipfs'" ; exit 1; } node electron/download-ipfs || { echo "Error: failed script 'node electron/download-ipfs'" ; exit 1; }
dockerfile=' dockerfile='
FROM electronuserland/builder:16 FROM node:22
# install node_modules # install node_modules
WORKDIR /usr/src/5chan WORKDIR /usr/src/5chan
@@ -19,44 +19,41 @@ COPY ./package.json .
COPY ./yarn.lock . COPY ./yarn.lock .
RUN yarn RUN yarn
# build native dependencies like sqlite3 # copy source files and configs
RUN electron-builder install-app-deps
# copy source files
COPY ./bin ./bin COPY ./bin ./bin
COPY ./electron ./electron COPY ./electron ./electron
COPY ./src ./src COPY ./src ./src
COPY ./public ./public COPY ./public ./public
COPY ./forge.config.js ./forge.config.js
# required or yarn build fails COPY ./vite.config.js ./vite.config.js
COPY ./.eslintrc.json ./.eslintrc.json COPY ./tsconfig.json ./tsconfig.json
COPY ./.prettierrc ./.prettierrc COPY ./index.html ./index.html
# react build # react build
RUN yarn build RUN yarn build
' '
# build electron-builder docker image # build electron-forge docker image
# temporary .dockerignore to save build time # temporary .dockerignore to save build time
echo $'node_modules\ndist' > .dockerignore echo $'node_modules\nbuild\nout' > .dockerignore
echo "$dockerfile" | sudo docker build \ echo "$dockerfile" | sudo docker build \
. \ . \
--tag 5chan-electron-builder \ --tag 5chan-electron-forge \
--file - --file -
rm .dockerignore rm .dockerignore
# build linux binary # build linux binary
sudo docker run \ sudo docker run \
--name 5chan-electron-builder \ --name 5chan-electron-forge \
--volume "$root_path"/dist:/usr/src/5chan/dist \ --volume "$root_path"/out:/usr/src/5chan/out \
--rm \ --rm \
5chan-electron-builder \ 5chan-electron-forge \
yarn electron:build:linux yarn electron:build:linux
# build windows binary # build windows binary
sudo docker run \ sudo docker run \
--name 5chan-electron-builder \ --name 5chan-electron-forge \
--volume "$root_path"/dist:/usr/src/5chan/dist \ --volume "$root_path"/out:/usr/src/5chan/out \
--rm \ --rm \
5chan-electron-builder \ 5chan-electron-forge \
yarn electron:build:windows yarn electron:build:windows
+3 -3
View File
@@ -94,7 +94,7 @@ const createMainWindow = () => {
nodeIntegration: false, nodeIntegration: false,
contextIsolation: true, contextIsolation: true,
devTools: true, // TODO: change to isDev when no bugs left devTools: true, // TODO: change to isDev when no bugs left
preload: path.join(dirname, '../dist/electron/preload.cjs'), preload: path.join(dirname, '../build/electron/preload.cjs'),
}, },
}); });
@@ -140,7 +140,7 @@ const createMainWindow = () => {
callback({ responseHeaders: details.responseHeaders }); callback({ responseHeaders: details.responseHeaders });
}); });
const startURL = isDev ? 'http://localhost:3000' : `file://${path.join(dirname, '../dist/index.html')}`; const startURL = isDev ? 'http://localhost:3000' : `file://${path.join(dirname, '../build/index.html')}`;
mainWindow.loadURL(startURL); mainWindow.loadURL(startURL);
@@ -244,7 +244,7 @@ const createMainWindow = () => {
if (process.platform !== 'darwin') { if (process.platform !== 'darwin') {
// tray // tray
const trayIconPath = path.join(dirname, '..', isDev ? 'public' : 'dist', 'electron-tray-icon.png'); const trayIconPath = path.join(dirname, '..', isDev ? 'public' : 'build', 'electron-tray-icon.png');
const tray = new Tray(trayIconPath); const tray = new Tray(trayIconPath);
tray.setToolTip('5chan'); tray.setToolTip('5chan');
const trayMenu = Menu.buildFromTemplate([ const trayMenu = Menu.buildFromTemplate([
+85 -36
View File
@@ -6,43 +6,92 @@ import ps from 'node:process';
import proxyServer from './proxy-server.js'; import proxyServer from './proxy-server.js';
import tcpPortUsed from 'tcp-port-used'; import tcpPortUsed from 'tcp-port-used';
import EnvPaths from 'env-paths'; import EnvPaths from 'env-paths';
import { fileURLToPath } from 'url'; import { fileURLToPath, pathToFileURL } from 'url';
const dirname = path.join(path.dirname(fileURLToPath(import.meta.url))); const dirname = path.join(path.dirname(fileURLToPath(import.meta.url)));
const envPaths = EnvPaths('plebbit', { suffix: false }); const envPaths = EnvPaths('plebbit', { suffix: false });
// Get platform-specific binary name
const getIpfsBinaryName = () => (process.platform === 'win32' ? 'ipfs.exe' : 'ipfs');
// Get platform subdirectory name for bin/ folder
const getPlatformDir = () => {
if (process.platform === 'win32') return 'win';
if (process.platform === 'darwin') return 'mac';
return 'linux';
};
// Resolve kubo binary path
const getKuboPath = async () => {
if (isDev) {
// In dev, use kubo from node_modules
const { path: getKuboBinaryPath } = await import('kubo');
return getKuboBinaryPath();
} else {
// In production, the binary is downloaded to bin/<platform>/ipfs by generateAssets hook
// With asar: false, files are at resources/app/ instead of resources/app.asar.unpacked
const appPath = process.resourcesPath;
const binaryName = getIpfsBinaryName();
const platformDir = getPlatformDir();
// Try the bin/ directory first (where generateAssets downloads binaries)
const binDirPath = path.join(appPath, 'app', 'bin', platformDir, binaryName);
if (fs.existsSync(binDirPath)) {
return binDirPath;
}
// Fallback: try app.asar.unpacked for ASAR builds (if we ever re-enable ASAR)
const unpackedPath = path.join(appPath, 'app.asar.unpacked');
const kuboModulePath = path.join(unpackedPath, 'node_modules', 'kubo');
// Try to import kubo from unpacked location
try {
const kuboUrl = pathToFileURL(path.resolve(kuboModulePath)).href;
const kuboModule = await import(kuboUrl);
const { path: getKuboBinaryPath } = kuboModule;
return getKuboBinaryPath();
} catch (err) {
// Fallback: try to find the binary directly in kubo module
const kuboBinPath = path.join(kuboModulePath, 'kubo', binaryName);
if (fs.existsSync(kuboBinPath)) {
return kuboBinPath;
}
// Last resort: check in resources/app/node_modules/kubo for non-ASAR builds
const appModulePath = path.join(appPath, 'app', 'node_modules', 'kubo');
const appKuboBinPath = path.join(appModulePath, 'kubo', binaryName);
if (fs.existsSync(appKuboBinPath)) {
return appKuboBinPath;
}
throw new Error(`Could not find kubo binary. Checked: ${binDirPath}, ${kuboBinPath}, ${appKuboBinPath}`);
}
}
};
// use this custom function instead of spawnSync for better logging // use this custom function instead of spawnSync for better logging
// also spawnSync might have been causing crash on start on windows // also spawnSync might have been causing crash on start on windows
const spawnAsync = (...args) => const spawnAsync = (...args) =>
new Promise((resolve, reject) => { new Promise((resolve, reject) => {
const spawedProcess = spawn(...args); const spawnedProcess = spawn(...args);
spawedProcess.on('exit', (exitCode, signal) => { spawnedProcess.on('exit', (exitCode, signal) => {
if (exitCode === 0) resolve(); if (exitCode === 0) resolve();
else reject(Error(`spawnAsync process '${spawedProcess.pid}' exited with code '${exitCode}' signal '${signal}'`)); else reject(Error(`spawnAsync process '${spawnedProcess.pid}' exited with code '${exitCode}' signal '${signal}'`));
}); });
spawedProcess.stderr.on('data', (data) => console.error(data.toString())); // Always surface errors from short-lived commands
spawedProcess.stdin.on('data', (data) => console.log(data.toString())); spawnedProcess.stderr.on('data', (data) => console.error(data.toString()));
spawedProcess.stdout.on('data', (data) => console.log(data.toString())); // Short-lived command stdout can be useful in dev, but is noisy in prod
spawedProcess.on('error', (data) => console.error(data.toString())); if (isDev) {
spawnedProcess.stdout.on('data', (data) => console.log(data.toString()));
} else {
// Drain to avoid backpressure without logging
spawnedProcess.stdout.on('data', () => {});
}
spawnedProcess.on('error', (data) => console.error(data.toString?.() || String(data)));
}); });
const startIpfs = async () => { const startIpfs = async () => {
const ipfsFileName = process.platform == 'win32' ? 'ipfs.exe' : 'ipfs'; const ipfsPath = await getKuboPath();
let ipfsPath = path.join(process.resourcesPath, 'bin', ipfsFileName); const ipfsDataPath = isDev ? path.join(dirname, '..', '.plebbit', 'ipfs') : path.join(envPaths.data, 'ipfs');
let ipfsDataPath = path.join(envPaths.data, 'ipfs');
// test launching the ipfs binary in dev mode
// they must be downloaded first using `yarn electron:build`
if (isDev) {
let binFolderName = 'win';
if (process.platform === 'linux') {
binFolderName = 'linux';
}
if (process.platform === 'darwin') {
binFolderName = 'mac';
}
ipfsPath = path.join(dirname, '..', 'bin', binFolderName, ipfsFileName);
ipfsDataPath = path.join(dirname, '..', '.plebbit', 'ipfs');
}
if (!fs.existsSync(ipfsPath)) { if (!fs.existsSync(ipfsPath)) {
throw Error(`ipfs binary '${ipfsPath}' doesn't exist`); throw Error(`ipfs binary '${ipfsPath}' doesn't exist`);
@@ -51,16 +100,17 @@ const startIpfs = async () => {
console.log({ ipfsPath, ipfsDataPath }); console.log({ ipfsPath, ipfsDataPath });
fs.ensureDirSync(ipfsDataPath); fs.ensureDirSync(ipfsDataPath);
const env = { IPFS_PATH: ipfsDataPath }; // Reduce IPFS daemon log verbosity in production to avoid UI lag from excessive logging
const env = { ...process.env, IPFS_PATH: ipfsDataPath, ...(isDev ? {} : { GOLOG_LOG_LEVEL: 'error' }) };
// init ipfs client on first launch // init ipfs client on first launch
try { try {
await spawnAsync(ipfsPath, ['init'], { env, hideWindows: true }); await spawnAsync(ipfsPath, ['init'], { env, hideWindows: true });
} catch (e) {} } catch {}
// make sure repo is migrated // make sure repo is migrated
try { try {
await spawnAsync(ipfsPath, ['repo', 'migrate'], { env, hideWindows: true }); await spawnAsync(ipfsPath, ['repo', 'migrate'], { env, hideWindows: true });
} catch (e) {} } catch {}
// dont use 8080 port because it's too common // dont use 8080 port because it's too common
await spawnAsync(ipfsPath, ['config', '--json', 'Addresses.Gateway', '"/ip4/127.0.0.1/tcp/6473"'], { await spawnAsync(ipfsPath, ['config', '--json', 'Addresses.Gateway', '"/ip4/127.0.0.1/tcp/6473"'], {
@@ -83,17 +133,16 @@ const startIpfs = async () => {
let lastError; let lastError;
ipfsProcess.stderr.on('data', (data) => { ipfsProcess.stderr.on('data', (data) => {
lastError = data.toString(); lastError = data.toString();
console.error(data.toString()); if (isDev) console.error(lastError);
}); });
ipfsProcess.stdin.on('data', (data) => console.log(data.toString())); ipfsProcess.stdout.on('data', (chunk) => {
ipfsProcess.stdout.on('data', (data) => { const text = chunk.toString();
data = data.toString(); if (isDev) console.log(text);
console.log(data); if (text.includes('Daemon is ready')) {
if (data.includes('Daemon is ready')) {
resolve(); resolve();
} }
}); });
ipfsProcess.on('error', (data) => console.error(data.toString())); ipfsProcess.on('error', (err) => console.error(err?.toString?.() || String(err)));
ipfsProcess.on('exit', () => { ipfsProcess.on('exit', () => {
console.error(`ipfs process with pid ${ipfsProcess.pid} exited`); console.error(`ipfs process with pid ${ipfsProcess.pid} exited`);
reject(Error(lastError)); reject(Error(lastError));
@@ -135,7 +184,7 @@ const startIpfsAutoRestart = async () => {
try { try {
// try to run exported onError callback, can be undefined // try to run exported onError callback, can be undefined
DefaultExport.onError(e)?.catch?.(console.log); DefaultExport.onError(e)?.catch?.(console.log);
} catch (e) {} } catch {}
} }
pendingStart = false; pendingStart = false;
}; };
+1 -1
View File
@@ -16,7 +16,7 @@ export default defineConfig({
fileName: () => 'preload.cjs', fileName: () => 'preload.cjs',
formats: ['cjs'], formats: ['cjs'],
}, },
outDir: resolve(__dirname, '../dist/electron'), outDir: resolve(__dirname, '../build/electron'),
emptyOutDir: true, emptyOutDir: true,
rollupOptions: { rollupOptions: {
external: ['electron'], external: ['electron'],
+98
View File
@@ -0,0 +1,98 @@
import { downloadIpfsClients } from './electron/before-pack.js';
const config = {
packagerConfig: {
name: '5chan',
executableName: '5chan',
appBundleId: '5chan.desktop',
// NOTE: asar is disabled because of a bug where electron-packager silently fails
// during asar creation with 5chan's large node_modules. The app works fine without it.
// TODO: investigate and fix the asar creation issue
asar: false,
// Exclude unnecessary files from the package
ignore: [
/^\/src$/,
/^\/public$/,
/^\/android$/,
/^\/\.github$/,
/^\/scripts$/,
/^\/\.git/,
/^\/\.plebbit$/,
/^\/out$/,
/^\/dist$/,
/^\/squashfs-root$/,
/\.map$/,
/\.md$/,
/\.ts$/,
/tsconfig\.json$/,
/\.oxfmtrc/,
/oxlintrc/,
/vite\.config/,
/forge\.config/,
/capacitor\.config/,
/\.env$/,
/\.DS_Store$/,
/yarn\.lock$/,
// Exclude build-time scripts from the package
/electron\/before-pack\.js/,
// kubo npm package creates symlinks that break build - exclude its bin dir
// (we download our own kubo binary in generateAssets hook)
/node_modules\/kubo\/bin/,
// Exclude .bin directories anywhere in node_modules (contain escaping symlinks)
/node_modules\/.*\/\.bin/,
/node_modules\/\.bin/,
/node_modules\/\.cache/,
],
},
rebuildConfig: {
force: true,
},
hooks: {
// Download IPFS/Kubo binaries before packaging
generateAssets: async () => {
console.log('Downloading IPFS clients...');
await downloadIpfsClients();
console.log('IPFS clients downloaded.');
},
},
makers: [
// macOS
{
name: '@electron-forge/maker-dmg',
platforms: ['darwin'],
config: {
name: '5chan',
format: 'UDZO',
},
},
{
name: '@electron-forge/maker-zip',
platforms: ['darwin'],
},
// Windows
{
name: '@electron-forge/maker-squirrel',
platforms: ['win32'],
config: {
name: '5chan',
},
},
// Linux
{
name: '@reforged/maker-appimage',
platforms: ['linux'],
config: {
options: {
categories: ['Network'],
},
},
},
],
};
export default config;
+14 -52
View File
@@ -43,7 +43,8 @@
"remark-supersub": "1.0.0", "remark-supersub": "1.0.0",
"tcp-port-used": "1.0.2", "tcp-port-used": "1.0.2",
"typescript": "5.1.6", "typescript": "5.1.6",
"zustand": "4.4.3" "zustand": "4.4.3",
"kubo": "0.39.0"
}, },
"scripts": { "scripts": {
"postinstall": "node scripts/fix-bonfida-imports.js", "postinstall": "node scripts/fix-bonfida-imports.js",
@@ -61,15 +62,12 @@
"electron:no-delete-data": "yarn electron:before:download-ipfs && electron .", "electron:no-delete-data": "yarn electron:before:download-ipfs && electron .",
"electron:start": "concurrently \"cross-env BROWSER=none yarn start\" \"wait-on http://localhost:3000 && yarn electron\"", "electron:start": "concurrently \"cross-env BROWSER=none yarn start\" \"wait-on http://localhost:3000 && yarn electron\"",
"electron:start:no-delete-data": "concurrently \"cross-env BROWSER=none yarn start\" \"wait-on http://localhost:3000 && yarn electron:no-delete-data\"", "electron:start:no-delete-data": "concurrently \"cross-env BROWSER=none yarn start\" \"wait-on http://localhost:3000 && yarn electron:no-delete-data\"",
"electron:build:linux": "yarn build && yarn build:preload && electron-builder build --publish never -l", "electron:package": "yarn build && yarn build:preload && electron-forge package",
"electron:build:linux:x64": "yarn build:preload && electron-builder build --publish never -l --x64", "electron:build": "yarn build && yarn build:preload && electron-forge make",
"electron:build:linux:arm64": "yarn build:preload && electron-builder build --publish never -l --arm64", "electron:build:linux": "yarn build && yarn build:preload && electron-forge make --platform=linux",
"electron:build:windows": "yarn build && yarn build:preload && electron-builder build --publish never -w", "electron:build:mac": "yarn build && yarn build:preload && electron-forge make --platform=darwin",
"electron:build:mac": "yarn build && yarn build:preload && electron-builder build --publish never -m", "electron:build:windows": "yarn build && yarn build:preload && electron-forge make --platform=win32",
"electron:build:mac:x64": "yarn build:preload && electron-builder build --publish never -m --x64", "electron:before": "yarn electron-rebuild && yarn electron:before:delete-data",
"electron:build:mac:arm64": "yarn build:preload && electron-builder build --publish never -m --arm64",
"electron:before": "yarn electron-rebuild && yarn electron:before:download-ipfs && yarn electron:before:delete-data",
"electron:before:download-ipfs": "node electron/download-ipfs",
"electron:before:delete-data": "rimraf .plebbit", "electron:before:delete-data": "rimraf .plebbit",
"android:build:icons": "cordova-res android --skip-config --copy --resources /tmp/plebbit-react-android-icons --icon-source ./android/icons/icon.png --splash-source ./android/icons/splash.png --icon-foreground-source ./android/icons/icon-foreground.png --icon-background-source '#ffffee'", "android:build:icons": "cordova-res android --skip-config --copy --resources /tmp/plebbit-react-android-icons --icon-source ./android/icons/icon.png --splash-source ./android/icons/splash.png --icon-foreground-source ./android/icons/icon-foreground.png --icon-background-source '#ffffee'",
"lint": "oxlint src/**/*.{js,ts,tsx}", "lint": "oxlint src/**/*.{js,ts,tsx}",
@@ -114,7 +112,12 @@
"cz-conventional-changelog": "3.3.0", "cz-conventional-changelog": "3.3.0",
"decompress": "4.2.1", "decompress": "4.2.1",
"electron": "36.9.5", "electron": "36.9.5",
"electron-builder": "24.13.2", "@electron-forge/cli": "7.6.0",
"@electron-forge/maker-dmg": "7.6.0",
"@electron-forge/maker-squirrel": "7.6.0",
"@electron-forge/maker-zip": "7.6.0",
"@electron-forge/plugin-auto-unpack-natives": "7.6.0",
"@reforged/maker-appimage": "5.1.1",
"husky": "4.3.8", "husky": "4.3.8",
"isomorphic-fetch": "3.0.0", "isomorphic-fetch": "3.0.0",
"jsdom": "27.3.0", "jsdom": "27.3.0",
@@ -157,47 +160,6 @@
"tar": "7.5.4" "tar": "7.5.4"
}, },
"main": "electron/main.js", "main": "electron/main.js",
"build": {
"appId": "5chan.desktop",
"productName": "5chan",
"icon": "public/icon.png",
"asarUnpack": [
"**/*.node"
],
"beforePack": "electron/before-pack.js",
"afterAllArtifactBuild": "electron/after-all-artifact-build.cjs",
"extraResources": [
{
"from": "bin/${os}",
"to": "bin",
"filter": [
"**/*"
]
}
],
"files": [
"dist/**/*",
"electron/**/*",
"package.json",
"node_modules/**/*"
],
"extends": null,
"mac": {
"target": "dmg",
"category": "public.app-category.social-networking",
"type": "distribution"
},
"win": {
"target": [
"portable",
"nsis"
]
},
"linux": {
"target": "AppImage",
"category": "Network"
}
},
"lint-staged": { "lint-staged": {
"src/**/*.{js,ts,tsx}": [ "src/**/*.{js,ts,tsx}": [
"oxfmt --write" "oxfmt --write"
+124
View File
@@ -0,0 +1,124 @@
#!/usr/bin/env node
/**
* Find the packaged Electron executable built by Electron Forge.
* This script locates the executable in the out/ directory structure.
*/
import { readdirSync, statSync, existsSync, readFileSync } from 'fs';
import { isAbsolute, join, resolve } from 'path';
import { fileURLToPath } from 'url';
const __filename = fileURLToPath(import.meta.url);
const __dirname = resolve(__filename, '..');
const platform = process.platform;
const repoRoot = resolve(__dirname, '..');
const packageJson = JSON.parse(readFileSync(join(repoRoot, 'package.json'), 'utf-8'));
const appName = (packageJson.build?.productName || packageJson.name || '5chan').toLowerCase();
const resolveOutDir = (dir) => (isAbsolute(dir) ? dir : join(repoRoot, dir));
const envOutDir = process.env.ELECTRON_FORGE_OUT_DIR;
const candidateRoots = [
envOutDir ? resolveOutDir(envOutDir) : null,
join(repoRoot, 'out'),
join(repoRoot, 'out', 'make'),
join(repoRoot, '..', 'out'),
join(repoRoot, '..', '..', 'out'),
join(repoRoot, 'electron', 'out'),
].filter(Boolean);
// Skip directories that contain helper binaries/app code, not the main executable
// 'resources' contains the app bundle with IPFS binaries in bin/ - don't recurse there
const skipDirs = new Set(['node_modules', '.git', 'bin', 'app', 'resources']);
function findExecutable(dir, platform) {
const entries = readdirSync(dir, { withFileTypes: true });
for (const entry of entries) {
const fullPath = join(dir, entry.name);
if (entry.isDirectory()) {
if (skipDirs.has(entry.name)) continue;
if (platform === 'darwin' && entry.name.endsWith('.app')) {
const appPath = fullPath;
const exePath = join(appPath, 'Contents', 'MacOS', entry.name.replace('.app', ''));
if (existsSync(exePath)) {
return exePath;
}
}
const result = findExecutable(fullPath, platform);
if (result) return result;
} else if (entry.isFile()) {
// Check if it's an executable
if (platform === 'win32') {
const lowerName = entry.name.toLowerCase();
if (lowerName.endsWith('.exe') && !lowerName.includes('electron') && !lowerName.includes('crashpad')) {
if (lowerName.includes(appName)) {
return fullPath;
}
return fullPath;
}
} else if (platform === 'darwin') {
const stat = statSync(fullPath);
if (stat.isFile() && stat.mode & parseInt('111', 8)) {
const lowerName = entry.name.toLowerCase();
if (!lowerName.includes('helper') && !lowerName.includes('crashpad')) {
if (lowerName.includes(appName)) {
return fullPath;
}
return fullPath;
}
}
} else if (platform === 'linux') {
// Linux executables (AppImage or unpacked)
if (entry.name.endsWith('.AppImage')) {
return fullPath;
}
// Check for executable files (not .so libraries)
const stat = statSync(fullPath);
if (stat.isFile() && stat.mode & parseInt('111', 8) && !entry.name.includes('.so')) {
// Skip helper binaries
const lowerName = entry.name.toLowerCase();
if (!lowerName.includes('chrome') && !lowerName.includes('crashpad')) {
if (lowerName.includes(appName)) {
return fullPath;
}
return fullPath;
}
}
}
}
}
return null;
}
let executable = null;
const checkedDirs = [];
for (const root of candidateRoots) {
if (!existsSync(root)) {
checkedDirs.push(`${root} (missing)`);
continue;
}
const result = findExecutable(root, platform);
checkedDirs.push(root);
if (result) {
executable = result;
break;
}
}
if (!executable) {
console.error('Error: Could not find packaged executable.');
console.error('Platform:', platform);
console.error('Checked directories:', checkedDirs.join(', '));
process.exit(1);
}
console.log(executable);
+34
View File
@@ -0,0 +1,34 @@
// Auto-generated file - do not edit manually
// Run 'node scripts/generate-asset-manifest.js' to regenerate
// This file is generated from public/assets/ directory
export const BANNERS = ['assets/banners/banner-1.jpg', 'assets/banners/banner-2.jpg', 'assets/banners/banner-3.gif', 'assets/banners/banner-4.png'] as const;
export const NOT_FOUND_IMAGES = ['assets/not-found/not-found-1.png'] as const;
// Theme button images (cross, plus, minus, help, button-fade variants)
export const THEME_BUTTON_IMAGES = [
'assets/buttons/button-fade-blue.png',
'assets/buttons/button-fade-dark.png',
'assets/buttons/button-fade.png',
'assets/buttons/cross-blue.png',
'assets/buttons/cross-dark.png',
'assets/buttons/cross-photon.png',
'assets/buttons/cross-red.png',
'assets/buttons/help-blue.png',
'assets/buttons/help-dark.png',
'assets/buttons/help-photon.png',
'assets/buttons/help-red.png',
'assets/buttons/icon-close-red.png',
'assets/buttons/minus-blue.png',
'assets/buttons/minus-dark.png',
'assets/buttons/minus-photon.png',
'assets/buttons/minus-red.png',
'assets/buttons/plus-blue.png',
'assets/buttons/plus-dark.png',
'assets/buttons/plus-photon.png',
'assets/buttons/plus-red.png',
] as const;
// Theme background pattern images
export const THEME_BACKGROUND_IMAGES = ['assets/background-fade.png', 'assets/background-fade-blue.png'] as const;
+4 -3
View File
@@ -1,7 +1,8 @@
{ {
"rewrites": [ "rewrites": [
{ "source": "/(.*)", "destination": "/index.html" } { "source": "/(.*)", "destination": "/index.html" }
] ],
"buildCommand": "yarn build",
"outputDirectory": "build",
"framework": "vite"
} }
+57 -62
View File
@@ -13,17 +13,21 @@ export default defineConfig({
react({ react({
babel: { babel: {
plugins: [ plugins: [
['babel-plugin-react-compiler', { [
verbose: true 'babel-plugin-react-compiler',
}] {
] verbose: true,
} },
],
],
},
}), }),
// Only include React Scan in development mode - never in production builds // Only include React Scan in development mode - never in production builds
(isDevelopment || (!isProduction && process.env.NODE_ENV !== 'production')) && reactScan({ (isDevelopment || (!isProduction && process.env.NODE_ENV !== 'production')) &&
showToolbar: true, reactScan({
playSound: true, showToolbar: true,
}), playSound: true,
}),
nodePolyfills({ nodePolyfills({
globals: { globals: {
Buffer: true, Buffer: true,
@@ -57,20 +61,20 @@ export default defineConfig({
{ {
src: '/android-chrome-192x192.png', src: '/android-chrome-192x192.png',
sizes: '192x192', sizes: '192x192',
type: 'image/png' type: 'image/png',
},
{
src: '/android-chrome-512x512.png',
sizes: '512x512',
type: 'image/png'
}, },
{ {
src: '/android-chrome-512x512.png', src: '/android-chrome-512x512.png',
sizes: '512x512', sizes: '512x512',
type: 'image/png', type: 'image/png',
purpose: 'any maskable' },
} {
] src: '/android-chrome-512x512.png',
sizes: '512x512',
type: 'image/png',
purpose: 'any maskable',
},
],
}, },
workbox: { workbox: {
clientsClaim: true, clientsClaim: true,
@@ -85,8 +89,8 @@ export default defineConfig({
urlPattern: ({ url }) => url.pathname === '/' || url.pathname === '/index.html', urlPattern: ({ url }) => url.pathname === '/' || url.pathname === '/index.html',
handler: 'StaleWhileRevalidate', handler: 'StaleWhileRevalidate',
options: { options: {
cacheName: 'html-cache' cacheName: 'html-cache',
} },
}, },
// PNG caching // PNG caching
{ {
@@ -95,9 +99,9 @@ export default defineConfig({
options: { options: {
cacheName: 'images', cacheName: 'images',
expiration: { expiration: {
maxEntries: 50 maxEntries: 50,
} },
} },
}, },
// Add additional asset caching // Add additional asset caching
{ {
@@ -107,9 +111,9 @@ export default defineConfig({
cacheName: 'assets-cache', cacheName: 'assets-cache',
expiration: { expiration: {
maxEntries: 100, maxEntries: 100,
maxAgeSeconds: 60 * 60 * 24 * 30 // 30 days maxAgeSeconds: 60 * 60 * 24 * 30, // 30 days
} },
} },
}, },
// Google Fonts caching // Google Fonts caching
{ {
@@ -119,12 +123,12 @@ export default defineConfig({
cacheName: 'google-fonts-cache', cacheName: 'google-fonts-cache',
expiration: { expiration: {
maxEntries: 10, maxEntries: 10,
maxAgeSeconds: 60 * 60 * 24 * 365 // 365 days maxAgeSeconds: 60 * 60 * 24 * 365, // 365 days
}, },
cacheableResponse: { cacheableResponse: {
statuses: [0, 200] statuses: [0, 200],
} },
} },
}, },
{ {
urlPattern: /^https:\/\/fonts\.gstatic\.com\/.*/i, urlPattern: /^https:\/\/fonts\.gstatic\.com\/.*/i,
@@ -133,27 +137,27 @@ export default defineConfig({
cacheName: 'google-fonts-webfonts', cacheName: 'google-fonts-webfonts',
expiration: { expiration: {
maxEntries: 30, maxEntries: 30,
maxAgeSeconds: 60 * 60 * 24 * 365 // 365 days maxAgeSeconds: 60 * 60 * 24 * 365, // 365 days
}, },
cacheableResponse: { cacheableResponse: {
statuses: [0, 200] statuses: [0, 200],
} },
} },
} },
] ],
} },
}), }),
], ],
resolve: { resolve: {
alias: { alias: {
'@': resolve(__dirname, 'src'), '@': resolve(__dirname, 'src'),
'node-fetch': 'isomorphic-fetch', 'node-fetch': 'isomorphic-fetch',
'assert': 'assert', assert: 'assert',
'stream': 'stream-browserify', stream: 'stream-browserify',
'crypto': 'crypto-browserify', crypto: 'crypto-browserify',
'buffer': 'buffer', buffer: 'buffer',
'util/': 'util', 'util/': 'util',
'util': 'util', util: 'util',
}, },
}, },
server: { server: {
@@ -163,11 +167,12 @@ export default defineConfig({
usePolling: true, usePolling: true,
}, },
hmr: { hmr: {
overlay: false overlay: false,
} },
}, },
build: { build: {
outDir: 'dist', // Use 'build' to match what electron/main.js expects (../build/index.html)
outDir: 'build',
emptyOutDir: true, emptyOutDir: true,
sourcemap: process.env.GENERATE_SOURCEMAP === 'true', sourcemap: process.env.GENERATE_SOURCEMAP === 'true',
target: process.env.ELECTRON ? 'electron-renderer' : 'esnext', target: process.env.ELECTRON ? 'electron-renderer' : 'esnext',
@@ -177,27 +182,17 @@ export default defineConfig({
if (/[\\/]node_modules[\\/](react|react-dom|react-router-dom|react-i18next|i18next|i18next-browser-languagedetector|i18next-http-backend)[\\/]/.test(id)) { if (/[\\/]node_modules[\\/](react|react-dom|react-router-dom|react-i18next|i18next|i18next-browser-languagedetector|i18next-http-backend)[\\/]/.test(id)) {
return 'vendor'; return 'vendor';
} }
} },
} },
} },
}, },
base: process.env.PUBLIC_URL || '/', base: process.env.PUBLIC_URL || '/',
optimizeDeps: { optimizeDeps: {
include: [ include: ['ethers', 'assert', 'buffer', 'process', 'util', 'stream-browserify', 'isomorphic-fetch', 'workbox-core', 'workbox-precaching'],
'ethers',
'assert',
'buffer',
'process',
'util',
'stream-browserify',
'isomorphic-fetch',
'workbox-core',
'workbox-precaching'
],
}, },
define: { define: {
'process.env.VITE_COMMIT_REF': JSON.stringify(process.env.COMMIT_REF), 'process.env.VITE_COMMIT_REF': JSON.stringify(process.env.COMMIT_REF),
'global': 'globalThis', global: 'globalThis',
'__dirname': '""', __dirname: '""',
} },
}); });
+1281 -423
View File
File diff suppressed because it is too large Load Diff