diff --git a/.github/workflows/javascript-tests.yml b/.github/workflows/javascript-tests.yml index 30c5a231..ae86886f 100644 --- a/.github/workflows/javascript-tests.yml +++ b/.github/workflows/javascript-tests.yml @@ -13,7 +13,7 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - node-version: [20] + node-version: [24] steps: - name: Check out repository diff --git a/.github/workflows/pypi-publish.yml b/.github/workflows/pypi-publish.yml index 074c6abb..2576c0b2 100644 --- a/.github/workflows/pypi-publish.yml +++ b/.github/workflows/pypi-publish.yml @@ -9,22 +9,32 @@ jobs: runs-on: ubuntu-latest steps: - - name: Checkout code - uses: actions/checkout@v6 + - name: Checkout code + uses: actions/checkout@v6 - - name: Set up Python - uses: actions/setup-python@v6 - with: - python-version: 3.12 + - name: Set up Python + uses: actions/setup-python@v6 + with: + python-version: 3.12 - - name: Install dependencies - run: pip install -r requirements/pip.txt + - name: Set up Node.js + uses: actions/setup-node@v6 + with: + node-version: 24 - - name: Build package - run: python setup.py sdist bdist_wheel + - name: Build JavaScript bundles + run: | + npm ci + npm run build - - name: Publish to PyPi - uses: pypa/gh-action-pypi-publish@master - with: - user: __token__ - password: ${{ secrets.PYPI_UPLOAD_TOKEN }} + - name: Install dependencies + run: pip install -r requirements/pip.txt + + - name: Build package + run: python setup.py sdist bdist_wheel + + - name: Publish to PyPi + uses: pypa/gh-action-pypi-publish@master + with: + user: __token__ + password: ${{ secrets.PYPI_UPLOAD_TOKEN }} diff --git a/.gitignore b/.gitignore index 27632111..110b7d05 100644 --- a/.gitignore +++ b/.gitignore @@ -1,39 +1,49 @@ +# Python bytecode / caches *.py[cod] -__pycache__ -.pytest_cache +__pycache__/ +.pytest_cache/ -### Editor and IDE artifacts +# Editor and IDE artifacts .idea/ +.vscode/ +*.iml # C extensions *.so -# Packages +# Packages / build artifacts *.egg *.egg-info -/dist -/build -/eggs -/parts -/bin -/var -/sdist -/develop-eggs +/dist/ +/build/ +/eggs/ +/parts/ +/bin/ +/var/ +/sdist/ +/develop-eggs/ /.installed.cfg -/lib -/lib64 +/lib/ +/lib64/ +wheels/ +pip-wheel-metadata/ # Installer logs pip-log.txt +pip-delete-this-directory.txt # Unit test / coverage reports .cache/ .pytest_cache/ .coverage .coverage.* -.tox +.tox/ +nox/ coverage.xml htmlcov/ +coverage/ +test-results/ +junit.xml # Virtual environments /venv/ @@ -41,15 +51,26 @@ htmlcov/ /.venv/ /.venv-*/ +# Python type checkers / linters +.mypy_cache/ +.ruff_cache/ +.pyre/ +.pytype/ + # The Silver Searcher .agignore # OS X artifacts *.DS_Store +.DS_Store? +._* +.Spotlight-V100 +.Trashes # Logging log/ logs/ +*.log chromedriver.log ghostdriver.log @@ -58,7 +79,7 @@ output/*.html output/*/index.html # Sphinx -docs/_build +docs/_build/ docs/modules.rst docs/xblocks_contrib.rst docs/xblocks_contrib.*.rst @@ -71,3 +92,31 @@ requirements/private.txt *.mo *.pot *.po + +# Node.js / npm / yarn / pnpm +node_modules/ +**/node_modules/ + +npm-debug.log* +yarn-debug.log* +yarn-error.log* +pnpm-debug.log* + +.npm/ +.yarn/ +.pnpm-store/ + +# Webpack / bundling outputs +public/ +**/public/ + +webpack-stats.json +webpack-stats.* +stats.json + +*.hot-update.* +*.map + +# Karma +.karma/ +**/.karma/ diff --git a/MANIFEST.in b/MANIFEST.in index bcd2b3eb..de545ee6 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -3,4 +3,16 @@ include LICENSE.txt include README.rst include requirements/base.in include requirements/constraints.txt -recursive-include xblocks_contrib *.html *.png *.gif *.js *.css *.jpg *.jpeg *.svg +recursive-include xblocks_contrib *.html *.yaml *.png *.gif *.jpg *.jpeg *.svg +recursive-include xblocks_contrib/*/public *.js *.css +recursive-include xblocks_contrib/*/static *.js *.css + +# --- EXCLUSIONS --- +prune xblocks_contrib/*/assets +prune xblocks_contrib/*/node_modules +prune */tests +prune */spec +global-exclude webpack*.config.js +global-exclude karma*.js +global-exclude package*.json +global-exclude .gitignore diff --git a/package-lock.json b/package-lock.json index 7d1a86b6..8da32e8d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -5,7 +5,7 @@ "packages": { "": { "workspaces": [ - "xblocks_contrib/*/static" + "xblocks_contrib/*/*" ] } } diff --git a/package.json b/package.json index 6dc17696..109d046b 100644 --- a/package.json +++ b/package.json @@ -1,9 +1,11 @@ { "private": true, "workspaces": [ - "xblocks_contrib/*/static" + "xblocks_contrib/*/*" ], "scripts": { + "build": "npm run build --workspaces", + "build:dev": "npm run build:dev --workspaces", "test": "npm run test --workspaces", "test:ci": "npm run test:ci --workspaces" } diff --git a/setup.py b/setup.py index dfae3d08..50486d02 100644 --- a/setup.py +++ b/setup.py @@ -5,8 +5,13 @@ import os import re import sys +import shutil +import subprocess from setuptools import find_packages, setup +from setuptools.command.build_py import build_py +from setuptools.command.develop import develop +from setuptools.command.sdist import sdist def get_version(*file_paths): @@ -151,6 +156,51 @@ def package_data(pkg, sub_roots): return {pkg: data} +JS_BUILD_DONE = False + + +def build_js(dist=None): + """Run npm install & build once. Updates package_data if run.""" + global JS_BUILD_DONE + if JS_BUILD_DONE: + return + JS_BUILD_DONE = True + + # Skip if no package.json (e.g. installing from PyPI) or no npm + if not os.path.exists("package.json") or not shutil.which("npm"): + if os.path.exists("package.json"): + print("Warning: npm not found, skipping JS build.") + return + + try: + print("Building JS assets...") + subprocess.check_call(["npm", "install"]) + subprocess.check_call(["npm", "run", "build"]) + # Refresh package data to include new assets + if dist: + dist.package_data = package_data("xblocks_contrib", ["static", "public", "templates"]) + except Exception as e: + print(f"Warning: JS build failed: {e}. Continuing installation...") + + +class JSBuildPy(build_py): + def run(self): + build_js(self.distribution) + super().run() + + +class JSDevelop(develop): + def run(self): + build_js(self.distribution) + super().run() + + +class JSSdist(sdist): + def run(self): + build_js(self.distribution) + super().run() + + VERSION = get_version("xblocks_contrib", "__init__.py") if sys.argv[-1] == "tag": @@ -207,4 +257,9 @@ def package_data(pkg, sub_roots): ] }, package_data=package_data("xblocks_contrib", ["static", "public", "templates"]), + cmdclass={ + "build_py": JSBuildPy, + "develop": JSDevelop, + "sdist": JSSdist, + }, )