Skip to main content

run_stack/
delegate.rs

1//! Handing a command to the shell implementation.
2//!
3//! The port is not finished, and a half-ported tool that refuses the commands
4//! it has not reached yet is worse than no port at all. So the shell version
5//! is carried in the binary as well, and anything not yet in Rust runs through
6//! it. Commands move across one at a time, and nobody has to care which is
7//! which.
8//!
9//! It needs bash and python3 on the machine. Once a command is ported, its
10//! entry disappears from here, and when the list is empty the shell copy and
11//! this module go with it.
12
13use std::os::unix::process::CommandExt;
14use std::process::Command;
15
16use anyhow::{Context, Result};
17
18use crate::assets;
19use crate::workspace::Workspace;
20
21/// Commands the shell version still owns, with what each is waiting on.
22pub const PENDING: &[(&str, &str)] = &[
23    ("init", "the prompts and the app scan"),
24    ("create", "workspace setup"),
25    ("new", "workspace setup"),
26    ("migrate", "layout conversion"),
27    ("layout-migrate", "layout conversion"),
28    ("upgrade", "layout conversion"),
29    ("clean", "volume deletion"),
30    ("rebuild", "image rebuild"),
31    ("fresh", "schema rebuild"),
32    ("seed", "database seeders"),
33    ("backend", "commands in the API container"),
34    ("artisan", "laravel"),
35    ("composer", "laravel"),
36    ("pnpm", "the workspace package manager"),
37    ("dash", "the dashboard"),
38    ("dashboard", "the dashboard"),
39    ("services", "the compose service table"),
40    ("ios", "simulator launch"),
41    ("android", "emulator launch"),
42    ("device", "device launch"),
43    ("mobile", "metro restart"),
44    ("reload", "metro reload"),
45    ("prebuild", "expo prebuild"),
46    ("bundler", "bundler configuration"),
47    ("configure-bundler", "bundler configuration"),
48    ("sync-mobile-port", "the native port sync"),
49    ("desktop", "the electron / tauri shell"),
50    ("deploy", "deploy targets"),
51    ("completion", "shell completion"),
52];
53
54pub fn is_pending(command: &str) -> bool {
55    PENDING.iter().any(|(name, _)| *name == command)
56}
57
58/// Run it through the shell version, replacing this process so signals, exit
59/// codes and interactive prompts all behave as if it had been called directly.
60pub fn run(command: &str, args: &[String], workspace: Option<&Workspace>) -> Result<i32> {
61    let package = assets::package_dir()?;
62    let runner = package.join("run.sh");
63    if !runner.is_file() {
64        anyhow::bail!(
65            "{} is missing — the shell implementation should have been unpacked here",
66            runner.display()
67        );
68    }
69
70    let mut process = Command::new("bash");
71    process.arg(&runner).arg(command).args(args);
72    process.env("RUN_PACKAGE_DIR", &package);
73    // So every hint it prints names a command that exists.
74    process.env("RUN_CMD", "rst");
75    if let Some(workspace) = workspace {
76        process.env("RUN_WORKSPACE_DIR", &workspace.root);
77    }
78
79    // exec: one process, not a parent watching a child.
80    let error = process.exec();
81    Err(error).with_context(|| format!("running {}", runner.display()))
82}