mirror of
https://github.com/rojo-rbx/rojo.git
synced 2026-10-21 12:01:38 +00:00
Add rojo open command (AI-assisted)
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
a30a8ecd0f
commit
dfa6dd7003
7 changed files with 224 additions and 17 deletions
|
|
@ -17,6 +17,8 @@ use super::resolve_path;
|
|||
|
||||
const UNKNOWN_OUTPUT_KIND_ERR: &str = "Could not detect what kind of file to build. \
|
||||
Expected output file to end in .rbxl, .rbxlx, .rbxm, or .rbxmx.";
|
||||
pub(super) const UNKNOWN_PLACE_KIND_ERR: &str = "Could not detect what kind of file to build. \
|
||||
Expected place file to end in .rbxl or .rbxlx.";
|
||||
const UNKNOWN_PLUGIN_KIND_ERR: &str = "Could not detect what kind of file to build. \
|
||||
Expected plugin file to end in .rbxm or .rbxmx.";
|
||||
|
||||
|
|
@ -113,7 +115,7 @@ impl BuildCommand {
|
|||
|
||||
/// The different kinds of output that Rojo can build to.
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
enum OutputKind {
|
||||
pub(super) enum OutputKind {
|
||||
/// An XML model file.
|
||||
Rbxmx,
|
||||
|
||||
|
|
@ -140,6 +142,16 @@ impl OutputKind {
|
|||
}
|
||||
}
|
||||
|
||||
pub(super) fn from_place_path(output: &Path) -> Option<OutputKind> {
|
||||
let extension = output.extension()?.to_str()?;
|
||||
|
||||
match extension {
|
||||
"rbxlx" => Some(OutputKind::Rbxlx),
|
||||
"rbxl" => Some(OutputKind::Rbxl),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
fn from_plugin_path(output: &Path) -> Option<OutputKind> {
|
||||
let extension = output.extension()?.to_str()?;
|
||||
|
||||
|
|
@ -156,7 +168,7 @@ fn xml_encode_config() -> rbx_xml::EncodeOptions<'static> {
|
|||
}
|
||||
|
||||
#[profiling::function]
|
||||
fn write_model(
|
||||
pub(super) fn write_model(
|
||||
session: &ServeSession,
|
||||
output: &Path,
|
||||
output_kind: OutputKind,
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ mod build;
|
|||
mod doc;
|
||||
mod fmt_project;
|
||||
mod init;
|
||||
mod open;
|
||||
mod plugin;
|
||||
mod serve;
|
||||
mod sourcemap;
|
||||
|
|
@ -20,6 +21,7 @@ pub use self::build::BuildCommand;
|
|||
pub use self::doc::DocCommand;
|
||||
pub use self::fmt_project::FmtProjectCommand;
|
||||
pub use self::init::{InitCommand, InitKind};
|
||||
pub use self::open::OpenCommand;
|
||||
pub use self::plugin::{PluginCommand, PluginSubcommand};
|
||||
pub use self::serve::ServeCommand;
|
||||
pub use self::sourcemap::SourcemapCommand;
|
||||
|
|
@ -44,6 +46,7 @@ impl Options {
|
|||
Subcommand::Init(subcommand) => subcommand.run(),
|
||||
Subcommand::Serve(subcommand) => subcommand.run(self.global),
|
||||
Subcommand::Build(subcommand) => subcommand.run(),
|
||||
Subcommand::Open(subcommand) => subcommand.run(self.global),
|
||||
Subcommand::Upload(subcommand) => subcommand.run(),
|
||||
Subcommand::Sourcemap(subcommand) => subcommand.run(),
|
||||
Subcommand::FmtProject(subcommand) => subcommand.run(),
|
||||
|
|
@ -118,6 +121,7 @@ pub enum Subcommand {
|
|||
Init(InitCommand),
|
||||
Serve(ServeCommand),
|
||||
Build(BuildCommand),
|
||||
Open(OpenCommand),
|
||||
Upload(UploadCommand),
|
||||
Sourcemap(SourcemapCommand),
|
||||
FmtProject(FmtProjectCommand),
|
||||
|
|
|
|||
116
src/cli/open.rs
Normal file
116
src/cli/open.rs
Normal file
|
|
@ -0,0 +1,116 @@
|
|||
use std::{path::PathBuf, process::Command, sync::Arc};
|
||||
|
||||
use anyhow::Context;
|
||||
use clap::{ArgGroup, Parser};
|
||||
use memofs::Vfs;
|
||||
use roblox_install::RobloxStudio;
|
||||
|
||||
use crate::{
|
||||
cli::{
|
||||
build::{write_model, OutputKind, UNKNOWN_PLACE_KIND_ERR},
|
||||
resolve_path,
|
||||
serve::{show_start_message, ServerOptions},
|
||||
GlobalOptions,
|
||||
},
|
||||
serve_session::ServeSession,
|
||||
web::LiveServer,
|
||||
};
|
||||
|
||||
/// Serve a project and open a place in Roblox Studio that connects to it.
|
||||
#[derive(Debug, Parser)]
|
||||
#[clap(group(
|
||||
ArgGroup::new("target").required(true).args(&["place", "output", "place-id"])
|
||||
))]
|
||||
pub struct OpenCommand {
|
||||
/// Path to the project to serve. Defaults to the current directory.
|
||||
#[clap(default_value = "")]
|
||||
pub project: PathBuf,
|
||||
|
||||
#[clap(flatten)]
|
||||
pub server: ServerOptions,
|
||||
|
||||
/// An existing place file to open. Must end in `.rbxl` or `.rbxlx`.
|
||||
#[clap(long)]
|
||||
pub place: Option<PathBuf>,
|
||||
|
||||
/// Build the project to this place file, then open it. Must end in `.rbxl`
|
||||
/// or `.rbxlx`.
|
||||
#[clap(long)]
|
||||
pub output: Option<PathBuf>,
|
||||
|
||||
/// The ID of a published place to open. Requires `--universe-id`.
|
||||
#[clap(long, requires = "universe-id")]
|
||||
pub place_id: Option<u64>,
|
||||
|
||||
/// The universe ID of the place given with `--place-id`.
|
||||
#[clap(long, requires = "place-id")]
|
||||
pub universe_id: Option<u64>,
|
||||
}
|
||||
|
||||
impl OpenCommand {
|
||||
pub fn run(self, global: GlobalOptions) -> anyhow::Result<()> {
|
||||
let project_path = resolve_path(&self.project)?;
|
||||
let studio = RobloxStudio::locate()?;
|
||||
let vfs = Vfs::new_default()?;
|
||||
let session = Arc::new(ServeSession::new(vfs, project_path)?);
|
||||
let (ip, port, allowed_hosts) = self.server.resolve_options(&session);
|
||||
let script_path =
|
||||
std::env::temp_dir().join(format!("rojo-open-{}.luau", std::process::id()));
|
||||
|
||||
let mut command = Command::new(studio.application_path());
|
||||
command
|
||||
.arg("--task")
|
||||
.arg("RunScript")
|
||||
.arg("--runScriptFile")
|
||||
.arg(&script_path);
|
||||
|
||||
if let Some(output) = self.output {
|
||||
let output_kind =
|
||||
OutputKind::from_place_path(&output).context(UNKNOWN_PLACE_KIND_ERR)?;
|
||||
|
||||
write_model(&session, &output, output_kind)?;
|
||||
|
||||
command.arg("--localPlaceFile").arg(output);
|
||||
} else if let Some(place) = self.place {
|
||||
OutputKind::from_place_path(&place).context(UNKNOWN_PLACE_KIND_ERR)?;
|
||||
|
||||
let place = dunce::canonicalize(&place)
|
||||
.with_context(|| format!("Could not find place file {}", place.display()))?;
|
||||
|
||||
command.arg("--localPlaceFile").arg(place);
|
||||
} else if let (Some(place_id), Some(universe_id)) = (self.place_id, self.universe_id) {
|
||||
command
|
||||
.arg("--universeId")
|
||||
.arg(universe_id.to_string())
|
||||
.arg("--placeId")
|
||||
.arg(place_id.to_string());
|
||||
}
|
||||
|
||||
let session_id = session.session_id();
|
||||
fs_err::write(
|
||||
&script_path,
|
||||
format!(
|
||||
r#"local value = Instance.new("Configuration")
|
||||
value.Name = `ROJO_OPEN_{{game:GetService("StudioService"):GetUserId()}}`
|
||||
value.Archivable = false
|
||||
value:SetAttribute("Host", "{ip}")
|
||||
value:SetAttribute("Port", "{port}")
|
||||
value:SetAttribute("SessionId", "{session_id}")
|
||||
value.Parent = game"#,
|
||||
),
|
||||
)?;
|
||||
|
||||
let server = LiveServer::new(session);
|
||||
|
||||
server.start((ip, port).into(), allowed_hosts, || {
|
||||
let _ = show_start_message(ip, port, global.color.into());
|
||||
|
||||
// Wait for a successful bind so the plugin can't connect to another server on this address.
|
||||
if let Err(err) = command.spawn() {
|
||||
log::error!("Could not launch Roblox Studio: {err}");
|
||||
}
|
||||
})?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
|
@ -16,13 +16,8 @@ use super::{resolve_path, GlobalOptions};
|
|||
const DEFAULT_BIND_ADDRESS: Ipv4Addr = Ipv4Addr::new(127, 0, 0, 1);
|
||||
const DEFAULT_PORT: u16 = 34872;
|
||||
|
||||
/// Expose a Rojo project to the Rojo Studio plugin.
|
||||
#[derive(Debug, Parser)]
|
||||
pub struct ServeCommand {
|
||||
/// Path to the project to serve. Defaults to the current directory.
|
||||
#[clap(default_value = "")]
|
||||
pub project: PathBuf,
|
||||
|
||||
pub struct ServerOptions {
|
||||
/// The IP address to listen on. Defaults to `127.0.0.1`.
|
||||
#[clap(long)]
|
||||
pub address: Option<IpAddr>,
|
||||
|
|
@ -41,14 +36,19 @@ pub struct ServeCommand {
|
|||
pub allowed_hosts: Vec<String>,
|
||||
}
|
||||
|
||||
impl ServeCommand {
|
||||
pub fn run(self, global: GlobalOptions) -> anyhow::Result<()> {
|
||||
let project_path = resolve_path(&self.project)?;
|
||||
/// Expose a Rojo project to the Rojo Studio plugin.
|
||||
#[derive(Debug, Parser)]
|
||||
pub struct ServeCommand {
|
||||
/// Path to the project to serve. Defaults to the current directory.
|
||||
#[clap(default_value = "")]
|
||||
pub project: PathBuf,
|
||||
|
||||
let vfs = Vfs::new_default()?;
|
||||
|
||||
let session = Arc::new(ServeSession::new(vfs, project_path)?);
|
||||
#[clap(flatten)]
|
||||
pub server: ServerOptions,
|
||||
}
|
||||
|
||||
impl ServerOptions {
|
||||
pub(super) fn resolve_options(self, session: &ServeSession) -> (IpAddr, u16, Vec<String>) {
|
||||
let ip = self
|
||||
.address
|
||||
.or_else(|| session.serve_address())
|
||||
|
|
@ -67,6 +67,20 @@ impl ServeCommand {
|
|||
self.allowed_hosts
|
||||
};
|
||||
|
||||
(ip, port, allowed_hosts)
|
||||
}
|
||||
}
|
||||
|
||||
impl ServeCommand {
|
||||
pub fn run(self, global: GlobalOptions) -> anyhow::Result<()> {
|
||||
let project_path = resolve_path(&self.project)?;
|
||||
|
||||
let vfs = Vfs::new_default()?;
|
||||
|
||||
let session = Arc::new(ServeSession::new(vfs, project_path)?);
|
||||
|
||||
let (ip, port, allowed_hosts) = self.server.resolve_options(&session);
|
||||
|
||||
let server = LiveServer::new(session);
|
||||
|
||||
server.start((ip, port).into(), allowed_hosts, || {
|
||||
|
|
@ -77,7 +91,11 @@ impl ServeCommand {
|
|||
}
|
||||
}
|
||||
|
||||
fn show_start_message(bind_address: IpAddr, port: u16, color: ColorChoice) -> io::Result<()> {
|
||||
pub(super) fn show_start_message(
|
||||
bind_address: IpAddr,
|
||||
port: u16,
|
||||
color: ColorChoice,
|
||||
) -> io::Result<()> {
|
||||
let mut green = ColorSpec::new();
|
||||
green.set_fg(Some(Color::Green)).set_bold(true);
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue