├── assets └── templates │ ├── quick │ ├── src │ │ ├── StarterGui │ │ │ └── .gitkeep │ │ ├── StarterPack │ │ │ └── .gitkeep │ │ ├── Workspace │ │ │ └── .gitkeep │ │ ├── ReplicatedFirst │ │ │ └── .gitkeep │ │ ├── ReplicatedStorage │ │ │ └── .gitkeep │ │ ├── ServerStorage │ │ │ └── .gitkeep │ │ ├── ServerScriptService │ │ │ └── .gitkeep │ │ └── StarterPlayer │ │ │ ├── StarterCharacterScripts │ │ │ └── .gitkeep │ │ │ └── StarterPlayerScripts │ │ │ └── .gitkeep │ ├── selene.toml │ ├── wally.toml │ ├── .gitignore │ └── project.json │ ├── plugin │ ├── src │ │ └── .src.server.luau │ ├── LICENSE.md │ ├── selene.toml │ ├── CHANGELOG.md │ ├── project.json │ ├── wally.toml │ ├── README.md │ └── .gitignore │ ├── place │ ├── src │ │ ├── Client │ │ │ └── Main.client.luau │ │ ├── Server │ │ │ └── Main.server.luau │ │ └── Shared │ │ │ └── Hello.luau │ ├── selene.toml │ ├── wally.toml │ ├── .gitignore │ ├── README.md │ └── project.json │ ├── empty │ ├── selene.toml │ ├── project.json │ ├── wally.toml │ └── .gitignore │ ├── model │ ├── LICENSE.md │ ├── selene.toml │ ├── src │ │ └── .src.luau │ ├── project.json │ ├── README.md │ ├── wally.toml │ └── .gitignore │ └── package │ ├── LICENSE.md │ ├── selene.toml │ ├── CHANGELOG.md │ ├── src │ └── init.luau │ ├── project.json │ ├── README.md │ ├── .gitignore │ └── wally.toml ├── crates ├── profiling │ ├── .gitignore │ ├── rustfmt.toml │ ├── Cargo.toml │ ├── profiling │ │ ├── Cargo.toml │ │ ├── src │ │ │ └── lib.rs │ │ └── tests │ │ │ └── mod.rs │ └── profiling-procmacros │ │ ├── Cargo.toml │ │ └── src │ │ └── lib.rs ├── self_update │ ├── rustfmt.toml │ ├── .gitignore │ ├── build.rs │ ├── LICENSE │ ├── Cargo.toml │ └── src │ │ ├── macros.rs │ │ ├── backends │ │ └── mod.rs │ │ ├── errors.rs │ │ └── version.rs ├── config-derive │ ├── .gitignore │ ├── rustfmt.toml │ ├── Cargo.toml │ ├── tests │ │ └── mod.rs │ ├── src │ │ ├── util.rs │ │ └── lib.rs │ └── Cargo.lock ├── json-formatter │ ├── .gitignore │ ├── rustfmt.toml │ ├── Cargo.toml │ ├── Cargo.lock │ └── src │ │ └── lib.rs └── notify-debouncer-full │ ├── rustfmt.toml │ ├── .gitignore │ ├── LICENSE-MIT │ ├── src │ ├── debounced_event.rs │ └── cache.rs │ └── Cargo.toml ├── rustfmt.toml ├── .github ├── FUNDING.yml ├── dependabot.yml └── workflows │ ├── ci.yml │ └── release.yml ├── scripts ├── pull └── release ├── .gitignore ├── src ├── server │ ├── home.rs │ ├── stop.rs │ ├── details.rs │ ├── snapshot.rs │ ├── unsubscribe.rs │ ├── write.rs │ ├── open.rs │ ├── read.rs │ ├── subscribe.rs │ ├── exec.rs │ └── mod.rs ├── core │ ├── helpers │ │ ├── mod.rs │ │ ├── migrations.rs │ │ └── syncback.rs │ ├── changes.rs │ ├── processor │ │ └── read.rs │ ├── tree.rs │ └── queue.rs ├── cli │ ├── doc.rs │ ├── studio.rs │ ├── update.rs │ ├── plugin.rs │ ├── debug.rs │ ├── exec.rs │ ├── stop.rs │ ├── sourcemap.rs │ ├── mod.rs │ ├── init.rs │ ├── config.rs │ └── serve.rs ├── middleware │ ├── rbxm.rs │ ├── rbxmx.rs │ ├── toml.rs │ ├── yaml.rs │ ├── md.rs │ ├── helpers │ │ ├── mod.rs │ │ ├── snapshot.rs │ │ ├── mesh_part.rs │ │ └── markdown.rs │ ├── json.rs │ ├── txt.rs │ ├── dir.rs │ ├── luau.rs │ ├── msgpack.rs │ ├── json_model.rs │ ├── csv.rs │ └── project.rs ├── lib.rs ├── glob.rs ├── main.rs ├── integration.rs ├── vfs │ ├── std_backend.rs │ ├── mod.rs │ └── mem_backend.rs ├── crash_handler.rs ├── studio.rs ├── ext.rs ├── stats.rs ├── util.rs └── sessions.rs ├── README.md └── Cargo.toml /assets/templates/quick/src/StarterGui/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /assets/templates/quick/src/StarterPack/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /assets/templates/quick/src/Workspace/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /assets/templates/quick/src/ReplicatedFirst/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /assets/templates/quick/src/ReplicatedStorage/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /assets/templates/quick/src/ServerStorage/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /crates/profiling/.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | .DS_Store 3 | -------------------------------------------------------------------------------- /crates/profiling/rustfmt.toml: -------------------------------------------------------------------------------- 1 | hard_tabs = true 2 | -------------------------------------------------------------------------------- /crates/self_update/rustfmt.toml: -------------------------------------------------------------------------------- 1 | hard_tabs = true 2 | -------------------------------------------------------------------------------- /rustfmt.toml: -------------------------------------------------------------------------------- 1 | hard_tabs = true 2 | max_width = 120 3 | -------------------------------------------------------------------------------- /assets/templates/quick/src/ServerScriptService/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /crates/config-derive/.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | .DS_Store 3 | -------------------------------------------------------------------------------- /crates/config-derive/rustfmt.toml: -------------------------------------------------------------------------------- 1 | hard_tabs = true 2 | -------------------------------------------------------------------------------- /crates/json-formatter/.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | .DS_Store 3 | -------------------------------------------------------------------------------- /crates/json-formatter/rustfmt.toml: -------------------------------------------------------------------------------- 1 | hard_tabs = true 2 | -------------------------------------------------------------------------------- /crates/self_update/.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | .DS_Store 3 | -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | github: DervexDev 2 | ko_fi: Dervex 3 | -------------------------------------------------------------------------------- /crates/notify-debouncer-full/rustfmt.toml: -------------------------------------------------------------------------------- 1 | hard_tabs = true 2 | -------------------------------------------------------------------------------- /crates/notify-debouncer-full/.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | .DS_Store 3 | -------------------------------------------------------------------------------- /assets/templates/quick/src/StarterPlayer/StarterCharacterScripts/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /assets/templates/quick/src/StarterPlayer/StarterPlayerScripts/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /scripts/pull: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | git pull 4 | git pull --tags --force 5 | -------------------------------------------------------------------------------- /assets/templates/plugin/src/.src.server.luau: -------------------------------------------------------------------------------- 1 | print('Hello world, from plugin!') 2 | -------------------------------------------------------------------------------- /assets/templates/place/src/Client/Main.client.luau: -------------------------------------------------------------------------------- 1 | print('Hello world, from client!') 2 | -------------------------------------------------------------------------------- /assets/templates/place/src/Server/Main.server.luau: -------------------------------------------------------------------------------- 1 | print('Hello world, from server!') 2 | -------------------------------------------------------------------------------- /assets/templates/empty/selene.toml: -------------------------------------------------------------------------------- 1 | std = "roblox" 2 | 3 | [lints] 4 | shadowing = "allow" 5 | -------------------------------------------------------------------------------- /assets/templates/model/LICENSE.md: -------------------------------------------------------------------------------- 1 | Licensed under $license license 2 | Copyright $year $owner 3 | -------------------------------------------------------------------------------- /assets/templates/model/selene.toml: -------------------------------------------------------------------------------- 1 | std = "roblox" 2 | 3 | [lints] 4 | shadowing = "allow" 5 | -------------------------------------------------------------------------------- /assets/templates/package/LICENSE.md: -------------------------------------------------------------------------------- 1 | Licensed under $license license 2 | Copyright $year $owner 3 | -------------------------------------------------------------------------------- /assets/templates/package/selene.toml: -------------------------------------------------------------------------------- 1 | std = "roblox" 2 | 3 | [lints] 4 | shadowing = "allow" 5 | -------------------------------------------------------------------------------- /assets/templates/place/selene.toml: -------------------------------------------------------------------------------- 1 | std = "roblox" 2 | 3 | [lints] 4 | shadowing = "allow" 5 | -------------------------------------------------------------------------------- /assets/templates/plugin/LICENSE.md: -------------------------------------------------------------------------------- 1 | Licensed under $license license 2 | Copyright $year $owner 3 | -------------------------------------------------------------------------------- /assets/templates/plugin/selene.toml: -------------------------------------------------------------------------------- 1 | std = "roblox" 2 | 3 | [lints] 4 | shadowing = "allow" 5 | -------------------------------------------------------------------------------- /assets/templates/quick/selene.toml: -------------------------------------------------------------------------------- 1 | std = "roblox" 2 | 3 | [lints] 4 | shadowing = "allow" 5 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | /target 3 | /sourcemap.json 4 | *.rbxm 5 | *.rbxmx 6 | *.rbxl 7 | *.rbxlx 8 | -------------------------------------------------------------------------------- /assets/templates/package/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # $name Changelog 2 | 3 | ## Unreleased Changes 4 | 5 | - 6 | -------------------------------------------------------------------------------- /assets/templates/place/src/Shared/Hello.luau: -------------------------------------------------------------------------------- 1 | return function() 2 | print('Hello, world!') 3 | end 4 | -------------------------------------------------------------------------------- /assets/templates/plugin/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # $name Changelog 2 | 3 | ## Unreleased Changes 4 | 5 | - 6 | -------------------------------------------------------------------------------- /crates/profiling/Cargo.toml: -------------------------------------------------------------------------------- 1 | [workspace] 2 | members = ["profiling", "profiling-procmacros", "."] 3 | -------------------------------------------------------------------------------- /assets/templates/model/src/.src.luau: -------------------------------------------------------------------------------- 1 | return function() 2 | print('Hello world, from model!') 3 | end 4 | -------------------------------------------------------------------------------- /assets/templates/package/src/init.luau: -------------------------------------------------------------------------------- 1 | return { 2 | hello = function() 3 | print('Hello world, from package!') 4 | end, 5 | } 6 | -------------------------------------------------------------------------------- /crates/self_update/build.rs: -------------------------------------------------------------------------------- 1 | fn main() { 2 | println!( 3 | "cargo:rustc-env=TARGET={}", 4 | std::env::var("TARGET").unwrap() 5 | ); 6 | } 7 | -------------------------------------------------------------------------------- /assets/templates/model/project.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "$name", 3 | "tree": { 4 | "$path": "src", 5 | "Packages": { 6 | "$path": "Packages" 7 | } 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /assets/templates/plugin/project.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "$name", 3 | "tree": { 4 | "$path": "src", 5 | "Packages": { 6 | "$path": "Packages" 7 | } 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /assets/templates/package/project.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "$name", 3 | "tree": { 4 | "$path": "src", 5 | "DevPackages": { 6 | "$path": "DevPackages" 7 | } 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /assets/templates/empty/project.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "$name", 3 | "tree": { 4 | "$className": "DataModel", 5 | "Packages": { 6 | "$path": "Packages" 7 | } 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /assets/templates/model/README.md: -------------------------------------------------------------------------------- 1 | # $name 2 | 3 | Model template, generated by Argon 4 | 5 | ## Getting Started 6 | 7 | To build the model use: 8 | 9 | ```bash 10 | argon build 11 | ``` 12 | -------------------------------------------------------------------------------- /assets/templates/model/wally.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "$author/$name" 3 | version = "0.1.0" 4 | registry = "https://github.com/UpliftGames/wally-index" 5 | realm = "shared" 6 | 7 | [dependencies] 8 | -------------------------------------------------------------------------------- /assets/templates/package/README.md: -------------------------------------------------------------------------------- 1 | # $name 2 | 3 | Package template, generated by Argon 4 | 5 | ## Getting Started 6 | 7 | To build the package use: 8 | 9 | ```bash 10 | argon build 11 | ``` 12 | -------------------------------------------------------------------------------- /assets/templates/empty/wally.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "$author/$name" 3 | version = "0.1.0" 4 | registry = "https://github.com/UpliftGames/wally-index" 5 | realm = "shared" 6 | private = true 7 | 8 | [dependencies] 9 | -------------------------------------------------------------------------------- /assets/templates/plugin/wally.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "$author/$name" 3 | version = "0.1.0" 4 | registry = "https://github.com/UpliftGames/wally-index" 5 | realm = "shared" 6 | private = true 7 | 8 | [dependencies] 9 | -------------------------------------------------------------------------------- /src/server/home.rs: -------------------------------------------------------------------------------- 1 | use actix_web::{get, HttpResponse, Responder}; 2 | use log::trace; 3 | 4 | #[get("/")] 5 | async fn main() -> impl Responder { 6 | trace!("Received request: home"); 7 | HttpResponse::Ok().body("Home Page") 8 | } 9 | -------------------------------------------------------------------------------- /assets/templates/plugin/README.md: -------------------------------------------------------------------------------- 1 | # $name 2 | 3 | Plugin template, generated by Argon 4 | 5 | ## Getting Started 6 | 7 | To build the plugin and then import to Roblox Studio use: 8 | 9 | ```bash 10 | argon build -p 11 | ``` 12 | -------------------------------------------------------------------------------- /src/core/helpers/mod.rs: -------------------------------------------------------------------------------- 1 | use crate::Properties; 2 | 3 | mod migrations; 4 | 5 | pub mod syncback; 6 | 7 | #[inline] 8 | pub fn apply_migrations(class: &str, properties: &mut Properties) { 9 | migrations::apply(class, properties); 10 | } 11 | -------------------------------------------------------------------------------- /assets/templates/place/wally.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "$author/$name" 3 | version = "0.1.0" 4 | registry = "https://github.com/UpliftGames/wally-index" 5 | realm = "shared" 6 | private = true 7 | 8 | [dependencies] 9 | 10 | [server-dependencies] 11 | -------------------------------------------------------------------------------- /assets/templates/quick/wally.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "$author/$name" 3 | version = "0.1.0" 4 | registry = "https://github.com/UpliftGames/wally-index" 5 | realm = "shared" 6 | private = true 7 | 8 | [dependencies] 9 | 10 | [server-dependencies] 11 | -------------------------------------------------------------------------------- /assets/templates/empty/.gitignore: -------------------------------------------------------------------------------- 1 | # Argon 2 | /sourcemap.json 3 | 4 | # Wally 5 | /Packages 6 | /ServerPackages 7 | /DevPackages 8 | 9 | # Artifacts 10 | /*.lock 11 | /*.rbxl 12 | /*.rbxlx 13 | /*.rbxm 14 | /*.rbxmx 15 | 16 | # Misc 17 | .DS_Store 18 | -------------------------------------------------------------------------------- /assets/templates/model/.gitignore: -------------------------------------------------------------------------------- 1 | # Argon 2 | /sourcemap.json 3 | 4 | # Wally 5 | /Packages 6 | /ServerPackages 7 | /DevPackages 8 | 9 | # Artifacts 10 | /*.lock 11 | /*.rbxl 12 | /*.rbxlx 13 | /*.rbxm 14 | /*.rbxmx 15 | 16 | # Misc 17 | .DS_Store 18 | -------------------------------------------------------------------------------- /assets/templates/package/.gitignore: -------------------------------------------------------------------------------- 1 | # Argon 2 | /sourcemap.json 3 | 4 | # Wally 5 | /Packages 6 | /ServerPackages 7 | /DevPackages 8 | 9 | # Artifacts 10 | /*.lock 11 | /*.rbxl 12 | /*.rbxlx 13 | /*.rbxm 14 | /*.rbxmx 15 | 16 | # Misc 17 | .DS_Store 18 | -------------------------------------------------------------------------------- /assets/templates/place/.gitignore: -------------------------------------------------------------------------------- 1 | # Argon 2 | /sourcemap.json 3 | 4 | # Wally 5 | /Packages 6 | /ServerPackages 7 | /DevPackages 8 | 9 | # Artifacts 10 | /*.lock 11 | /*.rbxl 12 | /*.rbxlx 13 | /*.rbxm 14 | /*.rbxmx 15 | 16 | # Misc 17 | .DS_Store 18 | -------------------------------------------------------------------------------- /assets/templates/plugin/.gitignore: -------------------------------------------------------------------------------- 1 | # Argon 2 | /sourcemap.json 3 | 4 | # Wally 5 | /Packages 6 | /ServerPackages 7 | /DevPackages 8 | 9 | # Artifacts 10 | /*.lock 11 | /*.rbxl 12 | /*.rbxlx 13 | /*.rbxm 14 | /*.rbxmx 15 | 16 | # Misc 17 | .DS_Store 18 | -------------------------------------------------------------------------------- /assets/templates/quick/.gitignore: -------------------------------------------------------------------------------- 1 | # Argon 2 | /sourcemap.json 3 | 4 | # Wally 5 | /Packages 6 | /ServerPackages 7 | /DevPackages 8 | 9 | # Artifacts 10 | /*.lock 11 | /*.rbxl 12 | /*.rbxlx 13 | /*.rbxm 14 | /*.rbxmx 15 | 16 | # Misc 17 | .DS_Store 18 | -------------------------------------------------------------------------------- /crates/profiling/profiling/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "profiling" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | [dependencies] 7 | profiling-procmacros = { path = "../profiling-procmacros" } 8 | 9 | [dev-dependencies] 10 | puffin_http = "0.16.0" 11 | puffin = "0.19.0" 12 | -------------------------------------------------------------------------------- /crates/json-formatter/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "json-formatter" 3 | authors = ["Dervex"] 4 | description = "Custom serde JSON formatter" 5 | license = "Apache-2.0" 6 | version = "0.1.0" 7 | edition = "2021" 8 | 9 | [dependencies] 10 | ryu = "1.0.20" 11 | serde_json = "1.0.117" 12 | -------------------------------------------------------------------------------- /crates/profiling/profiling-procmacros/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "profiling-procmacros" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | [lib] 7 | proc-macro = true 8 | 9 | [dependencies] 10 | syn = { version = "2.0.48", features = ["full"] } 11 | proc-macro2 = "1.0.76" 12 | quote = "1.0.35" 13 | -------------------------------------------------------------------------------- /assets/templates/package/wally.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "$author/$name" 3 | description = "Enter your description here, generated by Argon" 4 | version = "0.1.0" 5 | license = "$license" 6 | authors = ["$author"] 7 | registry = "https://github.com/UpliftGames/wally-index" 8 | realm = "shared" 9 | 10 | [dev-dependencies] 11 | -------------------------------------------------------------------------------- /assets/templates/place/README.md: -------------------------------------------------------------------------------- 1 | # $name 2 | 3 | Game template, generated by Argon 4 | 5 | ## Getting Started 6 | 7 | To build the place in the project root use: 8 | 9 | ```bash 10 | argon build 11 | ``` 12 | 13 | To begin syncing open Roblox Studio and start Argon server using: 14 | 15 | ```bash 16 | argon serve 17 | ``` 18 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: "cargo" 4 | directory: "/" 5 | schedule: 6 | interval: "quarterly" 7 | time: "06:00" 8 | open-pull-requests-limit: 40 9 | ignore: 10 | - dependency-name: "*" 11 | update-types: 12 | - "version-update:semver-major" 13 | -------------------------------------------------------------------------------- /src/server/stop.rs: -------------------------------------------------------------------------------- 1 | use actix_web::{post, HttpResponse, Responder}; 2 | use log::{info, trace}; 3 | use std::process; 4 | 5 | use crate::util; 6 | 7 | #[post("/stop")] 8 | async fn main() -> impl Responder { 9 | trace!("Received request: stop"); 10 | info!("Stopping Argon!"); 11 | 12 | util::kill_process(process::id()); 13 | 14 | HttpResponse::Ok().body("Argon stopped successfully") 15 | } 16 | -------------------------------------------------------------------------------- /crates/profiling/profiling/src/lib.rs: -------------------------------------------------------------------------------- 1 | pub use ::profiling_procmacros::function; 2 | 3 | #[macro_export] 4 | macro_rules! scope { 5 | ($name:expr) => { 6 | puffin::profile_scope!($name); 7 | }; 8 | ($name:expr, $data:expr) => { 9 | puffin::profile_scope!($name, $data); 10 | }; 11 | } 12 | 13 | #[macro_export] 14 | macro_rules! start_frame { 15 | () => { 16 | puffin::GlobalProfiler::lock().new_frame(); 17 | }; 18 | } 19 | -------------------------------------------------------------------------------- /crates/config-derive/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "config-derive" 3 | authors = ["Dervex"] 4 | description = "Macros to speed up your Config struct processing" 5 | license = "Apache-2.0" 6 | version = "0.1.0" 7 | edition = "2021" 8 | 9 | [lib] 10 | proc-macro = true 11 | 12 | [dependencies] 13 | proc-macro2 = "1.0.76" 14 | quote = "1.0.35" 15 | syn = "2.0.48" 16 | 17 | [dev-dependencies] 18 | serde = { version = "1.0.189", features = ["derive"] } 19 | -------------------------------------------------------------------------------- /src/server/details.rs: -------------------------------------------------------------------------------- 1 | use actix_msgpack::MsgPackResponseBuilder; 2 | use actix_web::{get, web::Data, HttpResponse, Responder}; 3 | use log::trace; 4 | use std::sync::Arc; 5 | 6 | use crate::{core::Core, project::ProjectDetails}; 7 | 8 | #[get("/details")] 9 | async fn main(core: Data>) -> impl Responder { 10 | trace!("Received request: details"); 11 | HttpResponse::Ok().msgpack(ProjectDetails::from_project(&core.project(), &core.tree())) 12 | } 13 | -------------------------------------------------------------------------------- /src/cli/doc.rs: -------------------------------------------------------------------------------- 1 | use anyhow::Result; 2 | use clap::Parser; 3 | use colored::Colorize; 4 | 5 | use crate::argon_info; 6 | 7 | const LINK: &str = "https://argon.wiki"; 8 | 9 | /// Open Argon's documentation in the browser 10 | #[derive(Parser)] 11 | pub struct Doc {} 12 | 13 | impl Doc { 14 | pub fn main(self) -> Result<()> { 15 | argon_info!("Launched browser. Manually go to: {}", LINK.bold()); 16 | 17 | open::that(LINK)?; 18 | 19 | Ok(()) 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /assets/templates/place/project.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "$name", 3 | "tree": { 4 | "$className": "DataModel", 5 | "ReplicatedStorage": { 6 | "$path": "src/Shared", 7 | "Packages": { 8 | "$path": "Packages" 9 | } 10 | }, 11 | "ServerScriptService": { 12 | "$path": "src/Server", 13 | "ServerPackages": { 14 | "$path": "ServerPackages" 15 | } 16 | }, 17 | "StarterPlayer": { 18 | "StarterPlayerScripts": { 19 | "$path": "src/Client" 20 | } 21 | } 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/middleware/rbxm.rs: -------------------------------------------------------------------------------- 1 | use anyhow::Result; 2 | use std::path::Path; 3 | 4 | use super::helpers; 5 | use crate::{core::snapshot::Snapshot, vfs::Vfs}; 6 | 7 | #[profiling::function] 8 | pub fn read_rbxm(path: &Path, vfs: &Vfs) -> Result { 9 | let dom = rbx_binary::from_reader(vfs.read(path)?.as_slice())?; 10 | 11 | let snapshot = if dom.root().children().len() == 1 { 12 | let id = dom.root().children()[0]; 13 | helpers::snapshot_from_dom(dom, id) 14 | } else { 15 | let id = dom.root_ref(); 16 | helpers::snapshot_from_dom(dom, id).with_class("Folder") 17 | }; 18 | 19 | Ok(snapshot) 20 | } 21 | -------------------------------------------------------------------------------- /src/middleware/rbxmx.rs: -------------------------------------------------------------------------------- 1 | use anyhow::Result; 2 | use std::path::Path; 3 | 4 | use super::helpers; 5 | use crate::{core::snapshot::Snapshot, vfs::Vfs}; 6 | 7 | #[profiling::function] 8 | pub fn read_rbxmx(path: &Path, vfs: &Vfs) -> Result { 9 | let dom = rbx_xml::from_reader_default(vfs.read(path)?.as_slice())?; 10 | 11 | let snapshot = if dom.root().children().len() == 1 { 12 | let id = dom.root().children()[0]; 13 | helpers::snapshot_from_dom(dom, id) 14 | } else { 15 | let id = dom.root_ref(); 16 | helpers::snapshot_from_dom(dom, id).with_class("Folder") 17 | }; 18 | 19 | Ok(snapshot) 20 | } 21 | -------------------------------------------------------------------------------- /src/middleware/toml.rs: -------------------------------------------------------------------------------- 1 | use anyhow::Result; 2 | use rbx_dom_weak::{types::Variant, ustr, HashMapExt, UstrMap}; 3 | use std::path::Path; 4 | 5 | use crate::{core::snapshot::Snapshot, vfs::Vfs}; 6 | 7 | #[profiling::function] 8 | pub fn read_toml(path: &Path, vfs: &Vfs) -> Result { 9 | let toml = vfs.read_to_string(path)?; 10 | let lua = toml2lua::parse(&toml)?; 11 | 12 | let source = format!("return {lua}"); 13 | 14 | let mut properties = UstrMap::new(); 15 | properties.insert(ustr("Source"), Variant::String(source)); 16 | 17 | Ok(Snapshot::new().with_class("ModuleScript").with_properties(properties)) 18 | } 19 | -------------------------------------------------------------------------------- /src/middleware/yaml.rs: -------------------------------------------------------------------------------- 1 | use anyhow::Result; 2 | use rbx_dom_weak::{types::Variant, ustr, HashMapExt, UstrMap}; 3 | use std::path::Path; 4 | 5 | use crate::{core::snapshot::Snapshot, vfs::Vfs}; 6 | 7 | #[profiling::function] 8 | pub fn read_yaml(path: &Path, vfs: &Vfs) -> Result { 9 | let yaml = vfs.read_to_string(path)?; 10 | let lua = yaml2lua::parse(&yaml)?; 11 | 12 | let source = format!("return {lua}"); 13 | 14 | let mut properties = UstrMap::new(); 15 | properties.insert(ustr("Source"), Variant::String(source)); 16 | 17 | Ok(Snapshot::new().with_class("ModuleScript").with_properties(properties)) 18 | } 19 | -------------------------------------------------------------------------------- /src/middleware/md.rs: -------------------------------------------------------------------------------- 1 | use anyhow::Result; 2 | use rbx_dom_weak::{types::Variant, ustr, HashMapExt, UstrMap}; 3 | use std::path::Path; 4 | 5 | use crate::{core::snapshot::Snapshot, middleware::helpers::markdown_to_rich_text, vfs::Vfs}; 6 | 7 | #[profiling::function] 8 | pub fn read_md(path: &Path, vfs: &Vfs) -> Result { 9 | let markdown = vfs.read_to_string(path)?; 10 | let rich_text = markdown_to_rich_text(&markdown); 11 | 12 | let mut properties = UstrMap::new(); 13 | properties.insert(ustr("Value"), Variant::String(rich_text)); 14 | 15 | Ok(Snapshot::new().with_class("StringValue").with_properties(properties)) 16 | } 17 | -------------------------------------------------------------------------------- /src/server/snapshot.rs: -------------------------------------------------------------------------------- 1 | use actix_msgpack::{MsgPack, MsgPackResponseBuilder}; 2 | use actix_web::{post, web::Data, HttpResponse, Responder}; 3 | use log::trace; 4 | use rbx_dom_weak::types::Ref; 5 | use serde::Deserialize; 6 | use std::sync::Arc; 7 | 8 | use crate::core::Core; 9 | 10 | #[derive(Deserialize, Debug)] 11 | #[serde(rename_all = "camelCase")] 12 | struct Request { 13 | instance: Ref, 14 | } 15 | 16 | #[post("/snapshot")] 17 | async fn main(request: MsgPack, core: Data>) -> impl Responder { 18 | trace!("Received request: snapshot"); 19 | HttpResponse::Ok().msgpack(core.snapshot(request.instance)) 20 | } 21 | -------------------------------------------------------------------------------- /src/server/unsubscribe.rs: -------------------------------------------------------------------------------- 1 | use actix_msgpack::MsgPack; 2 | use actix_web::{post, web::Data, HttpResponse, Responder}; 3 | use log::trace; 4 | use std::sync::Arc; 5 | 6 | use crate::{core::Core, server::AuthRequest}; 7 | 8 | #[post("/unsubscribe")] 9 | async fn main(request: MsgPack, core: Data>) -> impl Responder { 10 | trace!("Received request: unsubscribe"); 11 | 12 | let unsubscribed = core.queue().unsubscribe(request.client_id); 13 | 14 | if unsubscribed.is_ok() { 15 | HttpResponse::Ok().body("Unsubscribed successfully") 16 | } else { 17 | HttpResponse::BadRequest().body("Not subscribed") 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/server/write.rs: -------------------------------------------------------------------------------- 1 | use actix_msgpack::MsgPack; 2 | use actix_web::{post, web::Data, HttpResponse, Responder}; 3 | use log::trace; 4 | use std::sync::Arc; 5 | 6 | use crate::core::{processor::WriteRequest, Core}; 7 | 8 | #[post("/write")] 9 | async fn main(request: MsgPack, core: Data>) -> impl Responder { 10 | trace!("Received request: write"); 11 | 12 | let request = request.0; 13 | 14 | if !core.queue().is_subscribed(request.client_id) { 15 | return HttpResponse::Unauthorized().body("Not subscribed"); 16 | } 17 | 18 | core.processor().write(request); 19 | 20 | HttpResponse::Ok().body("Written changes successfully") 21 | } 22 | -------------------------------------------------------------------------------- /scripts/release: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | bold=$(tput bold) 4 | normal=$(tput sgr0) 5 | 6 | current=$(curl https://api.github.com/repos/argon-rbx/argon/releases/latest -s | grep -i "tag_name" | awk -F '"' '{print $4}') 7 | 8 | echo $current | tr -d '\n' | pbcopy 2> /dev/null 9 | 10 | echo Current Argon version is: ${bold}$current${normal} 11 | echo 12 | 13 | read -p "Enter a new version to release: " version 14 | echo 15 | 16 | read -p "Is this version correct: ${bold}$version${normal} [y/n] " confirm 17 | echo 18 | 19 | if [ "$confirm" != "y" ]; then 20 | echo Aborted! 21 | exit 1 22 | fi 23 | 24 | echo Releasing version ${bold}$version${normal} ... 25 | 26 | git tag $version && git push --tags 27 | -------------------------------------------------------------------------------- /src/middleware/helpers/mod.rs: -------------------------------------------------------------------------------- 1 | use rbx_dom_weak::{types::Ref, WeakDom}; 2 | 3 | use crate::{ 4 | core::{helpers::apply_migrations, snapshot::Snapshot}, 5 | Properties, 6 | }; 7 | 8 | mod markdown; 9 | mod mesh_part; 10 | mod snapshot; 11 | 12 | #[inline] 13 | pub fn save_mesh(properties: &Properties) -> Option { 14 | let mut properties = properties.clone(); 15 | apply_migrations("MeshPart", &mut properties); 16 | 17 | mesh_part::save_mesh(&properties) 18 | } 19 | 20 | #[inline] 21 | pub fn snapshot_from_dom(dom: WeakDom, id: Ref) -> Snapshot { 22 | snapshot::snapshot_from_dom(dom, id) 23 | } 24 | 25 | #[inline] 26 | pub fn markdown_to_rich_text(text: &str) -> String { 27 | markdown::parse(text) 28 | } 29 | -------------------------------------------------------------------------------- /src/middleware/json.rs: -------------------------------------------------------------------------------- 1 | use anyhow::Result; 2 | use rbx_dom_weak::{types::Variant, ustr, HashMapExt, UstrMap}; 3 | use std::path::Path; 4 | 5 | use crate::{core::snapshot::Snapshot, vfs::Vfs}; 6 | 7 | #[profiling::function] 8 | pub fn read_json(path: &Path, vfs: &Vfs) -> Result { 9 | let json = vfs.read_to_string(path)?; 10 | 11 | if json.is_empty() { 12 | return Ok(Snapshot::new().with_class("ModuleScript")); 13 | } 14 | 15 | let lua = json2lua::parse(&json)?; 16 | 17 | let source = format!("return {lua}"); 18 | 19 | let mut properties = UstrMap::new(); 20 | properties.insert(ustr("Source"), Variant::String(source)); 21 | 22 | Ok(Snapshot::new().with_class("ModuleScript").with_properties(properties)) 23 | } 24 | -------------------------------------------------------------------------------- /crates/profiling/profiling-procmacros/src/lib.rs: -------------------------------------------------------------------------------- 1 | use proc_macro::TokenStream; 2 | use quote::{quote, ToTokens}; 3 | use syn::{parse, parse_macro_input, Item, LitStr}; 4 | 5 | #[proc_macro_attribute] 6 | pub fn function(attr: TokenStream, item: TokenStream) -> TokenStream { 7 | let mut item = parse_macro_input!(item as Item); 8 | 9 | let item_fn = match &mut item { 10 | Item::Fn(item_fn) => item_fn, 11 | _ => panic!("expected function"), 12 | }; 13 | 14 | let data = parse_macro_input!(attr as Option); 15 | let puffin_macro = quote! { 16 | puffin::profile_function!(#data); 17 | }; 18 | 19 | item_fn 20 | .block 21 | .stmts 22 | .insert(0, parse(puffin_macro.into()).unwrap()); 23 | 24 | item.into_token_stream().into() 25 | } 26 | -------------------------------------------------------------------------------- /src/server/open.rs: -------------------------------------------------------------------------------- 1 | use actix_msgpack::MsgPack; 2 | use actix_web::{post, web::Data, HttpResponse, Responder}; 3 | use log::trace; 4 | use rbx_dom_weak::types::Ref; 5 | use serde::Deserialize; 6 | use std::sync::Arc; 7 | 8 | use crate::core::Core; 9 | 10 | #[derive(Deserialize, Debug)] 11 | #[serde(rename_all = "camelCase")] 12 | struct Request { 13 | instance: Ref, 14 | _line: u32, 15 | } 16 | 17 | #[post("/open")] 18 | async fn main(request: MsgPack, core: Data>) -> impl Responder { 19 | trace!("Received request: open"); 20 | 21 | match core.open(request.instance) { 22 | Ok(_) => HttpResponse::Ok().body("Opened file successfully"), 23 | Err(err) => HttpResponse::InternalServerError().body(err.to_string()), 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/server/read.rs: -------------------------------------------------------------------------------- 1 | use actix_msgpack::{MsgPack, MsgPackResponseBuilder}; 2 | use actix_web::{post, web::Data, HttpResponse, Responder}; 3 | use log::trace; 4 | use std::sync::Arc; 5 | 6 | use crate::{core::Core, server::AuthRequest}; 7 | 8 | #[post("/read")] 9 | async fn main(request: MsgPack, core: Data>) -> impl Responder { 10 | trace!("Received request: read"); 11 | 12 | let id = request.client_id; 13 | let queue = core.queue(); 14 | 15 | if !queue.is_subscribed(id) { 16 | return HttpResponse::Unauthorized().body("Not subscribed"); 17 | } 18 | 19 | match queue.get_timeout(id) { 20 | Ok(message) => HttpResponse::Ok().msgpack(message), 21 | Err(err) => HttpResponse::InternalServerError().body(err.to_string()), 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /crates/config-derive/tests/mod.rs: -------------------------------------------------------------------------------- 1 | #[cfg(test)] 2 | mod tests { 3 | use config_derive::{Get, Iter, Set, Val}; 4 | use serde::{Deserialize, Serialize}; 5 | 6 | #[test] 7 | fn it_works() { 8 | #[derive(Debug, Val, Iter, Get, Set)] 9 | struct Test { 10 | a: u16, 11 | b: i32, 12 | c: String, 13 | d: bool, 14 | e: String, 15 | } 16 | 17 | let mut test = Test { 18 | a: 1, 19 | b: 2, 20 | c: "hello".to_string(), 21 | d: true, 22 | e: "world".to_string(), 23 | }; 24 | 25 | println!("{:?}", test.get("c")); 26 | 27 | for (key, value) in &test { 28 | println!("{}: {:?}", key, value); 29 | } 30 | 31 | test.set("c", "goodbye").unwrap(); 32 | test.set("d", "false").unwrap(); 33 | 34 | println!("{:?}", test); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/server/subscribe.rs: -------------------------------------------------------------------------------- 1 | use actix_msgpack::MsgPack; 2 | use actix_web::{post, web::Data, HttpResponse, Responder}; 3 | use log::trace; 4 | use serde::Deserialize; 5 | use std::sync::Arc; 6 | 7 | use crate::core::Core; 8 | 9 | #[derive(Deserialize, Debug)] 10 | #[serde(rename_all = "camelCase")] 11 | struct Request { 12 | client_id: u32, 13 | name: String, 14 | } 15 | 16 | #[post("/subscribe")] 17 | async fn main(request: MsgPack, core: Data>) -> impl Responder { 18 | trace!("Received request: subscribe"); 19 | 20 | let subscribed = core.queue().subscribe(request.client_id, &request.name); 21 | 22 | if subscribed.is_ok() { 23 | HttpResponse::Ok().body("Subscribed successfully") 24 | } else { 25 | HttpResponse::BadRequest().body("Already subscribed") 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | #![allow(clippy::new_without_default)] 2 | 3 | use rbx_dom_weak::{types::Variant, UstrMap}; 4 | 5 | pub mod cli; 6 | pub mod config; 7 | pub mod constants; 8 | pub mod core; 9 | pub mod crash_handler; 10 | pub mod ext; 11 | pub mod glob; 12 | pub mod installer; 13 | pub mod integration; 14 | pub mod logger; 15 | pub mod middleware; 16 | pub mod program; 17 | pub mod project; 18 | pub mod resolution; 19 | pub mod server; 20 | pub mod sessions; 21 | pub mod stats; 22 | pub mod studio; 23 | pub mod updater; 24 | pub mod util; 25 | pub mod vfs; 26 | pub mod workspace; 27 | 28 | /// Global type for snapshot and instance properties 29 | pub type Properties = UstrMap; 30 | 31 | /// A shorter way to lock the Mutex 32 | #[macro_export] 33 | macro_rules! lock { 34 | ($mutex:expr) => { 35 | $mutex.lock().expect("Tried to lock Mutex that panicked!") 36 | }; 37 | } 38 | -------------------------------------------------------------------------------- /src/middleware/txt.rs: -------------------------------------------------------------------------------- 1 | use anyhow::Result; 2 | use rbx_dom_weak::{types::Variant, ustr, HashMapExt, UstrMap}; 3 | use std::path::Path; 4 | 5 | use crate::{core::snapshot::Snapshot, vfs::Vfs, Properties}; 6 | 7 | #[profiling::function] 8 | pub fn read_txt(path: &Path, vfs: &Vfs) -> Result { 9 | let value = vfs.read_to_string(path)?; 10 | 11 | let mut properties = UstrMap::new(); 12 | properties.insert(ustr("Value"), Variant::String(value)); 13 | 14 | Ok(Snapshot::new().with_class("StringValue").with_properties(properties)) 15 | } 16 | 17 | #[profiling::function] 18 | pub fn write_txt(mut properties: Properties, path: &Path, vfs: &Vfs) -> Result { 19 | let value = if let Some(Variant::String(value)) = properties.remove(&ustr("Value")) { 20 | value 21 | } else { 22 | String::new() 23 | }; 24 | 25 | vfs.write(path, value.as_bytes())?; 26 | 27 | Ok(properties) 28 | } 29 | -------------------------------------------------------------------------------- /src/middleware/dir.rs: -------------------------------------------------------------------------------- 1 | use anyhow::Result; 2 | use std::path::Path; 3 | 4 | use super::new_snapshot; 5 | use crate::{ 6 | core::{ 7 | meta::{Context, Meta, Source}, 8 | snapshot::Snapshot, 9 | }, 10 | ext::PathExt, 11 | vfs::Vfs, 12 | }; 13 | 14 | #[profiling::function] 15 | pub fn read_dir(path: &Path, context: &Context, vfs: &Vfs) -> Result { 16 | let name = path.get_name(); 17 | 18 | let mut snapshot = Snapshot::new() 19 | .with_name(name) 20 | .with_meta(Meta::new().with_context(context).with_source(Source::directory(path))); 21 | 22 | for path in vfs.read_dir(path)? { 23 | if let Some(child_snapshot) = new_snapshot(&path, context, vfs)? { 24 | snapshot.add_child(child_snapshot); 25 | } 26 | } 27 | 28 | Ok(snapshot) 29 | } 30 | 31 | #[profiling::function] 32 | pub fn write_dir(path: &Path, vfs: &Vfs) -> Result<()> { 33 | vfs.create_dir(path)?; 34 | 35 | Ok(()) 36 | } 37 | -------------------------------------------------------------------------------- /crates/profiling/profiling/tests/mod.rs: -------------------------------------------------------------------------------- 1 | #[cfg(test)] 2 | mod tests { 3 | use std::{thread::sleep, time::Duration}; 4 | 5 | #[test] 6 | fn it_works() { 7 | let _server = puffin_http::Server::new("localhost:8888").unwrap(); 8 | puffin::set_scopes_on(true); 9 | 10 | #[profiling::function] 11 | fn test() { 12 | let mut vec = vec![]; 13 | 14 | for i in 0..100 { 15 | profiling::scope!("scope1"); 16 | vec.push(i); 17 | 18 | for i in 0..200 { 19 | profiling::scope!("scope2", "with data"); 20 | vec.push(i); 21 | } 22 | } 23 | } 24 | 25 | #[profiling::function("with data")] 26 | fn test_data() { 27 | let mut vec = vec![]; 28 | 29 | for i in 0..100 { 30 | profiling::scope!("scope3", i.to_string()); 31 | vec.push(i); 32 | } 33 | } 34 | 35 | loop { 36 | profiling::start_frame!(); 37 | 38 | test(); 39 | test_data(); 40 | 41 | sleep(Duration::from_millis(100)); 42 | } 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | 8 | pull_request: 9 | branches: 10 | - main 11 | 12 | env: 13 | RUSTFLAGS: "-D warnings" 14 | 15 | jobs: 16 | build-test: 17 | name: Build and Test 18 | runs-on: ${{ matrix.os }} 19 | 20 | strategy: 21 | matrix: 22 | os: [ubuntu-latest, macos-latest, windows-latest] 23 | 24 | steps: 25 | - uses: actions/checkout@v4 26 | 27 | - name: Cache 28 | uses: Swatinem/rust-cache@v2 29 | 30 | - name: Build 31 | run: cargo build --verbose 32 | 33 | - name: Test 34 | run: cargo test --verbose 35 | 36 | lint-format: 37 | name: Lint and Format 38 | runs-on: ubuntu-latest 39 | 40 | steps: 41 | - uses: actions/checkout@v4 42 | 43 | - name: Cache 44 | uses: Swatinem/rust-cache@v2 45 | 46 | - name: Lint 47 | run: cargo clippy 48 | 49 | - name: Format 50 | run: cargo fmt -- --check 51 | -------------------------------------------------------------------------------- /assets/templates/quick/project.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "$name", 3 | "tree": { 4 | "$className": "DataModel", 5 | "Workspace": { 6 | "$path": "src/Workspace" 7 | }, 8 | "ReplicatedFirst": { 9 | "$path": "src/ReplicatedFirst" 10 | }, 11 | "ReplicatedStorage": { 12 | "$path": "src/ReplicatedStorage", 13 | "Packages": { 14 | "$path": "Packages" 15 | } 16 | }, 17 | "ServerScriptService": { 18 | "$path": "src/ServerScriptService", 19 | "ServerPackages": { 20 | "$path": "ServerPackages" 21 | } 22 | }, 23 | "ServerStorage": { 24 | "$path": "src/ServerStorage" 25 | }, 26 | "StarterGui": { 27 | "$path": "src/StarterGui" 28 | }, 29 | "StarterPack": { 30 | "$path": "src/StarterPack" 31 | }, 32 | "StarterPlayer": { 33 | "StarterCharacterScripts": { 34 | "$path": "src/StarterPlayer/StarterCharacterScripts" 35 | }, 36 | "StarterPlayerScripts": { 37 | "$path": "src/StarterPlayer/StarterPlayerScripts" 38 | } 39 | } 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/middleware/helpers/snapshot.rs: -------------------------------------------------------------------------------- 1 | use rbx_dom_weak::{types::Ref, AHashMap, Instance, WeakDom}; 2 | 3 | use crate::core::{meta::Meta, snapshot::Snapshot}; 4 | 5 | // Based on Rojo's InstanceSnapshot::from_tree (https://github.com/rojo-rbx/rojo/blob/master/src/snapshot/instance_snapshot.rs#L105) 6 | pub fn snapshot_from_dom(dom: WeakDom, id: Ref) -> Snapshot { 7 | let (_, mut raw_dom) = dom.into_raw(); 8 | 9 | fn walk(id: Ref, raw_dom: &mut AHashMap) -> Snapshot { 10 | let instance = raw_dom 11 | .remove(&id) 12 | .expect("Provided ID does not exist in the current DOM"); 13 | 14 | let children = instance 15 | .children() 16 | .iter() 17 | .map(|&child_id| walk(child_id, raw_dom)) 18 | .collect(); 19 | 20 | let mut meta = Meta::new(); 21 | 22 | if instance.class == "MeshPart" { 23 | meta.set_mesh_source(super::save_mesh(&instance.properties)); 24 | } 25 | 26 | Snapshot::new() 27 | .with_meta(meta) 28 | .with_name(&instance.name) 29 | .with_class(&instance.class) 30 | .with_properties(instance.properties) 31 | .with_children(children) 32 | } 33 | 34 | walk(id, &mut raw_dom) 35 | } 36 | -------------------------------------------------------------------------------- /src/server/exec.rs: -------------------------------------------------------------------------------- 1 | use actix_msgpack::MsgPack; 2 | use actix_web::{post, web::Data, HttpResponse, Responder}; 3 | use log::{error, trace}; 4 | use serde::Deserialize; 5 | use std::sync::Arc; 6 | 7 | use crate::{core::Core, server, studio}; 8 | 9 | #[derive(Deserialize, Debug)] 10 | #[serde(rename_all = "camelCase")] 11 | struct Request { 12 | code: String, 13 | focus: bool, 14 | } 15 | 16 | #[post("/exec")] 17 | async fn main(request: MsgPack, core: Data>) -> impl Responder { 18 | trace!("Received request: exec"); 19 | 20 | let queue = core.queue(); 21 | 22 | let pushed = queue.push( 23 | server::ExecuteCode { 24 | code: request.code.clone(), 25 | }, 26 | None, 27 | ); 28 | 29 | if request.focus { 30 | if let Some(name) = queue.get_first_non_internal_listener_name() { 31 | match studio::focus(Some(name)) { 32 | Ok(()) => (), 33 | Err(err) => error!("Failed to focus Roblox Studio: {err}"), 34 | } 35 | } 36 | } 37 | 38 | match pushed { 39 | Ok(()) => HttpResponse::Ok().body("Code executed successfully"), 40 | Err(err) => HttpResponse::InternalServerError().body(err.to_string()), 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /crates/self_update/LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 James Kominick 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /crates/notify-debouncer-full/LICENSE-MIT: -------------------------------------------------------------------------------- 1 | Copyright (c) 2023 Notify Contributors 2 | 3 | Permission is hereby granted, free of charge, to any 4 | person obtaining a copy of this software and associated 5 | documentation files (the "Software"), to deal in the 6 | Software without restriction, including without 7 | limitation the rights to use, copy, modify, merge, 8 | publish, distribute, sublicense, and/or sell copies of 9 | the Software, and to permit persons to whom the Software 10 | is furnished to do so, subject to the following 11 | conditions: 12 | 13 | The above copyright notice and this permission notice 14 | shall be included in all copies or substantial portions 15 | of the Software. 16 | 17 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF 18 | ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED 19 | TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A 20 | PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT 21 | SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 22 | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 23 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR 24 | IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 25 | DEALINGS IN THE SOFTWARE. 26 | -------------------------------------------------------------------------------- /src/cli/studio.rs: -------------------------------------------------------------------------------- 1 | use anyhow::Result; 2 | use clap::Parser; 3 | use std::path::PathBuf; 4 | 5 | use crate::{argon_info, config::Config, ext::PathExt, studio}; 6 | 7 | /// Launch a new Roblox Studio instance 8 | #[derive(Parser)] 9 | pub struct Studio { 10 | /// Path to place or model to open 11 | #[arg()] 12 | path: Option, 13 | 14 | /// Check if Roblox Studio is already running 15 | #[arg(short, long)] 16 | check: bool, 17 | } 18 | 19 | impl Studio { 20 | pub fn main(mut self) -> Result<()> { 21 | if self.check && studio::is_running(None)? { 22 | argon_info!("Roblox Studio is already running!"); 23 | return Ok(()); 24 | } 25 | 26 | argon_info!("Launching Roblox Studio.."); 27 | 28 | if let Some(path) = self.path.as_ref() { 29 | if Config::new().smart_paths && !path.exists() { 30 | let rbxl = path.with_file_name(path.get_name().to_owned() + ".rbxl"); 31 | let rbxlx = path.with_file_name(path.get_name().to_owned() + ".rbxlx"); 32 | 33 | if rbxl.exists() { 34 | self.path = Some(rbxl); 35 | } else if rbxlx.exists() { 36 | self.path = Some(rbxlx) 37 | } 38 | } 39 | } 40 | 41 | studio::launch(self.path)?; 42 | 43 | Ok(()) 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /crates/config-derive/src/util.rs: -------------------------------------------------------------------------------- 1 | use proc_macro2::{Ident, TokenTree}; 2 | use quote::ToTokens; 3 | use syn::{Data, Field, Fields, Type}; 4 | 5 | pub fn get_fields(data: &Data) -> Vec<&Field> { 6 | match data { 7 | Data::Struct(data) => match &data.fields { 8 | Fields::Named(named) => { 9 | let mut fields = vec![]; 10 | 11 | 'outer: for field in &named.named { 12 | match field.ident { 13 | Some(_) => { 14 | if let Some(attr) = field.attrs.first() { 15 | for tree in attr.meta.to_token_stream() { 16 | if let TokenTree::Ident(ident) = tree { 17 | if ident == "serde" { 18 | continue 'outer; 19 | } 20 | } 21 | } 22 | } 23 | 24 | fields.push(field); 25 | } 26 | None => unimplemented!("Tuples are not supported"), 27 | } 28 | } 29 | 30 | fields 31 | } 32 | _ => unimplemented!("Only named fields are supported"), 33 | }, 34 | _ => { 35 | unimplemented!("Only flat structs are supported") 36 | } 37 | } 38 | } 39 | 40 | pub fn get_type_ident(ty: &Type) -> Option { 41 | for tree in ty.to_token_stream() { 42 | if let TokenTree::Ident(ident) = tree { 43 | return Some(ident); 44 | } 45 | } 46 | 47 | None 48 | } 49 | -------------------------------------------------------------------------------- /crates/notify-debouncer-full/src/debounced_event.rs: -------------------------------------------------------------------------------- 1 | use std::ops::{Deref, DerefMut}; 2 | 3 | #[cfg(test)] 4 | use mock_instant::Instant; 5 | 6 | #[cfg(not(test))] 7 | use std::time::Instant; 8 | 9 | use notify::Event; 10 | 11 | /// A debounced event is emitted after a short delay. 12 | #[derive(Debug, Clone, PartialEq, Eq)] 13 | pub struct DebouncedEvent { 14 | /// The original event. 15 | pub event: Event, 16 | 17 | /// The time at which the event occurred. 18 | pub time: Instant, 19 | } 20 | 21 | impl DebouncedEvent { 22 | pub fn new(event: Event, time: Instant) -> Self { 23 | Self { event, time } 24 | } 25 | } 26 | 27 | impl Deref for DebouncedEvent { 28 | type Target = Event; 29 | 30 | fn deref(&self) -> &Self::Target { 31 | &self.event 32 | } 33 | } 34 | 35 | impl DerefMut for DebouncedEvent { 36 | fn deref_mut(&mut self) -> &mut Self::Target { 37 | &mut self.event 38 | } 39 | } 40 | 41 | impl Default for DebouncedEvent { 42 | fn default() -> Self { 43 | Self { 44 | event: Default::default(), 45 | time: Instant::now(), 46 | } 47 | } 48 | } 49 | 50 | impl From for DebouncedEvent { 51 | fn from(event: Event) -> Self { 52 | Self { 53 | event, 54 | time: Instant::now(), 55 | } 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /crates/config-derive/Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | version = 3 4 | 5 | [[package]] 6 | name = "config-derive" 7 | version = "0.1.0" 8 | dependencies = [ 9 | "proc-macro2", 10 | "quote", 11 | "syn", 12 | ] 13 | 14 | [[package]] 15 | name = "proc-macro2" 16 | version = "1.0.76" 17 | source = "registry+https://github.com/rust-lang/crates.io-index" 18 | checksum = "95fc56cda0b5c3325f5fbbd7ff9fda9e02bb00bb3dac51252d2f1bfa1cb8cc8c" 19 | dependencies = [ 20 | "unicode-ident", 21 | ] 22 | 23 | [[package]] 24 | name = "quote" 25 | version = "1.0.35" 26 | source = "registry+https://github.com/rust-lang/crates.io-index" 27 | checksum = "291ec9ab5efd934aaf503a6466c5d5251535d108ee747472c3977cc5acc868ef" 28 | dependencies = [ 29 | "proc-macro2", 30 | ] 31 | 32 | [[package]] 33 | name = "syn" 34 | version = "2.0.48" 35 | source = "registry+https://github.com/rust-lang/crates.io-index" 36 | checksum = "0f3531638e407dfc0814761abb7c00a5b54992b849452a0646b7f65c9f770f3f" 37 | dependencies = [ 38 | "proc-macro2", 39 | "quote", 40 | "unicode-ident", 41 | ] 42 | 43 | [[package]] 44 | name = "unicode-ident" 45 | version = "1.0.12" 46 | source = "registry+https://github.com/rust-lang/crates.io-index" 47 | checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" 48 | -------------------------------------------------------------------------------- /src/cli/update.rs: -------------------------------------------------------------------------------- 1 | use anyhow::Result; 2 | use clap::{Parser, ValueEnum}; 3 | 4 | use crate::{argon_error, argon_info, config::Config, updater}; 5 | 6 | /// Forcefully update Argon components if available 7 | #[derive(Parser)] 8 | pub struct Update { 9 | /// Whether to update `cli`, `plugin`, `templates` or `all` 10 | #[arg(hide_possible_values = true)] 11 | mode: Option, 12 | /// Whether to force update even if there is no newer version 13 | #[arg(short, long)] 14 | force: bool, 15 | } 16 | 17 | impl Update { 18 | pub fn main(self) -> Result<()> { 19 | let config = Config::new(); 20 | 21 | let (cli, plugin, templates) = match self.mode.unwrap_or_default() { 22 | UpdateMode::All => (true, config.install_plugin, config.update_templates), 23 | UpdateMode::Cli => (true, false, false), 24 | UpdateMode::Plugin => (false, true, false), 25 | UpdateMode::Templates => (false, false, true), 26 | }; 27 | 28 | match updater::manual_update(cli, plugin, templates, self.force) { 29 | Ok(updated) => { 30 | if !updated { 31 | argon_info!("Everything is up to date!"); 32 | } 33 | } 34 | Err(err) => argon_error!("Failed to update Argon: {}", err), 35 | } 36 | 37 | Ok(()) 38 | } 39 | } 40 | 41 | #[derive(Clone, Default, ValueEnum)] 42 | enum UpdateMode { 43 | Cli, 44 | Plugin, 45 | Templates, 46 | #[default] 47 | All, 48 | } 49 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |
2 | Argon 3 | Full featured tool for Roblox development 4 |
5 | 6 | # Argon 7 | 8 | Argon is a powerful CLI that improves Roblox development experience. This is core part of Argon project as this is where all the processing happens. [Argon VS Code](https://github.com/argon-rbx/argon-vscode) extension is user-friendly wrapper of this CLI and [Argon Roblox](https://github.com/argon-rbx/argon-roblox) is a Roblox Studio plugin that is required for live syncing. 9 | 10 | Some of the key features of Argon: 11 | 12 | - Two-Way sync of code and other instances with their properties 13 | - Building projects in Roblox binary or XML format 14 | - Beginner-friendly and professional at the same time 15 | - Extremely customizable and fast 16 | - Lots of useful helper commands 17 | - Workflow automation (CI/CD) 18 | 19 | ## Visit [argon.wiki](https://argon.wiki/) to learn more! 20 | 21 | Or follow one of these direct links to: 22 | 23 | - [Install](https://argon.wiki/docs/installation) Argon 24 | - [Get Started](https://argon.wiki/docs/category/getting-started) with Argon 25 | - Learn about Argon [Commands](https://argon.wiki/docs/category/commands) 26 | - Explore the Argon [API](https://argon.wiki/api/project) 27 | - Follow the latest [Changes](https://argon.wiki/changelog/argon) 28 | -------------------------------------------------------------------------------- /src/cli/plugin.rs: -------------------------------------------------------------------------------- 1 | use anyhow::Result; 2 | use clap::{Parser, ValueEnum}; 3 | use std::{fs, path::PathBuf}; 4 | 5 | use crate::{argon_info, config::Config, ext::PathExt, installer, util}; 6 | 7 | /// Install Argon Roblox Studio plugin locally 8 | #[derive(Parser)] 9 | pub struct Plugin { 10 | /// Whether to `install` or `uninstall` the plugin 11 | #[arg(hide_possible_values = true)] 12 | mode: Option, 13 | /// Custom plugin installation path 14 | #[arg()] 15 | path: Option, 16 | } 17 | 18 | impl Plugin { 19 | pub fn main(self) -> Result<()> { 20 | let plugin_path = if let Some(path) = self.path { 21 | let smart_paths = Config::new().smart_paths; 22 | 23 | if path.is_dir() || (smart_paths && (path.extension().is_none())) { 24 | if !smart_paths || path.get_name().to_lowercase() != "argon" { 25 | path.join("Argon.rbxm") 26 | } else { 27 | path.with_extension("rbxm") 28 | } 29 | } else { 30 | path 31 | } 32 | } else { 33 | util::get_plugin_path()? 34 | }; 35 | 36 | match self.mode.unwrap_or_default() { 37 | PluginMode::Install => { 38 | argon_info!("Installing Argon plugin.."); 39 | installer::install_plugin(&plugin_path, true)?; 40 | } 41 | PluginMode::Uninstall => { 42 | argon_info!("Uninstalling Argon plugin.."); 43 | fs::remove_file(plugin_path)?; 44 | } 45 | } 46 | 47 | Ok(()) 48 | } 49 | } 50 | 51 | #[derive(Clone, Default, ValueEnum)] 52 | enum PluginMode { 53 | #[default] 54 | Install, 55 | Uninstall, 56 | } 57 | -------------------------------------------------------------------------------- /src/core/helpers/migrations.rs: -------------------------------------------------------------------------------- 1 | // Temporary module for ContentId to Content migration of selected properties 2 | 3 | use rbx_dom_weak::{ 4 | types::{Content, Variant}, 5 | Ustr, 6 | }; 7 | use std::{collections::HashMap, sync::OnceLock}; 8 | 9 | use crate::Properties; 10 | 11 | pub type Migrations = HashMap<&'static str, HashMap<&'static str, &'static str>>; 12 | 13 | fn get_migrations() -> &'static Migrations { 14 | static MIGRATIONS: OnceLock = OnceLock::new(); 15 | 16 | MIGRATIONS.get_or_init(|| { 17 | HashMap::from([ 18 | ("ImageLabel", HashMap::from([("Image", "ImageContent")])), 19 | ("ImageButton", HashMap::from([("Image", "ImageContent")])), 20 | ( 21 | "MeshPart", 22 | HashMap::from([("MeshId", "MeshContent"), ("TextureID", "TextureContent")]), 23 | ), 24 | ("BaseWrap", HashMap::from([("CageMeshId", "CageMeshContent")])), 25 | ( 26 | "WrapLayer", 27 | HashMap::from([("ReferenceMeshId", "ReferenceMeshContent")]), 28 | ), 29 | ]) 30 | }) 31 | } 32 | 33 | pub fn apply<'a>(class: &str, properties: &'a mut Properties) -> Option<&'a mut Properties> { 34 | let migration = get_migrations().get(class)?; 35 | 36 | for (old, new) in migration { 37 | let new = Ustr::from(new); 38 | 39 | if properties.contains_key(&new) { 40 | continue; 41 | } 42 | 43 | if let Some(Variant::ContentId(value)) = properties.remove(&Ustr::from(old)) { 44 | properties.insert(new, Content::from(value.as_str()).into()); 45 | } 46 | } 47 | 48 | Some(properties) 49 | } 50 | -------------------------------------------------------------------------------- /src/core/changes.rs: -------------------------------------------------------------------------------- 1 | use rbx_dom_weak::types::Ref; 2 | use serde::{Deserialize, Serialize}; 3 | 4 | use super::snapshot::{AddedSnapshot, Snapshot, UpdatedSnapshot}; 5 | 6 | #[derive(Debug, Clone, Serialize, Deserialize)] 7 | pub struct Changes { 8 | pub additions: Vec, 9 | pub updates: Vec, 10 | pub removals: Vec, 11 | } 12 | 13 | impl Changes { 14 | pub fn new() -> Self { 15 | Self { 16 | additions: Vec::new(), 17 | updates: Vec::new(), 18 | removals: Vec::new(), 19 | } 20 | } 21 | 22 | pub fn add(&mut self, snapshot: Snapshot, parent: Ref) { 23 | self.additions.push(AddedSnapshot { 24 | id: snapshot.id, 25 | parent, 26 | name: snapshot.name, 27 | class: snapshot.class, 28 | properties: snapshot.properties, 29 | children: snapshot.children, 30 | meta: snapshot.meta, 31 | }); 32 | } 33 | 34 | pub fn update(&mut self, modified_snapshot: UpdatedSnapshot) { 35 | self.updates.push(modified_snapshot); 36 | } 37 | 38 | pub fn remove(&mut self, id: Ref) { 39 | self.removals.push(id); 40 | } 41 | 42 | pub fn extend(&mut self, changes: Self) { 43 | self.additions.extend(changes.additions); 44 | self.updates.extend(changes.updates); 45 | self.removals.extend(changes.removals); 46 | } 47 | 48 | pub fn is_empty(&self) -> bool { 49 | self.additions.is_empty() && self.updates.is_empty() && self.removals.is_empty() 50 | } 51 | 52 | pub fn total(&self) -> usize { 53 | self.additions.len() + self.updates.len() + self.removals.len() 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /src/cli/debug.rs: -------------------------------------------------------------------------------- 1 | use anyhow::{bail, Result}; 2 | use clap::{Parser, ValueEnum}; 3 | 4 | #[cfg(not(target_os = "linux"))] 5 | use keybd_event::{KeyBondingInstance, KeyboardKey}; 6 | 7 | use crate::studio; 8 | 9 | /// Start or stop Roblox playtest with selected mode 10 | #[derive(Parser)] 11 | pub struct Debug { 12 | /// Debug mode to use (`play`, `run`, `start` or `stop`) 13 | #[arg(hide_possible_values = true)] 14 | mode: Option, 15 | } 16 | 17 | impl Debug { 18 | pub fn main(self) -> Result<()> { 19 | if !studio::is_running(None)? { 20 | bail!("There is no running Roblox Studio instance!"); 21 | } 22 | 23 | studio::focus(None)?; 24 | send_keys(self.mode.unwrap_or_default()); 25 | 26 | Ok(()) 27 | } 28 | } 29 | 30 | #[allow(unused_variables)] 31 | fn send_keys(mode: DebugMode) { 32 | #[cfg(not(target_os = "linux"))] 33 | { 34 | let mut kb = KeyBondingInstance::new().unwrap(); 35 | 36 | match mode { 37 | DebugMode::Play => { 38 | kb.add_key(KeyboardKey::KeyF5); 39 | } 40 | DebugMode::Run => { 41 | kb.add_key(KeyboardKey::KeyF8); 42 | } 43 | DebugMode::Start => { 44 | kb.add_key(KeyboardKey::KeyF7); 45 | } 46 | DebugMode::Stop => { 47 | kb.has_shift(true); 48 | kb.add_key(KeyboardKey::KeyF5); 49 | } 50 | } 51 | 52 | kb.launching(); 53 | } 54 | 55 | #[cfg(target_os = "linux")] 56 | { 57 | panic!("This feature is not yet supported on Linux!"); 58 | } 59 | } 60 | 61 | #[derive(Clone, Default, ValueEnum)] 62 | enum DebugMode { 63 | #[default] 64 | Play, 65 | Run, 66 | Start, 67 | Stop, 68 | } 69 | -------------------------------------------------------------------------------- /crates/self_update/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "self_update" 3 | version = "0.39.0" 4 | description = "Self updates for standalone executables" 5 | repository = "https://github.com/jaemk/self_update" 6 | keywords = ["update", "upgrade", "download", "release"] 7 | categories = ["command-line-utilities"] 8 | license = "MIT" 9 | readme = "README.md" 10 | authors = ["James Kominick "] 11 | exclude = ["/ci/*", ".travis.yml", "appveyor.yml"] 12 | edition = "2018" 13 | rust = "1.64" 14 | 15 | [dependencies] 16 | serde_json = "1" 17 | tempfile = "3" 18 | flate2 = { version = "1", optional = true } 19 | tar = { version = "0.4", optional = true } 20 | semver = "1.0" 21 | zip = { version = "0.6", default-features = false, features = ["time"], optional = true } 22 | either = { version = "1", optional = true } 23 | reqwest = { version = "0.11", default-features = false, features = ["blocking", "json"] } 24 | hyper = "0.14" 25 | indicatif = "0.17" 26 | quick-xml = "0.23" 27 | regex = "1" 28 | log = "0.4" 29 | urlencoding = "2.1" 30 | self-replace = "1" 31 | zipsign-api = { version = "0.1.0-a.3", default-features = false, optional = true } 32 | 33 | [features] 34 | default = ["reqwest/default-tls"] 35 | archive-zip = ["zip", "zipsign-api?/verify-zip"] 36 | compression-zip-bzip2 = ["archive-zip", "zip/bzip2"] 37 | compression-zip-deflate = ["archive-zip", "zip/deflate"] 38 | archive-tar = ["tar", "zipsign-api?/verify-tar"] 39 | compression-flate2 = ["archive-tar", "flate2", "either"] 40 | rustls = ["reqwest/rustls-tls"] 41 | signatures = ["dep:zipsign-api"] 42 | 43 | [package.metadata.docs.rs] 44 | # Whether to pass `--all-features` to Cargo (default: false) 45 | all-features = true 46 | -------------------------------------------------------------------------------- /crates/self_update/src/macros.rs: -------------------------------------------------------------------------------- 1 | /// Allows you to pull the version from your Cargo.toml at compile time as 2 | /// `MAJOR.MINOR.PATCH_PKGVERSION_PRE` 3 | #[macro_export] 4 | macro_rules! cargo_crate_version { 5 | // -- Pulled from clap.rs src/macros.rs 6 | () => { 7 | env!("CARGO_PKG_VERSION") 8 | }; 9 | } 10 | 11 | /// Set ssl cert env. vars to make sure openssl can find required files 12 | macro_rules! set_ssl_vars { 13 | () => { 14 | #[cfg(target_os = "linux")] 15 | { 16 | if ::std::env::var_os("SSL_CERT_FILE").is_none() { 17 | ::std::env::set_var("SSL_CERT_FILE", "/etc/ssl/certs/ca-certificates.crt"); 18 | } 19 | if ::std::env::var_os("SSL_CERT_DIR").is_none() { 20 | ::std::env::set_var("SSL_CERT_DIR", "/etc/ssl/certs"); 21 | } 22 | } 23 | }; 24 | } 25 | 26 | /// Helper to `print!` and immediately `flush` `stdout` 27 | macro_rules! print_flush { 28 | ($literal:expr) => { 29 | print!($literal); 30 | ::std::io::Write::flush(&mut ::std::io::stdout())?; 31 | }; 32 | ($literal:expr, $($arg:expr),*) => { 33 | print!($literal, $($arg),*); 34 | ::std::io::Write::flush(&mut ::std::io::stdout())?; 35 | } 36 | } 37 | 38 | /// Helper for formatting `errors::Error`s 39 | macro_rules! format_err { 40 | ($e_type:expr, $literal:expr) => { 41 | $e_type(format!($literal)) 42 | }; 43 | ($e_type:expr, $literal:expr, $($arg:expr),*) => { 44 | $e_type(format!($literal, $($arg),*)) 45 | }; 46 | } 47 | 48 | /// Helper for formatting `errors::Error`s and returning early 49 | macro_rules! bail { 50 | ($e_type:expr, $literal:expr) => { 51 | return Err(format_err!($e_type, $literal)) 52 | }; 53 | ($e_type:expr, $literal:expr, $($arg:expr),*) => { 54 | return Err(format_err!($e_type, $literal, $($arg),*)) 55 | }; 56 | } 57 | -------------------------------------------------------------------------------- /crates/notify-debouncer-full/Cargo.toml: -------------------------------------------------------------------------------- 1 | # THIS FILE IS AUTOMATICALLY GENERATED BY CARGO 2 | # 3 | # When uploading crates to the registry Cargo will automatically 4 | # "normalize" Cargo.toml files for maximal compatibility 5 | # with all versions of Cargo and also rewrite `path` dependencies 6 | # to registry (e.g., crates.io) dependencies. 7 | # 8 | # If you are reading this file be aware that the original Cargo.toml 9 | # will likely look very different (and much more reasonable). 10 | # See Cargo.toml.orig for the original contents. 11 | 12 | [package] 13 | edition = "2021" 14 | rust-version = "1.60" 15 | name = "notify-debouncer-full" 16 | version = "0.3.1" 17 | authors = ["Daniel Faust "] 18 | description = "notify event debouncer optimized for ease of use" 19 | homepage = "https://github.com/notify-rs/notify" 20 | documentation = "https://docs.rs/notify-debouncer-full" 21 | readme = "README.md" 22 | keywords = [ 23 | "events", 24 | "filesystem", 25 | "notify", 26 | "watch", 27 | ] 28 | license = "MIT OR Apache-2.0" 29 | repository = "https://github.com/notify-rs/notify.git" 30 | resolver = "1" 31 | 32 | [lib] 33 | name = "notify_debouncer_full" 34 | path = "src/lib.rs" 35 | 36 | [dependencies.crossbeam-channel] 37 | version = "0.5" 38 | optional = true 39 | 40 | [dependencies.file-id] 41 | version = "0.2.1" 42 | 43 | [dependencies.log] 44 | version = "0.4.17" 45 | 46 | [dependencies.notify] 47 | version = "6.1.1" 48 | 49 | [dependencies.parking_lot] 50 | version = "0.12.1" 51 | 52 | [dependencies.walkdir] 53 | version = "2.2.2" 54 | 55 | [dev-dependencies.deser-hjson] 56 | version = "1.1.1" 57 | 58 | [dev-dependencies.mock_instant] 59 | version = "0.3.0" 60 | 61 | [dev-dependencies.pretty_assertions] 62 | version = "1.3.0" 63 | 64 | [dev-dependencies.rand] 65 | version = "0.8.5" 66 | 67 | [dev-dependencies.rstest] 68 | version = "0.17.0" 69 | 70 | [dev-dependencies.serde] 71 | version = "1.0.89" 72 | features = ["derive"] 73 | 74 | [features] 75 | crossbeam = [ 76 | "crossbeam-channel", 77 | "notify/crossbeam-channel", 78 | ] 79 | default = ["crossbeam"] 80 | -------------------------------------------------------------------------------- /src/middleware/luau.rs: -------------------------------------------------------------------------------- 1 | use anyhow::Result; 2 | use rbx_dom_weak::{ 3 | types::{Enum, Variant}, 4 | ustr, HashMapExt, UstrMap, 5 | }; 6 | use std::path::Path; 7 | 8 | use super::Middleware; 9 | use crate::{ 10 | core::{meta::Context, snapshot::Snapshot}, 11 | vfs::Vfs, 12 | Properties, 13 | }; 14 | 15 | #[derive(Debug, Clone, PartialEq)] 16 | pub enum ScriptType { 17 | Server, 18 | Client, 19 | Module, 20 | } 21 | 22 | impl From for ScriptType { 23 | fn from(middleware: Middleware) -> Self { 24 | match middleware { 25 | Middleware::ServerScript => ScriptType::Server, 26 | Middleware::ClientScript => ScriptType::Client, 27 | Middleware::ModuleScript => ScriptType::Module, 28 | _ => panic!("Cannot convert {middleware:?} to ScriptType"), 29 | } 30 | } 31 | } 32 | 33 | #[profiling::function] 34 | pub fn read_luau(path: &Path, context: &Context, vfs: &Vfs, script_type: ScriptType) -> Result { 35 | let (class_name, run_context) = match (context.use_legacy_scripts(), &script_type) { 36 | (false, ScriptType::Server) => ("Script", Some(Variant::Enum(Enum::from_u32(1)))), 37 | (false, ScriptType::Client) => ("Script", Some(Variant::Enum(Enum::from_u32(2)))), 38 | (true, ScriptType::Server) => ("Script", Some(Variant::Enum(Enum::from_u32(0)))), 39 | (true, ScriptType::Client) => ("LocalScript", None), 40 | (_, ScriptType::Module) => ("ModuleScript", None), 41 | }; 42 | 43 | let mut snapshot = Snapshot::new().with_class(class_name); 44 | let mut properties = UstrMap::new(); 45 | 46 | let source = vfs.read_to_string(path)?; 47 | 48 | if script_type != ScriptType::Module { 49 | if let Some(run_context) = run_context { 50 | properties.insert(ustr("RunContext"), run_context); 51 | } 52 | } 53 | 54 | properties.insert(ustr("Source"), Variant::String(source)); 55 | snapshot.set_properties(properties); 56 | 57 | Ok(snapshot) 58 | } 59 | 60 | #[profiling::function] 61 | pub fn write_luau(mut properties: Properties, path: &Path, vfs: &Vfs) -> Result { 62 | let source = if let Some(Variant::String(source)) = properties.remove(&ustr("Source")) { 63 | source 64 | } else { 65 | String::new() 66 | }; 67 | 68 | vfs.write(path, source.as_bytes())?; 69 | 70 | Ok(properties) 71 | } 72 | -------------------------------------------------------------------------------- /src/glob.rs: -------------------------------------------------------------------------------- 1 | use glob::{glob, Paths, Pattern, PatternError}; 2 | use serde::{de::Error, Deserialize, Deserializer, Serialize, Serializer}; 3 | use std::{ 4 | fmt::{self, Debug, Formatter}, 5 | path::{Path, PathBuf}, 6 | }; 7 | 8 | #[derive(Clone, PartialEq)] 9 | pub struct Glob { 10 | pattern: Pattern, 11 | } 12 | 13 | impl Glob { 14 | pub fn new(pattern: &str) -> Result { 15 | #[cfg(not(target_os = "windows"))] 16 | let pattern = pattern.replace('\\', "/"); 17 | 18 | #[cfg(target_os = "windows")] 19 | let pattern = pattern.replace('/', "\\"); 20 | 21 | Ok(Self { 22 | pattern: Pattern::new(&pattern)?, 23 | }) 24 | } 25 | 26 | pub fn from_path(path: &Path) -> Result { 27 | Self::new(path.to_str().unwrap_or_default()) 28 | } 29 | 30 | pub fn matches(&self, str: &str) -> bool { 31 | self.pattern.matches(str) 32 | } 33 | 34 | pub fn matches_path(&self, path: &Path) -> bool { 35 | self.pattern.matches_path(path) 36 | } 37 | 38 | pub fn matches_path_with_dir(&self, path: &Path) -> bool { 39 | let matches = self.pattern.matches_path(path); 40 | 41 | if !matches && self.pattern.as_str().ends_with("/**") { 42 | if let Ok(pattern) = Pattern::new(self.pattern.as_str().strip_suffix("/**").unwrap()) { 43 | return pattern.matches_path(path); 44 | } else { 45 | return false; 46 | } 47 | } 48 | 49 | matches 50 | } 51 | 52 | pub fn first(&self) -> Option { 53 | let path = glob(self.pattern.as_str()).unwrap().next(); 54 | 55 | if let Some(path) = path { 56 | return Some(path.unwrap_or_default()); 57 | } 58 | 59 | None 60 | } 61 | 62 | pub fn iter(&self) -> Paths { 63 | glob(self.pattern.as_str()).unwrap() 64 | } 65 | 66 | pub fn as_str(&self) -> &str { 67 | self.pattern.as_str() 68 | } 69 | } 70 | 71 | impl Serialize for Glob { 72 | fn serialize(&self, serializer: S) -> Result { 73 | serializer.serialize_str(self.pattern.as_str()) 74 | } 75 | } 76 | 77 | impl<'de> Deserialize<'de> for Glob { 78 | fn deserialize>(deserializer: D) -> Result { 79 | let pattern = String::deserialize(deserializer)?; 80 | Self::new(&pattern).map_err(Error::custom) 81 | } 82 | } 83 | 84 | impl Debug for Glob { 85 | fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { 86 | write!(f, "{}", self.pattern.as_str()) 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /src/middleware/msgpack.rs: -------------------------------------------------------------------------------- 1 | use anyhow::Result; 2 | use rbx_dom_weak::{types::Variant, ustr, HashMapExt, UstrMap}; 3 | use rmp_serde::Deserializer; 4 | use rmpv::Value; 5 | use std::path::Path; 6 | 7 | use crate::{core::snapshot::Snapshot, vfs::Vfs}; 8 | 9 | #[profiling::function] 10 | pub fn read_msgpack(path: &Path, vfs: &Vfs) -> Result { 11 | let msgpack = vfs.read(path)?; 12 | 13 | if msgpack.is_empty() { 14 | return Ok(Snapshot::new().with_class("ModuleScript")); 15 | } 16 | 17 | let mut deserializer = Deserializer::from_read_ref(&msgpack).with_human_readable(); 18 | let msgpack: Value = serde::Deserialize::deserialize(&mut deserializer)?; 19 | 20 | let lua = format!("return {}", msgpack_to_lua(&msgpack)); 21 | 22 | let mut properties = UstrMap::new(); 23 | properties.insert(ustr("Source"), Variant::String(lua)); 24 | 25 | Ok(Snapshot::new().with_class("ModuleScript").with_properties(properties)) 26 | } 27 | 28 | fn msgpack_to_lua(value: &Value) -> String { 29 | let mut lua = String::new(); 30 | 31 | match value { 32 | Value::Nil => lua.push_str("nil"), 33 | Value::Boolean(b) => lua.push_str(&b.to_string()), 34 | Value::Integer(i) => lua.push_str(&i.to_string()), 35 | Value::F32(f) => { 36 | if f.is_infinite() { 37 | lua.push_str("math.huge") 38 | } else { 39 | lua.push_str(&f.to_string()) 40 | } 41 | } 42 | Value::F64(f) => { 43 | if f.is_infinite() { 44 | lua.push_str("math.huge") 45 | } else { 46 | lua.push_str(&f.to_string()) 47 | } 48 | } 49 | Value::String(s) => lua.push_str(&format!("\"{}\"", &escape_chars(s.as_str().unwrap_or_default()))), 50 | Value::Binary(b) => lua.push_str(&String::from_utf8_lossy(b)), 51 | Value::Array(a) => { 52 | lua.push('{'); 53 | 54 | for v in a { 55 | lua.push_str(&msgpack_to_lua(v)); 56 | lua.push(','); 57 | } 58 | 59 | lua.push('}'); 60 | } 61 | Value::Map(t) => { 62 | lua.push('{'); 63 | 64 | for (k, v) in t { 65 | lua.push_str(&format!("[{}] = ", &msgpack_to_lua(k))); 66 | lua.push_str(&msgpack_to_lua(v)); 67 | lua.push(','); 68 | } 69 | 70 | lua.push('}'); 71 | } 72 | Value::Ext(_, _) => {} 73 | } 74 | 75 | lua 76 | } 77 | 78 | fn escape_chars(string: &str) -> String { 79 | let mut validated = String::new(); 80 | 81 | for char in string.chars() { 82 | match char { 83 | '\n' => validated.push_str("\\n"), 84 | '\t' => validated.push_str("\\t"), 85 | '\r' => validated.push_str("\\r"), 86 | '\\' => validated.push_str("\\\\"), 87 | '"' => validated.push_str("\\\""), 88 | _ => validated.push(char), 89 | } 90 | } 91 | 92 | validated 93 | } 94 | -------------------------------------------------------------------------------- /src/middleware/helpers/mesh_part.rs: -------------------------------------------------------------------------------- 1 | use anyhow::Result; 2 | use log::{debug, error, trace}; 3 | use rbx_dom_weak::{types::Variant, ustr, InstanceBuilder, WeakDom}; 4 | use roblox_install::RobloxStudio; 5 | use std::{ 6 | collections::HashMap, 7 | fs::{self, File}, 8 | io::BufWriter, 9 | path::Path, 10 | process, 11 | sync::RwLock, 12 | thread, 13 | }; 14 | 15 | use crate::{ext::PathExt, util, Properties}; 16 | 17 | const CUSTOM_MESH_PART_PROPERTIES: [&str; 2] = ["MeshContent", "InitialSize"]; 18 | 19 | static INDEX: RwLock = RwLock::new(0); 20 | 21 | pub fn save_mesh(properties: &Properties) -> Option { 22 | let mut mesh_properties: HashMap<&str, Variant> = HashMap::new(); 23 | 24 | for property in CUSTOM_MESH_PART_PROPERTIES { 25 | if let Some(value) = properties.get(&ustr(property)) { 26 | mesh_properties.insert(property, value.clone()); 27 | } 28 | } 29 | 30 | if mesh_properties.is_empty() { 31 | return None; 32 | } 33 | 34 | let dom = WeakDom::new(InstanceBuilder::new("MeshPart").with_properties(mesh_properties)); 35 | 36 | trace!("Writing MeshPart temporary binary model"); 37 | 38 | let result = || -> Result { 39 | let pid = process::id().to_string(); 40 | let path = RobloxStudio::locate()?.content_path().join("argon").join(&pid); 41 | 42 | let index = *INDEX.read().unwrap(); 43 | 44 | if index == 0 { 45 | let path = path.clone(); 46 | 47 | thread::spawn(move || match clear(&path) { 48 | Ok(_) => debug!("Cleared temporary mesh models"), 49 | Err(err) => error!("Failed to clear temporary mesh models: {err}"), 50 | }); 51 | } 52 | 53 | if !path.exists() { 54 | fs::create_dir_all(&path)?; 55 | } 56 | 57 | let name = index.to_string(); 58 | let writer = BufWriter::new(File::create(path.join(&name))?); 59 | 60 | rbx_binary::to_writer(writer, &dom, &[dom.root_ref()])?; 61 | 62 | Ok(pid + "/" + &name) 63 | }(); 64 | 65 | match result { 66 | Ok(name) => { 67 | let mut index = INDEX.write().unwrap(); 68 | *index += 1; 69 | 70 | Some(name) 71 | } 72 | Err(err) => { 73 | error!("Failed to write MeshPart temporary model: {err}"); 74 | None 75 | } 76 | } 77 | } 78 | 79 | fn clear(path: &Path) -> Result<()> { 80 | let ignore_name = path.get_name(); 81 | let parent = path.get_parent(); 82 | 83 | if !parent.exists() { 84 | return Ok(()); 85 | } 86 | 87 | for entry in fs::read_dir(parent)? { 88 | let path = entry?.path(); 89 | let name = path.get_name(); 90 | 91 | if name == ignore_name { 92 | continue; 93 | } 94 | 95 | if path.is_dir() && !name.parse().is_ok_and(util::process_exists) { 96 | fs::remove_dir_all(&path)?; 97 | } 98 | } 99 | 100 | Ok(()) 101 | } 102 | -------------------------------------------------------------------------------- /crates/self_update/src/backends/mod.rs: -------------------------------------------------------------------------------- 1 | /*! 2 | Collection of modules supporting various release distribution backends 3 | */ 4 | 5 | pub mod github; 6 | 7 | /// Search for the first "rel" link-header uri in a full link header string. 8 | /// Seems like reqwest/hyper threw away their link-header parser implementation... 9 | /// 10 | /// ex: 11 | /// `Link: ; rel="next"` 12 | /// `Link: ; rel="next"` 13 | /// 14 | /// https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Link 15 | /// header values may contain multiple values separated by commas 16 | /// `Link: ; rel="next", ; rel="next"` 17 | pub(crate) fn find_rel_next_link(link_str: &str) -> Option<&str> { 18 | for link in link_str.split(',') { 19 | let mut uri = None; 20 | let mut is_rel_next = false; 21 | for part in link.split(';') { 22 | let part = part.trim(); 23 | if part.starts_with('<') && part.ends_with('>') { 24 | uri = Some(part.trim_start_matches('<').trim_end_matches('>')); 25 | } else if part.starts_with("rel=") { 26 | let part = part 27 | .trim_start_matches("rel=") 28 | .trim_end_matches('"') 29 | .trim_start_matches('"'); 30 | if part == "next" { 31 | is_rel_next = true; 32 | } 33 | } 34 | 35 | if is_rel_next && uri.is_some() { 36 | return uri; 37 | } 38 | } 39 | } 40 | None 41 | } 42 | 43 | #[cfg(test)] 44 | mod test { 45 | use crate::backends::find_rel_next_link; 46 | 47 | #[test] 48 | fn test_find_rel_link() { 49 | let val = r##" ; rel="next" "##; 50 | let link = find_rel_next_link(val); 51 | assert_eq!(link, Some("https://api.github.com/resource?page=2")); 52 | 53 | let val = r##" ; rel="next" "##; 54 | let link = find_rel_next_link(val); 55 | assert_eq!( 56 | link, 57 | Some("https://gitlab.com/api/v4/projects/13083/releases?id=13083&page=2&per_page=20") 58 | ); 59 | 60 | // returns the first one 61 | let val = r##" ; rel="next", ; rel="next" "##; 62 | let link = find_rel_next_link(val); 63 | assert_eq!(link, Some("https://place.com")); 64 | 65 | // bad format, returns the second one 66 | let val = r##" https://bad-format.com; rel="next", ; rel="next" "##; 67 | let link = find_rel_next_link(val); 68 | assert_eq!(link, Some("https://wow.com")); 69 | 70 | // all bad format, returns none 71 | let val = r##" https://bad-format.com; rel="next",