Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
{
"name": "stratosort-core",
"productName": "StratoSort Core",
"version": "2.0.1",
"description": "StratoSort Core - Local AI Document Organizer. Privacy-focused document organization using in-process AI (node-llama-cpp + Orama). Zero external dependencies.",
"keywords": [
Expand Down
158 changes: 158 additions & 0 deletions scripts/patch-electron-mac.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,158 @@
#!/usr/bin/env node
/**
* Patch Electron.app Info.plist for macOS development
*
* During development, macOS reads the dock label and Activity Monitor process
* name from the Electron binary's Info.plist, which defaults to "Electron".
* This script patches CFBundleName and CFBundleDisplayName so the dock shows
* "StratoSort Core" instead.
*
* This is safe and idempotent — re-running produces the same result.
* Only affects node_modules (dev-only); production builds use electron-builder
* which sets these values correctly from electron-builder.json.
*
* Usage:
* node scripts/patch-electron-mac.js # Patch Info.plist
* node scripts/patch-electron-mac.js --check # Check current values (no changes)
*/

const fs = require('fs');
const path = require('path');

const APP_NAME = 'StratoSort Core';
const BUNDLE_ID = 'com.stratosort.app';

/**
* Locate the Electron.app Info.plist inside node_modules.
* Returns null if not found (e.g. running on non-macOS or Electron not installed).
*/
function findInfoPlist() {
const candidates = [
// Standard npm install location
path.join(
__dirname,
'..',
'node_modules',
'electron',
'dist',
'Electron.app',
'Contents',
'Info.plist'
),
// Hoisted or alternative layouts
path.join(__dirname, '..', '..', 'electron', 'dist', 'Electron.app', 'Contents', 'Info.plist')
];

for (const candidate of candidates) {
if (fs.existsSync(candidate)) {
return candidate;
}
}
return null;
}

/**
* Replace a plist string value.
* Matches: <key>KEY</key>\n\t<string>OLD</string>
* and replaces OLD with NEW.
*/
function replacePlistValue(content, key, newValue) {
// Match the key followed by a string value (handles various whitespace)
const regex = new RegExp(`(<key>${escapeRegex(key)}</key>\\s*<string>)(.*?)(</string>)`, 'g');
return content.replace(regex, `$1${newValue}$3`);
}

function escapeRegex(str) {
return str.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
}

function readPlistValue(content, key) {
const regex = new RegExp(`<key>${escapeRegex(key)}</key>\\s*<string>(.*?)</string>`);
const match = content.match(regex);
return match ? match[1] : null;
}

function main() {
if (process.platform !== 'darwin') {
console.log('[patch-electron-mac] Skipped — not macOS');
return 0;
}

const plistPath = findInfoPlist();
if (!plistPath) {
console.log('[patch-electron-mac] Skipped — Electron.app not found');
return 0;
}

const content = fs.readFileSync(plistPath, 'utf8');

const currentName = readPlistValue(content, 'CFBundleName');
const currentDisplayName = readPlistValue(content, 'CFBundleDisplayName');
const currentBundleId = readPlistValue(content, 'CFBundleIdentifier');

// --check mode: report current values without modifying
if (process.argv.includes('--check')) {
console.log('[patch-electron-mac] Info.plist:', plistPath);
console.log(` CFBundleName: ${currentName}`);
console.log(` CFBundleDisplayName: ${currentDisplayName}`);
console.log(` CFBundleIdentifier: ${currentBundleId}`);

const isPatched = currentName === APP_NAME;
console.log(
` Status: ${isPatched ? '✅ Patched' : '⚠️ Not patched (shows "Electron" in dock)'}`
);
return isPatched ? 0 : 1;
}

// Check if already patched
if (currentName === APP_NAME && currentDisplayName === APP_NAME) {
console.log('[patch-electron-mac] Already patched — dock will show "StratoSort Core"');
return 0;
}

// Apply patches
let patched = content;
patched = replacePlistValue(patched, 'CFBundleName', APP_NAME);

// CFBundleDisplayName may not exist in the original; add it if missing
if (currentDisplayName !== null) {
patched = replacePlistValue(patched, 'CFBundleDisplayName', APP_NAME);
} else {
// Insert after CFBundleName
patched = patched.replace(
/(<key>CFBundleName<\/key>\s*<string>.*?<\/string>)/,
`$1\n\t<key>CFBundleDisplayName</key>\n\t<string>${APP_NAME}</string>`
);
}

// Patch bundle identifier so macOS associates dock state correctly
if (currentBundleId && currentBundleId !== BUNDLE_ID) {
patched = replacePlistValue(patched, 'CFBundleIdentifier', BUNDLE_ID);
}

fs.writeFileSync(plistPath, patched, 'utf8');

// Verify
const verify = fs.readFileSync(plistPath, 'utf8');
const verifiedName = readPlistValue(verify, 'CFBundleName');
if (verifiedName !== APP_NAME) {
console.error('[patch-electron-mac] ❌ Patch failed — CFBundleName is still:', verifiedName);
return 1;
}

console.log('[patch-electron-mac] ✅ Patched Electron.app Info.plist');
console.log(` CFBundleName: "${currentName}" → "${APP_NAME}"`);
console.log(` CFBundleDisplayName: "${currentDisplayName || '(missing)'}" → "${APP_NAME}"`);
if (currentBundleId && currentBundleId !== BUNDLE_ID) {
console.log(` CFBundleIdentifier: "${currentBundleId}" → "${BUNDLE_ID}"`);
}
console.log(' The macOS dock will now show "StratoSort Core" in development.');

return 0;
}

if (require.main === module) {
process.exit(main());
}

module.exports = { main };
11 changes: 11 additions & 0 deletions scripts/postinstall.js
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,17 @@ function main(
}
}

// On macOS, patch the Electron.app Info.plist so the dock label and Activity
// Monitor show "StratoSort Core" instead of "Electron" during development.
if (platform === 'darwin') {
try {
const { main: patchMac } = require('./patch-electron-mac');
patchMac();
} catch (e) {
log.warn(`[postinstall] macOS Electron patch skipped: ${e.message}`);
}
}

log.log('\n[StratoSort] Setup complete!');
log.log(' Run: npm run dev');
log.log(' The app will use local AI engine (node-llama-cpp + Orama).\n');
Expand Down
30 changes: 30 additions & 0 deletions src/main/simple-main.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,14 @@ try {

const { app, BrowserWindow, ipcMain, dialog, shell, crashReporter } = require('electron');

// Set the application name as early as possible.
// On macOS this controls the dock label and application menu title.
// Electron defaults to package.json "productName" → "name", but the raw Electron
// binary in development reports itself as "Electron" to the OS. An explicit
// assignment ensures the correct name in all contexts (dock, Activity Monitor menu
// label, window title fallback) regardless of dev vs packaged mode.
app.name = 'StratoSort Core';

const isDev = process.env.NODE_ENV === 'development';

// Logging utility
Expand Down Expand Up @@ -543,6 +551,28 @@ if (gotTheLock || process.env.STRATOSORT_FORCE_LAUNCH === '1') {
// Initialize services after app is ready
app.whenReady().then(async () => {
logger.info('[STARTUP] app.whenReady resolved');

// macOS: Set the dock icon to the StratoSort logo so it replaces the default
// Electron atom icon during development. In production (packaged) builds
// electron-builder bakes the icon into the .app bundle so this is a no-op.
if (process.platform === 'darwin' && app.dock) {
try {
const { nativeImage } = require('electron');
const iconPath = path.join(__dirname, '..', 'assets', 'icons', 'png', '512x512.png');
// Resolve from repo root in dev, or from resources in packaged build
const resolvedIcon = app.isPackaged
? path.join(process.resourcesPath, 'assets', 'icons', 'png', '512x512.png')
: path.resolve(iconPath);
const fsSync = require('fs');
if (fsSync.existsSync(resolvedIcon)) {
app.dock.setIcon(nativeImage.createFromPath(resolvedIcon));
logger.info('[DOCK] Set macOS dock icon from', resolvedIcon);
}
} catch (dockErr) {
logger.debug('[DOCK] Could not set macOS dock icon:', dockErr?.message);
}
}

// FIX: Create a referenced interval to keep the event loop alive during startup
// This prevents premature exit when async operations use unreferenced timeouts
const startupKeepalive = trackInterval(() => {}, 1000);
Expand Down