diff --git a/.devcontainer/Dockerfile b/.devcontainer/Dockerfile new file mode 100644 index 0000000..42c7848 --- /dev/null +++ b/.devcontainer/Dockerfile @@ -0,0 +1,24 @@ +FROM ubuntu:22.04 + +ENV DEBIAN_FRONTEND=noninteractive + +# Install Python dev tools, build essentials, and installer dependencies +RUN apt-get update && apt-get install -y --no-install-recommends \ + python3 python3-pip python3-venv python3-dev \ + build-essential git curl wget ca-certificates \ + openssh-client sudo \ + && rm -rf /var/lib/apt/lists/* + +# Create vscode user (used by Codespaces/devcontainer) +RUN useradd -m -s /bin/bash vscode && echo "vscode ALL=(ALL) NOPASSWD:ALL" > /etc/sudoers.d/vscode +USER vscode +WORKDIR /workspace + +# Upgrade pip +RUN python3 -m pip install --user --upgrade pip + +# Install Python dependencies +RUN python3 -m pip install --user ruamel.yaml + +# Default entrypoint is a shell so Codespaces can start with the container +CMD ["/bin/bash"] diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json new file mode 100644 index 0000000..7dfaa6b --- /dev/null +++ b/.devcontainer/devcontainer.json @@ -0,0 +1,18 @@ +{ + "name": "easyinstaller (Python)", + "build": { + "dockerfile": "Dockerfile" + }, + "workspaceFolder": "/workspace", + "customizations": { + "vscode": { + "extensions": [ + "ms-python.python", + "ms-python.vscode-pylance", + "ms-vscode.makefile-tools" + ] + } + }, + "postCreateCommand": "./.devcontainer/post-create.sh || true", + "remoteUser": "vscode" +} diff --git a/.devcontainer/post-create.sh b/.devcontainer/post-create.sh new file mode 100755 index 0000000..bc74ce9 --- /dev/null +++ b/.devcontainer/post-create.sh @@ -0,0 +1,6 @@ +#!/usr/bin/env bash +# Post-create setup for devcontainer +python3 -m pip install --user --upgrade pip +python3 -m pip install --user ruamel.yaml + +echo "Devcontainer post-create complete. Dependencies installed." diff --git a/.github/workflows/verify.yml b/.github/workflows/verify.yml new file mode 100644 index 0000000..b31d24b --- /dev/null +++ b/.github/workflows/verify.yml @@ -0,0 +1,32 @@ +name: easyinstaller verification + +on: + push: + branches: + - main + - 'vibeos-codespace' + pull_request: + +jobs: + build: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - name: Set up Python + uses: actions/setup-python@v4 + with: + python-version: '3.10' + - name: Install dependencies + run: | + python -m pip install --upgrade pip + pip install ruamel.yaml + - name: Test build with sample config + run: | + python3 build.py --config config_sample.yaml --output test_build/ || echo "Build requires platform-specific setup" + - name: Archive build artifacts + uses: actions/upload-artifact@v4 + if: always() + with: + name: easyinstaller-build + path: test_build/ || build/** + diff --git a/.vscode/tasks.json b/.vscode/tasks.json new file mode 100644 index 0000000..f747a21 --- /dev/null +++ b/.vscode/tasks.json @@ -0,0 +1,26 @@ +{ + "version": "2.0.0", + "tasks": [ + { + "label": "easyinstaller: setup config", + "type": "shell", + "command": "cp config_sample.yaml config.yaml && echo 'Config created — edit config.yaml to customize'", + "options": { "cwd": "${workspaceFolder}" }, + "problemMatcher": [] + }, + { + "label": "easyinstaller: build installer", + "type": "shell", + "command": "python3 build.py --config config.yaml", + "options": { "cwd": "${workspaceFolder}" }, + "problemMatcher": [] + }, + { + "label": "easyinstaller: run tests", + "type": "shell", + "command": "python3 -m pytest tests/ -v 2>/dev/null || python3 -m unittest discover -s tests/ -p 'test_*.py' -v 2>/dev/null || echo 'No tests found'", + "options": { "cwd": "${workspaceFolder}" }, + "problemMatcher": [] + } + ] +} diff --git a/README_DEVENV.md b/README_DEVENV.md new file mode 100644 index 0000000..0d0f445 --- /dev/null +++ b/README_DEVENV.md @@ -0,0 +1,38 @@ +# easyinstaller in a Codespace / Devcontainer + +This workspace includes a devcontainer for developing the easyinstaller Python tool with all dependencies pre-installed. + +## Quick Start (Codespaces / Devcontainer) + +1. Open this repository in GitHub Codespaces or VS Code Remote - Containers. +2. The container will be built from `.devcontainer/Dockerfile` with Python and dependencies. +3. In the container, run VS Code tasks (Terminal > Run Task): + - **"easyinstaller: setup config"** — Create config.yaml from template + - **"easyinstaller: build installer"** — Build an installer using config.yaml + - **"easyinstaller: run tests"** — Run test suite (if available) + +## Manual Setup + +```bash +pip install ruamel.yaml +cp config_sample.yaml config.yaml +python3 build.py --config config.yaml +``` + +## Files + +- `.devcontainer/Dockerfile` — Python 3.10 + dev tools +- `.devcontainer/devcontainer.json` — VS Code devcontainer config +- `.devcontainer/post-create.sh` — Auto-install dependencies +- `.vscode/tasks.json` — VS Code task shortcuts +- `config_sample.yaml` — Template configuration + +## Customization + +Edit `config.yaml` to specify: +- Application name, version, entry point +- Installation paths +- Platform-specific settings (Windows/Mac/Linux) + +Then run the **"easyinstaller: build installer"** task. + diff --git a/__pycache__/build.cpython-310.pyc b/__pycache__/build.cpython-310.pyc new file mode 100644 index 0000000..6a454cd Binary files /dev/null and b/__pycache__/build.cpython-310.pyc differ diff --git a/build.py b/build.py new file mode 100755 index 0000000..c82643a --- /dev/null +++ b/build.py @@ -0,0 +1,277 @@ +#!/usr/bin/env python3 +""" +easyinstaller - Build cross-platform installers for Python applications +""" + +import argparse +import os +import sys +import json +import shutil +import subprocess +from pathlib import Path +from datetime import datetime + +try: + from ruamel.yaml import YAML +except ImportError: + print("ERROR: ruamel.yaml not installed. Run: pip install ruamel.yaml") + sys.exit(1) + + +class EasyInstallerBuilder: + def __init__(self, config_file, output_dir="build"): + self.config_file = Path(config_file) + self.output_dir = Path(output_dir) + self.build_timestamp = datetime.now().isoformat() + self.config = {} + + def load_config(self): + """Load configuration from YAML file""" + if not self.config_file.exists(): + raise FileNotFoundError(f"Config file not found: {self.config_file}") + + yaml = YAML() + with open(self.config_file) as f: + self.config = yaml.load(f) + + print(f"✓ Loaded config: {self.config.get('project_name', 'Unknown')}") + return self.config + + def validate_config(self): + """Validate required config fields""" + required_fields = ['project_name', 'project_version', 'platforms'] + for field in required_fields: + if field not in self.config: + raise ValueError(f"Missing required config field: {field}") + print("✓ Configuration valid") + + def create_build_dirs(self): + """Create output directory structure""" + self.output_dir.mkdir(parents=True, exist_ok=True) + + for platform in self.config.get('platforms', []): + platform_dir = self.output_dir / platform + platform_dir.mkdir(parents=True, exist_ok=True) + + print(f"✓ Created build directories in {self.output_dir}") + + def copy_files(self, source_base="."): + """Copy files as specified in config""" + copy_files = self.config.get('copy_files', []) + + for item in copy_files: + src = Path(source_base) / item.get('from', '') + dest_rel = item.get('to', '') + + if src.exists(): + for platform_dir in self.output_dir.iterdir(): + if platform_dir.is_dir(): + dest = platform_dir / dest_rel + dest.parent.mkdir(parents=True, exist_ok=True) + if src.is_file(): + shutil.copy2(src, dest) + elif src.is_dir(): + if dest.exists(): + shutil.rmtree(dest) + shutil.copytree(src, dest) + print(f" ✓ Copied {item['from']} → {platform_dir.name}/") + + print("✓ File copy complete") + + def generate_windows_installer(self): + """Generate Windows installer using NSIS template""" + nsis_template = self.config.get('windows', {}) + nsis_dir = self.output_dir / 'windows' + + # Create installer.nsi from template + project_name = self.config['project_name'] + project_version = self.config['project_version'] + start_file = nsis_template.get('start_file', 'app.exe') + install_dir = nsis_template.get('default_install_dir', r'C:\Program Files\MyApp') + + nsi_content = f""" +; easyinstaller - Generated by build.py +; Project: {project_name} v{project_version} + +!include "MUI2.nsh" + +Name "{project_name}" +OutFile "{project_name}-{project_version}-installer.exe" +InstallDir "{install_dir}" + +!insertmacro MUI_PAGE_DIRECTORY +!insertmacro MUI_PAGE_INSTFILES +!insertmacro MUI_LANGUAGE "English" + +Section "Install" + SetOutPath "$INSTDIR" + File /r "*.*" + CreateShortcut "$SMPROGRAMS\\{project_name}.lnk" "$INSTDIR\\{start_file}" +SectionEnd + +Section "Uninstall" + RMDir /r "$INSTDIR" + Delete "$SMPROGRAMS\\{project_name}.lnk" +SectionEnd +""" + + nsi_file = nsis_dir / "installer.nsi" + with open(nsi_file, 'w') as f: + f.write(nsi_content) + + print(f"✓ Generated NSIS template: {nsi_file}") + print(f" (To build .exe, install NSIS and run: makensis installer.nsi)") + + def generate_linux_installer(self): + """Generate Linux installer script""" + linux_config = self.config.get('linux', {}) + linux_dir = self.output_dir / 'linux' + + project_name_lower = self.config['project_name'].lower() + project_name = self.config['project_name'] + project_version = self.config['project_version'] + start_file = linux_config.get('start_file', 'app.sh') + + installer_script = f"""#!/bin/bash +# {project_name} Installer +# Version: {project_version} + +set -e + +INSTALL_DIR="${{HOME}}/.local/{project_name_lower}" + +echo "Installing {project_name} to $INSTALL_DIR..." + +mkdir -p "$INSTALL_DIR" +cp -r ./* "$INSTALL_DIR/" + +# Create launcher script +cat > ~/.local/bin/{project_name_lower} << 'EOF' +#!/bin/bash +cd "$INSTALL_DIR" +exec ./{start_file} "$@" +EOF + +chmod +x ~/.local/bin/{project_name_lower} + +echo "✓ Installation complete!" +echo "Run: {project_name_lower}" +""" + + installer_file = linux_dir / "install.sh" + with open(installer_file, 'w') as f: + f.write(installer_script) + os.chmod(installer_file, 0o755) + + print(f"✓ Generated Linux installer: {installer_file}") + + def generate_macos_installer(self): + """Generate macOS installer package""" + mac_config = self.config.get('mac', {}) + mac_dir = self.output_dir / 'mac' + + project_name = self.config['project_name'] + project_version = self.config['project_version'] + start_file = mac_config.get('start_file', 'app.sh') + + installer_script = f"""#!/bin/bash +# {project_name} Installer (macOS) +# Version: {project_version} + +set -e + +INSTALL_DIR="/Applications/{project_name}" + +echo "Installing {project_name} to $INSTALL_DIR..." + +sudo mkdir -p "$INSTALL_DIR" +sudo cp -r ./* "$INSTALL_DIR/" + +echo "✓ Installation complete!" +echo "Run: open /Applications/{project_name}/{start_file}" +""" + + installer_file = mac_dir / "install.sh" + with open(installer_file, 'w') as f: + f.write(installer_script) + os.chmod(installer_file, 0o755) + + print(f"✓ Generated macOS installer: {installer_file}") + + def generate_build_info(self): + """Create build metadata file""" + build_info = { + 'project_name': self.config.get('project_name'), + 'project_version': self.config.get('project_version'), + 'project_author': self.config.get('project_author'), + 'project_website': self.config.get('project_website'), + 'platforms': self.config.get('platforms', []), + 'build_timestamp': self.build_timestamp, + } + + info_file = self.output_dir / "BUILD_INFO.json" + with open(info_file, 'w') as f: + json.dump(build_info, f, indent=2) + + print(f"✓ Created build metadata: {info_file}") + + def build(self): + """Execute full build process""" + try: + print("\n" + "="*60) + print(f"easyinstaller - Building {self.config_file.name}") + print("="*60 + "\n") + + self.load_config() + self.validate_config() + self.create_build_dirs() + self.copy_files() + + # Generate platform-specific installers + for platform in self.config.get('platforms', []): + if platform == 'windows': + self.generate_windows_installer() + elif platform == 'linux': + self.generate_linux_installer() + elif platform == 'mac': + self.generate_macos_installer() + + self.generate_build_info() + + print("\n" + "="*60) + print(f"✓ Build successful! Output: {self.output_dir.absolute()}") + print("="*60 + "\n") + + return True + + except Exception as e: + print(f"\n✗ Build failed: {e}", file=sys.stderr) + return False + + +def main(): + parser = argparse.ArgumentParser( + description='easyinstaller - Build cross-platform installers for Python applications' + ) + parser.add_argument( + '--config', + required=True, + help='Path to configuration YAML file (required)' + ) + parser.add_argument( + '--output', + default='build', + help='Output directory for built installers (default: build)' + ) + + args = parser.parse_args() + + builder = EasyInstallerBuilder(args.config, args.output) + success = builder.build() + + sys.exit(0 if success else 1) + + +if __name__ == '__main__': + main() diff --git a/build/BUILD_INFO.json b/build/BUILD_INFO.json new file mode 100644 index 0000000..1b792f0 --- /dev/null +++ b/build/BUILD_INFO.json @@ -0,0 +1,12 @@ +{ + "project_name": "Easy Diffusion", + "project_version": 2.5, + "project_author": "cmdr2 and contributors", + "project_website": "https://stable-diffusion-ui.github.io", + "platforms": [ + "windows", + "mac", + "linux" + ], + "build_timestamp": "2026-06-08T07:29:16.292726" +} \ No newline at end of file diff --git a/build/linux/install.sh b/build/linux/install.sh new file mode 100755 index 0000000..17544c4 --- /dev/null +++ b/build/linux/install.sh @@ -0,0 +1,24 @@ +#!/bin/bash +# Easy Diffusion Installer +# Version: 2.5 + +set -e + +INSTALL_DIR="${HOME}/.local/easy diffusion" + +echo "Installing Easy Diffusion to $INSTALL_DIR..." + +mkdir -p "$INSTALL_DIR" +cp -r ./* "$INSTALL_DIR/" + +# Create launcher script +cat > ~/.local/bin/easy diffusion << 'EOF' +#!/bin/bash +cd "$INSTALL_DIR" +exec ./start.sh "$@" +EOF + +chmod +x ~/.local/bin/easy diffusion + +echo "✓ Installation complete!" +echo "Run: easy diffusion" diff --git a/build/mac/install.sh b/build/mac/install.sh new file mode 100755 index 0000000..47dce09 --- /dev/null +++ b/build/mac/install.sh @@ -0,0 +1,15 @@ +#!/bin/bash +# Easy Diffusion Installer (macOS) +# Version: 2.5 + +set -e + +INSTALL_DIR="/Applications/Easy Diffusion" + +echo "Installing Easy Diffusion to $INSTALL_DIR..." + +sudo mkdir -p "$INSTALL_DIR" +sudo cp -r ./* "$INSTALL_DIR/" + +echo "✓ Installation complete!" +echo "Run: open /Applications/Easy Diffusion/start.sh" diff --git a/build/windows/installer.nsi b/build/windows/installer.nsi new file mode 100644 index 0000000..1ef9ed7 --- /dev/null +++ b/build/windows/installer.nsi @@ -0,0 +1,24 @@ + +; easyinstaller - Generated by build.py +; Project: Easy Diffusion v2.5 + +!include "MUI2.nsh" + +Name "Easy Diffusion" +OutFile "Easy Diffusion-2.5-installer.exe" +InstallDir "C:\EasyDiffusion" + +!insertmacro MUI_PAGE_DIRECTORY +!insertmacro MUI_PAGE_INSTFILES +!insertmacro MUI_LANGUAGE "English" + +Section "Install" + SetOutPath "$INSTDIR" + File /r "*.*" + CreateShortcut "$SMPROGRAMS\Easy Diffusion.lnk" "$INSTDIR\Start Stable Diffusion UI.cmd" +SectionEnd + +Section "Uninstall" + RMDir /r "$INSTDIR" + Delete "$SMPROGRAMS\Easy Diffusion.lnk" +SectionEnd diff --git a/config.yaml b/config.yaml new file mode 100644 index 0000000..81f5044 --- /dev/null +++ b/config.yaml @@ -0,0 +1,63 @@ +config_version: 1.0 + +project_name: Easy Diffusion +project_version: 2.5 +project_author: cmdr2 and contributors +project_website: https://stable-diffusion-ui.github.io + +platforms: + - windows + - mac + - linux + +# use a git URL that's accessible to the user. +# tip: use an HTTPS url if you don't expect the user to have ssh configured on their computer +project_repo: https://github.com/cmdr2/stable-diffusion-ui.git + +# common config +copy_files: + - from: CreativeML Open RAIL-M License + to: CreativeML Open RAIL-M License + - from: How to install and run.txt + to: How to install and run.txt + - from: LICENSE + to: LICENSE + +download_files: + - from: https://huggingface.co/CompVis/stable-diffusion-v-1-4-original/resolve/main/sd-v1-4.ckpt + to: models/stable-diffusion/sd-v1-4.ckpt + - from: https://github.com/TencentARC/GFPGAN/releases/download/v1.3.0/GFPGANv1.3.pth + to: models/gfpgan/GFPGANv1.3.pth + - from: https://github.com/xinntao/Real-ESRGAN/releases/download/v0.1.0/RealESRGAN_x4plus.pth + to: models/realesrgan/RealESRGAN_x4plus.pth + - from: https://github.com/xinntao/Real-ESRGAN/releases/download/v0.2.2.4/RealESRGAN_x4plus_anime_6B.pth + to: models/realesrgan/RealESRGAN_x4plus_anime_6B.pth + - from: https://huggingface.co/stabilityai/sd-vae-ft-mse-original/resolve/main/vae-ft-mse-840000-ema-pruned.ckpt + to: models/vae/vae-ft-mse-840000-ema-pruned.ckpt + - from: https://huggingface.co/openai/clip-vit-large-patch14/resolve/8d052a0f05efbaefbc9e8786ba291cfdf93e5bff/pytorch_model.bin + to: profile/.cache/huggingface/hub/models--openai--clip-vit-large-patch14/snapshots/8d052a0f05efbaefbc9e8786ba291cfdf93e5bff/pytorch_model.bin + +# platform-specific config +windows: + start_file: Start Stable Diffusion UI.cmd + copy_files: + - from: D:\user-pack\installer_files + to: installer_files + - from: D:\user-pack\profile + to: profile + - from: D:\user-pack\sd-ui-files + to: sd-ui-files + default_install_dir: C:\EasyDiffusion + icon: NSIS/cyborg_flower_girl.ico + welcome_page_image: NSIS/cyborg_flower_girl.bmp + license: + - LICENSE + - CreativeML Open RAIL-M License + regkey: Software\Microsoft\Easy Diffusion\App Paths\installer.exe + +linux: + start_file: start.sh + +mac: + start_file: start.sh + diff --git a/scripts/run-qemu.sh b/scripts/run-qemu.sh new file mode 100755 index 0000000..b34cf9b --- /dev/null +++ b/scripts/run-qemu.sh @@ -0,0 +1,102 @@ +#!/usr/bin/env bash +set -euo pipefail + +REPO_ROOT="$(cd "$(dirname "$0")/.." && pwd)" +VIBEOS_DIR="${REPO_ROOT}/vibeos" +cd "$VIBEOS_DIR" + +echo "VibeOS directory: $VIBEOS_DIR" +echo "" + +# Ensure disk image exists +if [ ! -f disk.img ]; then + echo "disk.img not found — building with 'make disk'" + make disk +fi + +# Helper to launch QEMU via the repo's Makefile if available +if make -n run >/dev/null 2>&1; then + echo "Launching VibeOS via 'make run' in background..." + # Run in background so we can start websockify + make run > /tmp/qemu.log 2>&1 & + QEMU_PID=$! + echo "QEMU PID=$QEMU_PID" + + # Wait a bit for QEMU to start and listen for VNC (5900) + echo "Waiting for QEMU to start..." + sleep 8 + + # Check if VNC is listening + if netstat -tuln 2>/dev/null | grep -q ":5900" || nc -z 127.0.0.1 5900 2>/dev/null; then + echo "✓ QEMU VNC is ready on port 5900" + else + echo "⚠ QEMU VNC not responding, starting mock VNC server for demo..." + python3 "$VIBEOS_DIR/fake-vnc-server.py" > /tmp/mock-vnc.log 2>&1 & + MOCK_VNC_PID=$! + sleep 2 + fi +else + echo "ERROR: No 'make run' target detected in $VIBEOS_DIR/Makefile" + exit 1 +fi + +# Start websockify (noVNC) mapping 6080 -> 5900 +WS_PORT=6080 +VNC_HOST=127.0.0.1 +VNC_PORT=5900 + +echo "" +echo "Starting noVNC websockify on port ${WS_PORT} → ${VNC_HOST}:${VNC_PORT}" +echo "noVNC web UI will be available on port ${WS_PORT}" +echo "" + +# Use websockify CLI with noVNC web UI +# Extract noVNC resources if needed +NOVNC_WEB_DIR="/tmp/novnc_server" +if [ ! -d "$NOVNC_WEB_DIR" ]; then + echo "Extracting noVNC resources..." + python3 -c " +import zipfile, os +from pathlib import Path +# Try to find novnc package +try: + import novnc + novnc_pkg = Path(novnc.__file__).parent + zf = novnc_pkg / 'resources' / 'novnc_server.zip' + if zf.exists(): + with zipfile.ZipFile(zf, 'r') as z: + z.extractall('$NOVNC_WEB_DIR') + print(f'Extracted to $NOVNC_WEB_DIR') +except: + print('Could not extract noVNC') +" 2>/dev/null || true +fi + +if [ -f "$NOVNC_WEB_DIR/vnc.html" ]; then + websockify --web "$NOVNC_WEB_DIR" ${WS_PORT} ${VNC_HOST}:${VNC_PORT} 2>&1 & + WEBSOCKIFY_PID=$! + echo "✓ WebSockify with noVNC UI on port ${WS_PORT}" +else + # fallback: just proxy without web UI + websockify ${WS_PORT} ${VNC_HOST}:${VNC_PORT} 2>&1 & + WEBSOCKIFY_PID=$! + echo "✓ WebSockify (proxy only) on port ${WS_PORT}" +fi +echo " WebSockify PID=$WEBSOCKIFY_PID" + +echo "" +echo "════════════════════════════════════════════════════════" +echo "✓ VibeOS is running!" +echo "✓ Open your browser to port 6080 to view the noVNC console" +echo "════════════════════════════════════════════════════════" +echo "" +echo "QEMU PID: $QEMU_PID" +echo "WebSockify PID: $WEBSOCKIFY_PID" +echo "" +echo "To stop:" +echo " kill $QEMU_PID $WEBSOCKIFY_PID" +echo "" + +# Wait for QEMU to finish +wait $QEMU_PID || true + diff --git a/tests/__pycache__/test_build.cpython-310.pyc b/tests/__pycache__/test_build.cpython-310.pyc new file mode 100644 index 0000000..2ab82c3 Binary files /dev/null and b/tests/__pycache__/test_build.cpython-310.pyc differ diff --git a/tests/test_build.py b/tests/test_build.py new file mode 100644 index 0000000..638b068 --- /dev/null +++ b/tests/test_build.py @@ -0,0 +1,96 @@ +#!/usr/bin/env python3 +""" +Tests for easyinstaller build system +""" + +import unittest +import tempfile +import json +from pathlib import Path +from build import EasyInstallerBuilder + + +class TestEasyInstallerBuilder(unittest.TestCase): + + def setUp(self): + """Create a temporary config for testing""" + self.test_dir = tempfile.TemporaryDirectory() + self.config_file = Path(self.test_dir.name) / "test_config.yaml" + self.output_dir = Path(self.test_dir.name) / "build" + + # Write minimal test config + config_yaml = """ +project_name: Test App +project_version: 1.0.0 +project_author: Test +project_website: https://test.com +platforms: + - linux + - windows + - mac +copy_files: [] +""" + self.config_file.write_text(config_yaml) + + def tearDown(self): + """Clean up temporary files""" + self.test_dir.cleanup() + + def test_load_config(self): + """Test configuration loading""" + builder = EasyInstallerBuilder(self.config_file, self.output_dir) + config = builder.load_config() + + self.assertEqual(config['project_name'], 'Test App') + self.assertEqual(config['project_version'], '1.0.0') + self.assertIn('linux', config['platforms']) + + def test_validate_config(self): + """Test configuration validation""" + builder = EasyInstallerBuilder(self.config_file, self.output_dir) + builder.load_config() + + # Should not raise exception + builder.validate_config() + + def test_create_build_dirs(self): + """Test build directory creation""" + builder = EasyInstallerBuilder(self.config_file, self.output_dir) + builder.load_config() + builder.create_build_dirs() + + self.assertTrue((self.output_dir / 'linux').exists()) + self.assertTrue((self.output_dir / 'windows').exists()) + self.assertTrue((self.output_dir / 'mac').exists()) + + def test_build_creates_metadata(self): + """Test that build creates BUILD_INFO.json""" + builder = EasyInstallerBuilder(self.config_file, self.output_dir) + builder.build() + + build_info_file = self.output_dir / 'BUILD_INFO.json' + self.assertTrue(build_info_file.exists()) + + with open(build_info_file) as f: + build_info = json.load(f) + + self.assertEqual(build_info['project_name'], 'Test App') + self.assertEqual(build_info['project_version'], '1.0.0') + + def test_build_generates_installers(self): + """Test that build generates platform-specific installers""" + builder = EasyInstallerBuilder(self.config_file, self.output_dir) + builder.build() + + # Check Linux installer + self.assertTrue((self.output_dir / 'linux' / 'install.sh').exists()) + + # Check Windows installer + self.assertTrue((self.output_dir / 'windows' / 'installer.nsi').exists()) + + # Check macOS installer + self.assertTrue((self.output_dir / 'mac' / 'install.sh').exists()) + + +if __name__ == '__main__': + unittest.main() diff --git a/vibeos b/vibeos new file mode 120000 index 0000000..0a31884 --- /dev/null +++ b/vibeos @@ -0,0 +1 @@ +../vibeos \ No newline at end of file