# Skybolt Python

Python adapter for [Skybolt](https://github.com/JensRoland/skybolt) - High-performance asset caching for multi-page applications.

## Installation

```bash
pip install skybolt
```

Or with uv:

```bash
uv add skybolt
```

## Prerequisites

1. Install and configure the Vite plugin: `npm install @skybolt/vite-plugin`
2. Build your project: `npm run build`
3. Ensure `render-map.json` is generated in your build output

## Usage

```python
from skybolt import Skybolt

# Initialize with render map path and cookies
sb = Skybolt("dist/.skybolt/render-map.json", cookies=request.cookies)
```

```html
<!DOCTYPE html>
<html>
<head>
    {{ sb.css("src/css/critical.css")|safe }}
    {{ sb.launch_script()|safe }}
    {{ sb.css("src/css/main.css")|safe }}
</head>
<body>
    <h1>Hello Skybolt!</h1>
    {{ sb.script("src/js/app.js")|safe }}
</body>
</html>
```

## API

### `Skybolt(render_map_path, cookies=None, cdn_url=None)`

Create a new Skybolt instance.

- `render_map_path` - Path to `render-map.json` generated by Vite plugin
- `cookies` - Cookie dictionary (defaults to `None`)
- `cdn_url` - Optional CDN URL prefix (e.g., `'https://cdn.example.com'`)

```python
# Basic usage
sb = Skybolt("dist/.skybolt/render-map.json", cookies=request.cookies)

# With CDN
sb = Skybolt(
    "dist/.skybolt/render-map.json",
    cookies=request.cookies,
    cdn_url="https://cdn.example.com"
)
```

### `css(entry: str) -> str`

Render CSS asset.

- First visit: Inlines CSS with caching attributes
- Repeat visit: Outputs `<link>` tag (Service Worker serves from cache)

```python
sb.css("src/css/main.css")
```

### `script(entry: str, module: bool = True) -> str`

Render JavaScript asset.

- First visit: Inlines JS with caching attributes
- Repeat visit: Outputs `<script>` tag (Service Worker serves from cache)

```python
# ES module (default)
sb.script("src/js/app.js")

# Classic script
sb.script("src/js/legacy.js", module=False)
```

### `launch_script() -> str`

Render the Skybolt client launcher. Call once in `<head>` before other assets.

```html
<head>
    {{ sb.launch_script()|safe }}
</head>
```

### `get_asset_url(entry: str) -> str | None`

Get the URL for an asset (for manual use cases).

```python
url = sb.get_asset_url("src/css/main.css")
# "/assets/main-Pw3rT8vL.css"
```

### `get_asset_hash(entry: str) -> str | None`

Get the content hash for an asset.

```python
hash = sb.get_asset_hash("src/css/main.css")
# "Pw3rT8vL"
```

### `preload(entry, as_type, mime_type=None, crossorigin=None, fetchpriority=None) -> str`

Render preload link for critical resources like fonts and images.

```python
# Preload hero image with high priority
sb.preload("images/hero.jpg", as_type="image", fetchpriority="high")

# Preload font
sb.preload("fonts/inter.woff2", as_type="font", mime_type="font/woff2", crossorigin="anonymous")

# Preload a Vite-built asset (resolved from render-map)
sb.preload("src/css/main.css", as_type="style")
```

Parameters:

- `entry` - Source file path or direct URL
- `as_type` - Resource type (`'image'`, `'font'`, `'style'`, `'script'`, `'fetch'`)
- `mime_type` - MIME type (e.g., `'font/woff2'`, `'image/webp'`)
- `crossorigin` - Crossorigin attribute (`'anonymous'`, `'use-credentials'`)
- `fetchpriority` - Fetch priority (`'high'`, `'low'`, `'auto'`)

## Service Worker Setup

The Service Worker must be served from your domain root. Configure your web server:

**Nginx:**

```nginx
location = /skybolt-sw.js {
    alias /path/to/dist/skybolt-sw.js;
}
```

**Apache (.htaccess):**

```apache
RewriteRule ^skybolt-sw\.js$ dist/skybolt-sw.js [L]
```

## Framework Integration

### Django

```python
# views.py
from skybolt import Skybolt

def index(request):
    sb = Skybolt("static/.skybolt/render-map.json", cookies=request.COOKIES)
    return render(request, "index.html", {"sb": sb})
```

```html
<!-- templates/index.html -->
<!DOCTYPE html>
<html>
<head>
    {{ sb.css("src/css/critical.css")|safe }}
    {{ sb.launch_script()|safe }}
    {{ sb.css("src/css/app.css")|safe }}
</head>
<body>
    <!-- content -->
    {{ sb.script("src/js/app.js")|safe }}
</body>
</html>
```

### Flask

```python
from flask import Flask, render_template, request
from skybolt import Skybolt

app = Flask(__name__)

@app.route("/")
def index():
    sb = Skybolt("static/.skybolt/render-map.json", cookies=request.cookies)
    return render_template("index.html", sb=sb)
```

### FastAPI

```python
from fastapi import FastAPI, Request
from fastapi.responses import HTMLResponse
from fastapi.templating import Jinja2Templates
from skybolt import Skybolt

app = FastAPI()
templates = Jinja2Templates(directory="templates")

@app.get("/", response_class=HTMLResponse)
async def index(request: Request):
    sb = Skybolt("static/.skybolt/render-map.json", cookies=request.cookies)
    return templates.TemplateResponse("index.html", {"request": request, "sb": sb})
```

## Requirements

- Python 3.10+
- Vite with `@skybolt/vite-plugin`

## Publishing

This package is maintained in the [Skybolt monorepo](https://github.com/JensRoland/skybolt) and automatically synced to [skybolt-python](https://github.com/JensRoland/skybolt-python).

To publish a new version, run one command from the `packages/python` directory:

```sh
./scripts/release.sh patch   # 3.1.0 → 3.1.1
./scripts/release.sh minor   # 3.1.0 → 3.2.0
./scripts/release.sh major   # 3.1.0 → 4.0.0
```

This automatically:

1. Bumps the version in `VERSION`, `pyproject.toml`, and source files
2. Commits and pushes to the monorepo
3. Sync workflow pushes changes to the split repo
4. `tag-version.yml` in the split repo creates the `v*` tag
5. `publish.yml` builds and publishes to PyPI using trusted publishing (OIDC)

## License

MIT
