mirror of
https://github.com/rowboatlabs/rowboat.git
synced 2026-04-27 09:26:23 +02:00
252 lines
No EOL
11 KiB
JavaScript
252 lines
No EOL
11 KiB
JavaScript
// Electron Forge config file
|
|
// NOTE: Must be .cjs (CommonJS) because package.json has "type": "module"
|
|
// Forge loads configs with require(), which fails on ESM files
|
|
|
|
const path = require('path');
|
|
|
|
module.exports = {
|
|
packagerConfig: {
|
|
name: 'Rowboat',
|
|
executableName: 'rowboat',
|
|
icon: './icons/icon', // .icns extension added automatically
|
|
appBundleId: 'com.rowboat.app',
|
|
appCategoryType: 'public.app-category.productivity',
|
|
osxSign: {
|
|
batchCodesignCalls: true,
|
|
},
|
|
osxNotarize: {
|
|
appleId: process.env.APPLE_ID,
|
|
appleIdPassword: process.env.APPLE_PASSWORD,
|
|
teamId: process.env.APPLE_TEAM_ID
|
|
},
|
|
// NOTE: Electron Forge ignores packagerConfig.dir and always packages from the
|
|
// config file's directory. We use packageAfterCopy hook instead to customize output.
|
|
// dir: path.join(__dirname, '.package'), // Not supported by Forge
|
|
// Since we bundle everything with esbuild, we don't need node_modules at all.
|
|
// These settings prevent Forge's dependency walker (flora-colossus) from trying
|
|
// to analyze/copy node_modules, which fails with pnpm's symlinked workspaces.
|
|
prune: false,
|
|
ignore: [
|
|
// Skip any node_modules that might exist
|
|
/node_modules/,
|
|
// Skip source files
|
|
/\.ts$/,
|
|
/\.tsx$/,
|
|
// Skip the staging directory
|
|
/\.package/,
|
|
// Skip the bundle script
|
|
/bundle\.mjs$/,
|
|
],
|
|
},
|
|
makers: [
|
|
{
|
|
name: '@electron-forge/maker-dmg',
|
|
config: (arch) => ({
|
|
format: 'ULFO',
|
|
name: `Rowboat-${arch}`, // Architecture-specific name to avoid conflicts
|
|
})
|
|
},
|
|
{
|
|
name: '@electron-forge/maker-zip',
|
|
platforms: ['darwin'],
|
|
// ZIP is used by Squirrel.Mac for auto-updates
|
|
config: (arch) => ({
|
|
// Path must match S3 publisher's folder structure: releases/darwin/{arch}
|
|
macUpdateManifestBaseUrl: `https://rowboat-desktop-app-releases.s3.amazonaws.com/releases/darwin/${arch}`
|
|
})
|
|
}
|
|
],
|
|
publishers: [
|
|
{
|
|
name: '@electron-forge/publisher-s3',
|
|
config: {
|
|
bucket: 'rowboat-desktop-app-releases',
|
|
region: 'us-east-1',
|
|
public: true,
|
|
folder: 'releases' // Creates structure: releases/darwin/{arch}/files (separate builds for arm64 and x64)
|
|
}
|
|
}
|
|
],
|
|
hooks: {
|
|
// Hook signature: (forgeConfig, platform, arch)
|
|
// Note: Console output only shows if DEBUG or CI env vars are set
|
|
generateAssets: async (forgeConfig, platform, arch) => {
|
|
const { execSync } = require('child_process');
|
|
const fs = require('fs');
|
|
|
|
const packageDir = path.join(__dirname, '.package');
|
|
|
|
// Clean staging directory (ensures fresh build every time)
|
|
console.log('Cleaning staging directory...');
|
|
if (fs.existsSync(packageDir)) {
|
|
fs.rmSync(packageDir, { recursive: true });
|
|
}
|
|
fs.mkdirSync(packageDir, { recursive: true });
|
|
|
|
// Build order matters! Dependencies must be built before dependents:
|
|
// shared → core → (renderer, preload, main)
|
|
|
|
// Build shared (TypeScript compilation) - no dependencies
|
|
console.log('Building shared...');
|
|
execSync('pnpm run build', {
|
|
cwd: path.join(__dirname, '../../packages/shared'),
|
|
stdio: 'inherit'
|
|
});
|
|
|
|
// Build core (TypeScript compilation) - depends on shared
|
|
console.log('Building core...');
|
|
execSync('pnpm run build', {
|
|
cwd: path.join(__dirname, '../../packages/core'),
|
|
stdio: 'inherit'
|
|
});
|
|
|
|
// Build renderer (Vite build) - depends on shared
|
|
console.log('Building renderer...');
|
|
execSync('pnpm run build', {
|
|
cwd: path.join(__dirname, '../renderer'),
|
|
stdio: 'inherit'
|
|
});
|
|
|
|
// Build preload (TypeScript compilation) - depends on shared
|
|
console.log('Building preload...');
|
|
execSync('pnpm run build', {
|
|
cwd: path.join(__dirname, '../preload'),
|
|
stdio: 'inherit'
|
|
});
|
|
|
|
// Build main (TypeScript compilation) - depends on core, shared
|
|
console.log('Building main (tsc)...');
|
|
execSync('pnpm run build', {
|
|
cwd: __dirname,
|
|
stdio: 'inherit'
|
|
});
|
|
|
|
// Bundle main process with esbuild (inlines all dependencies)
|
|
console.log('Bundling main process...');
|
|
execSync('node bundle.mjs', {
|
|
cwd: __dirname,
|
|
stdio: 'inherit'
|
|
});
|
|
|
|
// Copy preload dist into staging directory
|
|
console.log('Copying preload...');
|
|
const preloadSrc = path.join(__dirname, '../preload/dist');
|
|
const preloadDest = path.join(packageDir, 'preload/dist');
|
|
fs.mkdirSync(preloadDest, { recursive: true });
|
|
fs.cpSync(preloadSrc, preloadDest, { recursive: true });
|
|
|
|
// Copy renderer dist into staging directory
|
|
console.log('Copying renderer...');
|
|
const rendererSrc = path.join(__dirname, '../renderer/dist');
|
|
const rendererDest = path.join(packageDir, 'renderer/dist');
|
|
fs.mkdirSync(rendererDest, { recursive: true });
|
|
fs.cpSync(rendererSrc, rendererDest, { recursive: true });
|
|
|
|
// Copy icons into staging directory
|
|
console.log('Copying icons...');
|
|
const iconsSrc = path.join(__dirname, 'icons');
|
|
const iconsDest = path.join(packageDir, 'icons');
|
|
if (fs.existsSync(iconsSrc)) {
|
|
fs.mkdirSync(iconsDest, { recursive: true });
|
|
fs.cpSync(iconsSrc, iconsDest, { recursive: true });
|
|
}
|
|
|
|
// Generate package.json in staging directory
|
|
// This tells Electron where to find the entry point
|
|
// Note: No "type": "module" since we bundle as CommonJS for compatibility
|
|
// with dependencies that use dynamic require()
|
|
// Read version from source package.json (updated by CI from git tag)
|
|
const sourcePackageJson = JSON.parse(fs.readFileSync(path.join(__dirname, 'package.json'), 'utf8'));
|
|
const packageJson = {
|
|
name: '@x/main',
|
|
version: sourcePackageJson.version,
|
|
main: 'dist-bundle/main.js',
|
|
};
|
|
fs.writeFileSync(
|
|
path.join(packageDir, 'package.json'),
|
|
JSON.stringify(packageJson, null, 2)
|
|
);
|
|
|
|
console.log('✅ All assets staged in .package/');
|
|
},
|
|
// Hook signature: async (config, buildPath, electronVersion, platform, arch)
|
|
// Called after Forge copies source directory to build output
|
|
// This is where we replace source files with bundled/staged files
|
|
packageAfterCopy: async (config, buildPath, electronVersion, platform, arch) => {
|
|
const fs = require('fs');
|
|
const packageDir = path.join(__dirname, '.package');
|
|
// buildPath already points to the app directory (Contents/Resources/app)
|
|
const appResourcesPath = buildPath;
|
|
|
|
console.log('📦 Copying staged files from .package/ to packaged app...');
|
|
|
|
// Remove unbundled dist/ directory (source TypeScript output)
|
|
const unbundledDist = path.join(appResourcesPath, 'dist');
|
|
if (fs.existsSync(unbundledDist)) {
|
|
console.log('Removing unbundled dist/...');
|
|
fs.rmSync(unbundledDist, { recursive: true });
|
|
}
|
|
|
|
// Copy bundled dist-bundle/ from staging
|
|
const distBundleSrc = path.join(packageDir, 'dist-bundle');
|
|
const distBundleDest = path.join(appResourcesPath, 'dist-bundle');
|
|
if (fs.existsSync(distBundleSrc)) {
|
|
console.log('Copying dist-bundle/...');
|
|
fs.mkdirSync(distBundleDest, { recursive: true });
|
|
fs.cpSync(distBundleSrc, distBundleDest, { recursive: true });
|
|
}
|
|
|
|
// Copy preload/ from staging
|
|
const preloadSrc = path.join(packageDir, 'preload');
|
|
const preloadDest = path.join(appResourcesPath, 'preload');
|
|
if (fs.existsSync(preloadSrc)) {
|
|
console.log('Copying preload/...');
|
|
// Remove old preload if it exists
|
|
if (fs.existsSync(preloadDest)) {
|
|
fs.rmSync(preloadDest, { recursive: true });
|
|
}
|
|
fs.cpSync(preloadSrc, preloadDest, { recursive: true });
|
|
}
|
|
|
|
// Copy renderer/ from staging
|
|
const rendererSrc = path.join(packageDir, 'renderer');
|
|
const rendererDest = path.join(appResourcesPath, 'renderer');
|
|
if (fs.existsSync(rendererSrc)) {
|
|
console.log('Copying renderer/...');
|
|
// Remove old renderer if it exists
|
|
if (fs.existsSync(rendererDest)) {
|
|
fs.rmSync(rendererDest, { recursive: true });
|
|
}
|
|
fs.cpSync(rendererSrc, rendererDest, { recursive: true });
|
|
}
|
|
|
|
// Update package.json to point to bundled entry point
|
|
const packageJsonPath = path.join(appResourcesPath, 'package.json');
|
|
if (fs.existsSync(packageJsonPath)) {
|
|
console.log('Updating package.json...');
|
|
// Read version from source package.json (updated by CI from git tag)
|
|
const sourcePackageJson = JSON.parse(fs.readFileSync(path.join(__dirname, 'package.json'), 'utf8'));
|
|
const packageJson = {
|
|
name: '@x/main',
|
|
version: sourcePackageJson.version,
|
|
main: 'dist-bundle/main.js',
|
|
// Note: No "type": "module" since we bundle as CommonJS
|
|
// No dependencies/devDependencies since everything is bundled
|
|
};
|
|
fs.writeFileSync(packageJsonPath, JSON.stringify(packageJson, null, 2));
|
|
}
|
|
|
|
// Clean up source files that shouldn't be in packaged app
|
|
const filesToRemove = ['src', 'tsconfig.json', 'forge.config.cjs', 'agents.md', '.gitignore', 'bundle.mjs'];
|
|
for (const file of filesToRemove) {
|
|
const filePath = path.join(appResourcesPath, file);
|
|
if (fs.existsSync(filePath)) {
|
|
console.log(`Removing ${file}...`);
|
|
fs.rmSync(filePath, { recursive: true, force: true });
|
|
}
|
|
}
|
|
|
|
console.log('✅ Staged files copied to packaged app');
|
|
}
|
|
}
|
|
}; |