diff --git a/bun.lock b/bun.lock index cafd303..c2d53f2 100644 --- a/bun.lock +++ b/bun.lock @@ -5,9 +5,14 @@ "": { "name": "pengine", "dependencies": { + "@dnd-kit/core": "^6.3.1", + "@dnd-kit/modifiers": "^9.0.0", + "@dnd-kit/sortable": "^10.0.0", + "@dnd-kit/utilities": "^3.2.2", "@radix-ui/react-accordion": "^1.2.12", "@radix-ui/react-menubar": "^1.1.16", "@radix-ui/react-scroll-area": "^1.2.10", + "@radix-ui/react-select": "^2.2.6", "@radix-ui/react-slider": "^1.3.6", "@tailwindcss/vite": "^4.2.2", "@tauri-apps/api": "^2", @@ -78,6 +83,16 @@ "@babel/types": ["@babel/types@7.29.0", "", { "dependencies": { "@babel/helper-string-parser": "^7.27.1", "@babel/helper-validator-identifier": "^7.28.5" } }, "sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A=="], + "@dnd-kit/accessibility": ["@dnd-kit/accessibility@3.1.1", "", { "dependencies": { "tslib": "^2.0.0" }, "peerDependencies": { "react": ">=16.8.0" } }, "sha512-2P+YgaXF+gRsIihwwY1gCsQSYnu9Zyj2py8kY5fFvUM1qm2WA2u639R6YNVfU4GWr+ZM5mqEsfHZZLoRONbemw=="], + + "@dnd-kit/core": ["@dnd-kit/core@6.3.1", "", { "dependencies": { "@dnd-kit/accessibility": "^3.1.1", "@dnd-kit/utilities": "^3.2.2", "tslib": "^2.0.0" }, "peerDependencies": { "react": ">=16.8.0", "react-dom": ">=16.8.0" } }, "sha512-xkGBRQQab4RLwgXxoqETICr6S5JlogafbhNsidmrkVv2YRs5MLwpjoF2qpiGjQt8S9AoxtIV603s0GIUpY5eYQ=="], + + "@dnd-kit/modifiers": ["@dnd-kit/modifiers@9.0.0", "", { "dependencies": { "@dnd-kit/utilities": "^3.2.2", "tslib": "^2.0.0" }, "peerDependencies": { "@dnd-kit/core": "^6.3.0", "react": ">=16.8.0" } }, "sha512-ybiLc66qRGuZoC20wdSSG6pDXFikui/dCNGthxv4Ndy8ylErY0N3KVxY2bgo7AWwIbxDmXDg3ylAFmnrjcbVvw=="], + + "@dnd-kit/sortable": ["@dnd-kit/sortable@10.0.0", "", { "dependencies": { "@dnd-kit/utilities": "^3.2.2", "tslib": "^2.0.0" }, "peerDependencies": { "@dnd-kit/core": "^6.3.0", "react": ">=16.8.0" } }, "sha512-+xqhmIIzvAYMGfBYYnbKuNicfSsk4RksY2XdmJhT+HAC01nix6fHCztU68jooFiMUB01Ky3F0FyOvhG/BZrWkg=="], + + "@dnd-kit/utilities": ["@dnd-kit/utilities@3.2.2", "", { "dependencies": { "tslib": "^2.0.0" }, "peerDependencies": { "react": ">=16.8.0" } }, "sha512-+MKAJEOfaBe5SmV6t34p80MMKhjvUz0vRrvVJbPT0WElzaOJ/1xs+D+KDv+tD/NE5ujfrChEcshd4fLn0wpiqg=="], + "@esbuild/aix-ppc64": ["@esbuild/aix-ppc64@0.27.7", "", { "os": "aix", "cpu": "ppc64" }, "sha512-EKX3Qwmhz1eMdEJokhALr0YiD0lhQNwDqkPYyPhiSwKrh7/4KRjQc04sZ8db+5DVVnZ1LmbNDI1uAMPEUBnQPg=="], "@esbuild/android-arm": ["@esbuild/android-arm@0.27.7", "", { "os": "android", "cpu": "arm" }, "sha512-jbPXvB4Yj2yBV7HUfE2KHe4GJX51QplCN1pGbYjvsyCZbQmies29EoJbkEc+vYuU5o45AfQn37vZlyXy4YJ8RQ=="], @@ -216,6 +231,8 @@ "@radix-ui/react-scroll-area": ["@radix-ui/react-scroll-area@1.2.10", "", { "dependencies": { "@radix-ui/number": "1.1.1", "@radix-ui/primitive": "1.1.3", "@radix-ui/react-compose-refs": "1.1.2", "@radix-ui/react-context": "1.1.2", "@radix-ui/react-direction": "1.1.1", "@radix-ui/react-presence": "1.1.5", "@radix-ui/react-primitive": "2.1.3", "@radix-ui/react-use-callback-ref": "1.1.1", "@radix-ui/react-use-layout-effect": "1.1.1" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-tAXIa1g3sM5CGpVT0uIbUx/U3Gs5N8T52IICuCtObaos1S8fzsrPXG5WObkQN3S6NVl6wKgPhAIiBGbWnvc97A=="], + "@radix-ui/react-select": ["@radix-ui/react-select@2.2.6", "", { "dependencies": { "@radix-ui/number": "1.1.1", "@radix-ui/primitive": "1.1.3", "@radix-ui/react-collection": "1.1.7", "@radix-ui/react-compose-refs": "1.1.2", "@radix-ui/react-context": "1.1.2", "@radix-ui/react-direction": "1.1.1", "@radix-ui/react-dismissable-layer": "1.1.11", "@radix-ui/react-focus-guards": "1.1.3", "@radix-ui/react-focus-scope": "1.1.7", "@radix-ui/react-id": "1.1.1", "@radix-ui/react-popper": "1.2.8", "@radix-ui/react-portal": "1.1.9", "@radix-ui/react-primitive": "2.1.3", "@radix-ui/react-slot": "1.2.3", "@radix-ui/react-use-callback-ref": "1.1.1", "@radix-ui/react-use-controllable-state": "1.2.2", "@radix-ui/react-use-layout-effect": "1.1.1", "@radix-ui/react-use-previous": "1.1.1", "@radix-ui/react-visually-hidden": "1.2.3", "aria-hidden": "^1.2.4", "react-remove-scroll": "^2.6.3" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-I30RydO+bnn2PQztvo25tswPH+wFBjehVGtmagkU78yMdwTwVf12wnAOF+AeP8S2N8xD+5UPbGhkUfPyvT+mwQ=="], + "@radix-ui/react-slider": ["@radix-ui/react-slider@1.3.6", "", { "dependencies": { "@radix-ui/number": "1.1.1", "@radix-ui/primitive": "1.1.3", "@radix-ui/react-collection": "1.1.7", "@radix-ui/react-compose-refs": "1.1.2", "@radix-ui/react-context": "1.1.2", "@radix-ui/react-direction": "1.1.1", "@radix-ui/react-primitive": "2.1.3", "@radix-ui/react-use-controllable-state": "1.2.2", "@radix-ui/react-use-layout-effect": "1.1.1", "@radix-ui/react-use-previous": "1.1.1", "@radix-ui/react-use-size": "1.1.1" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-JPYb1GuM1bxfjMRlNLE+BcmBC8onfCi60Blk7OBqi2MLTFdS+8401U4uFjnwkOr49BLmXxLC6JHkvAsx5OJvHw=="], "@radix-ui/react-slot": ["@radix-ui/react-slot@1.2.3", "", { "dependencies": { "@radix-ui/react-compose-refs": "1.1.2" }, "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A=="], @@ -236,6 +253,8 @@ "@radix-ui/react-use-size": ["@radix-ui/react-use-size@1.1.1", "", { "dependencies": { "@radix-ui/react-use-layout-effect": "1.1.1" }, "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-ewrXRDTAqAXlkl6t/fkXWNAhFX9I+CkKlw6zjEwk86RSPKwZr3xpBRso655aqYafwtnbpHLj6toFzmd6xdVptQ=="], + "@radix-ui/react-visually-hidden": ["@radix-ui/react-visually-hidden@1.2.3", "", { "dependencies": { "@radix-ui/react-primitive": "2.1.3" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-pzJq12tEaaIhqjbzpCuv/OypJY/BPavOofm+dbab+MHLajy277+1lLm6JFcGgF5eskJ6mquGirhXY2GD/8u8Ug=="], + "@radix-ui/rect": ["@radix-ui/rect@1.1.1", "", {}, "sha512-HPwpGIzkl28mWyZqG52jiqDJ12waP11Pa1lGoiyUkIEuMLBP0oeK/C89esbXrxsky5we7dfd8U58nm0SgAWpVw=="], "@rolldown/pluginutils": ["@rolldown/pluginutils@1.0.0-beta.27", "", {}, "sha512-+d0F4MKMCbeVUJwG96uQ4SgAznZNSq93I3V+9NHA4OpvqG8mRCpGdKmK8l/dl02h2CCDHwW2FqilnTyDcAnqjA=="], diff --git a/package-lock.json b/package-lock.json index e7b5ac5..c1f8f1c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "pengine", - "version": "0.1.0", + "version": "1.0.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "pengine", - "version": "0.1.0", + "version": "1.0.0", "dependencies": { "@dnd-kit/core": "^6.3.1", "@dnd-kit/modifiers": "^9.0.0", diff --git a/package.json b/package.json index f4bb1c3..098661c 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "pengine", "private": true, - "version": "0.1.0", + "version": "1.0.0", "type": "module", "scripts": { "dev": "vite", diff --git a/src-tauri/Cargo.lock b/src-tauri/Cargo.lock index 6a7878f..185d470 100644 --- a/src-tauri/Cargo.lock +++ b/src-tauri/Cargo.lock @@ -3170,7 +3170,7 @@ checksum = "df94ce210e5bc13cb6651479fa48d14f601d9858cfe0467f43ae157023b938d3" [[package]] name = "pengine" -version = "0.1.0" +version = "1.0.0" dependencies = [ "axum", "chrono", diff --git a/src-tauri/Cargo.toml b/src-tauri/Cargo.toml index f7f00d4..02140c0 100644 --- a/src-tauri/Cargo.toml +++ b/src-tauri/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "pengine" -version = "0.1.0" +version = "1.0.0" description = "A Tauri App" authors = ["you"] edition = "2021" diff --git a/src-tauri/build.rs b/src-tauri/build.rs index d860e1e..a909e54 100644 --- a/src-tauri/build.rs +++ b/src-tauri/build.rs @@ -1,3 +1,69 @@ fn main() { - tauri_build::build() + let manifest_dir = std::path::PathBuf::from( + std::env::var_os("CARGO_MANIFEST_DIR").expect("CARGO_MANIFEST_DIR"), + ); + + let app_version = package_json_version(&manifest_dir); + println!("cargo:rustc-env=PENGINE_APP_VERSION={app_version}"); + + let commit = git_head_commit(&manifest_dir); + println!("cargo:rustc-env=PENGINE_GIT_COMMIT={commit}"); + + let package_json = manifest_dir.join("../package.json"); + if let Ok(canonical) = package_json.canonicalize() { + println!("cargo:rerun-if-changed={}", canonical.display()); + } + + let git_head = manifest_dir.join("../.git/HEAD"); + if let Ok(canonical) = git_head.canonicalize() { + println!("cargo:rerun-if-changed={}", canonical.display()); + } + + tauri_build::build(); +} + +/// User-facing release version: root `package.json` `"version"` (same as Vite/npm). +fn package_json_version(manifest_dir: &std::path::Path) -> String { + let path = manifest_dir + .parent() + .unwrap_or(manifest_dir) + .join("package.json"); + let raw = match std::fs::read_to_string(&path) { + Ok(s) => s, + Err(_) => return std::env::var("CARGO_PKG_VERSION").unwrap_or_else(|_| "0.0.0".into()), + }; + for line in raw.lines() { + let t = line.trim_start(); + if let Some(rest) = t.strip_prefix("\"version\"") { + let rest = rest.trim_start(); + let rest = match rest.strip_prefix(':') { + Some(r) => r.trim_start(), + None => continue, + }; + let rest = match rest.strip_prefix('"') { + Some(r) => r, + None => continue, + }; + if let Some(end) = rest.find('"') { + let v = rest[..end].trim(); + if !v.is_empty() { + return v.to_string(); + } + } + } + } + std::env::var("CARGO_PKG_VERSION").unwrap_or_else(|_| "0.0.0".into()) +} + +/// Resolved `HEAD` at build time (tip of the checked-out branch). +fn git_head_commit(manifest_dir: &std::path::Path) -> String { + let repo_root = manifest_dir.parent().unwrap_or(manifest_dir); + let output = std::process::Command::new("git") + .args(["rev-parse", "HEAD"]) + .current_dir(repo_root) + .output(); + match output { + Ok(o) if o.status.success() => String::from_utf8_lossy(&o.stdout).trim().to_string(), + _ => "unknown".to_string(), + } } diff --git a/src-tauri/src/build_info.rs b/src-tauri/src/build_info.rs new file mode 100644 index 0000000..3720b96 --- /dev/null +++ b/src-tauri/src/build_info.rs @@ -0,0 +1,4 @@ +//! Compile-time app version (root `package.json`, set in `build.rs`) and git commit. + +pub const APP_VERSION: &str = env!("PENGINE_APP_VERSION"); +pub const GIT_COMMIT: &str = env!("PENGINE_GIT_COMMIT"); diff --git a/src-tauri/src/infrastructure/http_server.rs b/src-tauri/src/infrastructure/http_server.rs index 169096d..6cef885 100644 --- a/src-tauri/src/infrastructure/http_server.rs +++ b/src-tauri/src/infrastructure/http_server.rs @@ -1,3 +1,4 @@ +use crate::build_info; use crate::infrastructure::bot_lifecycle; use crate::modules::bot::{agent as bot_agent, repository, service as bot_service}; use crate::modules::cron::{ @@ -53,6 +54,10 @@ pub struct HealthResponse { pub bot_connected: bool, pub bot_username: Option, pub bot_id: Option, + /// Release version (root `package.json`, baked in at build). + pub app_version: String, + /// Git commit at build time (`HEAD`), or `"unknown"`. + pub git_commit: String, } #[derive(Serialize)] @@ -348,6 +353,8 @@ async fn handle_health(State(state): State) -> Json { bot_connected: conn.is_some(), bot_username: conn.as_ref().map(|c| c.bot_username.clone()), bot_id: conn.as_ref().map(|c| c.bot_id.clone()), + app_version: build_info::APP_VERSION.to_string(), + git_commit: build_info::GIT_COMMIT.to_string(), }) } diff --git a/src-tauri/src/lib.rs b/src-tauri/src/lib.rs index a56f05f..d57237d 100644 --- a/src-tauri/src/lib.rs +++ b/src-tauri/src/lib.rs @@ -1,4 +1,5 @@ mod app; +mod build_info; mod infrastructure; pub mod modules; mod shared; diff --git a/src-tauri/tauri.conf.json b/src-tauri/tauri.conf.json index 285d5e1..199812f 100644 --- a/src-tauri/tauri.conf.json +++ b/src-tauri/tauri.conf.json @@ -1,7 +1,7 @@ { "$schema": "https://schema.tauri.app/config/2", "productName": "pengine", - "version": "0.1.0", + "version": "1.0.0", "identifier": "com.maximedogawa.pengine", "build": { "beforeDevCommand": "bun run dev", diff --git a/src/modules/bot/types.ts b/src/modules/bot/types.ts index 5ec4114..0fa2cb4 100644 --- a/src/modules/bot/types.ts +++ b/src/modules/bot/types.ts @@ -3,4 +3,6 @@ export type PengineHealth = { bot_connected: boolean; bot_username?: string; bot_id?: string | null; + app_version?: string; + git_commit?: string; }; diff --git a/src/pages/DashboardPage.tsx b/src/pages/DashboardPage.tsx index c6255a4..bb70807 100644 --- a/src/pages/DashboardPage.tsx +++ b/src/pages/DashboardPage.tsx @@ -32,12 +32,21 @@ export function DashboardPage() { { name: "Ollama", status: "checking", detail: "Checking…" }, ]); const [disconnectError, setDisconnectError] = useState(null); + const [appVersion, setAppVersion] = useState(null); + const [gitCommit, setGitCommit] = useState(null); const refreshStatus = useCallback(async () => { let botUser = botUsername ?? "unknown"; const health = await getPengineHealth(3000); const pengineUp = !!health; const botConnected = health?.bot_connected ?? false; if (health?.bot_username) botUser = health.bot_username; + if (health) { + setAppVersion(health.app_version ?? null); + setGitCommit(health.git_commit ?? null); + } else { + setAppVersion(null); + setGitCommit(null); + } const ollama = await fetchOllamaModels(2500); const ollamaUp = ollama.reachable; @@ -204,6 +213,17 @@ export function DashboardPage() { )} {modelError &&

{modelError}

} + {appVersion && gitCommit && ( +

+ Pengine v{appVersion} + {gitCommit !== "unknown" ? ` · ${gitCommit.slice(0, 7)}` : ""} +

+ )} + {/* ── Terminal (full width) ────────────────────────────── */}