- );
- },
-});
-```
-
-Loaders simplify your data fetching logic dramatically. Check out more information in the [Loader documentation](https://tanstack.com/router/latest/docs/framework/react/guide/data-loading#loader-parameters).
-
-### React-Query
-
-React-Query is an excellent addition or alternative to route loading and integrating it into you application is a breeze.
-
-First add your dependencies:
-
-```bash
-npm install @tanstack/react-query @tanstack/react-query-devtools
-```
-
-Next we'll need to create a query client and provider. We recommend putting those in `main.tsx`.
-
-```tsx
-import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
-
-// ...
-
-const queryClient = new QueryClient();
-
-// ...
-
-if (!rootElement.innerHTML) {
- const root = ReactDOM.createRoot(rootElement);
-
- root.render(
-
-
-
- );
-}
-```
-
-You can also add TanStack Query Devtools to the root route (optional).
-
-```tsx
-import { ReactQueryDevtools } from "@tanstack/react-query-devtools";
-
-const rootRoute = createRootRoute({
- component: () => (
- <>
-
-
-
- >
- ),
-});
-```
-
-Now you can use `useQuery` to fetch your data.
-
-```tsx
-import { useQuery } from "@tanstack/react-query";
-
-import "./App.css";
-
-function App() {
- const { data } = useQuery({
- queryKey: ["people"],
- queryFn: () =>
- fetch("https://swapi.dev/api/people")
- .then((res) => res.json())
- .then((data) => data.results as { name: string }[]),
- initialData: [],
- });
-
- return (
-
-
- {data.map((person) => (
-
{person.name}
- ))}
-
-
- );
-}
-
-export default App;
-```
-
-You can find out everything you need to know on how to use React-Query in the [React-Query documentation](https://tanstack.com/query/latest/docs/framework/react/overview).
-
-## State Management
-
-Another common requirement for React applications is state management. There are many options for state management in React. TanStack Store provides a great starting point for your project.
-
-First you need to add TanStack Store as a dependency:
-
-```bash
-npm install @tanstack/store
-```
-
-Now let's create a simple counter in the `src/App.tsx` file as a demonstration.
-
-```tsx
-import { useStore } from "@tanstack/react-store";
-import { Store } from "@tanstack/store";
-import "./App.css";
-
-const countStore = new Store(0);
-
-function App() {
- const count = useStore(countStore);
- return (
-
-
-
- );
-}
-
-export default App;
-```
-
-One of the many nice features of TanStack Store is the ability to derive state from other state. That derived state will update when the base state updates.
-
-Let's check this out by doubling the count using derived state.
-
-```tsx
-import { useStore } from "@tanstack/react-store";
-import { Store, Derived } from "@tanstack/store";
-import "./App.css";
-
-const countStore = new Store(0);
-
-const doubledStore = new Derived({
- fn: () => countStore.state * 2,
- deps: [countStore],
-});
-doubledStore.mount();
-
-function App() {
- const count = useStore(countStore);
- const doubledCount = useStore(doubledStore);
-
- return (
-
-
-
Doubled - {doubledCount}
-
- );
-}
-
-export default App;
-```
-
-We use the `Derived` class to create a new store that is derived from another store. The `Derived` class has a `mount` method that will start the derived store updating.
-
-Once we've created the derived store we can use it in the `App` component just like we would any other store using the `useStore` hook.
-
-You can find out everything you need to know on how to use TanStack Store in the [TanStack Store documentation](https://tanstack.com/store/latest).
-
-# Demo files
-
-Files prefixed with `demo` can be safely deleted. They are there to provide a starting point for you to play around with the features you've installed.
-
-# Learn More
-
-You can learn more about all of the offerings from TanStack in the [TanStack documentation](https://tanstack.com).
+- `npm install` — Install dependencies
+- `npm run dev` — Start development server (port 3000)
+- `npm run build` — Production build
+- `npm run test` — Run tests
diff --git a/pyinstaller b/pyinstaller
deleted file mode 100644
index e69de29..0000000
diff --git a/build.py b/scripts/build.py
similarity index 97%
rename from build.py
rename to scripts/build.py
index 1d19afd..7c5b15a 100644
--- a/build.py
+++ b/scripts/build.py
@@ -4,10 +4,10 @@
Creates portable executables for Windows, macOS, and Linux
Usage:
- python build.py # Build for current platform
- python build.py --clean # Clean build artifacts first
- python build.py --skip-frontend # Skip frontend build (use existing)
- python build.py --version 1.2.3 # Override version (for CI)
+ python scripts/build.py # Build for current platform
+ python scripts/build.py --clean # Clean build artifacts first
+ python scripts/build.py --skip-frontend # Skip frontend build (use existing)
+ python scripts/build.py --version 1.2.3 # Override version (for CI)
"""
import os
@@ -23,10 +23,13 @@
# Build configuration
APP_NAME = "PrintQue"
+# Repo root (script lives in scripts/)
+ROOT_DIR = Path(__file__).resolve().parent.parent
+
def get_version_from_file() -> str:
"""Read version from api/__version__.py"""
- version_file = Path(__file__).parent / "api" / "__version__.py"
+ version_file = ROOT_DIR / "api" / "__version__.py"
if version_file.exists():
content = version_file.read_text()
match = re.search(r'__version__\s*=\s*["\']([^"\']+)["\']', content)
@@ -39,7 +42,6 @@ def get_version_from_file() -> str:
VERSION = get_version_from_file()
# Directories
-ROOT_DIR = Path(__file__).parent.absolute()
API_DIR = ROOT_DIR / "api"
APP_DIR = ROOT_DIR / "app"
BUILD_DIR = ROOT_DIR / "build"
diff --git a/build_windows.py b/scripts/build_windows.py
similarity index 62%
rename from build_windows.py
rename to scripts/build_windows.py
index 1aef6d5..368d7dc 100644
--- a/build_windows.py
+++ b/scripts/build_windows.py
@@ -1,6 +1,10 @@
#!/usr/bin/env python
"""
-Fixed build script for PrintQue Windows executable
+Legacy Windows-specific build script for PrintQue.
+For current builds use: python scripts/build.py
+
+This script expects to be run from the repository root. It uses ROOT_DIR
+so that build/ and dist/ are at repo root when run from scripts/.
"""
import os
import sys
@@ -8,53 +12,59 @@
import subprocess
import zipfile
from datetime import datetime
+from pathlib import Path
+
+# Repo root (script lives in scripts/)
+ROOT_DIR = Path(__file__).resolve().parent.parent
+
def clean_build():
"""Clean previous build artifacts"""
print("Cleaning previous builds...")
- dirs_to_remove = ['build', 'dist', '__pycache__']
- for dir_name in dirs_to_remove:
- if os.path.exists(dir_name):
- shutil.rmtree(dir_name)
- print(f"Removed {dir_name}")
+ dirs_to_remove = [ROOT_DIR / 'build', ROOT_DIR / 'dist', ROOT_DIR / 'api' / '__pycache__']
+ for d in dirs_to_remove:
+ if d.exists():
+ shutil.rmtree(d)
+ print(f"Removed {d}")
+
def check_files():
- """Verify all required files exist"""
+ """Verify all required files exist (under api/)"""
print("\nChecking required files...")
- required_files = [
- 'app.py',
- 'run_app.py',
- 'routes.py',
- 'state.py',
- 'printer_manager.py',
- 'config.py',
- 'requirements.txt'
+ api = ROOT_DIR / 'api'
+ required = [
+ api / 'app.py',
+ api / 'state.py',
+ api / 'printer_manager.py',
+ api / 'config.py',
+ api / 'requirements.txt',
]
-
- missing_files = []
- for file in required_files:
- if os.path.exists(file):
- print(f"[OK] {file}")
+ required_dirs = [api / 'routes']
+ missing = []
+ for f in required:
+ if f.exists():
+ print(f"[OK] {f.relative_to(ROOT_DIR)}")
else:
- print(f"[MISSING] {file}")
- missing_files.append(file)
-
- if missing_files:
+ print(f"[MISSING] {f.relative_to(ROOT_DIR)}")
+ missing.append(f)
+ for d in required_dirs:
+ if d.is_dir():
+ print(f"[OK] {d.relative_to(ROOT_DIR)}/")
+ else:
+ print(f"[MISSING] {d.relative_to(ROOT_DIR)}/")
+ missing.append(d)
+ if missing:
print("\nError: Missing required files!")
return False
-
- # Check templates
- print("\nChecking templates...")
- template_dir = 'templates'
- if os.path.exists(template_dir):
- templates = os.listdir(template_dir)
- print(f"Found {len(templates)} templates")
+ template_dir = api / 'templates'
+ if template_dir.exists():
+ print(f"Found {len(list(template_dir.iterdir()))} templates")
else:
- print("[MISSING] templates directory!")
+ print("[MISSING] api/templates directory!")
return False
-
return True
+
def build_exe():
"""Build the executable using PyInstaller"""
print("\nBuilding PrintQue.exe...")
@@ -152,15 +162,13 @@ def build_exe():
)
'''
- # Write spec file
- with open('PrintQue.spec', 'w') as f:
- f.write(spec_content)
-
- # Build using the spec file
- cmd = ['pyinstaller', 'PrintQue.spec', '--clean', '-y']
+ spec_file = ROOT_DIR / 'PrintQue.spec'
+ spec_file.write_text(spec_content)
+ # Build using the spec file (run from root so dist/build at root)
+ cmd = [sys.executable, '-m', 'PyInstaller', str(spec_file), '--clean', '-y']
try:
- result = subprocess.run(cmd, check=True, capture_output=True, text=True)
+ result = subprocess.run(cmd, cwd=str(ROOT_DIR), check=True, capture_output=True, text=True)
print("Build completed successfully!")
print(result.stdout)
return True
@@ -174,44 +182,28 @@ def build_exe():
print("Please install it with: pip install pyinstaller")
return False
+
def create_distribution():
"""Create a distribution package"""
print("\nCreating distribution package...")
dist_name = f"PrintQue_Windows_{datetime.now().strftime('%Y%m%d')}"
- dist_dir = os.path.join('dist', dist_name)
+ dist_dir = ROOT_DIR / 'dist' / dist_name
- # Create distribution directory
- os.makedirs(dist_dir, exist_ok=True)
+ dist_dir.mkdir(parents=True, exist_ok=True)
- # Copy executable
- if os.path.exists('dist/PrintQue.exe'):
- shutil.copy('dist/PrintQue.exe', dist_dir)
+ src_exe = ROOT_DIR / 'dist' / 'PrintQue.exe'
+ if src_exe.exists():
+ shutil.copy(src_exe, dist_dir)
print(f"Copied PrintQue.exe to {dist_dir}")
else:
print("Error: PrintQue.exe not found!")
return False
- # Create data directories
- data_dirs = ['data', 'uploads', 'certs']
- for dir_name in data_dirs:
- target_dir = os.path.join(dist_dir, dir_name)
- os.makedirs(target_dir, exist_ok=True)
+ for dir_name in ['data', 'uploads', 'certs']:
+ (dist_dir / dir_name).mkdir(exist_ok=True)
print(f"Created directory: {dir_name}")
- # Copy additional files
- files_to_copy = [
- 'README.txt',
- 'requirements.txt',
- ]
-
- for file in files_to_copy:
- if os.path.exists(file):
- shutil.copy(file, dist_dir)
- print(f"Copied {file}")
-
-
- # Create batch file launcher
batch_content = """@echo off
title PrintQue Server
echo ================================================
@@ -229,12 +221,9 @@ def create_distribution():
PrintQue.exe
pause
"""
-
- with open(os.path.join(dist_dir, 'Start_PrintQue.bat'), 'w') as f:
- f.write(batch_content)
+ (dist_dir / 'Start_PrintQue.bat').write_text(batch_content)
print("Created Start_PrintQue.bat")
- # Create README
readme_content = """PrintQue - Open Source Print Farm Manager
==========================================
@@ -266,39 +255,30 @@ def create_distribution():
Version: 1.0
"""
-
- with open(os.path.join(dist_dir, 'README.txt'), 'w') as f:
- f.write(readme_content)
+ (dist_dir / 'README.txt').write_text(readme_content)
print("Created README.txt")
- # Create a zip file
- print(f"\nCreating zip archive: {dist_name}.zip")
- zip_path = f'dist/{dist_name}.zip'
-
+ zip_path = ROOT_DIR / 'dist' / f'{dist_name}.zip'
with zipfile.ZipFile(zip_path, 'w', zipfile.ZIP_DEFLATED) as zipf:
for root, dirs, files in os.walk(dist_dir):
for file in files:
file_path = os.path.join(root, file)
- arcname = os.path.relpath(file_path, os.path.dirname(dist_dir))
+ arcname = os.path.relpath(file_path, dist_dir.parent)
zipf.write(file_path, arcname)
-
print(f"Distribution package created: {zip_path}")
return True
+
def main():
print("=" * 60)
- print(" PrintQue Windows Executable Builder")
+ print(" PrintQue Windows Executable Builder (legacy)")
+ print(" Prefer: python scripts/build.py")
print("=" * 60)
- # Check Python version
if sys.version_info < (3, 8):
print("Error: Python 3.8 or higher is required")
- print(f"Current version: {sys.version}")
return 1
- print(f"Python version: {sys.version.split()[0]}")
-
- # Check if PyInstaller is installed
try:
import PyInstaller
print(f"PyInstaller version: {PyInstaller.__version__}")
@@ -307,26 +287,13 @@ def main():
print("Install it with: pip install pyinstaller")
return 1
- # Clean previous builds
clean_build()
-
- # Check required files
if not check_files():
print("\nBuild cannot continue - missing required files!")
return 1
-
- # Build executable
- print("\n" + "=" * 60)
- print("Building executable...")
- print("=" * 60)
if not build_exe():
print("\nBuild failed! Check the error messages above.")
return 1
-
- # Create distribution
- print("\n" + "=" * 60)
- print("Creating distribution package...")
- print("=" * 60)
if not create_distribution():
print("\nDistribution creation failed!")
return 1
@@ -334,18 +301,9 @@ def main():
print("\n" + "=" * 60)
print(" Build Completed Successfully!")
print("=" * 60)
- print("\nOutput files:")
- print(f" - Executable: dist/PrintQue.exe")
- print(f" - Distribution folder: dist/PrintQue_Windows_{datetime.now().strftime('%Y%m%d')}/")
- print(f" - Zip package: dist/PrintQue_Windows_{datetime.now().strftime('%Y%m%d')}.zip")
- print("\nTo test the executable:")
- print(" 1. Navigate to: dist/PrintQue_Windows_{datetime.now().strftime('%Y%m%d')}/")
- print(" 2. Double-click: Start_PrintQue.bat")
- print(" 3. Open browser to: http://localhost:5000")
- print("\nNote: First run may be slow as Windows Defender scans the file.")
- print("=" * 60)
-
+ print(f"\nOutput: {ROOT_DIR / 'dist'}")
return 0
+
if __name__ == "__main__":
- sys.exit(main())
\ No newline at end of file
+ sys.exit(main())
diff --git a/complete_build.py b/scripts/complete_build.py
similarity index 71%
rename from complete_build.py
rename to scripts/complete_build.py
index fa3075e..bf634cf 100644
--- a/complete_build.py
+++ b/scripts/complete_build.py
@@ -1,11 +1,20 @@
#!/usr/bin/env python
"""
-Complete build script with all dependencies for PrintQue
+Legacy complete build script with all dependencies for PrintQue.
+For current builds use: python scripts/build.py
+
+Run from repository root. Uses ROOT_DIR for build/dist; runs PyInstaller from api/.
"""
import os
import sys
import shutil
import subprocess
+from pathlib import Path
+
+# Repo root (script lives in scripts/)
+ROOT_DIR = Path(__file__).resolve().parent.parent
+API_DIR = ROOT_DIR / "api"
+
def create_spec_file():
"""Create a comprehensive spec file with all dependencies"""
@@ -94,11 +103,11 @@ def create_spec_file():
'paho.mqtt.client',
]
-# Add your project files and folders
+# Add your project files and folders (paths relative to api_dir / cwd)
datas += [
('templates', 'templates'),
('static', 'static') if os.path.exists('static') else ('templates', '.'),
- ('README.txt', '.') if os.path.exists('README.txt') else ('requirements.txt', '.'),
+ ('requirements.txt', '.'),
]
# Add all your Python modules
@@ -162,48 +171,44 @@ def create_spec_file():
)
'''
- with open('PrintQue_Complete.spec', 'w') as f:
- f.write(spec_content)
+ spec_file = API_DIR / 'PrintQue_Complete.spec'
+ spec_file.write_text(spec_content)
print("Created PrintQue_Complete.spec")
+
def build_exe():
"""Build the executable"""
print("\nBuilding PrintQue.exe with all dependencies...")
- # First, make sure all packages are installed
- print("Verifying package installation...")
packages = [
- 'flask', 'flask-socketio', 'eventlet', 'python-socketio',
+ 'flask', 'flask-socketio', 'eventlet', 'python-socketio',
'python-engineio', 'werkzeug', 'jinja2', 'cryptography',
'aiohttp', 'requests', 'psutil',
'simple-websocket', 'dnspython', 'paho-mqtt'
]
-
for package in packages:
- result = subprocess.run([sys.executable, '-m', 'pip', 'show', package],
- capture_output=True, text=True)
+ result = subprocess.run([sys.executable, '-m', 'pip', 'show', package],
+ capture_output=True, text=True, cwd=str(ROOT_DIR))
if result.returncode != 0:
print(f"Installing missing package: {package}")
- subprocess.run([sys.executable, '-m', 'pip', 'install', package])
+ subprocess.run([sys.executable, '-m', 'pip', 'install', package], cwd=str(ROOT_DIR))
- # Create the spec file
create_spec_file()
- # Build using PyInstaller
- cmd = ['pyinstaller', 'PrintQue_Complete.spec', '--clean', '-y']
-
+ cmd = [
+ sys.executable, '-m', 'PyInstaller',
+ str(API_DIR / 'PrintQue_Complete.spec'),
+ '--clean', '-y',
+ '--distpath', str(ROOT_DIR / 'dist'),
+ '--workpath', str(ROOT_DIR / 'build'),
+ ]
try:
print("\nRunning PyInstaller...")
- result = subprocess.run(cmd, check=False)
-
+ result = subprocess.run(cmd, cwd=str(API_DIR), check=False)
if result.returncode == 0:
print("\nBuild completed successfully!")
-
- # Create the distribution folder
- if os.path.exists('dist/PrintQue.exe'):
- print("\nCreating distribution package...")
-
- # Create Start_PrintQue.bat
+ exe_path = ROOT_DIR / 'dist' / 'PrintQue.exe'
+ if exe_path.exists():
batch_content = """@echo off
title PrintQue Server
echo Starting PrintQue Server...
@@ -217,53 +222,39 @@ def build_exe():
PrintQue.exe
pause
"""
-
- with open('dist/Start_PrintQue.bat', 'w') as f:
- f.write(batch_content)
-
- # Copy templates if they exist
- if os.path.exists('templates'):
- dest_templates = 'dist/templates'
- if os.path.exists(dest_templates):
- shutil.rmtree(dest_templates)
- shutil.copytree('templates', dest_templates)
+ (ROOT_DIR / 'dist' / 'Start_PrintQue.bat').write_text(batch_content)
+ if (API_DIR / 'templates').exists():
+ dest = ROOT_DIR / 'dist' / 'templates'
+ if dest.exists():
+ shutil.rmtree(dest)
+ shutil.copytree(API_DIR / 'templates', dest)
print("Copied templates folder")
-
print("\n" + "="*60)
print("BUILD SUCCESSFUL!")
print("="*60)
- print("\nYour executable is ready in the 'dist' folder:")
- print(" - PrintQue.exe (main executable)")
- print(" - Start_PrintQue.bat (easy launcher)")
- print(" - templates/ (HTML templates)")
- print("\nPrintQue Open Source Edition - All features enabled!")
- print("\nTo run PrintQue:")
- print(" 1. Go to the dist folder")
- print(" 2. Double-click Start_PrintQue.bat")
-
+ print(f"\nOutput: {ROOT_DIR / 'dist'}")
else:
print("\nError: PrintQue.exe was not created!")
-
else:
print(f"\nBuild failed with return code: {result.returncode}")
-
except Exception as e:
print(f"\nBuild error: {str(e)}")
+
def main():
print("="*60)
- print("PrintQue Complete Build Script")
+ print("PrintQue Complete Build Script (legacy)")
+ print("Prefer: python scripts/build.py")
print("="*60)
- # Clean old builds
print("\nCleaning old builds...")
- for folder in ['build', 'dist']:
- if os.path.exists(folder):
+ for folder in [ROOT_DIR / 'build', ROOT_DIR / 'dist']:
+ if folder.exists():
shutil.rmtree(folder)
print(f"Removed {folder}")
- # Build the executable
build_exe()
+
if __name__ == "__main__":
- main()
\ No newline at end of file
+ main()
diff --git a/install-macos.sh b/scripts/install-macos.sh
similarity index 98%
rename from install-macos.sh
rename to scripts/install-macos.sh
index f117afe..04e55c3 100644
--- a/install-macos.sh
+++ b/scripts/install-macos.sh
@@ -126,4 +126,4 @@ echo "Default admin credentials: admin / printque"
echo "Please change these credentials on first login!"
echo
echo "A shortcut has been created in your Applications folder."
-echo "PrintQue will automatically start when you log in."
\ No newline at end of file
+echo "PrintQue will automatically start when you log in."
diff --git a/run_app.py b/scripts/run_app.py
similarity index 93%
rename from run_app.py
rename to scripts/run_app.py
index 70317d1..da774b5 100644
--- a/run_app.py
+++ b/scripts/run_app.py
@@ -1,6 +1,9 @@
"""
Enhanced error handling wrapper for PrintQue application.
Modified to remove tkinter dependency and work with PyInstaller.
+
+When run from scripts/ (e.g. python scripts/run_app.py), adds api/ to path
+and uses api/ for templates/static. Run from repo root.
"""
import os
import sys
@@ -10,8 +13,17 @@
import shutil
import atexit
import re
+from pathlib import Path
def main():
+ # When run from scripts/, ensure api is on path and we use api dir for templates/static
+ script_dir = Path(__file__).resolve().parent
+ root_dir = script_dir.parent
+ api_dir = root_dir / "api"
+ if script_dir.name == "scripts":
+ sys.path.insert(0, str(api_dir))
+ os.chdir(api_dir)
+
try:
# Set up error logging
error_log_path = os.path.join(os.path.expanduser("~"), "PrintQueData", "flask_error.log")
@@ -82,7 +94,8 @@ def open_browser():
if getattr(sys, 'frozen', False):
base_dir = sys._MEIPASS
else:
- base_dir = os.path.dirname(os.path.abspath(__file__))
+ # When run from scripts/, use api dir for templates/static
+ base_dir = str(api_dir) if script_dir.name == "scripts" else os.path.dirname(os.path.abspath(__file__))
template_folder = os.path.join(base_dir, "templates")
static_folder = os.path.join(base_dir, "static")
@@ -228,4 +241,4 @@ def cleanup_on_exit():
time.sleep(2)
if __name__ == "__main__":
- main()
\ No newline at end of file
+ main()
diff --git a/test_build.py b/scripts/test_build.py
similarity index 94%
rename from test_build.py
rename to scripts/test_build.py
index 52e42b9..c77b5db 100644
--- a/test_build.py
+++ b/scripts/test_build.py
@@ -3,8 +3,8 @@
Quick test script to verify the PrintQue build works correctly.
Usage:
- python test_build.py # Test the source directly
- python test_build.py --dist # Test the built executable
+ python scripts/test_build.py # Test the source directly
+ python scripts/test_build.py --dist # Test the built executable
"""
import os
@@ -16,7 +16,8 @@
import requests
from pathlib import Path
-ROOT_DIR = Path(__file__).parent.absolute()
+# Repo root (script lives in scripts/)
+ROOT_DIR = Path(__file__).resolve().parent.parent
DIST_DIR = ROOT_DIR / "dist"
API_DIR = ROOT_DIR / "api"
@@ -138,7 +139,7 @@ def run_dist_test():
if not exe_path.exists():
print(f"[ERROR] Executable not found at {exe_path}")
- print("Run 'python build.py' first to create the executable.")
+ print("Run 'python scripts/build.py' first to create the executable.")
return 1
print(f"Starting executable: {exe_path}")