build(release): dynamic builder config + fix bundled-tool lookup in packages

- electron-builder config moves to electron-builder.config.js so markitdown
  bundling is conditional per platform (PyInstaller only builds for the host
  OS; a missing binary now logs a warning and ships without it instead of
  failing the build); package.json static build section removed, all npm
  scripts pointed at the config; third-party-licenses/ added to packaged files
- FIX (pre-existing): packaged apps looked for bundled pandoc in
  resources/bin, but extraFiles land next to the executable (Contents/ on
  macOS) — packaged builds silently fell back to system pandoc since 4.5.
  New bundledToolDir() resolves the real location for pandoc + markitdown
- download-tools.js pins the win32 pandoc.exe SHA-256 (fetched + verified)
- sharp packaging test accepts sharp 0.35's versioned binding filename
- release.yml: bundle-markitdown step (best-effort) on every OS and a new
  macOS job; release aggregates linux+windows+macos artifacts

Local release artifacts built and verified (dist/):
- MarkdownConverter-4.7.0.AppImage (363MB, pandoc+markitdown bundled,
  packaged app boots clean, tools resolve at the fixed path)
- markdown-converter_4.7.0_amd64.deb (293MB)
- MarkdownConverter-Setup-4.7.0.exe (223MB), portable exe (223MB), zip
  (305MB) — pandoc bundled; markitdown omitted (cannot cross-build),
  legal docs verified inside app.asar

637/637 tests green; lint clean.
This commit is contained in:
2026-09-05 22:48:45 +05:30
parent 1e24b52f3e
commit 0be46d4bac
8 changed files with 246 additions and 174 deletions
+17 -1
View File
@@ -27,11 +27,27 @@ if (process.platform === 'win32') {
}
}
/**
* Directory of the bundled external tools (pandoc/markitdown) in a PACKAGED
* app. electron-builder extraFiles land next to the executable on Linux and
* Windows, and inside Contents/ on macOS — NOT in resources/bin (which is
* where the lookup incorrectly pointed before v4.7, making packaged builds
* silently fall back to system-installed tools).
*/
function bundledToolDir() {
if (process.platform === 'darwin') {
// resourcesPath = …/Contents/Resources → tools live in …/Contents/bin
return path.join(path.dirname(process.resourcesPath), 'bin');
}
// exePath = …/MarkdownConverter(.exe) → tools live in …/bin
return path.join(path.dirname(process.execPath), 'bin');
}
// Returns path to pandoc: bundled binary when packaged, dev bin or system fallback otherwise.
function getPandocPath() {
if (app.isPackaged) {
const ext = process.platform === 'win32' ? '.exe' : '';
return path.join(process.resourcesPath, 'bin', `pandoc${ext}`);
return path.join(bundledToolDir(), `pandoc${ext}`);
}
// Development: prefer locally-downloaded binary in bin/<platform>/
const devBin = path.join(
+20 -13
View File
@@ -24,25 +24,32 @@ const MAX_OUTPUT_BUFFER = 20 * 1024 * 1024;
/**
* Path to the bundled markitdown binary (built by scripts/bundle-markitdown.js
* via PyInstaller), when the app ships one. Mirrors getPandocPath's layout:
* dev: bin/<platform>/markitdown · packaged: <resourcesPath>/bin/markitdown.
* Returns null when no bundle exists (PATH/python fallbacks apply).
* via PyInstaller), when the app ships one. Layout matches getPandocPath:
* dev: bin/<platform>/markitdown · packaged: <app dir>/bin/markitdown (next
* to the executable on Linux/Windows, Contents/bin on macOS — the same
* extraFiles destination). Returns null when absent (PATH/python fallbacks).
*/
function getBundledMarkItDownPath() {
const pathUtil = require('path');
const fs = require('fs');
const exe = process.platform === 'win32' ? 'markitdown.exe' : 'markitdown';
if (typeof process === 'object' && process.resourcesPath && !process.resourcesPath.includes('node_modules')) {
// Packaged (Electron) — resourcesPath only exists in a real app runtime
try {
const electron = require('electron');
if (electron.app?.isPackaged) {
const packaged = pathUtil.join(process.resourcesPath, 'bin', exe);
if (fs.existsSync(packaged)) return packaged;
}
} catch {
/* not running under Electron (tests) — fall through to dev layout */
try {
// Only meaningful under Electron; tests import this module under Node
const electron = require('electron');
if (electron.app?.isPackaged) {
const base =
process.platform === 'darwin'
? pathUtil.dirname(electron.app.getPath('exe')) // …/Contents/MacOS
: pathUtil.dirname(process.execPath);
const packaged =
process.platform === 'darwin'
? pathUtil.join(base, '..', 'bin', exe) // …/Contents/bin
: pathUtil.join(base, 'bin', exe);
if (fs.existsSync(packaged)) return packaged;
return null;
}
} catch {
/* not running under Electron (tests) — use the dev layout below */
}
const dev = pathUtil.join(__dirname, '..', '..', 'bin', process.platform, exe);
return fs.existsSync(dev) ? dev : null;