tovuk 0.1.59

Deploy Rust backends, static frontends, and fullstack apps to Tovuk.
use serde_json::json;

pub(crate) fn rust_template_cargo_toml(name: &str) -> String {
    format!(
        "[package]\nname = \"{name}\"\nversion = \"0.1.0\"\nedition = \"2024\"\npublish = false\n\n[lints.rust]\nunsafe_code = \"forbid\"\nwarnings = \"deny\"\n\n[lints.clippy]\nall = {{ level = \"deny\", priority = -1 }}\npedantic = {{ level = \"deny\", priority = -1 }}\ndbg_macro = \"deny\"\ntodo = \"deny\"\nunimplemented = \"deny\"\npanic = \"deny\"\nunwrap_used = \"deny\"\nexpect_used = \"deny\"\nlarge_futures = \"deny\"\nlarge_include_file = \"deny\"\nlarge_stack_frames = \"deny\"\nmem_forget = \"deny\"\nrc_buffer = \"deny\"\nrc_mutex = \"deny\"\nredundant_clone = \"deny\"\nclone_on_ref_ptr = \"deny\"\n"
    )
}

pub(crate) fn rust_template_cargo_lock(name: &str) -> String {
    format!(
        "# This file is automatically @generated by Cargo.\nversion = 4\n\n[[package]]\nname = \"{name}\"\nversion = \"0.1.0\"\n"
    )
}

pub(crate) fn frontend_package_json(name: &str) -> String {
    serde_json::to_string_pretty(&json!({
        "name": name,
        "private": true,
        "type": "module",
        "scripts": {
            "typecheck": "oxlint src vite.config.ts --deny-warnings --type-aware --type-check --tsconfig tsconfig.json",
            "lint": "oxlint src vite.config.ts --deny-warnings && fallow dead-code --production --include-dupes --include-entry-exports --fail-on-issues && fallow dupes --production --mode semantic --threshold 1 --ignore-imports --fail-on-issues && fallow health --production --max-cyclomatic 10 --max-cognitive 15 --max-crap 20 --complexity",
            "build": "vite build",
            "preview": "vite preview --host 0.0.0.0"
        },
        "dependencies": {
            "@tanstack/react-router": "^1.170.8",
            "react": "^19.2.6",
            "react-dom": "^19.2.6"
        },
        "devDependencies": {
            "@types/node": "^25.9.1",
            "@types/react": "^19.2.15",
            "@types/react-dom": "^19.2.3",
            "@vitejs/plugin-react": "^6.0.2",
            "fallow": "^2.84.0",
            "oxlint": "^1.67.0",
            "oxlint-tsgolint": "^0.23.0",
            "vite": "^8.0.14"
        }
    }))
    .map_or_else(|_error| "{}\n".to_owned(), |source| format!("{source}\n"))
}

pub(crate) fn frontend_ts_config() -> String {
    serde_json::to_string_pretty(&json!({
        "compilerOptions": {
            "allowUnreachableCode": false,
            "allowUnusedLabels": false,
            "alwaysStrict": true,
            "erasableSyntaxOnly": true,
            "exactOptionalPropertyTypes": true,
            "forceConsistentCasingInFileNames": true,
            "isolatedModules": true,
            "jsx": "react-jsx",
            "lib": ["ESNext", "DOM"],
            "module": "ESNext",
            "moduleDetection": "force",
            "moduleResolution": "Bundler",
            "noEmit": true,
            "noFallthroughCasesInSwitch": true,
            "noImplicitAny": true,
            "noImplicitOverride": true,
            "noImplicitReturns": true,
            "noImplicitThis": true,
            "noPropertyAccessFromIndexSignature": true,
            "noUncheckedIndexedAccess": true,
            "noUncheckedSideEffectImports": true,
            "noUnusedLocals": true,
            "noUnusedParameters": true,
            "skipLibCheck": false,
            "strict": true,
            "strictBindCallApply": true,
            "strictFunctionTypes": true,
            "strictNullChecks": true,
            "strictPropertyInitialization": true,
            "target": "ES2022",
            "types": ["vite/client", "node"],
            "useUnknownInCatchVariables": true,
            "verbatimModuleSyntax": true
        },
        "include": ["src", "vite.config.ts"]
    }))
    .map_or_else(|_error| "{}\n".to_owned(), |source| format!("{source}\n"))
}

pub(crate) fn rust_api_source() -> &'static str {
    r##"use std::{
    io::{Read, Write},
    net::{TcpListener, TcpStream},
};

fn main() -> std::io::Result<()> {
    let port = std::env::var("PORT").unwrap_or_else(|_error| "3000".to_owned());
    let listener = TcpListener::bind(format!("0.0.0.0:{port}"))?;

    for stream in listener.incoming() {
        handle(stream?)?;
    }

    Ok(())
}

fn handle(mut stream: TcpStream) -> std::io::Result<()> {
    let mut buffer = [0_u8; 2048];
    let size = stream.read(&mut buffer)?;
    let request = String::from_utf8_lossy(&buffer[..size]);
    let mut parts = request
        .lines()
        .next()
        .unwrap_or_default()
        .split_whitespace();
    let method = parts.next().unwrap_or_default();
    let path = parts.next().unwrap_or("/");
    let origin = request
        .lines()
        .find_map(|line| line.strip_prefix("Origin: "))
        .unwrap_or("*");
    let cors_origin = allowed_origin(origin);

    if method == "OPTIONS" {
        return write_response(&mut stream, "204 No Content", "", &cors_origin);
    }

    let body = if path == "/healthz" || path == "/api/healthz" {
        r#"{"ok":true}"#
    } else {
        r#"{"message":"hello from tovuk","backend":"rust"}"#
    };
    write_response(&mut stream, "200 OK", body, &cors_origin)
}

fn allowed_origin(request_origin: &str) -> String {
    let configured = std::env::var("FRONTEND_ORIGIN").unwrap_or_else(|_error| request_origin.to_owned());
    if configured == "*" || configured == request_origin {
        configured
    } else {
        "null".to_owned()
    }
}

fn write_response(
    stream: &mut TcpStream,
    status: &str,
    body: &str,
    origin: &str,
) -> std::io::Result<()> {
    write!(
        stream,
        "HTTP/1.1 {status}\r\ncontent-type: application/json\r\ncontent-length: {}\r\naccess-control-allow-origin: {origin}\r\naccess-control-allow-methods: GET, OPTIONS\r\naccess-control-allow-headers: content-type, authorization\r\nconnection: close\r\n\r\n{body}",
        body.len()
    )
}
"##
}

pub(crate) fn frontend_source(api_base_url: &str) -> String {
    format!(
        "import {{ createRootRoute, createRouter, RouterProvider }} from '@tanstack/react-router'\nimport {{ createRoot }} from 'react-dom/client'\nimport './styles.css'\n\nconst apiBaseUrl = import.meta.env.VITE_API_URL ?? '{api_base_url}'\n\nfunction App() {{\n  return (\n    <main>\n      <section>\n        <h1>Tovuk TanStack Frontend</h1>\n        <p>Static runtime, dynamic Rust backend calls.</p>\n        <code>{{apiBaseUrl}}</code>\n      </section>\n    </main>\n  )\n}}\n\nconst rootRoute = createRootRoute({{ component: App }})\nconst router = createRouter({{ routeTree: rootRoute }})\n\ndeclare module '@tanstack/react-router' {{\n  interface Register {{\n    router: typeof router\n  }}\n}}\n\nconst rootElement = document.getElementById('root')\nif (rootElement === null) {{\n  throw new Error('missing root element')\n}}\n\ncreateRoot(rootElement).render(<RouterProvider router={{router}} />)\n"
    )
}

pub(crate) fn frontend_vite_env_source() -> &'static str {
    "/// <reference types=\"vite/client\" />\n\ninterface ViteTypeOptions {\n  strictImportMetaEnv: unknown\n}\n\ninterface ImportMetaEnv {\n  readonly VITE_API_URL?: string\n}\n"
}