diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md index 08a9e4c..ffd6a92 100644 --- a/.github/copilot-instructions.md +++ b/.github/copilot-instructions.md @@ -14,7 +14,7 @@ the frontend with a C++ backend. It is intentionally minimal: executables stay around 2 MB and no Electron/Chromium runtimes are required. - **Repository**: https://github.com/spur27/RenWeb-Engine -- **Version**: 0.0.7 +- **Version**: 0.1.0 - **License**: Boost Software License 1.0 (BSL-1.0) - **Author**: spur27 / Spur27 - **App ID**: `io.github.spur27.renweb-engine` @@ -183,7 +183,7 @@ All major components have interface base classes (prefix `I`): "description": "Base RenWeb engine.", "license": "BSL", "title": "RenWeb", - "version": "0.0.7", + "version": "0.1.0", "repository": "https://github.com/spur27/RenWeb-Engine", "category": "Utility", "copyright": "Copyright © 2025 Spur27", @@ -268,7 +268,7 @@ When modifying makefile defaults, always use the `ifndef VAR` / `VAR := value` p build/---[.exe] ``` -Example: `build/renweb-0.0.7-linux-x86_64` +Example: `build/renweb-0.1.0-linux-x86_64` Name and version are extracted from `./info.json` using `sed` at build time. diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..b7a1cdd --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,26 @@ +## Changelog + +## 0.1.0 - 2026-04-25 +- Bumped engine version to 0.1.0 across runtime metadata, site metadata, wiki templates, and maintenance references. +- Improved Windows plugin build reliability by adding Unix-like tool path handling for make-based plugin builds. +- Improved `rw doctor` on Windows by making npm detection work with `npm.cmd`, `npm`, and `cmd /c` fallback probing. +- Fixed Windows resource metadata mapping so file description uses app title and comments use app description. +- Added consistent cross-platform command naming support for packaging outputs. +- Reduced Windows build noise by replacing deprecated `getenv` usage with `_dupenv_s` for `ComSpec` lookup. +- Validated release build and Windows packaging flow for updated metadata and command behavior. +- Added chooseFiles function to FS for choosing files with full paths +- Added focus function to Window for forcing window focus +- Added isShown function to Window for checking window visibility + +## 0.0.7 - 2026-04-23 +- Added example plugin +- Added CLI tool (packager and project manager for applications, plugins, and engines) +- Added packages on JSR and NPM for both CLI and API +- Added an "Application" namespace of functions +- Added many more `window.renweb.` callbacks +- Fixed some issues with windows flashing geometry early +- Added installer generation graphics and more custom icon placeholders +- simplified info.json by removing unneeded server configuration options that didn't do anything +- Updated wiki pictures, contents, and filled in CLI page + plugin and CLI tool download sections +- Added new security distinction to toggle access to JS bindings +- Validation spreadsheet [here](https://drive.proton.me/urls/ZSCMMTTVPW#wQZdg1qyD0O3) \ No newline at end of file diff --git a/MAINTENANCE.md b/MAINTENANCE.md index d472c60..3905664 100644 --- a/MAINTENANCE.md +++ b/MAINTENANCE.md @@ -9,7 +9,7 @@ a checklist to ensure nothing is missed. To find all occurrences of any value quickly: ```sh -grep -r "0.0.7" --include="*.json" --include="*.html" --include="*.md" --include="*.js" \ +grep -r "0.1.0" --include="*.json" --include="*.html" --include="*.md" --include="*.js" \ --exclude-dir=external --exclude-dir=node_modules . ``` @@ -17,7 +17,7 @@ grep -r "0.0.7" --include="*.json" --include="*.html" --include="*.md" --include ## Engine Version -**Current value:** `0.0.7` +**Current value:** `0.1.0` The authoritative source is `info.json → "version"`. The makefile reads it at compile time via `sed` to construct the executable filename. All other locations @@ -30,17 +30,17 @@ must be kept in sync manually. | `index.html` | JSON-LD `"softwareVersion"` | | `wiki/home.html` | JSON-LD `"softwareVersion"` | | `wiki/template_jsons.js` | template JSON `"version"` field | -| `wiki/cli.html` | example output strings (`renweb-0.0.7-linux-x86_64`) | +| `wiki/cli.html` | example output strings (`renweb-0.1.0-linux-x86_64`) | | `.github/copilot-instructions.md` | `"Version"` field and example paths | -> `wiki/api.html` contains `"0.0.7"` in a code example comment — update as +> `wiki/api.html` contains `"0.1.0"` in a code example comment — update as > well for accuracy, but it has no functional effect. --- ## CLI Tool Version -**Current value:** `0.0.6` +**Current value:** `0.1.0` Versioned independently from the engine. The `package-lock.json` is auto-updated by `npm install`; do not edit it manually. @@ -54,7 +54,7 @@ auto-updated by `npm install`; do not edit it manually. ## JS API Version -**Current value:** `0.0.6` +**Current value:** `0.1.0` Three separate config files must stay in sync. `package-lock.json` at the root is auto-updated. diff --git a/cli/README.md b/cli/README.md index 9e51c23..221c340 100644 --- a/cli/README.md +++ b/cli/README.md @@ -42,7 +42,7 @@ npm install -g renweb-cli | `rw doctor` | Check environment and project health | | `rw package` | Package build output into distributable archives (.deb, .rpm, .zip, .tar.gz) | | `rw doc [pages...]` | Open RenWeb documentation pages in a browser | -| `rw fetch [verb]` | Download engine assets (executable, plugin headers, JS/TS API files) | +| `rw fetch [verb]` | Download engine assets (executable, plugin binary, JS/TS API files, or example archive) | | `rw docker ` | Manage the renweb-cli Docker image (build, rebuild, kill, delete) | ## Project Types (`rw create`) @@ -76,6 +76,9 @@ rw package -olinux -ax86_64 -edeb # Fetch the latest engine executable rw fetch executable +# Fetch the latest example archive (zip on Windows, tar.gz on macOS/Linux) +rw fetch example + # Check environment health rw doctor ``` diff --git a/cli/commands/build.js b/cli/commands/build.js index 02867d6..0cb745f 100644 --- a/cli/commands/build.js +++ b/cli/commands/build.js @@ -12,6 +12,26 @@ const { fetchPlugins } = require('../shared/fetchers'); const { ProjectState } = require('../project/project_state'); const ui = require('../shared/ui'); +function finishBuild(result, buildLabel) { + if (!result) { + ui.error(`Build failed (${buildLabel}) — no process result.`); + process.exit(1); + } + + if (result.error) { + ui.error(`Build failed (${buildLabel}) — ${result.error.message}`); + process.exit(1); + } + + if (result.status === 0) { + ui.ok(`Build complete (${buildLabel}).`); + process.exit(0); + } + + ui.error(`Build failed (${buildLabel}) with exit code ${result.status}.`); + process.exit(result.status ?? 1); +} + function ensureExecutable(projectRoot, buildDir, targetOs, targetArch) { if (findProjectExecutable(buildDir, targetOs, targetArch)) return true; @@ -49,6 +69,24 @@ function ensureExecutable(projectRoot, buildDir, targetOs, targetArch) { return true; } +/** On Windows, find MSYS2/Git-for-Windows usr/bin directories and prepend them + * to PATH so that make shell recipes can find grep, sed, tr, xargs, etc. */ +function buildMakeEnv() { + if (process.platform !== 'win32') return process.env; + + const candidates = [ + 'C:\\msys64\\usr\\bin', + 'C:\\msys32\\usr\\bin', + 'C:\\Program Files\\Git\\usr\\bin', + 'C:\\Git\\usr\\bin', + ]; + + const found = candidates.filter(p => { try { return fs.statSync(p).isDirectory(); } catch (_) { return false; } }); + if (found.length === 0) return process.env; + + return { ...process.env, PATH: found.join(path.delimiter) + path.delimiter + (process.env.PATH || '') }; +} + function hasBuildScript(projectRoot, jsEngine) { try { if (jsEngine === 'node' || jsEngine === 'bun') { @@ -103,16 +141,17 @@ function run(args) { const makefilePath = ['makefile', 'Makefile'].find(f => fs.existsSync(path.join(projectRoot, f))); if (makefilePath && state.js_engine === 'none') { ui.step('Building plugin (make)…'); - const r = spawnSync('make', [], { cwd: projectRoot, stdio: 'inherit' }); - process.exit(r.status ?? 0); + const makeEnv = buildMakeEnv(); + const r = spawnSync('make', [], { cwd: projectRoot, stdio: 'inherit', env: makeEnv }); + finishBuild(r, 'plugin'); } if (hasBuildScript(projectRoot, state.js_engine)) { - const [bin, bin_args] = state.pkg_manager().build_cmd(); + const pm = state.pkg_manager(); const buildLabel = state.isVanilla() ? state.js_engine : state.framework; ui.step(`Building (${buildLabel})…`); - const r = spawnSync(bin, bin_args, { cwd: projectRoot, stdio: 'inherit' }); - process.exit(r.status ?? 0); + const r = pm.run('build'); + finishBuild(r, buildLabel); } const srcDir = path.join(projectRoot, 'src'); diff --git a/cli/commands/create.js b/cli/commands/create.js index 119bbf5..847b493 100644 --- a/cli/commands/create.js +++ b/cli/commands/create.js @@ -23,13 +23,57 @@ const ui = require('../shared/ui'); const { prompt } = ui; function isDirectoryNonEmpty(dirPath) { - return fs.existsSync(dirPath) && fs.readdirSync(dirPath).length > 0; + if (!fs.existsSync(dirPath)) return false; + const entries = fs.readdirSync(dirPath); + return entries.some((entry) => !entry.startsWith('.')); +} + +function appendGitignoreEntries(projectDir, entries, sectionLabel = 'RenWeb') { + const giPath = path.join(projectDir, '.gitignore'); + const existing = fs.existsSync(giPath) ? fs.readFileSync(giPath, 'utf8') : ''; + const toAdd = entries.filter((entry) => !existing.includes(entry)); + if (toAdd.length === 0) return; + fs.appendFileSync(giPath, `\n# ${sectionLabel}\n${toAdd.join('\n')}\n`, 'utf8'); +} + +function mergeGitignoreFiles(srcPath, destPath) { + if (!fs.existsSync(srcPath)) return; + if (!fs.existsSync(destPath)) { + fs.renameSync(srcPath, destPath); + return; + } + + const srcLines = fs.readFileSync(srcPath, 'utf8').split(/\r?\n/); + const destText = fs.readFileSync(destPath, 'utf8'); + const toAdd = srcLines + .map((line) => line.trim()) + .filter((line) => line && !destText.includes(line)); + + if (toAdd.length) { + fs.appendFileSync(destPath, `\n# Existing scaffold\n${toAdd.join('\n')}\n`, 'utf8'); + } + + fs.unlinkSync(srcPath); } function moveDirectoryContents(srcDir, destDir) { fs.mkdirSync(destDir, { recursive: true }); for (const entry of fs.readdirSync(srcDir)) { - fs.renameSync(path.join(srcDir, entry), path.join(destDir, entry)); + const srcPath = path.join(srcDir, entry); + const destPath = path.join(destDir, entry); + + if (entry === '.gitignore') { + mergeGitignoreFiles(srcPath, destPath); + continue; + } + + // Preserve user/system dotfiles already present in destination. + if (entry.startsWith('.') && fs.existsSync(destPath)) { + fs.rmSync(srcPath, { recursive: true, force: true }); + continue; + } + + fs.renameSync(srcPath, destPath); } fs.rmdirSync(srcDir); } @@ -48,6 +92,109 @@ function relativeFromCwdOrAbsolute(targetPath) { return path.relative(cwd, targetPath) || '.'; } +function getTailText(buf) { + const s = (buf || '').toString().trim(); + if (!s) return ''; + return s.split('\n').slice(-8).join('\n'); +} + +function showScaffoldFailure(prefix, result, fallbackExitCode = 1) { + if (result.error) { + ui.error(`${prefix} failed: ${result.error.message}`); + } else { + ui.error(`${prefix} command failed.`); + } + + const stderr = getTailText(result.stderr); + const stdout = getTailText(result.stdout); + if (stderr) ui.dim(stderr); + else if (stdout) ui.dim(stdout); + + process.exit(result.status ?? fallbackExitCode); +} + +function ensureNpmAvailable(npmCmd) { + const check = spawnSync(npmCmd, ['--version'], { stdio: 'ignore' }); + if (check.error?.code === 'ENOENT') { + ui.error('npm is not installed or not on PATH.'); + ui.dim('Install Node.js from https://nodejs.org and try again.'); + process.exit(1); + } +} + +function installNpmPackages(projectDir, npmCmd) { + ui.step('Installing packages…'); + const install = runNpmWithWindowsFallback(projectDir, npmCmd, ['install']); + + if (install.error) { + ui.warn(`npm install failed — ${install.error.message}`); + const stderr = getTailText(install.stderr); + const stdout = getTailText(install.stdout); + if (stderr) ui.dim(stderr); + else if (stdout) ui.dim(stdout); + ui.warn('Run `npm install` manually in the project directory.'); + return; + } + + if (install.status !== 0) { + ui.warn('npm install failed — run it manually'); + const stderr = getTailText(install.stderr); + const stdout = getTailText(install.stdout); + if (stderr) ui.dim(stderr); + else if (stdout) ui.dim(stdout); + return; + } + + ui.ok('packages installed'); +} + +function normalizeScaffoldPaths(projectDir) { + const parent = path.dirname(projectDir); + const name = path.basename(projectDir); + const safeBaseName = toKebab(name) || 'renweb-app'; + const tempScaffoldName = `${safeBaseName}-rwtmp-${Date.now()}`; + const targetExists = fs.existsSync(projectDir); + const scaffoldName = (targetExists || safeBaseName !== name) ? tempScaffoldName : name; + return { + parent, + name, + scaffoldName, + scaffoldProjectDir: path.join(parent, scaffoldName), + }; +} + +function quoteForCmd(arg) { + const s = String(arg); + if (/^[A-Za-z0-9_@%+=:,./-]+$/.test(s)) return s; + return `"${s.replace(/(["^])/g, '^$1')}"`; +} + +function runNpmWithWindowsFallback(parentDir, npmCmd, args) { + const options = { cwd: parentDir, stdio: 'pipe', maxBuffer: 10 * 1024 * 1024 }; + let result = spawnSync(npmCmd, args, options); + + if (result.error && process.platform === 'win32') { + const cmd = `${npmCmd} ${args.map(quoteForCmd).join(' ')}`; + result = spawnSync('cmd.exe', ['/d', '/s', '/c', cmd], options); + } + + return result; +} + +function runViteScaffold(parentDir, npmCmd, scaffoldName, template) { + const npmCreateArgs = ['--yes', 'create', 'vite@5', scaffoldName, '--', '--template', template]; + return runNpmWithWindowsFallback(parentDir, npmCmd, npmCreateArgs); +} + +function runAngularScaffold(parentDir, npmCmd, scaffoldName) { + const npmExecArgs = [ + 'exec', '--yes', '--package', '@angular/cli@latest', + 'ng', 'new', scaffoldName, + '--routing=false', '--style=css', '--ssr=false', '--defaults', '--skip-install', + ]; + return runNpmWithWindowsFallback(parentDir, npmCmd, npmExecArgs); +} + function setupPluginBoostSubmodule(projectDir) { const BOOST_TAG = 'boost-1.90.0'; const BOOST_REPO = 'https://github.com/boostorg/boost.git'; @@ -382,7 +529,7 @@ async function createFrontend(projectDir, info) { fs.writeFileSync(path.join(buildDir, 'info.json'), infoText, 'utf8'); fetchEngineExecutable(buildDir); - const ignoreEntries = [ + appendGitignoreEntries(projectDir, [ 'build/', 'package/', 'release/', @@ -393,10 +540,7 @@ async function createFrontend(projectDir, info) { '.env', '*.log', '.rw/', - '', - ]; - const giPath = path.join(projectDir, '.gitignore'); - if (!fs.existsSync(giPath)) fs.writeFileSync(giPath, ignoreEntries.join('\n'), 'utf8'); + ]); } @@ -408,12 +552,7 @@ async function createFramework(projectDir, info, type) { const template = fw.template; const npmCmd = process.platform === 'win32' ? 'npm.cmd' : 'npm'; - const check = spawnSync(npmCmd, ['--version'], { stdio: 'ignore' }); - if (check.error?.code === 'ENOENT') { - ui.error('npm is not installed or not on PATH.'); - ui.dim('Install Node.js from https://nodejs.org and try again.'); - process.exit(1); - } + ensureNpmAvailable(npmCmd); if (isDirectoryNonEmpty(projectDir)) { ui.error(`Directory '${path.basename(projectDir)}' already exists and is not empty.`); @@ -421,21 +560,15 @@ async function createFramework(projectDir, info, type) { process.exit(1); } - const parent = path.dirname(projectDir); - const name = path.basename(projectDir); - const safeBaseName = toKebab(name) || 'renweb-app'; - const tempScaffoldName = `${safeBaseName}-rwtmp-${Date.now()}`; - const scaffoldName = (safeBaseName !== name) ? tempScaffoldName : name; - const scaffoldProjectDir = path.join(parent, scaffoldName); + const { parent, scaffoldName, scaffoldProjectDir } = normalizeScaffoldPaths(projectDir); fs.mkdirSync(parent, { recursive: true }); ui.step(`Scaffolding ${type} project via Vite…`); - const npxCmd = process.platform === 'win32' ? 'npx.cmd' : 'npx'; - const scaffold = spawnSync( - npxCmd, - ['--yes', 'create-vite@5', scaffoldName, '--template', template], - { cwd: parent, stdio: 'pipe' }, - ); + const scaffold = runViteScaffold(parent, npmCmd, scaffoldName, template); + + if (scaffold.error || scaffold.status !== 0) { + showScaffoldFailure('Vite scaffolding', scaffold); + } const scaffoldPkgPath = path.join(scaffoldProjectDir, 'package.json'); if (scaffoldProjectDir !== projectDir && fs.existsSync(scaffoldPkgPath)) { @@ -445,10 +578,10 @@ async function createFramework(projectDir, info, type) { const pkgPath = path.join(projectDir, 'package.json'); if (!fs.existsSync(pkgPath)) { ui.error('Vite scaffolding failed — package.json not found.'); - const stderr = (scaffold.stderr || '').toString().trim(); - const stdout = (scaffold.stdout || '').toString().trim(); - if (stderr) ui.dim(stderr.split('\n').slice(-8).join('\n')); - else if (stdout) ui.dim(stdout.split('\n').slice(-8).join('\n')); + const stderr = getTailText(scaffold.stderr); + const stdout = getTailText(scaffold.stdout); + if (stderr) ui.dim(stderr); + else if (stdout) ui.dim(stdout); process.exit(scaffold.status ?? 1); } const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf8')); @@ -497,30 +630,17 @@ export default defineConfig({ ui.step('Fetching credentials template…'); fetchGitHubDirectory('credentials', path.join(projectDir, 'credentials')); - const giPath = path.join(projectDir, '.gitignore'); - const giExisting = fs.existsSync(giPath) ? fs.readFileSync(giPath, 'utf8') : ''; - const giAppend = ['build/', 'credentials/', '.env', 'Thumbs.db', '.rw/'] - .filter(e => !giExisting.includes(e)); - if (giAppend.length) fs.appendFileSync(giPath, '\n# RenWeb\n' + giAppend.join('\n') + '\n', 'utf8'); + appendGitignoreEntries(projectDir, ['build/', 'credentials/', '.env', 'Thumbs.db', '.rw/']); - ui.step('Installing packages…'); - const install = spawnSync(npmCmd, ['install'], { cwd: projectDir, stdio: 'pipe' }); - if (install.status !== 0) ui.warn('npm install failed — run it manually'); - else ui.ok('packages installed'); + installNpmPackages(projectDir, npmCmd); } async function createAngular(projectDir, info) { const pageName = 'main'; const npmCmd = process.platform === 'win32' ? 'npm.cmd' : 'npm'; - const npxCmd = process.platform === 'win32' ? 'npx.cmd' : 'npx'; - const check = spawnSync(npmCmd, ['--version'], { stdio: 'ignore' }); - if (check.error?.code === 'ENOENT') { - ui.error('npm is not installed or not on PATH.'); - ui.dim('Install Node.js from https://nodejs.org and try again.'); - process.exit(1); - } + ensureNpmAvailable(npmCmd); if (isDirectoryNonEmpty(projectDir)) { ui.error(`Directory '${path.basename(projectDir)}' already exists and is not empty.`); @@ -528,21 +648,16 @@ async function createAngular(projectDir, info) { process.exit(1); } - const parent = path.dirname(projectDir); - const name = path.basename(projectDir); - const safeBaseName = toKebab(name) || 'renweb-app'; - const tempScaffoldName = `${safeBaseName}-rwtmp-${Date.now()}`; - const scaffoldName = (safeBaseName !== name) ? tempScaffoldName : name; - const scaffoldProjectDir = path.join(parent, scaffoldName); + const { parent, name, scaffoldName, scaffoldProjectDir } = normalizeScaffoldPaths(projectDir); fs.mkdirSync(parent, { recursive: true }); ui.step('Scaffolding Angular project…'); - const scaffold = spawnSync( - npxCmd, - ['--yes', '@angular/cli@latest', 'new', scaffoldName, - '--routing=false', '--style=css', '--ssr=false', '--defaults', '--skip-install'], - { cwd: parent, stdio: 'pipe' }, - ); + const scaffold = runAngularScaffold(parent, npmCmd, scaffoldName); + + if (scaffold.error || scaffold.status !== 0) { + showScaffoldFailure('Angular scaffolding', scaffold); + } + const scaffoldAngJsonPath = path.join(scaffoldProjectDir, 'angular.json'); if (scaffoldProjectDir !== projectDir && fs.existsSync(scaffoldAngJsonPath)) { moveDirectoryContents(scaffoldProjectDir, projectDir); @@ -551,10 +666,10 @@ async function createAngular(projectDir, info) { const angJsonPath = path.join(projectDir, 'angular.json'); if (!fs.existsSync(angJsonPath)) { ui.error('Angular scaffolding failed — angular.json not found.'); - const stderr = (scaffold.stderr || '').toString().trim(); - const stdout = (scaffold.stdout || '').toString().trim(); - if (stderr) ui.dim(stderr.split('\n').slice(-8).join('\n')); - else if (stdout) ui.dim(stdout.split('\n').slice(-8).join('\n')); + const stderr = getTailText(scaffold.stderr); + const stdout = getTailText(scaffold.stdout); + if (stderr) ui.dim(stderr); + else if (stdout) ui.dim(stdout); process.exit(scaffold.status ?? 1); } @@ -600,16 +715,9 @@ async function createAngular(projectDir, info) { ui.step('Fetching credentials template…'); fetchGitHubDirectory('credentials', path.join(projectDir, 'credentials')); - const giPath = path.join(projectDir, '.gitignore'); - const giExisting = fs.existsSync(giPath) ? fs.readFileSync(giPath, 'utf8') : ''; - const giAppend = ['build/', 'credentials/', '.env', 'Thumbs.db', '.rw/'] - .filter(e => !giExisting.includes(e)); - if (giAppend.length) fs.appendFileSync(giPath, '\n# RenWeb\n' + giAppend.join('\n') + '\n', 'utf8'); + appendGitignoreEntries(projectDir, ['build/', 'credentials/', '.env', 'Thumbs.db', '.rw/']); - ui.step('Installing packages…'); - const install = spawnSync(npmCmd, ['install'], { cwd: projectDir, stdio: 'pipe' }); - if (install.status !== 0) ui.warn('npm install failed — run it manually'); - else ui.ok('packages installed'); + installNpmPackages(projectDir, npmCmd); } async function createPlugin(projectDir, info, skipSubmodules = false) { @@ -650,8 +758,11 @@ async function createPlugin(projectDir, info, skipSubmodules = false) { fs.writeFileSync(path.join(projectDir, 'README.md'), makePluginReadme(info, pluginName), 'utf8'); - fs.writeFileSync(path.join(projectDir, '.gitignore'), - makePluginGitignore(), 'utf8'); + const pluginGitignore = makePluginGitignore() + .split(/\r?\n/) + .map((line) => line.trim()) + .filter((line) => line && !line.startsWith('#')); + appendGitignoreEntries(projectDir, pluginGitignore, 'RenWeb Plugin'); fs.writeFileSync(path.join(projectDir, '.github', 'workflows', 'build.yml'), makePluginWorkflow(pluginName), 'utf8'); @@ -689,15 +800,31 @@ function createEngine(projectDir, skipSubmodules) { const gitOk = spawnSync('git', ['--version'], { stdio: 'ignore' }).status === 0; if (!gitOk) { ui.error('git is required for `rw create engine`'); process.exit(1); } - const parent = path.dirname(projectDir); - const name = path.basename(projectDir); - const repoUrl = resolveEngineRepo(); + if (isDirectoryNonEmpty(projectDir)) { + ui.error(`Directory '${path.basename(projectDir)}' already exists and is not empty.`); + process.exit(1); + } + + // Ensure an empty directory exists for cloning into. + // We clear contents rather than deleting the directory itself so that any + // shell process whose CWD is projectDir doesn't end up with an invalid CWD. + if (fs.existsSync(projectDir)) { + for (const entry of fs.readdirSync(projectDir)) { + fs.rmSync(path.join(projectDir, entry), { recursive: true, force: true }); + } + } else { + fs.mkdirSync(projectDir, { recursive: true }); + } + + const name = path.basename(projectDir); + const repoUrl = resolveEngineRepo(); + // Clone into '.' (the now-empty projectDir) rather than creating a child directory. const cloneArgs = skipSubmodules - ? ['clone', repoUrl, name] - : ['clone', '--recurse-submodules', repoUrl, name]; + ? ['clone', repoUrl, '.'] + : ['clone', '--recurse-submodules', repoUrl, '.']; ui.step(`Cloning RenWeb Engine repository into ${name}/…${skipSubmodules ? '' : ' (including submodules)'}`); - const r = spawnSync('git', cloneArgs, { cwd: parent, stdio: 'inherit' }); + const r = spawnSync('git', cloneArgs, { cwd: projectDir, stdio: 'inherit' }); if (r.status !== 0) { ui.error('git clone failed'); process.exit(r.status); } } @@ -738,7 +865,13 @@ async function run(args) { type = await promptType(rl); } - const projectDir = path.resolve(dir || getCwdSafe() || '.'); + const cwd = dir || getCwdSafe(); + if (!cwd) { + ui.error('Cannot determine working directory. Please run from a valid directory or pass --dir.'); + if (rl) rl.close(); + process.exit(1); + } + const projectDir = path.resolve(cwd); // ── Engine clone: no further prompts needed ─────────────────────────────── if (type === 'engine') { diff --git a/cli/commands/doctor.js b/cli/commands/doctor.js index 12738d8..fbe512c 100644 --- a/cli/commands/doctor.js +++ b/cli/commands/doctor.js @@ -11,6 +11,55 @@ const PASS = '\u2713'; // ✓ const FAIL = '\u2717'; // ✗ const WARN = '\u26a0'; // ⚠ +function runVersionProbe(command, args = ['--version']) { + try { + return spawnSync(command, args, { encoding: 'utf8', stdio: 'pipe' }); + } catch (_) { + return null; + } +} + +function probeNpm() { + if (process.platform === 'win32') { + for (const [cmd, args] of [ + ['npm.cmd', ['--version']], + ['npm', ['--version']], + ['cmd.exe', ['/d', '/s', '/c', 'npm --version']], + ]) { + const r = runVersionProbe(cmd, args); + if (r && r.status === 0) return r; + } + return null; + } + + // First try with the inherited PATH + const r = runVersionProbe('npm', ['--version']); + if (r && r.status === 0) return r; + + // macOS / Linux: PATH may not include npm when launched outside an + // interactive shell (e.g. nvm, Homebrew). Try augmented PATH first. + const extra = [ + process.env.NVM_BIN, + process.env.NVM_DIR && require('path').join(process.env.NVM_DIR, 'versions', 'node', + (process.env.NODE_VERSION || ''), 'bin'), + '/opt/homebrew/bin', + '/usr/local/bin', + '/usr/bin', + ].filter(Boolean); + + const augmented = [...extra, process.env.PATH || ''].join(':'); + const r2 = spawnSync('npm', ['--version'], { + encoding: 'utf8', stdio: 'pipe', + env: { ...process.env, PATH: augmented }, + }); + if (r2 && r2.status === 0) return r2; + + // Last resort: use the shell to locate npm (handles aliases and custom PATHs) + const shell = process.env.SHELL || '/bin/sh'; + const r3 = spawnSync(shell, ['-lc', 'npm --version'], { encoding: 'utf8', stdio: 'pipe' }); + return (r3 && r3.status === 0) ? r3 : null; +} + function run() { let exitCode = 0; @@ -21,8 +70,8 @@ function run() { const sect = (msg) => ui.section(msg); function checkBin(name, installHint) { - const r = spawnSync(name, ['--version'], { encoding: 'utf8', stdio: 'pipe' }); - if (r.status === 0 || r.stdout) { + const r = runVersionProbe(name); + if (r && (r.status === 0 || r.stdout)) { const ver = (r.stdout || r.stderr || '').trim().split('\n')[0] || ''; ok(`${name}${ver ? ' (' + ver + ')' : ''}`); return true; @@ -32,14 +81,24 @@ function run() { } function checkBinOptional(name, note) { - const r = spawnSync(name, ['--version'], { encoding: 'utf8', stdio: 'pipe' }); - if (r.status === 0 || r.stdout) { + const r = runVersionProbe(name); + if (r && (r.status === 0 || r.stdout)) { ok(`${name} (optional, present)`); } else { warn(`${name} not found${note ? ' — ' + note : ''}`); } } + function checkNpmOptional(note) { + const r = probeNpm(); + if (r && r.status === 0) { + const ver = (r.stdout || r.stderr || '').trim().split('\n')[0] || ''; + ok(`npm${ver ? ' (' + ver + ')' : ''} (optional, present)`); + } else { + warn(`npm not found${note ? ' — ' + note : ''}`); + } + } + function checkNodeVersion() { const ver = process.version; const maj = parseInt(ver.slice(1), 10); @@ -117,7 +176,7 @@ function run() { else fail('curl / wget — at least one is required for downloads'); checkBin('git', 'needed for `rw create engine` and `rw plugin add`'); - checkBinOptional('npm', 'needed for Vite-based projects (react / vue / svelte / preact)'); + checkNpmOptional('needed for Vite-based projects (react / vue / svelte / preact)'); if (process.platform === 'linux') checkBinOptional('xdg-open', 'needed for `rw doc`'); checkBinOptional('make', 'needed for building plugins'); diff --git a/cli/commands/fetch.js b/cli/commands/fetch.js index bcb0acb..9cf24e5 100644 --- a/cli/commands/fetch.js +++ b/cli/commands/fetch.js @@ -84,6 +84,44 @@ function fetchApi(cwd) { } } +function selectExampleAsset(release) { + const assets = release.assets || []; + const isExampleArchive = (name) => /^example-\d+\.\d+\.\d+\.(zip|tar\.gz)$/i.test(name); + const candidates = assets.filter(a => isExampleArchive(a.name || '')); + if (candidates.length === 0) return null; + + const preferredExt = process.platform === 'win32' ? '.zip' : '.tar.gz'; + const preferred = candidates.find(a => (a.name || '').toLowerCase().endsWith(preferredExt)); + return preferred || candidates[0]; +} + +function fetchExample(cwd) { + ui.step('Fetching latest example project archive…'); + const release = fetchRelease(null); + if (!release) { + ui.error('Could not reach GitHub — aborting.'); + process.exit(1); + } + + const asset = selectExampleAsset(release); + if (!asset) { + const ver = release.tag_name || release.name || 'latest'; + ui.error(`No example archive found in release ${ver}`); + ui.info('Expected: example-x.y.z.zip or example-x.y.z.tar.gz'); + ui.info('Available assets:'); + (release.assets || []).forEach(a => ui.dim(a.name)); + process.exit(1); + } + + const dest = path.join(cwd, asset.name); + ui.step(`Downloading: ${asset.name}`); + if (!download(asset.browser_download_url, dest)) { + ui.error('Download failed'); + process.exit(1); + } + ui.ok(asset.name); +} + function run(args) { // Collect positional verbs (non-flag args before --version) const verbs = args.filter((a, i) => { @@ -95,13 +133,15 @@ function run(args) { const hasExe = verbs.includes('executable'); const hasPlugin = verbs.includes('plugin'); const hasApi = verbs.includes('api'); - const defaultMode = !hasExe && !hasPlugin && !hasApi; + const hasExample = verbs.includes('example'); + const defaultMode = !hasExe && !hasPlugin && !hasApi && !hasExample; if (defaultMode) { ui.info('Usage: rw fetch [--version ]'); ui.info(' executable Download the engine binary for the current OS/arch'); ui.info(' plugin Download the example plugin for the current OS/arch'); ui.info(' api Download the JS/TS API files (index.js, .ts, .d.ts, .js.map)'); + ui.info(' example Download latest example-x.y.z archive (zip on Windows, tar.gz elsewhere)'); process.exit(0); } @@ -121,6 +161,10 @@ function run(args) { fetchApi(cwd); } + if (hasExample) { + fetchExample(cwd); + } + if (hasExe) { const { os: tOs, arch: tArch } = detectTarget(); const label = tag ? `v${tag}` : 'latest'; diff --git a/cli/commands/init.js b/cli/commands/init.js index f4c2f12..a8a2aa2 100644 --- a/cli/commands/init.js +++ b/cli/commands/init.js @@ -10,6 +10,30 @@ const { FRAMEWORK_TYPES } = require('../shared/constants'); const { makeConfigJson, makeInfoJson } = require('../shared/templates/project'); const { fetchWebApi, fetchEngineExecutable, fetchGitHubDirectory } = require('../shared/fetchers'); +function quoteForCmd(arg) { + const s = String(arg); + if (/^[A-Za-z0-9_@%+=:,./-]+$/.test(s)) return s; + return `"${s.replace(/(["^])/g, '^$1')}"`; +} + +function runWithWindowsCmdFallback(bin, args, cwd, stdio = 'inherit') { + const options = { cwd, stdio }; + let result = spawnSync(bin, args, options); + + if (result.error && process.platform === 'win32') { + const cmd = `${bin} ${args.map(quoteForCmd).join(' ')}`; + result = spawnSync('cmd.exe', ['/d', '/s', '/c', cmd], options); + } + + return result; +} + +function getTailText(buf) { + const s = (buf || '').toString().trim(); + if (!s) return ''; + return s.split('\n').slice(-8).join('\n'); +} + // ─── Framework → npm dep name ──────────────────────────────────────────────── const FRAMEWORK_DEP = { @@ -347,13 +371,25 @@ async function run(args) { if (type === 'angular' || isViteBased || type === 'node-vanilla') { const npmCmd = process.platform === 'win32' ? 'npm.cmd' : 'npm'; ui.step('Installing npm packages…'); - const r = spawnSync(npmCmd, ['install'], { cwd: projectDir, stdio: 'inherit' }); - if (r.status !== 0) ui.warn('npm install failed — run it manually'); + const r = runWithWindowsCmdFallback(npmCmd, ['install'], projectDir, 'pipe'); + if (r.status !== 0) { + ui.warn('npm install failed — run it manually'); + const stderr = getTailText(r.stderr); + const stdout = getTailText(r.stdout); + if (stderr) ui.dim(stderr); + else if (stdout) ui.dim(stdout); + } else ui.ok('npm packages installed'); } else if (type === 'deno') { ui.step('Running deno install…'); - const r = spawnSync('deno', ['install'], { cwd: projectDir, stdio: 'inherit' }); - if (r.status !== 0) ui.warn('deno install failed — run it manually'); + const r = spawnSync('deno', ['install'], { cwd: projectDir, stdio: 'pipe' }); + if (r.status !== 0) { + ui.warn('deno install failed — run it manually'); + const stderr = getTailText(r.stderr); + const stdout = getTailText(r.stdout); + if (stderr) ui.dim(stderr); + else if (stdout) ui.dim(stdout); + } else ui.ok('deno packages installed'); } diff --git a/cli/commands/package.js b/cli/commands/package.js index 14a30af..7ea0501 100644 --- a/cli/commands/package.js +++ b/cli/commands/package.js @@ -217,6 +217,57 @@ function toSnake(str) { return str.trim().toLowerCase().replace(/[\s-]+/g, '_'); } +function packageCommandName(info) { + return toKebab((info && info.title) || 'app'); +} + +function windowsStableExeName(info) { + return packageCommandName(info) + '.exe'; +} + +function generateWindowsCommandShim(title, exeName) { + return [ + '@echo off', + 'setlocal EnableExtensions', + `set "_RW_HOME=%LOCALAPPDATA%\\${title}"`, + `set "_RW_TARGET=%_RW_HOME%\\${exeName}"`, + 'if not exist "%_RW_TARGET%" (', + ' set "_RW_TARGET="', + ' for /f "delims=" %%F in (\'dir /b /a:-d "%_RW_HOME%\\renweb-*-windows-*.exe" 2^>nul\') do set "_RW_TARGET=%_RW_HOME%\\%%F"', + ')', + 'if not exist "%_RW_TARGET%" (', + ' for /f "delims=" %%F in (\'dir /b /a:-d "%_RW_HOME%\\*.exe" 2^>nul\') do (', + ' if /I not "%%~nxF"=="Uninstall.exe" set "_RW_TARGET=%_RW_HOME%\\%%F"', + ' )', + ')', + 'if not exist "%_RW_TARGET%" (', + ' echo error: no executable found under "%_RW_HOME%" 1>&2', + ' exit /b 1', + ')', + '"%_RW_TARGET%" %*', + 'endlocal', + '', + ].join('\r\n'); +} + +function generateMacCommandWrapper(title, launcherName) { + return [ + '#!/bin/sh', + `APP_NAME="${title}"`, + `LAUNCHER_NAME="${launcherName}"`, + 'APP_PATH="$HOME/Applications/$APP_NAME.app/Contents/MacOS/$LAUNCHER_NAME"', + 'if [ ! -x "$APP_PATH" ]; then', + ' APP_PATH="/Applications/$APP_NAME.app/Contents/MacOS/$LAUNCHER_NAME"', + 'fi', + 'if [ ! -x "$APP_PATH" ]; then', + ' printf "error: %s.app is not installed in ~/Applications or /Applications\\n" "$APP_NAME" >&2', + ' exit 1', + 'fi', + 'exec "$APP_PATH" "$@"', + '', + ].join('\n'); +} + /** Stable Windows package ID; prefers info.app_id to avoid renaming drift. */ function windowsPackageId(info) { const raw = (info && typeof info.app_id === 'string' && info.app_id.trim()) @@ -643,6 +694,7 @@ function generateLinuxWrapperScript(pkgId, version) { /** Build system-layout staging tree under destRoot: seed at /usr/share//, wrapper at /usr/bin/, .desktop file, and icon. Shared by buildFpmPackages and buildXbpsPackage. */ function buildSystemLayoutStaging(stagingDir, pkgId, version, exeFilename, info, destRoot) { + const commandName = packageCommandName(info); const seedDir = path.join(destRoot, 'usr', 'share', pkgId); const appsDir = path.join(destRoot, 'usr', 'share', 'applications'); const iconsDir = path.join(destRoot, 'usr', 'share', 'icons', 'hicolor', '256x256', 'apps'); @@ -654,7 +706,7 @@ function buildSystemLayoutStaging(stagingDir, pkgId, version, exeFilename, info, // Wrapper script at /usr/bin/ const wrapperContent = generateLinuxWrapperScript(pkgId, version); - const binPath = path.join(binDir, pkgId); + const binPath = path.join(binDir, commandName); fs.writeFileSync(binPath, wrapperContent, 'utf8'); makeExecutable(binPath); @@ -681,8 +733,8 @@ function buildSystemLayoutStaging(stagingDir, pkgId, version, exeFilename, info, 'Type=Application', `Name=${info.title || pkgId}`, `Comment=${desc}`, - `Exec=/usr/bin/${pkgId} %u`, - `TryExec=/usr/bin/${pkgId}`, + `Exec=/usr/bin/${commandName} %u`, + `TryExec=/usr/bin/${commandName}`, `Icon=${iconSystemPath}`, 'Terminal=false', `Categories=${cats}`, @@ -728,6 +780,15 @@ function buildPackageForTarget(opts, buildSrc, pluginDirs, engineAsset, info, pk if (targetOs === 'windows' || targetOs === 'win') { const winExe = path.join(stagingDir, engineAsset.filename); if (fs.existsSync(winExe)) patchWindowsExe(winExe, info); + + // Create a stable executable name for shortcuts so GUI launch stays + // version-agnostic while still launching directly (no cmd shim). + const stableWinExeName = windowsStableExeName(info); + if (path.basename(engineAsset.filename).toLowerCase() !== stableWinExeName.toLowerCase()) { + const stableWinExe = path.join(stagingDir, stableWinExeName); + fs.copyFileSync(winExe, stableWinExe); + makeExecutable(stableWinExe); + } } // Archive outputs @@ -758,8 +819,9 @@ function buildPackageForTarget(opts, buildSrc, pluginDirs, engineAsset, info, pk if (targetOs === 'windows' || targetOs === 'win') { const nsisOut = path.join(outDir, `${stem}-setup.exe`); - buildNsisInstaller(opts, info, stagingDir, targetArch, nsisOut); - buildMsiInstaller(opts, info, stagingDir, targetArch, path.join(outDir, `${stem}.msi`)); + const shortcutExeName = windowsStableExeName(info); + buildNsisInstaller(opts, info, stagingDir, targetArch, nsisOut, engineAsset.filename, shortcutExeName); + buildMsiInstaller(opts, info, stagingDir, targetArch, path.join(outDir, `${stem}.msi`), engineAsset.filename, shortcutExeName); const msixExeFile = engineAsset.filename; buildMsixPackage(opts, info, stagingDir, targetArch, path.join(outDir, `${stem}.msix`), msixExeFile); buildChocoPackage(opts, info, targetArch, nsisOut, outDir); @@ -778,20 +840,44 @@ function buildPackageForTarget(opts, buildSrc, pluginDirs, engineAsset, info, pk const exeFor = engineAsset.filename; const pkgOut = path.join(outDir, `${stem}.pkg`); const pkgTmp = path.join(tmpDir, `${stem}-osxpkg`); + const commandName = packageCommandName(info); if (fs.existsSync(pkgTmp)) fs.rmSync(pkgTmp, { recursive: true, force: true }); fs.mkdirSync(pkgTmp, { recursive: true }); const appBundle = buildMacAppBundle(stagingDir, exeFor, info, pkgTmp); + const launcherName = (info.title || 'App').replace(/[^A-Za-z0-9_-]/g, '') || 'App'; const bundleId = info.app_id || info.bundle_id || ('com.' + toKebab(info.author || 'app').replace(/-/g, '.') + '.' + toKebab(info.title || 'app')); const pkgVersion = (info.version || '0.0.1').trim(); + const scriptsDir = path.join(pkgTmp, 'pkg-scripts'); + const postinstallPath = path.join(scriptsDir, 'postinstall'); + fs.mkdirSync(scriptsDir, { recursive: true }); + fs.writeFileSync(postinstallPath, [ + '#!/bin/sh', + 'set -e', + 'write_cmd() {', + ' _dir="$1"', + ' _name="$2"', + ' mkdir -p "$_dir"', + ' cat > "$_dir/$_name" <<\'RENWEOF\'', + generateMacCommandWrapper(info.title || 'App', launcherName), + 'RENWEOF', + ' chmod 755 "$_dir/$_name"', + '}', + `write_cmd /usr/local/bin ${commandName}`, + `[ -d /opt/homebrew/bin ] && write_cmd /opt/homebrew/bin ${commandName} || true`, + 'exit 0', + '', + ].filter(Boolean).join('\n'), 'utf8'); + makeExecutable(postinstallPath); // Build flat component pkg, then wrap with productbuild const componentPkg = pkgOut.replace(/\.pkg$/, '-component.pkg'); const pkgR = spawnSync('pkgbuild', [ '--component', appBundle, - '--install-location', '~/Applications', + '--install-location', '/Applications', '--identifier', bundleId, '--version', pkgVersion, + '--scripts', scriptsDir, componentPkg, ], { stdio: 'inherit' }); @@ -807,7 +893,17 @@ function buildPackageForTarget(opts, buildSrc, pluginDirs, engineAsset, info, pk ? ` ` : '', fs.existsSync(bgPkgSrc) ? ` ` : '', - ' ', + // hostArchitectures prevents the installer from attempting + // to run the distribution check under Rosetta (architecture translation). + (() => { + const hostArch = targetArch === 'universal' ? 'arm64,x86_64' + : targetArch === 'arm64' ? 'arm64' + : targetArch === 'x86_64' ? 'x86_64' + : null; + return hostArch + ? ` ` + : ' '; + })(), ' ', ' ', ' ', @@ -1292,7 +1388,7 @@ function xbpsSign(opts, filePath) { * Build a .nsi NSIS installer via makensis. String concatenation is used for NSIS lines to avoid * ambiguity between JS template-literal ${} and NSIS $VAR syntax. */ -function buildNsisInstaller(opts, info, stagingDir, arch, outPath) { +function buildNsisInstaller(opts, info, stagingDir, arch, outPath, exeFilename = '', shortcutExeName = '') { if (opts.exts.size > 0 && !opts.exts.has('exe') && !opts.exts.has('choco') && !opts.exts.has('nuget') && !opts.exts.has('winget')) return; if (spawnSync('which', ['makensis'], { encoding: 'utf8' }).status !== 0) { @@ -1304,12 +1400,20 @@ function buildNsisInstaller(opts, info, stagingDir, arch, outPath) { const desc = info.description || title; const pkgId = windowsPackageId(info); const regId = windowsRegistryId(info); + const commandName = packageCommandName(info); const exeId = toKebab(info.title || 'app'); const author = info.author || title; const website = info.repository || ''; const copyright = info.copyright || ('Copyright (C) ' + author); const winVer = version.replace(/[^\d.]/g,'').split('.').concat(['0','0','0','0']).slice(0,4).join('.'); - const exeName = exeId + '-' + version + '-windows-' + arch + '.exe'; + const exeName = exeFilename || (exeId + '-' + version + '-windows-' + arch + '.exe'); + const shortcutTarget = shortcutExeName || exeName; + const windowsShim = generateWindowsCommandShim(title, exeName); + const nsisWindowsShim = windowsShim + .replace(/\\/g, '\\\\') + .replace(/"/g, '$\\"') + .replace(/\r/g, '$\\r') + .replace(/\n/g, '$\\n'); const ukey = 'Software\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\' + regId; const bootstrapperName = 'MicrosoftEdgeWebview2Setup.exe'; const bootstrapperCandidates = [ @@ -1393,15 +1497,19 @@ function buildNsisInstaller(opts, info, stagingDir, arch, outPath) { L('Section "Install"'); L(' SetOutPath "$INSTDIR"'); L(' File /r "' + stagingDir + '/"'); + L(' CreateDirectory "$LOCALAPPDATA\\Microsoft\\WindowsApps"'); + L(' FileOpen $0 "$LOCALAPPDATA\\Microsoft\\WindowsApps\\' + commandName + '.cmd" w'); + L(' FileWrite $0 "' + nsisWindowsShim + '"'); + L(' FileClose $0'); L(' WriteRegStr HKCU "Software\\' + regId + '" "InstallDir" "$INSTDIR"'); L(' WriteUninstaller "$INSTDIR\\Uninstall.exe"'); L(' CreateDirectory "$SMPROGRAMS\\' + title + '"'); - L(' CreateShortCut "$SMPROGRAMS\\' + title + '\\' + title + '.lnk" "$INSTDIR\\' + exeName + '" "" "$INSTDIR\\' + exeName + '" 0'); + L(' CreateShortCut "$SMPROGRAMS\\' + title + '\\' + title + '.lnk" "$INSTDIR\\' + shortcutTarget + '" "" "$INSTDIR\\' + exeName + '" 0'); L(' CreateShortCut "$SMPROGRAMS\\' + title + '\\Uninstall ' + title + '.lnk" "$INSTDIR\\Uninstall.exe"'); if (iconPath) - L(' CreateShortCut "$DESKTOP\\' + title + '.lnk" "$INSTDIR\\' + exeName + '" "" "$INSTDIR\\' + exeName + '" 0'); + L(' CreateShortCut "$DESKTOP\\' + title + '.lnk" "$INSTDIR\\' + shortcutTarget + '" "" "$INSTDIR\\' + exeName + '" 0'); else - L(' CreateShortCut "$DESKTOP\\' + title + '.lnk" "$INSTDIR\\' + exeName + '"'); + L(' CreateShortCut "$DESKTOP\\' + title + '.lnk" "$INSTDIR\\' + shortcutTarget + '"'); L(' WriteRegStr HKCU "' + ukey + '" "DisplayName" "' + title + '"'); L(' WriteRegStr HKCU "' + ukey + '" "UninstallString" "$INSTDIR\\Uninstall.exe"'); L(' WriteRegStr HKCU "' + ukey + '" "DisplayVersion" "' + version + '"'); @@ -1435,6 +1543,7 @@ function buildNsisInstaller(opts, info, stagingDir, arch, outPath) { L('SectionEnd'); L(); L('Section "Uninstall"'); + L(' Delete "$LOCALAPPDATA\\Microsoft\\WindowsApps\\' + commandName + '.cmd"'); L(' RMDir /r "$INSTDIR"'); L(' DeleteRegKey HKCU "Software\\' + regId + '"'); L(' DeleteRegKey HKCU "' + ukey + '"'); @@ -1443,7 +1552,6 @@ function buildNsisInstaller(opts, info, stagingDir, arch, outPath) { L(' Delete "$SMPROGRAMS\\' + title + '\\Uninstall ' + title + '.lnk"'); L(' RMDir "$SMPROGRAMS\\' + title + '"'); L('SectionEnd'); - L(); fs.mkdirSync(path.dirname(outPath), { recursive: true }); const nsiPath = path.join(os.tmpdir(), '_renweb-nsis-' + pkgId + '-' + arch + '.nsi'); @@ -1457,7 +1565,7 @@ function buildNsisInstaller(opts, info, stagingDir, arch, outPath) { } /** Build a .msi installer via wixl (msitools). No Wine needed. */ -function buildMsiInstaller(opts, info, stagingDir, arch, outPath) { +function buildMsiInstaller(opts, info, stagingDir, arch, outPath, exeFilename = '', shortcutExeName = '') { if (opts.exts.size > 0 && !opts.exts.has('msi')) return; if (!findBin('wixl')) { ui.warn('wixl not found \u2014 skipping MSI'); return; @@ -1468,15 +1576,19 @@ function buildMsiInstaller(opts, info, stagingDir, arch, outPath) { const desc = info.description || title; const pkgId = windowsPackageId(info); const regId = windowsRegistryId(info); + const commandName = packageCommandName(info); const exeId = toKebab(info.title || 'app'); const author = info.author || title; const website = info.repository || ''; - const exeName = exeId + '-' + version + '-windows-' + arch + '.exe'; + const exeName = exeFilename || (exeId + '-' + version + '-windows-' + arch + '.exe'); + const shortcutTarget = shortcutExeName || exeName; const productCode = hashToUuid(pkgId + '-product-' + version); const upgradeCode = hashToUuid(pkgId + '-upgrade'); const tmpBase = path.join(path.dirname(outPath), '_msi-' + pkgId + '-' + arch); + const cliShimPath = path.join(tmpBase, `${commandName}.cmd`); if (fs.existsSync(tmpBase)) fs.rmSync(tmpBase, { recursive: true, force: true }); fs.mkdirSync(tmpBase, { recursive: true }); + fs.writeFileSync(cliShimPath, generateWindowsCommandShim(title, exeName), 'utf8'); // MSI version must be strictly numeric (max four dotted parts) const msiVersion = version.replace(/[^\d.]/g, '').split('.').slice(0, 4) @@ -1525,6 +1637,7 @@ function buildMsiInstaller(opts, info, stagingDir, arch, outPath) { const dirBody = buildDirBody(stagingDir, ' '); const filesWxs = path.join(tmpBase, 'files.wxs'); const shortcutsGuid = hashToUuid(pkgId + '-msi-shortcuts').toUpperCase(); + const cliShimGuid = hashToUuid(pkgId + '-msi-cli-shim').toUpperCase(); const filesContent = [ '', '', @@ -1536,14 +1649,14 @@ function buildMsiInstaller(opts, info, stagingDir, arch, outPath) { ' Directory="ProgramMenuDir"', ' Name="' + xmlEscape(title) + '"', ' Description="' + xmlEscape(desc) + '"', - ' Target="[APPDIR]' + xmlEscape(exeName) + '"', + ' Target="[APPDIR]' + xmlEscape(shortcutTarget) + '"', ...(iconPath ? [' Icon="AppIcon" IconIndex="0"'] : []), ' WorkingDirectory="APPDIR"/>', ' ', ' ', @@ -1551,11 +1664,17 @@ function buildMsiInstaller(opts, info, stagingDir, arch, outPath) { ' ', ' ', ' ', + ' ', + ' ', + ' ', + ' ', + ' ', ' ', ' ', ' ', ...compIds.map(id => ' '), ' ', + ' ', ' ', ' ', '', @@ -1591,6 +1710,9 @@ function buildMsiInstaller(opts, info, stagingDir, arch, outPath) { ' ', ' ', ' ', + ' ', + ' ', + ' ', ' ', ' ', ' ', @@ -1638,6 +1760,7 @@ function buildMsixPackage(opts, info, stagingDir, arch, outPath, exeFilename = ' const version = (info.version || '0.0.1').trim(); const desc = info.description || title; const pkgId = windowsPackageId(info); + const commandName = packageCommandName(info); const author = info.author || title; // Identity Name: only letters, numbers, dots, hyphens const identity = pkgId.replace(/[^A-Za-z0-9.\-]/g, '-'); @@ -1679,7 +1802,7 @@ function buildMsixPackage(opts, info, stagingDir, arch, outPath, exeFilename = ' // For bundles, the caller already provides the correct .exe filename (renweb-*.exe). const msixExe = exeFilename; - const msixIgnorableNamespaces = 'rescap win32dependencies'; + const msixIgnorableNamespaces = 'rescap win32dependencies uap3 desktop'; const msixExternalDeps = [ ' ', '', ' ', + ' ', + ' ', + ' ', + ' ', + ' ', + ' ', + ' ', ' ', ' ', ' ', @@ -2052,22 +2184,43 @@ function buildMacAppBundle(stagingDir, exeFilename, info, destDir) { '', '', - ' CFBundleIdentifier ' + bundleId + '', - ' CFBundleName ' + title + '', - ' CFBundleDisplayName ' + title + '', - ' CFBundleVersion ' + version + '', - ' CFBundleShortVersionString' + version + '', - ' CFBundleExecutable ' + launcherName + '', + ' CFBundleIdentifier ' + xmlEscapeSimple(bundleId) + '', + ' CFBundleName ' + xmlEscapeSimple(title) + '', + ' CFBundleDisplayName ' + xmlEscapeSimple(title) + '', + ' CFBundleVersion ' + xmlEscapeSimple(version) + '', + ' CFBundleShortVersionString' + xmlEscapeSimple(version) + '', + ' CFBundleExecutable ' + xmlEscapeSimple(launcherName) + '', ' CFBundlePackageType APPL', ' NSPrincipalClass NSApplication', ' NSHighResolutionCapable ', ' LSMinimumSystemVersion 10.15', - ' NSHumanReadableCopyright ' + copyright + '', + ' NSHumanReadableCopyright ' + xmlEscapeSimple(copyright) + '', ...nsKeys, '', ].join('\n'); fs.writeFileSync(path.join(contentsDir, 'Info.plist'), plist, 'utf8'); + // Generate entitlements.plist for permissions that have real macOS sandbox entitlement keys. + // Note: notifications are controlled by Info.plist usage description + signing context; there + // is no direct entitlement equivalent to "notifications: true" for this local-notification flow. + const entitlements_keys = []; + if (perms.geolocation) entitlements_keys.push(' com.apple.security.personal-information.location', ' '); + if (perms.media_devices) entitlements_keys.push(' com.apple.security.device.microphone', ' ', ' com.apple.security.device.camera', ' '); + + if (entitlements_keys.length > 0) { + const entitlements = [ + '', + '', + '', + ' com.apple.security.app-sandbox', + ' ', + ...entitlements_keys, + '', + ].join('\n'); + fs.writeFileSync(path.join(contentsDir, 'entitlements.plist'), entitlements, 'utf8'); + } + return appBundle; } @@ -2378,7 +2531,7 @@ function buildMacAppStorePackage(opts, info, stagingDir, arch, outDir, exeFilena const masComponentPkg = outFile.replace(/\.pkg$/, '-component.pkg'); const masCompR = spawnSync('pkgbuild', [ '--component', appBundle, - '--install-location', '~/Applications', + '--install-location', '/Applications', '--identifier', masBundleId, '--version', masVersion, masComponentPkg, @@ -2391,7 +2544,15 @@ function buildMacAppStorePackage(opts, info, stagingDir, arch, outDir, exeFilena ` ${xmlEscapeSimple(info.title || 'App')}`, fs.existsSync(masLicSrc) ? ` ` : '', fs.existsSync(masBgPkgSrc) ? ` ` : '', - ' ', + (() => { + const hostArch = arch === 'universal' ? 'arm64,x86_64' + : arch === 'arm64' ? 'arm64' + : arch === 'x86_64' ? 'x86_64' + : null; + return hostArch + ? ` ` + : ' '; + })(), ' ', ' ', ' ', diff --git a/cli/commands/update.js b/cli/commands/update.js index 96f5c6d..51e890d 100644 --- a/cli/commands/update.js +++ b/cli/commands/update.js @@ -13,6 +13,12 @@ const { const { ProjectState } = require('../project/project_state'); const ui = require('../shared/ui'); +function getTailText(buf) { + const s = (buf || '').toString().trim(); + if (!s) return ''; + return s.split('\n').slice(-8).join('\n'); +} + // ─── Update engine executable only ─────────────────────────────────────────── @@ -141,8 +147,23 @@ function run(args) { if (pm && pm.name() !== 'none') { ui.step(`Updating ${pm.name()} packages…`); const r = pm.install(); - if (r && r.status !== 0) ui.warn('Package install returned non-zero'); - else if (r) ui.ok('Packages up to date'); + if (!r) { + ui.warn('Package install did not run.'); + } else if (r.error) { + ui.warn(`Package install failed: ${r.error.message}`); + const stderr = getTailText(r.stderr); + const stdout = getTailText(r.stdout); + if (stderr) ui.dim(stderr); + else if (stdout) ui.dim(stdout); + } else if (r.status !== 0) { + ui.warn('Package install returned non-zero'); + const stderr = getTailText(r.stderr); + const stdout = getTailText(r.stdout); + if (stderr) ui.dim(stderr); + else if (stdout) ui.dim(stdout); + } else { + ui.ok('Packages up to date'); + } } // ── 2. Vanilla JS API modules ───────────────────────────────────────────── diff --git a/cli/index.js b/cli/index.js index 8dfd4ff..47ee987 100755 --- a/cli/index.js +++ b/cli/index.js @@ -18,7 +18,7 @@ const program = new Command(); program .name('rw') .description('RenWeb CLI — create, develop, and package RenWeb projects') - .option('-V, --version', 'Display version') + .option('-v, --version', 'Display version') .on('option:version', () => { ui.renwebBanner(version); process.exit(0); @@ -153,7 +153,7 @@ program program .command('fetch [verb]') - .description('Download RenWeb assets.\n Verbs: executable | plugin | api\n (default: executable)\n executable Download the engine executable + template info.json to build/\n plugin Download plugin.hpp to the current directory\n api Download the JS/TS API files (index.js, .ts, .d.ts, .js.map)') + .description('Download RenWeb assets.\n Verbs: executable | plugin | api | example\n (default: executable)\n executable Download the engine executable + template info.json to build/\n plugin Download plugin.hpp to the current directory\n api Download the JS/TS API files (index.js, .ts, .d.ts, .js.map)\n example Download latest example-x.y.z archive (zip on Windows, tar.gz elsewhere)') .option('--version ', 'Pin to a specific release tag (default: latest, executable only)') .allowUnknownOption(true) .action(() => { diff --git a/cli/package.json b/cli/package.json index bb13dc0..d5fef3c 100644 --- a/cli/package.json +++ b/cli/package.json @@ -1,6 +1,6 @@ { "name": "renweb-cli", - "version": "0.0.6", + "version": "0.1.0", "description": "CLI tool for scaffolding, building, running, and packaging RenWeb desktop apps. Supports Angular, Vite, Deno, and vanilla project templates.", "bin": { "rw": "index.js" diff --git a/cli/project/package_manager.js b/cli/project/package_manager.js index fd6d3d8..a016724 100644 --- a/cli/project/package_manager.js +++ b/cli/project/package_manager.js @@ -1,6 +1,24 @@ 'use strict'; const { spawnSync } = require('child_process'); +function quoteForCmd(arg) { + const s = String(arg); + if (/^[A-Za-z0-9_@%+=:,./-]+$/.test(s)) return s; + return `"${s.replace(/(["^])/g, '^$1')}"`; +} + +function runWithWindowsCmdFallback(bin, args, cwd, stdio = 'inherit', env = process.env) { + const options = { cwd, stdio, env }; + let result = spawnSync(bin, args, options); + + if (result.error && process.platform === 'win32') { + const cmd = `${bin} ${args.map(quoteForCmd).join(' ')}`; + result = spawnSync('cmd.exe', ['/d', '/s', '/c', cmd], options); + } + + return result; +} + class NoneAdapter { name() { return 'none'; } build_cmd() { return null; } @@ -13,8 +31,13 @@ class NpmAdapter { _bin() { return process.platform === 'win32' ? 'npm.cmd' : 'npm'; } name() { return 'node'; } build_cmd() { return [this._bin(), ['run', 'build']]; } - run(script, ...a) { return spawnSync(this._bin(), ['run', script, ...a], { cwd: this.root, stdio: 'inherit' }); } - install() { return spawnSync(this._bin(), ['install'], { cwd: this.root, stdio: 'inherit' }); } + run(script, ...a) { + const env = (script === 'build') + ? { ...process.env, NG_CLI_ANALYTICS: 'false' } + : process.env; + return runWithWindowsCmdFallback(this._bin(), ['run', '--silent', script, ...a], this.root, 'inherit', env); + } + install() { return runWithWindowsCmdFallback(this._bin(), ['install'], this.root, 'pipe'); } } class DenoAdapter { @@ -30,7 +53,7 @@ class BunAdapter { name() { return 'bun'; } build_cmd() { return ['bun', ['run', 'build']]; } run(script, ...a) { return spawnSync('bun', ['run', script, ...a], { cwd: this.root, stdio: 'inherit' }); } - install() { return spawnSync('bun', ['install'], { cwd: this.root, stdio: 'inherit' }); } + install() { return spawnSync('bun', ['install'], { cwd: this.root, stdio: 'pipe' }); } } class PackageManagerAdapter { diff --git a/cli/project/project_state.js b/cli/project/project_state.js index 227a8dc..4c4a44a 100644 --- a/cli/project/project_state.js +++ b/cli/project/project_state.js @@ -66,7 +66,11 @@ class ProjectState { } static detect(cwd) { const { findProjectRoot } = require('../shared/utils'); - const root = findProjectRoot(cwd || process.cwd()); + let activeCwd = cwd; + if (!activeCwd) { + try { activeCwd = process.cwd(); } catch (_) { return null; } + } + const root = findProjectRoot(activeCwd); if (!root) return null; return ProjectState._build(root); } diff --git a/docs/assets/2_download.png b/docs/assets/2_download.png index 4704247..3420b71 100644 Binary files a/docs/assets/2_download.png and b/docs/assets/2_download.png differ diff --git a/docs/assets/3_download_engine.png b/docs/assets/3_download_engine.png index d6e609a..5ca25b7 100644 Binary files a/docs/assets/3_download_engine.png and b/docs/assets/3_download_engine.png differ diff --git a/docs/assets/renweb.png b/docs/assets/renweb.png index 18debdc..a1bff22 100644 Binary files a/docs/assets/renweb.png and b/docs/assets/renweb.png differ diff --git a/docs/assets/window_example_linux.png b/docs/assets/window_example_linux.png index 47877b7..0e62fb5 100644 Binary files a/docs/assets/window_example_linux.png and b/docs/assets/window_example_linux.png differ diff --git a/docs/assets/window_example_macos.png b/docs/assets/window_example_macos.png index dde4661..df37403 100644 Binary files a/docs/assets/window_example_macos.png and b/docs/assets/window_example_macos.png differ diff --git a/docs/assets/window_example_windows.png b/docs/assets/window_example_windows.png index 8f47c00..987d66d 100644 Binary files a/docs/assets/window_example_windows.png and b/docs/assets/window_example_windows.png differ diff --git a/include/managers/process_manager.hpp b/include/managers/process_manager.hpp index 3ccf0e0..0fcd869 100644 --- a/include/managers/process_manager.hpp +++ b/include/managers/process_manager.hpp @@ -58,9 +58,11 @@ #include #include #include +#include #if defined(_WIN32) #include + #include #include #include #include @@ -1016,11 +1018,109 @@ inline json::object PM::createChildProcess(const std::vector& args, std::vector resolved_args = args; resolved_args[0] = executable; + +#if defined(_WIN32) + if (share_stdio) { + auto toLowerCopy = [](std::string input) -> std::string { + std::transform(input.begin(), input.end(), input.begin(), + [](unsigned char c) { return static_cast(std::tolower(c)); }); + return input; + }; + + const std::string exe_path = toLowerCopy(executable); + const bool looks_like_msys_or_cygwin = + exe_path.find("cygwin") != std::string::npos || + exe_path.find("msys") != std::string::npos || + exe_path.find("/usr/bin/") != std::string::npos || + exe_path.find("\\\\usr\\\\bin\\\\") != std::string::npos; + + if (looks_like_msys_or_cygwin) { + auto quoteForCmd = [](const std::string& arg) -> std::string { + if (arg.empty()) return "\"\""; + const bool needs_quotes = arg.find_first_of(" \t\"") != std::string::npos; + if (!needs_quotes) return arg; + std::string escaped; + escaped.reserve(arg.size() + 8); + escaped.push_back('"'); + for (char ch : arg) { + if (ch == '"') escaped.push_back('\\'); + escaped.push_back(ch); + } + escaped.push_back('"'); + return escaped; + }; + + std::string cmd_payload; + for (size_t i = 0; i < resolved_args.size(); ++i) { + if (i > 0) cmd_payload.push_back(' '); + cmd_payload += quoteForCmd(resolved_args[i]); + } + + std::string cmd_exe = "C:\\Windows\\System32\\cmd.exe"; + char* comspec = nullptr; + size_t comspec_len = 0; + if (_dupenv_s(&comspec, &comspec_len, "ComSpec") == 0 && comspec && *comspec) { + cmd_exe.assign(comspec); + } + free(comspec); + + resolved_args = { + cmd_exe, + "/d", + "/s", + "/c", + cmd_payload + }; + + this->logger->warn("[proc] share_stdio: routing MSYS/Cygwin executable through cmd.exe for compatibility"); + } + } +#endif Child proc; - Pid pid; + Pid pid = 0; File out_file; +#if defined(_WIN32) + auto closeHandleIfValid = [](HANDLE handle) { + if (handle && handle != INVALID_HANDLE_VALUE) { + CloseHandle(handle); + } + }; + + auto launchWindowsProcess = [&](const std::vector& launch_args, + STARTUPINFOW& startup_info, + const std::string& context) -> PROCESS_INFORMATION { + std::wstring cmd_line = buildCmdLine(launch_args); + PROCESS_INFORMATION process_info = {}; + if (!CreateProcessW( + nullptr, + cmd_line.data(), + nullptr, + nullptr, + TRUE, + 0, + nullptr, + nullptr, + &startup_info, + &process_info)) { + const DWORD error = GetLastError(); + const std::string message = context.empty() + ? "Failed to create process" + : "Failed to create process with " + context; + throw std::runtime_error(message + " (error " + std::to_string(error) + ")"); + } + return process_info; + }; + + auto adoptWindowsProcess = [&](const PROCESS_INFORMATION& process_info) { + pid = static_cast(process_info.dwProcessId); + proc = Child(process_info.dwProcessId); + closeHandleIfValid(process_info.hThread); + closeHandleIfValid(process_info.hProcess); + }; +#endif + if (share_stdio) { #if defined(__linux__) || defined(__unix__) { @@ -1034,6 +1134,134 @@ inline json::object PM::createChildProcess(const std::vector& args, proc = Child(resolved_args, BOOST_PROCESS_V1_NAMESPACE::std_in.close()); } } +#elif defined(_WIN32) + auto duplicateAsInheritable = [](HANDLE src) -> HANDLE { + if (!src || src == INVALID_HANDLE_VALUE) return INVALID_HANDLE_VALUE; + HANDLE dup = INVALID_HANDLE_VALUE; + if (!DuplicateHandle( + GetCurrentProcess(), + src, + GetCurrentProcess(), + &dup, + 0, + TRUE, + DUPLICATE_SAME_ACCESS)) { + return INVALID_HANDLE_VALUE; + } + return dup; + }; + + auto openSpecialOrNul = [](bool input_handle) -> HANDLE { + SECURITY_ATTRIBUTES sa = {}; + sa.nLength = sizeof(sa); + sa.bInheritHandle = TRUE; + + const wchar_t* special_name = input_handle ? L"CONIN$" : L"CONOUT$"; + HANDLE h = CreateFileW( + special_name, + input_handle ? GENERIC_READ : GENERIC_WRITE, + FILE_SHARE_READ | FILE_SHARE_WRITE, + &sa, + OPEN_EXISTING, + FILE_ATTRIBUTE_NORMAL, + nullptr + ); + if (h && h != INVALID_HANDLE_VALUE) { + return h; + } + + h = CreateFileW( + L"NUL", + input_handle ? GENERIC_READ : GENERIC_WRITE, + FILE_SHARE_READ | FILE_SHARE_WRITE, + &sa, + OPEN_EXISTING, + FILE_ATTRIBUTE_NORMAL, + nullptr + ); + return h; + }; + + auto makeInheritableStdHandle = [&](DWORD std_id, bool input_handle) -> HANDLE { + // Prefer CRT descriptors first because MSYS/Cygwin-style processes rely on them + // even when Win32 GetStdHandle() is unset in GUI parents. + int crt_fd = -1; + if (std_id == STD_INPUT_HANDLE) { + crt_fd = _fileno(stdin); + } else if (std_id == STD_OUTPUT_HANDLE) { + crt_fd = _fileno(stdout); + } else if (std_id == STD_ERROR_HANDLE) { + crt_fd = _fileno(stderr); + } + + if (crt_fd >= 0) { + intptr_t osfh = _get_osfhandle(crt_fd); + if (osfh != -1) { + HANDLE dup_from_crt = duplicateAsInheritable(reinterpret_cast(osfh)); + if (dup_from_crt && dup_from_crt != INVALID_HANDLE_VALUE) { + return dup_from_crt; + } + } + } + + HANDLE src = GetStdHandle(std_id); + HANDLE dup = duplicateAsInheritable(src); + if (dup && dup != INVALID_HANDLE_VALUE) { + return dup; + } + return openSpecialOrNul(input_handle); + }; + + HANDLE hIn = makeInheritableStdHandle(STD_INPUT_HANDLE, true); + HANDLE hOut = makeInheritableStdHandle(STD_OUTPUT_HANDLE, false); + HANDLE hErr = makeInheritableStdHandle(STD_ERROR_HANDLE, false); + + // Some runtimes (like Cygwin/MSYS) expect stderr to be truly distinct from stdout. + if (hErr == hOut && hOut && hOut != INVALID_HANDLE_VALUE) { + if (hErr && hErr != INVALID_HANDLE_VALUE) { + CloseHandle(hErr); + } + HANDLE distinct_err = openSpecialOrNul(false); + if (distinct_err && distinct_err != INVALID_HANDLE_VALUE) { + hErr = distinct_err; + } else { + hErr = duplicateAsInheritable(hOut); + } + } + + if (!hIn || hIn == INVALID_HANDLE_VALUE || + !hOut || hOut == INVALID_HANDLE_VALUE || + !hErr || hErr == INVALID_HANDLE_VALUE) { + if (hIn && hIn != INVALID_HANDLE_VALUE) CloseHandle(hIn); + if (hOut && hOut != INVALID_HANDLE_VALUE) CloseHandle(hOut); + if (hErr && hErr != INVALID_HANDLE_VALUE) CloseHandle(hErr); + throw std::runtime_error("Failed to prepare inheritable stdio handles (error " + + std::to_string(GetLastError()) + ")"); + } + + STARTUPINFOW si = {}; + si.cb = sizeof(si); + si.dwFlags = STARTF_USESTDHANDLES; + si.hStdInput = hIn; + si.hStdOutput = hOut; + si.hStdError = hErr; + + PROCESS_INFORMATION pi = {}; + try { + pi = launchWindowsProcess(resolved_args, si, "shared stdio"); + } catch (...) { + closeHandleIfValid(hIn); + closeHandleIfValid(hOut); + closeHandleIfValid(hErr); + throw; + } + + closeHandleIfValid(hIn); + closeHandleIfValid(hOut); + closeHandleIfValid(hErr); + + adoptWindowsProcess(pi); + out_file = File(""); #else proc = Child(resolved_args, BOOST_PROCESS_V1_NAMESPACE::std_in.close()); #endif @@ -1061,7 +1289,6 @@ inline json::object PM::createChildProcess(const std::vector& args, throw std::runtime_error("Failed to create output file: " + temp_path.string() + " (error " + std::to_string(GetLastError()) + ")"); - std::wstring cmd_line = buildCmdLine(resolved_args); STARTUPINFOW si = {}; si.cb = sizeof(si); si.dwFlags = STARTF_USESTDHANDLES; @@ -1070,18 +1297,17 @@ inline json::object PM::createChildProcess(const std::vector& args, si.hStdInput = INVALID_HANDLE_VALUE; PROCESS_INFORMATION pi = {}; - BOOL ok = CreateProcessW(nullptr, cmd_line.data(), - nullptr, nullptr, TRUE, 0, - nullptr, nullptr, &si, &pi); - CloseHandle(hOut); // child inherited its own copy with FILE_SHARE_DELETE - if (!ok) { + try { + pi = launchWindowsProcess(resolved_args, si, ""); + } catch (...) { + closeHandleIfValid(hOut); std::filesystem::remove(temp_path); - throw std::runtime_error("Failed to create process (error " + - std::to_string(GetLastError()) + ")"); + throw; } - pid = static_cast(pi.dwProcessId); - CloseHandle(pi.hThread); + closeHandleIfValid(hOut); // child inherited its own copy with FILE_SHARE_DELETE + + adoptWindowsProcess(pi); // Rename to the final {pid}.txt — works because the child's handle has FILE_SHARE_DELETE std::filesystem::path final_path = proc_dir / (std::to_string(pid) + ".txt"); @@ -1092,11 +1318,6 @@ inline json::object PM::createChildProcess(const std::vector& args, } else { out_file = File(final_path.string()); } - - // Construct Child from the native DWORD pid so the pid_t overload is - // selected instead of the variadic template launch constructor. - proc = Child(pi.dwProcessId); - CloseHandle(pi.hProcess); #else std::string temp_filename = "temp_" + std::to_string(std::chrono::system_clock::now().time_since_epoch().count()) + ".txt"; diff --git a/index.html b/index.html index c407749..21a0240 100644 --- a/index.html +++ b/index.html @@ -40,7 +40,7 @@ "codeRepository": "https://github.com/spur27/RenWeb-Engine", "programmingLanguage": ["C++", "JavaScript", "TypeScript"], "offers": { "@type": "Offer", "price": "0", "priceCurrency": "USD" }, - "softwareVersion": "0.0.7" + "softwareVersion": "0.1.0" } RenWeb Engine diff --git a/info.json b/info.json index 362a4ef..2c303b1 100644 --- a/info.json +++ b/info.json @@ -3,7 +3,7 @@ "description": "Base RenWeb engine. Use with build files to see what it can do!", "license": "BSL", "title": "RenWeb", - "version": "0.0.7", + "version": "0.1.0", "repository": "https://github.com/spur27/RenWeb-Engine", "category": "Utility", "copyright": "Copyright © 2025 Spur27", @@ -22,6 +22,7 @@ "origins": [ "https://www.google.com" ], + "trusted": [ ], "plugin_repositories": [ "https://github.com/spur27/renweb-example-plugin" ] diff --git a/makefile b/makefile index 004953b..96ba796 100644 --- a/makefile +++ b/makefile @@ -528,11 +528,11 @@ $(RES_FILE): resource/app.rc resource/app.ico resource/app.manifest info.json | $(call step,Compiling Resource File,$@) @INFO=./info.json; \ RC_TITLE=$$(sed -n 's/.*"title"[[:space:]]*:[[:space:]]*"\([^"]*\)".*/\1/p' "$$INFO" 2>/dev/null | xargs 2>/dev/null); \ - RC_TITLE=$${RC_TITLE:-}; \ + RC_TITLE=$${RC_TITLE:-$(EXE_NAME)}; \ RC_AUTHOR=$$(sed -n 's/.*"author"[[:space:]]*:[[:space:]]*"\([^"]*\)".*/\1/p' "$$INFO" 2>/dev/null | xargs 2>/dev/null); \ RC_AUTHOR=$${RC_AUTHOR:-}; \ RC_DESCRIPTION=$$(sed -n 's/.*"description"[[:space:]]*:[[:space:]]*"\([^"]*\)".*/\1/p' "$$INFO" 2>/dev/null | xargs 2>/dev/null); \ - RC_DESCRIPTION=$${RC_DESCRIPTION:-}; \ + RC_DESCRIPTION=$${RC_DESCRIPTION:-$$RC_TITLE}; \ RC_COPYRIGHT=$$(sed -n 's/.*"copyright"[[:space:]]*:[[:space:]]*"\([^"]*\)".*/\1/p' "$$INFO" 2>/dev/null | xargs 2>/dev/null); \ RC_COPYRIGHT=$${RC_COPYRIGHT:-}; \ RC_VERSION_CSV=$$(echo "$(EXE_VERSION)" | awk -F. 'NF>=3{printf "%s,%s,%s,0",$$1,$$2,$$3} NF<3{print "0,0,0,0"}' 2>/dev/null); \ diff --git a/resource/app.rc b/resource/app.rc index e9ea785..f638bb2 100644 --- a/resource/app.rc +++ b/resource/app.rc @@ -26,11 +26,12 @@ BEGIN BLOCK "040904b0" BEGIN VALUE "CompanyName", "@RC_AUTHOR@" - VALUE "FileDescription", "@RC_DESCRIPTION@" + VALUE "FileDescription", "@RC_TITLE@" VALUE "FileVersion", "@RC_VERSION_STR@" VALUE "InternalName", "@RC_TITLE@" VALUE "LegalCopyright", "@RC_COPYRIGHT@" VALUE "OriginalFilename","@RC_EXE_FILENAME@" + VALUE "Comments", "@RC_DESCRIPTION@" VALUE "ProductName", "@RC_TITLE@" VALUE "ProductVersion", "@RC_VERSION_STR@" END diff --git a/src/app.cpp b/src/app.cpp index 3da240a..1cd6786 100644 --- a/src/app.cpp +++ b/src/app.cpp @@ -247,6 +247,14 @@ void App::run() { } else { effective_state = current_state; } + const bool initially_shown = + effective_state.contains("initially_shown") && + effective_state.at("initially_shown").is_bool() + ? effective_state.at("initially_shown").as_bool() + : true; + if (!initially_shown) { + this->fns->window_callbacks->run("show", json::array({json::value(false)})); + } this->fns->setState(effective_state); this->fns->setup(effective_state); this->fns->window_callbacks->run("navigate_page", json::value(this->config->current_page)); diff --git a/src/main.cpp b/src/main.cpp index 36f5433..04bdbf5 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -34,9 +34,38 @@ #include #include #include +#include #endif #if defined(_WIN32) +static void configureWindowsConsoleUtf8() { + HANDLE hOut = GetStdHandle(STD_OUTPUT_HANDLE); + DWORD outMode = 0; + if (hOut && hOut != INVALID_HANDLE_VALUE && GetConsoleMode(hOut, &outMode)) { + SetConsoleOutputCP(CP_UTF8); + SetConsoleCP(CP_UTF8); + SetConsoleMode(hOut, outMode | ENABLE_VIRTUAL_TERMINAL_PROCESSING); + std::setlocale(LC_ALL, ".UTF-8"); + } +} + +static void syncWindowsStdHandlesWithConsole() { + HANDLE hIn = CreateFileW(L"CONIN$", GENERIC_READ | GENERIC_WRITE, + FILE_SHARE_READ | FILE_SHARE_WRITE, + nullptr, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, nullptr); + if (hIn && hIn != INVALID_HANDLE_VALUE) { + SetStdHandle(STD_INPUT_HANDLE, hIn); + } + + HANDLE hOut = CreateFileW(L"CONOUT$", GENERIC_READ | GENERIC_WRITE, + FILE_SHARE_READ | FILE_SHARE_WRITE, + nullptr, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, nullptr); + if (hOut && hOut != INVALID_HANDLE_VALUE) { + SetStdHandle(STD_OUTPUT_HANDLE, hOut); + SetStdHandle(STD_ERROR_HANDLE, hOut); + } +} + int WINAPI WinMain(HINSTANCE hInst, HINSTANCE hPrevInst, LPSTR lpCmdLine, int nCmdShow) { (void)hInst; (void)hPrevInst; (void)lpCmdLine; (void)nCmdShow; HANDLE hOut = GetStdHandle(STD_OUTPUT_HANDLE); @@ -57,11 +86,10 @@ int WINAPI WinMain(HINSTANCE hInst, HINSTANCE hPrevInst, LPSTR lpCmdLine, int nC FILE* f; freopen_s(&f, "CONOUT$", "w", stdout); freopen_s(&f, "CONOUT$", "w", stderr); - HANDLE h = GetStdHandle(STD_OUTPUT_HANDLE); - DWORD mode = 0; - if (h != NULL && h != INVALID_HANDLE_VALUE && GetConsoleMode(h, &mode)) - SetConsoleMode(h, mode | ENABLE_VIRTUAL_TERMINAL_PROCESSING); + freopen_s(&f, "CONIN$", "r", stdin); + syncWindowsStdHandlesWithConsole(); } + configureWindowsConsoleUtf8(); auto args = std::make_unique(__argc, __argv); #else int main(int argc, char** argv) { diff --git a/src/webview.cpp b/src/webview.cpp index aeb6ed7..dc6a5fa 100644 --- a/src/webview.cpp +++ b/src/webview.cpp @@ -329,6 +329,7 @@ Webview::Webview(bool debug, void* window, #endif { #if defined(__linux__) + (void)logger; if (!app_id.empty()) g_set_prgname(app_id.c_str()); webview_impl = std::make_unique(debug, window); @@ -425,15 +426,21 @@ void Webview::addWindowCallbacks() { " if (typeof window.renweb.onRenderProcessTerminated === 'undefined') window.renweb.onRenderProcessTerminated = undefined;" " if (typeof window.renweb.onCertificateError === 'undefined') window.renweb.onCertificateError = undefined;" - " window.addEventListener('load', function() {" - " setTimeout(function() {" - " _ready = true;" - " if (typeof _fn === 'function') {" - " (async function() { await _fn(); })()" - " .catch(function(e) { console.error('[renweb] onReady error:', e); });" - " }" - " }, 0);" - " });" + " function _markReady() {" + " if (_ready) return;" + " _ready = true;" + " if (typeof _fn === 'function') {" + " (async function() { await _fn(); })()" + " .catch(function(e) { console.error('[renweb] onReady error:', e); });" + " }" + " }" + " if (document.readyState === 'interactive' || document.readyState === 'complete') {" + " _markReady();" + " } else {" + " document.addEventListener('DOMContentLoaded', function() {" + " _markReady();" + " }, { once: true });" + " }" "})();" ); diff --git a/src/window_functions.cpp b/src/window_functions.cpp index 7d38f79..00a186b 100644 --- a/src/window_functions.cpp +++ b/src/window_functions.cpp @@ -48,6 +48,7 @@ #if defined(_WIN32) #include + #include #include #include #include @@ -252,6 +253,15 @@ namespace WebView2Helper { } namespace WindowHelper { + static std::wstring Utf8ToWide(const std::string& input) { + if (input.empty()) return std::wstring(); + int size = MultiByteToWideChar(CP_UTF8, 0, input.data(), static_cast(input.size()), nullptr, 0); + if (size <= 0) return std::wstring(); + std::wstring output(static_cast(size), L'\0'); + MultiByteToWideChar(CP_UTF8, 0, input.data(), static_cast(input.size()), output.data(), size); + return output; + } + static std::string WideToUtf8(const std::wstring& input) { if (input.empty()) return std::string(); int size = WideCharToMultiByte(CP_UTF8, 0, input.data(), static_cast(input.size()), nullptr, 0, nullptr, nullptr); @@ -628,7 +638,35 @@ WF* WF::setGetSets() { json::object obj = this->getSingleParameter(req).as_object(); int64_t width = obj.at("width").as_int64(); int64_t height = obj.at("height").as_int64(); + #if defined(__APPLE__) + if (width <= 0 || height <= 0) { + this->logger->warn("[function] Ignoring invalid window size request on macOS: " + + std::to_string(width) + "x" + std::to_string(height)); + return; + } + + NSWindow* nsWindow = (NSWindow*)this->app->w->window().value(); + if (!nsWindow) { + this->logger->warn("[function] Cannot set window size on macOS: NSWindow unavailable"); + return; + } + + auto apply_size = ^{ + const BOOL was_visible = [nsWindow isVisible]; + [nsWindow setContentSize:NSMakeSize((CGFloat)width, (CGFloat)height)]; + if (!was_visible) { + [nsWindow orderOut:nil]; + } + }; + + if ([NSThread isMainThread]) { + apply_size(); + } else { + dispatch_sync(dispatch_get_main_queue(), apply_size); + } + #else this->app->w->set_size(width, height); + #endif }) )) // ----------------------------------------- @@ -722,6 +760,11 @@ WF* WF::setGetSets() { SWP_NOMOVE | SWP_NOSIZE | SWP_NOZORDER | SWP_NOACTIVATE | SWP_FRAMECHANGED); } #elif defined(__APPLE__) + if (this->saved_states.find("window_visible") != this->saved_states.end() && + this->saved_states["window_visible"].is_bool() && + !this->saved_states["window_visible"].as_bool()) { + return; + } NSWindow* nsWindow = (NSWindow*)this->app->w->window().value(); NSUInteger styleMask = [nsWindow styleMask]; if (has_titlebar) { @@ -735,6 +778,11 @@ WF* WF::setGetSets() { [nsWindow orderOut:nil]; } #elif defined(__linux__) + if (this->saved_states.find("window_visible") != this->saved_states.end() && + this->saved_states["window_visible"].is_bool() && + !this->saved_states["window_visible"].as_bool()) { + return; + } auto window_widget = this->app->w->window().value(); auto apply_titlebar = [&](GtkWidget* w) { if (has_titlebar) { @@ -979,6 +1027,10 @@ WF* WF::setGetSets() { ->add("fullscreen", std::make_pair( std::function([this]() -> json::value { #if defined(_WIN32) + if (this->saved_states.find("fullscreen_active") != this->saved_states.end() && + this->saved_states["fullscreen_active"].is_bool()) { + return this->saved_states["fullscreen_active"]; + } auto window_result = this->app->w->window(); if (window_result.has_value()) { HWND hwnd = static_cast(window_result.value()); @@ -1009,18 +1061,106 @@ WF* WF::setGetSets() { if (window_result.has_value()) { HWND hwnd = static_cast(window_result.value()); if (fullscreen) { - SetWindowLongPtr(hwnd, GWL_STYLE, WS_POPUP); - if (IsWindowVisible(hwnd)) { - ShowWindow(hwnd, SW_MAXIMIZE); + if (this->saved_states.find("fullscreen_prev_style") == this->saved_states.end() || + !this->saved_states["fullscreen_prev_style"].is_int64()) { + const LONG_PTR prev_style = GetWindowLongPtr(hwnd, GWL_STYLE); + this->saved_states["fullscreen_prev_style"] = json::value(static_cast(prev_style)); + + WINDOWPLACEMENT wp = { sizeof(WINDOWPLACEMENT) }; + if (GetWindowPlacement(hwnd, &wp)) { + this->saved_states["fullscreen_prev_show_cmd"] = json::value(static_cast(wp.showCmd)); + this->saved_states["fullscreen_prev_left"] = json::value(static_cast(wp.rcNormalPosition.left)); + this->saved_states["fullscreen_prev_top"] = json::value(static_cast(wp.rcNormalPosition.top)); + this->saved_states["fullscreen_prev_right"] = json::value(static_cast(wp.rcNormalPosition.right)); + this->saved_states["fullscreen_prev_bottom"] = json::value(static_cast(wp.rcNormalPosition.bottom)); + } } + + HMONITOR monitor = MonitorFromWindow(hwnd, MONITOR_DEFAULTTONEAREST); + MONITORINFO monitor_info = { sizeof(MONITORINFO) }; + if (!GetMonitorInfo(monitor, &monitor_info)) { + this->logger->error("[function] Failed to get monitor info for fullscreen"); + return; + } + + LONG_PTR style = GetWindowLongPtr(hwnd, GWL_STYLE); + style &= ~(WS_CAPTION | WS_THICKFRAME); + SetWindowLongPtr(hwnd, GWL_STYLE, style); + + SetWindowPos( + hwnd, + HWND_TOP, + monitor_info.rcMonitor.left, + monitor_info.rcMonitor.top, + monitor_info.rcMonitor.right - monitor_info.rcMonitor.left, + monitor_info.rcMonitor.bottom - monitor_info.rcMonitor.top, + SWP_NOOWNERZORDER | SWP_FRAMECHANGED + ); + ShowWindow(hwnd, SW_SHOW); + this->saved_states["fullscreen_active"] = json::value(true); } else { - SetWindowLongPtr(hwnd, GWL_STYLE, WS_OVERLAPPEDWINDOW); - if (IsWindowVisible(hwnd) && (IsZoomed(hwnd) || IsIconic(hwnd))) { + if (this->saved_states.find("fullscreen_active") == this->saved_states.end() || + !this->saved_states["fullscreen_active"].is_bool() || + !this->saved_states["fullscreen_active"].as_bool()) { + return; + } + + LONG_PTR restore_style = GetWindowLongPtr(hwnd, GWL_STYLE); + + if (this->saved_states.find("fullscreen_prev_style") != this->saved_states.end() && + this->saved_states["fullscreen_prev_style"].is_int64()) { + restore_style = static_cast(this->saved_states["fullscreen_prev_style"].as_int64()); + } + + SetWindowLongPtr(hwnd, GWL_STYLE, restore_style); + + LONG left = 100; + LONG top = 100; + LONG right = 1280; + LONG bottom = 840; + if (this->saved_states.find("fullscreen_prev_left") != this->saved_states.end() && this->saved_states["fullscreen_prev_left"].is_int64()) { + left = static_cast(this->saved_states["fullscreen_prev_left"].as_int64()); + } + if (this->saved_states.find("fullscreen_prev_top") != this->saved_states.end() && this->saved_states["fullscreen_prev_top"].is_int64()) { + top = static_cast(this->saved_states["fullscreen_prev_top"].as_int64()); + } + if (this->saved_states.find("fullscreen_prev_right") != this->saved_states.end() && this->saved_states["fullscreen_prev_right"].is_int64()) { + right = static_cast(this->saved_states["fullscreen_prev_right"].as_int64()); + } + if (this->saved_states.find("fullscreen_prev_bottom") != this->saved_states.end() && this->saved_states["fullscreen_prev_bottom"].is_int64()) { + bottom = static_cast(this->saved_states["fullscreen_prev_bottom"].as_int64()); + } + + SetWindowPos( + hwnd, + nullptr, + left, + top, + right - left, + bottom - top, + SWP_NOZORDER | SWP_NOOWNERZORDER | SWP_FRAMECHANGED + ); + + UINT show_cmd = SW_SHOWNORMAL; + if (this->saved_states.find("fullscreen_prev_show_cmd") != this->saved_states.end() && + this->saved_states["fullscreen_prev_show_cmd"].is_int64()) { + show_cmd = static_cast(this->saved_states["fullscreen_prev_show_cmd"].as_int64()); + } + if (show_cmd == SW_SHOWMAXIMIZED || show_cmd == SW_MAXIMIZE) { + ShowWindow(hwnd, SW_MAXIMIZE); + } else { ShowWindow(hwnd, SW_RESTORE); } + ShowWindow(hwnd, SW_SHOW); + + this->saved_states.erase("fullscreen_prev_style"); + this->saved_states.erase("fullscreen_prev_show_cmd"); + this->saved_states.erase("fullscreen_prev_left"); + this->saved_states.erase("fullscreen_prev_top"); + this->saved_states.erase("fullscreen_prev_right"); + this->saved_states.erase("fullscreen_prev_bottom"); + this->saved_states["fullscreen_active"] = json::value(false); } - SetWindowPos(hwnd, NULL, 0, 0, 0, 0, - SWP_NOMOVE | SWP_NOSIZE | SWP_NOZORDER | SWP_NOACTIVATE | SWP_FRAMECHANGED); } #elif defined(__APPLE__) NSWindow* nsWindow = (NSWindow*)this->app->w->window().value(); @@ -1164,6 +1304,61 @@ WF* WF::setWindowCallbacks() { auto window_widget = this->app->w->window().value(); return json::value(gtk_window_has_toplevel_focus(GTK_WINDOW(window_widget))); #endif + }))->add("is_shown", + std::function([this](const json::value& req) -> json::value { + (void)req; + #if defined(_WIN32) + auto window_result = this->app->w->window(); + if (window_result.has_value()) { + HWND hwnd = static_cast(window_result.value()); + return json::value(IsWindowVisible(hwnd) != FALSE); + } + return json::value(false); + #elif defined(__APPLE__) + NSWindow* nsWindow = (NSWindow*)this->app->w->window().value(); + return json::value([nsWindow isVisible] == YES); + #elif defined(__linux__) + auto window_widget = this->app->w->window().value(); + return json::value(gtk_widget_get_visible(GTK_WIDGET(window_widget)) != FALSE); + #endif + }))->add("focus", + std::function([this](const json::value& req) -> json::value { + (void)req; + #if defined(_WIN32) + auto window_result = this->app->w->window(); + if (window_result.has_value()) { + HWND hwnd = static_cast(window_result.value()); + if (hwnd) { + if (IsIconic(hwnd)) { + ShowWindow(hwnd, SW_RESTORE); + } else { + ShowWindow(hwnd, SW_SHOW); + } + // Force a z-order promotion pass on Windows because + // SetForegroundWindow may be denied depending on focus rules. + SetWindowPos(hwnd, HWND_TOPMOST, 0, 0, 0, 0, + SWP_NOMOVE | SWP_NOSIZE | SWP_NOACTIVATE); + SetWindowPos(hwnd, HWND_NOTOPMOST, 0, 0, 0, 0, + SWP_NOMOVE | SWP_NOSIZE | SWP_NOACTIVATE); + + BringWindowToTop(hwnd); + SetForegroundWindow(hwnd); + SetFocus(hwnd); + } + } + #elif defined(__APPLE__) + NSWindow* nsWindow = (NSWindow*)this->app->w->window().value(); + if (nsWindow) { + dispatch_async(dispatch_get_main_queue(), ^{ + [NSApp activateIgnoringOtherApps:YES]; + [nsWindow makeKeyAndOrderFront:nil]; + }); + } + #elif defined(__linux__) + auto window_widget = this->app->w->window().value(); + gtk_window_present(GTK_WINDOW(window_widget)); + #endif + return json::value(nullptr); }))->add("show", std::function([this](const json::value& req) -> json::value { const bool show_window = this->getSingleParameter(req).as_bool(); @@ -1237,11 +1432,18 @@ WF* WF::setWindowCallbacks() { } #elif defined(__linux__) auto window_widget = this->app->w->window().value(); + auto webview_widget = this->app->w->widget(); if (show_window) { - gtk_widget_show_all(GTK_WIDGET(window_widget)); + gtk_widget_show(GTK_WIDGET(window_widget)); + if (webview_widget.has_value() && webview_widget.value()) { + GtkWidget* wk_widget = GTK_WIDGET(webview_widget.value()); + gtk_widget_show(wk_widget); + gtk_widget_queue_draw(wk_widget); + } + gtk_widget_queue_draw(GTK_WIDGET(window_widget)); } else { - if (!gtk_widget_get_realized(GTK_WIDGET(window_widget))) { - gtk_widget_realize(GTK_WIDGET(window_widget)); + if (webview_widget.has_value() && webview_widget.value()) { + gtk_widget_hide(GTK_WIDGET(webview_widget.value())); } gtk_widget_hide(GTK_WIDGET(window_widget)); } @@ -1573,6 +1775,7 @@ WF* WF::setWindowCallbacks() { }))->add("find_in_page", std::function([this](const json::value& req) -> json::value { const std::string search_text = this->getSingleParameter(req).as_string().c_str(); + this->saved_states["find_text"] = json::value(search_text); #if defined(_WIN32) // Use window.find() - most reliable cross-version approach // Escape single quotes in search text @@ -1623,12 +1826,24 @@ WF* WF::setWindowCallbacks() { }))->add("find_next", std::function([this](const json::value& req) -> json::value { (void)req; + if (this->saved_states.find("find_text") == this->saved_states.end() || + !this->saved_states["find_text"].is_string()) { + this->logger->debug("[function] find_next called before find_in_page; ignoring"); + return json::value(nullptr); + } + const std::string search_text = this->saved_states["find_text"].as_string().c_str(); + std::string escaped_text = search_text; + size_t pos = 0; + while ((pos = escaped_text.find("'", pos)) != std::string::npos) { + escaped_text.replace(pos, 1, "\\'"); + pos += 2; + } #if defined(_WIN32) - // Use window.find() with forward direction - std::string js = "window.find('', false, false, false, false, true, false);"; + std::string js = "window.find('" + escaped_text + "', false, false, true, false, true, false);"; this->app->w->eval(js); #elif defined(__APPLE__) - this->logger->debug("[function] apple doesn't have bindings for this findNext"); + std::string js = "window.find('" + escaped_text + "', false, false, true, false, true, false);"; + this->app->w->eval(js); #elif defined(__linux__) auto webview_widget = this->app->w->widget().value(); WebKitFindController* find_controller = webkit_web_view_get_find_controller(WEBKIT_WEB_VIEW(webview_widget)); @@ -1638,12 +1853,24 @@ WF* WF::setWindowCallbacks() { }))->add("find_previous", std::function([this](const json::value& req) -> json::value { (void)req; + if (this->saved_states.find("find_text") == this->saved_states.end() || + !this->saved_states["find_text"].is_string()) { + this->logger->debug("[function] find_previous called before find_in_page; ignoring"); + return json::value(nullptr); + } + const std::string search_text = this->saved_states["find_text"].as_string().c_str(); + std::string escaped_text = search_text; + size_t pos = 0; + while ((pos = escaped_text.find("'", pos)) != std::string::npos) { + escaped_text.replace(pos, 1, "\\'"); + pos += 2; + } #if defined(_WIN32) - // Use window.find() with backward direction - std::string js = "window.find('', false, true, false, false, true, false);"; + std::string js = "window.find('" + escaped_text + "', false, true, true, false, true, false);"; this->app->w->eval(js); #elif defined(__APPLE__) - this->logger->debug("[function] apple doesn't have bindings for this findPrevious"); + std::string js = "window.find('" + escaped_text + "', false, true, true, false, true, false);"; + this->app->w->eval(js); #elif defined(__linux__) auto webview_widget = this->app->w->widget().value(); WebKitFindController* find_controller = webkit_web_view_get_find_controller(WEBKIT_WEB_VIEW(webview_widget)); @@ -1662,7 +1889,31 @@ WF* WF::setWindowCallbacks() { if (window_result.has_value()) { id webview = getWKWebViewFromWindow(window_result.value()); if (webview) { - [webview findString:@"" withConfiguration:nil completionHandler:nil]; + if ([webview respondsToSelector:@selector(findString:withConfiguration:completionHandler:)]) { + // Use a sentinel string that won't match any real content to dismiss + // the native find overlay and clear WKFindConfiguration highlights. + NSString* noMatchSentinel = @"\uffff"; + id config = [[NSClassFromString(@"WKFindConfiguration") alloc] init]; + + void (^completionHandler)(id) = ^(id result) { + (void)result; + }; + + SEL selector = @selector(findString:withConfiguration:completionHandler:); + NSMethodSignature* signature = [webview methodSignatureForSelector:selector]; + if (signature) { + NSInvocation* invocation = [NSInvocation invocationWithMethodSignature:signature]; + [invocation setTarget:webview]; + [invocation setSelector:selector]; + [invocation setArgument:&noMatchSentinel atIndex:2]; + [invocation setArgument:&config atIndex:3]; + [invocation setArgument:&completionHandler atIndex:4]; + [invocation invoke]; + } + } + // Always clear JS-based selection (from find_next / find_previous). + std::string js = "if (window.getSelection) { window.getSelection().removeAllRanges(); }"; + this->app->w->eval(js); } } #elif defined(__linux__) @@ -1670,6 +1921,7 @@ WF* WF::setWindowCallbacks() { WebKitFindController* find_controller = webkit_web_view_get_find_controller(WEBKIT_WEB_VIEW(webview_widget)); webkit_find_controller_search_finish(find_controller); #endif + this->saved_states.erase("find_text"); return json::value(nullptr); })); return this; @@ -1754,7 +2006,7 @@ WF* WF::setFileSystemCallbacks() { std::filesystem::path parent_path = path.parent_path(); - if (!std::filesystem::exists(parent_path)) { + if (!parent_path.empty() && !std::filesystem::exists(parent_path)) { std::error_code ec; std::filesystem::create_directories(parent_path, ec); if (ec) { @@ -1851,10 +2103,10 @@ WF* WF::setFileSystemCallbacks() { std::error_code ec; if (!std::filesystem::exists(orig_path)) { this->logger->error("[function] Can't rename path that doesn't exist: " + orig_path.string()); - return json::value(nullptr); + return json::value(false); } else if (std::filesystem::exists(new_path) && !overwrite) { this->logger->error("[function] Can't overwrite already-existing new path if settings.overwrite is false: " + new_path.string()); - return json::value(nullptr); + return json::value(false); } else if (std::filesystem::exists(new_path)) { if (std::filesystem::is_directory(new_path)) { std::filesystem::remove_all(new_path, ec); @@ -1866,8 +2118,9 @@ WF* WF::setFileSystemCallbacks() { return json::value(false); } } - if (!std::filesystem::exists(new_path.parent_path())) { - std::filesystem::create_directories(new_path.parent_path(), ec); + const std::filesystem::path new_parent = new_path.parent_path(); + if (!new_parent.empty() && !std::filesystem::exists(new_parent)) { + std::filesystem::create_directories(new_parent, ec); if (ec) { this->logger->error("[function] " + ec.message()); return json::value(false); @@ -1908,10 +2161,10 @@ WF* WF::setFileSystemCallbacks() { std::error_code ec; if (!std::filesystem::exists(orig_path)) { this->logger->error("[function] Can't copy path that doesn't exist: " + orig_path.string()); - return json::value(nullptr); + return json::value(false); } else if (std::filesystem::exists(new_path) && !overwrite) { this->logger->error("[function] Can't overwrite already-existing new path if settings.overwrite is false: " + new_path.string()); - return json::value(nullptr); + return json::value(false); } else if (std::filesystem::exists(new_path)) { if (std::filesystem::is_directory(new_path)) { std::filesystem::remove_all(new_path, ec); @@ -1923,8 +2176,9 @@ WF* WF::setFileSystemCallbacks() { return json::value(false); } } - if (!std::filesystem::exists(new_path.parent_path())) { - std::filesystem::create_directories(new_path.parent_path(), ec); + const std::filesystem::path new_parent = new_path.parent_path(); + if (!new_parent.empty() && !std::filesystem::exists(new_parent)) { + std::filesystem::create_directories(new_parent, ec); if (ec) { this->logger->error("[function] " + ec.message()); return json::value(false); @@ -1959,32 +2213,398 @@ WF* WF::setFileSystemCallbacks() { } } return json::value(tmp_path.string()); - }))->add("download_uri", + }))->add("choose_files", std::function([this](const json::value& req) -> json::value { - const std::string uri = this->getSingleParameter(req).as_string().c_str(); + bool multiple = false; + bool directories = false; + std::vector extensions; + + json::value param = this->getSingleParameter(req); + if (param.is_object()) { + const json::object& options = param.as_object(); + if (options.contains("multiple") && options.at("multiple").is_bool()) { + multiple = options.at("multiple").as_bool(); + } + if (options.contains("directories") && options.at("directories").is_bool()) { + directories = options.at("directories").as_bool(); + } + if (options.contains("extensions") && options.at("extensions").is_array()) { + const json::array& extension_values = options.at("extensions").as_array(); + for (const json::value& extension_value : extension_values) { + if (!extension_value.is_string()) { + continue; + } + + std::string extension = extension_value.as_string().c_str(); + const size_t first_non_whitespace = extension.find_first_not_of(" \t\r\n"); + if (first_non_whitespace == std::string::npos) { + continue; + } + extension.erase(0, first_non_whitespace); + extension.erase(extension.find_last_not_of(" \t\r\n") + 1); + if (extension.empty()) { + continue; + } + if (extension == "*" || extension == "*.*") { + extensions.clear(); + break; + } + if (startsWith(extension, "*.")) { + extension.erase(0, 2); + } else if (!extension.empty() && extension.front() == '.') { + extension.erase(0, 1); + } + if (extension.empty()) { + continue; + } + if (std::find(extensions.begin(), extensions.end(), extension) == extensions.end()) { + extensions.push_back(extension); + } + } + } + } + + const bool use_extension_filter = !directories && !extensions.empty(); + + auto format_paths = [multiple](const std::vector& paths) -> json::value { + if (paths.empty()) { + return json::value(nullptr); + } + if (!multiple) { + return json::value(paths.front()); + } + json::array output; + for (const auto& path : paths) { + output.push_back(json::value(path)); + } + return output; + }; + #if defined(_WIN32) - // Navigate to URI - WebView2 will automatically prompt download for non-HTML content - std::wstring wuri(uri.begin(), uri.end()); - this->app->w->navigate(uri); - this->logger->info("[function] Initiated download for: " + uri); + HRESULT init_hr = CoInitializeEx(nullptr, COINIT_APARTMENTTHREADED | COINIT_DISABLE_OLE1DDE); + const bool did_init_com = SUCCEEDED(init_hr) || init_hr == S_FALSE; + + IFileOpenDialog* dialog = nullptr; + HRESULT hr = CoCreateInstance(CLSID_FileOpenDialog, nullptr, CLSCTX_INPROC_SERVER, IID_PPV_ARGS(&dialog)); + if (FAILED(hr) || !dialog) { + if (did_init_com) { + CoUninitialize(); + } + this->logger->error("[function] Failed to create Windows file dialog. HRESULT=" + std::to_string(static_cast(hr))); + return json::value(nullptr); + } + + DWORD options = 0; + dialog->GetOptions(&options); + options |= FOS_FORCEFILESYSTEM | FOS_PATHMUSTEXIST; + if (multiple) { + options |= FOS_ALLOWMULTISELECT; + } + if (directories) { + options |= FOS_PICKFOLDERS; + } else { + options |= FOS_FILEMUSTEXIST; + } + dialog->SetOptions(options); + + std::vector filter_names; + std::vector filter_patterns; + std::vector filter_specs; + std::wstring default_extension; + if (use_extension_filter) { + std::wstring allowed_patterns; + for (size_t index = 0; index < extensions.size(); ++index) { + if (!allowed_patterns.empty()) { + allowed_patterns += L";"; + } + allowed_patterns += L"*."; + allowed_patterns += WindowHelper::Utf8ToWide(extensions[index]); + } + + filter_names.push_back(L"Allowed Files"); + filter_patterns.push_back(allowed_patterns); + filter_names.push_back(L"All Files"); + filter_patterns.push_back(L"*.*"); + + for (size_t index = 0; index < filter_names.size(); ++index) { + filter_specs.push_back(COMDLG_FILTERSPEC{ + filter_names[index].c_str(), + filter_patterns[index].c_str() + }); + } + + default_extension = WindowHelper::Utf8ToWide(extensions.front()); + dialog->SetFileTypes(static_cast(filter_specs.size()), filter_specs.data()); + dialog->SetFileTypeIndex(1); + dialog->SetDefaultExtension(default_extension.c_str()); + } + + hr = dialog->Show(WindowHelper::GetHWND(this->app)); + if (hr == HRESULT_FROM_WIN32(ERROR_CANCELLED)) { + dialog->Release(); + if (did_init_com) { + CoUninitialize(); + } + return json::value(nullptr); + } + if (FAILED(hr)) { + dialog->Release(); + if (did_init_com) { + CoUninitialize(); + } + this->logger->error("[function] Windows file dialog failed to show. HRESULT=" + std::to_string(static_cast(hr))); + return json::value(nullptr); + } + + std::vector paths; + if (multiple) { + IShellItemArray* items = nullptr; + hr = dialog->GetResults(&items); + if (SUCCEEDED(hr) && items) { + DWORD count = 0; + items->GetCount(&count); + for (DWORD index = 0; index < count; ++index) { + IShellItem* item = nullptr; + if (SUCCEEDED(items->GetItemAt(index, &item)) && item) { + PWSTR raw_path = nullptr; + if (SUCCEEDED(item->GetDisplayName(SIGDN_FILESYSPATH, &raw_path)) && raw_path) { + paths.push_back(WindowHelper::WideToUtf8(raw_path)); + CoTaskMemFree(raw_path); + } + item->Release(); + } + } + items->Release(); + } + } else { + IShellItem* item = nullptr; + hr = dialog->GetResult(&item); + if (SUCCEEDED(hr) && item) { + PWSTR raw_path = nullptr; + if (SUCCEEDED(item->GetDisplayName(SIGDN_FILESYSPATH, &raw_path)) && raw_path) { + paths.push_back(WindowHelper::WideToUtf8(raw_path)); + CoTaskMemFree(raw_path); + } + item->Release(); + } + } + + dialog->Release(); + if (did_init_com) { + CoUninitialize(); + } + return format_paths(paths); #elif defined(__APPLE__) + NSOpenPanel* panel = [NSOpenPanel openPanel]; + [panel setCanChooseFiles:directories ? NO : YES]; + [panel setCanChooseDirectories:directories ? YES : NO]; + [panel setAllowsMultipleSelection:multiple ? YES : NO]; + [panel setResolvesAliases:YES]; + if (use_extension_filter) { + NSMutableArray* allowed_file_types = [NSMutableArray arrayWithCapacity:extensions.size()]; + for (const std::string& extension : extensions) { + [allowed_file_types addObject:[NSString stringWithUTF8String:extension.c_str()]]; + } + [panel setAllowedFileTypes:allowed_file_types]; + [panel setAllowsOtherFileTypes:NO]; + } + + NSInteger response = [panel runModal]; + if (response != NSModalResponseOK) { + return json::value(nullptr); + } + + std::vector paths; + for (NSURL* url in [panel URLs]) { + NSString* path = [url path]; + if (path) { + paths.push_back(std::string([path UTF8String])); + } + } + return format_paths(paths); + #elif defined(__linux__) + GtkFileChooserAction action = directories + ? GTK_FILE_CHOOSER_ACTION_SELECT_FOLDER + : GTK_FILE_CHOOSER_ACTION_OPEN; + GtkWindow* parent = nullptr; auto window_result = this->app->w->window(); if (window_result.has_value()) { - id webview = getWKWebViewFromWindow(window_result.value()); - if (webview) { - NSString* urlString = [NSString stringWithUTF8String:uri.c_str()]; - NSURL* url = [NSURL URLWithString:urlString]; - NSURLRequest* request = [NSURLRequest requestWithURL:url]; - if ([webview respondsToSelector:@selector(startDownloadUsingRequest:completionHandler:)]) { - [webview performSelector:@selector(startDownloadUsingRequest:completionHandler:) withObject:request withObject:nil]; - } else { - this->logger->warn("[function] WKWebView download not available on this macOS version"); + parent = GTK_WINDOW(window_result.value()); + } + + GtkFileChooserNative* dialog = gtk_file_chooser_native_new( + directories ? "Choose Directories" : "Choose Files", + parent, + action, + directories ? "Select" : "Open", + "Cancel" + ); + gtk_file_chooser_set_select_multiple(GTK_FILE_CHOOSER(dialog), multiple ? TRUE : FALSE); + if (use_extension_filter) { + GtkFileFilter* allowed_filter = gtk_file_filter_new(); + gtk_file_filter_set_name(allowed_filter, "Allowed files"); + for (const std::string& extension : extensions) { + const std::string pattern = "*." + extension; + gtk_file_filter_add_pattern(allowed_filter, pattern.c_str()); + } + gtk_file_chooser_add_filter(GTK_FILE_CHOOSER(dialog), allowed_filter); + gtk_file_chooser_set_filter(GTK_FILE_CHOOSER(dialog), allowed_filter); + + GtkFileFilter* all_filter = gtk_file_filter_new(); + gtk_file_filter_set_name(all_filter, "All files"); + gtk_file_filter_add_pattern(all_filter, "*"); + gtk_file_chooser_add_filter(GTK_FILE_CHOOSER(dialog), all_filter); + } + + gint response = gtk_native_dialog_run(GTK_NATIVE_DIALOG(dialog)); + if (response != GTK_RESPONSE_ACCEPT) { + g_object_unref(dialog); + return json::value(nullptr); + } + + std::vector paths; + if (multiple) { + GSList* filenames = gtk_file_chooser_get_filenames(GTK_FILE_CHOOSER(dialog)); + for (GSList* current = filenames; current != nullptr; current = current->next) { + if (current->data) { + paths.push_back(static_cast(current->data)); } } + g_slist_free_full(filenames, g_free); + } else { + gchar* filename = gtk_file_chooser_get_filename(GTK_FILE_CHOOSER(dialog)); + if (filename) { + paths.push_back(filename); + g_free(filename); + } + } + + g_object_unref(dialog); + return format_paths(paths); + #endif + }))->add("download_uri", + std::function([this](const json::value& req) -> json::value { + std::string uri; + std::string destination_path; + + if (req.is_array()) { + const json::array& params = req.as_array(); + if (!params.empty() && params[0].is_string()) { + uri = params[0].as_string().c_str(); + } + if (params.size() > 1 && params[1].is_string()) { + destination_path = params[1].as_string().c_str(); + } + } else if (req.is_string()) { + uri = req.as_string().c_str(); + } + + if (uri.empty()) { + this->logger->error("[function] downloadUri requires a non-empty URI"); + return json::value(nullptr); + } + #if defined(_WIN32) + if (!destination_path.empty()) { + const HRESULT hr = URLDownloadToFileA(nullptr, uri.c_str(), destination_path.c_str(), 0, nullptr); + if (SUCCEEDED(hr)) { + this->logger->info("[function] Downloaded URI to path: " + destination_path); + } else { + this->logger->error("[function] Failed to download URI to path. HRESULT=" + std::to_string(static_cast(hr)) + " URI=" + uri + " PATH=" + destination_path); + } + } else { + this->app->w->navigate(uri); + this->logger->info("[function] Initiated download for: " + uri); } + #elif defined(__APPLE__) + NSString* urlString = [NSString stringWithUTF8String:uri.c_str()]; + NSURL* url = [NSURL URLWithString:urlString]; + if (!url) { + this->logger->error("[function] Invalid URI for download on macOS: " + uri); + return json::value(nullptr); + } + + std::filesystem::path output_path; + if (!destination_path.empty()) { + output_path = std::filesystem::path(destination_path); + } else { + NSArray* downloads_dirs = NSSearchPathForDirectoriesInDomains(NSDownloadsDirectory, NSUserDomainMask, YES); + NSString* downloads_dir = ([downloads_dirs count] > 0) ? downloads_dirs[0] : [NSHomeDirectory() stringByAppendingPathComponent:@"Downloads"]; + NSString* filename = [[url path] lastPathComponent]; + if (!filename || [filename length] == 0) { + filename = @"download.bin"; + } + output_path = std::filesystem::path([downloads_dir UTF8String]) / std::string([filename UTF8String]); + } + + std::error_code ec; + output_path = std::filesystem::absolute(output_path, ec); + if (ec) { + this->logger->error("[function] Failed to resolve destination path on macOS: " + ec.message()); + return json::value(nullptr); + } + + const std::filesystem::path output_parent = output_path.parent_path(); + if (!output_parent.empty() && !std::filesystem::exists(output_parent)) { + std::filesystem::create_directories(output_parent, ec); + if (ec) { + this->logger->error("[function] Failed to create destination directory on macOS: " + ec.message()); + return json::value(nullptr); + } + } + + NSError* readError = nil; + NSData* data = [NSData dataWithContentsOfURL:url options:0 error:&readError]; + if (!data) { + const std::string errMsg = readError ? std::string([[readError localizedDescription] UTF8String]) : std::string("unknown"); + this->logger->error("[function] Failed to download URI on macOS: " + errMsg + " URI=" + uri); + return json::value(nullptr); + } + + NSString* outputPathNs = [NSString stringWithUTF8String:output_path.string().c_str()]; + NSError* writeError = nil; + if (![data writeToFile:outputPathNs options:NSDataWritingAtomic error:&writeError]) { + const std::string errMsg = writeError ? std::string([[writeError localizedDescription] UTF8String]) : std::string("unknown"); + this->logger->error("[function] Failed to write downloaded file on macOS: " + errMsg + " PATH=" + output_path.string()); + return json::value(nullptr); + } + + this->logger->info("[function] Downloaded URI to path: " + output_path.string()); #elif defined(__linux__) auto webview_widget = this->app->w->widget().value(); - webkit_web_view_download_uri(WEBKIT_WEB_VIEW(webview_widget), uri.c_str()); + WebKitDownload* download = webkit_web_view_download_uri(WEBKIT_WEB_VIEW(webview_widget), uri.c_str()); + if (!download) { + this->logger->error("[function] Failed to initiate download on Linux for URI: " + uri); + return json::value(nullptr); + } + + if (!destination_path.empty()) { + std::error_code ec; + std::filesystem::path output_path(destination_path); + output_path = std::filesystem::absolute(output_path, ec); + if (ec) { + this->logger->error("[function] Failed to resolve destination path on Linux: " + ec.message()); + return json::value(nullptr); + } + + const std::filesystem::path output_parent = output_path.parent_path(); + if (!output_parent.empty() && !std::filesystem::exists(output_parent)) { + std::filesystem::create_directories(output_parent, ec); + if (ec) { + this->logger->error("[function] Failed to create destination directory on Linux: " + ec.message()); + return json::value(nullptr); + } + } + + gchar* dest_uri = g_filename_to_uri(output_path.string().c_str(), nullptr, nullptr); + if (!dest_uri) { + this->logger->error("[function] Failed to convert destination path to file URI on Linux: " + output_path.string()); + return json::value(nullptr); + } + + webkit_download_set_destination(download, dest_uri); + g_free(dest_uri); + this->logger->info("[function] Download destination set to: " + output_path.string()); + } #endif return json::value(nullptr); })); @@ -2271,15 +2891,35 @@ WF* WF::setDebugCallbacks() { std::function([this](const json::value& req) -> json::value { (void)req; #if defined(_WIN32) - this->logger->error("[function] Can't close devtools programmatically on windows."); + this->logger->warn("[function] close_devtools is best-effort on Windows and is not supported by WebView2."); #elif defined(__APPLE__) - // macOS WKWebView inspector close + // macOS WKWebView inspector close via private API is best-effort only. auto window_result = this->app->w->window(); if (window_result.has_value()) { id webview = getWKWebViewFromWindow(window_result.value()); if (webview) { - [(id)[webview performSelector:@selector(_inspector)] performSelector:@selector(close)]; + @try { + id inspector = nil; + if ([webview respondsToSelector:@selector(_inspector)]) { + inspector = [webview performSelector:@selector(_inspector)]; + } + + if (inspector && [inspector respondsToSelector:@selector(close)]) { + [inspector performSelector:@selector(close)]; + } else if (inspector && [inspector respondsToSelector:@selector(hide)]) { + [inspector performSelector:@selector(hide)]; + } else { + this->logger->debug("[function] close_devtools inspector handle unavailable on macOS; ignoring"); + } + } @catch (NSException* exception) { + this->logger->warn(std::string("[function] close_devtools best-effort close failed on macOS: ") + + [[exception reason] UTF8String]); + } + } else { + this->logger->debug("[function] close_devtools webview unavailable on macOS; ignoring"); } + } else { + this->logger->debug("[function] close_devtools window unavailable on macOS; ignoring"); } #elif defined(__linux__) auto webview_widget = this->app->w->widget().value(); diff --git a/web/api/index.d.ts b/web/api/index.d.ts index c92c641..8d40241 100644 --- a/web/api/index.d.ts +++ b/web/api/index.d.ts @@ -279,6 +279,16 @@ export declare namespace Window { * @returns Promise that resolves to true if window is focused */ function isFocus(): Promise; + /** + * Checks if the window is currently visible. + * @returns Promise that resolves to true if window is shown + */ + function isShown(): Promise; + /** + * Requests focus for the current window. + * @returns Promise that resolves when the focus request has been issued + */ + function focus(): Promise; /** * Shows or hides the window. * @param is_window_shown - Whether to show the window @@ -518,13 +528,24 @@ export declare namespace FS { function getTmpDirPath(options?: { create?: boolean; }): Promise; + /** + * Opens the native file chooser and returns absolute selected path(s). + * @param options - Picker options + * @param options.multiple - Whether multiple selections are allowed (default: false) + * @param options.directories - Whether to select directories only (default: false) + * @returns Promise that resolves to a single path, an array of paths, or null if cancelled + */ + function chooseFiles(options?: { + multiple?: boolean; + directories?: boolean; + }): Promise; /** * Downloads a file from a URI to a local path. * @param uri - URI to download from * @param path - Local path to save the file * @returns Promise that resolves when download is complete */ - function downloadUri(uri: string, path: string): Promise; + function downloadUri(uri: string, path?: string): Promise; } /** * Configuration management functions. diff --git a/web/api/index.js b/web/api/index.js index bae179e..d4dd7ff 100644 --- a/web/api/index.js +++ b/web/api/index.js @@ -307,6 +307,18 @@ export var Window; */ async function isFocus() { return await BIND_is_focus(null); } Window.isFocus = isFocus; + /** + * Checks if the window is currently visible. + * @returns Promise that resolves to true if window is shown + */ + async function isShown() { return await BIND_is_shown(null); } + Window.isShown = isShown; + /** + * Requests focus for the current window. + * @returns Promise that resolves when the focus request has been issued + */ + async function focus() { await BIND_focus(null); } + Window.focus = focus; /** * Shows or hides the window. * @param is_window_shown - Whether to show the window @@ -579,6 +591,15 @@ export var FS; */ async function getTmpDirPath(options = { create: false }) { return decode(await BIND_get_tmp_dir_path(encode(options))); } FS.getTmpDirPath = getTmpDirPath; + /** + * Opens the native file chooser and returns absolute selected path(s). + * @param options - Picker options + * @param options.multiple - Whether multiple selections are allowed (default: false) + * @param options.directories - Whether to select directories only (default: false) + * @returns Promise that resolves to a single path, an array of paths, or null if cancelled + */ + async function chooseFiles(options = {}) { return decode(await BIND_choose_files(encode(options))); } + FS.chooseFiles = chooseFiles; /** * Downloads a file from a URI to a local path. * @param uri - URI to download from diff --git a/web/api/index.js.map b/web/api/index.js.map index d782add..835362f 100644 --- a/web/api/index.js.map +++ b/web/api/index.js.map @@ -1 +1 @@ -{"version":3,"file":"index.js","sourceRoot":"","sources":["index.ts"],"names":[],"mappings":"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"} \ No newline at end of file +{"version":3,"file":"index.js","sourceRoot":"","sources":["index.ts"],"names":[],"mappings":"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"} \ No newline at end of file diff --git a/web/api/index.ts b/web/api/index.ts index 624b991..c1dbf13 100644 --- a/web/api/index.ts +++ b/web/api/index.ts @@ -414,6 +414,20 @@ export namespace Window { */ export async function isFocus(): Promise { return await BIND_is_focus(null); } + + /** + * Checks if the window is currently visible. + * @returns Promise that resolves to true if window is shown + */ + export async function isShown(): Promise + { return await BIND_is_shown(null); } + + /** + * Requests focus for the current window. + * @returns Promise that resolves when the focus request has been issued + */ + export async function focus(): Promise + { await BIND_focus(null); } /** * Shows or hides the window. @@ -722,6 +736,16 @@ export namespace FS { */ export async function getTmpDirPath(options: { create?: boolean } = { create: false }): Promise { return decode(await BIND_get_tmp_dir_path(encode(options))); } + + /** + * Opens the native file chooser and returns absolute selected path(s). + * @param options - Picker options + * @param options.multiple - Whether multiple selections are allowed (default: false) + * @param options.directories - Whether to select directories only (default: false) + * @returns Promise that resolves to a single path, an array of paths, or null if cancelled + */ + export async function chooseFiles(options: { multiple?: boolean, directories?: boolean } = {}): Promise + { return decode(await BIND_choose_files(encode(options))); } /** * Downloads a file from a URI to a local path. @@ -729,7 +753,7 @@ export namespace FS { * @param path - Local path to save the file * @returns Promise that resolves when download is complete */ - export async function downloadUri(uri: string, path: string): Promise + export async function downloadUri(uri: string, path?: string): Promise { await BIND_download_uri(encode(uri), encode(path)); } } @@ -1560,6 +1584,8 @@ declare const BIND_get_opacity: (...args: any[]) => Promise; declare const BIND_set_opacity: (...args: any[]) => Promise; declare const BIND_is_focus: (...args: any[]) => Promise; +declare const BIND_is_shown: (...args: any[]) => Promise; +declare const BIND_focus: (...args: any[]) => Promise; declare const BIND_show: (...args: any[]) => Promise; declare const BIND_change_title: (...args: any[]) => Promise; declare const BIND_reset_title: (...args: any[]) => Promise; @@ -1600,6 +1626,7 @@ declare const BIND_rename: (...args: any[]) => Promise; declare const BIND_copy: (...args: any[]) => Promise; declare const BIND_get_application_dir_path: (...args: any[]) => Promise; declare const BIND_get_tmp_dir_path: (...args: any[]) => Promise; +declare const BIND_choose_files: (...args: any[]) => Promise; declare const BIND_download_uri: (...args: any[]) => Promise; declare const BIND_get_config: (...args: any[]) => Promise; diff --git a/web/api/jsr.json b/web/api/jsr.json index 4a885a4..24a5144 100644 --- a/web/api/jsr.json +++ b/web/api/jsr.json @@ -1,6 +1,6 @@ { "name": "@spur27/renweb-api", - "version": "0.0.6", + "version": "0.1.0", "license": "BSL-1.0", "exports": "./index.ts", "publish": { diff --git a/web/api/package.json b/web/api/package.json index 2af9069..478c092 100644 --- a/web/api/package.json +++ b/web/api/package.json @@ -1,6 +1,6 @@ { "name": "renweb-api", - "version": "0.0.6", + "version": "0.1.0", "description": "TypeScript/JavaScript API bindings for the RenWeb Engine desktop app runtime.", "type": "module", "main": "./index.js", @@ -53,6 +53,6 @@ "build": "tsc --target es2020 --module es2020 --declaration --sourceMap --strict --moduleResolution node --esModuleInterop --allowSyntheticDefaultImports --skipLibCheck --forceConsistentCasingInFileNames index.ts" }, "devDependencies": { - "typescript": "^5.0.0" + "typescript": "^5.9.3" } } diff --git a/web/example/pages/hello/script.js b/web/example/pages/hello/script.js index c951ab9..19d944a 100644 --- a/web/example/pages/hello/script.js +++ b/web/example/pages/hello/script.js @@ -38,6 +38,7 @@ window.renweb.onReady = async () => { const os = String(await System.getOS()).toLowerCase(); let targetOpacity = 1; if (os === "macos") { + if (await Window.isShown()) return; targetOpacity = await Properties.getOpacity(); await Properties.setOpacity(0); } @@ -52,6 +53,7 @@ window.renweb.onReady = async () => { await Properties.setOpacity((targetOpacity * i) / fadeSteps); } } + await Window.focus(); } // Back button diff --git a/web/example/pages/media/script.js b/web/example/pages/media/script.js index 7b6f62e..06be70e 100644 --- a/web/example/pages/media/script.js +++ b/web/example/pages/media/script.js @@ -40,6 +40,7 @@ window.renweb.onReady = async () => { const os = String(await System.getOS()).toLowerCase(); let targetOpacity = 1; if (os === "macos") { + if (await Window.isShown()) return; targetOpacity = await Properties.getOpacity(); await Properties.setOpacity(0); } @@ -54,6 +55,7 @@ window.renweb.onReady = async () => { await Properties.setOpacity((targetOpacity * i) / fadeSteps); } } + await Window.focus(); } // Web Audio API Setup diff --git a/web/example/pages/security/script.js b/web/example/pages/security/script.js index 90dca15..d939a73 100644 --- a/web/example/pages/security/script.js +++ b/web/example/pages/security/script.js @@ -41,6 +41,7 @@ window.renweb.onReady = async () => { const os = String(await System.getOS()).toLowerCase(); let targetOpacity = 1; if (os === "macos") { + if (await Window.isShown()) return; targetOpacity = await Properties.getOpacity(); await Properties.setOpacity(0); } @@ -55,6 +56,7 @@ window.renweb.onReady = async () => { await Properties.setOpacity((targetOpacity * i) / fadeSteps); } } + await Window.focus(); } function updateTestStatus(testId, status) { diff --git a/web/example/pages/test/index.html b/web/example/pages/test/index.html index fb79d76..a2f77ff 100644 --- a/web/example/pages/test/index.html +++ b/web/example/pages/test/index.html @@ -141,7 +141,6 @@

Rename/Copy

Choose Files

-
@@ -165,6 +164,8 @@

Window Controls

+ +
diff --git a/web/example/pages/test/script.js b/web/example/pages/test/script.js index 8af2e34..d6c34b1 100644 --- a/web/example/pages/test/script.js +++ b/web/example/pages/test/script.js @@ -101,10 +101,12 @@ window.renweb.onReady = async () => { const os = String(await System.getOS()).toLowerCase(); let targetOpacity = 1; if (os === "macos") { + if (await Window.isShown()) return; targetOpacity = await Properties.getOpacity(); await Properties.setOpacity(0); } + await Window.show(true); if (os === "macos") { @@ -115,6 +117,7 @@ window.renweb.onReady = async () => { await Properties.setOpacity((targetOpacity * i) / fadeSteps); } } + await Window.focus(); } window.renweb.onTerminate = async () => { @@ -384,39 +387,23 @@ document.querySelector(".copy_file").onclick = async () => { } }; -document.querySelector(".choose_files_input").onchange = async (event) => { - const files = event.target.files; - - if (files.length > 0) { - const file_list = Array.from(files).reduce((acc, f) => { - return acc + ` ├─ ${f.webkitRelativePath || f.name} (${(f.size / 1024).toFixed(2)} KB)\n`; - }, ""); - document.querySelector(".choose_files_output").textContent = file_list; - } else { - document.querySelector(".choose_files_output").textContent = "empty"; - } -}; - document.querySelector(".choose_files").onclick = async () => { await Log.debug(`Opening file/directory chooser...`); const multiple = document.querySelector(".multiple").checked; const directories = document.querySelector(".directories").checked; + const selected = await FS.chooseFiles({ multiple, directories }); + const paths = (selected == null) ? [] : (Array.isArray(selected) ? selected : [selected]); - if (multiple) { - document.querySelector(".choose_files_input").setAttribute("multiple", ""); - } else { - document.querySelector(".choose_files_input").removeAttribute("multiple"); - } - - if (directories) { - document.querySelector(".choose_files_input").setAttribute("webkitdirectory", ""); - document.querySelector(".choose_files_input").setAttribute("directory", ""); - } else { - document.querySelector(".choose_files_input").removeAttribute("webkitdirectory"); - document.querySelector(".choose_files_input").removeAttribute("directory"); + if (paths.length === 0) { + document.querySelector(".choose_files_output").textContent = "empty"; + return; } - document.querySelector(".choose_files_input").click(); + document.querySelector(".file_msg").value = paths[0]; + const file_list = paths.reduce((acc, path) => { + return acc + ` ├─ ${path}\n`; + }, ""); + document.querySelector(".choose_files_output").textContent = file_list; }; document.querySelector(".download_files").onclick = async () => { @@ -433,23 +420,22 @@ Test Data: - Timestamp: ${Date.now()} - User agent: ${navigator.userAgent} `; - - const blob = new Blob([testContent], { type: 'text/plain' }); - - const url = URL.createObjectURL(blob); - const a = document.createElement('a'); - a.href = url; - a.download = `renweb-test-${Date.now()}.txt`; - - document.body.appendChild(a); - a.click(); - - document.body.removeChild(a); - URL.revokeObjectURL(url); - - await Log.info(`Downloaded test file: ${a.download}`); - - document.querySelector(".download_files_output").textContent = `Downloaded: ${a.download}\nSaved to your default downloads folder`; + + const filename = `renweb-test-${Date.now()}.txt`; + const tmpDir = await FS.getTmpDirPath({ create: true }); + const tmpPath = `${tmpDir}/${filename}`; + const writeOk = await FS.writeFile(tmpPath, testContent, { append: false }); + + if (!writeOk) { + await Log.error(`Failed to create temporary test file: ${tmpPath}`); + document.querySelector(".download_files_output").textContent = `Download failed: could not create temp file`; + return; + } + + const sourceUri = `file://${encodeURI(tmpPath)}`; + await FS.downloadUri(sourceUri); + await Log.info(`Downloaded test file: ${filename}`); + document.querySelector(".download_files_output").textContent = `Downloaded: ${filename}\nSaved to your default downloads folder`; }; // ============================================================================ @@ -475,6 +461,25 @@ document.querySelector(".is_focus").onclick = async () => { } }; +document.querySelector(".test_focus").onclick = async () => { + await Log.debug(`Focusing window after 5 seconds...`); + setTimeout(async () => { + await Log.debug(`Focusing window now...`); + await Window.focus(); + }, 5000); +}; + +document.querySelector(".is_shown").onclick = async () => { + await Log.debug(`Is shown...`); + const res = await Window.isShown(); + if (res) { + document.querySelector(".is_shown").style.backgroundColor = "green"; + } else { + document.querySelector(".is_shown").style.backgroundColor = "red"; + } +}; + + document.querySelector(".test_hide").onclick = async () => { await Log.debug(`Hiding for 5 seconds...`); await Window.show(false); @@ -484,6 +489,8 @@ document.querySelector(".test_hide").onclick = async () => { }, 5000); }; + + document.querySelector(".change_title").onclick = async () => { await Log.debug(`Changing Title...`); const title = document.querySelector(".title_input").value; diff --git a/wiki/api.html b/wiki/api.html index 1c56192..ba213ed 100644 --- a/wiki/api.html +++ b/wiki/api.html @@ -580,6 +580,44 @@

Example

+
+

+ Window.isShown() + + async + +

+
isShown(): Promise<boolean>
+

Checks if the window is currently visible on screen.

+
+

Returns

+

Promise<boolean> - True if window is visible

+
+
+

Example

+
const visible = await Window.isShown();
+
+
+ +
+

+ Window.focus() + + async + +

+
focus(): Promise<void>
+

Requests focus for the current window and brings it to the foreground when the platform allows it.

+
+

Returns

+

Promise<void>

+
+
+

Example

+
await Window.focus();
+
+
+

Window.show() @@ -612,6 +650,7 @@

Example

+ {name: 'focus', signature: 'focus()', description: 'Returns: Promise'}, Window.changeTitle() async @@ -1568,6 +1607,62 @@

Example

+
+

+ FS.chooseFiles() + + async + +

+
chooseFiles(options?: { multiple?: boolean, directories?: boolean, extensions?: string[] }): Promise<string | string[] | null>
+

Opens a native file or folder picker dialog and returns the selected path(s). Returns null if the user cancels.

+
+

Parameters

+
    +
  • + options + object + - Optional configuration object +
      +
    • + multiple + boolean + - Allow selecting multiple files or folders. Defaults to false +
    • +
    • + directories + boolean + - Open a folder picker instead of a file picker. Defaults to false +
    • +
    • + extensions + string[] + - File extensions to filter by (e.g. ["png", "jpg"]). Ignored when directories is true. Pass ["*"] or omit to allow all files +
    • +
    +
  • +
+
+
+

Returns

+

Promise<string | string[] | null> - Selected path as a string when multiple is false, array of paths when multiple is true, or null if the dialog was cancelled

+
+
+

Example

+
// Single file
+const file = await FS.chooseFiles();
+if (file) await Log.debug(`Chosen: ${file}`);
+
+// Multiple files filtered by extension
+const images = await FS.chooseFiles({ multiple: true, extensions: ["png", "jpg", "webp"] });
+if (images) await Log.debug(`Selected ${images.length} image(s)`);
+
+// Folder picker
+const folder = await FS.chooseFiles({ directories: true });
+if (folder) await Log.debug(`Folder: ${folder}`);
+
+
+

FS.downloadUri() @@ -2672,7 +2767,7 @@

Returns

Example

const versions = await Application.fetchVersions();
-console.log(versions.engine); // "0.0.7"
+console.log(versions.engine); // "0.1.0"
 console.log(versions.plugins); // { "my-plugin": "1.2.0" }
diff --git a/wiki/api_data.js b/wiki/api_data.js index fd448f9..89ff629 100644 --- a/wiki/api_data.js +++ b/wiki/api_data.js @@ -80,6 +80,8 @@ window.apiData = { ], 'Window': [ {name: 'isFocus', signature: 'isFocus()', description: 'Returns: Promise'}, + {name: 'isShown', signature: 'isShown()', description: 'Returns: Promise'}, + {name: 'focus', signature: 'focus()', description: 'Returns: Promise'}, {name: 'show', signature: 'show(is_window_shown?)', description: 'Returns: Promise'}, {name: 'changeTitle', signature: 'changeTitle(title)', description: 'Returns: Promise'}, {name: 'resetTitle', signature: 'resetTitle()', description: 'Returns: Promise'}, @@ -122,6 +124,7 @@ window.apiData = { {name: 'copy', signature: 'copy(orig_path, new_path, settings = { overwrite: false })', description: 'Returns: Promise'}, {name: 'getApplicationDirPath', signature: 'getApplicationDirPath()', description: 'Returns: Promise'}, {name: 'getTmpDirPath', signature: 'getTmpDirPath(options = { create: false })', description: 'Returns: Promise'}, + {name: 'chooseFiles', signature: 'chooseFiles(options = { multiple: false, directories: false, extensions: [] })', description: 'Returns: Promise'}, {name: 'downloadUri', signature: 'downloadUri(uri, path)', description: 'Returns: Promise'} ], 'Config': [ diff --git a/wiki/cli.html b/wiki/cli.html index 25c59cb..2a40842 100644 --- a/wiki/cli.html +++ b/wiki/cli.html @@ -72,7 +72,7 @@

Command Summary

rw createScaffold a new RenWeb project from a template rw initIntegrate RenWeb into an existing project (Angular, Vite, Deno, or vanilla) - rw fetchDownload the engine executable, plugin header, or JS API files + rw fetch [verb]Download engine assets: executable, plugin, API files, or the example release archive rw runLaunch the engine — walks up from cwd to find build/ and the host executable rw buildPrepare build/ for launch — copy pages, fetch engine and plugins, then delegate to Vite or mirror src/ rw updateUpdate the engine, JS modules, npm/bun packages, and plugins in an existing project @@ -240,12 +240,31 @@

Use rw init vs rw create

rw fetch

Downloads engine assets from the latest GitHub release into the current - working directory. Without any flags, downloads the bare engine executable + working directory. Without a verb, shows usage help. + Use executable, plugin, api, or + example. + example downloads the latest + example-x.y.z.zip or example-x.y.z.tar.gz + archive (zip preferred on Windows, tar.gz preferred on macOS/Linux). + executable downloads the bare engine executable for the host platform.

Usage

-
rw fetch [flags]
+
rw fetch <verb> [--version <tag>]
+ +

Verbs

+ + + + + + + + + + +
VerbDescription
executableDownload the bare engine executable for the host OS/arch
pluginDownload the example plugin binary for the host OS/arch
apiDownload the JS/TS API files (index.js, index.d.ts, index.ts, index.js.map)
exampleDownload the latest example project archive (example-x.y.z.zip or example-x.y.z.tar.gz)

Flags

@@ -253,28 +272,28 @@

Flags

- - -
FlagDescription
--executableDownload the bare engine executable (default when no flag is given)
--pluginDownload plugin assets into the current directory
--apiDownload the JS API type definitions and source files (index.js, index.d.ts, index.ts)
--version <tag>Pin to a specific release tag instead of the latest (e.g. --version 0.0.6)
-

Flags can be combined. For example, rw fetch --api --executable +

Verbs can be combined. For example, rw fetch api executable downloads both the engine and the API files in one call.

Examples

# Download the engine executable (host platform auto-detected)
-rw fetch
+rw fetch executable
 
 # Download JS API type definitions
-rw fetch --api
+rw fetch api
+
+# Download the latest example archive (OS-preferred format)
+rw fetch example
 
 # Download engine and API together
-rw fetch --executable --api
+rw fetch executable api
 
 # Pin to a specific release version
-rw fetch --version 0.0.6
+rw fetch executable --version 0.0.6 @@ -296,7 +315,7 @@

Usage

What it does

  1. Walks up from the current directory to find the project root (the directory containing info.json and build/). If already inside build/, uses that directly.
  2. -
  3. Detects the host OS and architecture to select the correct executable (e.g. renweb-0.0.7-linux-x86_64)
  4. +
  5. Detects the host OS and architecture to select the correct executable (e.g. renweb-0.1.0-linux-x86_64)
  6. Ensures the executable is marked as executable (chmod +x)
  7. Launches the engine with build/ as the working directory and exits with the same exit code
@@ -565,7 +584,7 @@

Example output

Project ✓ info.json found - ✓ Engine: renweb-0.0.7-linux-x86_64 + ✓ Engine: renweb-0.1.0-linux-x86_64 diff --git a/wiki/home.html b/wiki/home.html index de69fbd..f7781c0 100644 --- a/wiki/home.html +++ b/wiki/home.html @@ -38,7 +38,7 @@ "codeRepository": "https://github.com/spur27/RenWeb-Engine", "programmingLanguage": ["C++", "JavaScript", "TypeScript"], "offers": { "@type": "Offer", "price": "0", "priceCurrency": "USD" }, - "softwareVersion": "0.0.7" + "softwareVersion": "0.1.0" } RenWeb Engine — Cross-Platform Desktop App Framework diff --git a/wiki/script.js b/wiki/script.js index 45dde81..639e9aa 100644 --- a/wiki/script.js +++ b/wiki/script.js @@ -1330,6 +1330,8 @@ function formatSignature(name, details) { }, 'Window': { 'isFocus': { params: [], returns: 'Promise' }, + 'isShown': { params: [], returns: 'Promise' }, + 'focus': { params: [], returns: 'Promise' }, 'show': { params: [{name: 'is_window_shown', type: 'boolean?'}], returns: 'Promise' }, 'changeTitle': { params: [{name: 'title', type: 'string'}], returns: 'Promise' }, 'resetTitle': { params: [], returns: 'Promise' }, @@ -1372,6 +1374,7 @@ function formatSignature(name, details) { 'copy': { params: [{name: 'orig_path', type: 'string'}, {name: 'new_path', type: 'string'}, {name: 'settings?', type: '{ overwrite: boolean }', defaultValue: '{ overwrite: false }'}], returns: 'Promise' }, 'getApplicationDirPath': { params: [], returns: 'Promise' }, 'getTmpDirPath': { params: [{name: "options", type: "{ create: boolean }", defaultValue: '{ create: false }'}], returns: 'Promise' }, + 'chooseFiles': { params: [{name: 'options?', type: '{ multiple: boolean, directories: boolean, extensions: string[] }', defaultValue: '{ multiple: false, directories: false, extensions: [] }'}], returns: 'Promise' }, 'downloadUri': { params: [{name: 'uri', type: 'string'}, {name: 'path', type: 'string'}], returns: 'Promise' } }, 'Config': { @@ -1447,9 +1450,9 @@ function formatSignature(name, details) { const apiTreeData = { 'Properties': ['getSize', 'setSize', 'getPosition', 'setPosition', 'getTitleBar', 'setTitleBar', 'getResizable', 'setResizable', 'getKeepAbove', 'setKeepAbove', 'getMinimize', 'setMinimize', 'getMaximize', 'setMaximize', 'getFullscreen', 'setFullscreen', 'getTaskbarShow', 'setTaskbarShow', 'getOpacity', 'setOpacity'], - 'Window': ['isFocus', 'show', 'changeTitle', 'resetTitle', 'currentTitle', 'resetPage', 'currentPage', 'initialPage', 'reloadPage', 'navigatePage', 'terminate', 'startWindowDrag', 'printPage', 'zoomIn', 'zoomOut', 'zoomReset', 'getZoomLevel', 'setZoomLevel', 'findInPage', 'findNext', 'findPrevious', 'clearFind'], + 'Window': ['isFocus', 'isShown', 'focus', 'show', 'changeTitle', 'resetTitle', 'currentTitle', 'resetPage', 'currentPage', 'initialPage', 'reloadPage', 'navigatePage', 'terminate', 'startWindowDrag', 'printPage', 'zoomIn', 'zoomOut', 'zoomReset', 'getZoomLevel', 'setZoomLevel', 'findInPage', 'findNext', 'findPrevious', 'clearFind'], 'Log': ['trace', 'debug', 'info', 'warn', 'error', 'critical'], - 'FS': ['readFile', 'writeFile', 'exists', 'isDir', 'mkDir', 'rm', 'ls', 'rename', 'copy', 'getApplicationDirPath', 'getTmpDirPath', 'downloadUri'], + 'FS': ['readFile', 'writeFile', 'exists', 'isDir', 'mkDir', 'rm', 'ls', 'rename', 'copy', 'getApplicationDirPath', 'getTmpDirPath', 'chooseFiles', 'downloadUri'], 'Config': ['getConfig', 'getInfo', 'getDefaults', 'getState', 'loadState', 'saveConfig', 'setConfigProperty', 'resetToDefaults'], 'System': ['getPID', 'getOS', 'getCPUArchitecture'], 'Process': { diff --git a/wiki/template_jsons.js b/wiki/template_jsons.js index 2f49bd6..b462905 100644 --- a/wiki/template_jsons.js +++ b/wiki/template_jsons.js @@ -34,7 +34,7 @@ const INFO_JSON = { "title": "RenWeb", - "version": "0.0.7", + "version": "0.1.0", "author": "Spur27", "description": "Base RenWeb engine", "license": "BSL",