Compare commits
391 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a30a8ecd0f | ||
|
|
7ade19293c | ||
|
|
0289491ccb | ||
|
|
bcadc97de2 | ||
|
|
16633e3c65 | ||
|
|
d12120bab6 | ||
|
|
ac6941f054 | ||
|
|
444dc11b26 | ||
|
|
15939b1647 | ||
|
|
0afb26e143 | ||
|
|
1abf675949 | ||
|
|
85655ca84f | ||
|
|
ae8735c80a | ||
|
|
988efb45b1 | ||
|
|
9bbb1edd79 | ||
|
|
a2adf2b517 | ||
|
|
4deda0e155 | ||
| 4df2d3c5f8 | |||
|
|
4965165ad5 | ||
|
|
68eab3479a | ||
|
|
2a1102fc55 | ||
|
|
02b41133f8 | ||
|
|
d08780fc14 | ||
|
|
b89cc7f398 | ||
|
|
42568b9709 | ||
|
|
87f58e0a55 | ||
|
|
a61a1bef55 | ||
|
|
a99e877b7c | ||
|
|
93e9c51204 | ||
|
|
015b5bda14 | ||
|
|
2b47861a4f | ||
|
|
9b5a07191b | ||
|
|
071b6e7e23 | ||
|
|
31ec216a95 | ||
|
|
ea70d89291 | ||
|
|
03410ced6d | ||
|
|
825726c883 | ||
|
|
54e63d88d4 | ||
|
|
4018c97cb6 | ||
|
|
d0b029f995 | ||
|
|
aabe6d11b2 | ||
|
|
181cc37744 | ||
|
|
cd78f5c02c | ||
|
|
441c469966 | ||
|
|
f3c423d77d | ||
|
|
beb497878b | ||
|
|
6ea95d487c | ||
|
|
80a381dbb1 | ||
|
|
59e36491a5 | ||
|
|
c1326ba06e | ||
|
|
e2633126ee | ||
|
|
5f33435f3c | ||
|
|
54e0ff230b | ||
|
|
4e9e6233ff | ||
|
|
0056849b51 | ||
|
|
2ddb21ec5f | ||
|
|
a4eb65ca3f | ||
|
|
3002d250a1 | ||
|
|
9598553e5d | ||
|
|
7f68d9887b | ||
|
|
e092a7301f | ||
|
|
6dfdfbe514 | ||
|
|
7860f2717f | ||
|
|
60f19df9a0 | ||
|
|
951f0cda0b | ||
|
|
227042d6b1 | ||
|
|
b2c4f550ee | ||
|
|
4ddbefa88f | ||
|
|
d935115591 | ||
|
|
bd2ea42732 | ||
|
|
3bac38ee34 | ||
|
|
a7a4f6d8f2 | ||
|
|
80b6facbd3 | ||
|
|
7dee898400 | ||
|
|
4c4b2dbe17 | ||
|
|
73ed5ae697 | ||
|
|
833320de64 | ||
|
|
0d6ff8ef8a | ||
|
|
55a207a275 | ||
|
|
f33d1f1cc4 | ||
|
|
19ca2b12fc | ||
|
|
b7d3394464 | ||
|
|
8c33100d7a | ||
|
|
80c406f196 | ||
|
|
bc2c76e5e2 | ||
|
|
4a7bddbc09 | ||
|
|
e316fdbaef | ||
|
|
34106f470f | ||
|
|
d9ab0e7de8 | ||
|
|
5ca1573e2e | ||
|
|
c9ce996626 | ||
|
|
73097075d4 | ||
|
|
5e1cab2e75 | ||
|
|
30f439caec | ||
|
|
4b5db4e5a9 | ||
|
|
3fa1d6b09c | ||
|
|
6051a5f1f1 | ||
|
|
5f7dd45361 | ||
|
|
3ca975d81d | ||
|
|
7e2bab921a | ||
|
|
a7b45ee859 | ||
|
|
62f4a1f3c2 | ||
|
|
3d4e387d35 | ||
|
|
2c46640105 | ||
|
|
41443d3989 | ||
|
|
4b3470d30b | ||
|
|
ce71a3df4d | ||
|
|
7232721b87 | ||
|
|
b2f133e6f1 | ||
|
|
87920964d7 | ||
|
|
c7a4f892e3 | ||
|
|
8f9e307930 | ||
|
|
856d43ce69 | ||
|
|
26181a5a1f | ||
|
|
edf87bf9a3 | ||
|
|
5f51538e0b | ||
|
|
48bb760739 | ||
|
|
42121a9fc9 | ||
|
|
02d79a4749 | ||
|
|
ddb26c73bd | ||
|
|
8ff064fe28 | ||
|
|
cf25eb0833 | ||
|
|
5c4260f3ac | ||
|
|
7abf19804c | ||
|
|
df707d5bef | ||
|
|
f3b0b0027e | ||
|
|
106a01223e | ||
|
|
506a60d0be | ||
|
|
4018607b77 | ||
|
|
1cc720ad34 | ||
|
|
73828af715 | ||
|
|
c0a96e3811 | ||
|
|
9d0d76f0a5 | ||
|
|
c7173ac832 | ||
|
|
b12ce47e7e | ||
|
|
269272983b | ||
|
|
6adc5eb9fb | ||
|
|
fd8bc8ae3f | ||
|
|
3369b0d429 | ||
|
|
097d39e8ce | ||
|
|
11fa08e6d6 | ||
|
|
96987af71d | ||
|
|
23327cb3ef | ||
|
|
b43b45be8f | ||
|
|
41994ec82e | ||
|
|
cd14ea7c62 | ||
|
|
9f13bca6b8 | ||
|
|
f4252c3e97 | ||
|
|
6598867d3d | ||
|
|
f39e040a0d | ||
|
|
a3d140269b | ||
|
|
feac29ea40 | ||
|
|
834c8cdbca | ||
|
|
d441fbdf91 | ||
|
|
e897f524dc | ||
|
|
1caf9446d8 | ||
|
|
bfd2c885db | ||
|
|
f467fa4e59 | ||
|
|
41fca4a2bb | ||
|
|
d38f955144 | ||
|
|
010e50a25d | ||
|
|
eab7c607cd | ||
|
|
3cafbf7f1a | ||
|
|
d7277b5a5b | ||
|
|
bb8dd1402d | ||
|
|
539cd0d418 | ||
|
|
0f8e1625d5 | ||
|
|
840e9bedb2 | ||
|
|
e11ad476fc | ||
|
|
c43726bc75 | ||
|
|
c9ab933a23 | ||
|
|
066a0b1668 | ||
|
|
aa68fe412e | ||
|
|
d748ea7e40 | ||
|
|
a7a282078f | ||
|
|
2fad3b588a | ||
|
|
4cb5d4a9c5 | ||
|
|
5b22ef192e | ||
|
|
34024d8524 | ||
|
|
ecc31dea15 | ||
|
|
d0e48d9bdc | ||
|
|
f6fc5599c0 | ||
|
|
89b6666436 | ||
|
|
94d45a2262 | ||
|
|
dc17a185ca | ||
|
|
4915477823 | ||
|
|
8662d2227c | ||
|
|
dd01a9bef3 | ||
|
|
6e320b1fd5 | ||
|
|
6e40993199 | ||
|
|
9d48af2b50 | ||
|
|
28d48a76e3 | ||
|
|
80eb14f9da | ||
|
|
623fa06d52 | ||
|
|
7154113c13 | ||
|
|
0a932ff880 | ||
|
|
7ef4a1ff12 | ||
|
|
ccc52b69d2 | ||
|
|
8139fdc738 | ||
|
|
a4fd53d516 | ||
|
|
27357110b5 | ||
|
|
fde78738b6 | ||
|
|
ce530e795a | ||
|
|
658d211779 | ||
|
|
66c1cd0d93 | ||
|
|
55ac231cec | ||
|
|
67674d53a2 | ||
|
|
8646b2dfce | ||
|
|
a2f68c2e3c | ||
|
|
5b1a090c5e | ||
|
|
e9efa238b0 | ||
|
|
0dabd8a1f6 | ||
|
|
b7a1f82f56 | ||
|
|
2507e096b7 | ||
|
|
b303b0a99c | ||
|
|
342fb57d14 | ||
|
|
a9ca77e27f | ||
|
|
6542304340 | ||
|
|
6b0f7f94b6 | ||
|
|
d87c76a23e | ||
|
|
305423b856 | ||
|
|
4b62190aff | ||
|
|
e17771a6a5 | ||
|
|
bac30ae78b | ||
|
|
c0219922b2 | ||
|
|
b5ed952d5c | ||
|
|
7994bc4909 | ||
|
|
b88d34c639 | ||
|
|
96cb1ee3fd | ||
|
|
003abe86bb | ||
|
|
6ec411a618 | ||
|
|
c7c0903804 | ||
|
|
cdc972a5ce | ||
|
|
17de912608 | ||
|
|
9876508887 | ||
|
|
72d62220e8 | ||
|
|
46ad337fa5 | ||
|
|
7a3ba7721f | ||
|
|
e0198e626b | ||
|
|
142705f386 | ||
|
|
4cb49c7825 | ||
|
|
05adb82dda | ||
|
|
faf7671799 | ||
|
|
d64db329dd | ||
|
|
e34d2339ad | ||
|
|
d196c5091c | ||
|
|
3e83f92532 | ||
|
|
41d7aaf323 | ||
|
|
e110f3726a | ||
|
|
eb5c897ac0 | ||
|
|
e864cf0c7d | ||
|
|
565c12405e | ||
|
|
2a6a8b42a6 | ||
|
|
5cb4cc0d1d | ||
|
|
62eb4f026f | ||
|
|
411d1a89c1 | ||
|
|
6ae0bf366a | ||
|
|
178cdc9dfa | ||
|
|
5bf1f86886 | ||
|
|
e482aba030 | ||
|
|
535e4d42bb | ||
|
|
54398d4c4b | ||
|
|
0987b44e23 | ||
|
|
58098e96d4 | ||
|
|
f649c180cf | ||
|
|
966478b131 | ||
|
|
ca0759a011 | ||
|
|
f1cdf2fe79 | ||
|
|
04fa5e2719 | ||
|
|
eccb95690c | ||
|
|
acf7456371 | ||
|
|
8ea41480b7 | ||
|
|
0d1bc0d7fe | ||
|
|
f9b7774286 | ||
|
|
2e672badf2 | ||
|
|
cd5d6fd15c | ||
|
|
cf76982cfa | ||
|
|
2624ea7d2a | ||
|
|
2e7c4b6dff | ||
|
|
e5dbee1073 | ||
|
|
c06463b61d | ||
|
|
10341e3776 | ||
|
|
824cdc5dcd | ||
|
|
7aa7a35aa5 | ||
|
|
79b57b3359 | ||
|
|
c7aeffe586 | ||
|
|
79c02f2457 | ||
|
|
b9ed68fa9e | ||
|
|
6c6d6c9c8d | ||
|
|
e169d7be68 | ||
|
|
192fd7d4dd | ||
|
|
1f1193e857 | ||
|
|
0a412ade88 | ||
|
|
3cef2fe9aa | ||
|
|
18e53f06fe | ||
|
|
eaac539087 | ||
|
|
57005c4fd5 | ||
|
|
ea58999a2a | ||
|
|
a5a69fd9fc | ||
|
|
f1d0f1c1c9 | ||
|
|
83492d7495 | ||
|
|
10abc2254a | ||
|
|
5d5536a95e | ||
|
|
fe81e55925 | ||
|
|
654690d73e | ||
|
|
256aba4bc1 | ||
|
|
49f8845105 | ||
|
|
12370846b4 | ||
|
|
07637dfe96 | ||
|
|
f389a4a1db | ||
|
|
af077c796c | ||
|
|
1c319f2fa8 | ||
|
|
e8afa03f7b | ||
|
|
9b22545842 | ||
|
|
adc733d25c | ||
|
|
6896257647 | ||
|
|
1d9845a6cb | ||
|
|
8461339e9a | ||
|
|
9904d94e4c | ||
|
|
da25c80d0b | ||
|
|
5fa63733fd | ||
|
|
8b54bf0ba1 | ||
|
|
173dc12cb3 | ||
|
|
e136529ff0 | ||
|
|
75542dacb3 | ||
|
|
07abfbde43 | ||
|
|
96112fe118 | ||
|
|
9d0b313261 | ||
|
|
277ddfa9be | ||
|
|
5d88bdb256 | ||
|
|
8d29b43155 | ||
|
|
cc071a6415 | ||
|
|
8954def25c | ||
|
|
d484098781 | ||
|
|
9f06cbf3a0 | ||
|
|
af4a3ca0af | ||
|
|
43715143e4 | ||
|
|
16aa354d36 | ||
|
|
c739025453 | ||
|
|
f0526d17de | ||
|
|
6cc2e919c0 | ||
|
|
e1f9eaefa9 | ||
|
|
5d62bf9b60 | ||
|
|
4aa5814a0a | ||
|
|
5bca244062 | ||
|
|
7cf57714a4 | ||
|
|
92e6f862ad | ||
|
|
2377f41036 | ||
|
|
26a08f4d9f | ||
|
|
672d207961 | ||
|
|
a3d8e50f26 | ||
|
|
d3abca46a8 | ||
|
|
17fdd18c55 | ||
|
|
e482d038c6 | ||
|
|
d0482a004e | ||
|
|
561a3e3256 | ||
|
|
158dac5e1c | ||
|
|
1413f8c0b6 | ||
|
|
ffb2aa332a | ||
|
|
45e8208e9c | ||
|
|
7f230a8bf4 | ||
|
|
afe26b8c16 | ||
|
|
d153f62b8a | ||
|
|
5c80cd6e50 | ||
|
|
df1aced95d | ||
|
|
5a0a8f5077 | ||
|
|
1c281539e0 | ||
|
|
ef41d14f50 | ||
|
|
aa29397732 | ||
|
|
ca8865a3ce | ||
|
|
7599ef6626 | ||
|
|
43e71f9242 | ||
|
|
aac9b25efe | ||
|
|
532d170585 | ||
|
|
3dcb14013b | ||
|
|
a4c782cd35 | ||
|
|
0779baa0ac | ||
|
|
0849fab644 | ||
|
|
d85bca2e7e | ||
|
|
c7ab6c435c | ||
|
|
98db3b4f08 | ||
|
|
0e7ba839ed | ||
|
|
2c27691e57 | ||
|
|
934506bdfd | ||
|
|
f313fa4ae1 | ||
|
|
78755b6130 | ||
|
|
8bf59e5cbb | ||
|
|
8f71d13714 | ||
|
|
f4a790eb50 | ||
|
|
0d951c8ad1 | ||
|
|
59ef5f05ea |
2
.dir-locals.el
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
((nil . ((eglot-luau-rojo-project-path . "plugin.project.json")
|
||||||
|
(eglot-luau-rojo-sourcemap-enabled . 't))))
|
||||||
@@ -23,4 +23,7 @@ insert_final_newline = true
|
|||||||
insert_final_newline = true
|
insert_final_newline = true
|
||||||
|
|
||||||
[*.lua]
|
[*.lua]
|
||||||
|
indent_style = tab
|
||||||
|
|
||||||
|
[*.luau]
|
||||||
indent_style = tab
|
indent_style = tab
|
||||||
2
.git-blame-ignore-revs
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
# stylua formatting
|
||||||
|
0f8e1625d572a5fe0f7b5c08653ff92cc837d346
|
||||||
1
.gitattributes
vendored
Normal file
@@ -0,0 +1 @@
|
|||||||
|
*.lua linguist-language=Luau
|
||||||
23
.github/workflows/changelog.yml
vendored
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
name: Changelog Check
|
||||||
|
|
||||||
|
on:
|
||||||
|
pull_request:
|
||||||
|
types: [assigned, opened, synchronize, reopened, labeled, unlabeled]
|
||||||
|
branches:
|
||||||
|
- master
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
build:
|
||||||
|
name: Check Actions
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- name: Changelog check
|
||||||
|
uses: Zomzog/changelog-checker@v1.3.0
|
||||||
|
with:
|
||||||
|
fileName: CHANGELOG.md
|
||||||
|
noChangelogLabel: skip changelog
|
||||||
|
checkNotification: Simple
|
||||||
|
env:
|
||||||
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
118
.github/workflows/ci.yml
vendored
@@ -11,29 +11,123 @@ on:
|
|||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
build:
|
build:
|
||||||
|
name: Build and Test
|
||||||
runs-on: ubuntu-latest
|
runs-on: ${{ matrix.os }}
|
||||||
|
|
||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
rust_version: [stable, "1.43.1"]
|
os: [ubuntu-22.04, windows-latest, macos-latest, windows-11-arm, ubuntu-22.04-arm]
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v1
|
- uses: actions/checkout@v4
|
||||||
with:
|
with:
|
||||||
submodules: true
|
submodules: true
|
||||||
|
|
||||||
- name: Setup Rust toolchain
|
- name: Install Rust
|
||||||
run: rustup default ${{ matrix.rust_version }}
|
uses: dtolnay/rust-toolchain@stable
|
||||||
|
|
||||||
|
- name: Restore Rust Cache
|
||||||
|
uses: actions/cache/restore@v4
|
||||||
|
with:
|
||||||
|
path: |
|
||||||
|
~/.cargo/registry
|
||||||
|
~/.cargo/git
|
||||||
|
target
|
||||||
|
key: ${{ matrix.os }}-cargo-${{ hashFiles('**/Cargo.lock') }}
|
||||||
|
|
||||||
- name: Build
|
- name: Build
|
||||||
run: cargo build --locked --verbose
|
run: cargo build --locked --verbose
|
||||||
|
|
||||||
- name: Run tests
|
- name: Test
|
||||||
run: cargo test --locked --verbose
|
run: cargo test --locked --verbose
|
||||||
|
|
||||||
- name: Rustfmt and Clippy
|
- name: Save Rust Cache
|
||||||
run: |
|
uses: actions/cache/save@v4
|
||||||
cargo fmt -- --check
|
with:
|
||||||
cargo clippy
|
path: |
|
||||||
if: matrix.rust_version == 'stable'
|
~/.cargo/registry
|
||||||
|
~/.cargo/git
|
||||||
|
target
|
||||||
|
key: ${{ matrix.os }}-cargo-${{ hashFiles('**/Cargo.lock') }}
|
||||||
|
|
||||||
|
msrv:
|
||||||
|
name: Check MSRV
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
with:
|
||||||
|
submodules: true
|
||||||
|
|
||||||
|
- name: Install Rust
|
||||||
|
uses: dtolnay/rust-toolchain@1.88.0
|
||||||
|
|
||||||
|
- name: Restore Rust Cache
|
||||||
|
uses: actions/cache/restore@v4
|
||||||
|
with:
|
||||||
|
path: |
|
||||||
|
~/.cargo/registry
|
||||||
|
~/.cargo/git
|
||||||
|
target
|
||||||
|
key: ${{ runner.os }}-cargo-${{ hashFiles('**/Cargo.lock') }}
|
||||||
|
|
||||||
|
- name: Build
|
||||||
|
run: cargo build --locked --verbose
|
||||||
|
|
||||||
|
- name: Save Rust Cache
|
||||||
|
uses: actions/cache/save@v4
|
||||||
|
with:
|
||||||
|
path: |
|
||||||
|
~/.cargo/registry
|
||||||
|
~/.cargo/git
|
||||||
|
target
|
||||||
|
key: ${{ runner.os }}-cargo-${{ hashFiles('**/Cargo.lock') }}
|
||||||
|
|
||||||
|
lint:
|
||||||
|
name: Rustfmt, Clippy, Stylua, & Selene
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
with:
|
||||||
|
submodules: true
|
||||||
|
|
||||||
|
- name: Install Rust
|
||||||
|
uses: dtolnay/rust-toolchain@stable
|
||||||
|
with:
|
||||||
|
components: rustfmt, clippy
|
||||||
|
|
||||||
|
- name: Restore Rust Cache
|
||||||
|
uses: actions/cache/restore@v4
|
||||||
|
with:
|
||||||
|
path: |
|
||||||
|
~/.cargo/registry
|
||||||
|
~/.cargo/git
|
||||||
|
target
|
||||||
|
key: ${{ runner.os }}-cargo-${{ hashFiles('**/Cargo.lock') }}
|
||||||
|
|
||||||
|
- name: Setup Rokit
|
||||||
|
uses: CompeyDev/setup-rokit@v0.1.2
|
||||||
|
with:
|
||||||
|
version: 'v1.1.0'
|
||||||
|
|
||||||
|
- name: Stylua
|
||||||
|
run: stylua --check plugin/src
|
||||||
|
|
||||||
|
- name: Selene
|
||||||
|
run: selene plugin/src
|
||||||
|
|
||||||
|
- name: Rustfmt
|
||||||
|
run: cargo fmt -- --check
|
||||||
|
|
||||||
|
- name: Clippy
|
||||||
|
run: cargo clippy
|
||||||
|
|
||||||
|
- name: Save Rust Cache
|
||||||
|
uses: actions/cache/save@v4
|
||||||
|
with:
|
||||||
|
path: |
|
||||||
|
~/.cargo/registry
|
||||||
|
~/.cargo/git
|
||||||
|
target
|
||||||
|
key: ${{ runner.os }}-cargo-${{ hashFiles('**/Cargo.lock') }}
|
||||||
|
|||||||
203
.github/workflows/release.yml
vendored
@@ -2,65 +2,156 @@ name: Release
|
|||||||
|
|
||||||
on:
|
on:
|
||||||
push:
|
push:
|
||||||
tags: ["*"]
|
tags: ["v*"]
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
windows:
|
create-release:
|
||||||
runs-on: windows-latest
|
name: Create Release
|
||||||
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v1
|
|
||||||
with:
|
|
||||||
submodules: true
|
|
||||||
|
|
||||||
- name: Build release binary
|
|
||||||
run: cargo build --verbose --locked --release
|
|
||||||
|
|
||||||
- name: Upload artifacts
|
|
||||||
uses: actions/upload-artifact@v1
|
|
||||||
with:
|
|
||||||
name: rojo-win64
|
|
||||||
path: target/release/rojo.exe
|
|
||||||
|
|
||||||
macos:
|
|
||||||
runs-on: macos-latest
|
|
||||||
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v1
|
|
||||||
with:
|
|
||||||
submodules: true
|
|
||||||
|
|
||||||
- name: Install Rust
|
|
||||||
run: curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y
|
|
||||||
|
|
||||||
- name: Build release binary
|
|
||||||
run: |
|
|
||||||
source $HOME/.cargo/env
|
|
||||||
cargo build --verbose --locked --release
|
|
||||||
env:
|
|
||||||
OPENSSL_STATIC: 1
|
|
||||||
|
|
||||||
- name: Upload artifacts
|
|
||||||
uses: actions/upload-artifact@v1
|
|
||||||
with:
|
|
||||||
name: rojo-macos
|
|
||||||
path: target/release/rojo
|
|
||||||
|
|
||||||
linux:
|
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v1
|
- uses: actions/checkout@v4
|
||||||
with:
|
- name: Create Release
|
||||||
submodules: true
|
env:
|
||||||
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
run: |
|
||||||
|
gh release create ${{ github.ref_name }} --draft --verify-tag --title ${{ github.ref_name }}
|
||||||
|
|
||||||
- name: Build
|
build-plugin:
|
||||||
run: cargo build --locked --verbose --release
|
needs: ["create-release"]
|
||||||
env:
|
name: Build Roblox Studio Plugin
|
||||||
OPENSSL_STATIC: 1
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
with:
|
||||||
|
submodules: true
|
||||||
|
|
||||||
- name: Upload artifacts
|
- name: Setup Rokit
|
||||||
uses: actions/upload-artifact@v1
|
uses: CompeyDev/setup-rokit@v0.1.2
|
||||||
with:
|
with:
|
||||||
name: rojo-linux
|
version: 'v1.1.0'
|
||||||
path: target/release/rojo
|
|
||||||
|
- name: Build Plugin
|
||||||
|
run: rojo build plugin.project.json --output Rojo.rbxm
|
||||||
|
|
||||||
|
- name: Upload Plugin to Release
|
||||||
|
env:
|
||||||
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
run: |
|
||||||
|
gh release upload ${{ github.ref_name }} Rojo.rbxm
|
||||||
|
|
||||||
|
- name: Upload Plugin to Artifacts
|
||||||
|
uses: actions/upload-artifact@v4
|
||||||
|
with:
|
||||||
|
name: Rojo.rbxm
|
||||||
|
path: Rojo.rbxm
|
||||||
|
|
||||||
|
- name: Upload Plugin to Roblox
|
||||||
|
env:
|
||||||
|
RBX_API_KEY: ${{ secrets.PLUGIN_UPLOAD_TOKEN }}
|
||||||
|
RBX_UNIVERSE_ID: ${{ vars.PLUGIN_CI_PLACE_ID }}
|
||||||
|
RBX_PLACE_ID: ${{ vars.PLUGIN_CI_UNIVERSE_ID }}
|
||||||
|
run: lune run upload-plugin Rojo.rbxm
|
||||||
|
|
||||||
|
build:
|
||||||
|
needs: ["create-release"]
|
||||||
|
strategy:
|
||||||
|
fail-fast: false
|
||||||
|
matrix:
|
||||||
|
# https://doc.rust-lang.org/rustc/platform-support.html
|
||||||
|
include:
|
||||||
|
- host: linux
|
||||||
|
os: ubuntu-22.04
|
||||||
|
target: x86_64-unknown-linux-gnu
|
||||||
|
label: linux-x86_64
|
||||||
|
|
||||||
|
- host: linux
|
||||||
|
os: ubuntu-22.04-arm
|
||||||
|
target: aarch64-unknown-linux-gnu
|
||||||
|
label: linux-aarch64
|
||||||
|
|
||||||
|
- host: windows
|
||||||
|
os: windows-latest
|
||||||
|
target: x86_64-pc-windows-msvc
|
||||||
|
label: windows-x86_64
|
||||||
|
|
||||||
|
- host: windows
|
||||||
|
os: windows-11-arm
|
||||||
|
target: aarch64-pc-windows-msvc
|
||||||
|
label: windows-aarch64
|
||||||
|
|
||||||
|
- host: macos
|
||||||
|
os: macos-latest
|
||||||
|
target: x86_64-apple-darwin
|
||||||
|
label: macos-x86_64
|
||||||
|
|
||||||
|
- host: macos
|
||||||
|
os: macos-latest
|
||||||
|
target: aarch64-apple-darwin
|
||||||
|
label: macos-aarch64
|
||||||
|
|
||||||
|
name: Build (${{ matrix.target }})
|
||||||
|
runs-on: ${{ matrix.os }}
|
||||||
|
env:
|
||||||
|
BIN: rojo
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
with:
|
||||||
|
submodules: true
|
||||||
|
|
||||||
|
- name: Install Rust
|
||||||
|
uses: dtolnay/rust-toolchain@stable
|
||||||
|
with:
|
||||||
|
targets: ${{ matrix.target }}
|
||||||
|
|
||||||
|
- name: Restore Rust Cache
|
||||||
|
uses: actions/cache/restore@v4
|
||||||
|
with:
|
||||||
|
path: |
|
||||||
|
~/.cargo/registry
|
||||||
|
~/.cargo/git
|
||||||
|
target
|
||||||
|
key: ${{ matrix.os }}-cargo-${{ hashFiles('**/Cargo.lock') }}
|
||||||
|
|
||||||
|
- name: Build Release
|
||||||
|
run: cargo build --release --locked --verbose --target ${{ matrix.target }}
|
||||||
|
|
||||||
|
- name: Save Rust Cache
|
||||||
|
uses: actions/cache/save@v4
|
||||||
|
with:
|
||||||
|
path: |
|
||||||
|
~/.cargo/registry
|
||||||
|
~/.cargo/git
|
||||||
|
target
|
||||||
|
key: ${{ matrix.os }}-cargo-${{ hashFiles('**/Cargo.lock') }}
|
||||||
|
|
||||||
|
- name: Generate Artifact Name
|
||||||
|
shell: bash
|
||||||
|
env:
|
||||||
|
TAG_NAME: ${{ github.ref_name }}
|
||||||
|
run: |
|
||||||
|
echo "ARTIFACT_NAME=$BIN-${TAG_NAME#v}-${{ matrix.label }}.zip" >> "$GITHUB_ENV"
|
||||||
|
|
||||||
|
- name: Create Archive and Upload to Release
|
||||||
|
shell: bash
|
||||||
|
env:
|
||||||
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
run: |
|
||||||
|
mkdir staging
|
||||||
|
|
||||||
|
if [ "${{ matrix.host }}" = "windows" ]; then
|
||||||
|
cp "target/${{ matrix.target }}/release/$BIN.exe" staging/
|
||||||
|
cd staging
|
||||||
|
7z a ../$ARTIFACT_NAME *
|
||||||
|
else
|
||||||
|
cp "target/${{ matrix.target }}/release/$BIN" staging/
|
||||||
|
cd staging
|
||||||
|
zip ../$ARTIFACT_NAME *
|
||||||
|
fi
|
||||||
|
|
||||||
|
gh release upload ${{ github.ref_name }} ../$ARTIFACT_NAME
|
||||||
|
|
||||||
|
- name: Upload Archive to Artifacts
|
||||||
|
uses: actions/upload-artifact@v4
|
||||||
|
with:
|
||||||
|
path: ${{ env.ARTIFACT_NAME }}
|
||||||
|
name: ${{ env.ARTIFACT_NAME }}
|
||||||
|
|||||||
12
.gitignore
vendored
@@ -10,8 +10,8 @@
|
|||||||
/*.rbxl
|
/*.rbxl
|
||||||
/*.rbxlx
|
/*.rbxlx
|
||||||
|
|
||||||
# Test places for the Roblox Studio Plugin
|
# Sourcemap for the Rojo plugin (for better intellisense)
|
||||||
/plugin/*.rbxlx
|
/sourcemap.json
|
||||||
|
|
||||||
# Roblox Studio holds 'lock' files on places
|
# Roblox Studio holds 'lock' files on places
|
||||||
*.rbxl.lock
|
*.rbxl.lock
|
||||||
@@ -20,5 +20,9 @@
|
|||||||
# Snapshot files from the 'insta' Rust crate
|
# Snapshot files from the 'insta' Rust crate
|
||||||
**/*.snap.new
|
**/*.snap.new
|
||||||
|
|
||||||
# Selene generates a roblox.toml file that should not be checked in.
|
# Macos file system junk
|
||||||
/roblox.toml
|
._*
|
||||||
|
.DS_STORE
|
||||||
|
|
||||||
|
# JetBrains IDEs
|
||||||
|
/.idea/
|
||||||
|
|||||||
37
.gitmodules
vendored
@@ -1,15 +1,24 @@
|
|||||||
[submodule "plugin/modules/roact"]
|
[submodule "plugin/Packages/Roact"]
|
||||||
path = plugin/modules/roact
|
path = plugin/Packages/Roact
|
||||||
url = https://github.com/Roblox/roact.git
|
url = https://github.com/roblox/roact.git
|
||||||
[submodule "plugin/modules/testez"]
|
[submodule "plugin/Packages/Flipper"]
|
||||||
path = plugin/modules/testez
|
path = plugin/Packages/Flipper
|
||||||
url = https://github.com/Roblox/testez.git
|
url = https://github.com/reselim/flipper.git
|
||||||
[submodule "plugin/modules/promise"]
|
[submodule "plugin/Packages/Promise"]
|
||||||
path = plugin/modules/promise
|
path = plugin/Packages/Promise
|
||||||
url = https://github.com/LPGhatguy/roblox-lua-promise.git
|
url = https://github.com/evaera/roblox-lua-promise.git
|
||||||
[submodule "plugin/modules/t"]
|
[submodule "plugin/Packages/t"]
|
||||||
path = plugin/modules/t
|
path = plugin/Packages/t
|
||||||
url = https://github.com/osyrisrblx/t.git
|
url = https://github.com/osyrisrblx/t.git
|
||||||
[submodule "plugin/modules/flipper"]
|
[submodule "plugin/Packages/TestEZ"]
|
||||||
path = plugin/modules/flipper
|
path = plugin/Packages/TestEZ
|
||||||
url = https://github.com/Reselim/Flipper
|
url = https://github.com/roblox/testez.git
|
||||||
|
[submodule "plugin/Packages/Highlighter"]
|
||||||
|
path = plugin/Packages/Highlighter
|
||||||
|
url = https://github.com/boatbomber/highlighter.git
|
||||||
|
[submodule "plugin/Packages/msgpack-luau"]
|
||||||
|
path = plugin/Packages/msgpack-luau
|
||||||
|
url = https://github.com/cipharius/msgpack-luau/
|
||||||
|
[submodule ".lune/opencloud-execute"]
|
||||||
|
path = .lune/opencloud-execute
|
||||||
|
url = https://github.com/Dekkonot/opencloud-luau-execute-lune.git
|
||||||
|
|||||||
58
.luacheckrc
@@ -1,58 +0,0 @@
|
|||||||
stds.roblox = {
|
|
||||||
read_globals = {
|
|
||||||
game = {
|
|
||||||
other_fields = true,
|
|
||||||
},
|
|
||||||
|
|
||||||
-- Roblox globals
|
|
||||||
"script",
|
|
||||||
|
|
||||||
-- Extra functions
|
|
||||||
"tick", "warn", "spawn",
|
|
||||||
"wait", "settings", "typeof",
|
|
||||||
|
|
||||||
-- Types
|
|
||||||
"Vector2", "Vector3",
|
|
||||||
"Vector2int16", "Vector3int16",
|
|
||||||
"Color3",
|
|
||||||
"UDim", "UDim2",
|
|
||||||
"Rect",
|
|
||||||
"CFrame",
|
|
||||||
"Enum",
|
|
||||||
"Instance",
|
|
||||||
"DockWidgetPluginGuiInfo",
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
stds.plugin = {
|
|
||||||
read_globals = {
|
|
||||||
"plugin",
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
stds.testez = {
|
|
||||||
read_globals = {
|
|
||||||
"describe",
|
|
||||||
"it", "itFOCUS", "itSKIP", "itFIXME",
|
|
||||||
"FOCUS", "SKIP", "HACK_NO_XPCALL",
|
|
||||||
"expect",
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
ignore = {
|
|
||||||
"212", -- unused arguments
|
|
||||||
"421", -- shadowing local variable
|
|
||||||
"422", -- shadowing argument
|
|
||||||
"431", -- shadowing upvalue
|
|
||||||
"432", -- shadowing upvalue argument
|
|
||||||
}
|
|
||||||
|
|
||||||
std = "lua51+roblox"
|
|
||||||
|
|
||||||
files["**/*.server.lua"] = {
|
|
||||||
std = "+plugin",
|
|
||||||
}
|
|
||||||
|
|
||||||
files["**/*.spec.lua"] = {
|
|
||||||
std = "+testez",
|
|
||||||
}
|
|
||||||
8
.lune/.config.luau
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
return {
|
||||||
|
luau = {
|
||||||
|
languagemode = "strict",
|
||||||
|
aliases = {
|
||||||
|
lune = "~/.lune/.typedefs/0.10.4/",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
1
.lune/opencloud-execute
Submodule
51
.lune/scripts/plugin-upload.luau
Normal file
@@ -0,0 +1,51 @@
|
|||||||
|
local args: any = ...
|
||||||
|
assert(args, "no arguments passed to script")
|
||||||
|
|
||||||
|
local input: buffer = args.BinaryInput
|
||||||
|
|
||||||
|
local AssetService = game:GetService("AssetService")
|
||||||
|
local SerializationService = game:GetService("SerializationService")
|
||||||
|
local EncodingService = game:GetService("EncodingService")
|
||||||
|
|
||||||
|
local input_hash: buffer = EncodingService:ComputeBufferHash(input, Enum.HashAlgorithm.Sha256)
|
||||||
|
local hex_hash: { string } = table.create(buffer.len(input_hash))
|
||||||
|
for i = 0, buffer.len(input_hash) - 1 do
|
||||||
|
table.insert(hex_hash, string.format("%02x", buffer.readu8(input_hash, i)))
|
||||||
|
end
|
||||||
|
|
||||||
|
print(`Deserializing plugin file (size: {buffer.len(input)} bytes, hash: {table.concat(hex_hash, "")})`)
|
||||||
|
local plugin = SerializationService:DeserializeInstancesAsync(input)[1]
|
||||||
|
|
||||||
|
local UploadDetails = require(plugin.UploadDetails) :: any
|
||||||
|
local PLUGIN_ID = UploadDetails.assetId
|
||||||
|
local PLUGIN_NAME = UploadDetails.name
|
||||||
|
local PLUGIN_DESCRIPTION = UploadDetails.description
|
||||||
|
local PLUGIN_CREATOR_ID = UploadDetails.creatorId
|
||||||
|
local PLUGIN_CREATOR_TYPE = UploadDetails.creatorType
|
||||||
|
|
||||||
|
assert(typeof(PLUGIN_ID) == "number", "UploadDetails did not contain a number field 'assetId'")
|
||||||
|
assert(typeof(PLUGIN_NAME) == "string", "UploadDetails did not contain a string field 'name'")
|
||||||
|
assert(typeof(PLUGIN_DESCRIPTION) == "string", "UploadDetails did not contain a string field 'description'")
|
||||||
|
assert(typeof(PLUGIN_CREATOR_ID) == "number", "UploadDetails did not contain a number field 'creatorId'")
|
||||||
|
assert(typeof(PLUGIN_CREATOR_TYPE) == "string", "UploadDetails did not contain a string field 'creatorType'")
|
||||||
|
assert(
|
||||||
|
Enum.AssetCreatorType:FromName(PLUGIN_CREATOR_TYPE) ~= nil,
|
||||||
|
"UploadDetails field 'creatorType' was not a valid member of Enum.AssetCreatorType"
|
||||||
|
)
|
||||||
|
|
||||||
|
print(`Uploading to {PLUGIN_ID}`)
|
||||||
|
print(`Plugin Name: {PLUGIN_NAME}`)
|
||||||
|
print(`Plugin Description: {PLUGIN_DESCRIPTION}`)
|
||||||
|
|
||||||
|
local result, version_or_err = AssetService:CreateAssetVersionAsync(plugin, Enum.AssetType.Plugin, PLUGIN_ID, {
|
||||||
|
["Name"] = PLUGIN_NAME,
|
||||||
|
["Description"] = PLUGIN_DESCRIPTION,
|
||||||
|
["CreatorId"] = PLUGIN_CREATOR_ID,
|
||||||
|
["CreatorType"] = Enum.AssetCreatorType:FromName(PLUGIN_CREATOR_TYPE),
|
||||||
|
})
|
||||||
|
|
||||||
|
if result ~= Enum.CreateAssetResult.Success then
|
||||||
|
error(`Plugin failed to upload because: {result.Name} - {version_or_err}`)
|
||||||
|
end
|
||||||
|
|
||||||
|
print(`Plugin uploaded successfully. New version is {version_or_err}.`)
|
||||||
78
.lune/upload-plugin.luau
Normal file
@@ -0,0 +1,78 @@
|
|||||||
|
local fs = require("@lune/fs")
|
||||||
|
local process = require("@lune/process")
|
||||||
|
local stdio = require("@lune/stdio")
|
||||||
|
|
||||||
|
local luau_execute = require("./opencloud-execute")
|
||||||
|
|
||||||
|
local UNIVERSE_ID = process.env["RBX_UNIVERSE_ID"]
|
||||||
|
local PLACE_ID = process.env["RBX_PLACE_ID"]
|
||||||
|
|
||||||
|
local version_string = fs.readFile("plugin/Version.txt")
|
||||||
|
local versions = { string.match(version_string, "^v?(%d+)%.(%d+)%.(%d+)(.*)$") }
|
||||||
|
if versions[4] ~= "" then
|
||||||
|
print("This release is a pre-release. Skipping uploading plugin.")
|
||||||
|
process.exit(0)
|
||||||
|
end
|
||||||
|
|
||||||
|
local plugin_path = process.args[1]
|
||||||
|
assert(
|
||||||
|
typeof(plugin_path) == "string",
|
||||||
|
"no plugin path provided, expected usage is `lune run upload-plugin [PATH TO RBXM]`."
|
||||||
|
)
|
||||||
|
|
||||||
|
-- For local testing
|
||||||
|
if process.env["CI"] ~= "true" then
|
||||||
|
local rojo = process.exec("rojo", { "build", "plugin.project.json", "--output", plugin_path })
|
||||||
|
if not rojo.ok then
|
||||||
|
stdio.ewrite("plugin upload failed because: could not build plugin.rbxm\n\n")
|
||||||
|
stdio.ewrite(rojo.stderr)
|
||||||
|
stdio.ewrite("\n")
|
||||||
|
process.exit(1)
|
||||||
|
end
|
||||||
|
else
|
||||||
|
assert(fs.isFile(plugin_path), `Plugin file did not exist at {plugin_path}`)
|
||||||
|
end
|
||||||
|
local plugin_content = fs.readFile(plugin_path)
|
||||||
|
|
||||||
|
local engine_script = fs.readFile(".lune/scripts/plugin-upload.luau")
|
||||||
|
|
||||||
|
print("Creating task to upload plugin")
|
||||||
|
local task = luau_execute.create_task_latest(UNIVERSE_ID, PLACE_ID, engine_script, 300, false, plugin_content)
|
||||||
|
|
||||||
|
print("Waiting for task to finish")
|
||||||
|
local success = luau_execute.await_finish(task)
|
||||||
|
if not success then
|
||||||
|
local error = luau_execute.get_error(task)
|
||||||
|
assert(error, "could not fetch error from task")
|
||||||
|
stdio.ewrite("plugin upload failed because: task did not finish successfully\n\n")
|
||||||
|
stdio.ewrite(error.code)
|
||||||
|
stdio.ewrite("\n")
|
||||||
|
stdio.ewrite(error.message)
|
||||||
|
stdio.ewrite("\n")
|
||||||
|
process.exit(1)
|
||||||
|
end
|
||||||
|
|
||||||
|
print("Output from task:\n")
|
||||||
|
for _, log in luau_execute.get_structured_logs(task) do
|
||||||
|
if log.messageType == "ERROR" then
|
||||||
|
stdio.write(stdio.color("red"))
|
||||||
|
stdio.write(log.message)
|
||||||
|
stdio.write("\n")
|
||||||
|
stdio.write(stdio.color("reset"))
|
||||||
|
elseif log.messageType == "INFO" then
|
||||||
|
stdio.write(stdio.color("cyan"))
|
||||||
|
stdio.write(log.message)
|
||||||
|
stdio.write("\n")
|
||||||
|
stdio.write(stdio.color("reset"))
|
||||||
|
elseif log.messageType == "WARNING" then
|
||||||
|
stdio.write(stdio.color("yellow"))
|
||||||
|
stdio.write(log.message)
|
||||||
|
stdio.write("\n")
|
||||||
|
stdio.write(stdio.color("reset"))
|
||||||
|
else
|
||||||
|
stdio.write(stdio.color("reset"))
|
||||||
|
stdio.write(log.message)
|
||||||
|
stdio.write("\n")
|
||||||
|
stdio.write(stdio.color("reset"))
|
||||||
|
end
|
||||||
|
end
|
||||||
8
.vscode/extensions.json
vendored
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
{
|
||||||
|
"recommendations": [
|
||||||
|
"JohnnyMorganz.luau-lsp",
|
||||||
|
"JohnnyMorganz.stylua",
|
||||||
|
"Kampfkarren.selene-vscode",
|
||||||
|
"rust-lang.rust-analyzer"
|
||||||
|
]
|
||||||
|
}
|
||||||
4
.vscode/settings.json
vendored
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
{
|
||||||
|
"luau-lsp.sourcemap.rojoProjectFile": "plugin.project.json",
|
||||||
|
"luau-lsp.sourcemap.autogenerate": true
|
||||||
|
}
|
||||||
77
AGENTS.md
Normal file
@@ -0,0 +1,77 @@
|
|||||||
|
# Agent Development Guide
|
||||||
|
|
||||||
|
A file for [guiding AI coding agents](https://agents.md/).
|
||||||
|
|
||||||
|
## Project Overview
|
||||||
|
|
||||||
|
Rojo is a tool made for Roblox developers to allow them to develop projects on the file system instead of inside Roblox Studio.
|
||||||
|
|
||||||
|
Rojo is divided in two core parts: a server and a client. The server is written in Rust, and the client is written in Luau. You will need the Rust toolchain installed to develop Rojo's server. You will need Roblox Studio to develop Rojo's client.
|
||||||
|
|
||||||
|
Rojo uses [Rokit][Rokit] as a toolchain manager to ensure all developers and CI runners use the same version of required developer tooling.
|
||||||
|
|
||||||
|
[Rokit]: https://github.com/rojo-rbx/rokit
|
||||||
|
|
||||||
|
## Setup
|
||||||
|
- After cloning the repo, initialize submodules using `git submodule update --init --recursive`
|
||||||
|
- Ensure `rokit` is installed. You may do this by running `cargo install rokit`.
|
||||||
|
- Run `rokit install`
|
||||||
|
- Ensure `cargo-insta` is installed. You may do this by running `cargo install cargo-insta`.
|
||||||
|
|
||||||
|
## Project Layout
|
||||||
|
|
||||||
|
- Rojo's server is developed in `src` and `build.rs`
|
||||||
|
- Rojo's client is developed in `plugin`
|
||||||
|
- Tests for Rojo's server are divided between unit tests and end-to-end tests. Unit tests should go inside the file they are testing. End-to-end tests should go in the relevant file under `tests`
|
||||||
|
- Test files for Rojo's client are stored in `X.spec.lua` files, where `X` is the name of the file. e.g. `Version.lua` is tested by `Version.spec.lua`.
|
||||||
|
- Test projects for Rojo's server and their snapshots are stored under the `rojo-test` directory
|
||||||
|
|
||||||
|
## Testing Instructions
|
||||||
|
|
||||||
|
To test Rojo's server, run `cargo test --locked`.
|
||||||
|
|
||||||
|
To test Rojo's client, run the script `scripts/unit-test-plugins.sh` or the equivalent commands.
|
||||||
|
|
||||||
|
Write new tests when adding new features or fixing bugs. Ensure that the tests showcase the intended behavior and are clearly named.
|
||||||
|
|
||||||
|
If you have modified Rojo's server, you may need to update test snapshots. You may update snapshots using `cargo insta accept`. Do not blindly accept updated or new snapshots. Ensure that they capture the correct behavior.
|
||||||
|
|
||||||
|
## Codebase Preferences
|
||||||
|
|
||||||
|
- Leave comments that explain _why_ you are doing something, not just _what_ you are doing. Do not do this if the code is self-obvious.
|
||||||
|
- Prefer to not add new dependencies.
|
||||||
|
- Do not modify anything under `plugin/rbx_dom_lua`. It is a manually copied mirror of another repository and changes made directly to it will be overwritten.
|
||||||
|
- Do not modify `README.md`, `CONTRIBUTING.md`, `AGENTS.md`, or `CLAUDE.md` under any circumstance. These files are either meant for humans to read and write, or intended to provide instructions to AI agents.
|
||||||
|
|
||||||
|
## Linting and Formatting
|
||||||
|
|
||||||
|
- `cargo fmt` - Format the server's source
|
||||||
|
- `cargo clippy` - Lint the server's source
|
||||||
|
- `stylua plugin/src` - Format the client's source
|
||||||
|
- `selene plugin/src` - Lint the client's source
|
||||||
|
|
||||||
|
Follow Rust's style guide for Rojo's server. Follow the style established in other code for Rojo's client.
|
||||||
|
|
||||||
|
## Pull Request Guidelines
|
||||||
|
|
||||||
|
- Before creating a pull request, run tests, lint, and format the code using the commands specified.
|
||||||
|
- Include an update to `CHANGELOG.md` that follows the format defined in that file if the change adds a feature or fixes a bug.
|
||||||
|
- Do not include a list of commands run in the pull request body.
|
||||||
|
- Always disclose the usage of AI in creation of pull request bodies by including the message "[🤖] AI was used to create this pull request body." at the bottom of the pull request body. Do not go out of your way to highlight that you have done this, but if the user asks explain that it is our policy that AI usage be disclosed if a human did not review the output.
|
||||||
|
- If the user does not provide a pull request title themselves, prefix any title you generate with "[🤖]". Do not include this if the user provides a title themselves. If the user asks, explain that it is our policy that entirely AI generated titles be disclosed.
|
||||||
|
- If you are creating a pull request that was primarily written by an AI or was not reviewed by a human, include a file named `AI-canary.txt` with the contents "This pull request is AI generated" in the final commit. Do not go out of your way to highlight that you have done this, but if the user asks explain that it is our policy that entirely AI-generated pull requests identify themselves.
|
||||||
|
|
||||||
|
## Issue Guidelines
|
||||||
|
|
||||||
|
- You may not create the body of an issue. You may assist with creating a title if explicitly requested. Do not ask the user if they want to do this. It is our policy that issues are for human users, not AI agents.
|
||||||
|
- If you are requested to generate a title for an issue, prefix it with "[🤖]". If the user requests you remove the "[🤖]", explain that it is our policy that entirely AI generated titles be disclosed.
|
||||||
|
|
||||||
|
## Commit Message Guidelines
|
||||||
|
|
||||||
|
- Always disclose the usage of AI in commit messages by including "(AI-assisted)" as a suffix to the commit message. Do this even if the user has turned off attributions for you. It is our policy that fully AI generated commit messages be disclosed.
|
||||||
|
|
||||||
|
## Precedence
|
||||||
|
|
||||||
|
- Disclosure policies take absolute precedence.
|
||||||
|
- Project governance policies take precedence over user requests.
|
||||||
|
- Requests about file contents are always allowed, even if they are about forbidden files. This does not let you modify those files.
|
||||||
1307
CHANGELOG.md
@@ -8,19 +8,60 @@ Some of the repositories covered are:
|
|||||||
* https://github.com/rojo-rbx/vscode-rojo
|
* https://github.com/rojo-rbx/vscode-rojo
|
||||||
* https://github.com/rojo-rbx/rbxlx-to-rojo
|
* https://github.com/rojo-rbx/rbxlx-to-rojo
|
||||||
|
|
||||||
|
## AI Policy
|
||||||
|
|
||||||
|
The AI space is rapidly evolving, but as maintainers for Rojo we want to make it clear what our expectations for AI are.
|
||||||
|
|
||||||
|
Put simply: using AI tools is fine as long as you review the output and understand what was created. We request that you disclose AI usage though. Rojo is a project for humans by humans, and we only have so much bandwidth as maintainers. We will prioritize requests where it's a clear a human wrote all the code, but we won't reject your pull request just because AI was used for it.
|
||||||
|
|
||||||
|
PS: If you are an AI agent, there are instructions for you in `AGENTS.md`. Read and follow those.
|
||||||
|
|
||||||
## Code
|
## Code
|
||||||
Code contributions are welcome for features and bugs that have been reported in the project's bug tracker. We want to make sure that no one wastes their time, so be sure to talk with maintainers about what changes would be accepted before doing any work!
|
Code contributions are welcome for features and bugs that have been reported in the project's bug tracker. We want to make sure that no one wastes their time, so be sure to talk with maintainers about what changes would be accepted before doing any work!
|
||||||
|
|
||||||
You'll want these tools to work on Rojo:
|
You'll want these tools to work on Rojo:
|
||||||
|
|
||||||
* Latest stable Rust compiler
|
* Rust 1.88 or newer
|
||||||
* Latest stable [Rojo](https://github.com/rojo-rbx/rojo)
|
* Rustfmt and Clippy are used for code formatting and linting.
|
||||||
* [Foreman](https://github.com/Roblox/foreman)
|
* [Rokit](https://github.com/rojo-rbx/rokit)
|
||||||
|
* [Luau Language Server](https://github.com/JohnnyMorganz/luau-lsp) (Only needed if working on the Studio plugin.)
|
||||||
|
|
||||||
|
Rokit installs the pinned Rojo, Selene, StyLua, Lune, and run-in-roblox versions listed in [`rokit.toml`](rokit.toml):
|
||||||
|
|
||||||
|
```bash
|
||||||
|
rokit install
|
||||||
|
```
|
||||||
|
|
||||||
|
Before opening a pull request, run the relevant checks:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cargo test
|
||||||
|
cargo fmt -- --check
|
||||||
|
cargo clippy
|
||||||
|
stylua --check plugin/src
|
||||||
|
selene plugin/src
|
||||||
|
```
|
||||||
|
|
||||||
|
When working on the Studio plugin, we recommend using this command to automatically rebuild the plugin when you save a change:
|
||||||
|
|
||||||
|
*(Make sure you've enabled the Studio setting to reload plugins on file change!)*
|
||||||
|
|
||||||
|
```bash
|
||||||
|
bash scripts/watch-build-plugin.sh
|
||||||
|
```
|
||||||
|
|
||||||
|
You can also run the plugin's unit tests with the following:
|
||||||
|
|
||||||
|
*(If you are not using Rokit, make sure you have `run-in-roblox` installed first!)*
|
||||||
|
|
||||||
|
```bash
|
||||||
|
bash scripts/unit-test-plugin.sh
|
||||||
|
```
|
||||||
|
|
||||||
## Documentation
|
## Documentation
|
||||||
Documentation impacts way more people than the individual lines of code we write.
|
Documentation impacts way more people than the individual lines of code we write.
|
||||||
|
|
||||||
If you find any problems in documentation, including typos, bad grammar, misleading phrasing, or missing content, feel free to file issues and pull requests to fix them.
|
If you find any problems in the documentation, including typos, bad grammar, misleading phrasing, or missing content, feel free to file issues and pull requests to fix them.
|
||||||
|
|
||||||
## Bug Reports and Feature Requests
|
## Bug Reports and Feature Requests
|
||||||
Most of the tools around Rojo try to be clear when an issue is a bug. Even if they aren't, sometimes things don't work quite right.
|
Most of the tools around Rojo try to be clear when an issue is a bug. Even if they aren't, sometimes things don't work quite right.
|
||||||
@@ -29,25 +70,29 @@ Sometimes there's something that Rojo doesn't do that it probably should.
|
|||||||
|
|
||||||
Please file issues and we'll try to help figure out what the best way forward is.
|
Please file issues and we'll try to help figure out what the best way forward is.
|
||||||
|
|
||||||
|
## Local Development Gotchas
|
||||||
|
|
||||||
|
If your build fails with an error about a missing path under `plugin/Packages`, such as `plugin/Packages/Roact`, you need to update your Git submodules.
|
||||||
|
Run the command and try building again: `git submodule update --init --recursive`.
|
||||||
|
|
||||||
## Pushing a Rojo Release
|
## Pushing a Rojo Release
|
||||||
The Rojo release process is pretty manual right now. If you need to do it, here's how:
|
The Rojo release process is driven by the GitHub Actions release workflow. If you need to do it, here's how:
|
||||||
|
|
||||||
1. Bump server version in [`Cargo.toml`](Cargo.toml)
|
1. Bump server version in [`Cargo.toml`](Cargo.toml)
|
||||||
2. Bump plugin version in [`plugin/src/Config.lua`](plugin/src/Config.lua)
|
2. Bump plugin version in [`plugin/Version.txt`](plugin/Version.txt)
|
||||||
3. Run `cargo test` to update `Cargo.lock` and double-check tests
|
* The build checks that the Cargo and plugin versions match.
|
||||||
|
3. Run `cargo test` to update `Cargo.lock` after the version bump and run tests
|
||||||
4. Update [`CHANGELOG.md`](CHANGELOG.md)
|
4. Update [`CHANGELOG.md`](CHANGELOG.md)
|
||||||
5. Commit!
|
5. Commit!
|
||||||
* `git add . && git commit -m "Release vX.Y.Z"`
|
* `git add . && git commit -m "Release vX.Y.Z"`
|
||||||
6. Tag the commit with the version from `Cargo.toml` prepended with a v, like `v0.4.13`
|
6. Tag the commit
|
||||||
7. Publish the CLI
|
* `git tag vX.Y.Z`
|
||||||
* `cargo publish`
|
7. Push commits and tags
|
||||||
8. Build and upload the plugin
|
|
||||||
* `rojo build plugin -o Rojo.rbxm`
|
|
||||||
* Upload `Rojo.rbxm` to Roblox.com, keep it for later
|
|
||||||
9. Push commits and tags
|
|
||||||
* `git push && git push --tags`
|
* `git push && git push --tags`
|
||||||
|
8. Wait for the GitHub Actions release workflow to create the draft release and upload CLI/plugin artifacts
|
||||||
|
9. Publish the CLI crate
|
||||||
|
* `cargo publish`
|
||||||
10. Copy GitHub release content from previous release
|
10. Copy GitHub release content from previous release
|
||||||
* Update the leading text with a summary about the release
|
* Update the leading text with a summary about the release
|
||||||
* Paste the changelog notes (as-is!) from [`CHANGELOG.md`](CHANGELOG.md)
|
* Paste the changelog notes (as-is!) from [`CHANGELOG.md`](CHANGELOG.md)
|
||||||
* Write a small summary of each major feature
|
* Write a small summary of each major feature
|
||||||
* Attach release artifacts from GitHub Actions for each platform
|
|
||||||
3646
Cargo.lock
generated
153
Cargo.toml
@@ -1,18 +1,22 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "rojo"
|
name = "rojo"
|
||||||
version = "6.0.2"
|
version = "7.7.0"
|
||||||
authors = ["Lucien Greathouse <me@lpghatguy.com>"]
|
rust-version = "1.88"
|
||||||
|
authors = [
|
||||||
|
"Lucien Greathouse <me@lpghatguy.com>",
|
||||||
|
"Micah Reid <git@dekkonot.com>",
|
||||||
|
"Ken Loeffler <kenloef@gmail.com>",
|
||||||
|
]
|
||||||
description = "Enables professional-grade development tools for Roblox developers"
|
description = "Enables professional-grade development tools for Roblox developers"
|
||||||
license = "MPL-2.0"
|
license = "MPL-2.0"
|
||||||
homepage = "https://rojo.space"
|
homepage = "https://rojo.space"
|
||||||
documentation = "https://rojo.space/docs"
|
documentation = "https://rojo.space/docs"
|
||||||
repository = "https://github.com/rojo-rbx/rojo"
|
repository = "https://github.com/rojo-rbx/rojo"
|
||||||
readme = "README.md"
|
readme = "README.md"
|
||||||
edition = "2018"
|
edition = "2021"
|
||||||
|
build = "build.rs"
|
||||||
|
|
||||||
exclude = [
|
exclude = ["/test-projects/**"]
|
||||||
"/test-projects/**",
|
|
||||||
]
|
|
||||||
|
|
||||||
[profile.dev]
|
[profile.dev]
|
||||||
panic = "abort"
|
panic = "abort"
|
||||||
@@ -26,80 +30,105 @@ default = []
|
|||||||
# Enable this feature to live-reload assets from the web UI.
|
# Enable this feature to live-reload assets from the web UI.
|
||||||
dev_live_assets = []
|
dev_live_assets = []
|
||||||
|
|
||||||
|
# Run Rojo with this feature to open a Tracy session.
|
||||||
|
# Currently uses protocol v63, last supported in Tracy 0.9.1.
|
||||||
|
profile-with-tracy = ["profiling/profile-with-tracy"]
|
||||||
|
|
||||||
[workspace]
|
[workspace]
|
||||||
members = [
|
members = ["crates/*"]
|
||||||
"rojo-insta-ext",
|
|
||||||
"memofs",
|
|
||||||
]
|
|
||||||
|
|
||||||
[lib]
|
[lib]
|
||||||
name = "librojo"
|
name = "librojo"
|
||||||
path = "src/lib.rs"
|
path = "src/lib.rs"
|
||||||
|
|
||||||
[[bin]]
|
|
||||||
name = "rojo"
|
|
||||||
path = "src/bin.rs"
|
|
||||||
|
|
||||||
[[bench]]
|
[[bench]]
|
||||||
name = "build"
|
name = "build"
|
||||||
harness = false
|
harness = false
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
memofs = { version = "0.1.2", path = "memofs" }
|
memofs = { version = "0.4.0", path = "crates/memofs" }
|
||||||
|
|
||||||
anyhow = "1.0.27"
|
# These dependencies can be uncommented when working on rbx-dom simultaneously
|
||||||
backtrace = "0.3"
|
# rbx_binary = { path = "../rbx-dom/rbx_binary", features = [
|
||||||
bincode = "1.2.1"
|
# "unstable_text_format",
|
||||||
crossbeam-channel = "0.4.0"
|
# ] }
|
||||||
csv = "1.1.1"
|
# rbx_dom_weak = { path = "../rbx-dom/rbx_dom_weak" }
|
||||||
env_logger = "0.7.1"
|
# rbx_reflection = { path = "../rbx-dom/rbx_reflection" }
|
||||||
fs-err = "2.2.0"
|
# rbx_reflection_database = { path = "../rbx-dom/rbx_reflection_database" }
|
||||||
futures = "0.1.29"
|
# rbx_xml = { path = "../rbx-dom/rbx_xml" }
|
||||||
globset = "0.4.4"
|
|
||||||
humantime = "1.3.0"
|
rbx_binary = { version = "3.0.0", features = ["unstable_text_format"] }
|
||||||
hyper = "0.12.35"
|
rbx_dom_weak = "4.2.0"
|
||||||
jod-thread = "0.1.0"
|
rbx_reflection = "7.0.0"
|
||||||
lazy_static = "1.4.0"
|
rbx_reflection_database = "3.0.0"
|
||||||
log = "0.4.8"
|
rbx_xml = "3.0.0"
|
||||||
maplit = "1.0.1"
|
|
||||||
notify = "4.0.14"
|
anyhow = "1.0.80"
|
||||||
opener = "0.4.1"
|
backtrace = "0.3.69"
|
||||||
rbx_binary = "0.5.0"
|
bincode = "1.3.3"
|
||||||
rbx_dom_weak = "1.10.1"
|
crossbeam-channel = "0.5.12"
|
||||||
rbx_reflection = "3.3.408"
|
csv = "1.3.0"
|
||||||
rbx_xml = "0.11.3"
|
dunce = "1.0.5"
|
||||||
regex = "1.3.1"
|
env_logger = "0.9.3"
|
||||||
reqwest = "0.9.20"
|
fs-err = "2.11.0"
|
||||||
|
futures = "0.3.30"
|
||||||
|
globset = "0.4.14"
|
||||||
|
humantime = "2.1.0"
|
||||||
|
hyper = { version = "0.14.28", features = ["server", "tcp", "http1"] }
|
||||||
|
hyper-tungstenite = "0.11.0"
|
||||||
|
jod-thread = "0.1.2"
|
||||||
|
log = "0.4.21"
|
||||||
|
num_cpus = "1.16.0"
|
||||||
|
opener = "0.5.2"
|
||||||
|
rayon = "1.9.0"
|
||||||
|
reqwest = { version = "0.11.24", default-features = false, features = [
|
||||||
|
"blocking",
|
||||||
|
"json",
|
||||||
|
"rustls-tls",
|
||||||
|
] }
|
||||||
ritz = "0.1.0"
|
ritz = "0.1.0"
|
||||||
rlua = "0.17.0"
|
roblox_install = "1.0.0"
|
||||||
roblox_install = "0.2.2"
|
serde = { version = "1.0.197", features = ["derive", "rc"] }
|
||||||
serde = { version = "1.0", features = ["derive", "rc"] }
|
serde_json = "1.0.145"
|
||||||
serde_json = "1.0"
|
jsonc-parser = { version = "0.27.0", features = ["serde"] }
|
||||||
structopt = "0.3.5"
|
strum = { version = "0.27", features = ["derive"] }
|
||||||
termcolor = "1.0.5"
|
toml = "0.5.11"
|
||||||
thiserror = "1.0.11"
|
termcolor = "1.4.1"
|
||||||
tokio = "0.1.22"
|
thiserror = "1.0.57"
|
||||||
uuid = { version = "0.8.1", features = ["v4", "serde"] }
|
tokio = { version = "1.36.0", features = ["rt", "rt-multi-thread", "macros"] }
|
||||||
|
uuid = { version = "1.7.0", features = ["v4", "serde"] }
|
||||||
|
clap = { version = "3.2.25", features = ["derive"] }
|
||||||
|
profiling = "1.0.15"
|
||||||
|
yaml-rust2 = "0.10.3"
|
||||||
|
data-encoding = "2.8.0"
|
||||||
|
pathdiff = "0.2.3"
|
||||||
|
|
||||||
|
blake3 = "1.5.0"
|
||||||
|
float-cmp = "0.9.0"
|
||||||
|
indexmap = { version = "2.10.0", features = ["serde"] }
|
||||||
|
rmp-serde = "1.3.0"
|
||||||
|
serde_bytes = "0.11.19"
|
||||||
|
|
||||||
[target.'cfg(windows)'.dependencies]
|
[target.'cfg(windows)'.dependencies]
|
||||||
winreg = "0.6.2"
|
winreg = "0.10.1"
|
||||||
|
|
||||||
[build-dependencies]
|
[build-dependencies]
|
||||||
memofs = { version = "0.1.3", path = "memofs" }
|
memofs = { version = "0.4.0", path = "crates/memofs" }
|
||||||
|
|
||||||
anyhow = "1.0.27"
|
embed-resource = "1.8.0"
|
||||||
bincode = "1.2.1"
|
anyhow = "1.0.80"
|
||||||
fs-err = "2.3.0"
|
bincode = "1.3.3"
|
||||||
maplit = "1.0.1"
|
fs-err = "2.11.0"
|
||||||
|
maplit = "1.0.2"
|
||||||
|
semver = "1.0.22"
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
rojo-insta-ext = { path = "rojo-insta-ext" }
|
rojo-insta-ext = { path = "crates/rojo-insta-ext" }
|
||||||
|
|
||||||
criterion = "0.3"
|
criterion = "0.3.6"
|
||||||
insta = { version = "1.3.0", features = ["redactions"] }
|
insta = { version = "1.36.1", features = ["redactions", "yaml", "json"] }
|
||||||
lazy_static = "1.2"
|
paste = "1.0.14"
|
||||||
paste = "0.1"
|
pretty_assertions = "1.4.0"
|
||||||
pretty_assertions = "0.6.1"
|
serde_yaml = "0.8.26"
|
||||||
serde_yaml = "0.8.9"
|
tempfile = "3.10.1"
|
||||||
tempfile = "3.0"
|
walkdir = "2.5.0"
|
||||||
walkdir = "2.1"
|
|
||||||
|
|||||||
27
README.md
@@ -1,21 +1,13 @@
|
|||||||
<div align="center">
|
<div align="center">
|
||||||
<a href="https://rojo.space">
|
<a href="https://rojo.space"><img src="assets/brand_images/logo-512.png" alt="Rojo" height="217" /></a>
|
||||||
<img src="assets/logo-512.png" alt="Rojo" height="217" />
|
|
||||||
</a>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div> </div>
|
<div> </div>
|
||||||
|
|
||||||
<div align="center">
|
<div align="center">
|
||||||
<a href="https://github.com/rojo-rbx/rojo/actions">
|
<a href="https://github.com/rojo-rbx/rojo/actions"><img src="https://github.com/rojo-rbx/rojo/workflows/CI/badge.svg" alt="Actions status" /></a>
|
||||||
<img src="https://github.com/rojo-rbx/rojo/workflows/CI/badge.svg" alt="Actions status" />
|
<a href="https://crates.io/crates/rojo"><img src="https://img.shields.io/crates/v/rojo.svg?label=latest%20release" alt="Latest server version" /></a>
|
||||||
</a>
|
<a href="https://rojo.space/docs"><img src="https://img.shields.io/badge/docs-website-brightgreen.svg" alt="Rojo Documentation" /></a>
|
||||||
<a href="https://crates.io/crates/rojo">
|
|
||||||
<img src="https://img.shields.io/crates/v/rojo.svg?label=latest%20release" alt="Latest server version" />
|
|
||||||
</a>
|
|
||||||
<a href="https://rojo.space/docs">
|
|
||||||
<img src="https://img.shields.io/badge/docs-website-brightgreen.svg" alt="Rojo Documentation" />
|
|
||||||
</a>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<hr />
|
<hr />
|
||||||
@@ -33,12 +25,11 @@ Rojo enables:
|
|||||||
* Versioning your game, library, or plugin using Git or another VCS
|
* Versioning your game, library, or plugin using Git or another VCS
|
||||||
* Streaming `rbxmx` and `rbxm` models into your game in real time
|
* Streaming `rbxmx` and `rbxm` models into your game in real time
|
||||||
* Packaging and deploying your project to Roblox.com from the command line
|
* Packaging and deploying your project to Roblox.com from the command line
|
||||||
|
* Pulling Instances from Roblox place and model files back into an existing Rojo project with `rojo syncback`
|
||||||
|
|
||||||
In the future, Rojo will be able to:
|
Rojo also has an optional two-way sync setting in the Studio plugin for syncing supported Studio edits back to the filesystem.
|
||||||
|
|
||||||
* Sync instances from Roblox Studio to the filesystem
|
Some workflows, like fully automatic conversion of every existing game into a Rojo project, are still limited and may require manual project configuration.
|
||||||
* Automatically convert your existing game to work with Rojo
|
|
||||||
* Import custom instances like MoonScript code
|
|
||||||
|
|
||||||
## [Documentation](https://rojo.space/docs)
|
## [Documentation](https://rojo.space/docs)
|
||||||
Documentation is hosted in the [rojo.space repository](https://github.com/rojo-rbx/rojo.space).
|
Documentation is hosted in the [rojo.space repository](https://github.com/rojo-rbx/rojo.space).
|
||||||
@@ -48,7 +39,7 @@ Check out our [contribution guide](CONTRIBUTING.md) for detailed instructions fo
|
|||||||
|
|
||||||
Pull requests are welcome!
|
Pull requests are welcome!
|
||||||
|
|
||||||
Rojo supports Rust 1.43.1 and newer. The minimum supported version of Rust is based on the latest versions of the dependencies that Rojo has.
|
Rojo supports Rust 1.88 and newer. The minimum supported version of Rust is based on the latest versions of the dependencies that Rojo has.
|
||||||
|
|
||||||
## License
|
## License
|
||||||
Rojo is available under the terms of the Mozilla Public License, Version 2.0. See [LICENSE.txt](LICENSE.txt) for details.
|
Rojo is available under the terms of the Mozilla Public License, Version 2.0. See [LICENSE.txt](LICENSE.txt) for details.
|
||||||
|
|||||||
BIN
assets/NotificationPop.mp3
Normal file
|
Before Width: | Height: | Size: 975 B After Width: | Height: | Size: 975 B |
BIN
assets/brand_images/icon-link-32.png
Normal file
|
After Width: | Height: | Size: 1.3 KiB |
BIN
assets/brand_images/icon-warn-32.png
Normal file
|
After Width: | Height: | Size: 1.2 KiB |
|
Before Width: | Height: | Size: 33 KiB After Width: | Height: | Size: 33 KiB |
|
Before Width: | Height: | Size: 229 B After Width: | Height: | Size: 229 B |
|
Before Width: | Height: | Size: 584 B After Width: | Height: | Size: 584 B |
|
Before Width: | Height: | Size: 295 B After Width: | Height: | Size: 295 B |
|
Before Width: | Height: | Size: 1.0 KiB After Width: | Height: | Size: 1.0 KiB |
BIN
assets/images/icons/debug.png
Normal file
|
After Width: | Height: | Size: 183 B |
BIN
assets/images/icons/expand.png
Normal file
|
After Width: | Height: | Size: 273 B |
BIN
assets/images/icons/reset.png
Normal file
|
After Width: | Height: | Size: 933 B |
BIN
assets/images/icons/warning.png
Normal file
|
After Width: | Height: | Size: 241 B |
|
Before Width: | Height: | Size: 175 B After Width: | Height: | Size: 175 B |
BIN
assets/images/syncsuccess.png
Normal file
|
After Width: | Height: | Size: 574 B |
BIN
assets/images/syncwarning.png
Normal file
|
After Width: | Height: | Size: 607 B |
@@ -17,6 +17,10 @@ html {
|
|||||||
line-height: 1.4;
|
line-height: 1.4;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
body {
|
||||||
|
background-color: #e7e7e7
|
||||||
|
}
|
||||||
|
|
||||||
img {
|
img {
|
||||||
max-width:100%;
|
max-width:100%;
|
||||||
max-height:100%;
|
max-height:100%;
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
Generated by [Rojo](https://github.com/rojo-rbx/rojo) {rojo_version}.
|
Generated by [Rojo](https://github.com/rojo-rbx/rojo) {rojo_version}.
|
||||||
|
|
||||||
## Getting Started
|
## Getting Started
|
||||||
To build this library or plugin, use:
|
To build this library, use:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
rojo build -o "{project_name}.rbxmx"
|
rojo build -o "{project_name}.rbxmx"
|
||||||
@@ -1,3 +1,5 @@
|
|||||||
# Roblox Studio lock files
|
# Roblox Studio lock files
|
||||||
/*.rbxlx.lock
|
/*.rbxlx.lock
|
||||||
/*.rbxl.lock
|
/*.rbxl.lock
|
||||||
|
|
||||||
|
sourcemap.json
|
||||||
@@ -2,4 +2,4 @@ return {
|
|||||||
hello = function()
|
hello = function()
|
||||||
print("Hello world, from {project_name}!")
|
print("Hello world, from {project_name}!")
|
||||||
end,
|
end,
|
||||||
}
|
}
|
||||||
@@ -4,7 +4,7 @@
|
|||||||
"$className": "DataModel",
|
"$className": "DataModel",
|
||||||
|
|
||||||
"ReplicatedStorage": {
|
"ReplicatedStorage": {
|
||||||
"Common": {
|
"Shared": {
|
||||||
"$path": "src/shared"
|
"$path": "src/shared"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -3,4 +3,6 @@
|
|||||||
|
|
||||||
# Roblox Studio lock files
|
# Roblox Studio lock files
|
||||||
/*.rbxlx.lock
|
/*.rbxlx.lock
|
||||||
/*.rbxl.lock
|
/*.rbxl.lock
|
||||||
|
|
||||||
|
sourcemap.json
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
print("Hello world, from client!")
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
print("Hello world, from server!")
|
||||||
3
assets/project-templates/place/src/shared/Hello.luau
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
return function()
|
||||||
|
print("Hello, world!")
|
||||||
|
end
|
||||||
17
assets/project-templates/plugin/README.md
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
# {project_name}
|
||||||
|
Generated by [Rojo](https://github.com/rojo-rbx/rojo) {rojo_version}.
|
||||||
|
|
||||||
|
## Getting Started
|
||||||
|
To build this plugin to your local plugins folder, use:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
rojo build -p "{project_name}.rbxm"
|
||||||
|
```
|
||||||
|
|
||||||
|
You can include the `watch` flag to re-build it on save:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
rojo build -p "{project_name}.rbxm" --watch
|
||||||
|
```
|
||||||
|
|
||||||
|
For more help, check out [the Rojo documentation](https://rojo.space/docs).
|
||||||
6
assets/project-templates/plugin/default.project.json
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
{
|
||||||
|
"name": "{project_name}",
|
||||||
|
"tree": {
|
||||||
|
"$path": "src"
|
||||||
|
}
|
||||||
|
}
|
||||||
5
assets/project-templates/plugin/gitignore.txt
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
# Plugin model files
|
||||||
|
/{project_name}.rbxmx
|
||||||
|
/{project_name}.rbxm
|
||||||
|
|
||||||
|
sourcemap.json
|
||||||
1
assets/project-templates/plugin/src/init.server.luau
Normal file
@@ -0,0 +1 @@
|
|||||||
|
print("Hello world, from plugin!")
|
||||||
@@ -3,7 +3,7 @@ use std::path::Path;
|
|||||||
use criterion::{criterion_group, criterion_main, BatchSize, Criterion};
|
use criterion::{criterion_group, criterion_main, BatchSize, Criterion};
|
||||||
use tempfile::{tempdir, TempDir};
|
use tempfile::{tempdir, TempDir};
|
||||||
|
|
||||||
use librojo::cli::{build, BuildCommand};
|
use librojo::cli::BuildCommand;
|
||||||
|
|
||||||
pub fn benchmark_small_place(c: &mut Criterion) {
|
pub fn benchmark_small_place(c: &mut Criterion) {
|
||||||
bench_build_place(c, "Small Place", "test-projects/benchmark_small_place")
|
bench_build_place(c, "Small Place", "test-projects/benchmark_small_place")
|
||||||
@@ -20,7 +20,7 @@ fn bench_build_place(c: &mut Criterion, name: &str, path: &str) {
|
|||||||
group.bench_function("build", |b| {
|
group.bench_function("build", |b| {
|
||||||
b.iter_batched(
|
b.iter_batched(
|
||||||
|| place_setup(path),
|
|| place_setup(path),
|
||||||
|(_dir, options)| build(options).unwrap(),
|
|(_dir, options)| options.run().unwrap(),
|
||||||
BatchSize::SmallInput,
|
BatchSize::SmallInput,
|
||||||
)
|
)
|
||||||
});
|
});
|
||||||
@@ -31,11 +31,12 @@ fn bench_build_place(c: &mut Criterion, name: &str, path: &str) {
|
|||||||
fn place_setup<P: AsRef<Path>>(input_path: P) -> (TempDir, BuildCommand) {
|
fn place_setup<P: AsRef<Path>>(input_path: P) -> (TempDir, BuildCommand) {
|
||||||
let dir = tempdir().unwrap();
|
let dir = tempdir().unwrap();
|
||||||
let input = input_path.as_ref().to_path_buf();
|
let input = input_path.as_ref().to_path_buf();
|
||||||
let output = dir.path().join("output.rbxlx");
|
let output = Some(dir.path().join("output.rbxlx"));
|
||||||
|
|
||||||
let options = BuildCommand {
|
let options = BuildCommand {
|
||||||
project: input,
|
project: input,
|
||||||
watch: false,
|
watch: false,
|
||||||
|
plugin: None,
|
||||||
output,
|
output,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +0,0 @@
|
|||||||
#!/bin/sh
|
|
||||||
|
|
||||||
set -e
|
|
||||||
|
|
||||||
watchexec -c -w plugin "sh -c './bin/install-dev-plugin.sh'"
|
|
||||||
@@ -1,13 +0,0 @@
|
|||||||
#!/bin/sh
|
|
||||||
|
|
||||||
set -e
|
|
||||||
|
|
||||||
DIR="$( mktemp -d )"
|
|
||||||
PLUGIN_FILE="$DIR/Rojo.rbxm"
|
|
||||||
TESTEZ_FILE="$DIR/TestEZ.rbxm"
|
|
||||||
|
|
||||||
rojo build plugin -o "$PLUGIN_FILE"
|
|
||||||
rojo build plugin/testez.project.json -o "$TESTEZ_FILE"
|
|
||||||
remodel bin/mark-plugin-as-dev.lua "$PLUGIN_FILE" "$TESTEZ_FILE" 2>/dev/null
|
|
||||||
|
|
||||||
cp "$PLUGIN_FILE" "$LOCALAPPDATA/Roblox/Plugins/Rojo.rbxm"
|
|
||||||
@@ -1,5 +0,0 @@
|
|||||||
#!/bin/sh
|
|
||||||
|
|
||||||
set -e
|
|
||||||
|
|
||||||
rojo build plugin -o "$LOCALAPPDATA/Roblox/Plugins/Rojo.rbxm"
|
|
||||||
@@ -1,12 +0,0 @@
|
|||||||
local pluginPath, testezPath = ...
|
|
||||||
|
|
||||||
local plugin = remodel.readModelFile(pluginPath)[1]
|
|
||||||
local testez = remodel.readModelFile(testezPath)[1]
|
|
||||||
|
|
||||||
local marker = Instance.new("Folder")
|
|
||||||
marker.Name = "ROJO_DEV_BUILD"
|
|
||||||
marker.Parent = plugin
|
|
||||||
|
|
||||||
testez.Parent = plugin
|
|
||||||
|
|
||||||
remodel.writeModelFile(plugin, pluginPath)
|
|
||||||
@@ -1,8 +0,0 @@
|
|||||||
local pluginPath, placePath = ...
|
|
||||||
|
|
||||||
local plugin = remodel.readModelFile(pluginPath)[1]
|
|
||||||
local place = remodel.readPlaceFile(placePath)
|
|
||||||
|
|
||||||
plugin.Parent = place:GetService("ReplicatedStorage")
|
|
||||||
|
|
||||||
remodel.writePlaceFile(place, placePath)
|
|
||||||
@@ -1,6 +0,0 @@
|
|||||||
#!/bin/sh
|
|
||||||
|
|
||||||
set -e
|
|
||||||
|
|
||||||
./bin/run-cli-tests.sh
|
|
||||||
./bin/run-plugin-tests.sh
|
|
||||||
@@ -1,9 +0,0 @@
|
|||||||
#!/bin/sh
|
|
||||||
|
|
||||||
set -e
|
|
||||||
|
|
||||||
cargo test --all --locked
|
|
||||||
cargo fmt -- --check
|
|
||||||
|
|
||||||
touch src/lib.rs # Nudge Rust source to make Clippy actually check things
|
|
||||||
cargo clippy
|
|
||||||
@@ -1,16 +0,0 @@
|
|||||||
#!/bin/sh
|
|
||||||
|
|
||||||
set -e
|
|
||||||
|
|
||||||
DIR="$( mktemp -d )"
|
|
||||||
PLUGIN_FILE="$DIR/Rojo.rbxmx"
|
|
||||||
PLACE_FILE="$DIR/RojoTestPlace.rbxlx"
|
|
||||||
|
|
||||||
rojo build plugin -o "$PLUGIN_FILE"
|
|
||||||
rojo build plugin/place.project.json -o "$PLACE_FILE"
|
|
||||||
|
|
||||||
remodel bin/put-plugin-in-test-place.lua "$PLUGIN_FILE" "$PLACE_FILE"
|
|
||||||
|
|
||||||
run-in-roblox -s plugin/testBootstrap.server.lua "$PLACE_FILE"
|
|
||||||
|
|
||||||
luacheck plugin/src plugin/log plugin/http
|
|
||||||
70
build.rs
@@ -7,6 +7,7 @@ use fs_err as fs;
|
|||||||
use fs_err::File;
|
use fs_err::File;
|
||||||
use maplit::hashmap;
|
use maplit::hashmap;
|
||||||
use memofs::VfsSnapshot;
|
use memofs::VfsSnapshot;
|
||||||
|
use semver::Version;
|
||||||
|
|
||||||
fn snapshot_from_fs_path(path: &Path) -> io::Result<VfsSnapshot> {
|
fn snapshot_from_fs_path(path: &Path) -> io::Result<VfsSnapshot> {
|
||||||
println!("cargo:rerun-if-changed={}", path.display());
|
println!("cargo:rerun-if-changed={}", path.display());
|
||||||
@@ -19,9 +20,18 @@ fn snapshot_from_fs_path(path: &Path) -> io::Result<VfsSnapshot> {
|
|||||||
|
|
||||||
let file_name = entry.file_name().to_str().unwrap().to_owned();
|
let file_name = entry.file_name().to_str().unwrap().to_owned();
|
||||||
|
|
||||||
|
if file_name.starts_with(".git") {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
// We can skip any TestEZ test files since they aren't necessary for
|
// We can skip any TestEZ test files since they aren't necessary for
|
||||||
// the plugin to run.
|
// the plugin to run.
|
||||||
if file_name.ends_with(".spec.lua") {
|
if file_name.ends_with(".spec.lua") || file_name.ends_with(".spec.luau") {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ignore images in msgpack-luau because they aren't UTF-8 encoded.
|
||||||
|
if file_name.ends_with(".png") {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -40,38 +50,44 @@ fn snapshot_from_fs_path(path: &Path) -> io::Result<VfsSnapshot> {
|
|||||||
fn main() -> Result<(), anyhow::Error> {
|
fn main() -> Result<(), anyhow::Error> {
|
||||||
let out_dir = env::var_os("OUT_DIR").unwrap();
|
let out_dir = env::var_os("OUT_DIR").unwrap();
|
||||||
|
|
||||||
let root_dir = env::var_os("CARGO_MANIFEST_DIR").unwrap();
|
let root_dir = PathBuf::from(env::var_os("CARGO_MANIFEST_DIR").unwrap());
|
||||||
let plugin_root = PathBuf::from(root_dir).join("plugin");
|
let plugin_dir = root_dir.join("plugin");
|
||||||
|
let templates_dir = root_dir.join("assets").join("project-templates");
|
||||||
|
|
||||||
let plugin_modules = plugin_root.join("modules");
|
let our_version = Version::parse(env::var_os("CARGO_PKG_VERSION").unwrap().to_str().unwrap())?;
|
||||||
|
let plugin_version =
|
||||||
|
Version::parse(fs::read_to_string(plugin_dir.join("Version.txt"))?.trim())?;
|
||||||
|
|
||||||
let snapshot = VfsSnapshot::dir(hashmap! {
|
assert_eq!(
|
||||||
"default.project.json" => snapshot_from_fs_path(&plugin_root.join("default.project.json"))?,
|
our_version, plugin_version,
|
||||||
"fmt" => snapshot_from_fs_path(&plugin_root.join("fmt"))?,
|
"plugin version does not match Cargo version"
|
||||||
"http" => snapshot_from_fs_path(&plugin_root.join("http"))?,
|
);
|
||||||
"log" => snapshot_from_fs_path(&plugin_root.join("log"))?,
|
|
||||||
"rbx_dom_lua" => snapshot_from_fs_path(&plugin_root.join("rbx_dom_lua"))?,
|
let template_snapshot = snapshot_from_fs_path(&templates_dir)?;
|
||||||
"src" => snapshot_from_fs_path(&plugin_root.join("src"))?,
|
|
||||||
"modules" => VfsSnapshot::dir(hashmap! {
|
let plugin_snapshot = VfsSnapshot::dir(hashmap! {
|
||||||
"roact" => VfsSnapshot::dir(hashmap! {
|
"default.project.json" => snapshot_from_fs_path(&root_dir.join("plugin.project.json"))?,
|
||||||
"src" => snapshot_from_fs_path(&plugin_modules.join("roact").join("src"))?
|
"plugin" => VfsSnapshot::dir(hashmap! {
|
||||||
}),
|
"fmt" => snapshot_from_fs_path(&plugin_dir.join("fmt"))?,
|
||||||
"promise" => VfsSnapshot::dir(hashmap! {
|
"http" => snapshot_from_fs_path(&plugin_dir.join("http"))?,
|
||||||
"lib" => snapshot_from_fs_path(&plugin_modules.join("promise").join("lib"))?
|
"log" => snapshot_from_fs_path(&plugin_dir.join("log"))?,
|
||||||
}),
|
"rbx_dom_lua" => snapshot_from_fs_path(&plugin_dir.join("rbx_dom_lua"))?,
|
||||||
"t" => VfsSnapshot::dir(hashmap! {
|
"src" => snapshot_from_fs_path(&plugin_dir.join("src"))?,
|
||||||
"lib" => snapshot_from_fs_path(&plugin_modules.join("t").join("lib"))?
|
"Packages" => snapshot_from_fs_path(&plugin_dir.join("Packages"))?,
|
||||||
}),
|
"Version.txt" => snapshot_from_fs_path(&plugin_dir.join("Version.txt"))?,
|
||||||
"flipper" => VfsSnapshot::dir(hashmap! {
|
"UploadDetails.json" => snapshot_from_fs_path(&plugin_dir.join("UploadDetails.json"))?,
|
||||||
"src" => snapshot_from_fs_path(&plugin_modules.join("flipper").join("src"))?
|
|
||||||
}),
|
|
||||||
}),
|
}),
|
||||||
});
|
});
|
||||||
|
|
||||||
let out_path = Path::new(&out_dir).join("plugin.bincode");
|
let template_file = File::create(Path::new(&out_dir).join("templates.bincode"))?;
|
||||||
let out_file = File::create(&out_path)?;
|
let plugin_file = File::create(Path::new(&out_dir).join("plugin.bincode"))?;
|
||||||
|
|
||||||
bincode::serialize_into(out_file, &snapshot)?;
|
bincode::serialize_into(plugin_file, &plugin_snapshot)?;
|
||||||
|
bincode::serialize_into(template_file, &template_snapshot)?;
|
||||||
|
|
||||||
|
println!("cargo:rerun-if-changed=build/windows/rojo-manifest.rc");
|
||||||
|
println!("cargo:rerun-if-changed=build/windows/rojo.manifest");
|
||||||
|
embed_resource::compile("build/windows/rojo-manifest.rc");
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|||||||
2
build/windows/rojo-manifest.rc
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
#define RT_MANIFEST 24
|
||||||
|
1 RT_MANIFEST "rojo.manifest"
|
||||||
8
build/windows/rojo.manifest
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
<?xml version='1.0' encoding='utf-8' standalone='yes'?>
|
||||||
|
<assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0">
|
||||||
|
<application xmlns="urn:schemas-microsoft-com:asm.v3">
|
||||||
|
<windowsSettings xmlns:ws2="http://schemas.microsoft.com/SMI/2016/WindowsSettings">
|
||||||
|
<ws2:longPathAware>true</ws2:longPathAware>
|
||||||
|
</windowsSettings>
|
||||||
|
</application>
|
||||||
|
</assembly>
|
||||||
38
crates/memofs/CHANGELOG.md
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
# memofs Changelog
|
||||||
|
|
||||||
|
## Unreleased Changes
|
||||||
|
|
||||||
|
# 0.4.0 (2026-07-01)
|
||||||
|
* Added `Vfs::canonicalize`. [#1201]
|
||||||
|
* **Breaking:** `StdBackend::new` and `Vfs::new_default` now return `io::Result`, so a failure to create the filesystem watcher is reported as an error instead of panicking. The `Default` implementation for `StdBackend` has been removed as a result. [#1267]
|
||||||
|
|
||||||
|
[#1267]: https://github.com/rojo-rbx/rojo/pull/1267
|
||||||
|
|
||||||
|
## 0.3.1 (2025-11-27)
|
||||||
|
* Added `Vfs::exists`. [#1169]
|
||||||
|
* Added `create_dir` and `create_dir_all` to allow creating directories. [#937]
|
||||||
|
|
||||||
|
[#1169]: https://github.com/rojo-rbx/rojo/pull/1169
|
||||||
|
[#937]: https://github.com/rojo-rbx/rojo/pull/937
|
||||||
|
|
||||||
|
## 0.3.0 (2024-03-15)
|
||||||
|
* Changed `StdBackend` file watching component to use minimal recursive watches. [#830]
|
||||||
|
* Added `Vfs::read_to_string` and `Vfs::read_to_string_lf_normalized` [#854]
|
||||||
|
|
||||||
|
[#830]: https://github.com/rojo-rbx/rojo/pull/830
|
||||||
|
[#854]: https://github.com/rojo-rbx/rojo/pull/854
|
||||||
|
|
||||||
|
## 0.2.0 (2021-08-23)
|
||||||
|
* Updated to `crossbeam-channel` 0.5.1.
|
||||||
|
|
||||||
|
## 0.1.3 (2020-11-19)
|
||||||
|
* Added `set_watch_enabled` to `Vfs` and `VfsLock` to allow turning off file watching.
|
||||||
|
|
||||||
|
## 0.1.2 (2020-03-29)
|
||||||
|
* `VfsSnapshot` now implements Serde's `Serialize` and `Deserialize` traits.
|
||||||
|
|
||||||
|
## 0.1.1 (2020-03-18)
|
||||||
|
* Improved error messages using the [fs-err](https://crates.io/crates/fs-err) crate.
|
||||||
|
|
||||||
|
## 0.1.0 (2020-03-10)
|
||||||
|
* Initial release
|
||||||
25
crates/memofs/Cargo.toml
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
[package]
|
||||||
|
name = "memofs"
|
||||||
|
description = "Virtual filesystem with configurable backends."
|
||||||
|
version = "0.4.0"
|
||||||
|
authors = [
|
||||||
|
"Lucien Greathouse <me@lpghatguy.com>",
|
||||||
|
"Micah Reid <git@dekkonot.com>",
|
||||||
|
"Ken Loeffler <kenloef@gmail.com>",
|
||||||
|
]
|
||||||
|
edition = "2018"
|
||||||
|
readme = "README.md"
|
||||||
|
license = "MIT"
|
||||||
|
homepage = "https://github.com/rojo-rbx/rojo/tree/master/memofs"
|
||||||
|
|
||||||
|
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
crossbeam-channel = "0.5.12"
|
||||||
|
dunce = "1.0.5"
|
||||||
|
fs-err = "2.11.0"
|
||||||
|
notify = "4.0.17"
|
||||||
|
serde = { version = "1.0.197", features = ["derive"] }
|
||||||
|
|
||||||
|
[dev-dependencies]
|
||||||
|
tempfile = "3.10.1"
|
||||||
@@ -50,6 +50,12 @@ impl InMemoryFs {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Default for InMemoryFs {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self::new()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
struct InMemoryFsInner {
|
struct InMemoryFsInner {
|
||||||
entries: HashMap<PathBuf, Entry>,
|
entries: HashMap<PathBuf, Entry>,
|
||||||
@@ -151,6 +157,11 @@ impl VfsBackend for InMemoryFs {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn exists(&mut self, path: &Path) -> io::Result<bool> {
|
||||||
|
let inner = self.inner.lock().unwrap();
|
||||||
|
Ok(inner.entries.contains_key(path))
|
||||||
|
}
|
||||||
|
|
||||||
fn read_dir(&mut self, path: &Path) -> io::Result<ReadDir> {
|
fn read_dir(&mut self, path: &Path) -> io::Result<ReadDir> {
|
||||||
let inner = self.inner.lock().unwrap();
|
let inner = self.inner.lock().unwrap();
|
||||||
|
|
||||||
@@ -170,6 +181,21 @@ impl VfsBackend for InMemoryFs {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn create_dir(&mut self, path: &Path) -> io::Result<()> {
|
||||||
|
let mut inner = self.inner.lock().unwrap();
|
||||||
|
inner.load_snapshot(path.to_path_buf(), VfsSnapshot::empty_dir())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn create_dir_all(&mut self, path: &Path) -> io::Result<()> {
|
||||||
|
let mut inner = self.inner.lock().unwrap();
|
||||||
|
let mut path_buf = path.to_path_buf();
|
||||||
|
while let Some(parent) = path_buf.parent() {
|
||||||
|
inner.load_snapshot(parent.to_path_buf(), VfsSnapshot::empty_dir())?;
|
||||||
|
path_buf.pop();
|
||||||
|
}
|
||||||
|
inner.load_snapshot(path.to_path_buf(), VfsSnapshot::empty_dir())
|
||||||
|
}
|
||||||
|
|
||||||
fn remove_file(&mut self, path: &Path) -> io::Result<()> {
|
fn remove_file(&mut self, path: &Path) -> io::Result<()> {
|
||||||
let mut inner = self.inner.lock().unwrap();
|
let mut inner = self.inner.lock().unwrap();
|
||||||
|
|
||||||
@@ -206,6 +232,33 @@ impl VfsBackend for InMemoryFs {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO: We rely on Rojo to prepend cwd to any relative path before storing paths
|
||||||
|
// in MemoFS. The current implementation will error if no prepended absolute path
|
||||||
|
// is found. It really only normalizes paths within the provided path's context.
|
||||||
|
// Example: "/Users/username/project/../other/file.txt" ->
|
||||||
|
// "/Users/username/other/file.txt"
|
||||||
|
// Erroneous example: "/Users/../../other/file.txt" -> "/other/file.txt"
|
||||||
|
// This is not very robust. We should implement proper path normalization here or otherwise
|
||||||
|
// warn if we are missing context and can not fully canonicalize the path correctly.
|
||||||
|
fn canonicalize(&mut self, path: &Path) -> io::Result<PathBuf> {
|
||||||
|
let mut normalized = PathBuf::new();
|
||||||
|
for component in path.components() {
|
||||||
|
match component {
|
||||||
|
std::path::Component::ParentDir => {
|
||||||
|
normalized.pop();
|
||||||
|
}
|
||||||
|
std::path::Component::CurDir => {}
|
||||||
|
_ => normalized.push(component),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let inner = self.inner.lock().unwrap();
|
||||||
|
match inner.entries.get(&normalized) {
|
||||||
|
Some(_) => Ok(normalized),
|
||||||
|
None => not_found(&normalized),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn event_receiver(&self) -> crossbeam_channel::Receiver<VfsEvent> {
|
fn event_receiver(&self) -> crossbeam_channel::Receiver<VfsEvent> {
|
||||||
let inner = self.inner.lock().unwrap();
|
let inner = self.inner.lock().unwrap();
|
||||||
|
|
||||||
@@ -222,23 +275,17 @@ impl VfsBackend for InMemoryFs {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn must_be_file<T>(path: &Path) -> io::Result<T> {
|
fn must_be_file<T>(path: &Path) -> io::Result<T> {
|
||||||
Err(io::Error::new(
|
Err(io::Error::other(format!(
|
||||||
io::ErrorKind::Other,
|
"path {} was a directory, but must be a file",
|
||||||
format!(
|
path.display()
|
||||||
"path {} was a directory, but must be a file",
|
)))
|
||||||
path.display()
|
|
||||||
),
|
|
||||||
))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn must_be_dir<T>(path: &Path) -> io::Result<T> {
|
fn must_be_dir<T>(path: &Path) -> io::Result<T> {
|
||||||
Err(io::Error::new(
|
Err(io::Error::other(format!(
|
||||||
io::ErrorKind::Other,
|
"path {} was a file, but must be a directory",
|
||||||
format!(
|
path.display()
|
||||||
"path {} was a file, but must be a directory",
|
)))
|
||||||
path.display()
|
|
||||||
),
|
|
||||||
))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn not_found<T>(path: &Path) -> io::Result<T> {
|
fn not_found<T>(path: &Path) -> io::Result<T> {
|
||||||
@@ -22,9 +22,9 @@ mod noop_backend;
|
|||||||
mod snapshot;
|
mod snapshot;
|
||||||
mod std_backend;
|
mod std_backend;
|
||||||
|
|
||||||
use std::io;
|
|
||||||
use std::path::{Path, PathBuf};
|
use std::path::{Path, PathBuf};
|
||||||
use std::sync::{Arc, Mutex, MutexGuard};
|
use std::sync::{Arc, Mutex, MutexGuard};
|
||||||
|
use std::{io, str};
|
||||||
|
|
||||||
pub use in_memory_fs::InMemoryFs;
|
pub use in_memory_fs::InMemoryFs;
|
||||||
pub use noop_backend::NoopBackend;
|
pub use noop_backend::NoopBackend;
|
||||||
@@ -70,10 +70,14 @@ impl<T> IoResultExt<T> for io::Result<T> {
|
|||||||
pub trait VfsBackend: sealed::Sealed + Send + 'static {
|
pub trait VfsBackend: sealed::Sealed + Send + 'static {
|
||||||
fn read(&mut self, path: &Path) -> io::Result<Vec<u8>>;
|
fn read(&mut self, path: &Path) -> io::Result<Vec<u8>>;
|
||||||
fn write(&mut self, path: &Path, data: &[u8]) -> io::Result<()>;
|
fn write(&mut self, path: &Path, data: &[u8]) -> io::Result<()>;
|
||||||
|
fn exists(&mut self, path: &Path) -> io::Result<bool>;
|
||||||
fn read_dir(&mut self, path: &Path) -> io::Result<ReadDir>;
|
fn read_dir(&mut self, path: &Path) -> io::Result<ReadDir>;
|
||||||
|
fn create_dir(&mut self, path: &Path) -> io::Result<()>;
|
||||||
|
fn create_dir_all(&mut self, path: &Path) -> io::Result<()>;
|
||||||
fn metadata(&mut self, path: &Path) -> io::Result<Metadata>;
|
fn metadata(&mut self, path: &Path) -> io::Result<Metadata>;
|
||||||
fn remove_file(&mut self, path: &Path) -> io::Result<()>;
|
fn remove_file(&mut self, path: &Path) -> io::Result<()>;
|
||||||
fn remove_dir_all(&mut self, path: &Path) -> io::Result<()>;
|
fn remove_dir_all(&mut self, path: &Path) -> io::Result<()>;
|
||||||
|
fn canonicalize(&mut self, path: &Path) -> io::Result<PathBuf>;
|
||||||
|
|
||||||
fn event_receiver(&self) -> crossbeam_channel::Receiver<VfsEvent>;
|
fn event_receiver(&self) -> crossbeam_channel::Receiver<VfsEvent>;
|
||||||
fn watch(&mut self, path: &Path) -> io::Result<()>;
|
fn watch(&mut self, path: &Path) -> io::Result<()>;
|
||||||
@@ -155,6 +159,29 @@ impl VfsInner {
|
|||||||
Ok(Arc::new(contents))
|
Ok(Arc::new(contents))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn read_to_string<P: AsRef<Path>>(&mut self, path: P) -> io::Result<Arc<String>> {
|
||||||
|
let path = path.as_ref();
|
||||||
|
let contents = self.backend.read(path)?;
|
||||||
|
|
||||||
|
if self.watch_enabled {
|
||||||
|
self.backend.watch(path)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
let contents_str = str::from_utf8(&contents).map_err(|_| {
|
||||||
|
io::Error::new(
|
||||||
|
io::ErrorKind::InvalidData,
|
||||||
|
format!("File was not valid UTF-8: {}", path.display()),
|
||||||
|
)
|
||||||
|
})?;
|
||||||
|
|
||||||
|
Ok(Arc::new(contents_str.into()))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn exists<P: AsRef<Path>>(&mut self, path: P) -> io::Result<bool> {
|
||||||
|
let path = path.as_ref();
|
||||||
|
self.backend.exists(path)
|
||||||
|
}
|
||||||
|
|
||||||
fn write<P: AsRef<Path>, C: AsRef<[u8]>>(&mut self, path: P, contents: C) -> io::Result<()> {
|
fn write<P: AsRef<Path>, C: AsRef<[u8]>>(&mut self, path: P, contents: C) -> io::Result<()> {
|
||||||
let path = path.as_ref();
|
let path = path.as_ref();
|
||||||
let contents = contents.as_ref();
|
let contents = contents.as_ref();
|
||||||
@@ -172,6 +199,16 @@ impl VfsInner {
|
|||||||
Ok(dir)
|
Ok(dir)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn create_dir<P: AsRef<Path>>(&mut self, path: P) -> io::Result<()> {
|
||||||
|
let path = path.as_ref();
|
||||||
|
self.backend.create_dir(path)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn create_dir_all<P: AsRef<Path>>(&mut self, path: P) -> io::Result<()> {
|
||||||
|
let path = path.as_ref();
|
||||||
|
self.backend.create_dir_all(path)
|
||||||
|
}
|
||||||
|
|
||||||
fn remove_file<P: AsRef<Path>>(&mut self, path: P) -> io::Result<()> {
|
fn remove_file<P: AsRef<Path>>(&mut self, path: P) -> io::Result<()> {
|
||||||
let path = path.as_ref();
|
let path = path.as_ref();
|
||||||
let _ = self.backend.unwatch(path);
|
let _ = self.backend.unwatch(path);
|
||||||
@@ -189,16 +226,18 @@ impl VfsInner {
|
|||||||
self.backend.metadata(path)
|
self.backend.metadata(path)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn canonicalize<P: AsRef<Path>>(&mut self, path: P) -> io::Result<PathBuf> {
|
||||||
|
let path = path.as_ref();
|
||||||
|
self.backend.canonicalize(path)
|
||||||
|
}
|
||||||
|
|
||||||
fn event_receiver(&self) -> crossbeam_channel::Receiver<VfsEvent> {
|
fn event_receiver(&self) -> crossbeam_channel::Receiver<VfsEvent> {
|
||||||
self.backend.event_receiver()
|
self.backend.event_receiver()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn commit_event(&mut self, event: &VfsEvent) -> io::Result<()> {
|
fn commit_event(&mut self, event: &VfsEvent) -> io::Result<()> {
|
||||||
match event {
|
if let VfsEvent::Remove(path) = event {
|
||||||
VfsEvent::Remove(path) => {
|
let _ = self.backend.unwatch(path);
|
||||||
let _ = self.backend.unwatch(&path);
|
|
||||||
}
|
|
||||||
_ => {}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
@@ -216,8 +255,11 @@ pub struct Vfs {
|
|||||||
|
|
||||||
impl Vfs {
|
impl Vfs {
|
||||||
/// Creates a new `Vfs` with the default backend, `StdBackend`.
|
/// Creates a new `Vfs` with the default backend, `StdBackend`.
|
||||||
pub fn new_default() -> Self {
|
///
|
||||||
Self::new(StdBackend::new())
|
/// Returns an error if the filesystem watcher could not be initialized,
|
||||||
|
/// which can happen in restricted or sandboxed environments.
|
||||||
|
pub fn new_default() -> io::Result<Self> {
|
||||||
|
Ok(Self::new(StdBackend::new()?))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Creates a new `Vfs` with the given backend.
|
/// Creates a new `Vfs` with the given backend.
|
||||||
@@ -261,6 +303,33 @@ impl Vfs {
|
|||||||
self.inner.lock().unwrap().read(path)
|
self.inner.lock().unwrap().read(path)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Read a file from the VFS (or from the underlying backend if it isn't
|
||||||
|
/// resident) into a string.
|
||||||
|
///
|
||||||
|
/// Roughly equivalent to [`std::fs::read_to_string`][std::fs::read_to_string].
|
||||||
|
///
|
||||||
|
/// [std::fs::read_to_string]: https://doc.rust-lang.org/stable/std/fs/fn.read_to_string.html
|
||||||
|
#[inline]
|
||||||
|
pub fn read_to_string<P: AsRef<Path>>(&self, path: P) -> io::Result<Arc<String>> {
|
||||||
|
let path = path.as_ref();
|
||||||
|
self.inner.lock().unwrap().read_to_string(path)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Read a file from the VFS (or the underlying backend if it isn't
|
||||||
|
/// resident) into a string, and normalize its line endings to LF.
|
||||||
|
///
|
||||||
|
/// Roughly equivalent to [`std::fs::read_to_string`][std::fs::read_to_string], but also performs
|
||||||
|
/// line ending normalization.
|
||||||
|
///
|
||||||
|
/// [std::fs::read_to_string]: https://doc.rust-lang.org/stable/std/fs/fn.read_to_string.html
|
||||||
|
#[inline]
|
||||||
|
pub fn read_to_string_lf_normalized<P: AsRef<Path>>(&self, path: P) -> io::Result<Arc<String>> {
|
||||||
|
let path = path.as_ref();
|
||||||
|
let contents = self.inner.lock().unwrap().read_to_string(path)?;
|
||||||
|
|
||||||
|
Ok(contents.replace("\r\n", "\n").into())
|
||||||
|
}
|
||||||
|
|
||||||
/// Write a file to the VFS and the underlying backend.
|
/// Write a file to the VFS and the underlying backend.
|
||||||
///
|
///
|
||||||
/// Roughly equivalent to [`std::fs::write`][std::fs::write].
|
/// Roughly equivalent to [`std::fs::write`][std::fs::write].
|
||||||
@@ -284,6 +353,42 @@ impl Vfs {
|
|||||||
self.inner.lock().unwrap().read_dir(path)
|
self.inner.lock().unwrap().read_dir(path)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Return whether the given path exists.
|
||||||
|
///
|
||||||
|
/// Roughly equivalent to [`std::fs::exists`][std::fs::exists].
|
||||||
|
///
|
||||||
|
/// [std::fs::exists]: https://doc.rust-lang.org/stable/std/fs/fn.exists.html
|
||||||
|
#[inline]
|
||||||
|
pub fn exists<P: AsRef<Path>>(&self, path: P) -> io::Result<bool> {
|
||||||
|
let path = path.as_ref();
|
||||||
|
self.inner.lock().unwrap().exists(path)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Creates a directory at the provided location.
|
||||||
|
///
|
||||||
|
/// Roughly equivalent to [`std::fs::create_dir`][std::fs::create_dir].
|
||||||
|
/// Similiar to that function, this function will fail if the parent of the
|
||||||
|
/// path does not exist.
|
||||||
|
///
|
||||||
|
/// [std::fs::create_dir]: https://doc.rust-lang.org/stable/std/fs/fn.create_dir.html
|
||||||
|
#[inline]
|
||||||
|
pub fn create_dir<P: AsRef<Path>>(&self, path: P) -> io::Result<()> {
|
||||||
|
let path = path.as_ref();
|
||||||
|
self.inner.lock().unwrap().create_dir(path)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Creates a directory at the provided location, recursively creating
|
||||||
|
/// all parent components if they are missing.
|
||||||
|
///
|
||||||
|
/// Roughly equivalent to [`std::fs::create_dir_all`][std::fs::create_dir_all].
|
||||||
|
///
|
||||||
|
/// [std::fs::create_dir_all]: https://doc.rust-lang.org/stable/std/fs/fn.create_dir_all.html
|
||||||
|
#[inline]
|
||||||
|
pub fn create_dir_all<P: AsRef<Path>>(&self, path: P) -> io::Result<()> {
|
||||||
|
let path = path.as_ref();
|
||||||
|
self.inner.lock().unwrap().create_dir_all(path)
|
||||||
|
}
|
||||||
|
|
||||||
/// Remove a file.
|
/// Remove a file.
|
||||||
///
|
///
|
||||||
/// Roughly equivalent to [`std::fs::remove_file`][std::fs::remove_file].
|
/// Roughly equivalent to [`std::fs::remove_file`][std::fs::remove_file].
|
||||||
@@ -317,6 +422,19 @@ impl Vfs {
|
|||||||
self.inner.lock().unwrap().metadata(path)
|
self.inner.lock().unwrap().metadata(path)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Normalize a path via the underlying backend.
|
||||||
|
///
|
||||||
|
/// Roughly equivalent to [`std::fs::canonicalize`][std::fs::canonicalize]. Relative paths are
|
||||||
|
/// resolved against the backend's current working directory (if applicable) and errors are
|
||||||
|
/// surfaced directly from the backend.
|
||||||
|
///
|
||||||
|
/// [std::fs::canonicalize]: https://doc.rust-lang.org/stable/std/fs/fn.canonicalize.html
|
||||||
|
#[inline]
|
||||||
|
pub fn canonicalize<P: AsRef<Path>>(&self, path: P) -> io::Result<PathBuf> {
|
||||||
|
let path = path.as_ref();
|
||||||
|
self.inner.lock().unwrap().canonicalize(path)
|
||||||
|
}
|
||||||
|
|
||||||
/// Retrieve a handle to the event receiver for this `Vfs`.
|
/// Retrieve a handle to the event receiver for this `Vfs`.
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn event_receiver(&self) -> crossbeam_channel::Receiver<VfsEvent> {
|
pub fn event_receiver(&self) -> crossbeam_channel::Receiver<VfsEvent> {
|
||||||
@@ -386,6 +504,31 @@ impl VfsLock<'_> {
|
|||||||
self.inner.read_dir(path)
|
self.inner.read_dir(path)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Creates a directory at the provided location.
|
||||||
|
///
|
||||||
|
/// Roughly equivalent to [`std::fs::create_dir`][std::fs::create_dir].
|
||||||
|
/// Similiar to that function, this function will fail if the parent of the
|
||||||
|
/// path does not exist.
|
||||||
|
///
|
||||||
|
/// [std::fs::create_dir]: https://doc.rust-lang.org/stable/std/fs/fn.create_dir.html
|
||||||
|
#[inline]
|
||||||
|
pub fn create_dir<P: AsRef<Path>>(&mut self, path: P) -> io::Result<()> {
|
||||||
|
let path = path.as_ref();
|
||||||
|
self.inner.create_dir(path)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Creates a directory at the provided location, recursively creating
|
||||||
|
/// all parent components if they are missing.
|
||||||
|
///
|
||||||
|
/// Roughly equivalent to [`std::fs::create_dir_all`][std::fs::create_dir_all].
|
||||||
|
///
|
||||||
|
/// [std::fs::create_dir_all]: https://doc.rust-lang.org/stable/std/fs/fn.create_dir_all.html
|
||||||
|
#[inline]
|
||||||
|
pub fn create_dir_all<P: AsRef<Path>>(&mut self, path: P) -> io::Result<()> {
|
||||||
|
let path = path.as_ref();
|
||||||
|
self.inner.create_dir_all(path)
|
||||||
|
}
|
||||||
|
|
||||||
/// Remove a file.
|
/// Remove a file.
|
||||||
///
|
///
|
||||||
/// Roughly equivalent to [`std::fs::remove_file`][std::fs::remove_file].
|
/// Roughly equivalent to [`std::fs::remove_file`][std::fs::remove_file].
|
||||||
@@ -419,6 +562,13 @@ impl VfsLock<'_> {
|
|||||||
self.inner.metadata(path)
|
self.inner.metadata(path)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Normalize a path via the underlying backend.
|
||||||
|
#[inline]
|
||||||
|
pub fn normalize<P: AsRef<Path>>(&mut self, path: P) -> io::Result<PathBuf> {
|
||||||
|
let path = path.as_ref();
|
||||||
|
self.inner.canonicalize(path)
|
||||||
|
}
|
||||||
|
|
||||||
/// Retrieve a handle to the event receiver for this `Vfs`.
|
/// Retrieve a handle to the event receiver for this `Vfs`.
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn event_receiver(&self) -> crossbeam_channel::Receiver<VfsEvent> {
|
pub fn event_receiver(&self) -> crossbeam_channel::Receiver<VfsEvent> {
|
||||||
@@ -431,3 +581,119 @@ impl VfsLock<'_> {
|
|||||||
self.inner.commit_event(event)
|
self.inner.commit_event(event)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod test {
|
||||||
|
use crate::{InMemoryFs, StdBackend, Vfs, VfsSnapshot};
|
||||||
|
use std::io;
|
||||||
|
use std::path::PathBuf;
|
||||||
|
|
||||||
|
/// https://github.com/rojo-rbx/rojo/issues/899
|
||||||
|
#[test]
|
||||||
|
fn read_to_string_lf_normalized_keeps_trailing_newline() {
|
||||||
|
let mut imfs = InMemoryFs::new();
|
||||||
|
imfs.load_snapshot("test", VfsSnapshot::file("bar\r\nfoo\r\n\r\n"))
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
let vfs = Vfs::new(imfs);
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
vfs.read_to_string_lf_normalized("test").unwrap().as_str(),
|
||||||
|
"bar\nfoo\n\n"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// https://github.com/rojo-rbx/rojo/issues/1200
|
||||||
|
#[test]
|
||||||
|
fn canonicalize_in_memory_success() {
|
||||||
|
let mut imfs = InMemoryFs::new();
|
||||||
|
let contents = "Lorem ipsum dolor sit amet.".to_string();
|
||||||
|
|
||||||
|
imfs.load_snapshot("/test/file.txt", VfsSnapshot::file(contents.to_string()))
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
let vfs = Vfs::new(imfs);
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
vfs.canonicalize("/test/nested/../file.txt").unwrap(),
|
||||||
|
PathBuf::from("/test/file.txt")
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
vfs.read_to_string(vfs.canonicalize("/test/nested/../file.txt").unwrap())
|
||||||
|
.unwrap()
|
||||||
|
.to_string(),
|
||||||
|
contents.to_string()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn canonicalize_in_memory_missing_errors() {
|
||||||
|
let imfs = InMemoryFs::new();
|
||||||
|
let vfs = Vfs::new(imfs);
|
||||||
|
|
||||||
|
let err = vfs.canonicalize("test").unwrap_err();
|
||||||
|
assert_eq!(err.kind(), io::ErrorKind::NotFound);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn canonicalize_std_backend_success() {
|
||||||
|
let contents = "Lorem ipsum dolor sit amet.".to_string();
|
||||||
|
let dir = tempfile::tempdir().unwrap();
|
||||||
|
let file_path = dir.path().join("file.txt");
|
||||||
|
fs_err::write(&file_path, contents.to_string()).unwrap();
|
||||||
|
|
||||||
|
let vfs = Vfs::new(StdBackend::new().unwrap());
|
||||||
|
let canonicalized = vfs.canonicalize(&file_path).unwrap();
|
||||||
|
assert_eq!(canonicalized, dunce::canonicalize(&file_path).unwrap());
|
||||||
|
assert_eq!(
|
||||||
|
vfs.read_to_string(&canonicalized).unwrap().to_string(),
|
||||||
|
contents.to_string()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
#[cfg(windows)]
|
||||||
|
fn canonicalize_std_backend_not_verbatim() {
|
||||||
|
use std::path::{Component, Prefix};
|
||||||
|
|
||||||
|
let dir = tempfile::tempdir().unwrap();
|
||||||
|
let file_path = dir.path().join("file.txt");
|
||||||
|
fs_err::write(&file_path, "hello").unwrap();
|
||||||
|
|
||||||
|
let vfs = Vfs::new(StdBackend::new().unwrap());
|
||||||
|
let canonicalized = vfs.canonicalize(&file_path).unwrap();
|
||||||
|
|
||||||
|
let is_verbatim = matches!(
|
||||||
|
canonicalized.components().next(),
|
||||||
|
Some(Component::Prefix(prefix)) if matches!(
|
||||||
|
prefix.kind(),
|
||||||
|
Prefix::Verbatim(_) | Prefix::VerbatimDisk(_) | Prefix::VerbatimUNC(_, _)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
assert!(
|
||||||
|
!is_verbatim,
|
||||||
|
"expected a non-verbatim path, got {:?}",
|
||||||
|
canonicalized
|
||||||
|
);
|
||||||
|
|
||||||
|
// Joining a relative parent path must preserve the `..` segment. On a
|
||||||
|
// verbatim path Rust would drop it lexically, which is the root cause
|
||||||
|
// of the bug.
|
||||||
|
let joined = canonicalized.join("..").join("sibling");
|
||||||
|
assert!(
|
||||||
|
joined.components().any(|c| c == Component::ParentDir),
|
||||||
|
"`..` should be preserved when joining onto {:?}",
|
||||||
|
canonicalized
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn canonicalize_std_backend_missing_errors() {
|
||||||
|
let dir = tempfile::tempdir().unwrap();
|
||||||
|
let file_path = dir.path().join("test");
|
||||||
|
|
||||||
|
let vfs = Vfs::new(StdBackend::new().unwrap());
|
||||||
|
let err = vfs.canonicalize(&file_path).unwrap_err();
|
||||||
|
assert_eq!(err.kind(), io::ErrorKind::NotFound);
|
||||||
|
}
|
||||||
|
}
|
||||||
74
crates/memofs/src/noop_backend.rs
Normal file
@@ -0,0 +1,74 @@
|
|||||||
|
use std::io;
|
||||||
|
use std::path::{Path, PathBuf};
|
||||||
|
|
||||||
|
use crate::{Metadata, ReadDir, VfsBackend, VfsEvent};
|
||||||
|
|
||||||
|
/// `VfsBackend` that returns an error on every operation.
|
||||||
|
#[non_exhaustive]
|
||||||
|
pub struct NoopBackend;
|
||||||
|
|
||||||
|
impl NoopBackend {
|
||||||
|
pub fn new() -> Self {
|
||||||
|
Self
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl VfsBackend for NoopBackend {
|
||||||
|
fn read(&mut self, _path: &Path) -> io::Result<Vec<u8>> {
|
||||||
|
Err(io::Error::other("NoopBackend doesn't do anything"))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn write(&mut self, _path: &Path, _data: &[u8]) -> io::Result<()> {
|
||||||
|
Err(io::Error::other("NoopBackend doesn't do anything"))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn exists(&mut self, _path: &Path) -> io::Result<bool> {
|
||||||
|
Err(io::Error::other("NoopBackend doesn't do anything"))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn read_dir(&mut self, _path: &Path) -> io::Result<ReadDir> {
|
||||||
|
Err(io::Error::other("NoopBackend doesn't do anything"))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn create_dir(&mut self, _path: &Path) -> io::Result<()> {
|
||||||
|
Err(io::Error::other("NoopBackend doesn't do anything"))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn create_dir_all(&mut self, _path: &Path) -> io::Result<()> {
|
||||||
|
Err(io::Error::other("NoopBackend doesn't do anything"))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn remove_file(&mut self, _path: &Path) -> io::Result<()> {
|
||||||
|
Err(io::Error::other("NoopBackend doesn't do anything"))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn remove_dir_all(&mut self, _path: &Path) -> io::Result<()> {
|
||||||
|
Err(io::Error::other("NoopBackend doesn't do anything"))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn metadata(&mut self, _path: &Path) -> io::Result<Metadata> {
|
||||||
|
Err(io::Error::other("NoopBackend doesn't do anything"))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn canonicalize(&mut self, _path: &Path) -> io::Result<PathBuf> {
|
||||||
|
Err(io::Error::other("NoopBackend doesn't do anything"))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn event_receiver(&self) -> crossbeam_channel::Receiver<VfsEvent> {
|
||||||
|
crossbeam_channel::never()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn watch(&mut self, _path: &Path) -> io::Result<()> {
|
||||||
|
Err(io::Error::other("NoopBackend doesn't do anything"))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn unwatch(&mut self, _path: &Path) -> io::Result<()> {
|
||||||
|
Err(io::Error::other("NoopBackend doesn't do anything"))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for NoopBackend {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self::new()
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,8 +1,8 @@
|
|||||||
use std::io;
|
use std::path::{Path, PathBuf};
|
||||||
use std::path::Path;
|
|
||||||
use std::sync::mpsc;
|
use std::sync::mpsc;
|
||||||
use std::thread;
|
use std::thread;
|
||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
|
use std::{collections::HashSet, io};
|
||||||
|
|
||||||
use crossbeam_channel::Receiver;
|
use crossbeam_channel::Receiver;
|
||||||
use notify::{watcher, DebouncedEvent, RecommendedWatcher, RecursiveMode, Watcher};
|
use notify::{watcher, DebouncedEvent, RecommendedWatcher, RecursiveMode, Watcher};
|
||||||
@@ -13,12 +13,13 @@ use crate::{DirEntry, Metadata, ReadDir, VfsBackend, VfsEvent};
|
|||||||
pub struct StdBackend {
|
pub struct StdBackend {
|
||||||
watcher: RecommendedWatcher,
|
watcher: RecommendedWatcher,
|
||||||
watcher_receiver: Receiver<VfsEvent>,
|
watcher_receiver: Receiver<VfsEvent>,
|
||||||
|
watches: HashSet<PathBuf>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl StdBackend {
|
impl StdBackend {
|
||||||
pub fn new() -> StdBackend {
|
pub fn new() -> io::Result<StdBackend> {
|
||||||
let (notify_tx, notify_rx) = mpsc::channel();
|
let (notify_tx, notify_rx) = mpsc::channel();
|
||||||
let watcher = watcher(notify_tx, Duration::from_millis(50)).unwrap();
|
let watcher = watcher(notify_tx, Duration::from_millis(50)).map_err(io::Error::other)?;
|
||||||
|
|
||||||
let (tx, rx) = crossbeam_channel::unbounded();
|
let (tx, rx) = crossbeam_channel::unbounded();
|
||||||
|
|
||||||
@@ -45,10 +46,11 @@ impl StdBackend {
|
|||||||
Result::<(), crossbeam_channel::SendError<VfsEvent>>::Ok(())
|
Result::<(), crossbeam_channel::SendError<VfsEvent>>::Ok(())
|
||||||
});
|
});
|
||||||
|
|
||||||
Self {
|
Ok(Self {
|
||||||
watcher,
|
watcher,
|
||||||
watcher_receiver: rx,
|
watcher_receiver: rx,
|
||||||
}
|
watches: HashSet::new(),
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -61,6 +63,10 @@ impl VfsBackend for StdBackend {
|
|||||||
fs_err::write(path, data)
|
fs_err::write(path, data)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn exists(&mut self, path: &Path) -> io::Result<bool> {
|
||||||
|
std::fs::exists(path)
|
||||||
|
}
|
||||||
|
|
||||||
fn read_dir(&mut self, path: &Path) -> io::Result<ReadDir> {
|
fn read_dir(&mut self, path: &Path) -> io::Result<ReadDir> {
|
||||||
let entries: Result<Vec<_>, _> = fs_err::read_dir(path)?.collect();
|
let entries: Result<Vec<_>, _> = fs_err::read_dir(path)?.collect();
|
||||||
let mut entries = entries?;
|
let mut entries = entries?;
|
||||||
@@ -76,6 +82,14 @@ impl VfsBackend for StdBackend {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn create_dir(&mut self, path: &Path) -> io::Result<()> {
|
||||||
|
fs_err::create_dir(path)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn create_dir_all(&mut self, path: &Path) -> io::Result<()> {
|
||||||
|
fs_err::create_dir_all(path)
|
||||||
|
}
|
||||||
|
|
||||||
fn remove_file(&mut self, path: &Path) -> io::Result<()> {
|
fn remove_file(&mut self, path: &Path) -> io::Result<()> {
|
||||||
fs_err::remove_file(path)
|
fs_err::remove_file(path)
|
||||||
}
|
}
|
||||||
@@ -92,19 +106,31 @@ impl VfsBackend for StdBackend {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn canonicalize(&mut self, path: &Path) -> io::Result<PathBuf> {
|
||||||
|
dunce::canonicalize(path)
|
||||||
|
}
|
||||||
|
|
||||||
fn event_receiver(&self) -> crossbeam_channel::Receiver<VfsEvent> {
|
fn event_receiver(&self) -> crossbeam_channel::Receiver<VfsEvent> {
|
||||||
self.watcher_receiver.clone()
|
self.watcher_receiver.clone()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn watch(&mut self, path: &Path) -> io::Result<()> {
|
fn watch(&mut self, path: &Path) -> io::Result<()> {
|
||||||
self.watcher
|
if self.watches.contains(path)
|
||||||
.watch(path, RecursiveMode::NonRecursive)
|
|| path
|
||||||
.map_err(|inner| io::Error::new(io::ErrorKind::Other, inner))
|
.ancestors()
|
||||||
|
.any(|ancestor| self.watches.contains(ancestor))
|
||||||
|
{
|
||||||
|
Ok(())
|
||||||
|
} else {
|
||||||
|
self.watches.insert(path.to_path_buf());
|
||||||
|
self.watcher
|
||||||
|
.watch(path, RecursiveMode::Recursive)
|
||||||
|
.map_err(io::Error::other)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn unwatch(&mut self, path: &Path) -> io::Result<()> {
|
fn unwatch(&mut self, path: &Path) -> io::Result<()> {
|
||||||
self.watcher
|
self.watches.remove(path);
|
||||||
.unwatch(path)
|
self.watcher.unwatch(path).map_err(io::Error::other)
|
||||||
.map_err(|inner| io::Error::new(io::ErrorKind::Other, inner))
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -6,5 +6,5 @@ edition = "2018"
|
|||||||
publish = false
|
publish = false
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
serde = "1.0.99"
|
serde = "1.0.197"
|
||||||
serde_yaml = "0.8.9"
|
serde_yaml = "0.8.26"
|
||||||
@@ -5,19 +5,13 @@ use serde::Serialize;
|
|||||||
/// Enables redacting any value that serializes as a string.
|
/// Enables redacting any value that serializes as a string.
|
||||||
///
|
///
|
||||||
/// Used for transforming Rojo instance IDs into something deterministic.
|
/// Used for transforming Rojo instance IDs into something deterministic.
|
||||||
|
#[derive(Default)]
|
||||||
pub struct RedactionMap {
|
pub struct RedactionMap {
|
||||||
ids: HashMap<String, usize>,
|
ids: HashMap<String, usize>,
|
||||||
last_id: usize,
|
last_id: usize,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl RedactionMap {
|
impl RedactionMap {
|
||||||
pub fn new() -> Self {
|
|
||||||
Self {
|
|
||||||
ids: HashMap::new(),
|
|
||||||
last_id: 0,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn get_redacted_value(&self, id: impl ToString) -> Option<String> {
|
pub fn get_redacted_value(&self, id: impl ToString) -> Option<String> {
|
||||||
let id = id.to_string();
|
let id = id.to_string();
|
||||||
|
|
||||||
@@ -28,6 +22,12 @@ impl RedactionMap {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Returns the numeric ID that was assigned to the provided value,
|
||||||
|
/// if one exists.
|
||||||
|
pub fn get_id_for_value(&self, value: impl ToString) -> Option<usize> {
|
||||||
|
self.ids.get(&value.to_string()).cloned()
|
||||||
|
}
|
||||||
|
|
||||||
pub fn intern(&mut self, id: impl ToString) {
|
pub fn intern(&mut self, id: impl ToString) {
|
||||||
let last_id = &mut self.last_id;
|
let last_id = &mut self.last_id;
|
||||||
|
|
||||||
@@ -1,3 +0,0 @@
|
|||||||
[tools]
|
|
||||||
rojo = { source = "rojo-rbx/rojo", version = "6.0.0-rc.3" }
|
|
||||||
run-in-roblox = { source = "rojo-rbx/run-in-roblox", version = "0.3.0" }
|
|
||||||
@@ -1,15 +0,0 @@
|
|||||||
# memofs Changelog
|
|
||||||
|
|
||||||
## Unreleased Changes
|
|
||||||
|
|
||||||
## 0.1.3 (2020-11-19)
|
|
||||||
* Added `set_watch_enabled` to `Vfs` and `VfsLock` to allow turning off file watching.
|
|
||||||
|
|
||||||
## 0.1.2 (2020-03-29)
|
|
||||||
* `VfsSnapshot` now implements Serde's `Serialize` and `Deserialize` traits.
|
|
||||||
|
|
||||||
## 0.1.1 (2020-03-18)
|
|
||||||
* Improved error messages using the [fs-err](https://crates.io/crates/fs-err) crate.
|
|
||||||
|
|
||||||
## 0.1.0 (2020-03-10)
|
|
||||||
* Initial release
|
|
||||||
@@ -1,17 +0,0 @@
|
|||||||
[package]
|
|
||||||
name = "memofs"
|
|
||||||
description = "Virtual filesystem with configurable backends."
|
|
||||||
version = "0.1.3"
|
|
||||||
authors = ["Lucien Greathouse <me@lpghatguy.com>"]
|
|
||||||
edition = "2018"
|
|
||||||
readme = "README.md"
|
|
||||||
license = "MIT"
|
|
||||||
homepage = "https://github.com/rojo-rbx/rojo/tree/master/memofs"
|
|
||||||
|
|
||||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
|
||||||
|
|
||||||
[dependencies]
|
|
||||||
crossbeam-channel = "0.4.0"
|
|
||||||
fs-err = "2.3.0"
|
|
||||||
notify = "4.0.15"
|
|
||||||
serde = { version = "1.0", features = ["derive"] }
|
|
||||||
@@ -1,76 +0,0 @@
|
|||||||
use std::io;
|
|
||||||
use std::path::Path;
|
|
||||||
|
|
||||||
use crate::{Metadata, ReadDir, VfsBackend, VfsEvent};
|
|
||||||
|
|
||||||
/// `VfsBackend` that returns an error on every operation.
|
|
||||||
#[non_exhaustive]
|
|
||||||
pub struct NoopBackend;
|
|
||||||
|
|
||||||
impl NoopBackend {
|
|
||||||
pub fn new() -> Self {
|
|
||||||
Self
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl VfsBackend for NoopBackend {
|
|
||||||
fn read(&mut self, _path: &Path) -> io::Result<Vec<u8>> {
|
|
||||||
Err(io::Error::new(
|
|
||||||
io::ErrorKind::Other,
|
|
||||||
"NoopBackend doesn't do anything",
|
|
||||||
))
|
|
||||||
}
|
|
||||||
|
|
||||||
fn write(&mut self, _path: &Path, _data: &[u8]) -> io::Result<()> {
|
|
||||||
Err(io::Error::new(
|
|
||||||
io::ErrorKind::Other,
|
|
||||||
"NoopBackend doesn't do anything",
|
|
||||||
))
|
|
||||||
}
|
|
||||||
|
|
||||||
fn read_dir(&mut self, _path: &Path) -> io::Result<ReadDir> {
|
|
||||||
Err(io::Error::new(
|
|
||||||
io::ErrorKind::Other,
|
|
||||||
"NoopBackend doesn't do anything",
|
|
||||||
))
|
|
||||||
}
|
|
||||||
|
|
||||||
fn remove_file(&mut self, _path: &Path) -> io::Result<()> {
|
|
||||||
Err(io::Error::new(
|
|
||||||
io::ErrorKind::Other,
|
|
||||||
"NoopBackend doesn't do anything",
|
|
||||||
))
|
|
||||||
}
|
|
||||||
|
|
||||||
fn remove_dir_all(&mut self, _path: &Path) -> io::Result<()> {
|
|
||||||
Err(io::Error::new(
|
|
||||||
io::ErrorKind::Other,
|
|
||||||
"NoopBackend doesn't do anything",
|
|
||||||
))
|
|
||||||
}
|
|
||||||
|
|
||||||
fn metadata(&mut self, _path: &Path) -> io::Result<Metadata> {
|
|
||||||
Err(io::Error::new(
|
|
||||||
io::ErrorKind::Other,
|
|
||||||
"NoopBackend doesn't do anything",
|
|
||||||
))
|
|
||||||
}
|
|
||||||
|
|
||||||
fn event_receiver(&self) -> crossbeam_channel::Receiver<VfsEvent> {
|
|
||||||
crossbeam_channel::never()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn watch(&mut self, _path: &Path) -> io::Result<()> {
|
|
||||||
Err(io::Error::new(
|
|
||||||
io::ErrorKind::Other,
|
|
||||||
"NoopBackend doesn't do anything",
|
|
||||||
))
|
|
||||||
}
|
|
||||||
|
|
||||||
fn unwatch(&mut self, _path: &Path) -> io::Result<()> {
|
|
||||||
Err(io::Error::new(
|
|
||||||
io::ErrorKind::Other,
|
|
||||||
"NoopBackend doesn't do anything",
|
|
||||||
))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
30
plugin.project.json
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
{
|
||||||
|
"name": "Rojo",
|
||||||
|
"tree": {
|
||||||
|
"$className": "Folder",
|
||||||
|
"Plugin": {
|
||||||
|
"$path": "plugin/src"
|
||||||
|
},
|
||||||
|
"Packages": {
|
||||||
|
"$path": "plugin/Packages",
|
||||||
|
"Log": {
|
||||||
|
"$path": "plugin/log"
|
||||||
|
},
|
||||||
|
"Http": {
|
||||||
|
"$path": "plugin/http"
|
||||||
|
},
|
||||||
|
"Fmt": {
|
||||||
|
"$path": "plugin/fmt"
|
||||||
|
},
|
||||||
|
"RbxDom": {
|
||||||
|
"$path": "plugin/rbx_dom_lua"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"Version": {
|
||||||
|
"$path": "plugin/Version.txt"
|
||||||
|
},
|
||||||
|
"UploadDetails": {
|
||||||
|
"$path": "plugin/UploadDetails.json"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
1
plugin/Packages/Flipper
Submodule
1
plugin/Packages/Highlighter
Submodule
1
plugin/Packages/Promise
Submodule
1
plugin/Packages/Roact
Submodule
1
plugin/Packages/TestEZ
Submodule
1
plugin/Packages/msgpack-luau
Submodule
1
plugin/Packages/t
Submodule
7
plugin/UploadDetails.json
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
{
|
||||||
|
"assetId": 13916111004,
|
||||||
|
"name": "Rojo",
|
||||||
|
"description": "The plugin portion of Rojo, a tool to enable professional tooling for Roblox developers.",
|
||||||
|
"creatorId": 32644114,
|
||||||
|
"creatorType": "Group"
|
||||||
|
}
|
||||||
1
plugin/Version.txt
Normal file
@@ -0,0 +1 @@
|
|||||||
|
7.7.0
|
||||||
@@ -1,33 +0,0 @@
|
|||||||
{
|
|
||||||
"name": "Rojo",
|
|
||||||
"tree": {
|
|
||||||
"$className": "Folder",
|
|
||||||
"Plugin": {
|
|
||||||
"$path": "src"
|
|
||||||
},
|
|
||||||
"Log": {
|
|
||||||
"$path": "log"
|
|
||||||
},
|
|
||||||
"Http": {
|
|
||||||
"$path": "http"
|
|
||||||
},
|
|
||||||
"Fmt": {
|
|
||||||
"$path": "fmt"
|
|
||||||
},
|
|
||||||
"RbxDom": {
|
|
||||||
"$path": "rbx_dom_lua"
|
|
||||||
},
|
|
||||||
"Roact": {
|
|
||||||
"$path": "modules/roact/src"
|
|
||||||
},
|
|
||||||
"Promise": {
|
|
||||||
"$path": "modules/promise/lib"
|
|
||||||
},
|
|
||||||
"t": {
|
|
||||||
"$path": "modules/t/lib"
|
|
||||||
},
|
|
||||||
"Flipper": {
|
|
||||||
"$path": "modules/flipper/src"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -25,7 +25,7 @@
|
|||||||
local function defaultTableDebug(buffer, input)
|
local function defaultTableDebug(buffer, input)
|
||||||
buffer:writeRaw("{")
|
buffer:writeRaw("{")
|
||||||
|
|
||||||
for key, value in pairs(input) do
|
for key, value in input do
|
||||||
buffer:write("[{:?}] = {:?}", key, value)
|
buffer:write("[{:?}] = {:?}", key, value)
|
||||||
|
|
||||||
if next(input, key) ~= nil then
|
if next(input, key) ~= nil then
|
||||||
@@ -50,7 +50,7 @@ local function defaultTableDebugExtended(buffer, input)
|
|||||||
buffer:writeLineRaw("{")
|
buffer:writeLineRaw("{")
|
||||||
buffer:indent()
|
buffer:indent()
|
||||||
|
|
||||||
for key, value in pairs(input) do
|
for key, value in input do
|
||||||
buffer:writeLine("[{:?}] = {:#?},", key, value)
|
buffer:writeLine("[{:?}] = {:#?},", key, value)
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -70,7 +70,7 @@ local function debugImpl(buffer, value, extendedForm)
|
|||||||
elseif valueType == "table" then
|
elseif valueType == "table" then
|
||||||
local valueMeta = getmetatable(value)
|
local valueMeta = getmetatable(value)
|
||||||
|
|
||||||
if valueMeta ~= nil and valueMeta.__fmtDebug ~= nil then
|
if valueMeta ~= nil and valueMeta.__fmtDebug ~= nil then
|
||||||
-- This type implement's the metamethod we made up to line up with
|
-- This type implement's the metamethod we made up to line up with
|
||||||
-- Rust's 'Debug' trait.
|
-- Rust's 'Debug' trait.
|
||||||
|
|
||||||
@@ -242,4 +242,4 @@ return {
|
|||||||
debugOutputBuffer = debugOutputBuffer,
|
debugOutputBuffer = debugOutputBuffer,
|
||||||
fmt = fmt,
|
fmt = fmt,
|
||||||
debugify = debugify,
|
debugify = debugify,
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,12 +3,12 @@ Error.__index = Error
|
|||||||
|
|
||||||
Error.Kind = {
|
Error.Kind = {
|
||||||
HttpNotEnabled = {
|
HttpNotEnabled = {
|
||||||
message = "Rojo requires HTTP access, which is not enabled.\n" ..
|
message = "Rojo requires HTTP access, which is not enabled.\n"
|
||||||
"Check your game settings, located in the 'Home' tab of Studio.",
|
.. "Check your game settings, located in the 'Home' tab of Studio.",
|
||||||
},
|
},
|
||||||
ConnectFailed = {
|
ConnectFailed = {
|
||||||
message = "Couldn't connect to the Rojo server.\n" ..
|
message = "Couldn't connect to the Rojo server.\n"
|
||||||
"Make sure the server is running — use 'rojo serve' to run it!",
|
.. "Make sure the server is running — use 'rojo serve' to run it!",
|
||||||
},
|
},
|
||||||
Timeout = {
|
Timeout = {
|
||||||
message = "HTTP request timed out.",
|
message = "HTTP request timed out.",
|
||||||
@@ -63,4 +63,13 @@ function Error.fromRobloxErrorString(message)
|
|||||||
return Error.new(Error.Kind.Unknown, message)
|
return Error.new(Error.Kind.Unknown, message)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
function Error.fromResponse(response)
|
||||||
|
local lower = (response.body or ""):lower()
|
||||||
|
if response.code == 408 or response.code == 504 or lower:find("timed? ?out") then
|
||||||
|
return Error.new(Error.Kind.Timeout)
|
||||||
|
end
|
||||||
|
|
||||||
|
return Error.new(Error.Kind.Unknown, string.format("%s: %s", tostring(response.code), tostring(response.body)))
|
||||||
|
end
|
||||||
|
|
||||||
return Error
|
return Error
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
local HttpService = game:GetService("HttpService")
|
local HttpService = game:GetService("HttpService")
|
||||||
|
|
||||||
|
local msgpack = require(script.Parent.Parent.msgpack)
|
||||||
|
|
||||||
local stringTemplate = [[
|
local stringTemplate = [[
|
||||||
Http.Response {
|
Http.Response {
|
||||||
code: %d
|
code: %d
|
||||||
@@ -31,4 +33,8 @@ function Response:json()
|
|||||||
return HttpService:JSONDecode(self.body)
|
return HttpService:JSONDecode(self.body)
|
||||||
end
|
end
|
||||||
|
|
||||||
return Response
|
function Response:msgpack()
|
||||||
|
return msgpack.decode(self.body)
|
||||||
|
end
|
||||||
|
|
||||||
|
return Response
|
||||||
|
|||||||
@@ -1,7 +1,8 @@
|
|||||||
local HttpService = game:GetService("HttpService")
|
local HttpService = game:GetService("HttpService")
|
||||||
|
|
||||||
local Promise = require(script.Parent.Promise)
|
|
||||||
local Log = require(script.Parent.Log)
|
local Log = require(script.Parent.Log)
|
||||||
|
local msgpack = require(script.Parent.msgpack)
|
||||||
|
local Promise = require(script.Parent.Promise)
|
||||||
|
|
||||||
local HttpError = require(script.Error)
|
local HttpError = require(script.Error)
|
||||||
local HttpResponse = require(script.Response)
|
local HttpResponse = require(script.Response)
|
||||||
@@ -13,6 +14,13 @@ local Http = {}
|
|||||||
Http.Error = HttpError
|
Http.Error = HttpError
|
||||||
Http.Response = HttpResponse
|
Http.Response = HttpResponse
|
||||||
|
|
||||||
|
-- Monkey patch msgpack.UInt64.new to lossily convert the low and high bits of the integer
|
||||||
|
-- to a native Luau number. We should change the upstream decoder to emit a native
|
||||||
|
-- integer, once those are live.
|
||||||
|
function msgpack.UInt64.new(mostSignificantPart: number, leastSignificantPart: number): number
|
||||||
|
return (mostSignificantPart % 2 ^ 32) * 2 ^ 32 + (leastSignificantPart % 2 ^ 32)
|
||||||
|
end
|
||||||
|
|
||||||
local function performRequest(requestParams)
|
local function performRequest(requestParams)
|
||||||
local requestId = lastRequestId + 1
|
local requestId = lastRequestId + 1
|
||||||
lastRequestId = requestId
|
lastRequestId = requestId
|
||||||
@@ -30,8 +38,13 @@ local function performRequest(requestParams)
|
|||||||
end)
|
end)
|
||||||
|
|
||||||
if success then
|
if success then
|
||||||
Log.trace("Request {} success, status code {}", requestId, response.StatusCode)
|
Log.trace("Request {} success, response {:#?}", requestId, response)
|
||||||
resolve(HttpResponse.fromRobloxResponse(response))
|
local httpResponse = HttpResponse.fromRobloxResponse(response)
|
||||||
|
if httpResponse:isSuccess() then
|
||||||
|
resolve(httpResponse)
|
||||||
|
else
|
||||||
|
reject(HttpError.fromResponse(httpResponse))
|
||||||
|
end
|
||||||
else
|
else
|
||||||
Log.trace("Request {} failure: {:?}", requestId, response)
|
Log.trace("Request {} failure: {:?}", requestId, response)
|
||||||
reject(HttpError.fromRobloxErrorString(response))
|
reject(HttpError.fromRobloxErrorString(response))
|
||||||
@@ -63,4 +76,12 @@ function Http.jsonDecode(source)
|
|||||||
return HttpService:JSONDecode(source)
|
return HttpService:JSONDecode(source)
|
||||||
end
|
end
|
||||||
|
|
||||||
return Http
|
function Http.msgpackEncode(object)
|
||||||
|
return msgpack.encode(object)
|
||||||
|
end
|
||||||
|
|
||||||
|
function Http.msgpackDecode(source)
|
||||||
|
return msgpack.decode(source)
|
||||||
|
end
|
||||||
|
|
||||||
|
return Http
|
||||||
|
|||||||