Skip to content

Commit 2575032

Browse files
committed
Merge branch 'main' into feat/theme-embed
2 parents 303b4ed + 6d00ad1 commit 2575032

6 files changed

Lines changed: 128 additions & 7 deletions

File tree

README.md

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -27,10 +27,15 @@ Here are the key development loops:
2727

2828
```sh
2929
pnpm install
30-
pnpm dev:website # http://localhost:5173/playground
31-
pnpm storybook # http://localhost:6006
32-
pnpm test # runs all tests
30+
pnpm dev:website # vite hotreload at http://localhost:5173/playground
31+
pnpm dev:standalone # tauri hotreload
32+
3333
pnpm dogfood:vscode # builds the VSCode extension and installs it into your local VSCode
34+
pnpm dogfood:standalone # builds and runs the standalone app
35+
pnpm dogfood:standalone --install # installs your local build overtop of your existing system installation
36+
37+
pnpm storybook # http://localhost:6006
38+
pnpm test # runs all tests
3439
```
3540

3641
### Folder structure

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
"build:standalone": "pnpm --filter mouseterm-standalone tauri build",
1313
"build:website": "pnpm --filter mouseterm-website build",
1414
"dogfood:vscode": "pnpm run build:vscode && pnpm --filter mouseterm dogfood",
15+
"dogfood:standalone": "bash standalone/scripts/dogfood.sh",
1516
"storybook": "pnpm --filter mouseterm-lib storybook",
1617
"bundle-themes": "node lib/scripts/bundle-themes.mjs"
1718
},

standalone/scripts/dogfood.sh

Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
#!/usr/bin/env bash
2+
#
3+
# Builds the standalone app and either launches or installs it.
4+
#
5+
# Usage:
6+
# pnpm dogfood:standalone Build and launch from the build directory.
7+
# pnpm dogfood:standalone --install Build and copy into the system install location.
8+
#
9+
# Launch mode (default):
10+
# Runs the built binary directly from target/release. Works on Windows, macOS,
11+
# and Linux with no prior setup. This is the fastest way to test changes.
12+
#
13+
# Install mode (--install):
14+
# Copies the built files over the system-installed copy, bypassing the slow
15+
# bundling/installer step. Requires a one-time install via the NSIS installer
16+
# so that registry entries, shortcuts, etc. are in place. Currently Windows only.
17+
#
18+
set -euo pipefail
19+
20+
# Skip past "--" that pnpm injects when forwarding arguments
21+
[[ "${1:-}" == "--" ]] && shift
22+
23+
RELEASE_DIR="standalone/src-tauri/target/release"
24+
25+
if [[ "${1:-}" == "--install" ]]; then
26+
# Full build with bundling, but disable updater artifact signing
27+
pnpm --filter mouseterm-standalone tauri build \
28+
-c '{"bundle":{"createUpdaterArtifacts":false}}'
29+
else
30+
# Fast build: skip bundling entirely since we just need the exe
31+
pnpm --filter mouseterm-standalone tauri build --no-bundle
32+
fi
33+
34+
if [[ "${1:-}" == "--install" ]]; then
35+
# --- Install mode ---
36+
# Platform-specific: copy built files to system install location
37+
case "$(uname -s)" in
38+
MINGW*|MSYS*|CYGWIN*|Windows_NT)
39+
INSTALL_DIR="$LOCALAPPDATA/MouseTerm"
40+
if [[ ! -f "$INSTALL_DIR/uninstall.exe" ]]; then
41+
echo "MouseTerm is not installed yet."
42+
echo "Run the installer once first:"
43+
echo " $RELEASE_DIR/bundle/nsis/MouseTerm_*-setup.exe"
44+
echo ""
45+
echo "After that, 'dogfood:standalone --install' will work from then on."
46+
exit 1
47+
fi
48+
# Wipe everything except uninstall.exe (managed by NSIS), then copy
49+
TMP_UNINSTALL="$(mktemp)"
50+
cp "$INSTALL_DIR/uninstall.exe" "$TMP_UNINSTALL"
51+
rm -rf "$INSTALL_DIR"
52+
mkdir -p "$INSTALL_DIR"
53+
mv "$TMP_UNINSTALL" "$INSTALL_DIR/uninstall.exe"
54+
cp "$RELEASE_DIR/mouseterm.exe" "$INSTALL_DIR/"
55+
cp "$RELEASE_DIR/node.exe" "$INSTALL_DIR/"
56+
cp -r "$RELEASE_DIR/_up_/" "$INSTALL_DIR/_up_/"
57+
echo "✦ Installed to $INSTALL_DIR"
58+
;;
59+
*)
60+
echo "--install is not yet implemented for this platform."
61+
exit 1
62+
;;
63+
esac
64+
else
65+
# --- Launch mode (default) ---
66+
case "$(uname -s)" in
67+
MINGW*|MSYS*|CYGWIN*|Windows_NT)
68+
"$RELEASE_DIR/mouseterm.exe" ;;
69+
Darwin)
70+
"$RELEASE_DIR/mouseterm" ;;
71+
Linux)
72+
"$RELEASE_DIR/mouseterm" ;;
73+
*)
74+
echo "Unsupported platform: $(uname -s)"
75+
exit 1 ;;
76+
esac
77+
fi

standalone/src-tauri/Cargo.lock

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

standalone/src-tauri/Cargo.toml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,9 @@ tauri-plugin-updater = "2"
2323
serde = { version = "1", features = ["derive"] }
2424
serde_json = "1"
2525

26+
[target.'cfg(unix)'.dependencies]
27+
libc = "0.2"
28+
2629
[profile.release]
2730
strip = true
2831
lto = true

standalone/src-tauri/src/lib.rs

Lines changed: 38 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ use std::{
1111
sync::{Arc, Mutex},
1212
time::{Duration, SystemTime, UNIX_EPOCH},
1313
};
14-
use tauri::{AppHandle, Emitter, Manager};
14+
use tauri::{AppHandle, Emitter, Manager, RunEvent};
1515
use tauri_plugin_shell::{process::CommandEvent, ShellExt};
1616

1717
enum SidecarMsg {
@@ -26,6 +26,7 @@ struct SidecarState {
2626
tx: SidecarSender,
2727
pending_requests: PendingRequests,
2828
next_request_id: AtomicU64,
29+
child_pid: u32,
2930
}
3031

3132
const LOG_FILE_ENV: &str = "MOUSETERM_LOG_FILE";
@@ -215,6 +216,28 @@ fn pty_get_scrollback(
215216
#[tauri::command]
216217
fn shutdown_sidecar(state: tauri::State<'_, SidecarState>) {
217218
let _ = state.tx.send(SidecarMsg::Shutdown);
219+
kill_process_tree(state.child_pid);
220+
}
221+
222+
/// Kill the sidecar process. On Windows, `taskkill /T` kills the entire
223+
/// process tree so that child shell processes don't outlive the sidecar.
224+
/// On Unix, a single SIGTERM to the sidecar is sufficient because node-pty
225+
/// manages its own child processes and cleans them up on exit.
226+
fn kill_process_tree(pid: u32) {
227+
append_log(format!("[sidecar] killing process tree (pid={pid})"));
228+
#[cfg(target_os = "windows")]
229+
{
230+
use std::os::windows::process::CommandExt;
231+
const CREATE_NO_WINDOW: u32 = 0x08000000;
232+
let _ = std::process::Command::new("taskkill")
233+
.args(["/F", "/T", "/PID", &pid.to_string()])
234+
.creation_flags(CREATE_NO_WINDOW)
235+
.output();
236+
}
237+
#[cfg(unix)]
238+
{
239+
unsafe { libc::kill(pid as i32, libc::SIGTERM); }
240+
}
218241
}
219242

220243
#[tauri::command]
@@ -298,7 +321,8 @@ fn start_sidecar(app: &AppHandle) -> Result<SidecarState, String> {
298321
.set_raw_out(false)
299322
.spawn()
300323
.map_err(|err| format!("failed to start Node.js sidecar: {err}"))?;
301-
append_log("[sidecar] spawned Node.js runtime");
324+
let child_pid = child.pid();
325+
append_log(format!("[sidecar] spawned Node.js runtime (pid={child_pid})"));
302326

303327
let handle = app.clone();
304328
let pending_requests: PendingRequests = Arc::new(Mutex::new(HashMap::new()));
@@ -392,6 +416,7 @@ fn start_sidecar(app: &AppHandle) -> Result<SidecarState, String> {
392416
tx,
393417
pending_requests,
394418
next_request_id: AtomicU64::new(0),
419+
child_pid,
395420
})
396421
}
397422

@@ -437,8 +462,17 @@ pub fn run() {
437462
get_project_dir,
438463
get_available_shells,
439464
])
440-
.run(tauri::generate_context!())
441-
.expect("error while running MouseTerm");
465+
.build(tauri::generate_context!())
466+
.expect("error while building MouseTerm")
467+
.run(|app, event| {
468+
if let RunEvent::Exit = event {
469+
if let Some(state) = app.try_state::<SidecarState>() {
470+
append_log("[app] exit — killing sidecar");
471+
let _ = state.tx.send(SidecarMsg::Shutdown);
472+
kill_process_tree(state.child_pid);
473+
}
474+
}
475+
});
442476
}
443477

444478
#[cfg(test)]

0 commit comments

Comments
 (0)