mirror of
https://github.com/bitsocialnet/5chan.git
synced 2026-08-03 07:41:04 +02:00
refactor: migrate from electron-rebuild to electron-forge
This commit is contained in:
+105
-136
@@ -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
|
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -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'],
|
||||||
|
|||||||
@@ -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
@@ -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"
|
||||||
|
|||||||
@@ -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);
|
||||||
@@ -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
@@ -1,7 +1,8 @@
|
|||||||
{
|
{
|
||||||
"rewrites": [
|
"rewrites": [
|
||||||
{ "source": "/(.*)", "destination": "/index.html" }
|
{ "source": "/(.*)", "destination": "/index.html" }
|
||||||
]
|
],
|
||||||
|
"buildCommand": "yarn build",
|
||||||
|
"outputDirectory": "build",
|
||||||
|
"framework": "vite"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
+54
-59
@@ -13,14 +13,18 @@ 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')) &&
|
||||||
|
reactScan({
|
||||||
showToolbar: true,
|
showToolbar: true,
|
||||||
playSound: true,
|
playSound: 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: '""',
|
||||||
}
|
},
|
||||||
});
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user