From e92003719c42b15d3d048504076f926110461502 Mon Sep 17 00:00:00 2001 From: Danny Rorabaugh Date: Mon, 29 Jun 2026 11:11:44 -0400 Subject: [PATCH 1/5] Pass stable data dir to FW Lite to survive extension version updates Platform.Bible runs each extension version from a versioned cache directory, so FW Lite's default of storing projects and auth cache relative to its binary would orphan data on every update. Pass explicit paths rooted at ~/.platform.bible/extensions/lexicon/ to keep data stable across versions. Closes #2351 Co-Authored-By: Claude Sonnet 4.6 --- platform.bible-extension/src/main.ts | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/platform.bible-extension/src/main.ts b/platform.bible-extension/src/main.ts index 4fb6ed70ac..fed76134f9 100644 --- a/platform.bible-extension/src/main.ts +++ b/platform.bible-extension/src/main.ts @@ -2,6 +2,8 @@ import papi, { logger } from '@papi/backend'; import type { ExecutionActivationContext } from '@papi/core'; import { ChildProcessByStdio } from 'child_process'; import type { BrowseWebViewOptions } from 'lexicon'; +import os from 'os'; +import path from 'path'; import { Stream } from 'stream'; import { EntryService } from './services/entry-service'; import { WebViewType } from './types/enums'; @@ -241,6 +243,14 @@ export async function deactivate(): Promise { return await shutDownFwLite(); } +/** + * Returns a stable per-user directory for FW Lite data (projects, auth cache). + * Mirrors Platform.Bible's own app:// convention so the path survives extension updates. + */ +function getFwLiteDataDir(): string { + return path.join(os.homedir(), '.platform.bible', 'extensions', 'lexicon'); +} + /** Launches the FieldWorks Lite process and returns its URL domain. */ function launchFwLite(context: ExecutionActivationContext): string { const binaryPath = 'fw-lite/FwLiteWeb.exe'; @@ -253,6 +263,7 @@ function launchFwLite(context: ExecutionActivationContext): string { // TODO: Instead of hardcoding the URL and port we should run it and find them in the output. const baseUrl = 'http://localhost:29348'; + const dataDir = getFwLiteDataDir(); fwLiteProcess = context.elevatedPrivileges.createProcess.spawn( context.executionToken, binaryPath, @@ -263,6 +274,8 @@ function launchFwLite(context: ExecutionActivationContext): string { '--FwLiteWeb:CorsAllowAny=true', '--FwLiteWeb:EnableFileLogging=false', // already piped to P.B (and triggers npm watch) '--FwLiteWeb:OpenBrowser=false', + `--LcmCrdt:ProjectPath=${dataDir}`, + `--Auth:CacheFileName=${path.join(dataDir, 'msal.json')}`, ], { stdio: ['pipe', 'pipe', 'pipe'] }, ); From bb5848ab521e0f85054de7e0a0cf3a4ed539bb40 Mon Sep 17 00:00:00 2001 From: Danny Rorabaugh Date: Mon, 29 Jun 2026 11:52:17 -0400 Subject: [PATCH 2/5] Fix first-run error --- platform.bible-extension/src/main.ts | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/platform.bible-extension/src/main.ts b/platform.bible-extension/src/main.ts index fed76134f9..3a77e8e17d 100644 --- a/platform.bible-extension/src/main.ts +++ b/platform.bible-extension/src/main.ts @@ -1,6 +1,7 @@ import papi, { logger } from '@papi/backend'; import type { ExecutionActivationContext } from '@papi/core'; import { ChildProcessByStdio } from 'child_process'; +import { mkdirSync } from 'fs'; import type { BrowseWebViewOptions } from 'lexicon'; import os from 'os'; import path from 'path'; @@ -244,8 +245,8 @@ export async function deactivate(): Promise { } /** - * Returns a stable per-user directory for FW Lite data (projects, auth cache). - * Mirrors Platform.Bible's own app:// convention so the path survives extension updates. + * Returns a stable per-user directory for FW Lite data (projects, auth cache). Mirrors + * Platform.Bible's own app:// convention so the path survives extension updates. */ function getFwLiteDataDir(): string { return path.join(os.homedir(), '.platform.bible', 'extensions', 'lexicon'); @@ -264,6 +265,7 @@ function launchFwLite(context: ExecutionActivationContext): string { const baseUrl = 'http://localhost:29348'; const dataDir = getFwLiteDataDir(); + mkdirSync(dataDir, { recursive: true }); fwLiteProcess = context.elevatedPrivileges.createProcess.spawn( context.executionToken, binaryPath, From 022cea128bbe0fd4e454d5aa9ed415a734478b6f Mon Sep 17 00:00:00 2001 From: Danny Rorabaugh Date: Mon, 29 Jun 2026 13:57:16 -0400 Subject: [PATCH 3/5] Update external exemptions --- platform.bible-extension/webpack/webpack.config.base.ts | 3 +++ 1 file changed, 3 insertions(+) diff --git a/platform.bible-extension/webpack/webpack.config.base.ts b/platform.bible-extension/webpack/webpack.config.base.ts index 3c2b8fbc60..42540a31b2 100644 --- a/platform.bible-extension/webpack/webpack.config.base.ts +++ b/platform.bible-extension/webpack/webpack.config.base.ts @@ -33,6 +33,9 @@ const configBase: webpack.Configuration = { externals: [ // Built-in node modules that are not blocked by Platform.Bible 'crypto', + 'fs', + 'os', + 'path', // Additional modules provided by Platform.Bible 'react', 'react/jsx-runtime', From b1875e5bd6a0d705a5bccd91e2c2edd799356e30 Mon Sep 17 00:00:00 2001 From: Danny Rorabaugh Date: Mon, 29 Jun 2026 14:08:34 -0400 Subject: [PATCH 4/5] Fix blocked-module error by avoiding require('fs'/'os'/'path') Platform.Bible actively rejects require() calls for non-papi modules at runtime. Replace fs/os/path imports with process.env globals (which are Node.js globals, not requires) and remove the pre-launch mkdirSync (FW Lite creates its own data directories). Also reverts the incorrect webpack externals change that made the build succeed but not the runtime. Co-Authored-By: Claude Sonnet 4.6 --- platform.bible-extension/src/main.ts | 11 +++++------ .../webpack/webpack.config.base.ts | 3 --- 2 files changed, 5 insertions(+), 9 deletions(-) diff --git a/platform.bible-extension/src/main.ts b/platform.bible-extension/src/main.ts index 3a77e8e17d..fa8e6cefb7 100644 --- a/platform.bible-extension/src/main.ts +++ b/platform.bible-extension/src/main.ts @@ -1,10 +1,7 @@ import papi, { logger } from '@papi/backend'; import type { ExecutionActivationContext } from '@papi/core'; import { ChildProcessByStdio } from 'child_process'; -import { mkdirSync } from 'fs'; import type { BrowseWebViewOptions } from 'lexicon'; -import os from 'os'; -import path from 'path'; import { Stream } from 'stream'; import { EntryService } from './services/entry-service'; import { WebViewType } from './types/enums'; @@ -247,9 +244,12 @@ export async function deactivate(): Promise { /** * Returns a stable per-user directory for FW Lite data (projects, auth cache). Mirrors * Platform.Bible's own app:// convention so the path survives extension updates. + * + * Uses process.env instead of require('os') because Platform.Bible blocks non-papi requires. */ function getFwLiteDataDir(): string { - return path.join(os.homedir(), '.platform.bible', 'extensions', 'lexicon'); + const home = process.env.USERPROFILE ?? process.env.HOME ?? ''; + return `${home}\\.platform.bible\\extensions\\lexicon`; } /** Launches the FieldWorks Lite process and returns its URL domain. */ @@ -265,7 +265,6 @@ function launchFwLite(context: ExecutionActivationContext): string { const baseUrl = 'http://localhost:29348'; const dataDir = getFwLiteDataDir(); - mkdirSync(dataDir, { recursive: true }); fwLiteProcess = context.elevatedPrivileges.createProcess.spawn( context.executionToken, binaryPath, @@ -277,7 +276,7 @@ function launchFwLite(context: ExecutionActivationContext): string { '--FwLiteWeb:EnableFileLogging=false', // already piped to P.B (and triggers npm watch) '--FwLiteWeb:OpenBrowser=false', `--LcmCrdt:ProjectPath=${dataDir}`, - `--Auth:CacheFileName=${path.join(dataDir, 'msal.json')}`, + `--Auth:CacheFileName=${dataDir}\\msal.json`, ], { stdio: ['pipe', 'pipe', 'pipe'] }, ); diff --git a/platform.bible-extension/webpack/webpack.config.base.ts b/platform.bible-extension/webpack/webpack.config.base.ts index 42540a31b2..3c2b8fbc60 100644 --- a/platform.bible-extension/webpack/webpack.config.base.ts +++ b/platform.bible-extension/webpack/webpack.config.base.ts @@ -33,9 +33,6 @@ const configBase: webpack.Configuration = { externals: [ // Built-in node modules that are not blocked by Platform.Bible 'crypto', - 'fs', - 'os', - 'path', // Additional modules provided by Platform.Bible 'react', 'react/jsx-runtime', From 6eae32e05ee6cd79526707c9ed70d5bc19df4fde Mon Sep 17 00:00:00 2001 From: Danny Rorabaugh Date: Tue, 7 Jul 2026 14:04:45 -0400 Subject: [PATCH 5/5] Address review points 2, 3, 5 on FW Lite data dir - Nest under a fw-lite subfolder so it doesn't share extensions/lexicon with Platform.Bible's own papi.storage user-data for this extension. - Mirror dev vs. packaged app:// resolution (paranext-core's getAppDir) so `npm start` uses dev-appdata instead of the real per-user location. - Throw on missing USERPROFILE instead of silently building a drive-relative path; drop the dead HOME fallback (win32 is already guaranteed by the time this runs). Co-Authored-By: Claude Sonnet 5 --- platform.bible-extension/src/main.ts | 21 ++++++++++++++++----- 1 file changed, 16 insertions(+), 5 deletions(-) diff --git a/platform.bible-extension/src/main.ts b/platform.bible-extension/src/main.ts index fa8e6cefb7..758b69e12f 100644 --- a/platform.bible-extension/src/main.ts +++ b/platform.bible-extension/src/main.ts @@ -242,14 +242,25 @@ export async function deactivate(): Promise { } /** - * Returns a stable per-user directory for FW Lite data (projects, auth cache). Mirrors - * Platform.Bible's own app:// convention so the path survives extension updates. + * Returns a stable per-user directory for FW Lite data (projects, auth cache), in its own + * subdirectory so it doesn't collide with Platform.Bible's own `papi.storage` data for this + * extension (`.../extensions/lexicon/user-data/`). Mirrors Platform.Bible's own `app://` scheme + * (paranext-core's `getAppDir()`): the real per-user location when packaged, the repo-local + * dev-appdata directory in development, so `npm start` doesn't read/write production user data. * - * Uses process.env instead of require('os') because Platform.Bible blocks non-papi requires. + * Uses process.env/globalThis instead of require('os'/'path') because Platform.Bible blocks + * non-papi requires. */ function getFwLiteDataDir(): string { - const home = process.env.USERPROFILE ?? process.env.HOME ?? ''; - return `${home}\\.platform.bible\\extensions\\lexicon`; + let appDataDir: string; + if (globalThis.isPackaged) { + const home = process.env.USERPROFILE; + if (!home) throw new Error('Cannot determine FW Lite data directory: USERPROFILE is not set'); + appDataDir = `${home}\\.platform.bible`; + } else { + appDataDir = `${globalThis.resourcesPath}\\dev-appdata`; + } + return `${appDataDir}\\extensions\\lexicon\\fw-lite`; } /** Launches the FieldWorks Lite process and returns its URL domain. */