Migrate to Poetry packaging and restructure codebase #21

Merged
Sudo-Ivan merged 23 commits from codebase-improvements into master 2025-12-01 18:24:25 +00:00
9 changed files with 126 additions and 130 deletions
Showing only changes of commit 0b0a39ea86 - Show all commits

View File

@@ -15,6 +15,7 @@ Makefile
build/
dist/
public/
meshchatx/public/
node_modules/
__pycache__/
*.py[cod]

View File

@@ -27,102 +27,14 @@ on:
default: 'true'
type: boolean
permissions:
contents: read
jobs:
build_windows:
runs-on: windows-latest
if: github.event_name == 'push' || (github.event_name == 'workflow_dispatch' && github.event.inputs.build_windows == 'true')
permissions:
contents: write
steps:
- name: Clone Repo
uses: actions/checkout@50fbc622fc4ef5163becd7fab6573eac35f8462e # v1
- name: Install NodeJS
uses: actions/setup-node@f1f314fca9dfce2769ece7d933488f076716723e # v1
with:
node-version: 22
- name: Install Python
uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5
with:
python-version: "3.12"
- name: Install Poetry
run: python -m pip install --upgrade pip poetry
- name: Sync versions
run: python scripts/sync_version.py
- name: Install Python Deps
run: python -m poetry install
- name: Install NodeJS Deps
run: npm install
- name: Build Electron App
run: npm run dist
- name: Create Release
id: create_release
uses: ncipollo/release-action@b7eabc95ff50cbeeedec83973935c8f306dfcd0b # v1
with:
draft: true
allowUpdates: true
replacesArtifacts: true
omitDraftDuringUpdate: true
omitNameDuringUpdate: true
artifacts: "dist/*-win-installer.exe,dist/*-win-portable.exe"
build_mac:
runs-on: macos-13
if: github.event_name == 'push' || (github.event_name == 'workflow_dispatch' && github.event.inputs.build_mac == 'true')
permissions:
contents: write
steps:
- name: Clone Repo
uses: actions/checkout@50fbc622fc4ef5163becd7fab6573eac35f8462e # v1
- name: Install NodeJS
uses: actions/setup-node@f1f314fca9dfce2769ece7d933488f076716723e # v1
with:
node-version: 18
- name: Install Python
uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5
with:
python-version: "3.11"
- name: Install Poetry
run: python -m pip install --upgrade pip poetry
- name: Sync versions
run: python scripts/sync_version.py
- name: Install Python Deps
run: python -m poetry install
- name: Install NodeJS Deps
run: npm install
- name: Build Electron App
run: npm run dist
- name: Create Release
id: create_release
uses: ncipollo/release-action@b7eabc95ff50cbeeedec83973935c8f306dfcd0b # v1
with:
draft: true
allowUpdates: true
replacesArtifacts: true
omitDraftDuringUpdate: true
omitNameDuringUpdate: true
artifacts: "dist/*-mac.dmg"
build_linux:
build_frontend:
runs-on: ubuntu-latest
if: github.event_name == 'push' || (github.event_name == 'workflow_dispatch' && github.event.inputs.build_linux == 'true')
permissions:
contents: write
contents: read
steps:
- name: Clone Repo
uses: actions/checkout@50fbc622fc4ef5163becd7fab6573eac35f8462e # v1
@@ -137,33 +49,108 @@ jobs:
with:
coderabbitai[bot] commented 2025-12-01 06:13:57 +00:00 (Migrated from github.com)
Review

⚠️ Potential issue | 🔴 Critical

Fix shell syntax error in SHA256 checksum generation script.

The for loop at line 226 has invalid shell syntax: 2>/dev/null cannot be placed after the glob pattern list in a for statement. This causes shellcheck to fail parsing (SC1058, SC1072, SC1073). Since the conditional if [ -f "$file" ] already handles non-matching globs, the redirection is unnecessary.

Apply this diff to fix the syntax error:

      - name: Generate SHA256 checksums
        run: |
          cd release-assets
          echo "## SHA256 Checksums" > release-body.md
          echo "" >> release-body.md
          
-         for file in *.exe *.dmg *.AppImage *.deb *.whl 2>/dev/null; do
+         for file in *.exe *.dmg *.AppImage *.deb *.whl; do
            if [ -f "$file" ]; then
              sha256sum "$file" | tee "${file}.sha256"
              echo "\`$(cat "${file}.sha256")\`" >> release-body.md
            fi
          done
          
          echo "" >> release-body.md
          echo "Individual \`.sha256\` files are included for each artifact." >> release-body.md
          
          cat release-body.md
          echo ""
          echo "Generated .sha256 files:"
-         ls -1 *.sha256 2>/dev/null || echo "No .sha256 files found"
+         ls -1 *.sha256 || echo "No .sha256 files found"
🧰 Tools
🪛 actionlint (1.7.9)

221-221: shellcheck reported issue in this script: SC1058:error:5:49: Expected 'do'

(shellcheck)


221-221: shellcheck reported issue in this script: SC1072:error:5:50: Fix any mentioned problems and try again

(shellcheck)


221-221: shellcheck reported issue in this script: SC1073:error:5:1: Couldn't parse this for loop. Fix to allow more checks

(shellcheck)

🤖 Prompt for AI Agents
In .github/workflows/build.yml around lines 220 to 239, the for loop at line
~226 includes an invalid redirection after the glob list which breaks shell
parsing; remove the trailing `2>/dev/null` from the for statement so the loop
reads: for file in *.exe *.dmg *.AppImage *.deb *.whl; do — keep the existing
`if [ -f "$file" ]` check to ignore non-matching globs and leave other
redirections (e.g. the final `ls -1 *.sha256 2>/dev/null`) untouched.
_⚠️ Potential issue_ | _🔴 Critical_ **Fix shell syntax error in SHA256 checksum generation script.** The for loop at line 226 has invalid shell syntax: `2>/dev/null` cannot be placed after the glob pattern list in a for statement. This causes shellcheck to fail parsing (SC1058, SC1072, SC1073). Since the conditional `if [ -f "$file" ]` already handles non-matching globs, the redirection is unnecessary. Apply this diff to fix the syntax error: ```diff - name: Generate SHA256 checksums run: | cd release-assets echo "## SHA256 Checksums" > release-body.md echo "" >> release-body.md - for file in *.exe *.dmg *.AppImage *.deb *.whl 2>/dev/null; do + for file in *.exe *.dmg *.AppImage *.deb *.whl; do if [ -f "$file" ]; then sha256sum "$file" | tee "${file}.sha256" echo "\`$(cat "${file}.sha256")\`" >> release-body.md fi done echo "" >> release-body.md echo "Individual \`.sha256\` files are included for each artifact." >> release-body.md cat release-body.md echo "" echo "Generated .sha256 files:" - ls -1 *.sha256 2>/dev/null || echo "No .sha256 files found" + ls -1 *.sha256 || echo "No .sha256 files found" ``` <details> <summary>🧰 Tools</summary> <details> <summary>🪛 actionlint (1.7.9)</summary> 221-221: shellcheck reported issue in this script: SC1058:error:5:49: Expected 'do' (shellcheck) --- 221-221: shellcheck reported issue in this script: SC1072:error:5:50: Fix any mentioned problems and try again (shellcheck) --- 221-221: shellcheck reported issue in this script: SC1073:error:5:1: Couldn't parse this for loop. Fix to allow more checks (shellcheck) </details> </details> <details> <summary>🤖 Prompt for AI Agents</summary> ``` In .github/workflows/build.yml around lines 220 to 239, the for loop at line ~226 includes an invalid redirection after the glob list which breaks shell parsing; remove the trailing `2>/dev/null` from the for statement so the loop reads: for file in *.exe *.dmg *.AppImage *.deb *.whl; do — keep the existing `if [ -f "$file" ]` check to ignore non-matching globs and leave other redirections (e.g. the final `ls -1 *.sha256 2>/dev/null`) untouched. ``` </details> <!-- This is an auto-generated comment by CodeRabbit -->
python-version: "3.12"
- name: Sync versions
run: python scripts/sync_version.py
- name: Install NodeJS Deps
run: npm install
- name: Build Frontend
run: npm run build-frontend
- name: Upload frontend artifact
uses: actions/upload-artifact@v4
with:
name: frontend-build
path: meshchatx/public
if-no-files-found: error
build_desktop:
name: Build Desktop (${{ matrix.name }})
needs: build_frontend
runs-on: ${{ matrix.os }}
if: |
github.event_name == 'push' ||
(
github.event_name == 'workflow_dispatch' &&
(
(matrix.name == 'windows' && github.event.inputs.build_windows == 'true') ||
(matrix.name == 'mac' && github.event.inputs.build_mac == 'true') ||
(matrix.name == 'linux' && github.event.inputs.build_linux == 'true')
)
)
strategy:
fail-fast: false
matrix:
include:
- name: windows
os: windows-latest
node: 22
python: "3.12"
release_artifacts: "dist/*-win-installer.exe,dist/*-win-portable.exe"
- name: mac
os: macos-13
node: 18
python: "3.11"
release_artifacts: "dist/*-mac.dmg"
- name: linux
os: ubuntu-latest
node: 22
python: "3.12"
release_artifacts: "dist/*-linux.AppImage,dist/*-linux.deb,python-dist/*.whl"
permissions:
contents: write
steps:
- name: Clone Repo
uses: actions/checkout@50fbc622fc4ef5163becd7fab6573eac35f8462e # v1
- name: Install NodeJS
uses: actions/setup-node@f1f314fca9dfce2769ece7d933488f076716723e # v1
with:
node-version: ${{ matrix.node }}
- name: Install Python
uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5
with:
python-version: ${{ matrix.python }}
- name: Install Poetry
run: python -m pip install --upgrade pip poetry
- name: Sync versions
run: python scripts/sync_version.py
- name: Install Python Deps
run: python -m poetry install
- name: Install NodeJS Deps
run: npm install
- name: Prepare frontend directory
run: python scripts/prepare_frontend_dir.py
- name: Download frontend artifact
uses: actions/download-artifact@v4
with:
name: frontend-build
path: meshchatx/public
- name: Install patchelf
if: matrix.name == 'linux'
run: sudo apt-get update && sudo apt-get install -y patchelf
- name: Install Poetry
run: python -m pip install --upgrade pip poetry
- name: Sync versions
run: python scripts/sync_version.py
- name: Install Python Deps
run: python -m poetry install
- name: Build Python wheel
if: matrix.name == 'linux'
run: |
python -m poetry build -f wheel
mkdir -p python-dist
mv dist/*.whl python-dist/
rm -rf dist
- name: Install NodeJS Deps
run: npm install
- name: Build Electron App
run: npm run dist
run: npm run dist-prebuilt
- name: Create Release
id: create_release
uses: ncipollo/release-action@b7eabc95ff50cbeeedec83973935c8f306dfcd0b # v1
with:
draft: true
@@ -171,7 +158,7 @@ jobs:
replacesArtifacts: true
omitDraftDuringUpdate: true
omitNameDuringUpdate: true
artifacts: "dist/*-linux.AppImage,dist/*-linux.deb,python-dist/*.whl"
artifacts: ${{ matrix.release_artifacts }}
build_docker:
runs-on: ubuntu-latest

View File

@@ -10,9 +10,8 @@ FROM node:${NODE_VERSION}-alpine@${NODE_ALPINE_SHA256} AS build-frontend
WORKDIR /src
# Copy required source files
COPY *.json .
COPY *.js .
COPY src/frontend ./src/frontend
COPY package*.json vite.config.js ./
COPY meshchatx ./meshchatx
# Install NodeJS deps, exluding electron
RUN npm install --omit=dev && \
@@ -34,12 +33,10 @@ RUN apk add --no-cache --virtual .build-deps \
apk del .build-deps
# Copy prebuilt frontend
COPY --from=build-frontend /src/public public
COPY --from=build-frontend /src/meshchatx/public meshchatx/public
# Copy other required source files
COPY *.py .
COPY src/__init__.py ./src/__init__.py
COPY src/backend ./src/backend
COPY *.json .
COPY meshchatx ./meshchatx
COPY pyproject.toml poetry.lock ./
CMD ["python", "meshchat.py", "--host=0.0.0.0", "--reticulum-config-dir=/config/.reticulum", "--storage-dir=/config/.meshchat", "--headless"]
CMD ["python", "-m", "meshchatx.meshchat", "--host=0.0.0.0", "--reticulum-config-dir=/config/.reticulum", "--storage-dir=/config/.meshchat", "--headless"]

View File

@@ -39,6 +39,7 @@ clean:
rm -rf build
rm -rf dist
rm -rf python-dist
rm -rf meshchatx/public
sync-version:
$(PYTHON) scripts/sync_version.py

View File

@@ -49,20 +49,19 @@ def get_file_path(filename):
datadir = os.path.dirname(sys.executable)
return os.path.join(datadir, filename)
# Running from source or an installed wheel: assets live inside the meshchatx package
# Assets live inside the meshchatx package when installed from a wheel
package_dir = os.path.dirname(__file__)
test_path = os.path.join(package_dir, filename)
if os.path.exists(test_path):
return test_path
package_path = os.path.join(package_dir, filename)
if os.path.exists(package_path):
return package_path
# Fall back to repo root when running directly from the source tree
# When running from the repository, fall back to the project root
repo_root = os.path.dirname(package_dir)
repo_path = os.path.join(repo_root, filename)
if os.path.exists(repo_path):
return repo_path
# Return the package path even if it does not exist so callers raise a clear error
return test_path
return package_path
class ReticulumMeshChat:

View File

@@ -11,7 +11,8 @@
"build": "npm run build-frontend && npm run build-backend",
"electron-postinstall": "electron-builder install-app-deps",
"electron": "npm run electron-postinstall && npm run build && electron .",
"dist": "npm run electron-postinstall && npm run build && electron-builder --publish=never"
"dist": "npm run electron-postinstall && npm run build && electron-builder --publish=never",
"dist-prebuilt": "npm run electron-postinstall && npm run build-backend && electron-builder --publish=never"
},
"license": "MIT",
"engines": {

View File

@@ -0,0 +1,11 @@
#!/usr/bin/env python3
coderabbitai[bot] commented 2025-12-01 06:13:58 +00:00 (Migrated from github.com)
Review

⚠️ Potential issue | 🟡 Minor

Make the file executable or remove the shebang.

The shebang is present but the file is not executable.

Based on static analysis hints, run:

chmod +x scripts/prepare_frontend_dir.py

Or remove the shebang if the script is only intended to be invoked via python scripts/prepare_frontend_dir.py.

🧰 Tools
🪛 Ruff (0.14.6)

1-1: Shebang is present but file is not executable

(EXE001)

🤖 Prompt for AI Agents
In scripts/prepare_frontend_dir.py around line 1, the file contains a shebang
but is not executable; either make the file executable by running chmod +x
scripts/prepare_frontend_dir.py so it can be run directly, or remove the shebang
line if you intend to always invoke it via the python interpreter (e.g., python
scripts/prepare_frontend_dir.py); apply one of these two changes and update any
relevant documentation/CI that assumes direct execution.

Addressed in commits ac839df to c98131f

_⚠️ Potential issue_ | _🟡 Minor_ **Make the file executable or remove the shebang.** The shebang is present but the file is not executable. Based on static analysis hints, run: ```shell chmod +x scripts/prepare_frontend_dir.py ``` Or remove the shebang if the script is only intended to be invoked via `python scripts/prepare_frontend_dir.py`. <details> <summary>🧰 Tools</summary> <details> <summary>🪛 Ruff (0.14.6)</summary> 1-1: Shebang is present but file is not executable (EXE001) </details> </details> <details> <summary>🤖 Prompt for AI Agents</summary> ``` In scripts/prepare_frontend_dir.py around line 1, the file contains a shebang but is not executable; either make the file executable by running chmod +x scripts/prepare_frontend_dir.py so it can be run directly, or remove the shebang line if you intend to always invoke it via the python interpreter (e.g., python scripts/prepare_frontend_dir.py); apply one of these two changes and update any relevant documentation/CI that assumes direct execution. ``` </details> <!-- This is an auto-generated comment by CodeRabbit --> ✅ Addressed in commits ac839df to c98131f
coderabbitai[bot] commented 2025-12-01 06:13:58 +00:00 (Migrated from github.com)
Review

🛠️ Refactor suggestion | 🟠 Major

Add safety checks before removing the directory.

The script unconditionally removes the target directory without validation. If the TARGET path is misconfigured or if there's a symlink, this could lead to unintended data loss.

Consider adding basic safety checks:

+# Sanity check: ensure we're in the project root
+if not Path("pyproject.toml").exists():
+    raise RuntimeError("Must run from project root")
+
 if TARGET.exists():
+    # Additional safety: verify it's a directory, not a symlink
+    if TARGET.is_symlink():
+        raise RuntimeError(f"{TARGET} is a symlink, refusing to remove")
     shutil.rmtree(TARGET)

Addressed in commits ac839df to c98131f

_🛠️ Refactor suggestion_ | _🟠 Major_ **Add safety checks before removing the directory.** The script unconditionally removes the target directory without validation. If the `TARGET` path is misconfigured or if there's a symlink, this could lead to unintended data loss. Consider adding basic safety checks: ```diff +# Sanity check: ensure we're in the project root +if not Path("pyproject.toml").exists(): + raise RuntimeError("Must run from project root") + if TARGET.exists(): + # Additional safety: verify it's a directory, not a symlink + if TARGET.is_symlink(): + raise RuntimeError(f"{TARGET} is a symlink, refusing to remove") shutil.rmtree(TARGET) ``` <!-- This is an auto-generated comment by CodeRabbit --> ✅ Addressed in commits ac839df to c98131f
from pathlib import Path
import shutil
TARGET = Path("meshchatx") / "public"
if TARGET.exists():
shutil.rmtree(TARGET)
TARGET.mkdir(parents=True, exist_ok=True)

View File

1
@@ -28,8 +28,7 @@ print(f'meshchat module location: {meshchat.__file__}')
# Check if public directory exists
meshchat_dir = os.path.dirname(meshchat.__file__)
package_dir = os.path.dirname(os.path.dirname(meshchat_dir))
public_path = os.path.join(package_dir, 'public')
public_path = os.path.join(meshchat_dir, 'public')
print(f'Checking for public at: {public_path}')
print(f'Exists: {os.path.exists(public_path)}')
@@ -45,7 +44,7 @@ if os.path.exists(test_path):
else:
print('WARNING: public directory not found!')
print('Checking parent directories...')
current = package_dir
current = meshchat_dir
for i in range(3):
test = os.path.join(current, 'public')
print(f' {test}: {os.path.exists(test)}')

View File

@@ -14,8 +14,8 @@ export default {
build: {
// we want to compile vite app to /public which is bundled and served by the python executable
outDir: path.join(__dirname, "public"),
// we want to compile vite app to meshchatx/public which is bundled and served by the python executable
outDir: path.join(__dirname, "meshchatx", "public"),
emptyOutDir: true,
rollupOptions: {