diff --git a/.github/SETUP.md b/.github/SETUP.md deleted file mode 100644 index 05bd0b9..0000000 --- a/.github/SETUP.md +++ /dev/null @@ -1,60 +0,0 @@ -#Setup Guide - -In order to run Web2Executable from Python code to make modifications to this repo, you'll need to install a few prerequisites first. - -##Prerequisites - -###Qt 4.8.7 - -Download and install Qt 4.8.7 from [here](https://download.qt.io/official_releases/qt/4.8/4.8.7/) or from your package manager. PySide uses Qt 4.8.X and is incompatible with Qt 5 and higher. - -For Mac OSX, it might be needed to install the latest 4.8.X version via compiling the source. Download the `qt-everywhere-opensource-src-4.8.X` version and run the following in the extracted directory: - -``` -./configure -make -sudo make install -``` - -Alternatively, you may wish to install it via homebrew if you have it installed: - -``` -brew install qt -``` - -###Python 3.4 - -Download and install the latest Python3.4.X release from the Python website. Python 3.5 and higher may work, but this repo was only tested with Python 3.4. - -###System requirements - -If you want to use the conversion of any image to icns and png using Pillow, you must install some libraries. - -libjpeg, libpng, openjpeg - -###Pip Requirements - -Install pip requirements with - -``` -pip install -r requirements.txt -``` - -##Running - -Once all of the above are installed, simply run: - -``` -python3.4 main.py -``` - -##Tests - -Tests are located in the tests directory and were created with pytest. -They can be run with: - -``` -pytest -``` - -in the root directory. diff --git a/.github/dependabot.yml b/.github/dependabot.yml index 600c340..daf573a 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -3,17 +3,15 @@ updates: - package-ecosystem: "pip" directory: "." schedule: - interval: "weekly" - day: "saturday" + interval: "monthly" groups: pip: patterns: - "*" - package-ecosystem: "github-actions" - directory: ".github/" + directory: ".github" schedule: - interval: "weekly" - day: "saturday" + interval: "monthly" groups: gha: patterns: diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..a8615fe --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,69 @@ +name: Python CI + +on: + pull_request: + branches: + - main + +permissions: + contents: read + pull-requests: write + +concurrency: + group: ${{ github.ref }} + cancel-in-progress: true + +jobs: + test: + strategy: + matrix: + os: + - macos-26 + - macos-26-intel + - ubuntu-24.04 + # TODO: fails on ARM + # - ubuntu-24.04-arm + - windows-2025 + - windows-11-arm + python-version: + - '3.14' + include: + - os: macos-26 + display: MacOS 26 ARM + - os: macos-26-intel + display: MacOS 26 Intel + - os: ubuntu-24.04 + display: Ubuntu 24.04 Intel + - os: ubuntu-24.04-arm + # TODO: fails on ARM + # display: Ubuntu 24.04 ARM + # - os: windows-2025 + display: Windows 11 Intel + - os: windows-11-arm + display: Windows 11 (ARM) + fail-fast: false + + name: ${{ matrix.display }} / Python ${{ matrix.python-version }} + + runs-on: ${{ matrix.os }} + steps: + - uses: actions/checkout@v4.2.2 + - uses: actions/setup-python@v5.6.0 + with: + python-version: ${{ matrix.python-version }} + cache: pip + - if: matrix.os == 'ubuntu-24.04' || matrix.os == 'ubuntu-24.04-arm' + uses: awalsh128/cache-apt-pkgs-action@v1.5.1 + with: + packages: libegl1 + version: 1.0 + execute_install_scripts: true + - name: Install dependencies + run: python -m pip install -r requirements.txt + - name: Check for formatting errors + run: python -m ruff format ./**/*.py + # TODO: resolve all linting errors + # - name: Check for linting errors + # run: python -m ruff check ./**/*.py + - name: Run test suite + run: python -m pytest diff --git a/.github/workflows/e2e.yml b/.github/workflows/e2e.yml deleted file mode 100644 index 32bcf28..0000000 --- a/.github/workflows/e2e.yml +++ /dev/null @@ -1,31 +0,0 @@ -name: CI - -on: - pull_request: - branches: [ main ] - -concurrency: - group: ${{ github.ref }} - cancel-in-progress: true - -jobs: - test: - strategy: - matrix: - os: [macos-15, ubuntu-24.04, windows-2025] - fail-fast: false - runs-on: ${{ matrix.os }} - steps: - - uses: actions/checkout@v4.2.2 - - uses: actions/setup-python@v5.6.0 - with: - python-version: '3.13.0' - cache: pip - - if: matrix.os == 'ubuntu-24.04' - uses: awalsh128/cache-apt-pkgs-action@v1.5.1 - with: - packages: libegl1 - version: 1.0 - - run: python -m pip install -r requirements.txt - # - run: python -m pylint ./**/*.py - - run: python -m pytest diff --git a/LICENSE.md b/LICENSE.md index 6ecd81a..d068537 100644 --- a/LICENSE.md +++ b/LICENSE.md @@ -1,7 +1,7 @@ The MIT License (MIT) Copyright (c) 2015-2023 SimplyPixelated -Copyright (c) 2022-2023 NW.js Utilities +Copyright (c) 2022-2026 NW.js Utilities Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/README.md b/README.md index ca28069..f506c1b 100644 --- a/README.md +++ b/README.md @@ -1,16 +1,15 @@ -> This repository is in maintainance mode. Bug fixes will be provided on a best effort basis. If you use this project, please consider contributing back. +# Web2Executable -Web2Executable -============== +> [!Note] +> This repository is in maintainance mode. Bug fixes will be provided on a best effort basis. If you use this project, please consider contributing back. -[![Github Releases (by Release)](https://img.shields.io/github/downloads/jyapayne/Web2Executable/latest/total.svg)]() +![Github Releases (by Release)](https://img.shields.io/github/downloads/jyapayne/Web2Executable/latest/total.svg) -[Releases (Downloads)](https://github.com/jyapayne/Web2Executable/releases) (new!) +![Releases (Downloads)](https://github.com/jyapayne/Web2Executable/releases) -[![Github All Releases](https://img.shields.io/github/downloads/jyapayne/Web2Executable/total.svg)]() +![Github All Releases](https://img.shields.io/github/downloads/jyapayne/Web2Executable/total.svg) -What is it? ------------ +## What is it? Web2Executable is a friendly command line and GUI application that can transform your Nodejs (or any other JS/HTML) app into an executable (and libraries) that can run in a contained, desktop-like style. It can export to Mac OS X, Windows and Linux all from one platform, so no need to go out and buy expensive hardware. @@ -20,14 +19,11 @@ If you have an idea for a feature, please create a new issue with a format like On the other hand, if you have any annoyances with the application and want to contribute to making it better for everyone, please file an issue with "Annoyance:" as the first part of the title. Sometimes it's hard to know what is annoying for people and input is much appreciated :) -What About Electron? --------------------- +## What About Electron? If you want to export using Electron instead of NW.js, try [Electrify](https://github.com/jyapayne/Electrify), my other app based on Web2Executable. - -Who's Using It? ---------------- +## Who's Using It? Lots of people! There are currently thousands of downloads and several articles written about using Web2Executable. @@ -45,9 +41,7 @@ Some articles include: If you have a project you'd like to see listed here that was successfully built using Web2Executable or you have written an article that mentions it, feel free to send me an email with a link and I'd be super stoked to paste it here :) - -Features --------- +## Features - Cross platform to Mac, Windows, Linux - Working media out of the box (sound and video) @@ -61,15 +55,12 @@ Features - A command line utility with functionality equivalent to the GUI. Useful for automated builds. - Compression of executables with upx -Planned New Features --------------------- +## Planned New Features - The ability to add external files to the project - Minifying JS and HTML - -Getting Started ---------------- +## Getting Started ### Using Prebuilt Binaries @@ -77,11 +68,10 @@ When using the prebuilt binaries for Windows, Mac, or Ubuntu, there are NO depen **NOTE!**: Some people report needing the Microsoft Visual C++ 2008/2010 SP1 and regular Redistributable package. I don't have a machine to test this, but just know that you might need the package if the application won't open or spits out the following error: -``` +```shell Error: The application has failed to start because the side by side configuration is incorrect please see the application event log or use the command line sxstrace.exe tool for more detail. ``` - ### Building from Source Requires the PySide library and Python 3.4.3 or higher. If you want to replace the icon in the Windows Exe's, this will do it automatically with the latest code if you have PIL or Pillow installed. @@ -94,7 +84,7 @@ Run the command_line.py with the --help option to see a list of export options. Example usage (if using the prebuilt binary, replace `python3.4 command_line.py` with the exe name): -``` +```shell python3.4 command_line.py /var/www/html/CargoBlaster/ --main html/index.html --export-to linux-x64 windows mac --width 900 --height 700 --nw-version 0.10.5 ``` @@ -102,19 +92,19 @@ python3.4 command_line.py /var/www/html/CargoBlaster/ --main html/index.html --e Install dependencies: -``` +```shell pip install -r requirements.txt ``` Initiate submodules: -``` +```shell git submodule update --init --recursive ``` Run with: -``` +```shell python3.4 main.py ``` @@ -125,42 +115,18 @@ General Instructions for exporting: 3. Choose at least one export platform and then the Export button should be enabled (as long as the field names marked with a star are filled out and all files in the fields exist). 4. Click the export button and once it's done, click the "Open Export Folder" button to go to the folder where your exported project will be. - ### Issues? If you have an issue, please check the FAQ before filing an issue to see if it helps. [FAQ](https://github.com/jyapayne/Web2Executable/wiki/FAQ) - -### Additional Info +## Additional Info [Changelog](https://github.com/jyapayne/Web2Executable/releases) [Screenshots](https://github.com/jyapayne/Web2Executable/wiki/Screenshots) +## License -License -------- - -The MIT License (MIT) - -Copyright (c) 2015 SimplyPixelated - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. +MIT. diff --git a/files/env_vars.py b/files/env_vars.py index 34c6681..02a3b8d 100644 --- a/files/env_vars.py +++ b/files/env_vars.py @@ -1,11 +1,11 @@ PROJECT_DIR = "{proj_dir}" PROJECT_NAME = "{proj_name}" EXPORT_DIR = "{export_dir}" -EXPORT_DIRS = {export_dirs} +EXPORT_DIRS = "{export_dirs}" MAC64_EXPORT_DIR = "{mac-x64_dir}" MAC32_EXPORT_DIR = "{mac-x32_dir}" WIN64_EXPORT_DIR = "{windows-x64_dir}" WIN32_EXPORT_DIR = "{windows-x32_dir}" LINUX64_EXPORT_DIR = "{linux-x64_dir}" LINUX32_EXPORT_DIR = "{linux-x32_dir}" -NUM_DIRS = {num_dirs} +NUM_DIRS = "{num_dirs}" diff --git a/files/settings.cfg b/files/settings.cfg index 1ae0302..0319809 100644 --- a/files/settings.cfg +++ b/files/settings.cfg @@ -20,7 +20,7 @@ linux_64_dir_prefix = 'nwjs-v{}-linux-x64' required=True type='string' description='The name in the internal package.json. Must be alpha-numeric with no spaces.' - filter=r'[a-z0-9_\\-\.]+' + filter=r'[a-z0-9_\-\.]+' filter_action='lower' [[[app_name]]] display_name='App Name' diff --git a/image_utils/icns_info.py b/image_utils/icns_info.py index d8fb004..06e3c0a 100644 --- a/image_utils/icns_info.py +++ b/image_utils/icns_info.py @@ -1,12 +1,13 @@ """A module to parse the contents of an ICNS file.""" -import struct -import image_utils.image_utils as image_utils -import image_utils.png as png -from PIL import Image import os +import struct from io import BytesIO +from PIL import Image + +from image_utils import image_utils, png + # ---------------------CONSTANTS-----------------------------------------------# ICNS_TABLE_OF_CONTENTS = 0x544F4320 # "TOC " @@ -446,9 +447,9 @@ def encode_rle24(data): ): dataTemp[dataTempCount] = runLength - 3 dataTempCount += 1 - dataTemp[ - dataTempCount : dataTempCount + runLength - 2 - ] = dataRun[: runLength - 2] + dataTemp[dataTempCount : dataTempCount + runLength - 2] = ( + dataRun[: runLength - 2] + ) dataTempCount += runLength - 2 runCount += 1 @@ -639,7 +640,7 @@ def type_to_str(type): return bytes(s) -class Printable(object): +class Printable: def _attrs(self): a = [] for attr in dir(self): @@ -655,22 +656,22 @@ def _dict_string(self): vals = [] for key, val in self._dict_items(): try: - vals.append("{}={}".format(key, val)) + vals.append(f"{key}={val}") except UnicodeDecodeError: - vals.append("{}=".format(key)) + vals.append(f"{key}=") return ", ".join(vals) def __repr__(self): return str(self) def __str__(self): - return "{} [{}]".format(self.__class__.__name__, self._dict_string()) + return f"{self.__class__.__name__} [{self._dict_string()}]" # ---------------------------CLASSES-------------------------------------------# -class Field(object): +class Field: """This is a field object that will describe a field on the class it is a part of. """ @@ -994,7 +995,7 @@ def from_type(cls, type): icon_info.iconPixelDepth = 1 icon_info.iconBitDepth = 1 else: - print("Unable to parse icon type {}".format(type_to_str(type))) + print(f"Unable to parse icon type {type_to_str(type)}") icon_info.iconType = ICNS_NULL_TYPE icon_info.iconRawDataSize = int( @@ -1296,8 +1297,7 @@ def get_mask(self): while data_count < icns_info.iconSize.height: data_pos = data_count * mask_data_row_size icns_info.data[data_pos : data_pos + mask_data_row_size] = data[ - data_pos - + mask_data_size : data_pos + data_pos + mask_data_size : data_pos + mask_data_row_size + mask_data_size ] @@ -1355,7 +1355,9 @@ def icns_header_check(icns_data): if resource_type != ICNS_FAMILY_TYPE: raise Exception("File is not an ICNS file.") if resource_size != len(icns_data): - raise Exception("Expected size {}, but got {}".format(len(icns_data))) + raise Exception( + f"Expected size {resource_size}, but got {len(icns_data)}" + ) def icns_parse_family_data(icns_data): diff --git a/image_utils/image_utils.py b/image_utils/image_utils.py index f100d16..c97e124 100644 --- a/image_utils/image_utils.py +++ b/image_utils/image_utils.py @@ -9,7 +9,7 @@ def resize(image, size): output = BytesIO() back = im.new("RGBA", size, (0, 0, 0, 0)) - image.thumbnail(size, im.ANTIALIAS) + image.thumbnail(size, im.LANCZOS) offset = [0, 0] if image.size[0] >= image.size[1]: offset[1] = int(back.size[1] / 2 - image.size[1] / 2) diff --git a/image_utils/png.py b/image_utils/png.py index 2273acf..ccb4c1f 100644 --- a/image_utils/png.py +++ b/image_utils/png.py @@ -1,6 +1,5 @@ #!/usr/bin/env python -from __future__ import print_function # png.py - PNG encoder/decoder in pure Python # @@ -75,7 +74,7 @@ or Lightness which is the channel used in greyscale images; *R*, *G*, *B* stand for Red, Green, Blue, the components of a colour image; *A* stands for Alpha, the opacity channel (used for transparency effects, -but higher values are more opaque, so it makes sense to call it +but higher values are more opaque, so it makes sense to call it opacity). A note on formats @@ -157,7 +156,6 @@ # http://www.python.org/doc/2.4.4/lib/module-warnings.html import warnings import zlib - from array import array from functools import reduce @@ -172,7 +170,7 @@ pass -__all__ = ["Image", "Reader", "Writer", "write_chunks", "from_array"] +__all__ = ["Image", "Reader", "Writer", "from_array", "write_chunks"] # The PNG signature. @@ -785,7 +783,7 @@ def extend(sl): try: # If this fails... extend(row) - except: + except Exception: # ... try a version that converts the values to int first. # Not only does this work for the (slightly broken) NumPy # types, there are probably lots of other, unknown, "nearly" @@ -1287,7 +1285,7 @@ def from_array(a, mode=None, info={}): bitdepth = 8 * dtype.itemsize info["bitdepth"] = bitdepth - for thing in "width height bitdepth greyscale alpha".split(): + for thing in ["width", "height", "bitdepth", "greyscale", "alpha"]: assert thing in info return Image(a, info) @@ -1980,15 +1978,15 @@ def iterdecomp(idat): # each row. pixels = map( lambda *row: array(arraycode, row), - *[iter(self.deinterlace(raw))] * self.width * self.planes + *[iter(self.deinterlace(raw))] * self.width * self.planes, ) else: pixels = self.iterboxed(self.iterstraight(raw)) meta = dict() - for attr in "greyscale alpha planes bitdepth interlace".split(): + for attr in ["greyscale", "alpha", "planes", "bitdepth", "interlace"]: meta[attr] = getattr(self, attr) meta["size"] = (self.width, self.height) - for attr in "gamma transparent background".split(): + for attr in ["gamma", "transparent", "background"]: a = getattr(self, attr, None) if a is not None: meta[attr] = a @@ -2339,7 +2337,7 @@ def isinteger(x): pngfilters except NameError: - class pngfilters(object): + class pngfilters: def undo_filter_sub(filter_unit, scanline, previous, result): """Undo sub filter.""" @@ -2708,7 +2706,8 @@ def _main(argv): parser.error("more than one input file") outfile = sys.stdout if sys.platform == "win32": - import msvcrt, os + import msvcrt + import os msvcrt.setmode(sys.stdout.fileno(), os.O_BINARY) diff --git a/image_utils/pycns.py b/image_utils/pycns.py index a31e58c..f066f06 100644 --- a/image_utils/pycns.py +++ b/image_utils/pycns.py @@ -1,6 +1,7 @@ +import sys + from image_utils.icns_info import ICNSHeader, icns_to_png from image_utils.image_utils import Image -import sys """This module takes any image that is readable by PIL and exports it to an icns file. The image will be scaled keeping the aspect ratio if it is a non square image. diff --git a/main.py b/main.py index 5540808..abe5ea8 100644 --- a/main.py +++ b/main.py @@ -781,7 +781,7 @@ def download_file(self, path, setting): reply = self.net_manager.get(request) reply.readyRead.connect(self.ready_read_file) - reply.error[QNetworkReply.NetworkError].connect(self.network_error) + reply.errorOccurred.connect(self.network_error) reply.sslErrors.connect(self.network_ssl_error) reply.downloadProgress.connect(self.update_progress_bar) self.current_download_request = reply @@ -1801,7 +1801,7 @@ def main(): frame = MainWindow(900, 500, app) frame.show_and_raise() - sys.exit(app.exec_()) + sys.exit(app.exec()) if __name__ == "__main__": diff --git a/pe.py b/pe.py index 1e507c0..8322235 100644 --- a/pe.py +++ b/pe.py @@ -19,10 +19,10 @@ def resize(image, size, format=None): else: factor = size[1] / image.width image = image.resize( - (int(image.width * factor), int(image.height * factor)), Image.ANTIALIAS + (int(image.width * factor), int(image.height * factor)), Image.LANCZOS ) else: - image.thumbnail(size, Image.ANTIALIAS) + image.thumbnail(size, Image.LANCZOS) offset = [0, 0] if image.size[0] > image.size[1]: @@ -1328,11 +1328,6 @@ def get_directory_by_type(self, type): def is_PEFile(self): """Checks if the file is a proper PE file""" - signature = None - try: - with open(self.file_path, "rb") as f: - signature = f.read(2) - except IOError as e: - raise e - finally: - return signature == self.signature + with open(self.file_path, "rb") as f: + signature = f.read(2) + return signature == self.signature diff --git a/requirements.txt b/requirements.txt index d90509b..6cdfe46 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,11 +1,12 @@ appdirs==1.4.4 -black==25.11.0 +black==26.5.1 configobj==5.0.9 -pillow==12.0.0 -pyinstaller==6.16.0 -pylint==4.0.3 -pyside6==6.10.1 -pytest==9.0.1 -requests==2.32.5 +ruff==0.16.3 +pillow==12.3.0 +pyinstaller==6.22.0 +pylint==4.0.7 +pyside6==6.11.1 +pytest==9.1.1 +requests==2.34.2 semantic_version==2.10.0 validators==0.35.0 diff --git a/scripts/upload_release.py b/scripts/upload_release.py index a427a21..4d2e31d 100644 --- a/scripts/upload_release.py +++ b/scripts/upload_release.py @@ -1,14 +1,12 @@ #!/usr/bin/python -import requests +import getpass import json import os -from pprint import pprint from glob import glob +import requests from semantic_version import Version -import getpass -import sys def main(): @@ -81,7 +79,7 @@ def main(): with open(zip_file, "rb") as zipf: file_data = zipf.read() - print("\nUploading file {}...".format(zip_file)) + print(f"\nUploading file {zip_file}...") data = {"name": zip_file} headers = {"Content-Type": "application/zip"} diff --git a/tests/conftest.py b/tests/conftest.py index 8e2ab6c..816c178 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -1,6 +1,3 @@ -import pytest - - def pytest_addoption(parser): parser.addoption( "--runapi", diff --git a/tests/test_command_line.py b/tests/test_command_line.py index b828723..0fd1db9 100644 --- a/tests/test_command_line.py +++ b/tests/test_command_line.py @@ -1,8 +1,9 @@ import os -import config -import utils + import pytest +import config +import utils from command_line import CommandBase # api = pytest.mark.skipif( @@ -77,6 +78,7 @@ def test_invalid_get_setting_objects(command_base): setting = command_base.get_setting(setting_name) assert setting == None + # TODO: investigate why this test is failing # def test_get_default_nwjs_branch(command_base): # import re @@ -87,6 +89,7 @@ def test_invalid_get_setting_objects(command_base): # assert match != None + def test_download_nwjs(command_base): command_base.get_setting("nw_version").value = "0.19.0" command_base.get_setting("windows-x64").value = True