bridge 0.1.3

Command Line Interface for BridgeRust framework
use anyhow::Result;
use std::fs;
use std::path::Path;

pub fn generate_project(
    project_dir: &Path,
    name: &str,
    description: &str,
    author: &str,
) -> Result<()> {
    fs::create_dir_all(project_dir.join("src"))?;
    fs::create_dir_all(project_dir.join("python"))?;
    fs::create_dir_all(project_dir.join("nodejs"))?;
    fs::create_dir_all(project_dir.join("tests"))?;

    let cargo_toml = format!(
        r#"[package]
name = "{}"
version = "0.1.0"
edition = "2021"
description = "{}"
authors = ["{}"]
license = "MIT OR Apache-2.0"

[lib]
crate-type = ["cdylib", "rlib"]

[dependencies]
bridgerust = {{ path = "../../crates/bridgerust", version = "0.1.2" }}

[features]
default = []
python = ["dep:pyo3", "bridgerust/python"]
nodejs = ["dep:napi", "dep:napi-derive", "bridgerust/nodejs"]

[dependencies.pyo3]
version = "0.27"
optional = true
features = ["extension-module"]

[dependencies.napi]
version = "3"
optional = true
features = ["serde-json"]


[dependencies.napi-derive]
version = "3"
optional = true
"#,
        name, description, author
    );
    fs::write(project_dir.join("Cargo.toml"), cargo_toml)?;

    let module_name = name.replace("-", "_");
    let lib_rs = format!(
        r#"use bridgerust::export;

#[export]
pub fn greet(name: String) -> String {{
    format!("Hello, {{}}!", name)
}}

#[export]
pub fn add(a: i32, b: i32) -> i32 {{
    a + b
}}

#[cfg(feature = "python")]
#[bridgerust::pyo3::pymodule]
fn {}(m: &bridgerust::pyo3::Bound<'_, bridgerust::pyo3::types::PyModule>) -> bridgerust::pyo3::PyResult<()> {{
    m.add_function(bridgerust::pyo3::wrap_pyfunction!(greet, m)?)?;
    m.add_function(bridgerust::pyo3::wrap_pyfunction!(add, m)?)?;
    Ok(())
}}
"#,
        module_name
    );
    fs::write(project_dir.join("src/lib.rs"), lib_rs)?;

    let config_toml = format!(
        r#"[package]
name = "{}"
version = "0.1.0"
description = "{}"
authors = ["{}"]

[python]
module_name = "{}"

[nodejs]
package_name = "@bridgerust/{}"
"#,
        name, description, author, name, name
    );
    fs::write(project_dir.join("bridgerust.toml"), config_toml)?;

    let pyproject_toml = format!(
        r#"[build-system]
requires = ["maturin>=1.0,<2.0"]
build-backend = "maturin"

[project]
name = "{}"
version = "0.1.0"
description = "{}"
requires-python = ">=3.8"
classifiers = [
    "Programming Language :: Rust",
    "Programming Language :: Python :: Implementation :: CPython",
    "Programming Language :: Python :: Implementation :: PyPy",
]
"#,
        name, description
    );
    fs::write(project_dir.join("python/pyproject.toml"), pyproject_toml)?;

    let package_json = format!(
        r#"{{
  "name": "@bridgerust/{}",
  "version": "0.1.0",
  "description": "{}",
  "main": "index.js",
  "types": "index.d.ts",
  "files": [
    "index.js",
    "index.d.ts",
    "*.node"
  ],
  "napi": {{
    "name": "{}",
    "triples": {{
      "defaults": true,
      "additional": []
    }}
  }}
}}
"#,
        name, description, name
    );
    fs::write(project_dir.join("nodejs/package.json"), package_json)?;

    let readme = format!(
        r#"# {}

{}

## Building

```bash
# Build for Python
bridge build --python

# Build for Node.js
bridge build --nodejs

# Build for both
bridge build --all
```

## Testing

```bash
bridge test --all
```

## Publishing

```bash
bridge publish --all
```
"#,
        name, description
    );
    fs::write(project_dir.join("README.md"), readme)?;

    Ok(())
}

pub fn generate_workflows(output_dir: &Path) -> Result<()> {
    let ci_workflow = r#"name: CI

on:
  push:
    branches: [main, develop]
  pull_request:
    branches: [main, develop]

env:
  CARGO_TERM_COLOR: always

jobs:
  rust:
    name: Rust Tests
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      
      - name: Install Rust
        uses: dtolnay/rust-toolchain@stable
        with:
          toolchain: stable
      
      - name: Rust Cache
        uses: Swatinem/rust-cache@v2
        with:
          cache-on-failure: true
      
      - name: Check formatting
        run: cargo fmt --all -- --check
      
      - name: Clippy
        run: cargo clippy --workspace --all-features -- -D warnings
      
      - name: Run tests
        run: cargo test --workspace --all-features

  python:
    name: Python Tests
    runs-on: ubuntu-latest
    strategy:
      matrix:
        python-version: ["3.10", "3.11", "3.12"]
    steps:
      - uses: actions/checkout@v4
      
      - name: Set up Python ${{ matrix.python-version }}
        uses: actions/setup-python@v5
        with:
          python-version: ${{ matrix.python-version }}
      
      - name: Install Rust
        uses: dtolnay/rust-toolchain@stable
        with:
          toolchain: stable
      
      - name: Install maturin
        run: pip install maturin
      
      - name: Rust Cache
        uses: Swatinem/rust-cache@v2
        with:
          cache-on-failure: true
      
      - name: Build Python bindings
        run: bridge build --target python
      
      - name: Test Python bindings
        run: bridge test --target python

  nodejs:
    name: Node.js Tests
    runs-on: ubuntu-latest
    strategy:
      matrix:
        node-version: ["20", "22"]
    steps:
      - uses: actions/checkout@v4
      
      - name: Set up Node.js ${{ matrix.node-version }}
        uses: actions/setup-node@v4
        with:
          node-version: ${{ matrix.node-version }}
      
      - name: Install Rust
        uses: dtolnay/rust-toolchain@stable
        with:
          toolchain: stable
      
      - name: Rust Cache
        uses: Swatinem/rust-cache@v2
        with:
          cache-on-failure: true
      
      - name: Build Node.js bindings
        run: bridge build --target nodejs
      
      - name: Test Node.js bindings
        run: bridge test --target nodejs
"#;

    let release_workflow = r#"name: Release

on:
  push:
    tags:
      - "v*.*.*"

env:
  CARGO_TERM_COLOR: always

jobs:
  build:
    name: Build All Targets
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      
      - name: Install Rust
        uses: dtolnay/rust-toolchain@stable
        with:
          toolchain: stable
      
      - name: Set up Python
        uses: actions/setup-python@v5
        with:
          python-version: "3.12"
      
      - name: Set up Node.js
        uses: actions/setup-node@v4
        with:
          node-version: "22"
      
      - name: Install maturin
        run: pip install maturin
      
      - name: Rust Cache
        uses: Swatinem/rust-cache@v2
        with:
          cache-on-failure: true
      
      - name: Build and test all targets
        run: bridge build --all --release && bridge test --all

  publish:
    name: Publish Packages
    needs: build
    runs-on: ubuntu-latest
    environment:
      name: publish
    steps:
      - uses: actions/checkout@v4
      
      - name: Install Rust
        uses: dtolnay/rust-toolchain@stable
        with:
          toolchain: stable
      
      - name: Set up Python
        uses: actions/setup-python@v5
        with:
          python-version: "3.12"
      
      - name: Set up Node.js
        uses: actions/setup-node@v4
        with:
          node-version: "22"
          registry-url: "https://registry.npmjs.org"
      
      - name: Install maturin
        run: pip install maturin
      
      - name: Rust Cache
        uses: Swatinem/rust-cache@v2
        with:
          cache-on-failure: true
      
      - name: Publish to PyPI
        if: contains(github.event.head_commit.message, '[publish-python]') || contains(github.ref, 'refs/tags/')
        run: bridge publish --target python
        env:
          TWINE_USERNAME: __token__
          TWINE_PASSWORD: ${{ secrets.PYPI_API_TOKEN }}
      
      - name: Publish to npm
        if: contains(github.event.head_commit.message, '[publish-nodejs]') || contains(github.ref, 'refs/tags/')
        run: bridge publish --target nodejs
        env:
          NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
"#;

    fs::write(output_dir.join("ci.yml"), ci_workflow)?;
    fs::write(output_dir.join("release.yml"), release_workflow)?;

    Ok(())
}