Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 5 additions & 2 deletions .github/workflows/publish-pypi.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,8 @@ name: Publish to PyPI

on:
push:
tags: ['*']
tags:
- "v*.*.*"

jobs:
test:
Expand All @@ -17,6 +18,8 @@ jobs:

steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0

- name: Set up Python
uses: actions/setup-python@v4
Expand All @@ -40,7 +43,7 @@ jobs:
retention-days: 5

- name: Create GitHub Release
uses: softprops/action-gh-release@v1
uses: softprops/action-gh-release@v2
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
Expand Down
42 changes: 42 additions & 0 deletions .github/workflows/publish-test-pypi.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
name: Publish to TestPyPI

on:
push:
branches:
- dev

jobs:
test:
uses: ./.github/workflows/test.yml
secrets:
CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}

publish-test-pypi:
needs: test
runs-on: ubuntu-latest
permissions:
id-token: write

steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0

- name: Set up Python
uses: actions/setup-python@v4
with:
python-version: '3.11'

- name: Install dependencies
run: |
python -m pip install --upgrade pip
pip install build

- name: Build package
run: |
python -m build

- name: Publish to TestPyPI
uses: pypa/gh-action-pypi-publish@release/v1
with:
repository-url: https://test.pypi.org/legacy/
6 changes: 5 additions & 1 deletion .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@ jobs:

steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0

- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v4
Expand Down Expand Up @@ -54,6 +56,8 @@ jobs:

- name: Upload test results to Codecov
if: ${{ !cancelled() }}
uses: codecov/test-results-action@v1
uses: codecov/codecov-action@v5
with:
token: ${{ secrets.CODECOV_TOKEN }}
report_type: test_results
flags: unittests
49 changes: 47 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -94,14 +94,59 @@ where = ["python"]

### Multiple Frontend Projects with Output Directories

`output_dir` specifies the **relative subdirectory path inside the package** for frontend build artifacts. It does not create a separate package namespace. If not specified, it defaults to `frontend`.

For example, with a package named `myapp`:

| `output_dir` value | Artifact install path |
|-------------------|----------------------|
| Not set (default `frontend`) | `myapp/frontend/index.html` |
| `static/admin` | `myapp/static/admin/index.html` |
| `assets/client` | `myapp/assets/client/index.html` |

```toml
[tool.setuptools-nodejs]
frontend-projects = [
{target = "admin-panel", source_dir = "admin", artifacts_dir = "dist", output_dir = "my_package/admin"},
{target = "client-app", source_dir = "client", artifacts_dir = "build", output_dir = "my_package/client"}
{target = "admin-panel", source_dir = "admin", artifacts_dir = "dist", output_dir = "static/admin"},
{target = "client-app", source_dir = "client", artifacts_dir = "build", output_dir = "static/client"}
]
```

### Accessing Frontend Artifacts in Python Code

Use `importlib.resources` to access frontend build artifacts inside the package:

```python
# Example: Serve frontend static files in Flask/FastAPI
try:
import importlib.resources as resources
except ImportError:
# Python < 3.9 fallback
import importlib_resources as resources

def get_frontend_dir(package_name: str = "myapp", sub_dir: str = "frontend") -> str:
"""Get frontend artifacts directory path, works with both
pip install -e . and pip install ."""
with resources.path(package_name, sub_dir) as path:
return str(path)

# Usage example (Flask)
from flask import Flask, send_from_directory
app = Flask(__name__)

FRONTEND_DIR = get_frontend_dir("myapp", "frontend")

@app.route("/")
def serve_index():
return send_from_directory(FRONTEND_DIR, "index.html")

@app.route("/assets/<path:filename>")
def serve_assets(filename):
return send_from_directory(FRONTEND_DIR, f"assets/{filename}")
```

> **Note**: With `pip install -e .` (editable install), Python references the source directory directly instead of site-packages. Frontend artifacts are automatically copied to `<package_dir>/<package_name>/<output_dir>/`, consistent with regular install behavior.

### Advanced Configuration

```toml
Expand Down
48 changes: 46 additions & 2 deletions README_CN.md
Original file line number Diff line number Diff line change
Expand Up @@ -94,14 +94,58 @@ where = ["python"]

### 多个前端项目及输出目录

`output_dir` 指定前端构建产物在**包内的相对子目录路径**,不占用包名空间。不指定时默认值为 `frontend`。

例如,对于包名为 `myapp` 的项目:

| output_dir 值 | 产物安装路径 |
|---------------|-------------|
| 不指定(默认 `frontend`) | `myapp/frontend/index.html` |
| `static/admin` | `myapp/static/admin/index.html` |
| `assets/client` | `myapp/assets/client/index.html` |

```toml
[tool.setuptools-nodejs]
frontend-projects = [
{target = "admin-panel", source_dir = "admin", artifacts_dir = "dist", output_dir = "my_package/admin"},
{target = "client-app", source_dir = "client", artifacts_dir = "build", output_dir = "my_package/client"}
{target = "admin-panel", source_dir = "admin", artifacts_dir = "dist", output_dir = "static/admin"},
{target = "client-app", source_dir = "client", artifacts_dir = "build", output_dir = "static/client"}
]
```

### 在 Python 代码中访问前端产物

通过 `importlib.resources` 访问包内的前端构建产物:

```python
# 示例:将前端产物作为静态文件目录提供给 Flask/FastAPI
try:
import importlib.resources as resources
except ImportError:
# Python < 3.9 兼容
import importlib_resources as resources

def get_frontend_dir(package_name: str = "myapp", sub_dir: str = "frontend") -> str:
"""获取前端产物目录路径,兼容 pip install -e . 和 pip install ."""
with resources.path(package_name, sub_dir) as path:
return str(path)

# 使用示例(Flask)
from flask import Flask, send_from_directory
app = Flask(__name__)

FRONTEND_DIR = get_frontend_dir("myapp", "frontend")

@app.route("/")
def serve_index():
return send_from_directory(FRONTEND_DIR, "index.html")

@app.route("/assets/<path:filename>")
def serve_assets(filename):
return send_from_directory(FRONTEND_DIR, f"assets/{filename}")
```

> **注意**:`pip install -e .`(可编辑安装)下,Python 直接引用源码目录而非 site-packages。前端产物会被自动复制到 `<package_dir>/<package_name>/<output_dir>/` 下,与普通安装行为一致。

### 高级配置

```toml
Expand Down
5 changes: 5 additions & 0 deletions examples/vue-helloworld-setuppy/MANIFEST.in
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
# Include README file
include README.md

# Include package data
recursive-include vue_helloworld_setuppy *.py
98 changes: 98 additions & 0 deletions examples/vue-helloworld-setuppy/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
# Vue HelloWorld with Traditional setup.py

This is a test project demonstrating the integration of `setuptools-nodejs` with traditional `setup.py` configuration.

## Purpose

This example shows how to use `setuptools-nodejs` with the traditional `setup.py` approach instead of the modern `pyproject.toml` configuration. It's useful for:

1. Testing compatibility with legacy projects
2. Demonstrating alternative configuration methods
3. Providing a reference for projects that cannot migrate to `pyproject.toml`

## Project Structure

```
vue-helloworld-setuppy/
├── browser/ # Vue.js frontend project
│ ├── package.json
│ ├── vite.config.ts
│ ├── src/
│ └── dist/ # Built frontend artifacts
├── vue_helloworld_setuppy/ # Python package
│ └── __init__.py
├── setup.py # Traditional setup configuration
├── MANIFEST.in # Manifest for including frontend artifacts
└── README.md # This file
```

## Configuration

The key configuration is in `setup.py`:

```python
from setuptools_nodejs import NodeJSExtension

setup(
# ... other setup arguments ...
nodejs_extensions=[
NodeJSExtension(
target="vue_helloworld_setuppy",
source_dir="browser",
artifacts_dir="dist",
),
],
)
```

## Building and Installation

### 1. Build the frontend

```bash
cd browser
npm install
npm run build
```

### 2. Create source distribution (sdist)

```bash
python setup.py sdist
```

This will create a `.tar.gz` file in the `dist/` directory containing both Python code and frontend build artifacts.

### 3. Install the package

```bash
pip install .
```

During installation, `setuptools-nodejs` will:
1. Detect the `nodejs_extensions` configuration
2. Build the frontend if not already built
3. Include the built artifacts in the installed package

## Testing

This example is used for testing `setuptools-nodejs` functionality with traditional `setup.py` configuration. It helps ensure:

- Proper handling of `nodejs_extensions` in `setup()`
- Correct inclusion of frontend artifacts in sdist
- Successful build and installation workflows

## Comparison with pyproject.toml

| Aspect | setup.py | pyproject.toml |
|--------|----------|----------------|
| Configuration | Python code in `setup()` | TOML in `[tool.setuptools-nodejs]` |
| Flexibility | Full Python programmability | Declarative configuration |
| Modern standards | Legacy approach | PEP 517/518 compliant |
| Integration | Direct import of `NodeJSExtension` | Tool-specific section |

## Notes

- The frontend must be built before creating sdist, or `setuptools-nodejs` will build it during packaging
- `MANIFEST.in` ensures frontend artifacts are included in source distribution
- This example complements the existing `vue-helloworld` example which uses `pyproject.toml`
24 changes: 24 additions & 0 deletions examples/vue-helloworld-setuppy/browser/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
lerna-debug.log*

node_modules
dist
dist-ssr
*.local

# Editor directories and files
.vscode/*
!.vscode/extensions.json
.idea
.DS_Store
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw?
5 changes: 5 additions & 0 deletions examples/vue-helloworld-setuppy/browser/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
# Vue 3 + TypeScript + Vite

This template should help get you started developing with Vue 3 and TypeScript in Vite. The template uses Vue 3 `<script setup>` SFCs, check out the [script setup docs](https://v3.vuejs.org/api/sfc-script-setup.html#sfc-script-setup) to learn more.

Learn more about the recommended Project Setup and IDE Support in the [Vue Docs TypeScript Guide](https://vuejs.org/guide/typescript/overview.html#project-setup).
13 changes: 13 additions & 0 deletions examples/vue-helloworld-setuppy/browser/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>test-nodejs</title>
</head>
<body>
<div id="app"></div>
<script type="module" src="/src/main.ts"></script>
</body>
</html>
Loading
Loading