├── .github
├── FUNDING.yml
├── dependabot.yml
└── workflows
│ ├── ci.yml
│ └── release.yml
├── .gitignore
├── CHANGELOG.md
├── Cargo.lock
├── Cargo.toml
├── LICENSE.md
├── README.md
├── assets
└── templates
│ ├── empty
│ ├── .gitignore
│ ├── project.json
│ ├── selene.toml
│ └── wally.toml
│ ├── model
│ ├── .gitignore
│ ├── LICENSE.md
│ ├── README.md
│ ├── project.json
│ ├── selene.toml
│ ├── src
│ │ └── .src.luau
│ └── wally.toml
│ ├── package
│ ├── .gitignore
│ ├── CHANGELOG.md
│ ├── LICENSE.md
│ ├── README.md
│ ├── project.json
│ ├── selene.toml
│ ├── src
│ │ └── init.luau
│ └── wally.toml
│ ├── place
│ ├── .gitignore
│ ├── README.md
│ ├── project.json
│ ├── selene.toml
│ ├── src
│ │ ├── Client
│ │ │ └── Main.client.luau
│ │ ├── Server
│ │ │ └── Main.server.luau
│ │ └── Shared
│ │ │ └── Hello.luau
│ └── wally.toml
│ ├── plugin
│ ├── .gitignore
│ ├── CHANGELOG.md
│ ├── LICENSE.md
│ ├── README.md
│ ├── project.json
│ ├── selene.toml
│ ├── src
│ │ └── .src.server.luau
│ └── wally.toml
│ └── quick
│ ├── .gitignore
│ ├── project.json
│ ├── selene.toml
│ ├── src
│ ├── ReplicatedFirst
│ │ └── .gitkeep
│ ├── ReplicatedStorage
│ │ └── .gitkeep
│ ├── ServerScriptService
│ │ └── .gitkeep
│ ├── ServerStorage
│ │ └── .gitkeep
│ ├── StarterGui
│ │ └── .gitkeep
│ ├── StarterPack
│ │ └── .gitkeep
│ ├── StarterPlayer
│ │ ├── StarterCharacterScripts
│ │ │ └── .gitkeep
│ │ └── StarterPlayerScripts
│ │ │ └── .gitkeep
│ └── Workspace
│ │ └── .gitkeep
│ └── wally.toml
├── build.rs
├── crates
├── config-derive
│ ├── .gitignore
│ ├── Cargo.lock
│ ├── Cargo.toml
│ ├── LICENSE
│ ├── rustfmt.toml
│ ├── src
│ │ ├── lib.rs
│ │ └── util.rs
│ └── tests
│ │ └── mod.rs
├── json-formatter
│ ├── .gitignore
│ ├── Cargo.lock
│ ├── Cargo.toml
│ ├── LICENSE
│ ├── rustfmt.toml
│ └── src
│ │ └── lib.rs
├── notify-debouncer-full
│ ├── .gitignore
│ ├── Cargo.lock
│ ├── Cargo.toml
│ ├── LICENSE-APACHE
│ ├── LICENSE-MIT
│ ├── rustfmt.toml
│ └── src
│ │ ├── cache.rs
│ │ ├── debounced_event.rs
│ │ ├── lib.rs
│ │ └── testing.rs
├── profiling
│ ├── .gitignore
│ ├── Cargo.lock
│ ├── Cargo.toml
│ ├── profiling-procmacros
│ │ ├── Cargo.toml
│ │ └── src
│ │ │ └── lib.rs
│ ├── profiling
│ │ ├── Cargo.toml
│ │ ├── src
│ │ │ └── lib.rs
│ │ └── tests
│ │ │ └── mod.rs
│ └── rustfmt.toml
└── self_update
│ ├── .gitignore
│ ├── Cargo.lock
│ ├── Cargo.toml
│ ├── LICENSE
│ ├── build.rs
│ ├── rustfmt.toml
│ └── src
│ ├── backends
│ ├── github.rs
│ └── mod.rs
│ ├── errors.rs
│ ├── lib.rs
│ ├── macros.rs
│ ├── update.rs
│ └── version.rs
├── rustfmt.toml
├── scripts
└── release
├── src
├── cli
│ ├── build.rs
│ ├── config.rs
│ ├── debug.rs
│ ├── doc.rs
│ ├── exec.rs
│ ├── init.rs
│ ├── mod.rs
│ ├── plugin.rs
│ ├── serve.rs
│ ├── sourcemap.rs
│ ├── stop.rs
│ ├── studio.rs
│ └── update.rs
├── config.rs
├── constants.rs
├── core
│ ├── changes.rs
│ ├── helpers
│ │ ├── migrations.rs
│ │ ├── mod.rs
│ │ └── syncback.rs
│ ├── meta.rs
│ ├── mod.rs
│ ├── processor
│ │ ├── mod.rs
│ │ ├── read.rs
│ │ └── write.rs
│ ├── queue.rs
│ ├── snapshot.rs
│ └── tree.rs
├── crash_handler.rs
├── ext.rs
├── glob.rs
├── installer.rs
├── integration.rs
├── lib.rs
├── logger.rs
├── main.rs
├── middleware
│ ├── csv.rs
│ ├── data.rs
│ ├── dir.rs
│ ├── helpers
│ │ ├── markdown.rs
│ │ ├── mesh_part.rs
│ │ ├── mod.rs
│ │ └── snapshot.rs
│ ├── json.rs
│ ├── json_model.rs
│ ├── luau.rs
│ ├── md.rs
│ ├── mod.rs
│ ├── msgpack.rs
│ ├── project.rs
│ ├── rbxm.rs
│ ├── rbxmx.rs
│ ├── toml.rs
│ ├── txt.rs
│ └── yaml.rs
├── program.rs
├── project.rs
├── resolution.rs
├── server
│ ├── details.rs
│ ├── exec.rs
│ ├── home.rs
│ ├── mod.rs
│ ├── open.rs
│ ├── read.rs
│ ├── snapshot.rs
│ ├── stop.rs
│ ├── subscribe.rs
│ ├── unsubscribe.rs
│ └── write.rs
├── sessions.rs
├── stats.rs
├── studio.rs
├── updater.rs
├── util.rs
├── vfs
│ ├── debouncer.rs
│ ├── mem_backend.rs
│ ├── mod.rs
│ └── std_backend.rs
└── workspace.rs
└── tests
└── resolution.rs
/.github/FUNDING.yml:
--------------------------------------------------------------------------------
1 | github: DervexDev
2 | ko_fi: Dervex
3 |
--------------------------------------------------------------------------------
/.github/dependabot.yml:
--------------------------------------------------------------------------------
1 | version: 2
2 | updates:
3 | - package-ecosystem: "cargo"
4 | directory: "/"
5 | schedule:
6 | interval: "monthly"
7 | time: "06:00"
8 | open-pull-requests-limit: 40
9 | ignore:
10 | - dependency-name: "*"
11 | update-types:
12 | - "version-update:semver-major"
13 |
--------------------------------------------------------------------------------
/.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 |
--------------------------------------------------------------------------------
/.github/workflows/release.yml:
--------------------------------------------------------------------------------
1 | name: Release
2 |
3 | on:
4 | push:
5 | tags: ["*"]
6 |
7 | jobs:
8 | bump:
9 | name: Bump Version
10 | runs-on: ubuntu-latest
11 |
12 | steps:
13 | - uses: actions/checkout@v4
14 | with:
15 | ref: main
16 |
17 | - name: Update Changelog
18 | uses: thomaseizinger/keep-a-changelog-new-release@v3
19 | with:
20 | tag: ${{ github.ref_name }}
21 |
22 | - name: Bump Cargo version
23 | id: version-bump
24 | uses: DervexDev/file-version-bumper@v1
25 | with:
26 | path: ./Cargo.toml
27 |
28 | - name: Update Cargo lock
29 | run: cargo update --workspace
30 |
31 | - name: Commit and Push
32 | uses: EndBug/add-and-commit@v9
33 | if: ${{ github.ref_name != steps.version-bump.outputs.old_version }}
34 | with:
35 | message: Bump version to ${{ github.ref_name }}
36 | default_author: github_actions
37 |
38 | - name: Update tag
39 | if: ${{ github.ref_name != steps.version-bump.outputs.old_version }}
40 | run: |
41 | git tag -fa ${{ github.ref_name }} -m "Release ${{ github.ref_name }}"
42 | git push -f --tags
43 |
44 | draft-release:
45 | name: Draft Release
46 | runs-on: ubuntu-latest
47 | needs: bump
48 |
49 | outputs:
50 | upload_url: ${{ steps.create-release.outputs.upload_url }}
51 | release_id: ${{ steps.create-release.outputs.id }}
52 |
53 | steps:
54 | - uses: actions/checkout@v4
55 | with:
56 | ref: main
57 |
58 | - name: Read Changelog
59 | id: read-changes
60 | uses: mindsers/changelog-reader-action@v2
61 | with:
62 | version: ${{ github.ref_name }}
63 |
64 | - name: Get previous Tag
65 | id: previous-tag
66 | uses: WyriHaximus/github-action-get-previous-tag@v1
67 |
68 | - name: Create Release
69 | id: create-release
70 | uses: shogo82148/actions-create-release@v1
71 | with:
72 | release_name: ${{ github.ref_name }}
73 | body: |
74 | ## Changelog
75 | ${{ steps.read-changes.outputs.changes }}
76 | prerelease: ${{ contains(github.ref_name, 'pre') }}
77 | notes_start_tag: ${{ steps.previous-tag.outputs.tag }}
78 | generate_release_notes: true
79 | commitish: main
80 | draft: true
81 |
82 | build:
83 | name: Build (${{ matrix.label }})
84 | runs-on: ${{ matrix.os }}
85 | needs: draft-release
86 |
87 | strategy:
88 | matrix:
89 | include:
90 | # x86_64
91 | - host: linux
92 | os: ubuntu-22.04
93 | target: x86_64-unknown-linux-gnu
94 | label: linux-x86_64
95 |
96 | - host: macos
97 | os: macos-latest
98 | target: x86_64-apple-darwin
99 | label: macos-x86_64
100 |
101 | - host: windows
102 | os: windows-latest
103 | target: x86_64-pc-windows-msvc
104 | label: windows-x86_64
105 |
106 | # aarch64
107 | - host: macos
108 | os: macos-latest
109 | target: aarch64-apple-darwin
110 | label: macos-aarch64
111 |
112 | steps:
113 | - uses: actions/checkout@v4
114 | with:
115 | ref: main
116 |
117 | - name: Setup Rust
118 | uses: hecrj/setup-rust-action@v2
119 | with:
120 | targets: ${{ matrix.target }}
121 |
122 | - name: Build
123 | run: cargo build --all-features --release --verbose --target ${{ matrix.target }}
124 | env:
125 | ARGON_TOKEN: ${{ secrets.ARGON_TOKEN }}
126 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
127 |
128 | - name: Archive
129 | shell: bash
130 | run: |
131 | mkdir release
132 |
133 | if [ "${{ matrix.host }}" = "windows" ]; then
134 | cp "target/${{ matrix.target }}/release/argon.exe" release/
135 | cd release
136 | 7z a ../release.zip *
137 | else
138 | cp "target/${{ matrix.target }}/release/argon" release/
139 | cd release
140 | zip ../release.zip *
141 | fi
142 |
143 | - name: Upload to Artifacts
144 | uses: actions/upload-artifact@v4
145 | with:
146 | name: argon-${{ github.ref_name }}-${{ matrix.label }}.zip
147 | path: release.zip
148 |
149 | - name: Upload to Release
150 | uses: shogo82148/actions-upload-release-asset@v1
151 | with:
152 | upload_url: ${{ needs.draft-release.outputs.upload_url }}
153 | asset_name: argon-${{ github.ref_name }}-${{ matrix.label }}.zip
154 | asset_path: release.zip
155 |
156 | publish-release:
157 | name: Publish Release
158 | runs-on: ubuntu-latest
159 | needs: [build, draft-release]
160 |
161 | steps:
162 | - name: Publish on GitHub
163 | uses: eregon/publish-release@v1
164 | env:
165 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
166 | with:
167 | release_id: ${{ needs.draft-release.outputs.release_id }}
168 |
169 | # - name: Publish on crates.io
170 | # run: cargo publish
171 | # env:
172 | # CARGO_REGISTRY_TOKEN: ${{ secrets.CARGO_REGISTRY_TOKEN }}
173 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 | /target
3 | /sourcemap.json
4 | *.rbxm
5 | *.rbxmx
6 | *.rbxl
7 | *.rbxlx
8 |
--------------------------------------------------------------------------------
/Cargo.toml:
--------------------------------------------------------------------------------
1 | [package]
2 | name = "argon-rbx"
3 | authors = ["Dervex"]
4 | description = "Full featured tool for Roblox development"
5 | repository = "https://github.com/argon-rbx/argon"
6 | documentation = "https://argon.wiki/docs"
7 | homepage = "https://argon.wiki"
8 | license = "Apache-2.0"
9 | version = "2.0.24"
10 | edition = "2021"
11 | build = "build.rs"
12 |
13 | [[bin]]
14 | name = "argon"
15 | path = "src/main.rs"
16 |
17 | [lib]
18 | name = "argon"
19 | path = "src/lib.rs"
20 |
21 | [features]
22 | plugin = []
23 |
24 | [dependencies]
25 | rbx_xml = "1.0.0"
26 | rbx_binary = "1.0.0"
27 | rbx_dom_weak = "3.0.0"
28 | rbx_reflection = "5.0.0"
29 | rbx_reflection_database = "1.0.3"
30 |
31 | config-derive = { version = "*", path = "crates/config-derive" }
32 | json-formatter = { version = "*", path = "crates/json-formatter" }
33 | profiling = { version = "*", path = "crates/profiling/profiling" }
34 |
35 | uuid = { version = "1.17.0", features = ["v4", "fast-rng"] }
36 | serde = { version = "1.0.219", features = ["derive"] }
37 | rmpv = { version = "1.3.0", features = ["with-serde"] }
38 | clap = { version = "4.5.40", features = ["derive", "cargo"] }
39 | reqwest = { version = "0.12.18", default-features = false, features = [
40 | "blocking",
41 | "rustls-tls",
42 | "json",
43 | ] }
44 | self_update = { version = "0.39.0", default-features = false, features = [
45 | "compression-zip-deflate",
46 | "rustls",
47 | ] }
48 |
49 | notify-debouncer-full = "0.3.1"
50 | clap-verbosity-flag = "2.2.3"
51 | crossbeam-channel = "0.5.15"
52 | derive-from-one = "0.1.0"
53 | roblox_install = "1.0.0"
54 | panic-message = "0.3.0"
55 | actix-msgpack = "0.1.4"
56 | puffin_http = "0.16.0"
57 | serde_json = "1.0.140"
58 | env_logger = "0.11.6"
59 | include_dir = "0.7.4"
60 | directories = "5.0.1"
61 | lazy_static = "1.5.0"
62 | backtrace = "0.3.75"
63 | documented = "0.9.1"
64 | dialoguer = "0.11.0"
65 | path-clean = "1.0.1"
66 | rmp-serde = "1.3.0"
67 | actix-web = "4.11.0"
68 | multimap = "0.10.1"
69 | optfield = "0.4.0"
70 | markdown = "0.3.0"
71 |
72 | json2lua = "0.1.3"
73 | toml2lua = "0.1.0"
74 | yaml2lua = "0.1.1"
75 | globenv = "0.2.1"
76 |
77 | puffin = "0.19.0"
78 | colored = "2.2.0"
79 | anyhow = "1.0.98"
80 | chrono = "0.4.41"
81 | notify = "6.1.1"
82 | whoami = "1.6.0"
83 | trash = "5.2.2"
84 | ctrlc = "3.4.7"
85 | toml = "0.8.22"
86 | glob = "0.3.2"
87 | open = "5.3.2"
88 | log = "0.4.27"
89 | csv = "1.3.1"
90 |
91 | [target.'cfg(not(target_os = "linux"))'.dependencies]
92 | keybd_event = "0.1.2"
93 |
94 | [target.'cfg(target_os = "windows")'.dependencies]
95 | winsafe = { version = "0.0.23", features = ["user"] }
96 |
97 | [build-dependencies]
98 | anyhow = "1.0.98"
99 | self_update = { version = "0.39.0", default-features = false, features = [
100 | "rustls",
101 | ] }
102 |
103 | [dev-dependencies]
104 | approx = "0.5.1"
105 |
106 | [patch.crates-io]
107 | notify-debouncer-full = { path = "crates/notify-debouncer-full" }
108 | self_update = { path = "crates/self_update" }
109 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
2 |

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 |
--------------------------------------------------------------------------------
/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/empty/project.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "$name",
3 | "tree": {
4 | "$className": "DataModel",
5 | "Packages": {
6 | "$path": "Packages"
7 | }
8 | }
9 | }
10 |
--------------------------------------------------------------------------------
/assets/templates/empty/selene.toml:
--------------------------------------------------------------------------------
1 | std = "roblox"
2 |
3 | [lints]
4 | shadowing = "allow"
5 |
--------------------------------------------------------------------------------
/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/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/model/LICENSE.md:
--------------------------------------------------------------------------------
1 | Licensed under $license license
2 | Copyright $year $owner
3 |
--------------------------------------------------------------------------------
/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/project.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "$name",
3 | "tree": {
4 | "$path": "src",
5 | "Packages": {
6 | "$path": "Packages"
7 | }
8 | }
9 | }
10 |
--------------------------------------------------------------------------------
/assets/templates/model/selene.toml:
--------------------------------------------------------------------------------
1 | std = "roblox"
2 |
3 | [lints]
4 | shadowing = "allow"
5 |
--------------------------------------------------------------------------------
/assets/templates/model/src/.src.luau:
--------------------------------------------------------------------------------
1 | return function()
2 | print('Hello world, from model!')
3 | end
4 |
--------------------------------------------------------------------------------
/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/.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/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | # $name Changelog
2 |
3 | ## Unreleased Changes
4 |
5 | -
6 |
--------------------------------------------------------------------------------
/assets/templates/package/LICENSE.md:
--------------------------------------------------------------------------------
1 | Licensed under $license license
2 | Copyright $year $owner
3 |
--------------------------------------------------------------------------------
/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/package/project.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "$name",
3 | "tree": {
4 | "$path": "src",
5 | "DevPackages": {
6 | "$path": "DevPackages"
7 | }
8 | }
9 | }
10 |
--------------------------------------------------------------------------------
/assets/templates/package/selene.toml:
--------------------------------------------------------------------------------
1 | std = "roblox"
2 |
3 | [lints]
4 | shadowing = "allow"
5 |
--------------------------------------------------------------------------------
/assets/templates/package/src/init.luau:
--------------------------------------------------------------------------------
1 | return {
2 | hello = function()
3 | print('Hello world, from package!')
4 | end,
5 | }
6 |
--------------------------------------------------------------------------------
/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/.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/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/assets/templates/place/selene.toml:
--------------------------------------------------------------------------------
1 | std = "roblox"
2 |
3 | [lints]
4 | shadowing = "allow"
5 |
--------------------------------------------------------------------------------
/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/place/src/Shared/Hello.luau:
--------------------------------------------------------------------------------
1 | return function()
2 | print('Hello, world!')
3 | end
4 |
--------------------------------------------------------------------------------
/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/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/plugin/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | # $name Changelog
2 |
3 | ## Unreleased Changes
4 |
5 | -
6 |
--------------------------------------------------------------------------------
/assets/templates/plugin/LICENSE.md:
--------------------------------------------------------------------------------
1 | Licensed under $license license
2 | Copyright $year $owner
3 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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/plugin/selene.toml:
--------------------------------------------------------------------------------
1 | std = "roblox"
2 |
3 | [lints]
4 | shadowing = "allow"
5 |
--------------------------------------------------------------------------------
/assets/templates/plugin/src/.src.server.luau:
--------------------------------------------------------------------------------
1 | print('Hello world, from plugin!')
2 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/assets/templates/quick/selene.toml:
--------------------------------------------------------------------------------
1 | std = "roblox"
2 |
3 | [lints]
4 | shadowing = "allow"
5 |
--------------------------------------------------------------------------------
/assets/templates/quick/src/ReplicatedFirst/.gitkeep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/argon-rbx/argon/f516ba421631739ed4726e74dbdbdc5d17d6af70/assets/templates/quick/src/ReplicatedFirst/.gitkeep
--------------------------------------------------------------------------------
/assets/templates/quick/src/ReplicatedStorage/.gitkeep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/argon-rbx/argon/f516ba421631739ed4726e74dbdbdc5d17d6af70/assets/templates/quick/src/ReplicatedStorage/.gitkeep
--------------------------------------------------------------------------------
/assets/templates/quick/src/ServerScriptService/.gitkeep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/argon-rbx/argon/f516ba421631739ed4726e74dbdbdc5d17d6af70/assets/templates/quick/src/ServerScriptService/.gitkeep
--------------------------------------------------------------------------------
/assets/templates/quick/src/ServerStorage/.gitkeep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/argon-rbx/argon/f516ba421631739ed4726e74dbdbdc5d17d6af70/assets/templates/quick/src/ServerStorage/.gitkeep
--------------------------------------------------------------------------------
/assets/templates/quick/src/StarterGui/.gitkeep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/argon-rbx/argon/f516ba421631739ed4726e74dbdbdc5d17d6af70/assets/templates/quick/src/StarterGui/.gitkeep
--------------------------------------------------------------------------------
/assets/templates/quick/src/StarterPack/.gitkeep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/argon-rbx/argon/f516ba421631739ed4726e74dbdbdc5d17d6af70/assets/templates/quick/src/StarterPack/.gitkeep
--------------------------------------------------------------------------------
/assets/templates/quick/src/StarterPlayer/StarterCharacterScripts/.gitkeep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/argon-rbx/argon/f516ba421631739ed4726e74dbdbdc5d17d6af70/assets/templates/quick/src/StarterPlayer/StarterCharacterScripts/.gitkeep
--------------------------------------------------------------------------------
/assets/templates/quick/src/StarterPlayer/StarterPlayerScripts/.gitkeep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/argon-rbx/argon/f516ba421631739ed4726e74dbdbdc5d17d6af70/assets/templates/quick/src/StarterPlayer/StarterPlayerScripts/.gitkeep
--------------------------------------------------------------------------------
/assets/templates/quick/src/Workspace/.gitkeep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/argon-rbx/argon/f516ba421631739ed4726e74dbdbdc5d17d6af70/assets/templates/quick/src/Workspace/.gitkeep
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/build.rs:
--------------------------------------------------------------------------------
1 | use anyhow::{Context, Result};
2 | use self_update::backends::github::Update;
3 | use std::{env, fs::File, path::PathBuf};
4 |
5 | fn main() -> Result<()> {
6 | let out_path = PathBuf::from(env::var("OUT_DIR")?).join("Argon.rbxm");
7 |
8 | if !cfg!(feature = "plugin") {
9 | File::create(out_path)?;
10 | return Ok(());
11 | }
12 |
13 | let mut builder = Update::configure();
14 |
15 | if let Ok(token) = env::var("GITHUB_TOKEN") {
16 | builder.auth_token(&token);
17 | } else {
18 | println!("cargo:warning=GITHUB_TOKEN not set, rate limits may apply!")
19 | }
20 |
21 | builder
22 | .repo_owner("argon-rbx")
23 | .repo_name("argon-roblox")
24 | .bin_name("Argon.rbxm")
25 | .bin_install_path(out_path)
26 | .target("");
27 |
28 | builder
29 | .build()?
30 | .download()
31 | .context("Failed to download Argon plugin from GitHub!")?;
32 |
33 | Ok(())
34 | }
35 |
--------------------------------------------------------------------------------
/crates/config-derive/.gitignore:
--------------------------------------------------------------------------------
1 | /target
2 | .DS_Store
3 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/crates/config-derive/rustfmt.toml:
--------------------------------------------------------------------------------
1 | hard_tabs = true
2 |
--------------------------------------------------------------------------------
/crates/config-derive/src/lib.rs:
--------------------------------------------------------------------------------
1 | use proc_macro2::{Ident, TokenStream};
2 | use quote::quote;
3 | use syn::{parse_macro_input, DeriveInput};
4 |
5 | mod util;
6 |
7 | #[proc_macro_derive(Val)]
8 | pub fn derive_val(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
9 | let input = parse_macro_input!(input as DeriveInput);
10 |
11 | let fields = util::get_fields(&input.data);
12 |
13 | let (variants, fmts, impls) = {
14 | let mut defined = vec![];
15 |
16 | let mut variants = TokenStream::new();
17 | let mut fmts = TokenStream::new();
18 | let mut impls = TokenStream::new();
19 |
20 | for field in fields {
21 | let ty = &field.ty;
22 |
23 | let ident = util::get_type_ident(ty).unwrap();
24 |
25 | if defined.contains(&ident) {
26 | continue;
27 | } else {
28 | defined.push(ident.clone());
29 | }
30 |
31 | let variant = {
32 | let variant = ident.to_string();
33 | let variant = variant[0..1].to_uppercase() + &variant[1..];
34 | Ident::new(&variant, ident.span())
35 | };
36 |
37 | variants.extend(quote! {
38 | #variant(#ty),
39 | });
40 |
41 | fmts.extend(quote! {
42 | Value::#variant(v) => write!(f, "{}", v.to_string()),
43 | });
44 |
45 | impls.extend(quote!(
46 | impl From<#ty> for Value {
47 | fn from(value: #ty) -> Self {
48 | Value::#variant(value)
49 | }
50 | }
51 | ))
52 | }
53 |
54 | (variants, fmts, impls)
55 | };
56 |
57 | let expanded = quote! {
58 | #[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
59 | #[serde(untagged)]
60 | pub enum Value {
61 | #variants
62 | }
63 |
64 | impl std::fmt::Display for Value {
65 | fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
66 | match self {
67 | #fmts
68 | }
69 | }
70 | }
71 |
72 | #impls
73 | };
74 |
75 | proc_macro::TokenStream::from(expanded)
76 | }
77 |
78 | #[proc_macro_derive(Iter)]
79 | pub fn derive_iter(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
80 | let input = parse_macro_input!(input as DeriveInput);
81 |
82 | let name = &input.ident;
83 | let data = input.data;
84 | let fields = util::get_fields(&data);
85 |
86 | let arms = {
87 | let mut arms = TokenStream::new();
88 |
89 | for (index, field) in fields.iter().enumerate() {
90 | let ident = field.ident.as_ref().unwrap().to_string();
91 |
92 | arms.extend(quote! {
93 | #index => #ident,
94 | });
95 | }
96 |
97 | arms
98 | };
99 |
100 | let expanded = quote! {
101 | pub struct IntoIter<'a> {
102 | inner: &'a #name,
103 | index: usize,
104 | }
105 |
106 | impl<'a> Iterator for IntoIter<'a> {
107 | type Item = (&'a str, Value);
108 |
109 | fn next(&mut self) -> Option {
110 | let index = match self.index {
111 | #arms
112 | _ => return None,
113 | };
114 |
115 | self.index += 1;
116 |
117 | Some((index, self.inner.get(index).unwrap()))
118 | }
119 | }
120 |
121 | impl<'a> IntoIterator for &'a #name {
122 | type Item = (&'a str, Value);
123 | type IntoIter = IntoIter<'a>;
124 |
125 | fn into_iter(self) -> Self::IntoIter {
126 | IntoIter {
127 | inner: self,
128 | index: 0,
129 | }
130 | }
131 | }
132 | };
133 |
134 | proc_macro::TokenStream::from(expanded)
135 | }
136 |
137 | #[proc_macro_derive(Get)]
138 | pub fn derive_get(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
139 | let input = parse_macro_input!(input as DeriveInput);
140 |
141 | let name = &input.ident;
142 | let data = input.data;
143 | let fields = util::get_fields(&data);
144 |
145 | let arms = {
146 | let mut arms = TokenStream::new();
147 |
148 | for field in fields {
149 | let ident = field.ident.as_ref().unwrap();
150 | let index = ident.to_string();
151 |
152 | arms.extend(quote! {
153 | #index => Some(self.#ident.clone().into()),
154 | });
155 | }
156 |
157 | arms
158 | };
159 |
160 | let expanded = quote! {
161 | impl #name {
162 | pub fn get(&self, index: &str) -> Option {
163 | match index {
164 | #arms
165 | _ => None,
166 | }
167 | }
168 | }
169 | };
170 |
171 | proc_macro::TokenStream::from(expanded)
172 | }
173 |
174 | #[proc_macro_derive(Set)]
175 | pub fn derive_set(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
176 | let input = parse_macro_input!(input as DeriveInput);
177 |
178 | let name = &input.ident;
179 | let data = input.data;
180 | let fields = util::get_fields(&data);
181 |
182 | let arms = {
183 | let mut arms = TokenStream::new();
184 |
185 | for field in fields {
186 | let ident = field.ident.as_ref().unwrap();
187 | let index = ident.to_string();
188 |
189 | arms.extend(quote! {
190 | #index => self.#ident = value.parse()?,
191 | });
192 | }
193 |
194 | arms
195 | };
196 |
197 | let expanded = quote! {
198 | impl #name {
199 | pub fn set(&mut self, index: &str, value: &str) -> Result<(), Box> {
200 | match index {
201 | #arms
202 | _ => return Err(format!("Field: {} does not exist", index).into()),
203 | }
204 |
205 | Ok(())
206 | }
207 | }
208 | };
209 |
210 | proc_macro::TokenStream::from(expanded)
211 | }
212 |
--------------------------------------------------------------------------------
/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/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 |
--------------------------------------------------------------------------------
/crates/json-formatter/.gitignore:
--------------------------------------------------------------------------------
1 | /target
2 | .DS_Store
3 |
--------------------------------------------------------------------------------
/crates/json-formatter/Cargo.lock:
--------------------------------------------------------------------------------
1 | # This file is automatically @generated by Cargo.
2 | # It is not intended for manual editing.
3 | version = 4
4 |
5 | [[package]]
6 | name = "itoa"
7 | version = "1.0.15"
8 | source = "registry+https://github.com/rust-lang/crates.io-index"
9 | checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c"
10 |
11 | [[package]]
12 | name = "json-formatter"
13 | version = "0.1.0"
14 | dependencies = [
15 | "ryu",
16 | "serde_json",
17 | ]
18 |
19 | [[package]]
20 | name = "memchr"
21 | version = "2.7.5"
22 | source = "registry+https://github.com/rust-lang/crates.io-index"
23 | checksum = "32a282da65faaf38286cf3be983213fcf1d2e2a58700e808f83f4ea9a4804bc0"
24 |
25 | [[package]]
26 | name = "proc-macro2"
27 | version = "1.0.95"
28 | source = "registry+https://github.com/rust-lang/crates.io-index"
29 | checksum = "02b3e5e68a3a1a02aad3ec490a98007cbc13c37cbe84a3cd7b8e406d76e7f778"
30 | dependencies = [
31 | "unicode-ident",
32 | ]
33 |
34 | [[package]]
35 | name = "quote"
36 | version = "1.0.40"
37 | source = "registry+https://github.com/rust-lang/crates.io-index"
38 | checksum = "1885c039570dc00dcb4ff087a89e185fd56bae234ddc7f056a945bf36467248d"
39 | dependencies = [
40 | "proc-macro2",
41 | ]
42 |
43 | [[package]]
44 | name = "ryu"
45 | version = "1.0.20"
46 | source = "registry+https://github.com/rust-lang/crates.io-index"
47 | checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f"
48 |
49 | [[package]]
50 | name = "serde"
51 | version = "1.0.219"
52 | source = "registry+https://github.com/rust-lang/crates.io-index"
53 | checksum = "5f0e2c6ed6606019b4e29e69dbaba95b11854410e5347d525002456dbbb786b6"
54 | dependencies = [
55 | "serde_derive",
56 | ]
57 |
58 | [[package]]
59 | name = "serde_derive"
60 | version = "1.0.219"
61 | source = "registry+https://github.com/rust-lang/crates.io-index"
62 | checksum = "5b0276cf7f2c73365f7157c8123c21cd9a50fbbd844757af28ca1f5925fc2a00"
63 | dependencies = [
64 | "proc-macro2",
65 | "quote",
66 | "syn",
67 | ]
68 |
69 | [[package]]
70 | name = "serde_json"
71 | version = "1.0.140"
72 | source = "registry+https://github.com/rust-lang/crates.io-index"
73 | checksum = "20068b6e96dc6c9bd23e01df8827e6c7e1f2fddd43c21810382803c136b99373"
74 | dependencies = [
75 | "itoa",
76 | "memchr",
77 | "ryu",
78 | "serde",
79 | ]
80 |
81 | [[package]]
82 | name = "syn"
83 | version = "2.0.104"
84 | source = "registry+https://github.com/rust-lang/crates.io-index"
85 | checksum = "17b6f705963418cdb9927482fa304bc562ece2fdd4f616084c50b7023b435a40"
86 | dependencies = [
87 | "proc-macro2",
88 | "quote",
89 | "unicode-ident",
90 | ]
91 |
92 | [[package]]
93 | name = "unicode-ident"
94 | version = "1.0.18"
95 | source = "registry+https://github.com/rust-lang/crates.io-index"
96 | checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512"
97 |
--------------------------------------------------------------------------------
/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/json-formatter/rustfmt.toml:
--------------------------------------------------------------------------------
1 | hard_tabs = true
2 |
--------------------------------------------------------------------------------
/crates/json-formatter/src/lib.rs:
--------------------------------------------------------------------------------
1 | use serde_json::ser::Formatter;
2 | use std::io;
3 |
4 | macro_rules! tri {
5 | ($e:expr $(,)?) => {
6 | match $e {
7 | core::result::Result::Ok(val) => val,
8 | core::result::Result::Err(err) => return core::result::Result::Err(err),
9 | }
10 | };
11 | }
12 |
13 | /// This structure pretty prints a JSON value to make it human readable.
14 | #[derive(Clone, Debug)]
15 | pub struct JsonFormatter<'a> {
16 | current_indent: usize,
17 | has_value: bool,
18 | indent: &'a [u8],
19 | array_breaks: bool,
20 | extra_newline: bool,
21 | max_decimals: usize,
22 | }
23 |
24 | impl<'a> JsonFormatter<'a> {
25 | /// Construct a pretty printer formatter that defaults to using two spaces for indentation.
26 | pub fn new() -> Self {
27 | JsonFormatter {
28 | current_indent: 0,
29 | has_value: false,
30 | indent: b" ",
31 | array_breaks: true,
32 | extra_newline: false,
33 | max_decimals: 0,
34 | }
35 | }
36 |
37 | /// Construct a pretty printer formatter that uses the `indent` string for indentation.
38 | pub fn with_indent(mut self, indent: &'a [u8]) -> Self {
39 | self.indent = indent;
40 | self
41 | }
42 |
43 | /// Construct a pretty printer formatter that optionally break arrays into multiple lines.
44 | pub fn with_array_breaks(mut self, array_breaks: bool) -> Self {
45 | self.array_breaks = array_breaks;
46 | self
47 | }
48 |
49 | /// Construct a pretty printer formatter that adds an extra newline at the end.
50 | pub fn with_extra_newline(mut self, extra_newline: bool) -> Self {
51 | self.extra_newline = extra_newline;
52 | self
53 | }
54 |
55 | /// Construct a pretty printer formatter that limits the number of decimal places.
56 | pub fn with_max_decimals(mut self, max_decimals: usize) -> Self {
57 | self.max_decimals = max_decimals;
58 | self
59 | }
60 | }
61 |
62 | impl<'a> Default for JsonFormatter<'a> {
63 | fn default() -> Self {
64 | JsonFormatter::new()
65 | }
66 | }
67 |
68 | impl<'a> Formatter for JsonFormatter<'a> {
69 | #[inline]
70 | fn write_f64(&mut self, writer: &mut W, mut value: f64) -> io::Result<()>
71 | where
72 | W: ?Sized + io::Write,
73 | {
74 | if self.max_decimals > 0 {
75 | let multiplier = 10_f64.powi(self.max_decimals as i32);
76 | value = (value * multiplier).round() / multiplier;
77 | }
78 |
79 | let mut buffer = ryu::Buffer::new();
80 | let s = buffer.format_finite(value);
81 | writer.write_all(s.as_bytes())
82 | }
83 |
84 | #[inline]
85 | fn begin_array(&mut self, writer: &mut W) -> io::Result<()>
86 | where
87 | W: ?Sized + io::Write,
88 | {
89 | self.current_indent += 1;
90 | self.has_value = false;
91 | writer.write_all(b"[")
92 | }
93 |
94 | #[inline]
95 | fn end_array(&mut self, writer: &mut W) -> io::Result<()>
96 | where
97 | W: ?Sized + io::Write,
98 | {
99 | self.current_indent -= 1;
100 |
101 | if self.has_value && self.array_breaks {
102 | tri!(writer.write_all(b"\n"));
103 | tri!(indent(writer, self.current_indent, self.indent));
104 | }
105 |
106 | writer.write_all(b"]")
107 | }
108 |
109 | #[inline]
110 | fn begin_array_value(&mut self, writer: &mut W, first: bool) -> io::Result<()>
111 | where
112 | W: ?Sized + io::Write,
113 | {
114 | if !self.array_breaks {
115 | if !first {
116 | tri!(writer.write_all(b", "));
117 | }
118 |
119 | return Ok(());
120 | }
121 |
122 | tri!(writer.write_all(if first { b"\n" } else { b",\n" }));
123 | indent(writer, self.current_indent, self.indent)
124 | }
125 |
126 | #[inline]
127 | fn end_array_value(&mut self, _writer: &mut W) -> io::Result<()>
128 | where
129 | W: ?Sized + io::Write,
130 | {
131 | self.has_value = true;
132 | Ok(())
133 | }
134 |
135 | #[inline]
136 | fn begin_object(&mut self, writer: &mut W) -> io::Result<()>
137 | where
138 | W: ?Sized + io::Write,
139 | {
140 | self.current_indent += 1;
141 | self.has_value = false;
142 | writer.write_all(b"{")
143 | }
144 |
145 | #[inline]
146 | fn end_object(&mut self, writer: &mut W) -> io::Result<()>
147 | where
148 | W: ?Sized + io::Write,
149 | {
150 | self.current_indent -= 1;
151 |
152 | if self.has_value {
153 | tri!(writer.write_all(b"\n"));
154 | tri!(indent(writer, self.current_indent, self.indent));
155 | }
156 |
157 | tri!(writer.write_all(b"}"));
158 |
159 | if self.current_indent == 0 && self.extra_newline {
160 | writer.write_all(b"\n")
161 | } else {
162 | Ok(())
163 | }
164 | }
165 |
166 | #[inline]
167 | fn begin_object_key(&mut self, writer: &mut W, first: bool) -> io::Result<()>
168 | where
169 | W: ?Sized + io::Write,
170 | {
171 | tri!(writer.write_all(if first { b"\n" } else { b",\n" }));
172 | indent(writer, self.current_indent, self.indent)
173 | }
174 |
175 | #[inline]
176 | fn begin_object_value(&mut self, writer: &mut W) -> io::Result<()>
177 | where
178 | W: ?Sized + io::Write,
179 | {
180 | writer.write_all(b": ")
181 | }
182 |
183 | #[inline]
184 | fn end_object_value(&mut self, _writer: &mut W) -> io::Result<()>
185 | where
186 | W: ?Sized + io::Write,
187 | {
188 | self.has_value = true;
189 | Ok(())
190 | }
191 | }
192 |
193 | fn indent(wr: &mut W, n: usize, s: &[u8]) -> io::Result<()>
194 | where
195 | W: ?Sized + io::Write,
196 | {
197 | for _ in 0..n {
198 | tri!(wr.write_all(s));
199 | }
200 |
201 | Ok(())
202 | }
203 |
--------------------------------------------------------------------------------
/crates/notify-debouncer-full/.gitignore:
--------------------------------------------------------------------------------
1 | /target
2 | .DS_Store
3 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/crates/notify-debouncer-full/rustfmt.toml:
--------------------------------------------------------------------------------
1 | hard_tabs = true
2 |
--------------------------------------------------------------------------------
/crates/notify-debouncer-full/src/cache.rs:
--------------------------------------------------------------------------------
1 | use std::{
2 | collections::HashMap,
3 | path::{Path, PathBuf},
4 | };
5 |
6 | use file_id::{get_file_id, FileId};
7 | use notify::RecursiveMode;
8 | use walkdir::WalkDir;
9 |
10 | /// The interface of a file ID cache.
11 | ///
12 | /// This trait can be implemented for an existing cache, if it already holds `FileId`s.
13 | pub trait FileIdCache {
14 | /// Get a `FileId` from the cache for a given `path`.
15 | ///
16 | /// If the path is not cached, `None` should be returned and there should not be any attempt to read the file ID from disk.
17 | fn cached_file_id(&self, path: &Path) -> Option<&FileId>;
18 |
19 | /// Add a new path to the cache or update its value.
20 | ///
21 | /// This will be called if a new file or directory is created or if an existing file is overridden.
22 | fn add_path(&mut self, path: &Path);
23 |
24 | /// Remove a path from the cache.
25 | ///
26 | /// This will be called if a file or directory is deleted.
27 | fn remove_path(&mut self, path: &Path);
28 |
29 | /// Re-scan all paths.
30 | ///
31 | /// This will be called if the notification back-end has dropped events.
32 | fn rescan(&mut self);
33 | }
34 |
35 | /// A cache to hold the file system IDs of all watched files.
36 | ///
37 | /// The file ID cache uses unique file IDs provided by the file system and is used to stich together
38 | /// rename events in case the notification back-end doesn't emit rename cookies.
39 | #[derive(Debug, Clone, Default)]
40 | pub struct FileIdMap {
41 | paths: HashMap,
42 | roots: Vec<(PathBuf, RecursiveMode)>,
43 | }
44 |
45 | impl FileIdMap {
46 | /// Construct an empty cache.
47 | pub fn new() -> Self {
48 | Default::default()
49 | }
50 |
51 | /// Add a path to the cache.
52 | ///
53 | /// If `recursive_mode` is `Recursive`, all children will be added to the cache as well
54 | /// and all paths will be kept up-to-date in case of changes like new files being added,
55 | /// files being removed or renamed.
56 | pub fn add_root(&mut self, path: impl Into, recursive_mode: RecursiveMode) {
57 | let path = path.into();
58 |
59 | self.roots.push((path.clone(), recursive_mode));
60 |
61 | self.add_path(&path);
62 | }
63 |
64 | /// Remove a path form the cache.
65 | ///
66 | /// If the path was added with `Recursive` mode, all children will also be removed from the cache.
67 | pub fn remove_root(&mut self, path: impl AsRef) {
68 | self.roots.retain(|(root, _)| !root.starts_with(&path));
69 |
70 | self.remove_path(path.as_ref());
71 | }
72 |
73 | fn dir_scan_depth(is_recursive: bool) -> usize {
74 | if is_recursive {
75 | usize::max_value()
76 | } else {
77 | 1
78 | }
79 | }
80 | }
81 |
82 | impl FileIdCache for FileIdMap {
83 | fn cached_file_id(&self, path: &Path) -> Option<&FileId> {
84 | self.paths.get(path)
85 | }
86 |
87 | fn add_path(&mut self, path: &Path) {
88 | let is_recursive = self
89 | .roots
90 | .iter()
91 | .find_map(|(root, recursive_mode)| {
92 | if path.starts_with(root) {
93 | Some(*recursive_mode == RecursiveMode::Recursive)
94 | } else {
95 | None
96 | }
97 | })
98 | .unwrap_or_default();
99 |
100 | for (path, file_id) in WalkDir::new(path)
101 | .follow_links(true)
102 | .max_depth(Self::dir_scan_depth(is_recursive))
103 | .into_iter()
104 | .filter_map(|entry| {
105 | let path = entry.ok()?.into_path();
106 | let file_id = get_file_id(&path).ok()?;
107 | Some((path, file_id))
108 | }) {
109 | self.paths.insert(path, file_id);
110 | }
111 | }
112 |
113 | fn remove_path(&mut self, path: &Path) {
114 | self.paths.retain(|p, _| !p.starts_with(path));
115 | }
116 |
117 | fn rescan(&mut self) {
118 | for (root, _) in self.roots.clone() {
119 | self.add_path(&root);
120 | }
121 | }
122 | }
123 |
124 | /// An implementation of the `FileIdCache` trait that doesn't hold any data.
125 | ///
126 | /// This pseudo cache can be used to disable the file tracking using file system IDs.
127 | pub struct NoCache;
128 |
129 | impl FileIdCache for NoCache {
130 | fn cached_file_id(&self, _path: &Path) -> Option<&FileId> {
131 | None
132 | }
133 |
134 | fn add_path(&mut self, _path: &Path) {}
135 |
136 | fn remove_path(&mut self, _path: &Path) {}
137 |
138 | fn rescan(&mut self) {}
139 | }
140 |
--------------------------------------------------------------------------------
/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/profiling/.gitignore:
--------------------------------------------------------------------------------
1 | /target
2 | .DS_Store
3 |
--------------------------------------------------------------------------------
/crates/profiling/Cargo.toml:
--------------------------------------------------------------------------------
1 | [workspace]
2 | members = ["profiling", "profiling-procmacros", "."]
3 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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/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/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 |
--------------------------------------------------------------------------------
/crates/profiling/rustfmt.toml:
--------------------------------------------------------------------------------
1 | hard_tabs = true
2 |
--------------------------------------------------------------------------------
/crates/self_update/.gitignore:
--------------------------------------------------------------------------------
1 | /target
2 | .DS_Store
3 |
--------------------------------------------------------------------------------
/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/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/self_update/build.rs:
--------------------------------------------------------------------------------
1 | fn main() {
2 | println!(
3 | "cargo:rustc-env=TARGET={}",
4 | std::env::var("TARGET").unwrap()
5 | );
6 | }
7 |
--------------------------------------------------------------------------------
/crates/self_update/rustfmt.toml:
--------------------------------------------------------------------------------
1 | hard_tabs = true
2 |
--------------------------------------------------------------------------------
/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", ; rel="preconnect" "##;
72 | let link = find_rel_next_link(val);
73 | assert!(link.is_none());
74 | }
75 | }
76 |
--------------------------------------------------------------------------------
/crates/self_update/src/errors.rs:
--------------------------------------------------------------------------------
1 | /*!
2 | Error type, conversions, and macros
3 |
4 | */
5 | #[cfg(feature = "archive-zip")]
6 | use zip::result::ZipError;
7 |
8 | pub type Result = std::result::Result;
9 |
10 | #[derive(Debug)]
11 | pub enum Error {
12 | Update(String),
13 | Network(String),
14 | Release(String),
15 | Config(String),
16 | Io(std::io::Error),
17 | #[cfg(feature = "archive-zip")]
18 | Zip(ZipError),
19 | Json(serde_json::Error),
20 | Reqwest(reqwest::Error),
21 | SemVer(semver::Error),
22 | ArchiveNotEnabled(String),
23 | #[cfg(feature = "signatures")]
24 | NoSignatures(crate::ArchiveKind),
25 | #[cfg(feature = "signatures")]
26 | Signature(zipsign_api::ZipsignError),
27 | #[cfg(feature = "signatures")]
28 | NonUTF8,
29 | }
30 |
31 | impl std::fmt::Display for Error {
32 | fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
33 | use Error::*;
34 | match *self {
35 | Update(ref s) => write!(f, "UpdateError: {}", s),
36 | Network(ref s) => write!(f, "NetworkError: {}", s),
37 | Release(ref s) => write!(f, "ReleaseError: {}", s),
38 | Config(ref s) => write!(f, "ConfigError: {}", s),
39 | Io(ref e) => write!(f, "IoError: {}", e),
40 | Json(ref e) => write!(f, "JsonError: {}", e),
41 | Reqwest(ref e) => write!(f, "ReqwestError: {}", e),
42 | SemVer(ref e) => write!(f, "SemVerError: {}", e),
43 | #[cfg(feature = "archive-zip")]
44 | Zip(ref e) => write!(f, "ZipError: {}", e),
45 | ArchiveNotEnabled(ref s) => write!(f, "ArchiveNotEnabled: Archive extension '{}' not supported, please enable 'archive-{}' feature!", s, s),
46 | #[cfg(feature = "signatures")]
47 | NoSignatures(kind) => {
48 | write!(f, "No signature verification implemented for {:?} files", kind)
49 | }
50 | #[cfg(feature = "signatures")]
51 | Signature(ref e) => write!(f, "SignatureError: {}", e),
52 | #[cfg(feature = "signatures")]
53 | NonUTF8 => write!(f, "Cannot verify signature of a file with a non-UTF-8 name"),
54 | }
55 | }
56 | }
57 |
58 | impl std::error::Error for Error {
59 | fn description(&self) -> &str {
60 | "Self Update Error"
61 | }
62 |
63 | fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
64 | Some(match *self {
65 | Error::Io(ref e) => e,
66 | Error::Json(ref e) => e,
67 | Error::Reqwest(ref e) => e,
68 | Error::SemVer(ref e) => e,
69 | #[cfg(feature = "signatures")]
70 | Error::Signature(ref e) => e,
71 | _ => return None,
72 | })
73 | }
74 | }
75 |
76 | impl From for Error {
77 | fn from(e: std::io::Error) -> Error {
78 | Error::Io(e)
79 | }
80 | }
81 |
82 | impl From for Error {
83 | fn from(e: serde_json::Error) -> Error {
84 | Error::Json(e)
85 | }
86 | }
87 |
88 | impl From for Error {
89 | fn from(e: reqwest::Error) -> Error {
90 | Error::Reqwest(e)
91 | }
92 | }
93 |
94 | impl From for Error {
95 | fn from(e: semver::Error) -> Error {
96 | Error::SemVer(e)
97 | }
98 | }
99 |
100 | #[cfg(feature = "archive-zip")]
101 | impl From for Error {
102 | fn from(e: ZipError) -> Error {
103 | Error::Zip(e)
104 | }
105 | }
106 |
107 | #[cfg(feature = "signatures")]
108 | impl From for Error {
109 | fn from(e: zipsign_api::ZipsignError) -> Error {
110 | Error::Signature(e)
111 | }
112 | }
113 |
--------------------------------------------------------------------------------
/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/self_update/src/version.rs:
--------------------------------------------------------------------------------
1 | /*! Semver version checks
2 |
3 | The following functions compare two semver compatible version strings.
4 | */
5 | use crate::errors::*;
6 | use semver::Version;
7 |
8 | /// Check if a version is greater than the current
9 | pub fn bump_is_greater(current: &str, other: &str) -> Result {
10 | Ok(Version::parse(other)? > Version::parse(current)?)
11 | }
12 |
13 | /// Check if a new version is compatible with the current
14 | pub fn bump_is_compatible(current: &str, other: &str) -> Result {
15 | let current = Version::parse(current)?;
16 | let other = Version::parse(other)?;
17 | Ok(if other.major == 0 && current.major == 0 {
18 | current.minor == other.minor && other.patch > current.patch
19 | } else if other.major > 0 {
20 | current.major == other.major
21 | && ((other.minor > current.minor)
22 | || (current.minor == other.minor && other.patch > current.patch))
23 | } else {
24 | false
25 | })
26 | }
27 |
28 | /// Check if a new version is a major bump
29 | pub fn bump_is_major(current: &str, other: &str) -> Result {
30 | let current = Version::parse(current)?;
31 | let other = Version::parse(other)?;
32 | Ok(other.major > current.major)
33 | }
34 |
35 | /// Check if a new version is a minor bump
36 | pub fn bump_is_minor(current: &str, other: &str) -> Result {
37 | let current = Version::parse(current)?;
38 | let other = Version::parse(other)?;
39 | Ok(current.major == other.major && other.minor > current.minor)
40 | }
41 |
42 | /// Check if a new version is a patch bump
43 | pub fn bump_is_patch(current: &str, other: &str) -> Result {
44 | let current = Version::parse(current)?;
45 | let other = Version::parse(other)?;
46 | Ok(current.major == other.major && current.minor == other.minor && other.patch > current.patch)
47 | }
48 |
49 | #[cfg(test)]
50 | mod test {
51 | use super::*;
52 |
53 | #[test]
54 | fn test_bump_greater() {
55 | assert!(bump_is_greater("1.2.0", "1.2.3").unwrap());
56 | assert!(bump_is_greater("0.2.0", "1.2.3").unwrap());
57 | assert!(bump_is_greater("0.2.0", "0.2.3").unwrap());
58 | }
59 |
60 | #[test]
61 | fn test_bump_is_compatible() {
62 | assert!(!bump_is_compatible("1.2.0", "2.3.1").unwrap());
63 | assert!(!bump_is_compatible("0.2.0", "2.3.1").unwrap());
64 | assert!(!bump_is_compatible("1.2.3", "3.3.0").unwrap());
65 | assert!(!bump_is_compatible("1.2.3", "0.2.0").unwrap());
66 | assert!(!bump_is_compatible("0.2.0", "0.3.0").unwrap());
67 | assert!(!bump_is_compatible("0.3.0", "0.2.0").unwrap());
68 | assert!(!bump_is_compatible("1.2.3", "1.1.0").unwrap());
69 | assert!(bump_is_compatible("1.2.0", "1.2.3").unwrap());
70 | assert!(bump_is_compatible("0.2.0", "0.2.3").unwrap());
71 | assert!(bump_is_compatible("1.2.0", "1.3.3").unwrap());
72 | }
73 |
74 | #[test]
75 | fn test_bump_is_major() {
76 | assert!(bump_is_major("1.2.0", "2.3.1").unwrap());
77 | assert!(bump_is_major("0.2.0", "2.3.1").unwrap());
78 | assert!(bump_is_major("1.2.3", "3.3.0").unwrap());
79 | assert!(!bump_is_major("1.2.3", "1.2.0").unwrap());
80 | assert!(!bump_is_major("1.2.3", "0.2.0").unwrap());
81 | }
82 |
83 | #[test]
84 | fn test_bump_is_minor() {
85 | assert!(!bump_is_minor("1.2.0", "2.3.1").unwrap());
86 | assert!(!bump_is_minor("0.2.0", "2.3.1").unwrap());
87 | assert!(!bump_is_minor("1.2.3", "3.3.0").unwrap());
88 | assert!(bump_is_minor("1.2.3", "1.3.0").unwrap());
89 | assert!(bump_is_minor("0.2.3", "0.4.0").unwrap());
90 | }
91 |
92 | #[test]
93 | fn test_bump_is_patch() {
94 | assert!(!bump_is_patch("1.2.0", "2.3.1").unwrap());
95 | assert!(!bump_is_patch("0.2.0", "2.3.1").unwrap());
96 | assert!(!bump_is_patch("1.2.3", "3.3.0").unwrap());
97 | assert!(!bump_is_patch("1.2.3", "1.2.3").unwrap());
98 | assert!(bump_is_patch("1.2.0", "1.2.3").unwrap());
99 | assert!(bump_is_patch("0.2.3", "0.2.4").unwrap());
100 | }
101 | }
102 |
--------------------------------------------------------------------------------
/rustfmt.toml:
--------------------------------------------------------------------------------
1 | hard_tabs = true
2 | max_width = 120
3 |
--------------------------------------------------------------------------------
/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/cli/config.rs:
--------------------------------------------------------------------------------
1 | use anyhow::{anyhow, bail, Result};
2 | use clap::{Parser, ValueEnum};
3 | use colored::Colorize;
4 | use open;
5 | use std::{env, fs::File, path::PathBuf};
6 |
7 | use crate::{
8 | argon_info,
9 | config::{Config as ArgonConfig, ConfigKind},
10 | ext::PathExt,
11 | logger, util,
12 | };
13 |
14 | /// Edit global or workspace config with editor or CLI
15 | #[derive(Parser)]
16 | pub struct Config {
17 | /// Setting to change (if left empty config will be opened)
18 | #[arg()]
19 | setting: Option,
20 |
21 | /// Value to set setting to (if left empty default value will be used)
22 | #[arg()]
23 | value: Option,
24 |
25 | /// List all available settings
26 | #[arg(short, long)]
27 | list: bool,
28 |
29 | /// Restore all settings to default values
30 | #[arg(short, long)]
31 | default: bool,
32 |
33 | /// Export current config to the custom file
34 | #[arg(short, long)]
35 | export: Option,
36 |
37 | /// Which config file to work with (`global` or `workspace`)
38 | #[arg(short, long, hide_possible_values = true)]
39 | config: Option,
40 | }
41 |
42 | impl Config {
43 | pub fn main(self) -> Result<()> {
44 | let config = ArgonConfig::new();
45 |
46 | let config_kind = match self.config.unwrap_or_default() {
47 | ConfigType::Default => ConfigKind::Default,
48 | ConfigType::Global => ConfigKind::Global(util::get_argon_dir()?.join("config.toml")),
49 | ConfigType::Workspace => ConfigKind::Workspace(env::current_dir()?.join("argon.toml")),
50 | };
51 |
52 | if *config.kind() == ConfigKind::Default
53 | || (config_kind != ConfigKind::Default && *config.kind() != config_kind)
54 | {
55 | drop(config);
56 | ArgonConfig::load_virtual(config_kind)?;
57 | } else {
58 | drop(config);
59 | };
60 |
61 | let config = ArgonConfig::new();
62 |
63 | if self.list {
64 | argon_info!(
65 | "List of all available config options:\n\n{}\nVisit {} to learn more details!",
66 | config.list(),
67 | "https://argon.wiki/docs/configuration#global-config".bold()
68 | );
69 |
70 | return Ok(());
71 | }
72 |
73 | let config_path = config
74 | .kind()
75 | .path()
76 | .ok_or(anyhow!("Resolve all config errors before using this command!"))?
77 | .to_owned();
78 |
79 | if self.default {
80 | if config_path.exists() {
81 | File::create(config_path)?;
82 | }
83 |
84 | argon_info!(
85 | "Restored all settings to default values in {} config",
86 | config.kind().to_string().bold()
87 | );
88 |
89 | return Ok(());
90 | }
91 |
92 | if let Some(path) = self.export {
93 | config.save(&path)?;
94 |
95 | argon_info!(
96 | "Exported {} to {} config",
97 | config.kind().to_string().bold(),
98 | path.to_string().bold()
99 | );
100 |
101 | return Ok(());
102 | }
103 |
104 | match (self.setting, self.value) {
105 | (Some(setting), Some(value)) => {
106 | drop(config);
107 | let mut config = ArgonConfig::new_mut();
108 |
109 | if config.has_setting(&setting) {
110 | if let Err(err) = config.set(&setting, &value) {
111 | bail!("Failed to parse value: {}", err);
112 | }
113 |
114 | config.save(&config_path)?;
115 |
116 | argon_info!(
117 | "Set {} setting to {} in {} config",
118 | setting.bold(),
119 | value.bold(),
120 | config.kind().to_string().bold()
121 | );
122 | } else {
123 | bail!("Setting {} does not exist", setting.bold());
124 | }
125 | }
126 | (Some(setting), None) => {
127 | let default = ArgonConfig::default();
128 |
129 | if default.has_setting(&setting) {
130 | drop(config);
131 | let mut config = ArgonConfig::new_mut();
132 |
133 | config
134 | .set(&setting, &default.get(&setting).unwrap().to_string())
135 | .unwrap();
136 |
137 | config.save(&config_path)?;
138 |
139 | argon_info!(
140 | "Set {} to its default value in {} config",
141 | setting.bold(),
142 | config.kind().to_string().bold()
143 | );
144 | } else {
145 | bail!("Setting {} does not exist", setting.bold());
146 | }
147 | }
148 | _ => {
149 | if !config_path.exists() {
150 | let create_config = logger::prompt(
151 | &format!(
152 | "{} config does not exist. Would you like to create one?",
153 | config.kind().to_string().bold()
154 | ),
155 | true,
156 | );
157 |
158 | if create_config {
159 | File::create(&config_path)?;
160 | } else {
161 | return Ok(());
162 | }
163 | }
164 |
165 | argon_info!("Opened config file. Manually go to: {}", config_path.to_string().bold());
166 |
167 | open::that(config_path)?;
168 | }
169 | }
170 |
171 | Ok(())
172 | }
173 | }
174 |
175 | #[derive(Clone, Default, ValueEnum, PartialEq)]
176 | enum ConfigType {
177 | #[default]
178 | Default,
179 | Global,
180 | Workspace,
181 | }
182 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/src/cli/exec.rs:
--------------------------------------------------------------------------------
1 | use anyhow::Result;
2 | use clap::Parser;
3 | use reqwest::{blocking::Client, header::CONTENT_TYPE};
4 | use serde::Serialize;
5 | use std::{fs, path::MAIN_SEPARATOR};
6 |
7 | use crate::{argon_error, argon_info, sessions};
8 |
9 | /// Execute Luau code in Roblox Studio (requires running session)
10 | #[derive(Parser)]
11 | pub struct Exec {
12 | /// Luau code to execute (can be file path)
13 | #[arg()]
14 | code: String,
15 |
16 | /// Session identifier
17 | #[arg()]
18 | session: Option,
19 |
20 | /// Focus Roblox Studio window when executing code
21 | #[arg(short, long)]
22 | focus: bool,
23 |
24 | /// Launch Roblox Studio, run code and return the result
25 | #[arg(short, long)]
26 | standalone: bool,
27 |
28 | /// Server host name
29 | #[arg(short = 'H', long)]
30 | host: Option,
31 |
32 | /// Server port
33 | #[arg(short = 'P', long)]
34 | port: Option,
35 | }
36 |
37 | impl Exec {
38 | pub fn main(self) -> Result<()> {
39 | let code = if self.is_path() {
40 | fs::read_to_string(self.code)?
41 | } else {
42 | self.code
43 | };
44 |
45 | if self.standalone {
46 | // TODO: Implement standalone mode
47 | argon_error!("Standalone mode is not implemented yet!");
48 | } else if let Some(session) = sessions::get(self.session, self.host, self.port)? {
49 | let address = session.get_address().or_else(|| {
50 | sessions::get_all()
51 | .unwrap_or_default()
52 | .into_iter()
53 | .find_map(|(_, session)| session.get_address())
54 | });
55 |
56 | if let Some(address) = address {
57 | let url = format!("{}/exec", address);
58 |
59 | let body = rmp_serde::to_vec(&Request {
60 | code: code.to_owned(),
61 | focus: if cfg!(not(target_os = "windows")) {
62 | self.focus
63 | } else {
64 | false
65 | },
66 | })?;
67 |
68 | let response = Client::default()
69 | .post(url)
70 | .header(CONTENT_TYPE, "application/msgpack")
71 | .body(body)
72 | .send();
73 |
74 | match response {
75 | Ok(_) => argon_info!("Code executed successfully!"),
76 | Err(err) => argon_error!("Code execution failed: {}", err),
77 | }
78 |
79 | #[cfg(target_os = "windows")]
80 | if self.focus {
81 | crate::studio::focus(None)?;
82 | }
83 | } else {
84 | argon_error!("Code execution failed: running session does not have an address");
85 | }
86 | } else {
87 | argon_error!("Code execution failed: no running session was found");
88 | }
89 |
90 | Ok(())
91 | }
92 |
93 | fn is_path(&self) -> bool {
94 | if self.code.contains('\n') {
95 | return false;
96 | }
97 |
98 | if !self.code.contains(MAIN_SEPARATOR) {
99 | return false;
100 | }
101 |
102 | true
103 | }
104 | }
105 |
106 | #[derive(Serialize)]
107 | struct Request {
108 | code: String,
109 | focus: bool,
110 | }
111 |
--------------------------------------------------------------------------------
/src/cli/init.rs:
--------------------------------------------------------------------------------
1 | use anyhow::Result;
2 | use clap::{ArgAction, Parser};
3 | use colored::Colorize;
4 | use std::path::PathBuf;
5 |
6 | use crate::{
7 | argon_error, argon_info,
8 | config::Config,
9 | ext::PathExt,
10 | logger, project, stats,
11 | workspace::{self, WorkspaceConfig, WorkspaceLicense},
12 | };
13 |
14 | /// Initialize a new Argon project
15 | #[derive(Parser)]
16 | pub struct Init {
17 | /// Project path
18 | #[arg()]
19 | project: Option,
20 |
21 | /// Workspace template
22 | #[arg(short = 'T', long)]
23 | template: Option,
24 |
25 | /// Workspace license
26 | #[arg(short, long)]
27 | license: Option,
28 |
29 | /// Configure Git
30 | #[arg(
31 | short,
32 | long,
33 | default_missing_value("true"),
34 | hide_possible_values = true,
35 | num_args(0..=1),
36 | action = ArgAction::Set,
37 | )]
38 | git: Option,
39 |
40 | /// Setup Wally
41 | #[arg(
42 | short,
43 | long,
44 | default_missing_value("true"),
45 | hide_possible_values = true,
46 | num_args(0..=1),
47 | action = ArgAction::Set,
48 | )]
49 | wally: Option,
50 |
51 | /// Setup selene
52 | #[arg(
53 | short,
54 | long,
55 | default_missing_value("true"),
56 | hide_possible_values = true,
57 | num_args(0..=1),
58 | action = ArgAction::Set,
59 | )]
60 | selene: Option,
61 |
62 | /// Include docs (README, CHANGELOG, etc.)
63 | #[arg(
64 | short,
65 | long,
66 | default_missing_value("true"),
67 | hide_possible_values = true,
68 | num_args(0..=1),
69 | action = ArgAction::Set,
70 | )]
71 | docs: Option,
72 |
73 | /// Initialize using roblox-ts
74 | #[arg(
75 | short,
76 | long,
77 | default_missing_value("true"),
78 | hide_possible_values = true,
79 | num_args(0..=1),
80 | action = ArgAction::Set,
81 | )]
82 | ts: Option,
83 | }
84 |
85 | impl Init {
86 | pub fn main(self) -> Result<()> {
87 | let project_path = project::resolve(self.project.clone().unwrap_or_default())?;
88 |
89 | Config::load_workspace(project_path.get_parent());
90 | let config = Config::new();
91 |
92 | let project = self.project.unwrap_or_default();
93 | let template = self.template.unwrap_or(config.template.clone());
94 | let git = self.git.unwrap_or(config.use_git);
95 | let wally = self.wally.unwrap_or(config.use_wally);
96 | let selene = self.selene.unwrap_or(config.use_selene);
97 | let docs = self.docs.unwrap_or(config.include_docs);
98 | let ts = self.ts.unwrap_or(config.ts_mode);
99 |
100 | let license = WorkspaceLicense {
101 | force: self.license.is_some(),
102 | inner: &self.license.unwrap_or(config.license.clone()),
103 | };
104 |
105 | let mut workspace_config = WorkspaceConfig {
106 | project: &project,
107 | template: &template,
108 | license,
109 | git,
110 | wally,
111 | selene,
112 | docs,
113 | rojo_mode: config.rojo_mode,
114 | use_lua: config.lua_extension,
115 | };
116 |
117 | if ts {
118 | if let Some(path) = workspace::init_ts(workspace_config)? {
119 | let path = path.resolve()?.join("default.project.json");
120 |
121 | argon_info!(
122 | "Successfully initialized roblox-ts project: {}",
123 | path.to_string().bold()
124 | );
125 | }
126 |
127 | return Ok(());
128 | }
129 |
130 | if project_path.exists() {
131 | argon_error!("Project {} already exists!", project_path.to_string().bold());
132 |
133 | if !logger::prompt("Would you like to continue and add potentially missing files?", false) {
134 | return Ok(());
135 | }
136 | }
137 |
138 | workspace_config.project = &project_path;
139 | workspace::init(workspace_config)?;
140 |
141 | argon_info!("Successfully initialized project: {}", project_path.to_string().bold());
142 |
143 | stats::projects_created(1);
144 |
145 | Ok(())
146 | }
147 | }
148 |
--------------------------------------------------------------------------------
/src/cli/mod.rs:
--------------------------------------------------------------------------------
1 | use anyhow::Result;
2 | use clap::{ColorChoice, Parser, Subcommand};
3 | use clap_verbosity_flag::Verbosity;
4 | use env_logger::fmt::WriteStyle;
5 | use log::LevelFilter;
6 | use std::env;
7 |
8 | use crate::util;
9 |
10 | mod build;
11 | mod config;
12 | mod debug;
13 | mod doc;
14 | mod exec;
15 | mod init;
16 | mod plugin;
17 | mod serve;
18 | mod sourcemap;
19 | mod stop;
20 | mod studio;
21 | mod update;
22 |
23 | macro_rules! about {
24 | () => {
25 | concat!("Argon ", env!("CARGO_PKG_VERSION"))
26 | };
27 | }
28 |
29 | macro_rules! long_about {
30 | () => {
31 | concat!(
32 | "Argon ",
33 | env!("CARGO_PKG_VERSION"),
34 | "\n",
35 | env!("CARGO_PKG_DESCRIPTION"),
36 | "\n",
37 | "Made with <3 by ",
38 | env!("CARGO_PKG_AUTHORS")
39 | )
40 | };
41 | }
42 |
43 | #[derive(Parser)]
44 | #[clap(about = about!(), long_about = long_about!(), version)]
45 | pub struct Cli {
46 | #[command(subcommand)]
47 | command: Commands,
48 |
49 | #[command(flatten)]
50 | verbose: Verbosity,
51 |
52 | /// Automatically answer to any prompts
53 | #[arg(short, long, global = true)]
54 | yes: bool,
55 |
56 | /// Print full backtrace on panic
57 | #[arg(short = 'B', long, global = true)]
58 | backtrace: bool,
59 |
60 | #[arg(long, hide = true, global = true)]
61 | profile: bool,
62 |
63 | /// Output coloring: auto, always, never
64 | #[arg(
65 | long,
66 | short = 'C',
67 | global = true,
68 | value_name = "WHEN",
69 | default_value = "auto",
70 | hide_default_value = true,
71 | hide_possible_values = true
72 | )]
73 | pub color: ColorChoice,
74 | }
75 |
76 | impl Cli {
77 | pub fn new() -> Cli {
78 | Cli::parse()
79 | }
80 |
81 | pub fn profile(&self) -> bool {
82 | self.profile
83 | }
84 |
85 | pub fn yes(&self) -> bool {
86 | if env::var("RUST_YES").is_ok() {
87 | return util::env_yes();
88 | }
89 |
90 | self.yes
91 | }
92 |
93 | pub fn backtrace(&self) -> bool {
94 | if env::var("RUST_BACKTRACE").is_ok() {
95 | return util::env_backtrace();
96 | }
97 |
98 | self.backtrace
99 | }
100 |
101 | pub fn verbosity(&self) -> LevelFilter {
102 | if env::var("RUST_VERBOSE").is_ok() {
103 | return util::env_verbosity();
104 | }
105 |
106 | self.verbose.log_level_filter()
107 | }
108 |
109 | pub fn log_style(&self) -> WriteStyle {
110 | if env::var("RUST_LOG_STYLE").is_ok() {
111 | return util::env_log_style();
112 | }
113 |
114 | match self.color {
115 | ColorChoice::Always => WriteStyle::Always,
116 | ColorChoice::Never => WriteStyle::Never,
117 | _ => WriteStyle::Auto,
118 | }
119 | }
120 |
121 | pub fn main(self) -> Result<()> {
122 | match self.command {
123 | Commands::Init(command) => command.main(),
124 | Commands::Serve(command) => command.main(),
125 | Commands::Build(command) => command.main(),
126 | Commands::Sourcemap(command) => command.main(),
127 | Commands::Stop(command) => command.main(),
128 | Commands::Studio(command) => command.main(),
129 | Commands::Debug(command) => command.main(),
130 | Commands::Exec(command) => command.main(),
131 | Commands::Update(command) => command.main(),
132 | Commands::Plugin(command) => command.main(),
133 | Commands::Config(command) => command.main(),
134 | Commands::Doc(command) => command.main(),
135 | }
136 | }
137 | }
138 |
139 | #[derive(Subcommand)]
140 | pub enum Commands {
141 | Init(init::Init),
142 | Serve(serve::Serve),
143 | Build(build::Build),
144 | Sourcemap(sourcemap::Sourcemap),
145 | Stop(stop::Stop),
146 | Studio(studio::Studio),
147 | Debug(debug::Debug),
148 | Exec(exec::Exec),
149 | Update(update::Update),
150 | Plugin(plugin::Plugin),
151 | Config(config::Config),
152 | Doc(doc::Doc),
153 | }
154 |
--------------------------------------------------------------------------------
/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/cli/serve.rs:
--------------------------------------------------------------------------------
1 | use anyhow::{bail, Result};
2 | use clap::Parser;
3 | use colored::Colorize;
4 | use log::{debug, info};
5 | use std::{path::PathBuf, process, sync::Arc, thread};
6 |
7 | use crate::{
8 | argon_error, argon_info, argon_warn,
9 | config::Config,
10 | core::Core,
11 | ext::PathExt,
12 | integration,
13 | program::{Program, ProgramName},
14 | project::{self, Project},
15 | server::{self, Server},
16 | sessions,
17 | };
18 |
19 | /// Start local server and listen for file changes
20 | #[derive(Parser)]
21 | pub struct Serve {
22 | /// Project path
23 | #[arg()]
24 | project: Option,
25 |
26 | /// Session identifier
27 | #[arg()]
28 | session: Option,
29 |
30 | /// Server host name
31 | #[arg(short = 'H', long)]
32 | host: Option,
33 |
34 | /// Server port
35 | #[arg(short = 'P', long)]
36 | port: Option,
37 |
38 | /// Generate sourcemap every time files change
39 | #[arg(short, long)]
40 | sourcemap: bool,
41 |
42 | /// Whether to run using roblox-ts
43 | #[arg(short, long)]
44 | ts: bool,
45 |
46 | /// Run Argon asynchronously
47 | #[arg(short = 'A', long = "async")]
48 | run_async: bool,
49 |
50 | /// Spawn the Argon child process (internal)
51 | #[arg(long, hide = true)]
52 | argon_spawn: bool,
53 | }
54 |
55 | impl Serve {
56 | pub fn main(self) -> Result<()> {
57 | let project_path = project::resolve(self.project.clone().unwrap_or_default())?;
58 |
59 | Config::load_workspace(project_path.get_parent());
60 | let config = Config::new();
61 |
62 | if !self.argon_spawn && (self.run_async || config.run_async) {
63 | return self.spawn();
64 | }
65 |
66 | let sourcemap_path = if self.sourcemap || config.with_sourcemap {
67 | Some(project_path.with_file_name("sourcemap.json"))
68 | } else {
69 | None
70 | };
71 |
72 | if !project_path.exists() {
73 | bail!(
74 | "No project files found in {}. Run {} to create new one",
75 | project_path.get_parent().to_string().bold(),
76 | "argon init".bold(),
77 | );
78 | }
79 |
80 | let project = Project::load(&project_path)?;
81 |
82 | if !project.is_place() {
83 | bail!("Cannot serve non-place project!");
84 | }
85 |
86 | let use_wally = config.use_wally || (config.detect_project && project.is_wally());
87 | let use_ts = self.ts || config.ts_mode || (config.detect_project && project.is_ts());
88 |
89 | if use_wally {
90 | integration::check_wally_packages(&project.workspace_dir);
91 | }
92 |
93 | if use_ts {
94 | debug!("Starting roblox-ts");
95 |
96 | let working_dir = project_path.get_parent();
97 |
98 | let child = Program::new(ProgramName::Npx)
99 | .message("Failed to serve roblox-ts project")
100 | .current_dir(working_dir)
101 | .arg("rbxtsc")
102 | .arg("--watch")
103 | .spawn()?;
104 |
105 | if child.is_none() {
106 | return Ok(());
107 | }
108 | }
109 |
110 | let core = Core::new(project, true)?;
111 | let host = self.host.unwrap_or(core.host().unwrap_or(config.host.clone()));
112 | let mut port = self.port.unwrap_or(core.port().unwrap_or(config.port));
113 |
114 | if !server::is_port_free(&host, port) {
115 | if config.scan_ports {
116 | let new_port = server::get_free_port(&host, port);
117 |
118 | argon_warn!(
119 | "Port {} is already in use, using {} instead!",
120 | port.to_string().bold(),
121 | new_port.to_string().bold()
122 | );
123 |
124 | port = new_port;
125 | } else {
126 | bail!(
127 | "Port {} is already in use! Enable {} setting to use first available port automatically",
128 | port.to_string().bold(),
129 | "scan_ports".bold()
130 | );
131 | }
132 | }
133 |
134 | let core = Arc::new(core);
135 |
136 | if let Some(path) = sourcemap_path {
137 | let core = core.clone();
138 | let queue = core.queue();
139 |
140 | queue.subscribe_internal().unwrap();
141 | core.sourcemap(Some(path.clone()), false)?;
142 |
143 | argon_info!("Generated sourcemap at: {}", path.to_string().bold());
144 |
145 | thread::spawn(move || loop {
146 | let _message = queue.get_change(0).unwrap();
147 |
148 | info!("Regenerating sourcemap..");
149 |
150 | match core.sourcemap(Some(path.clone()), false) {
151 | Ok(()) => (),
152 | Err(err) => {
153 | argon_error!("Failed to regenerate sourcemap: {}", err);
154 | }
155 | }
156 | });
157 | }
158 |
159 | sessions::add(
160 | self.session,
161 | Some(host.clone()),
162 | Some(port),
163 | process::id(),
164 | config.run_async,
165 | )?;
166 |
167 | let server = Server::new(core, &host, port);
168 |
169 | argon_info!(
170 | "Serving on: {}, project: {}",
171 | server::format_address(&host, port).bold(),
172 | project_path.to_string().bold()
173 | );
174 |
175 | server.start()?;
176 |
177 | Ok(())
178 | }
179 |
180 | fn spawn(self) -> Result<()> {
181 | let mut args = vec![String::from("serve")];
182 |
183 | if let Some(project) = self.project {
184 | args.push(project.to_string());
185 | }
186 |
187 | if let Some(session) = self.session {
188 | args.push(session);
189 | }
190 |
191 | if let Some(host) = self.host {
192 | args.push("--host".into());
193 | args.push(host)
194 | }
195 |
196 | if let Some(port) = self.port {
197 | args.push("--port".into());
198 | args.push(port.to_string());
199 | }
200 |
201 | if self.sourcemap {
202 | args.push("--sourcemap".into());
203 | }
204 |
205 | if self.ts {
206 | args.push("--ts".into());
207 | }
208 |
209 | Program::new(ProgramName::Argon).args(args).spawn()?;
210 |
211 | Ok(())
212 | }
213 | }
214 |
--------------------------------------------------------------------------------
/src/cli/sourcemap.rs:
--------------------------------------------------------------------------------
1 | use anyhow::{bail, Result};
2 | use clap::Parser;
3 | use colored::Colorize;
4 | use log::info;
5 | use std::{path::PathBuf, process};
6 |
7 | use crate::{
8 | argon_info,
9 | config::Config,
10 | core::Core,
11 | ext::PathExt,
12 | program::{Program, ProgramName},
13 | project::{self, Project},
14 | sessions,
15 | };
16 |
17 | /// Generate JSON sourcemap of the project
18 | #[derive(Parser)]
19 | pub struct Sourcemap {
20 | /// Project path
21 | #[arg()]
22 | project: Option,
23 |
24 | /// Session identifier
25 | #[arg()]
26 | session: Option,
27 |
28 | /// Output path
29 | #[arg(short, long)]
30 | output: Option,
31 |
32 | /// Regenerate sourcemap every time files change
33 | #[arg(short, long)]
34 | watch: bool,
35 |
36 | /// Whether non-script files should be included
37 | #[arg(short, long)]
38 | non_scripts: bool,
39 |
40 | /// Run Argon asynchronously
41 | #[arg(short = 'A', long = "async")]
42 | run_async: bool,
43 |
44 | /// Spawn the Argon child process (internal)
45 | #[arg(long, hide = true)]
46 | argon_spawn: bool,
47 | }
48 |
49 | impl Sourcemap {
50 | pub fn main(mut self) -> Result<()> {
51 | let project_path = project::resolve(self.project.clone().unwrap_or_default())?;
52 |
53 | Config::load_workspace(project_path.get_parent());
54 | let config = Config::new();
55 |
56 | if self.watch && !self.argon_spawn && (self.run_async || config.run_async) {
57 | return self.spawn();
58 | }
59 |
60 | if !project_path.exists() {
61 | bail!(
62 | "No project files found in {}",
63 | project_path.get_parent().to_string().bold()
64 | );
65 | }
66 |
67 | if let Some(path) = self.output.as_ref() {
68 | if config.smart_paths && (path.is_dir() || path.extension().is_none()) {
69 | let output = if path.get_name().to_lowercase() == "sourcemap" {
70 | path.with_extension("json")
71 | } else {
72 | path.join("sourcemap.json")
73 | };
74 |
75 | self.output = Some(output);
76 | }
77 | }
78 |
79 | let project = Project::load(&project_path)?;
80 | let core = Core::new(project, self.watch)?;
81 |
82 | core.sourcemap(self.output.clone(), self.non_scripts)?;
83 |
84 | if let Some(output) = &self.output {
85 | argon_info!(
86 | "Generated sourcemap of project: {} at: {}",
87 | project_path.to_string().bold(),
88 | output.resolve()?.to_string().bold()
89 | );
90 | }
91 |
92 | if self.watch {
93 | sessions::add(self.session, None, None, process::id(), config.run_async)?;
94 |
95 | if self.output.is_some() {
96 | argon_info!("Watching for changes..");
97 | }
98 |
99 | let queue = core.queue();
100 | queue.subscribe_internal().unwrap();
101 |
102 | loop {
103 | let _message = queue.get_change(0).unwrap();
104 |
105 | info!("Regenerating sourcemap..");
106 | core.sourcemap(self.output.clone(), self.non_scripts)?;
107 | }
108 | }
109 |
110 | Ok(())
111 | }
112 |
113 | fn spawn(self) -> Result<()> {
114 | let mut args = vec![String::from("sourcemap")];
115 |
116 | if let Some(project) = self.project {
117 | args.push(project.to_string())
118 | }
119 |
120 | if let Some(session) = self.session {
121 | args.push(session);
122 | }
123 |
124 | if let Some(output) = self.output {
125 | args.push("--output".into());
126 | args.push(output.to_string())
127 | }
128 |
129 | if self.watch {
130 | args.push("--watch".into())
131 | }
132 |
133 | if self.non_scripts {
134 | args.push("--non-scripts".into())
135 | }
136 |
137 | Program::new(ProgramName::Argon).args(args).spawn()?;
138 |
139 | Ok(())
140 | }
141 | }
142 |
--------------------------------------------------------------------------------
/src/cli/stop.rs:
--------------------------------------------------------------------------------
1 | use anyhow::Result;
2 | use clap::Parser;
3 | use colored::Colorize;
4 | use reqwest::blocking::Client;
5 |
6 | use crate::{argon_info, argon_warn, logger::Table, sessions, util};
7 |
8 | /// Stop Argon session by address, ID or all running sessions
9 | #[derive(Parser)]
10 | pub struct Stop {
11 | /// Session identifier
12 | #[arg()]
13 | session: Vec,
14 |
15 | /// Server host name
16 | #[arg(short = 'H', long)]
17 | host: Option,
18 |
19 | /// Server port
20 | #[arg(short = 'P', long)]
21 | port: Option,
22 |
23 | /// Stop all running session
24 | #[arg(short, long)]
25 | all: bool,
26 |
27 | /// List all running session
28 | #[arg(short, long)]
29 | list: bool,
30 | }
31 |
32 | impl Stop {
33 | pub fn main(self) -> Result<()> {
34 | if self.list {
35 | let sessions = sessions::get_all()?;
36 |
37 | if sessions.is_empty() {
38 | argon_warn!("There are no running sessions");
39 | return Ok(());
40 | }
41 |
42 | let mut table = Table::new();
43 | table.set_header(vec!["ID", "Host", "Port", "PID"]);
44 |
45 | for (id, session) in sessions {
46 | table.add_row(vec![
47 | id,
48 | session.host.unwrap_or("None".into()),
49 | session.port.map(|p| p.to_string()).unwrap_or("None".into()),
50 | session.pid.to_string(),
51 | ]);
52 | }
53 |
54 | argon_info!("All running sessions:\n\n{}", table);
55 |
56 | return Ok(());
57 | }
58 |
59 | if self.all {
60 | let sessions = sessions::get_all()?;
61 |
62 | if sessions.is_empty() {
63 | argon_warn!("There are no running sessions");
64 | return Ok(());
65 | }
66 |
67 | for (_, session) in sessions {
68 | if let Some(address) = session.get_address() {
69 | Self::make_request(&address, session.pid);
70 | } else {
71 | Self::kill_process(session.pid);
72 | }
73 | }
74 |
75 | return sessions::remove_all();
76 | }
77 |
78 | if self.session.is_empty() {
79 | if let Some(session) = sessions::get(None, self.host, self.port)? {
80 | if let Some(address) = session.get_address() {
81 | Self::make_request(&address, session.pid);
82 | } else {
83 | Self::kill_process(session.pid);
84 | }
85 |
86 | sessions::remove(&session)?;
87 | } else {
88 | argon_warn!("There is no matching session to stop");
89 | }
90 | } else {
91 | let sessions = sessions::get_multiple(&self.session)?;
92 |
93 | if sessions.is_empty() {
94 | argon_warn!("There are no running sessions with provided IDs");
95 | } else {
96 | for session in sessions.values() {
97 | if let Some(address) = session.get_address() {
98 | Self::make_request(&address, session.pid);
99 | } else {
100 | Self::kill_process(session.pid);
101 | }
102 | }
103 |
104 | sessions::remove_multiple(&self.session)?;
105 | }
106 | }
107 |
108 | Ok(())
109 | }
110 |
111 | fn make_request(address: &String, pid: u32) {
112 | let url = format!("{}/stop", address);
113 |
114 | match Client::new().post(url).send() {
115 | Ok(_) => argon_info!("Stopped Argon session with address: {}", address.bold()),
116 | Err(_) => {
117 | Self::kill_process(pid);
118 | }
119 | }
120 | }
121 |
122 | fn kill_process(pid: u32) {
123 | util::kill_process(pid);
124 | argon_info!("Stopped Argon process with PID: {}", pid.to_string().bold())
125 | }
126 | }
127 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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/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/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 |
--------------------------------------------------------------------------------
/src/core/processor/read.rs:
--------------------------------------------------------------------------------
1 | use std::path::Path;
2 |
3 | use log::{error, trace};
4 | use rbx_dom_weak::types::Ref;
5 |
6 | use crate::{
7 | core::{
8 | changes::Changes,
9 | meta::SourceKind,
10 | snapshot::{Snapshot, UpdatedSnapshot},
11 | tree::Tree,
12 | },
13 | middleware::{new_snapshot, project::new_snapshot_node},
14 | stats, util,
15 | vfs::Vfs,
16 | };
17 |
18 | pub fn process_changes(id: Ref, tree: &mut Tree, vfs: &Vfs) -> Option {
19 | trace!("Processing changes for instance: {:?}", id);
20 |
21 | let mut changes = Changes::new();
22 |
23 | let meta = tree.get_meta(id)?;
24 | let source = meta.source.get();
25 |
26 | let process_path = |path: &Path| -> Option]