mirror of
https://github.com/PR0M3TH3AN/VoxVera.git
synced 2026-10-27 09:40:40 +00:00
chore: release v0.3.4
This commit is contained in:
1 parent
46360ca6ea
commit
2b29d71ac5
15 files changed
+150
-16
No files matched your search
@@ -37,6 +37,7 @@ Run the following commands to verify your changes:
|
||||
- **Dependencies**: if you add a new Python library to `requirements.txt`, you MUST run `voxvera vendorize` to include it in the portable distribution.
|
||||
- **Layout Integrity**: VoxVera uses visual width validation. If you add new UI elements, ensure they respect the physical boundaries of an 8.5"x11" flyer.
|
||||
- **Tests**: Ensure all tests pass: `pytest tests/`. Run the integrity suite specifically when modifying locales: `pytest tests/test_integrity.py`.
|
||||
- **Recommended local check**: Run `bash scripts/check.sh` before opening a PR. It mirrors the main lint/test/build checks in one command.
|
||||
- **Style**: Follow the existing coding style (PEP 8 for Python).
|
||||
- **Documentation**: If adding a new feature, update the master templates in `docs/templates/` and run `voxvera build-docs`.
|
||||
|
||||
|
||||
@@ -30,6 +30,13 @@ pip install .
|
||||
voxvera vendorize # Optional: pull dependencies locally for offline use
|
||||
```
|
||||
|
||||
### 5. Local Validation
|
||||
Run the full local validation sweep with:
|
||||
```bash
|
||||
bash scripts/check.sh
|
||||
```
|
||||
This runs linting, Python tests, Electron smoke tests, dependency checks, and the docs/site rebuild steps used during development.
|
||||
|
||||
---
|
||||
|
||||
## Key Features
|
||||
|
||||
+13
-4
@@ -15,9 +15,12 @@ echo "Starting E2E verification at $(date)"
|
||||
if [ -n "${VOXVERA_BIN:-}" ] && [ -f "$VOXVERA_BIN" ]; then
|
||||
voxvera_cmd="$VOXVERA_BIN"
|
||||
echo "Using provided VoxVera binary: $voxvera_cmd" >&2
|
||||
elif command -v voxvera >/dev/null 2>&1; then
|
||||
voxvera_cmd="$(command -v voxvera)"
|
||||
echo "Using existing VoxVera on PATH: $voxvera_cmd" >&2
|
||||
else
|
||||
echo "Installing VoxVera in editable mode..."
|
||||
python3 -m pip install --break-system-packages -e ".[e2e]"
|
||||
python3 -m pip install --break-system-packages --no-build-isolation -e ".[e2e]"
|
||||
voxvera_cmd="$(command -v voxvera || echo "$HOME/.local/bin/voxvera")"
|
||||
fi
|
||||
|
||||
@@ -30,16 +33,22 @@ DemoUser
|
||||
voxvera
|
||||
EOI
|
||||
|
||||
config_path="$HOME/host/voxvera/config.json"
|
||||
if [ ! -f "$config_path" ]; then
|
||||
echo "CRITICAL: Expected config file not found after init: $config_path" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "Building demo flyer..."
|
||||
"$voxvera_cmd" --lang en build
|
||||
"$voxvera_cmd" --lang en --config "$config_path" build
|
||||
|
||||
# Verify build output exists in host/
|
||||
folder_name="voxvera"
|
||||
host_dir="voxvera/host/$folder_name"
|
||||
host_dir="$HOME/host/$folder_name"
|
||||
|
||||
if [ ! -d "$host_dir" ]; then
|
||||
echo "CRITICAL: Host directory $host_dir not found!" >&2
|
||||
ls -R voxvera/host/ || true
|
||||
ls -R "$HOME/host/" || true
|
||||
exit 1
|
||||
fi
|
||||
|
||||
|
||||
+10
-5
@@ -4,6 +4,7 @@ const path = require('path');
|
||||
const which = require('which');
|
||||
const fs = require('fs');
|
||||
const { launchTor } = require('./tor.js');
|
||||
const { extractOnionUrl } = require('./runtime-utils');
|
||||
|
||||
let mainWindow;
|
||||
let onionProc;
|
||||
@@ -74,8 +75,12 @@ async function runServe (retry = false) {
|
||||
|
||||
onionProc.stdout.on('data', buf => {
|
||||
const line = buf.toString();
|
||||
const m = line.match(/OnionShare is hosting at (http.*\.onion)/);
|
||||
if (m) { gotURL = true; clearTimeout(softTimeout); }
|
||||
const onionUrl = extractOnionUrl(line);
|
||||
if (onionUrl) {
|
||||
gotURL = true;
|
||||
clearTimeout(softTimeout);
|
||||
mainWindow.webContents.send('onion-url', onionUrl);
|
||||
}
|
||||
mainWindow.webContents.send('log', { text: line });
|
||||
});
|
||||
|
||||
@@ -134,9 +139,9 @@ ipcMain.handle('run-quickstart', async (_, config) => {
|
||||
if (mainWindow) {
|
||||
mainWindow.webContents.send('log', { text: line, isError: false });
|
||||
}
|
||||
const m = line.match(/Onion URL:\s*(https?:\/\/[a-z0-9.-]+\.onion)/i);
|
||||
if (m && mainWindow) {
|
||||
mainWindow.webContents.send('onion-url', m[1]);
|
||||
const onionUrl = extractOnionUrl(line);
|
||||
if (onionUrl && mainWindow) {
|
||||
mainWindow.webContents.send('onion-url', onionUrl);
|
||||
}
|
||||
});
|
||||
proc.stderr.on('data', data => {
|
||||
|
||||
@@ -4,7 +4,8 @@
|
||||
"main": "main.js",
|
||||
"scripts": {
|
||||
"start": "electron .",
|
||||
"lint": "echo 'lint pass'"
|
||||
"lint": "echo 'lint pass'",
|
||||
"test": "node --test runtime-utils.test.js"
|
||||
},
|
||||
"devDependencies": {
|
||||
"electron": "^29.0.0",
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
function extractOnionUrl(line) {
|
||||
const match = line.match(/Onion URL:\s*(https?:\/\/[a-z0-9.-]+\.onion)/i)
|
||||
|| line.match(/OnionShare is hosting at\s*(https?:\/\/[a-z0-9.-]+\.onion)/i);
|
||||
return match ? match[1] : null;
|
||||
}
|
||||
|
||||
function platformDirName(platform = process.platform) {
|
||||
if (platform === 'win32') return 'win';
|
||||
if (platform === 'darwin') return 'mac';
|
||||
return 'linux';
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
extractOnionUrl,
|
||||
platformDirName,
|
||||
};
|
||||
@@ -0,0 +1,28 @@
|
||||
const test = require('node:test');
|
||||
const assert = require('node:assert/strict');
|
||||
|
||||
const { extractOnionUrl, platformDirName } = require('./runtime-utils');
|
||||
|
||||
test('extractOnionUrl handles current CLI output', () => {
|
||||
assert.equal(
|
||||
extractOnionUrl('[voxvera] Onion URL: http://test123.onion'),
|
||||
'http://test123.onion'
|
||||
);
|
||||
});
|
||||
|
||||
test('extractOnionUrl handles legacy OnionShare output', () => {
|
||||
assert.equal(
|
||||
extractOnionUrl('OnionShare is hosting at http://legacy456.onion'),
|
||||
'http://legacy456.onion'
|
||||
);
|
||||
});
|
||||
|
||||
test('extractOnionUrl returns null when no onion URL is present', () => {
|
||||
assert.equal(extractOnionUrl('still bootstrapping'), null);
|
||||
});
|
||||
|
||||
test('platformDirName maps bundled resource folders correctly', () => {
|
||||
assert.equal(platformDirName('linux'), 'linux');
|
||||
assert.equal(platformDirName('darwin'), 'mac');
|
||||
assert.equal(platformDirName('win32'), 'win');
|
||||
});
|
||||
+4
-2
@@ -3,14 +3,16 @@ const path = require('path');
|
||||
const fs = require('fs');
|
||||
const which = require('which');
|
||||
const getPort = (...args) => import('get-port').then(m => m.default(...args));
|
||||
const { platformDirName } = require('./runtime-utils');
|
||||
|
||||
async function launchTor() {
|
||||
const socks = await getPort();
|
||||
const control = await getPort();
|
||||
const torBase = path.join(__dirname, '..', '..', 'voxvera', 'resources', 'tor');
|
||||
let exe = path.join(torBase, process.platform,
|
||||
const platformDir = platformDirName(process.platform);
|
||||
let exe = path.join(torBase, platformDir,
|
||||
process.platform === 'win32' ? 'tor.exe' : 'tor');
|
||||
let obfs4 = path.join(torBase, process.platform,
|
||||
let obfs4 = path.join(torBase, platformDir,
|
||||
process.platform === 'win32' ? 'obfs4proxy.exe' : 'obfs4proxy');
|
||||
|
||||
const missing = p => {
|
||||
|
||||
+1
-1
@@ -14,7 +14,7 @@ voxvera = ["templates/**", "src/**", "resources/**", "locales/**", "resources/bi
|
||||
|
||||
[project]
|
||||
name = "voxvera"
|
||||
version = "0.3.3"
|
||||
version = "0.3.4"
|
||||
description = "VoxVera CLI utilities"
|
||||
readme = "README.md"
|
||||
authors = [{name = "VoxVera"}]
|
||||
|
||||
Executable
+27
@@ -0,0 +1,27 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
cd "$(dirname "$0")/.."
|
||||
|
||||
echo "==> flake8"
|
||||
python3 -m flake8 voxvera tests \
|
||||
--jobs=1 \
|
||||
--exclude=voxvera/vendor \
|
||||
--ignore=E501,W503,W293,E402,E306,E704,E261,F841,E302,F401,E401,F541
|
||||
|
||||
echo "==> pytest"
|
||||
python3 -m pytest tests/ -v
|
||||
|
||||
echo "==> electron smoke tests"
|
||||
(cd gui/electron && npm test)
|
||||
|
||||
echo "==> dependency check"
|
||||
python3 -m voxvera.cli --lang en check
|
||||
|
||||
echo "==> build docs"
|
||||
python3 -m voxvera.cli build-docs
|
||||
|
||||
echo "==> build site"
|
||||
python3 -m voxvera.cli build-site
|
||||
|
||||
echo "All local checks passed."
|
||||
Binary file not shown.
Binary file not shown.
@@ -126,6 +126,21 @@ def test_import_preserves_session(tmp_path, monkeypatch):
|
||||
assert (host_dir / "index.html").exists()
|
||||
|
||||
|
||||
def test_import_rejects_zip_path_traversal(tmp_path, monkeypatch, capsys):
|
||||
_setup_tmp(monkeypatch, tmp_path)
|
||||
malicious_zip = tmp_path / "malicious.zip"
|
||||
with zipfile.ZipFile(malicious_zip, "w") as zf:
|
||||
zf.writestr("../escape.txt", "owned")
|
||||
zf.writestr("config.json", json.dumps({"folder_name": "evil"}))
|
||||
|
||||
cli.import_site(str(malicious_zip))
|
||||
|
||||
captured = capsys.readouterr()
|
||||
assert "Refusing to extract path outside destination" in captured.out
|
||||
assert not (tmp_path / "escape.txt").exists()
|
||||
assert not ((tmp_path / "data" / "host" / "evil").exists())
|
||||
|
||||
|
||||
def test_check_all_present(capsys, monkeypatch):
|
||||
monkeypatch.setattr(shutil, "which", lambda cmd: "/usr/bin/" + cmd)
|
||||
cli.main(["check"])
|
||||
|
||||
+1
-1
@@ -1,7 +1,7 @@
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
__version__ = "0.3.3"
|
||||
__version__ = "0.3.4"
|
||||
|
||||
# Add the vendor directory to sys.path ONLY for PyInstaller (frozen) builds.
|
||||
# For pip/pipx installs the system/venv packages are correct and the vendored
|
||||
|
||||
+25
-2
@@ -28,6 +28,8 @@ from InquirerPy import prompt, inquirer
|
||||
from rich.console import Console
|
||||
from voxvera import __version__
|
||||
|
||||
GITHUB_RECOMMENDED_BINARY_LIMIT = 95 * 1024 * 1024
|
||||
|
||||
# package root (contains bundled templates and src/)
|
||||
if getattr(sys, 'frozen', False):
|
||||
ROOT = Path(sys._MEIPASS).joinpath("voxvera")
|
||||
@@ -858,6 +860,19 @@ import zipfile
|
||||
import tempfile
|
||||
|
||||
|
||||
def _safe_extract_zip(zipf: zipfile.ZipFile, dest_dir: Path) -> None:
|
||||
"""Extract a ZIP archive without allowing path traversal."""
|
||||
dest_dir = dest_dir.resolve()
|
||||
for member in zipf.infolist():
|
||||
member_path = Path(member.filename)
|
||||
if member_path.is_absolute():
|
||||
raise ValueError(f"Refusing to extract absolute path from archive: {member.filename}")
|
||||
target_path = (dest_dir / member_path).resolve()
|
||||
if not str(target_path).startswith(str(dest_dir) + os.sep) and target_path != dest_dir:
|
||||
raise ValueError(f"Refusing to extract path outside destination: {member.filename}")
|
||||
zipf.extractall(dest_dir)
|
||||
|
||||
|
||||
def get_export_dir() -> Path:
|
||||
"""Get the default export directory (~/voxvera-exports)."""
|
||||
export_dir = Path.home() / "voxvera-exports"
|
||||
@@ -911,7 +926,11 @@ def import_site(zip_path: str):
|
||||
|
||||
with tempfile.TemporaryDirectory() as tmpdir:
|
||||
with zipfile.ZipFile(zip_path, 'r') as zipf:
|
||||
zipf.extractall(tmpdir)
|
||||
try:
|
||||
_safe_extract_zip(zipf, Path(tmpdir))
|
||||
except ValueError as e:
|
||||
print(f"Error: Invalid export file. {e}")
|
||||
return
|
||||
|
||||
tmp_path = Path(tmpdir)
|
||||
config_file = tmp_path / "config.json"
|
||||
@@ -1185,11 +1204,15 @@ def build_site():
|
||||
# Bundle source version
|
||||
bundle_source(download_dir / "voxvera-source.zip")
|
||||
|
||||
# Copy standalone binaries if they exist
|
||||
# Copy standalone binaries if they exist, but avoid writing oversized
|
||||
# artifacts into the tracked site tree where GitHub rejects >100 MB files.
|
||||
bin_dir = ROOT / "resources" / "bin"
|
||||
if bin_dir.exists():
|
||||
for bin_file in bin_dir.iterdir():
|
||||
if bin_file.is_file():
|
||||
if bin_file.stat().st_size > GITHUB_RECOMMENDED_BINARY_LIMIT:
|
||||
print(f"Skipping oversized site download artifact: {bin_file.name}")
|
||||
continue
|
||||
shutil.copy(bin_file, download_dir / bin_file.name)
|
||||
|
||||
print("site/ directory successfully synchronized.")
|
||||
|
||||
Reference in new issue
Block a user