diff --git a/.github/workflows/desktop-linux-test-pull.yml b/.github/workflows/desktop-linux-test-pull.yml index dad96d70..f1a77ef0 100644 --- a/.github/workflows/desktop-linux-test-pull.yml +++ b/.github/workflows/desktop-linux-test-pull.yml @@ -16,7 +16,7 @@ jobs: - name: setup node uses: actions/setup-node@v4 with: - node-version: 20 + node-version: 24 - name: install Rust stable uses: dtolnay/rust-toolchain@stable with: diff --git a/.github/workflows/desktop-mac-m1-test-pull.yml b/.github/workflows/desktop-mac-m1-test-pull.yml index e24527be..e2f2ae7e 100644 --- a/.github/workflows/desktop-mac-m1-test-pull.yml +++ b/.github/workflows/desktop-mac-m1-test-pull.yml @@ -18,7 +18,7 @@ jobs: - name: setup node uses: actions/setup-node@v4 with: - node-version: 20 + node-version: 24 - name: install Rust stable uses: dtolnay/rust-toolchain@stable with: diff --git a/.github/workflows/desktop-mac-test-pull.yml b/.github/workflows/desktop-mac-test-pull.yml index 4cbbe2ff..314f3418 100644 --- a/.github/workflows/desktop-mac-test-pull.yml +++ b/.github/workflows/desktop-mac-test-pull.yml @@ -16,7 +16,7 @@ jobs: - name: setup node uses: actions/setup-node@v4 with: - node-version: 20 + node-version: 24 - name: install Rust stable uses: dtolnay/rust-toolchain@stable with: diff --git a/.github/workflows/desktop-windows-test-pull.yml b/.github/workflows/desktop-windows-test-pull.yml index fc641988..7a0d58d0 100644 --- a/.github/workflows/desktop-windows-test-pull.yml +++ b/.github/workflows/desktop-windows-test-pull.yml @@ -16,7 +16,7 @@ jobs: - name: setup node uses: actions/setup-node@v4 with: - node-version: 20 + node-version: 24 - name: install Rust stable uses: dtolnay/rust-toolchain@stable with: diff --git a/.github/workflows/prod-desktop-tests-on-pull.yml b/.github/workflows/prod-desktop-tests-on-pull.yml index d36b3a76..f6b153c4 100644 --- a/.github/workflows/prod-desktop-tests-on-pull.yml +++ b/.github/workflows/prod-desktop-tests-on-pull.yml @@ -14,7 +14,7 @@ jobs: - name: setup node uses: actions/setup-node@v4 with: - node-version: 20 + node-version: 24 - name: install Rust stable uses: dtolnay/rust-toolchain@stable with: @@ -129,7 +129,7 @@ jobs: - name: setup node uses: actions/setup-node@v4 with: - node-version: 20 + node-version: 24 - name: install Rust stable uses: dtolnay/rust-toolchain@stable with: @@ -237,7 +237,7 @@ jobs: - name: setup node uses: actions/setup-node@v4 with: - node-version: 20 + node-version: 24 - name: install Rust stable uses: dtolnay/rust-toolchain@stable with: diff --git a/.github/workflows/staging-desktop-tests-on-pull.yml b/.github/workflows/staging-desktop-tests-on-pull.yml index 5089812f..f31d55df 100644 --- a/.github/workflows/staging-desktop-tests-on-pull.yml +++ b/.github/workflows/staging-desktop-tests-on-pull.yml @@ -14,7 +14,7 @@ jobs: - name: setup node uses: actions/setup-node@v4 with: - node-version: 20 + node-version: 24 - name: install Rust stable uses: dtolnay/rust-toolchain@stable with: @@ -117,7 +117,7 @@ jobs: - name: setup node uses: actions/setup-node@v4 with: - node-version: 20 + node-version: 24 - name: install Rust stable uses: dtolnay/rust-toolchain@stable with: @@ -213,7 +213,7 @@ jobs: - name: setup node uses: actions/setup-node@v4 with: - node-version: 20 + node-version: 24 - name: install Rust stable uses: dtolnay/rust-toolchain@stable with: diff --git a/.github/workflows/tauri-build-dev.yml b/.github/workflows/tauri-build-dev.yml index 3bc19fe7..c978ff1f 100644 --- a/.github/workflows/tauri-build-dev.yml +++ b/.github/workflows/tauri-build-dev.yml @@ -17,7 +17,7 @@ jobs: - name: setup node uses: actions/setup-node@v4 with: - node-version: 20 + node-version: 24 - name: get version run: | echo "PACKAGE_VERSION=$(node -p "require('./package.json').version")" >> $GITHUB_ENV @@ -51,7 +51,7 @@ jobs: - name: setup node uses: actions/setup-node@v4 with: - node-version: 20 + node-version: 24 - name: install frontend dependencies env: GH_TOKEN: ${{ github.token }} @@ -91,7 +91,7 @@ jobs: - name: setup node uses: actions/setup-node@v4 with: - node-version: 20 + node-version: 24 - name: Download src-node built on mac arm (Mac only) if: matrix.platform == 'macos-15-intel' @@ -291,7 +291,7 @@ jobs: - name: setup node uses: actions/setup-node@v4 with: - node-version: 20 + node-version: 24 - name: GLIBC version run: | @@ -315,7 +315,6 @@ jobs: run: | npm ci npm run _ci-release:dev - npm run _ci-disableBundleConfig npm run tauri build ls -alh ./src-tauri/target/release mkdir ./src-tauri/target/release/phoenix-code diff --git a/.github/workflows/tauri-build-prod.yml b/.github/workflows/tauri-build-prod.yml index 897d0636..840757f6 100644 --- a/.github/workflows/tauri-build-prod.yml +++ b/.github/workflows/tauri-build-prod.yml @@ -17,7 +17,7 @@ jobs: - name: setup node uses: actions/setup-node@v4 with: - node-version: 20 + node-version: 24 - name: get version run: | echo "PACKAGE_VERSION=$(node -p "require('./package.json').version")" >> $GITHUB_ENV @@ -51,7 +51,7 @@ jobs: - name: setup node uses: actions/setup-node@v4 with: - node-version: 20 + node-version: 24 - name: install frontend dependencies env: GH_TOKEN: ${{ github.token }} @@ -91,7 +91,7 @@ jobs: - name: setup node uses: actions/setup-node@v4 with: - node-version: 20 + node-version: 24 - name: Download src-node built on mac arm (Mac only) if: matrix.platform == 'macos-15-intel' @@ -339,7 +339,7 @@ jobs: - name: setup node uses: actions/setup-node@v4 with: - node-version: 20 + node-version: 24 - name: GLIBC version run: | @@ -363,7 +363,6 @@ jobs: run: | npm ci npm run _ci-release:prod - npm run _ci-disableBundleConfig npm run tauri build ls -alh ./src-tauri/target/release mkdir ./src-tauri/target/release/binDist diff --git a/.github/workflows/tauri-build-staging.yml b/.github/workflows/tauri-build-staging.yml index fdbb2535..ca5d28f2 100644 --- a/.github/workflows/tauri-build-staging.yml +++ b/.github/workflows/tauri-build-staging.yml @@ -17,7 +17,7 @@ jobs: - name: setup node uses: actions/setup-node@v4 with: - node-version: 20 + node-version: 24 - name: get version run: | echo "PACKAGE_VERSION=$(node -p "require('./package.json').version")" >> $GITHUB_ENV @@ -51,7 +51,7 @@ jobs: - name: setup node uses: actions/setup-node@v4 with: - node-version: 20 + node-version: 24 - name: install frontend dependencies env: GH_TOKEN: ${{ github.token }} @@ -91,7 +91,7 @@ jobs: - name: setup node uses: actions/setup-node@v4 with: - node-version: 20 + node-version: 24 - name: Download src-node built on mac arm (Mac only) if: matrix.platform == 'macos-15-intel' @@ -290,7 +290,7 @@ jobs: - name: setup node uses: actions/setup-node@v4 with: - node-version: 20 + node-version: 24 - name: GLIBC version run: | @@ -314,7 +314,6 @@ jobs: run: | npm ci npm run _ci-release:staging - npm run _ci-disableBundleConfig npm run tauri build ls -alh ./src-tauri/target/release mkdir ./src-tauri/target/release/phoenix-code diff --git a/.github/workflows/update-phcode-build.yml b/.github/workflows/update-phcode-build.yml index fe960349..e06d6c88 100644 --- a/.github/workflows/update-phcode-build.yml +++ b/.github/workflows/update-phcode-build.yml @@ -20,7 +20,7 @@ jobs: - name: setup node uses: actions/setup-node@v4 with: - node-version: 20 + node-version: 24 - name: Update phcode and version env: diff --git a/.gitignore b/.gitignore index 5b4c7492..17e19c73 100644 --- a/.gitignore +++ b/.gitignore @@ -19,6 +19,10 @@ src-tauri/phnode-* src-tauri/tauri-local.conf.json phoenix +# Electron things +src-electron/bin +src-electron/src-node + node_modules dist dist-ssr diff --git a/package-lock.json b/package-lock.json index 879eebf1..485f7547 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "phoenix-code-ide", - "version": "4.0.3", + "version": "5.0.5", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "phoenix-code-ide", - "version": "4.0.3", + "version": "5.0.5", "hasInstallScript": true, "devDependencies": { "@tauri-apps/cli": "1.6.3", @@ -453,6 +453,7 @@ "integrity": "sha512-eBSYY4Y68NNlHbHBMdeDmKNtDgXWhQsJcGqzO3iLUM0GraQFSS9cVgPX5I9b3lbdFKyYoAEGAZF1DwhTaljNAw==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "follow-redirects": "^1.15.6", "form-data": "^4.0.0", diff --git a/package.json b/package.json index 863999fb..6fec18be 100644 --- a/package.json +++ b/package.json @@ -11,7 +11,6 @@ "_createDistBundleReleaseConfig": "node src-build/createDistBundleReleaseConfig.js", "_createDistTestReleaseConfig": "node src-build/createDistTestReleaseConfig.js", "_ci-createDistReleaseConfig": "node src-build/ci-createDistReleaseConfig.js", - "_ci-disableBundleConfig": "node src-build/ci-disableBundleConfig.js", "_ci-env-warn": "echo !!!This script is supposed to executed in github actions only. Ignore if you are seeing this message in a github actions log.", "releaseSrc": "npm run _make_src-node && npm run _createSrcReleaseConfig && tauri build --config ./src-tauri/tauri-local.conf.json", "releaseSrcDebug": "npm run _make_src-node && npm run _createSrcReleaseConfig && tauri build --config ./src-tauri/tauri-local.conf.json --debug --verbose", @@ -20,21 +19,18 @@ "releaseDistDebug": "npm run _make_src-node && npm run _createDistReleaseConfig && tauri build --config ./src-tauri/tauri-local.conf.json --debug --verbose", "releaseDistTest": "npm run _make_src-node && npm run _createDistTestReleaseConfig && tauri build --config ./src-tauri/tauri-local.conf.json", "releaseDistTestDebug": "npm run _make_src-node && npm run _createDistTestReleaseConfig && tauri build --config ./src-tauri/tauri-local.conf.json --debug", - "_src-node_npm_install": "cd src-tauri/src-node && npm ci --production && cd ../../ && npm run _src-node_remove_unsupported_bin", - "_src-node_remove_unsupported_bin": "shx rm -f src-tauri/src-node/node_modules/@msgpackr-extract/msgpackr-extract-linux-*/*.musl.node src-tauri/src-node/node_modules/@lmdb/lmdb-linux-*/*.musl.node", - "_make_src-node": "shx rm -rf src-tauri/src-node && shx cp -r ../phoenix/src-node src-tauri/src-node && npm run _src-node_npm_install", - "_make_src-node_debug_dev": "npm run _make_src-node && shx rm -rf src-tauri/target/debug/src-node && shx cp -r src-tauri/src-node src-tauri/target/debug/src-node", - "_ci_make_src-node": "shx rm -rf src-tauri/src-node && shx cp -r phoenix/src-node src-tauri/src-node && npm run _src-node_npm_install", - "_servePhoenix": "echo -e \"\nEnsure to start phoenix server at http://localhost:8000 for development.\n Follow https://github.com/phcode-dev/phoenix#running-phoenix for instructions.\"", + "_make_src-node": "node ./src-build/makeSrcNode.js ../phoenix/src-node", + "_ci_make_src-node": "node ./src-build/makeSrcNode.js phoenix/src-node", "_ci-clonePhoenixForTests": "npm run _ci-env-warn && node ./src-build/clonePhoenixForTests.js", "_ci-cloneAndBuildPhoenix": "npm run _ci-env-warn && node ./src-build/clonePhoenix.js && cd phoenix && npm ci && npm run build && cd ..", "_ci-release:dev": "npm run _ci-env-warn && npm run _ci-cloneAndBuildPhoenix && cd phoenix && npm run release:dev && cd .. && npm run _ci-createDistReleaseConfig && npm run _ci_make_src-node", "_ci-release:staging": "npm run _ci-env-warn && npm run _ci-cloneAndBuildPhoenix && cd phoenix && npm run release:staging && cd .. && npm run _ci-createDistReleaseConfig && npm run _ci_make_src-node", "_ci-release:prod": "npm run _ci-env-warn && npm run _ci-cloneAndBuildPhoenix && cd phoenix && npm run release:prod && cd .. && npm run _ci-createDistReleaseConfig && npm run _ci_make_src-node", "_ci-update-phcode-build": "node src-build/update-phcode-build.js", - "_watch_src-node": "chokidar '../phoenix/src-node/**/*' --ignore '../phoenix/src-node/node_modules/**/*' -c 'npm run _make_src-node_debug_dev'", - "serve": "npm run _servePhoenix && npm run _make_src-node && tauri dev", - "postinstall": "node ./src-build/downloadNodeBinary.js", + "serve": "node src-build/serveForPlatform.js", + "serve:tauri": "node src-build/serveForPlatform.js tauri", + "serve:electron": "node src-build/serveForPlatform.js electron", + "postinstall": "node ./src-build/downloadNodeBinary.js && node ./src-build/setupElectron.js", "cleanNodeBinary": "node src-build/cleanNodeBinary.js", "installNodeArmDarwin": "node ./src-build/downloadNodeBinary.js '{\"platform\":\"darwin\",\"arch\":\"arm64\"}'", "getPackageVersion": "node ./src-build/getVersion.js" @@ -56,4 +52,4 @@ "branch": "tauri", "commit": "23987dad9240266fd297cbb99202178a3f68a6b2" } -} \ No newline at end of file +} diff --git a/src-build/ci-disableBundleConfig.js b/src-build/ci-disableBundleConfig.js deleted file mode 100644 index 84350a4b..00000000 --- a/src-build/ci-disableBundleConfig.js +++ /dev/null @@ -1,29 +0,0 @@ -import {dirname, join} from "path"; -import fs from "fs"; -import {fileURLToPath} from "url"; -import os from "os"; - -const __filename = fileURLToPath(import.meta.url); -const __dirname = dirname(__filename); -async function ciCreateDistReleaseConfig() { - const tauriConfigPath = join(__dirname, '..', 'src-tauri', 'tauri.conf.json'); - - console.log("remove bundle config in tauri.conf.json", tauriConfigPath); - const configJson = JSON.parse(fs.readFileSync(tauriConfigPath)); - configJson.tauri.bundle.active = false; - configJson.tauri.updater.active = false; - if(os.platform() === 'darwin'){ - // inject macos icons - configJson.tauri.bundle.icon = [ - "icons-mac/32x32.png", - "icons-mac/128x128.png", - "icons-mac/128x128@2x.png", - "icons-mac/icon.icns", - "icons-mac/icon.ico" - ]; - } - console.log("Product name is: ", configJson.package.productName); - fs.writeFileSync(tauriConfigPath, JSON.stringify(configJson, null, 4)); -} - -await ciCreateDistReleaseConfig(); diff --git a/src-build/downloadNodeBinary.js b/src-build/downloadNodeBinary.js index fdc47ab5..2568517e 100644 --- a/src-build/downloadNodeBinary.js +++ b/src-build/downloadNodeBinary.js @@ -239,6 +239,24 @@ async function copyLatestNodeForBuild(platform, arch) { console.log("Error Found:", err); throw new Error(err); } + + // Also copy to src-electron/bin folder + const electronBinDir = (platform === "win") ? `${__dirname}\\..\\src-electron\\bin` : `${__dirname}/../src-electron/bin`; + const electronBinName = (platform === "win") ? "phnode.exe" : "phnode"; + const electronDestNode = (platform === "win") ? `${electronBinDir}\\${electronBinName}` : `${electronBinDir}/${electronBinName}`; + + try { + // Ensure the directory exists + if (!fs.existsSync(electronBinDir)) { + fs.mkdirSync(electronBinDir, { recursive: true }); + } + fs.copyFileSync(srcNode, electronDestNode); + console.log("File copied to src-electron/bin successfully!"); + } catch (err) { + console.log("Error copying to src-electron/bin:", err); + throw new Error(err); + } + await removeDir(fullPathOfNode); } diff --git a/src-build/makeSrcNode.js b/src-build/makeSrcNode.js new file mode 100644 index 00000000..5d7f3f08 --- /dev/null +++ b/src-build/makeSrcNode.js @@ -0,0 +1,50 @@ +import { execSync } from 'child_process'; +import { fileURLToPath } from 'url'; +import { dirname, join } from 'path'; + +const __filename = fileURLToPath(import.meta.url); +const __dirname = dirname(__filename); + +// Get source path from command line argument +const srcNodeSource = process.argv[2]; +if (!srcNodeSource) { + console.error('Usage: node makeSrcNode.js '); + console.error('Example: node makeSrcNode.js ../phoenix/src-node'); + process.exit(1); +} + +const targets = [ + join(__dirname, '..', 'src-tauri', 'src-node'), + join(__dirname, '..', 'src-electron', 'src-node') +]; + +function setupTarget(srcPath, destPath) { + console.log(`\nSetting up ${destPath}...`); + + // Remove existing and copy from source + execSync(`shx rm -rf ${destPath}`, { stdio: 'inherit' }); + execSync(`shx cp -r ${srcPath} ${destPath}`, { stdio: 'inherit' }); + + // Install production dependencies + console.log('Installing production dependencies...'); + execSync('npm ci --production', { cwd: destPath, stdio: 'inherit' }); + + // Remove unsupported musl binaries + console.log('Removing unsupported musl binaries...'); + execSync(`shx rm -f ${destPath}/node_modules/@msgpackr-extract/msgpackr-extract-linux-*/*.musl.node`, { stdio: 'pipe' }); + execSync(`shx rm -f ${destPath}/node_modules/@lmdb/lmdb-linux-*/*.musl.node`, { stdio: 'pipe' }); + + console.log(`${destPath} setup complete!`); +} + +function main() { + const absoluteSrc = join(process.cwd(), srcNodeSource); + + for (const target of targets) { + setupTarget(absoluteSrc, target); + } + + console.log('\nAll targets setup successfully!'); +} + +main(); diff --git a/src-build/serveForPlatform.js b/src-build/serveForPlatform.js new file mode 100644 index 00000000..a1e1e211 --- /dev/null +++ b/src-build/serveForPlatform.js @@ -0,0 +1,57 @@ +import {getPlatformDetails} from "./utils.js"; +import {execa} from "execa"; +import chalk from "chalk"; + +const {platform} = getPlatformDetails(); + +// Get target from CLI arg, or detect from platform +const cliArg = process.argv[2]; +let target; + +if (cliArg === 'tauri' || cliArg === 'electron') { + target = cliArg; +} else if (cliArg) { + console.error(`Unknown target: ${cliArg}`); + console.error('Usage: npm run serve [tauri|electron]'); + process.exit(1); +} else { + // Auto-detect: Linux uses Electron, Windows/Mac use Tauri + target = (platform === "linux") ? "electron" : "tauri"; +} + +// Warn about non-standard platform/target combinations +const recommendedTarget = (platform === "linux") ? "electron" : "tauri"; +if (target !== recommendedTarget) { + const y = chalk.yellow; + const b = chalk.bold.yellow; + const line1 = ` Running ${target} on ${platform} is not officially supported.`; + const line2 = ` Recommended: npm run serve (auto-detects ${recommendedTarget} for ${platform})`; + const width = Math.max(50, line1.length, line2.length) + 2; + const border = '═'.repeat(width); + const pad = (str) => str + ' '.repeat(width - str.length); + + console.warn(y(`\n╔${border}╗`)); + console.warn(y('║') + b(pad(' ⚠️ NON-STANDARD PLATFORM CONFIGURATION')) + y('║')); + console.warn(y(`╠${border}╣`)); + console.warn(y('║') + y(pad(line1)) + y('║')); + console.warn(y('║') + y(pad(line2)) + y('║')); + console.warn(y(`╚${border}╝\n`)); +} + +console.log(`Platform: ${platform}, target: ${target}`); + +// Run common setup +console.log('\nEnsure to start phoenix server at http://localhost:8000 for development.'); +console.log('Follow https://github.com/phcode-dev/phoenix#running-phoenix for instructions.\n'); + +console.log('Setting up src-node...'); +await execa("npm", ["run", "_make_src-node"], {stdio: "inherit"}); + +// Run platform-specific command +if (target === "tauri") { + console.log('Starting Tauri dev server...'); + await execa("npx", ["tauri", "dev"], {stdio: "inherit"}); +} else { + console.log('Starting Electron...'); + await execa("./src-electron/node_modules/.bin/electron", ["src-electron/main.js"], {stdio: "inherit"}); +} diff --git a/src-build/setupElectron.js b/src-build/setupElectron.js new file mode 100644 index 00000000..a435e87d --- /dev/null +++ b/src-build/setupElectron.js @@ -0,0 +1,12 @@ +import { execSync } from 'child_process'; +import { fileURLToPath } from 'url'; +import { dirname, join } from 'path'; + +const __filename = fileURLToPath(import.meta.url); +const __dirname = dirname(__filename); + +const electronDir = join(__dirname, '..', 'src-electron'); + +console.log('Installing src-electron dependencies...'); +execSync('npm ci', { cwd: electronDir, stdio: 'inherit' }); +console.log('src-electron dependencies installed successfully!'); diff --git a/src-electron/main-app-ipc.js b/src-electron/main-app-ipc.js new file mode 100644 index 00000000..f85e5476 --- /dev/null +++ b/src-electron/main-app-ipc.js @@ -0,0 +1,142 @@ +const { app, ipcMain } = require('electron'); +const { spawn } = require('child_process'); +const readline = require('readline'); +const { productName } = require('./package.json'); + +let processInstanceId = 0; +// Map of instanceId -> { process, terminated } +const spawnedProcesses = new Map(); + +// In-memory key-value store shared across all windows (mirrors Tauri's put_item/get_all_items) +// Used for multi-window storage synchronization +const sharedStorageMap = new Map(); + +function waitForTrue(fn, timeout) { + return new Promise((resolve) => { + const startTime = Date.now(); + function check() { + if (fn()) { + resolve(true); + } else if (Date.now() - startTime > timeout) { + resolve(false); + } else { + setTimeout(check, 50); + } + } + check(); + }); +} + +async function terminateAllProcesses() { + for (const [, instance] of spawnedProcesses) { + if (!instance.terminated) { + try { + instance.process.kill(); + } catch (e) { + // Process may already be terminated + } + + await waitForTrue(() => instance.terminated, 1000); + } + } +} + +function registerAppIpcHandlers() { + // Spawn a child process and forward stdio to the calling renderer. + // Returns an instanceId so the renderer can target the correct process. + ipcMain.handle('spawn-process', async (event, command, args) => { + const instanceId = ++processInstanceId; + const sender = event.sender; + console.log(`Spawning: ${command} ${args.join(' ')} (instance ${instanceId})`); + + const childProcess = spawn(command, args, { + stdio: ['pipe', 'pipe', 'pipe'] + }); + + const instance = { process: childProcess, terminated: false }; + spawnedProcesses.set(instanceId, instance); + + const rl = readline.createInterface({ + input: childProcess.stdout, + crlfDelay: Infinity + }); + + rl.on('line', (line) => { + if (!sender.isDestroyed()) { + sender.send('process-stdout', instanceId, line); + } + }); + + childProcess.stderr.on('data', (data) => { + if (!sender.isDestroyed()) { + sender.send('process-stderr', instanceId, data.toString()); + } + }); + + childProcess.on('close', (code, signal) => { + instance.terminated = true; + console.log(`Process (instance ${instanceId}) exited with code ${code} and signal ${signal}`); + if (!sender.isDestroyed()) { + sender.send('process-close', instanceId, { code, signal }); + } + }); + + childProcess.on('error', (err) => { + console.error(`Failed to start process (instance ${instanceId}):`, err); + }); + + return instanceId; + }); + + // Write data to a specific spawned process stdin + ipcMain.handle('write-to-process', (event, instanceId, data) => { + const instance = spawnedProcesses.get(instanceId); + if (instance && !instance.terminated) { + instance.process.stdin.write(data); + } + }); + + ipcMain.handle('quit-app', (event, exitCode) => { + console.log('Quit requested with exit code:', exitCode); + // This will be handled by the main module's gracefulShutdown + app.emit('quit-requested', exitCode); + }); + + ipcMain.on('console-log', (event, message) => { + console.log('Renderer:', message); + }); + + // CLI args (mirrors Tauri's cli.getMatches for --quit-when-done / -q) + ipcMain.handle('get-cli-args', () => { + return process.argv; + }); + + // App path (repo root when running from source) + ipcMain.handle('get-app-path', () => { + return app.getAppPath(); + }); + + // App name from package.json + ipcMain.handle('get-app-name', () => { + return productName; + }); + + // Set zoom factor on the webview (mirrors Tauri's zoom_window) + ipcMain.handle('zoom-window', (event, scaleFactor) => { + event.sender.setZoomFactor(scaleFactor); + }); + + // In-memory storage for multi-window sync (mirrors Tauri's put_item/get_all_items) + ipcMain.handle('put-item', (event, key, value) => { + sharedStorageMap.set(key, value); + }); + + ipcMain.handle('get-all-items', () => { + return Object.fromEntries(sharedStorageMap); + }); +} + +module.exports = { + registerAppIpcHandlers, + terminateAllProcesses +}; diff --git a/src-electron/main-fs-ipc.js b/src-electron/main-fs-ipc.js new file mode 100644 index 00000000..d2eeb6f2 --- /dev/null +++ b/src-electron/main-fs-ipc.js @@ -0,0 +1,128 @@ +const { ipcMain, dialog, BrowserWindow } = require('electron'); +const path = require('path'); +const fsp = require('fs/promises'); +const os = require('os'); +const { identifier: APP_IDENTIFIER } = require('./package.json'); + +// Electron IPC only preserves Error.message when errors cross the IPC boundary (see +// https://github.com/electron/electron/issues/24427). To preserve error.code for FS +// operations, we catch errors and return them as plain objects {error: {code, message}}. +// The preload layer unwraps these back into proper Error objects. +function fsResult(promise) { + return promise.catch(err => { + return { __fsError: true, code: err.code, message: err.message }; + }); +} + +/** + * Returns the app's local data directory path with trailing separator. + * Matches Tauri's appLocalDataDir which uses the bundle identifier. + * - Linux: ~/.local/share/{APP_IDENTIFIER}/ + * - macOS: ~/Library/Application Support/{APP_IDENTIFIER}/ + * - Windows: %LOCALAPPDATA%/{APP_IDENTIFIER}/ + */ +function getAppDataDir() { + const home = os.homedir(); + let appDataDir; + switch (process.platform) { + case 'darwin': + appDataDir = path.join(home, 'Library', 'Application Support', APP_IDENTIFIER); + break; + case 'win32': + appDataDir = path.join(process.env.LOCALAPPDATA || path.join(home, 'AppData', 'Local'), APP_IDENTIFIER); + break; + default: + appDataDir = path.join(process.env.XDG_DATA_HOME || path.join(home, '.local', 'share'), APP_IDENTIFIER); + } + return appDataDir + path.sep; +} + +function registerFsIpcHandlers() { + // Directory APIs + ipcMain.handle('get-documents-dir', () => { + // Match Tauri's documentDir which ends with a trailing slash + return path.join(os.homedir(), 'Documents') + path.sep; + }); + + ipcMain.handle('get-home-dir', () => { + // Match Tauri's homeDir which ends with a trailing slash + const home = os.homedir(); + return home.endsWith(path.sep) ? home : home + path.sep; + }); + + ipcMain.handle('get-temp-dir', () => { + return os.tmpdir(); + }); + + ipcMain.handle('get-app-data-dir', () => getAppDataDir()); + + // Get Windows drive letters (returns null on non-Windows platforms) + ipcMain.handle('get-windows-drives', async () => { + if (process.platform !== 'win32') { + return null; + } + // On Windows, check which drive letters exist by testing A-Z + const drives = []; + for (let i = 65; i <= 90; i++) { // A-Z + const letter = String.fromCharCode(i); + const drivePath = `${letter}:\\`; + try { + await fsp.access(drivePath); + drives.push(letter); + } catch { + // Drive doesn't exist + } + } + return drives.length > 0 ? drives : null; + }); + + // Dialogs + ipcMain.handle('show-open-dialog', async (event, options) => { + const win = BrowserWindow.fromWebContents(event.sender); + const result = await dialog.showOpenDialog(win, options); + return result.filePaths; + }); + + ipcMain.handle('show-save-dialog', async (event, options) => { + const win = BrowserWindow.fromWebContents(event.sender); + const result = await dialog.showSaveDialog(win, options); + return result.filePath; + }); + + // FS operations + ipcMain.handle('fs-readdir', async (event, dirPath) => { + return fsResult( + fsp.readdir(dirPath, { withFileTypes: true }) + .then(entries => entries.map(e => ({ name: e.name, isDirectory: e.isDirectory() }))) + ); + }); + + ipcMain.handle('fs-stat', async (event, filePath) => { + return fsResult( + fsp.stat(filePath).then(stats => ({ + isFile: stats.isFile(), + isDirectory: stats.isDirectory(), + isSymbolicLink: stats.isSymbolicLink(), + size: stats.size, + mode: stats.mode, + ctimeMs: stats.ctimeMs, + atimeMs: stats.atimeMs, + mtimeMs: stats.mtimeMs, + nlink: stats.nlink, + dev: stats.dev + })) + ); + }); + + ipcMain.handle('fs-mkdir', (event, dirPath, options) => fsResult(fsp.mkdir(dirPath, options))); + ipcMain.handle('fs-unlink', (event, filePath) => fsResult(fsp.unlink(filePath))); + ipcMain.handle('fs-rmdir', (event, dirPath, options) => fsResult(fsp.rm(dirPath, options))); + ipcMain.handle('fs-rename', (event, oldPath, newPath) => fsResult(fsp.rename(oldPath, newPath))); + ipcMain.handle('fs-read-file', (event, filePath) => fsResult(fsp.readFile(filePath))); + ipcMain.handle('fs-write-file', (event, filePath, data) => fsResult(fsp.writeFile(filePath, Buffer.from(data)))); +} + +module.exports = { + registerFsIpcHandlers, + getAppDataDir +}; diff --git a/src-electron/main.js b/src-electron/main.js new file mode 100644 index 00000000..161f5737 --- /dev/null +++ b/src-electron/main.js @@ -0,0 +1,89 @@ +const { app, BrowserWindow, protocol } = require('electron'); +const path = require('path'); + +const { registerAppIpcHandlers, terminateAllProcesses } = require('./main-app-ipc'); +const { registerFsIpcHandlers, getAppDataDir } = require('./main-fs-ipc'); + +let mainWindow; + +async function createWindow() { + mainWindow = new BrowserWindow({ + width: 1200, + height: 800, + webPreferences: { + preload: path.join(__dirname, 'preload.js'), + contextIsolation: true, + nodeIntegration: false + }, + icon: path.join(__dirname, '..', 'src-tauri', 'icons', 'icon.png') + }); + + // Load the test page from the http-server + mainWindow.loadURL('http://localhost:8000/src/'); + + // Open DevTools for debugging + mainWindow.webContents.openDevTools(); + + mainWindow.on('closed', () => { + mainWindow = null; + }); +} + +async function gracefulShutdown(exitCode = 0) { + console.log('Initiating graceful shutdown...'); + await terminateAllProcesses(); + app.exit(exitCode); +} + +// Register all IPC handlers +registerAppIpcHandlers(); +registerFsIpcHandlers(); + +// Handle quit request from renderer +app.on('quit-requested', (exitCode) => { + gracefulShutdown(exitCode); +}); + +app.whenReady().then(async () => { + // Register asset:// protocol for serving local files from appLocalData/assets/ + const appDataDir = getAppDataDir(); + const assetsDir = path.join(appDataDir, 'assets'); + + protocol.registerFileProtocol('asset', (request, callback) => { + try { + const url = new URL(request.url); + // Decode the path from URL encoding + const requestedPath = decodeURIComponent(url.pathname.substring(1)); // Remove leading / + const normalizedRequested = path.normalize(requestedPath); + const normalizedAssetsDir = path.normalize(assetsDir); + + // Security: Ensure path is under assets directory (prevent directory traversal) + if (!normalizedRequested.startsWith(normalizedAssetsDir)) { + console.error('Asset access denied - path not under assets dir:', requestedPath); + callback({ error: -10 }); // net::ERR_ACCESS_DENIED + return; + } + + callback({ path: normalizedRequested }); + } catch (err) { + console.error('Asset protocol error:', err); + callback({ error: -2 }); // net::ERR_FAILED + } + }); + + await createWindow(); +}); + +app.on('window-all-closed', () => { + gracefulShutdown(0); +}); + +app.on('activate', () => { + if (BrowserWindow.getAllWindows().length === 0) { + createWindow(); + } +}); + +// Handle process termination signals +process.on('SIGINT', () => gracefulShutdown(0)); +process.on('SIGTERM', () => gracefulShutdown(0)); diff --git a/src-electron/package-lock.json b/src-electron/package-lock.json new file mode 100644 index 00000000..e8f4879d --- /dev/null +++ b/src-electron/package-lock.json @@ -0,0 +1,801 @@ +{ + "name": "phoenix-fs-electron-shell", + "version": "1.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "phoenix-fs-electron-shell", + "version": "1.0.0", + "devDependencies": { + "electron": "^40.0.0" + } + }, + "node_modules/@electron/get": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/@electron/get/-/get-2.0.3.tgz", + "integrity": "sha512-Qkzpg2s9GnVV2I2BjRksUi43U5e6+zaQMcjoJy0C+C5oxaKl+fmckGDQFtRpZpZV0NQekuZZ+tGz7EA9TVnQtQ==", + "dev": true, + "dependencies": { + "debug": "^4.1.1", + "env-paths": "^2.2.0", + "fs-extra": "^8.1.0", + "got": "^11.8.5", + "progress": "^2.0.3", + "semver": "^6.2.0", + "sumchecker": "^3.0.1" + }, + "engines": { + "node": ">=12" + }, + "optionalDependencies": { + "global-agent": "^3.0.0" + } + }, + "node_modules/@sindresorhus/is": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-4.6.0.tgz", + "integrity": "sha512-t09vSN3MdfsyCHoFcTRCH/iUtG7OJ0CsjzB8cjAmKc/va/kIgeDI/TxsigdncE/4be734m0cvIYwNaV4i2XqAw==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sindresorhus/is?sponsor=1" + } + }, + "node_modules/@szmarczak/http-timer": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/@szmarczak/http-timer/-/http-timer-4.0.6.tgz", + "integrity": "sha512-4BAffykYOgO+5nzBWYwE3W90sBgLJoUPRWWcL8wlyiM8IB8ipJz3UMJ9KXQd1RKQXpKp8Tutn80HZtWsu2u76w==", + "dev": true, + "dependencies": { + "defer-to-connect": "^2.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@types/cacheable-request": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/@types/cacheable-request/-/cacheable-request-6.0.3.tgz", + "integrity": "sha512-IQ3EbTzGxIigb1I3qPZc1rWJnH0BmSKv5QYTalEwweFvyBDLSAe24zP0le/hyi7ecGfZVlIVAg4BZqb8WBwKqw==", + "dev": true, + "dependencies": { + "@types/http-cache-semantics": "*", + "@types/keyv": "^3.1.4", + "@types/node": "*", + "@types/responselike": "^1.0.0" + } + }, + "node_modules/@types/http-cache-semantics": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/@types/http-cache-semantics/-/http-cache-semantics-4.0.4.tgz", + "integrity": "sha512-1m0bIFVc7eJWyve9S0RnuRgcQqF/Xd5QsUZAZeQFr1Q3/p9JWoQQEqmVy+DPTNpGXwhgIetAoYF8JSc33q29QA==", + "dev": true + }, + "node_modules/@types/keyv": { + "version": "3.1.4", + "resolved": "https://registry.npmjs.org/@types/keyv/-/keyv-3.1.4.tgz", + "integrity": "sha512-BQ5aZNSCpj7D6K2ksrRCTmKRLEpnPvWDiLPfoGyhZ++8YtiK9d/3DBKPJgry359X/P1PfruyYwvnvwFjuEiEIg==", + "dev": true, + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/node": { + "version": "24.10.9", + "resolved": "https://registry.npmjs.org/@types/node/-/node-24.10.9.tgz", + "integrity": "sha512-ne4A0IpG3+2ETuREInjPNhUGis1SFjv1d5asp8MzEAGtOZeTeHVDOYqOgqfhvseqg/iXty2hjBf1zAOb7RNiNw==", + "dev": true, + "dependencies": { + "undici-types": "~7.16.0" + } + }, + "node_modules/@types/responselike": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@types/responselike/-/responselike-1.0.3.tgz", + "integrity": "sha512-H/+L+UkTV33uf49PH5pCAUBVPNj2nDBXTN+qS1dOwyyg24l3CcicicCA7ca+HMvJBZcFgl5r8e+RR6elsb4Lyw==", + "dev": true, + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/yauzl": { + "version": "2.10.3", + "resolved": "https://registry.npmjs.org/@types/yauzl/-/yauzl-2.10.3.tgz", + "integrity": "sha512-oJoftv0LSuaDZE3Le4DbKX+KS9G36NzOeSap90UIK0yMA/NhKJhqlSGtNDORNRaIbQfzjXDrQa0ytJ6mNRGz/Q==", + "dev": true, + "optional": true, + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/boolean": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/boolean/-/boolean-3.2.0.tgz", + "integrity": "sha512-d0II/GO9uf9lfUHH2BQsjxzRJZBdsjgsBiW4BvhWk/3qoKwQFjIDVN19PfX8F2D/r9PCMTtLWjYVCFrpeYUzsw==", + "deprecated": "Package no longer supported. Contact Support at https://www.npmjs.com/support for more info.", + "dev": true, + "optional": true + }, + "node_modules/buffer-crc32": { + "version": "0.2.13", + "resolved": "https://registry.npmjs.org/buffer-crc32/-/buffer-crc32-0.2.13.tgz", + "integrity": "sha512-VO9Ht/+p3SN7SKWqcrgEzjGbRSJYTx+Q1pTQC0wrWqHx0vpJraQ6GtHx8tvcg1rlK1byhU5gccxgOgj7B0TDkQ==", + "dev": true, + "engines": { + "node": "*" + } + }, + "node_modules/cacheable-lookup": { + "version": "5.0.4", + "resolved": "https://registry.npmjs.org/cacheable-lookup/-/cacheable-lookup-5.0.4.tgz", + "integrity": "sha512-2/kNscPhpcxrOigMZzbiWF7dz8ilhb/nIHU3EyZiXWXpeq/au8qJ8VhdftMkty3n7Gj6HIGalQG8oiBNB3AJgA==", + "dev": true, + "engines": { + "node": ">=10.6.0" + } + }, + "node_modules/cacheable-request": { + "version": "7.0.4", + "resolved": "https://registry.npmjs.org/cacheable-request/-/cacheable-request-7.0.4.tgz", + "integrity": "sha512-v+p6ongsrp0yTGbJXjgxPow2+DL93DASP4kXCDKb8/bwRtt9OEF3whggkkDkGNzgcWy2XaF4a8nZglC7uElscg==", + "dev": true, + "dependencies": { + "clone-response": "^1.0.2", + "get-stream": "^5.1.0", + "http-cache-semantics": "^4.0.0", + "keyv": "^4.0.0", + "lowercase-keys": "^2.0.0", + "normalize-url": "^6.0.1", + "responselike": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/clone-response": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/clone-response/-/clone-response-1.0.3.tgz", + "integrity": "sha512-ROoL94jJH2dUVML2Y/5PEDNaSHgeOdSDicUyS7izcF63G6sTc/FTjLub4b8Il9S8S0beOfYt0TaA5qvFK+w0wA==", + "dev": true, + "dependencies": { + "mimic-response": "^1.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "dev": true, + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/decompress-response": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-6.0.0.tgz", + "integrity": "sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ==", + "dev": true, + "dependencies": { + "mimic-response": "^3.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/decompress-response/node_modules/mimic-response": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-3.1.0.tgz", + "integrity": "sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/defer-to-connect": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/defer-to-connect/-/defer-to-connect-2.0.1.tgz", + "integrity": "sha512-4tvttepXG1VaYGrRibk5EwJd1t4udunSOVMdLSAL6mId1ix438oPwPZMALY41FCijukO1L0twNcGsdzS7dHgDg==", + "dev": true, + "engines": { + "node": ">=10" + } + }, + "node_modules/define-data-property": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz", + "integrity": "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==", + "dev": true, + "optional": true, + "dependencies": { + "es-define-property": "^1.0.0", + "es-errors": "^1.3.0", + "gopd": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/define-properties": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.2.1.tgz", + "integrity": "sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg==", + "dev": true, + "optional": true, + "dependencies": { + "define-data-property": "^1.0.1", + "has-property-descriptors": "^1.0.0", + "object-keys": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/detect-node": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/detect-node/-/detect-node-2.1.0.tgz", + "integrity": "sha512-T0NIuQpnTvFDATNuHN5roPwSBG83rFsuO+MXXH9/3N1eFbn4wcPjttvjMLEPWJ0RGUYgQE7cGgS3tNxbqCGM7g==", + "dev": true, + "optional": true + }, + "node_modules/electron": { + "version": "40.0.0", + "resolved": "https://registry.npmjs.org/electron/-/electron-40.0.0.tgz", + "integrity": "sha512-UyBy5yJ0/wm4gNugCtNPjvddjAknMTuXR2aCHioXicH7aKRKGDBPp4xqTEi/doVcB3R+MN3wfU9o8d/9pwgK2A==", + "dev": true, + "hasInstallScript": true, + "dependencies": { + "@electron/get": "^2.0.0", + "@types/node": "^24.9.0", + "extract-zip": "^2.0.1" + }, + "bin": { + "electron": "cli.js" + }, + "engines": { + "node": ">= 12.20.55" + } + }, + "node_modules/end-of-stream": { + "version": "1.4.5", + "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.5.tgz", + "integrity": "sha512-ooEGc6HP26xXq/N+GCGOT0JKCLDGrq2bQUZrQ7gyrJiZANJ/8YDTxTpQBXGMn+WbIQXNVpyWymm7KYVICQnyOg==", + "dev": true, + "dependencies": { + "once": "^1.4.0" + } + }, + "node_modules/env-paths": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/env-paths/-/env-paths-2.2.1.tgz", + "integrity": "sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/es-define-property": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", + "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", + "dev": true, + "optional": true, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-errors": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "dev": true, + "optional": true, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es6-error": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/es6-error/-/es6-error-4.1.1.tgz", + "integrity": "sha512-Um/+FxMr9CISWh0bi5Zv0iOD+4cFh5qLeks1qhAopKVAJw3drgKbKySikp7wGhDL0HPeaja0P5ULZrxLkniUVg==", + "dev": true, + "optional": true + }, + "node_modules/escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "dev": true, + "optional": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/extract-zip": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extract-zip/-/extract-zip-2.0.1.tgz", + "integrity": "sha512-GDhU9ntwuKyGXdZBUgTIe+vXnWj0fppUEtMDL0+idd5Sta8TGpHssn/eusA9mrPr9qNDym6SxAYZjNvCn/9RBg==", + "dev": true, + "dependencies": { + "debug": "^4.1.1", + "get-stream": "^5.1.0", + "yauzl": "^2.10.0" + }, + "bin": { + "extract-zip": "cli.js" + }, + "engines": { + "node": ">= 10.17.0" + }, + "optionalDependencies": { + "@types/yauzl": "^2.9.1" + } + }, + "node_modules/fd-slicer": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/fd-slicer/-/fd-slicer-1.1.0.tgz", + "integrity": "sha512-cE1qsB/VwyQozZ+q1dGxR8LBYNZeofhEdUNGSMbQD3Gw2lAzX9Zb3uIU6Ebc/Fmyjo9AWWfnn0AUCHqtevs/8g==", + "dev": true, + "dependencies": { + "pend": "~1.2.0" + } + }, + "node_modules/fs-extra": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-8.1.0.tgz", + "integrity": "sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g==", + "dev": true, + "dependencies": { + "graceful-fs": "^4.2.0", + "jsonfile": "^4.0.0", + "universalify": "^0.1.0" + }, + "engines": { + "node": ">=6 <7 || >=8" + } + }, + "node_modules/get-stream": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-5.2.0.tgz", + "integrity": "sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA==", + "dev": true, + "dependencies": { + "pump": "^3.0.0" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/global-agent": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/global-agent/-/global-agent-3.0.0.tgz", + "integrity": "sha512-PT6XReJ+D07JvGoxQMkT6qji/jVNfX/h364XHZOWeRzy64sSFr+xJ5OX7LI3b4MPQzdL4H8Y8M0xzPpsVMwA8Q==", + "dev": true, + "optional": true, + "dependencies": { + "boolean": "^3.0.1", + "es6-error": "^4.1.1", + "matcher": "^3.0.0", + "roarr": "^2.15.3", + "semver": "^7.3.2", + "serialize-error": "^7.0.1" + }, + "engines": { + "node": ">=10.0" + } + }, + "node_modules/global-agent/node_modules/semver": { + "version": "7.7.3", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.3.tgz", + "integrity": "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==", + "dev": true, + "optional": true, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/globalthis": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/globalthis/-/globalthis-1.0.4.tgz", + "integrity": "sha512-DpLKbNU4WylpxJykQujfCcwYWiV/Jhm50Goo0wrVILAv5jOr9d+H+UR3PhSCD2rCCEIg0uc+G+muBTwD54JhDQ==", + "dev": true, + "optional": true, + "dependencies": { + "define-properties": "^1.2.1", + "gopd": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/gopd": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", + "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", + "dev": true, + "optional": true, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/got": { + "version": "11.8.6", + "resolved": "https://registry.npmjs.org/got/-/got-11.8.6.tgz", + "integrity": "sha512-6tfZ91bOr7bOXnK7PRDCGBLa1H4U080YHNaAQ2KsMGlLEzRbk44nsZF2E1IeRc3vtJHPVbKCYgdFbaGO2ljd8g==", + "dev": true, + "dependencies": { + "@sindresorhus/is": "^4.0.0", + "@szmarczak/http-timer": "^4.0.5", + "@types/cacheable-request": "^6.0.1", + "@types/responselike": "^1.0.0", + "cacheable-lookup": "^5.0.3", + "cacheable-request": "^7.0.2", + "decompress-response": "^6.0.0", + "http2-wrapper": "^1.0.0-beta.5.2", + "lowercase-keys": "^2.0.0", + "p-cancelable": "^2.0.0", + "responselike": "^2.0.0" + }, + "engines": { + "node": ">=10.19.0" + }, + "funding": { + "url": "https://github.com/sindresorhus/got?sponsor=1" + } + }, + "node_modules/graceful-fs": { + "version": "4.2.11", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", + "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", + "dev": true + }, + "node_modules/has-property-descriptors": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz", + "integrity": "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==", + "dev": true, + "optional": true, + "dependencies": { + "es-define-property": "^1.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/http-cache-semantics": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/http-cache-semantics/-/http-cache-semantics-4.2.0.tgz", + "integrity": "sha512-dTxcvPXqPvXBQpq5dUr6mEMJX4oIEFv6bwom3FDwKRDsuIjjJGANqhBuoAn9c1RQJIdAKav33ED65E2ys+87QQ==", + "dev": true + }, + "node_modules/http2-wrapper": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/http2-wrapper/-/http2-wrapper-1.0.3.tgz", + "integrity": "sha512-V+23sDMr12Wnz7iTcDeJr3O6AIxlnvT/bmaAAAP/Xda35C90p9599p0F1eHR/N1KILWSoWVAiOMFjBBXaXSMxg==", + "dev": true, + "dependencies": { + "quick-lru": "^5.1.1", + "resolve-alpn": "^1.0.0" + }, + "engines": { + "node": ">=10.19.0" + } + }, + "node_modules/json-buffer": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", + "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==", + "dev": true + }, + "node_modules/json-stringify-safe": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz", + "integrity": "sha512-ZClg6AaYvamvYEE82d3Iyd3vSSIjQ+odgjaTzRuO3s7toCdFKczob2i0zCh7JE8kWn17yvAWhUVxvqGwUalsRA==", + "dev": true, + "optional": true + }, + "node_modules/jsonfile": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-4.0.0.tgz", + "integrity": "sha512-m6F1R3z8jjlf2imQHS2Qez5sjKWQzbuuhuJ/FKYFRZvPE3PuHcSMVZzfsLhGVOkfd20obL5SWEBew5ShlquNxg==", + "dev": true, + "optionalDependencies": { + "graceful-fs": "^4.1.6" + } + }, + "node_modules/keyv": { + "version": "4.5.4", + "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", + "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==", + "dev": true, + "dependencies": { + "json-buffer": "3.0.1" + } + }, + "node_modules/lowercase-keys": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-2.0.0.tgz", + "integrity": "sha512-tqNXrS78oMOE73NMxK4EMLQsQowWf8jKooH9g7xPavRT706R6bkQJ6DY2Te7QukaZsulxa30wQ7bk0pm4XiHmA==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/matcher": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/matcher/-/matcher-3.0.0.tgz", + "integrity": "sha512-OkeDaAZ/bQCxeFAozM55PKcKU0yJMPGifLwV4Qgjitu+5MoAfSQN4lsLJeXZ1b8w0x+/Emda6MZgXS1jvsapng==", + "dev": true, + "optional": true, + "dependencies": { + "escape-string-regexp": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/mimic-response": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-1.0.1.tgz", + "integrity": "sha512-j5EctnkH7amfV/q5Hgmoal1g2QHFJRraOtmx0JpIqkxhBhI/lJSl1nMpQ45hVarwNETOoWEimndZ4QK0RHxuxQ==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true + }, + "node_modules/normalize-url": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/normalize-url/-/normalize-url-6.1.0.tgz", + "integrity": "sha512-DlL+XwOy3NxAQ8xuC0okPgK46iuVNAK01YN7RueYBqqFeGsBjV9XmCAzAdgt+667bCl5kPh9EqKKDwnaPG1I7A==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/object-keys": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", + "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==", + "dev": true, + "optional": true, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "dev": true, + "dependencies": { + "wrappy": "1" + } + }, + "node_modules/p-cancelable": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/p-cancelable/-/p-cancelable-2.1.1.tgz", + "integrity": "sha512-BZOr3nRQHOntUjTrH8+Lh54smKHoHyur8We1V8DSMVrl5A2malOOwuJRnKRDjSnkoeBh4at6BwEnb5I7Jl31wg==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/pend": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/pend/-/pend-1.2.0.tgz", + "integrity": "sha512-F3asv42UuXchdzt+xXqfW1OGlVBe+mxa2mqI0pg5yAHZPvFmY3Y6drSf/GQ1A86WgWEN9Kzh/WrgKa6iGcHXLg==", + "dev": true + }, + "node_modules/progress": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/progress/-/progress-2.0.3.tgz", + "integrity": "sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==", + "dev": true, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/pump": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.3.tgz", + "integrity": "sha512-todwxLMY7/heScKmntwQG8CXVkWUOdYxIvY2s0VWAAMh/nd8SoYiRaKjlr7+iCs984f2P8zvrfWcDDYVb73NfA==", + "dev": true, + "dependencies": { + "end-of-stream": "^1.1.0", + "once": "^1.3.1" + } + }, + "node_modules/quick-lru": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/quick-lru/-/quick-lru-5.1.1.tgz", + "integrity": "sha512-WuyALRjWPDGtt/wzJiadO5AXY+8hZ80hVpe6MyivgraREW751X3SbhRvG3eLKOYN+8VEvqLcf3wdnt44Z4S4SA==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/resolve-alpn": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/resolve-alpn/-/resolve-alpn-1.2.1.tgz", + "integrity": "sha512-0a1F4l73/ZFZOakJnQ3FvkJ2+gSTQWz/r2KE5OdDY0TxPm5h4GkqkWWfM47T7HsbnOtcJVEF4epCVy6u7Q3K+g==", + "dev": true + }, + "node_modules/responselike": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/responselike/-/responselike-2.0.1.tgz", + "integrity": "sha512-4gl03wn3hj1HP3yzgdI7d3lCkF95F21Pz4BPGvKHinyQzALR5CapwC8yIi0Rh58DEMQ/SguC03wFj2k0M/mHhw==", + "dev": true, + "dependencies": { + "lowercase-keys": "^2.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/roarr": { + "version": "2.15.4", + "resolved": "https://registry.npmjs.org/roarr/-/roarr-2.15.4.tgz", + "integrity": "sha512-CHhPh+UNHD2GTXNYhPWLnU8ONHdI+5DI+4EYIAOaiD63rHeYlZvyh8P+in5999TTSFgUYuKUAjzRI4mdh/p+2A==", + "dev": true, + "optional": true, + "dependencies": { + "boolean": "^3.0.1", + "detect-node": "^2.0.4", + "globalthis": "^1.0.1", + "json-stringify-safe": "^5.0.1", + "semver-compare": "^1.0.0", + "sprintf-js": "^1.1.2" + }, + "engines": { + "node": ">=8.0" + } + }, + "node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/semver-compare": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/semver-compare/-/semver-compare-1.0.0.tgz", + "integrity": "sha512-YM3/ITh2MJ5MtzaM429anh+x2jiLVjqILF4m4oyQB18W7Ggea7BfqdH/wGMK7dDiMghv/6WG7znWMwUDzJiXow==", + "dev": true, + "optional": true + }, + "node_modules/serialize-error": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/serialize-error/-/serialize-error-7.0.1.tgz", + "integrity": "sha512-8I8TjW5KMOKsZQTvoxjuSIa7foAwPWGOts+6o7sgjz41/qMD9VQHEDxi6PBvK2l0MXUmqZyNpUK+T2tQaaElvw==", + "dev": true, + "optional": true, + "dependencies": { + "type-fest": "^0.13.1" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/sprintf-js": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.1.3.tgz", + "integrity": "sha512-Oo+0REFV59/rz3gfJNKQiBlwfHaSESl1pcGyABQsnnIfWOFt6JNj5gCog2U6MLZ//IGYD+nA8nI+mTShREReaA==", + "dev": true, + "optional": true + }, + "node_modules/sumchecker": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/sumchecker/-/sumchecker-3.0.1.tgz", + "integrity": "sha512-MvjXzkz/BOfyVDkG0oFOtBxHX2u3gKbMHIF/dXblZsgD3BWOFLmHovIpZY7BykJdAjcqRCBi1WYBNdEC9yI7vg==", + "dev": true, + "dependencies": { + "debug": "^4.1.0" + }, + "engines": { + "node": ">= 8.0" + } + }, + "node_modules/type-fest": { + "version": "0.13.1", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.13.1.tgz", + "integrity": "sha512-34R7HTnG0XIJcBSn5XhDd7nNFPRcXYRZrBB2O2jdKqYODldSzBAqzsWoZYYvduky73toYS/ESqxPvkDf/F0XMg==", + "dev": true, + "optional": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/undici-types": { + "version": "7.16.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.16.0.tgz", + "integrity": "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw==", + "dev": true + }, + "node_modules/universalify": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.2.tgz", + "integrity": "sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==", + "dev": true, + "engines": { + "node": ">= 4.0.0" + } + }, + "node_modules/wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", + "dev": true + }, + "node_modules/yauzl": { + "version": "2.10.0", + "resolved": "https://registry.npmjs.org/yauzl/-/yauzl-2.10.0.tgz", + "integrity": "sha512-p4a9I6X6nu6IhoGmBqAcbJy1mlC4j27vEPZX9F4L4/vZT3Lyq1VkFHw/V/PUcB9Buo+DG3iHkT0x3Qya58zc3g==", + "dev": true, + "dependencies": { + "buffer-crc32": "~0.2.3", + "fd-slicer": "~1.1.0" + } + } + } +} diff --git a/src-electron/package.json b/src-electron/package.json new file mode 100644 index 00000000..4ecda03d --- /dev/null +++ b/src-electron/package.json @@ -0,0 +1,11 @@ +{ + "name": "phoenix-code-electron-shell", + "identifier": "io.phcode.dev-electon-migration", + "version": "5.0.5", + "productName": "Phoenix Code Experimental Build", + "description": "Phoenix Code Experimental Build", + "main": "main.js", + "devDependencies": { + "electron": "^40.0.0" + } +} diff --git a/src-electron/preload.js b/src-electron/preload.js new file mode 100644 index 00000000..9f9e29ea --- /dev/null +++ b/src-electron/preload.js @@ -0,0 +1,73 @@ +const { contextBridge, ipcRenderer } = require('electron'); + +contextBridge.exposeInMainWorld('electronAppAPI', { + // App info + getAppName: () => ipcRenderer.invoke('get-app-name'), + getAppPath: () => ipcRenderer.invoke('get-app-path'), + + // Process lifecycle + spawnProcess: (command, args) => ipcRenderer.invoke('spawn-process', command, args), + writeToProcess: (instanceId, data) => ipcRenderer.invoke('write-to-process', instanceId, data), + onProcessStdout: (callback) => ipcRenderer.on('process-stdout', (_event, instanceId, line) => callback(instanceId, line)), + onProcessStderr: (callback) => ipcRenderer.on('process-stderr', (_event, instanceId, line) => callback(instanceId, line)), + onProcessClose: (callback) => ipcRenderer.on('process-close', (_event, instanceId, data) => callback(instanceId, data)), + + // Quit the app with an exit code (for CI) + quitApp: (exitCode) => ipcRenderer.invoke('quit-app', exitCode), + + // Log to main process console + consoleLog: (message) => ipcRenderer.send('console-log', message), + + // Flag to identify Electron environment + isElectron: true, + + // CLI + getCliArgs: () => ipcRenderer.invoke('get-cli-args') +}); + +// the electronFSAPI is the fn that you need to copy to your election app impl for the fs to work. +contextBridge.exposeInMainWorld('electronFSAPI', { + // Path utilities + path: { + sep: process.platform === 'win32' ? '\\' : '/' + }, + documentDir: () => ipcRenderer.invoke('get-documents-dir'), + homeDir: () => ipcRenderer.invoke('get-home-dir'), + tempDir: () => ipcRenderer.invoke('get-temp-dir'), + appLocalDataDir: () => ipcRenderer.invoke('get-app-data-dir'), + getWindowsDrives: () => ipcRenderer.invoke('get-windows-drives'), + showOpenDialog: (options) => ipcRenderer.invoke('show-open-dialog', options), + showSaveDialog: (options) => ipcRenderer.invoke('show-save-dialog', options), + + // FS operations — results may be {__fsError, code, message} on failure since + // Electron IPC strips Error.code. The renderer (fslib_electron.js) handles unwrapping. + fsReaddir: (path) => ipcRenderer.invoke('fs-readdir', path), + fsStat: (path) => ipcRenderer.invoke('fs-stat', path), + fsMkdir: (path, options) => ipcRenderer.invoke('fs-mkdir', path, options), + fsUnlink: (path) => ipcRenderer.invoke('fs-unlink', path), + fsRmdir: (path, options) => ipcRenderer.invoke('fs-rmdir', path, options), + fsRename: (oldPath, newPath) => ipcRenderer.invoke('fs-rename', oldPath, newPath), + fsReadFile: (path) => ipcRenderer.invoke('fs-read-file', path), + fsWriteFile: (path, data) => ipcRenderer.invoke('fs-write-file', path, data) +}); + +// Phoenix-specific Electron APIs (not part of the copy-paste FS library) +contextBridge.exposeInMainWorld('electronAPI', { + // Asset URL conversion - converts platform path to asset:// URL + // Only allows paths under appLocalData/assets/ directory for security + // The actual path validation happens in the protocol handler in main.js + // Returns null if platformPath is falsy + convertToAssetURL: (platformPath) => { + if (!platformPath) return null; + // Normalize path separators to forward slashes for URL + const normalizedPath = platformPath.replace(/\\/g, '/'); + return `asset://localhost/${encodeURIComponent(normalizedPath)}`; + }, + + // Set zoom factor on the webview (mirrors Tauri's zoom_window) + zoomWindow: (scaleFactor) => ipcRenderer.invoke('zoom-window', scaleFactor), + + // In-memory storage for multi-window sync (mirrors Tauri's put_item/get_all_items) + putItem: (key, value) => ipcRenderer.invoke('put-item', key, value), + getAllItems: () => ipcRenderer.invoke('get-all-items') +}); diff --git a/src-tauri/Cargo.lock b/src-tauri/Cargo.lock index 948d6931..0960b6f6 100644 --- a/src-tauri/Cargo.lock +++ b/src-tauri/Cargo.lock @@ -3407,7 +3407,7 @@ dependencies = [ [[package]] name = "phoenix-code-ide" -version = "4.1.1" +version = "5.0.5" dependencies = [ "aes-gcm", "backtrace", diff --git a/src-tauri/tauri.conf.json b/src-tauri/tauri.conf.json index f3a08375..00a481cf 100644 --- a/src-tauri/tauri.conf.json +++ b/src-tauri/tauri.conf.json @@ -2,7 +2,6 @@ "$schema": "../node_modules/@tauri-apps/cli/schema.json", "build": { "beforeBuildCommand": "", - "beforeDevCommand": "npm run _watch_src-node", "devPath": "http://localhost:8000/src/", "distDir": "../src/", "withGlobalTauri": true @@ -484,4 +483,4 @@ } ] } -} \ No newline at end of file +}